From 884dac2adca114cb32aab3b081f87d79c1a7ae27 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 13:52:48 +0200 Subject: [PATCH 001/534] feat(early): add authoritative physics store lifecycle Signed-off-by: Blovien --- build.gradle.kts | 53 +- .../impulse/core/ImpulsePlugin.java | 2 + .../PhysicsStoreEarlyPluginProbe.java | 69 +++ impulse-early-plugin/build.gradle.kts | 15 + .../universe/world/storage/PhysicsStore.java | 96 +++ .../early/PhysicsStoreEarlyTransformer.java | 566 ++++++++++++++++++ .../impulse/early/PhysicsStoreHooks.java | 42 ++ ...pixel.hytale.plugin.early.ClassTransformer | 1 + settings.gradle.kts | 1 + 9 files changed, 840 insertions(+), 5 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java create mode 100644 impulse-early-plugin/build.gradle.kts create mode 100644 impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java create mode 100644 impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java create mode 100644 impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java create mode 100644 impulse-early-plugin/src/main/resources/META-INF/services/com.hypixel.hytale.plugin.early.ClassTransformer diff --git a/build.gradle.kts b/build.gradle.kts index a5b73db7..bc02fddd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.gradle.process.CommandLineArgumentProvider plugins { alias(libs.plugins.hytale.workspace) @@ -66,6 +67,10 @@ subprojects { val backendProjectPaths = setOf(":impulse-bullet", ":impulse-rapier") val stagedBackendJarDirectory = layout.projectDirectory.dir("run/mods/impulse-backends") +val stagedEarlyPluginJarDirectory = layout.projectDirectory.dir("run/earlyplugins") +val physicsStoreEarlyPluginEnabled = providers.gradleProperty("impulse.physicsStoreEarlyPlugin") + .map(String::toBoolean) + .orElse(false) val hytaleToolProjectPaths = listOf( ":impulse-core", ":impulse-examples") @@ -95,6 +100,12 @@ val cleanStagedBackendJars by tasks.registering(Delete::class) { delete(stagedBackendJarDirectory) } +val cleanStagedPhysicsStoreEarlyPluginJar by tasks.registering(Delete::class) { + delete(fileTree(stagedEarlyPluginJarDirectory) { + include("impulse-early-plugin-*.jar") + }) +} + val stageBackendJarsForRunAllMods by tasks.registering(Copy::class) { group = "hytale" description = "Stages backend provider jars beside Hytale mods for runAllMods" @@ -108,6 +119,20 @@ val stageBackendJarsForRunAllMods by tasks.registering(Copy::class) { into(stagedBackendJarDirectory) } +val stagePhysicsStoreEarlyPluginJar by tasks.registering(Copy::class) { + group = "hytale" + description = "Stages the PhysicsStore early plugin for runAllMods" + + onlyIf("PhysicsStore early plugin opt-in is enabled") { + physicsStoreEarlyPluginEnabled.get() + } + dependsOn(cleanStagedPhysicsStoreEarlyPluginJar) + val earlyJar = project(":impulse-early-plugin").tasks.named("jar") + dependsOn(earlyJar) + from(earlyJar) + into(stagedEarlyPluginJarDirectory) +} + tasks.register("packageBackendPlatformJars") { group = "build" description = "Packages all per-platform and universal backend provider jars" @@ -125,7 +150,8 @@ tasks.register("headlessTest") { ":impulse-native-loader:test", ":impulse-bullet:test", ":impulse-rapier:test", - ":impulse-core:test" + ":impulse-core:test", + ":impulse-early-plugin:test" ) } @@ -133,16 +159,27 @@ tasks.register("headlessTest") { gradle.projectsEvaluated { tasks.named("runAllMods").configure { dependsOn(stageBackendJarsForRunAllMods) + if (physicsStoreEarlyPluginEnabled.get()) { + dependsOn(stagePhysicsStoreEarlyPluginJar) + } else { + dependsOn(cleanStagedPhysicsStoreEarlyPluginJar) + } val runTask = this as JavaExec runTask.standardInput = System.`in` // hytale-gradle 1.0.37 can omit project resources from run task classpaths. + val toolRuntimeClasspaths = hytaleToolProjectPaths.map { path -> + val sourceSets = project(path).extensions.getByType() + sourceSets.named("main").get().runtimeClasspath + }.toMutableList() + if (physicsStoreEarlyPluginEnabled.get()) { + val sourceSets = project(":impulse-early-plugin") + .extensions.getByType() + toolRuntimeClasspaths.add(sourceSets.named("main").get().runtimeClasspath) + } runTask.classpath = files( - hytaleToolProjectPaths.map { path -> - val sourceSets = project(path).extensions.getByType() - sourceSets.named("main").get().runtimeClasspath - }, + toolRuntimeClasspaths, project(if (coreOnlyWorkspace.get()) ":impulse-core" else ":impulse-examples") .configurations.named("vineServerJar") ) @@ -152,5 +189,11 @@ gradle.projectsEvaluated { .map { args -> args.split(Regex("\\s+")).filter { it.isNotBlank() } } .orNull ?.let { runTask.jvmArgs(it) } + + if (physicsStoreEarlyPluginEnabled.get()) { + runTask.argumentProviders.add(CommandLineArgumentProvider { + listOf("--accept-early-plugins") + }) + } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 20a067fd..04e8f5e3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -29,6 +29,7 @@ import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerLaneScheduler; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.body.PhysicsBodyIdentityCleanupSystem; import dev.hytalemodding.impulse.core.internal.systems.body.RigidBodyLifecycleCleanupSystem; @@ -141,6 +142,7 @@ public BackendId getDefaultBackendId() { @Override protected void setup() { + PhysicsStoreEarlyPluginProbe.requireAvailable(); ImpulseSubPluginRegistration.register(this); discoverBackends(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java new file mode 100644 index 00000000..be2bcf6f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java @@ -0,0 +1,69 @@ +package dev.hytalemodding.impulse.core.internal.store.integration; + +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.universe.system.WorldConfigSaveSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import java.lang.reflect.Method; +import javax.annotation.Nonnull; + +/** + * Reflection-only readiness check for the PhysicsStore early plugin transform. + */ +public final class PhysicsStoreEarlyPluginProbe { + + private static final String PHYSICS_STORE_CLASS = + "com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore"; + private static final String WORLD_LIFECYCLE_MARKER = + "impulse$physicsStoreLifecyclePatched"; + private static final String WORLD_RESOURCE_SAVE_MARKER = + "impulse$physicsStoreResourceSavePatched"; + + private PhysicsStoreEarlyPluginProbe() { + } + + public static void requireAvailable() { + Class physicsStoreClass = requireClass(PHYSICS_STORE_CLASS); + requireMethod(PluginBase.class, "getPhysicsStoreRegistry"); + Method worldAccessor = requireMethod(World.class, "getPhysicsStore"); + if (!worldAccessor.getReturnType().equals(physicsStoreClass)) { + throw new IllegalStateException("Impulse PhysicsStore early plugin installed an " + + "unexpected World.getPhysicsStore() return type: " + + worldAccessor.getReturnType().getName()); + } + requireField(World.class, WORLD_LIFECYCLE_MARKER); + requireField(WorldConfigSaveSystem.class, WORLD_RESOURCE_SAVE_MARKER); + } + + @Nonnull + private static Class requireClass(@Nonnull String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException exception) { + throw unavailable(exception); + } + } + + @Nonnull + private static Method requireMethod(@Nonnull Class owner, @Nonnull String methodName) { + try { + return owner.getMethod(methodName); + } catch (NoSuchMethodException exception) { + throw unavailable(exception); + } + } + + private static void requireField(@Nonnull Class owner, @Nonnull String fieldName) { + try { + owner.getDeclaredField(fieldName); + } catch (NoSuchFieldException exception) { + throw unavailable(exception); + } + } + + @Nonnull + private static IllegalStateException unavailable(@Nonnull ReflectiveOperationException cause) { + return new IllegalStateException("Impulse requires the PhysicsStore early plugin. " + + "Install impulse-early-plugin as a Hytale early plugin and start the server with " + + "--accept-early-plugins.", cause); + } +} diff --git a/impulse-early-plugin/build.gradle.kts b/impulse-early-plugin/build.gradle.kts new file mode 100644 index 00000000..0b80a1ee --- /dev/null +++ b/impulse-early-plugin/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + `java-library` +} + +version = rootProject.version + +val coreVineServerJar = project(":impulse-core").configurations.named("vineServerJar") +val coreVineServerJarFiles = files({ coreVineServerJar.get().files }) + +dependencies { + compileOnly(coreVineServerJarFiles) + compileOnly(libs.jsr305) + testCompileOnly(coreVineServerJarFiles) + testRuntimeOnly(coreVineServerJarFiles) +} diff --git a/impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java b/impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java new file mode 100644 index 00000000..aa87f6ac --- /dev/null +++ b/impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +import com.hypixel.hytale.codec.store.CodecKey; +import com.hypixel.hytale.codec.store.CodecStore; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PhysicsStore { + + @Nonnull + public static final ComponentRegistry REGISTRY = new ComponentRegistry<>(); + + @Nonnull + public static final CodecKey> HOLDER_CODEC_KEY = + new CodecKey<>("PhysicsHolder"); + + static { + CodecStore.STATIC.putCodecSupplier(HOLDER_CODEC_KEY, REGISTRY::getEntityCodec); + } + + @Nonnull + private final World world; + @Nonnull + private final Map> refsByUuid = new ConcurrentHashMap<>(); + @Nullable + private Store store; + + public PhysicsStore(@Nonnull World world) { + this.world = Objects.requireNonNull(world, "world"); + } + + public synchronized void start(@Nonnull IResourceStorage resourceStorage) { + Objects.requireNonNull(resourceStorage, "resourceStorage"); + Store current = store; + if (current != null && !current.isShutdown()) { + throw new IllegalStateException("PhysicsStore is already started"); + } + store = REGISTRY.addStore(this, resourceStorage, addedStore -> store = addedStore); + } + + public synchronized void shutdown() { + Store current = requireStarted(); + if (!current.isShutdown()) { + current.shutdown(); + } + store = null; + refsByUuid.clear(); + } + + @Nonnull + public Store getStore() { + return requireStarted(); + } + + @Nonnull + public World getWorld() { + return world; + } + + @Nullable + public Ref getRefFromUUID(@Nonnull UUID uuid) { + return refsByUuid.get(Objects.requireNonNull(uuid, "uuid")); + } + + public void putRefForUUID(@Nonnull UUID uuid, @Nonnull Ref ref) { + refsByUuid.put(Objects.requireNonNull(uuid, "uuid"), Objects.requireNonNull(ref, "ref")); + } + + public void removeRefForUUID(@Nonnull UUID uuid, @Nonnull Ref ref) { + refsByUuid.remove(Objects.requireNonNull(uuid, "uuid"), Objects.requireNonNull(ref, "ref")); + } + + public void clearUuidIndex() { + refsByUuid.clear(); + } + + @Nonnull + private synchronized Store requireStarted() { + Store current = store; + if (current == null || current.isShutdown()) { + throw new IllegalStateException("PhysicsStore is not started; the Impulse early plugin " + + "must transform World before core physics-store systems run"); + } + return current; + } +} diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java new file mode 100644 index 00000000..df76de64 --- /dev/null +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java @@ -0,0 +1,566 @@ +package dev.hytalemodding.impulse.early; + +import com.hypixel.hytale.plugin.early.ClassTransformer; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassElement; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.ClassTransform; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.MethodModel; +import java.lang.classfile.TypeKind; +import java.lang.classfile.instruction.FieldInstruction; +import java.lang.classfile.instruction.InvokeInstruction; +import java.lang.classfile.instruction.ReturnInstruction; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import javax.annotation.Nonnull; + +public final class PhysicsStoreEarlyTransformer implements ClassTransformer { + + private static final String PLUGIN_BASE_NAME = + "com.hypixel.hytale.server.core.plugin.PluginBase"; + private static final String WORLD_NAME = + "com.hypixel.hytale.server.core.universe.world.World"; + private static final String WORLD_CONFIG_SAVE_SYSTEM_NAME = + "com.hypixel.hytale.server.core.universe.system.WorldConfigSaveSystem"; + + private static final String PLUGIN_SHUTDOWN_TASKS_FIELD = "shutdownTasks"; + private static final String WORLD_CHUNK_STORE_FIELD = "chunkStore"; + private static final String WORLD_IS_TICKING_FIELD = "isTicking"; + private static final String WORLD_IS_PAUSED_FIELD = "isPaused"; + private static final String WORLD_STORE_FIELD = "physicsStore"; + private static final String WORLD_PATCH_MARKER_FIELD = "impulse$physicsStoreLifecyclePatched"; + private static final String WORLD_SAVE_PATCH_MARKER_FIELD = + "impulse$physicsStoreResourceSavePatched"; + private static final String PLUGIN_REGISTRY_METHOD = "getPhysicsStoreRegistry"; + private static final String WORLD_STORE_METHOD = "getPhysicsStore"; + private static final String SAVE_WORLD_CONFIG_AND_RESOURCES_METHOD = + "saveWorldConfigAndResources"; + + private static final ClassFile CLASS_FILE = ClassFile.of(); + private static final ClassDesc CD_CHUNK_STORE = + ClassDesc.of("com.hypixel.hytale.server.core.universe.world.storage.ChunkStore"); + private static final ClassDesc CD_COMPLETABLE_FUTURE = + ClassDesc.of("java.util.concurrent.CompletableFuture"); + private static final ClassDesc CD_COMPONENT_REGISTRY = + ClassDesc.of("com.hypixel.hytale.component.ComponentRegistry"); + private static final ClassDesc CD_COMPONENT_REGISTRY_PROXY = + ClassDesc.of("com.hypixel.hytale.component.ComponentRegistryProxy"); + private static final ClassDesc CD_COPY_ON_WRITE_ARRAY_LIST = + ClassDesc.of("java.util.concurrent.CopyOnWriteArrayList"); + private static final ClassDesc CD_ENTITY_STORE = + ClassDesc.of("com.hypixel.hytale.server.core.universe.world.storage.EntityStore"); + private static final ClassDesc CD_I_RESOURCE_STORAGE = + ClassDesc.of("com.hypixel.hytale.component.IResourceStorage"); + private static final ClassDesc CD_LIST = ClassDesc.of("java.util.List"); + private static final ClassDesc CD_PATH = ClassDesc.of("java.nio.file.Path"); + private static final ClassDesc CD_PHYSICS_STORE = + ClassDesc.of("com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore"); + private static final ClassDesc CD_PHYSICS_STORE_HOOKS = + ClassDesc.of("dev.hytalemodding.impulse.early.PhysicsStoreHooks"); + private static final ClassDesc CD_PLUGIN_BASE = + ClassDesc.of("com.hypixel.hytale.server.core.plugin.PluginBase"); + private static final ClassDesc CD_STORE = ClassDesc.of("com.hypixel.hytale.component.Store"); + private static final ClassDesc CD_UNIVERSE = + ClassDesc.of("com.hypixel.hytale.server.core.universe.Universe"); + private static final ClassDesc CD_WORLD = + ClassDesc.of("com.hypixel.hytale.server.core.universe.world.World"); + private static final ClassDesc CD_WORLD_CONFIG = + ClassDesc.of("com.hypixel.hytale.server.core.universe.world.WorldConfig"); + private static final ClassDesc CD_WORLD_CONFIG_PROVIDER = + ClassDesc.of("com.hypixel.hytale.server.core.universe.world.WorldConfigProvider"); + + private static final MethodTypeDesc MTD_BOOLEAN = MethodTypeDesc.of(ConstantDescs.CD_boolean); + private static final MethodTypeDesc MTD_CHUNK_STORE = MethodTypeDesc.of(CD_CHUNK_STORE); + private static final MethodTypeDesc MTD_COMPONENT_REGISTRY_PROXY = + MethodTypeDesc.of(CD_COMPONENT_REGISTRY_PROXY); + private static final MethodTypeDesc MTD_COMPONENT_REGISTRY_PROXY_INIT = + MethodTypeDesc.of(ConstantDescs.CD_void, CD_LIST, CD_COMPONENT_REGISTRY); + private static final MethodTypeDesc MTD_COMPLETABLE_FUTURE = + MethodTypeDesc.of(CD_COMPLETABLE_FUTURE); + private static final MethodTypeDesc MTD_COMPLETABLE_FUTURE_ARRAY_TO_FUTURE = + MethodTypeDesc.of(CD_COMPLETABLE_FUTURE, CD_COMPLETABLE_FUTURE.arrayType()); + private static final MethodTypeDesc MTD_ENTITY_STORE = MethodTypeDesc.of(CD_ENTITY_STORE); + private static final MethodTypeDesc MTD_PHYSICS_STORE = MethodTypeDesc.of(CD_PHYSICS_STORE); + private static final MethodTypeDesc MTD_PHYSICS_STORE_INIT = + MethodTypeDesc.of(ConstantDescs.CD_void, CD_WORLD); + private static final MethodTypeDesc MTD_PHYSICS_START = + MethodTypeDesc.of(ConstantDescs.CD_void, CD_PHYSICS_STORE, CD_I_RESOURCE_STORAGE); + private static final MethodTypeDesc MTD_PHYSICS_TICK = + MethodTypeDesc.of(ConstantDescs.CD_void, + CD_PHYSICS_STORE, + ConstantDescs.CD_float, + ConstantDescs.CD_boolean, + ConstantDescs.CD_boolean); + private static final MethodTypeDesc MTD_PHYSICS_SAVE = + MethodTypeDesc.of(CD_COMPLETABLE_FUTURE, CD_PHYSICS_STORE); + private static final MethodTypeDesc MTD_PHYSICS_SHUTDOWN = + MethodTypeDesc.of(ConstantDescs.CD_void, CD_PHYSICS_STORE); + private static final MethodTypeDesc MTD_SAVE_WORLD_CONFIG_AND_RESOURCES = + MethodTypeDesc.of(CD_COMPLETABLE_FUTURE, CD_WORLD); + private static final MethodTypeDesc MTD_STORE = MethodTypeDesc.of(CD_STORE); + private static final MethodTypeDesc MTD_UNIVERSE = MethodTypeDesc.of(CD_UNIVERSE); + private static final MethodTypeDesc MTD_VOID = MethodTypeDesc.of(ConstantDescs.CD_void); + private static final MethodTypeDesc MTD_VOID_FLOAT = + MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_float); + private static final MethodTypeDesc MTD_VOID_RESOURCE_STORAGE = + MethodTypeDesc.of(ConstantDescs.CD_void, CD_I_RESOURCE_STORAGE); + private static final MethodTypeDesc MTD_WORLD_CONFIG = MethodTypeDesc.of(CD_WORLD_CONFIG); + private static final MethodTypeDesc MTD_WORLD_CONFIG_PROVIDER = + MethodTypeDesc.of(CD_WORLD_CONFIG_PROVIDER); + private static final MethodTypeDesc MTD_WORLD_CONFIG_PROVIDER_SAVE = + MethodTypeDesc.of(CD_COMPLETABLE_FUTURE, CD_PATH, CD_WORLD_CONFIG, CD_WORLD); + private static final MethodTypeDesc MTD_PATH = MethodTypeDesc.of(CD_PATH); + + @Override + public int priority() { + return 100; + } + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + String target = transformedName != null ? transformedName : name; + if (matches(target, PLUGIN_BASE_NAME)) { + return transformPluginBase(bytes); + } + if (matches(target, WORLD_NAME)) { + return transformWorld(bytes); + } + if (matches(target, WORLD_CONFIG_SAVE_SYSTEM_NAME)) { + return transformWorldConfigSaveSystem(bytes); + } + return bytes; + } + + @Nonnull + private static byte[] transformPluginBase(@Nonnull byte[] bytes) { + ClassModel model = CLASS_FILE.parse(bytes); + if (hasMethod(model.methods(), PLUGIN_REGISTRY_METHOD, MTD_COMPONENT_REGISTRY_PROXY)) { + return bytes; + } + return CLASS_FILE.transformClass(model, ClassTransform.ACCEPT_ALL.andThen( + ClassTransform.endHandler(PhysicsStoreEarlyTransformer::addPluginRegistryAccessor))); + } + + @Nonnull + private static byte[] transformWorld(@Nonnull byte[] bytes) { + ClassModel model = CLASS_FILE.parse(bytes); + if (hasField(model.fields(), WORLD_PATCH_MARKER_FIELD, ConstantDescs.CD_boolean)) { + return bytes; + } + boolean fieldPresent = hasField(model.fields(), WORLD_STORE_FIELD, CD_PHYSICS_STORE); + boolean methodPresent = hasMethod(model.methods(), WORLD_STORE_METHOD, MTD_PHYSICS_STORE); + var initializePhysicsStore = new InitializePhysicsStoreTransform(); + var startPhysicsStore = new StartPhysicsStoreTransform(); + var tickPhysicsStore = new TickPhysicsStoreTransform(); + var shutdownPhysicsStore = new ShutdownPhysicsStoreTransform(); + ClassTransform transform = ClassTransform.ACCEPT_ALL + .andThen(ClassTransform.transformingMethodBodies( + PhysicsStoreEarlyTransformer::isWorldConstructor, + initializePhysicsStore)) + .andThen(ClassTransform.transformingMethodBodies( + PhysicsStoreEarlyTransformer::isWorldStartMethod, + startPhysicsStore)) + .andThen(ClassTransform.transformingMethodBodies( + PhysicsStoreEarlyTransformer::isWorldTickMethod, + tickPhysicsStore)) + .andThen(ClassTransform.transformingMethodBodies( + PhysicsStoreEarlyTransformer::isWorldShutdownMethod, + shutdownPhysicsStore)) + .andThen(ClassTransform.endHandler(builder -> { + if (!fieldPresent) { + builder.withField(WORLD_STORE_FIELD, CD_PHYSICS_STORE, ClassFile.ACC_PRIVATE); + } + if (!methodPresent) { + addWorldStoreAccessor(builder); + } + builder.withField(WORLD_PATCH_MARKER_FIELD, + ConstantDescs.CD_boolean, + ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC | ClassFile.ACC_FINAL + | ClassFile.ACC_SYNTHETIC); + })); + byte[] transformed = CLASS_FILE.transformClass(model, transform); + requireTransformApplied(WORLD_NAME, "constructor PhysicsStore initialization", + initializePhysicsStore.injected()); + requireTransformApplied(WORLD_NAME, "onStart PhysicsStore start hook", + startPhysicsStore.injected()); + requireTransformApplied(WORLD_NAME, "tick PhysicsStore tick hook", + tickPhysicsStore.injected()); + requireTransformApplied(WORLD_NAME, "onShutdown PhysicsStore shutdown hook", + shutdownPhysicsStore.injected()); + return transformed; + } + + @Nonnull + private static byte[] transformWorldConfigSaveSystem(@Nonnull byte[] bytes) { + ClassModel model = CLASS_FILE.parse(bytes); + if (hasField(model.fields(), WORLD_SAVE_PATCH_MARKER_FIELD, ConstantDescs.CD_boolean)) { + return bytes; + } + var replaceSave = new ReplaceSaveWorldConfigAndResourcesTransform(); + ClassTransform transform = ClassTransform.ACCEPT_ALL + .andThen(ClassTransform.transformingMethodBodies( + PhysicsStoreEarlyTransformer::isSaveWorldConfigAndResourcesMethod, + replaceSave)) + .andThen(ClassTransform.endHandler(builder -> builder.withField( + WORLD_SAVE_PATCH_MARKER_FIELD, + ConstantDescs.CD_boolean, + ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC | ClassFile.ACC_FINAL + | ClassFile.ACC_SYNTHETIC))); + byte[] transformed = CLASS_FILE.transformClass(model, transform); + requireTransformApplied(WORLD_CONFIG_SAVE_SYSTEM_NAME, + "saveWorldConfigAndResources resource save hook", + replaceSave.replaced()); + return transformed; + } + + private static void addPluginRegistryAccessor(@Nonnull ClassBuilder builder) { + builder.withMethodBody(PLUGIN_REGISTRY_METHOD, + MTD_COMPONENT_REGISTRY_PROXY, + ClassFile.ACC_PUBLIC, + code -> code.new_(CD_COMPONENT_REGISTRY_PROXY) + .dup() + .aload(0) + .getfield(CD_PLUGIN_BASE, PLUGIN_SHUTDOWN_TASKS_FIELD, CD_COPY_ON_WRITE_ARRAY_LIST) + .getstatic(CD_PHYSICS_STORE, "REGISTRY", CD_COMPONENT_REGISTRY) + .invokespecial(CD_COMPONENT_REGISTRY_PROXY, + "", + MTD_COMPONENT_REGISTRY_PROXY_INIT) + .areturn()); + } + + private static void addWorldStoreAccessor(@Nonnull ClassBuilder builder) { + builder.withMethodBody(WORLD_STORE_METHOD, + MTD_PHYSICS_STORE, + ClassFile.ACC_PUBLIC, + code -> code.aload(0) + .getfield(CD_WORLD, WORLD_STORE_FIELD, CD_PHYSICS_STORE) + .areturn()); + } + + private static final class InitializePhysicsStoreTransform implements CodeTransform { + + private boolean injected; + + @Override + public void accept(CodeBuilder builder, CodeElement element) { + if (element instanceof ReturnInstruction returnInstruction + && returnInstruction.typeKind() == TypeKind.VOID) { + emitInitializePhysicsStore(builder); + injected = true; + } + builder.with(element); + } + + boolean injected() { + return injected; + } + } + + private static final class StartPhysicsStoreTransform implements CodeTransform { + + private boolean injected; + + @Override + public void accept(CodeBuilder builder, CodeElement element) { + builder.with(element); + if (!injected && isEntityStoreStartInvoke(element)) { + emitStartPhysicsStore(builder); + injected = true; + } + } + + boolean injected() { + return injected; + } + } + + private static final class TickPhysicsStoreTransform implements CodeTransform { + + private boolean pendingChunkStore; + private boolean chunkStoreTickSeen; + private boolean injected; + + @Override + public void accept(CodeBuilder builder, CodeElement element) { + if (!injected && chunkStoreTickSeen && isWorldConsumeTaskQueueInvoke(element)) { + emitTickPhysicsStore(builder); + injected = true; + } + builder.with(element); + if (isWorldChunkStoreField(element)) { + pendingChunkStore = true; + } else if (pendingChunkStore && isStoreTickInvoke(element)) { + chunkStoreTickSeen = true; + pendingChunkStore = false; + } + } + + boolean injected() { + return injected; + } + } + + private static final class ShutdownPhysicsStoreTransform implements CodeTransform { + + private boolean injected; + + @Override + public void accept(CodeBuilder builder, CodeElement element) { + if (element instanceof ReturnInstruction returnInstruction + && returnInstruction.typeKind() == TypeKind.VOID) { + emitShutdownPhysicsStore(builder); + injected = true; + } + builder.with(element); + } + + boolean injected() { + return injected; + } + } + + private static final class ReplaceSaveWorldConfigAndResourcesTransform implements CodeTransform { + + private boolean replaced; + + @Override + public void atStart(CodeBuilder builder) { + emitSaveWorldConfigAndResources(builder); + replaced = true; + } + + @Override + public void accept(CodeBuilder builder, CodeElement element) { + } + + boolean replaced() { + return replaced; + } + } + + private static void emitInitializePhysicsStore(@Nonnull CodeBuilder code) { + code.aload(0) + .new_(CD_PHYSICS_STORE) + .dup() + .aload(0) + .invokespecial(CD_PHYSICS_STORE, "", MTD_PHYSICS_STORE_INIT) + .putfield(CD_WORLD, WORLD_STORE_FIELD, CD_PHYSICS_STORE); + } + + private static void emitStartPhysicsStore(@Nonnull CodeBuilder code) { + code.aload(0) + .getfield(CD_WORLD, WORLD_STORE_FIELD, CD_PHYSICS_STORE) + .aload(1) + .invokestatic(CD_PHYSICS_STORE_HOOKS, "start", MTD_PHYSICS_START); + } + + private static void emitTickPhysicsStore(@Nonnull CodeBuilder code) { + code.aload(0) + .getfield(CD_WORLD, WORLD_STORE_FIELD, CD_PHYSICS_STORE) + .fload(1) + .aload(0) + .getfield(CD_WORLD, WORLD_IS_TICKING_FIELD, ConstantDescs.CD_boolean) + .aload(0) + .getfield(CD_WORLD, WORLD_IS_PAUSED_FIELD, ConstantDescs.CD_boolean) + .invokestatic(CD_PHYSICS_STORE_HOOKS, "tickAfterChunk", MTD_PHYSICS_TICK); + } + + private static void emitShutdownPhysicsStore(@Nonnull CodeBuilder code) { + code.aload(0) + .getfield(CD_WORLD, WORLD_STORE_FIELD, CD_PHYSICS_STORE) + .invokestatic(CD_PHYSICS_STORE_HOOKS, "shutdown", MTD_PHYSICS_SHUTDOWN); + } + + private static void emitSaveWorldConfigAndResources(@Nonnull CodeBuilder code) { + var resourceOnly = code.newLabel(); + code.aload(0) + .invokevirtual(CD_WORLD, "getWorldConfig", MTD_WORLD_CONFIG) + .astore(1) + .aload(1) + .invokevirtual(CD_WORLD_CONFIG, "isSavingConfig", MTD_BOOLEAN) + .ifeq(resourceOnly) + .aload(1) + .invokevirtual(CD_WORLD_CONFIG, "consumeHasChanged", MTD_BOOLEAN) + .ifeq(resourceOnly) + .iconst_4() + .anewarray(CD_COMPLETABLE_FUTURE) + .dup() + .iconst_0(); + emitChunkStoreSave(code); + code.aastore() + .dup() + .iconst_1(); + emitEntityStoreSave(code); + code.aastore() + .dup() + .iconst_2(); + emitPhysicsStoreSave(code); + code.aastore() + .dup() + .iconst_3(); + emitWorldConfigSave(code); + code.aastore() + .invokestatic(CD_COMPLETABLE_FUTURE, + "allOf", + MTD_COMPLETABLE_FUTURE_ARRAY_TO_FUTURE) + .areturn() + .labelBinding(resourceOnly) + .iconst_3() + .anewarray(CD_COMPLETABLE_FUTURE) + .dup() + .iconst_0(); + emitChunkStoreSave(code); + code.aastore() + .dup() + .iconst_1(); + emitEntityStoreSave(code); + code.aastore() + .dup() + .iconst_2(); + emitPhysicsStoreSave(code); + code.aastore() + .invokestatic(CD_COMPLETABLE_FUTURE, + "allOf", + MTD_COMPLETABLE_FUTURE_ARRAY_TO_FUTURE) + .areturn(); + } + + private static void emitChunkStoreSave(@Nonnull CodeBuilder code) { + code.aload(0) + .invokevirtual(CD_WORLD, "getChunkStore", MTD_CHUNK_STORE) + .invokevirtual(CD_CHUNK_STORE, "getStore", MTD_STORE) + .invokevirtual(CD_STORE, "saveAllResources", MTD_COMPLETABLE_FUTURE); + } + + private static void emitEntityStoreSave(@Nonnull CodeBuilder code) { + code.aload(0) + .invokevirtual(CD_WORLD, "getEntityStore", MTD_ENTITY_STORE) + .invokevirtual(CD_ENTITY_STORE, "getStore", MTD_STORE) + .invokevirtual(CD_STORE, "saveAllResources", MTD_COMPLETABLE_FUTURE); + } + + private static void emitPhysicsStoreSave(@Nonnull CodeBuilder code) { + code.aload(0) + .invokevirtual(CD_WORLD, WORLD_STORE_METHOD, MTD_PHYSICS_STORE) + .invokestatic(CD_PHYSICS_STORE_HOOKS, "saveResources", MTD_PHYSICS_SAVE); + } + + private static void emitWorldConfigSave(@Nonnull CodeBuilder code) { + code.invokestatic(CD_UNIVERSE, "get", MTD_UNIVERSE) + .invokevirtual(CD_UNIVERSE, "getWorldConfigProvider", MTD_WORLD_CONFIG_PROVIDER) + .aload(0) + .invokevirtual(CD_WORLD, "getSavePath", MTD_PATH) + .aload(0) + .invokevirtual(CD_WORLD, "getWorldConfig", MTD_WORLD_CONFIG) + .aload(0) + .invokeinterface(CD_WORLD_CONFIG_PROVIDER, "save", MTD_WORLD_CONFIG_PROVIDER_SAVE); + } + + private static boolean isWorldConstructor(@Nonnull MethodModel method) { + return method.methodName().equalsString("") + && method.methodType().equalsString("(Ljava/lang/String;Ljava/nio/file/Path;" + + "Lcom/hypixel/hytale/server/core/universe/world/WorldConfig;)V"); + } + + private static boolean isWorldStartMethod(@Nonnull MethodModel method) { + return method.methodName().equalsString("onStart") + && method.methodType().equalsString("()V"); + } + + private static boolean isWorldTickMethod(@Nonnull MethodModel method) { + return method.methodName().equalsString("tick") + && method.methodType().equalsString("(F)V"); + } + + private static boolean isWorldShutdownMethod(@Nonnull MethodModel method) { + return method.methodName().equalsString("onShutdown") + && method.methodType().equalsString("()V"); + } + + private static boolean isSaveWorldConfigAndResourcesMethod(@Nonnull MethodModel method) { + return method.methodName().equalsString(SAVE_WORLD_CONFIG_AND_RESOURCES_METHOD) + && method.methodType().equalsString( + "(Lcom/hypixel/hytale/server/core/universe/world/World;)" + + "Ljava/util/concurrent/CompletableFuture;"); + } + + private static boolean isWorldChunkStoreField(@Nonnull CodeElement element) { + return element instanceof FieldInstruction instruction + && instruction.owner().asSymbol().equals(CD_WORLD) + && instruction.name().equalsString(WORLD_CHUNK_STORE_FIELD) + && instruction.typeSymbol().equals(CD_CHUNK_STORE); + } + + private static boolean isStoreTickInvoke(@Nonnull CodeElement element) { + return element instanceof InvokeInstruction instruction + && instruction.owner().asSymbol().equals(CD_STORE) + && (instruction.name().equalsString("tick") + || instruction.name().equalsString("pausedTick")) + && instruction.typeSymbol().equals(MTD_VOID_FLOAT); + } + + private static boolean isWorldConsumeTaskQueueInvoke(@Nonnull CodeElement element) { + return element instanceof InvokeInstruction instruction + && instruction.owner().asSymbol().equals(CD_WORLD) + && instruction.name().equalsString("consumeTaskQueue") + && instruction.typeSymbol().equals(MTD_VOID); + } + + private static boolean isEntityStoreStartInvoke(@Nonnull CodeElement element) { + return element instanceof InvokeInstruction instruction + && instruction.owner().asSymbol().equals(CD_ENTITY_STORE) + && instruction.name().equalsString("start") + && instruction.typeSymbol().equals(MTD_VOID_RESOURCE_STORAGE); + } + + private static boolean isChunkStoreShutdownInvoke(@Nonnull CodeElement element) { + return element instanceof InvokeInstruction instruction + && instruction.owner().asSymbol().equals(CD_CHUNK_STORE) + && instruction.name().equalsString("shutdown") + && instruction.typeSymbol().equals(MTD_VOID); + } + + private static void requireTransformApplied(@Nonnull String target, + @Nonnull String hook, + boolean applied) { + if (!applied) { + throw new IllegalStateException("Could not patch " + hook + " in " + target + + "; Impulse PhysicsStore early plugin requires the Hytale 0.6.0-pre.3 " + + "server lifecycle bytecode shape"); + } + } + + private static boolean hasField(@Nonnull Iterable elements, + @Nonnull String name, + @Nonnull ClassDesc descriptor) { + for (ClassElement element : elements) { + if (element instanceof java.lang.classfile.FieldModel field + && field.fieldName().equalsString(name) + && field.fieldType().equalsString(descriptor.descriptorString())) { + return true; + } + } + return false; + } + + private static boolean hasMethod(@Nonnull Iterable methods, + @Nonnull String name, + @Nonnull MethodTypeDesc descriptor) { + for (MethodModel method : methods) { + if (method.methodName().equalsString(name) + && method.methodType().equalsString(descriptor.descriptorString())) { + return true; + } + } + return false; + } + + private static boolean matches(@Nonnull String name, @Nonnull String target) { + return target.equals(name) || target.replace('.', '/').equals(name); + } +} diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java new file mode 100644 index 00000000..26ca5623 --- /dev/null +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java @@ -0,0 +1,42 @@ +package dev.hytalemodding.impulse.early; + +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public final class PhysicsStoreHooks { + + private PhysicsStoreHooks() { + } + + public static void start(@Nonnull PhysicsStore physicsStore, + @Nonnull IResourceStorage resourceStorage) { + Objects.requireNonNull(physicsStore, "physicsStore") + .start(Objects.requireNonNull(resourceStorage, "resourceStorage")); + } + + public static void tickAfterChunk(@Nonnull PhysicsStore physicsStore, + float dt, + boolean ticking, + boolean paused) { + Store store = Objects.requireNonNull(physicsStore, "physicsStore") + .getStore(); + if (ticking && !paused) { + store.tick(dt); + return; + } + store.pausedTick(dt); + } + + @Nonnull + public static CompletableFuture saveResources(@Nonnull PhysicsStore physicsStore) { + return Objects.requireNonNull(physicsStore, "physicsStore").getStore().saveAllResources(); + } + + public static void shutdown(@Nonnull PhysicsStore physicsStore) { + Objects.requireNonNull(physicsStore, "physicsStore").shutdown(); + } +} diff --git a/impulse-early-plugin/src/main/resources/META-INF/services/com.hypixel.hytale.plugin.early.ClassTransformer b/impulse-early-plugin/src/main/resources/META-INF/services/com.hypixel.hytale.plugin.early.ClassTransformer new file mode 100644 index 00000000..c841000d --- /dev/null +++ b/impulse-early-plugin/src/main/resources/META-INF/services/com.hypixel.hytale.plugin.early.ClassTransformer @@ -0,0 +1 @@ +dev.hytalemodding.impulse.early.PhysicsStoreEarlyTransformer diff --git a/settings.gradle.kts b/settings.gradle.kts index 650ffe02..614eaaa8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,3 +32,4 @@ include("impulse-bullet") include("impulse-rapier") include("impulse-core") include("impulse-examples") +include("impulse-early-plugin") From 44d9c2ed6d28cebfd8c3fecc28bcb327f555766f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 14:40:03 +0200 Subject: [PATCH 002/534] feat(core): add physics store runtime systems Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 25 ++ .../PhysicsIdentityIndexResource.java | 4 + .../resources/PhysicsRuntimeResource.java | 88 ++++++- .../systems/BodyBindingSystem.java | 237 ++++++++++++++++++ .../systems/ColliderBindingSystem.java | 35 +++ .../CompletedStepPublicationSystem.java | 145 +++++++++++ .../systems/IdentityIndexSystem.java | 67 +++++ .../systems/JointBindingSystem.java | 129 ++++++++++ .../systems/PersistenceCaptureSystem.java | 36 +++ .../systems/PersistenceHydrationSystem.java | 47 ++++ .../systems/PhysicsStoreSystemSupport.java | 52 ++++ .../systems/RequestDrainSystem.java | 81 ++++++ .../systems/SpaceBindingSystem.java | 104 ++++++++ .../systems/StepSubmissionSystem.java | 44 ++++ .../systems/TargetBindingSystem.java | 107 ++++++++ .../systems/TerrainColliderBindingSystem.java | 35 +++ 16 files changed, 1232 insertions(+), 4 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index fbbee32d..bedcee7c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -11,6 +11,18 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.ColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.CompletedStepPublicationSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.IdentityIndexSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.JointBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceCaptureSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceHydrationSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.RequestDrainSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TargetBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -101,6 +113,19 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setDebugResourceType(registry.registerResource( PhysicsDebugResource.class, PhysicsDebugResource::new)); + + registry.registerSystem(new RequestDrainSystem()); + registry.registerSystem(new PersistenceHydrationSystem()); + registry.registerSystem(new IdentityIndexSystem()); + registry.registerSystem(new SpaceBindingSystem()); + registry.registerSystem(new BodyBindingSystem()); + registry.registerSystem(new ColliderBindingSystem()); + registry.registerSystem(new JointBindingSystem()); + registry.registerSystem(new TerrainColliderBindingSystem()); + registry.registerSystem(new TargetBindingSystem()); + registry.registerSystem(new CompletedStepPublicationSystem()); + registry.registerSystem(new PersistenceCaptureSystem()); + registry.registerSystem(new StepSubmissionSystem()); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java index f73a2d3f..e4ad47d0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java @@ -49,6 +49,10 @@ public void removeUuid(@Nonnull UUID uuid, @Nonnull Ref ref) { refsByUuid.remove(uuid, ref); } + public void clearUuidRefs() { + refsByUuid.clear(); + } + public void putSpaceHandle(@Nonnull BackendSpaceHandle handle, @Nonnull Ref ref) { spaceRefsByHandle.put(handle.value(), ref); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index a3751576..5e61f754 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -9,9 +9,13 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.UUID; +import java.util.function.LongConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -27,11 +31,20 @@ public final class PhysicsRuntimeResource implements Resource { private final Map spaceHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map backendIdsBySpaceUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Map bodyHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map bodySpaceHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Map jointHandlesByUuid = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Int2ObjectOpenHashMap bodyHandlesBySpaceHandle = + new Int2ObjectOpenHashMap<>(); private boolean started; public PhysicsRuntimeResource() { @@ -54,7 +67,10 @@ public PhysicsBackendRuntime getRuntime(@Nonnull BackendId backendId) { return runtimesByBackend.get(backendId); } - public void putSpaceHandle(@Nonnull UUID spaceUuid, @Nonnull BackendSpaceHandle handle) { + public void putSpaceBinding(@Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle handle) { + backendIdsBySpaceUuid.put(spaceUuid, backendId); spaceHandlesByUuid.put(spaceUuid, handle); } @@ -63,12 +79,26 @@ public BackendSpaceHandle getSpaceHandle(@Nonnull UUID spaceUuid) { return spaceHandlesByUuid.get(spaceUuid); } + @Nullable + public BackendId getSpaceBackendId(@Nonnull UUID spaceUuid) { + return backendIdsBySpaceUuid.get(spaceUuid); + } + public void removeSpaceHandle(@Nonnull UUID spaceUuid) { - spaceHandlesByUuid.remove(spaceUuid); + BackendSpaceHandle removed = spaceHandlesByUuid.remove(spaceUuid); + backendIdsBySpaceUuid.remove(spaceUuid); + if (removed != null) { + bodyHandlesBySpaceHandle.remove(removed.value()); + } } - public void putBodyHandle(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle handle) { + public void putBodyHandle(@Nonnull UUID bodyUuid, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle) { bodyHandlesByUuid.put(bodyUuid, handle); + bodySpaceHandlesByUuid.put(bodyUuid, spaceHandle); + bodyHandlesBySpaceHandle.computeIfAbsent(spaceHandle.value(), _ -> new LongArrayList()) + .add(handle.value()); } @Nullable @@ -76,8 +106,23 @@ public BackendBodyHandle getBodyHandle(@Nonnull UUID bodyUuid) { return bodyHandlesByUuid.get(bodyUuid); } + @Nullable + public BackendSpaceHandle getBodySpaceHandle(@Nonnull UUID bodyUuid) { + return bodySpaceHandlesByUuid.get(bodyUuid); + } + public void removeBodyHandle(@Nonnull UUID bodyUuid) { - bodyHandlesByUuid.remove(bodyUuid); + BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); + BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); + if (removed != null && spaceHandle != null) { + LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); + if (bodyHandles != null) { + bodyHandles.rem(removed.value()); + if (bodyHandles.isEmpty()) { + bodyHandlesBySpaceHandle.remove(spaceHandle.value()); + } + } + } } public void putJointHandle(@Nonnull UUID jointUuid, @Nonnull BackendJointHandle handle) { @@ -93,11 +138,33 @@ public void removeJointHandle(@Nonnull UUID jointUuid) { jointHandlesByUuid.remove(jointUuid); } + public void forEachSpaceBinding(@Nonnull SpaceBindingConsumer consumer) { + spaceHandlesByUuid.forEach((spaceUuid, spaceHandle) -> { + BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); + PhysicsBackendRuntime runtime = backendId != null ? runtimesByBackend.get(backendId) : null; + if (backendId != null && runtime != null) { + consumer.accept(spaceUuid, backendId, spaceHandle, runtime); + } + }); + } + + public void forEachBodyHandle(@Nonnull BackendSpaceHandle spaceHandle, + @Nonnull LongConsumer consumer) { + LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); + if (bodyHandles == null) { + return; + } + bodyHandles.forEach(consumer); + } + public void clear() { runtimesByBackend.clear(); spaceHandlesByUuid.clear(); + backendIdsBySpaceUuid.clear(); bodyHandlesByUuid.clear(); + bodySpaceHandlesByUuid.clear(); jointHandlesByUuid.clear(); + bodyHandlesBySpaceHandle.clear(); started = false; } @@ -107,8 +174,12 @@ public PhysicsRuntimeResource clone() { PhysicsRuntimeResource copy = new PhysicsRuntimeResource(); copy.runtimesByBackend.putAll(runtimesByBackend); copy.spaceHandlesByUuid.putAll(spaceHandlesByUuid); + copy.backendIdsBySpaceUuid.putAll(backendIdsBySpaceUuid); copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); + copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); copy.jointHandlesByUuid.putAll(jointHandlesByUuid); + bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> + copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); copy.started = started; return copy; } @@ -117,4 +188,13 @@ public PhysicsRuntimeResource clone() { public static ResourceType getResourceType() { return PhysicsStoreTypes.runtimeResourceType(); } + + @FunctionalInterface + public interface SpaceBindingConsumer { + + void accept(@Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull PhysicsBackendRuntime runtime); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java new file mode 100644 index 00000000..6a5d2523 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -0,0 +1,237 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Creates backend bodies from authoritative PhysicsStore body rows. + */ +public final class BodyBindingSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), + new SystemDependency<>(Order.BEFORE, ColliderBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + Map collidersByBodyUuid = collectColliders(store, systemIndex); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> bindBodies(store, runtime, identity, restore, collidersByBodyUuid, chunk); + store.forEachChunk(systemIndex, collector); + } + + @Nonnull + private static Map collectColliders(@Nonnull Store store, + int systemIndex) { + Map collidersByBodyUuid = new Object2ObjectOpenHashMap<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> { + for (int index = 0; index < chunk.size(); index++) { + ColliderComponent collider = chunk.getComponent(index, + ColliderComponent.getComponentType()); + if (collider == null) { + continue; + } + UUID colliderUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (!PhysicsStoreSystemSupport.isNil(colliderUuid)) { + collidersByBodyUuid.putIfAbsent(collider.getBodyUuid(), + new ColliderRow(colliderUuid, collider)); + } + } + }; + store.forEachChunk(systemIndex, collector); + return collidersByBodyUuid; + } + + private static void bindBodies(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Map collidersByBodyUuid, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body == null) { + continue; + } + UUID bodyUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(bodyUuid) + || runtime.getBodyHandle(bodyUuid) != null) { + continue; + } + ColliderRow collider = collidersByBodyUuid.get(bodyUuid); + if (collider == null) { + restore.recordSoftSkip("Body has no collider: " + bodyUuid); + continue; + } + bindBody(store, + runtime, + identity, + restore, + chunk.getReferenceTo(index), + bodyUuid, + body, + chunk.getComponent(index, DynamicsComponent.getComponentType()), + chunk.getComponent(index, TargetComponent.getComponentType()), + collider); + } + } + + private static void bindBody(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull BodyComponent body, + @Nullable DynamicsComponent dynamics, + @Nullable TargetComponent target, + @Nonnull ColliderRow colliderRow) { + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(body.getSpaceUuid()); + if (spaceHandle == null) { + restore.recordSoftSkip("Body references unbound space: " + bodyUuid); + return; + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, body.getSpaceUuid()); + if (backendRuntime == null) { + restore.recordSoftSkip("Body references missing backend runtime: " + bodyUuid); + return; + } + ShapeComponent shape = componentByUuid(store, + identity, + colliderRow.collider().getShapeUuid(), + ShapeComponent.getComponentType()); + MaterialComponent material = componentByUuid(store, + identity, + colliderRow.collider().getMaterialUuid(), + MaterialComponent.getComponentType()); + CollisionFilterComponent filter = componentByUuid(store, + identity, + colliderRow.collider().getFilterUuid(), + CollisionFilterComponent.getComponentType()); + if (shape == null || material == null || filter == null) { + restore.recordSoftSkip("Body references incomplete collider rows: " + bodyUuid); + return; + } + DynamicsComponent bodyDynamics = dynamics != null ? dynamics : new DynamicsComponent(); + TargetComponent initialTarget = target != null ? target : new TargetComponent(); + Vector3f position = initialTarget.isActive() ? initialTarget.getPosition() : new Vector3f(); + Quaternionf rotation = initialTarget.isActive() + ? initialTarget.getRotation() + : new Quaternionf(); + PhysicsBodyType bodyType = bodyDynamics.getBodyType(); + float mass = bodyType == PhysicsBodyType.DYNAMIC ? bodyDynamics.getMass() : 0.0f; + long bodyId = backendRuntime.createBody(spaceHandle.value(), + BackendRuntimeCodes.shapeTypeCode(shape.getShapeType()), + shape.getHalfExtentX(), + shape.getHalfExtentY(), + shape.getHalfExtentZ(), + shape.getRadius(), + shape.getHalfHeight(), + BackendRuntimeCodes.axisCode(shape.getAxis()), + shape.getGroundY(), + mass, + BackendRuntimeCodes.bodyTypeCode(bodyType), + position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w); + BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); + backendRuntime.setBodyDamping(spaceHandle.value(), + bodyId, + bodyDynamics.getLinearDamping(), + bodyDynamics.getAngularDamping()); + backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, material.getFriction()); + backendRuntime.setBodyRestitution(spaceHandle.value(), bodyId, material.getRestitution()); + backendRuntime.setBodyCollisionFilter(spaceHandle.value(), + bodyId, + filter.getCollisionGroup(), + filter.getCollisionMask()); + backendRuntime.setBodySensor(spaceHandle.value(), bodyId, colliderRow.collider().isSensor()); + if (bodyDynamics.isContinuousCollisionEnabled() + && backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); + } + runtime.putBodyHandle(bodyUuid, spaceHandle, bodyHandle); + identity.putBodyHandle(bodyHandle, bodyRef); + } + + @Nullable + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID spaceUuid) { + var backendId = runtime.getSpaceBackendId(spaceUuid); + return backendId != null ? runtime.getRuntime(backendId) : null; + } + + @Nullable + private static > C componentByUuid(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID uuid, + @Nonnull com.hypixel.hytale.component.ComponentType type) { + return PhysicsStoreSystemSupport.component(store, + PhysicsStoreSystemSupport.refForUuid(identity, uuid), + type); + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + private record ColliderRow(@Nonnull UUID uuid, @Nonnull ColliderComponent collider) { + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java new file mode 100644 index 00000000..7c767839 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java @@ -0,0 +1,35 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Reserved for multi-collider and compound-shape reconciliation. + */ +public final class ColliderBindingSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, BodyBindingSystem.class), + new SystemDependency<>(Order.BEFORE, JointBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + /* + * V1 body binding creates one backend body from the first collider row. Compound collider + * updates stay explicit here so they do not silently become EntityStore authority again. + */ + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java new file mode 100644 index 00000000..28856dd3 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -0,0 +1,145 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Publishes the last completed backend state as a copied PhysicsStore snapshot frame. + */ +public final class CompletedStepPublicationSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), + new SystemDependency<>(Order.BEFORE, PersistenceCaptureSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsSnapshotResource snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()); + List bodies = new ArrayList<>(); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + backendRuntime.snapshotBodies(spaceHandle.value(), + bodyConsumer -> runtime.forEachBodyHandle(spaceHandle, bodyConsumer::accept), + (bodyId, + _, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + sleeping, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _) -> collectBodySnapshot(store, + identity, + bodies, + bodyId, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + sleeping))); + long nextSequence = snapshot.getLatestFrame().sequence() + 1L; + snapshot.publish(new PhysicsStoreSnapshotFrame(nextSequence, dt, bodies)); + } + + private static void collectBodySnapshot(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull List bodies, + long bodyId, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping) { + Ref ref = identity.getByBodyHandle(new BackendBodyHandle(bodyId)); + if (ref == null || !ref.isValid()) { + return; + } + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + BodyComponent body = store.getComponent(ref, BodyComponent.getComponentType()); + if (uuid == null || body == null) { + return; + } + bodies.add(new PhysicsStoreBodySnapshot(uuid.getUuid(), + body.getSpaceUuid(), + BackendRuntimeCodes.bodyType(bodyTypeCode), + new Vector3f(positionX, positionY, positionZ), + new Quaternionf(rotationX, rotationY, rotationZ, rotationW), + new Vector3f(linearVelocityX, linearVelocityY, linearVelocityZ), + new Vector3f(angularVelocityX, angularVelocityY, angularVelocityZ), + sleeping)); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java new file mode 100644 index 00000000..dcf8afbc --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java @@ -0,0 +1,67 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +/** + * Rebuilds durable UUID indexes for boundary lookup. + */ +public final class IdentityIndexSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class), + new SystemDependency<>(Order.BEFORE, SpaceBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.clearUuidRefs(); + store.getExternalData().clearUuidIndex(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> indexChunk(store, identity, chunk); + store.forEachChunk(systemIndex, collector); + } + + private static void indexChunk(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + UUID uuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(uuid)) { + continue; + } + Ref ref = chunk.getReferenceTo(index); + identity.putUuid(uuid, ref); + store.getExternalData().putRefForUUID(uuid, ref); + } + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java new file mode 100644 index 00000000..0d97be29 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -0,0 +1,129 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.BackendJointType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * Binds joint rows once both endpoint bodies are bound. + */ +public final class JointBindingSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, ColliderBindingSystem.class), + new SystemDependency<>(Order.BEFORE, TerrainColliderBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> bindChunk(runtime, identity, restore, chunk); + store.forEachChunk(systemIndex, collector); + } + + private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); + if (joint == null) { + continue; + } + UUID jointUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(jointUuid) + || runtime.getJointHandle(jointUuid) != null) { + continue; + } + bindJoint(runtime, identity, restore, chunk.getReferenceTo(index), jointUuid, joint); + } + } + + private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Ref jointRef, + @Nonnull UUID jointUuid, + @Nonnull JointComponent joint) { + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); + BackendBodyHandle bodyA = runtime.getBodyHandle(joint.getBodyAUuid()); + BackendBodyHandle bodyB = runtime.getBodyHandle(joint.getBodyBUuid()); + var backendId = runtime.getSpaceBackendId(joint.getSpaceUuid()); + PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; + if (spaceHandle == null || bodyA == null || bodyB == null || backendRuntime == null) { + restore.recordSoftSkip("Joint references unbound endpoint: " + jointUuid); + return; + } + Vector3f anchorA = joint.getAnchorA(); + Vector3f anchorB = joint.getAnchorB(); + Vector3f axis = joint.getAxis(); + long jointId = backendRuntime.createJoint(spaceHandle.value(), + BackendRuntimeCodes.jointTypeCode(BackendJointType.valueOf(joint.getType().name())), + bodyA.value(), + bodyB.value(), + anchorA.x, + anchorA.y, + anchorA.z, + anchorB.x, + anchorB.y, + anchorB.z, + axis.x, + axis.y, + axis.z, + joint.getSpringRestLength(), + joint.getSpringStiffness(), + joint.getSpringDamping(), + joint.getLowerLimit(), + joint.getUpperLimit(), + joint.isMotorEnabled(), + joint.getMotorTargetVelocity(), + joint.getMotorMaxForce()); + BackendJointHandle handle = new BackendJointHandle(jointId); + runtime.putJointHandle(jointUuid, handle); + identity.putJointHandle(handle, jointRef); + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java new file mode 100644 index 00000000..c66905b4 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -0,0 +1,36 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Captures serializable PhysicsStore rows into compact DTO resources. + */ +public final class PersistenceCaptureSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, CompletedStepPublicationSystem.class), + new SystemDependency<>(Order.BEFORE, StepSubmissionSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + /* + * DTO capture is intentionally after completed snapshot publication and before next-step + * submission. The capture body is filled once Worker D finishes projection/authoring + * migration so the DTO wire format is not populated from legacy EntityStore state. + */ + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java new file mode 100644 index 00000000..240f5c13 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -0,0 +1,47 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStorePreflight; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Preflights persisted PhysicsStore DTOs before backend mutation is allowed. + */ +public final class PersistenceHydrationSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, RequestDrainSystem.class), + new SystemDependency<>(Order.BEFORE, IdentityIndexSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } + PersistentPhysicsStoreResource persistent = store.getResource( + PersistentPhysicsStoreResource.getResourceType()); + PersistentPhysicsStorePreflight.Result result = persistent.preflight(); + if (!result.valid()) { + restore.markFailed(String.join("; ", result.errors())); + return; + } + restore.markComplete(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java new file mode 100644 index 00000000..182e12cc --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java @@ -0,0 +1,52 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class PhysicsStoreSystemSupport { + + static final UUID NIL_UUID = new UUID(0L, 0L); + static final ComponentType UUID_TYPE = + UuidComponent.getComponentType(); + static final Query UUID_QUERY = UUID_TYPE; + + private PhysicsStoreSystemSupport() { + } + + @Nonnull + static UUID rowUuid(@Nonnull ArchetypeChunk chunk, int index) { + UuidComponent uuid = chunk.getComponent(index, UUID_TYPE); + return uuid != null ? uuid.getUuid() : NIL_UUID; + } + + static boolean isNil(@Nonnull UUID uuid) { + return NIL_UUID.equals(uuid); + } + + @Nullable + static > C component(@Nonnull Store store, + @Nullable Ref ref, + @Nonnull ComponentType type) { + if (ref == null || !ref.isValid()) { + return null; + } + return store.getComponent(ref, type); + } + + @Nullable + static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID uuid) { + Ref ref = identity.getByUuid(uuid); + return ref != null && ref.isValid() ? ref : null; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java new file mode 100644 index 00000000..853db659 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -0,0 +1,81 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Applies copied boundary requests before backend reconciliation. + */ +public final class RequestDrainSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.BEFORE, PersistenceHydrationSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRequestQueueResource queue = store.getResource( + PhysicsRequestQueueResource.getResourceType()); + List requests = queue.drain(); + if (requests.isEmpty()) { + return; + } + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + for (PhysicsStoreRequest request : requests) { + if (request instanceof BodyTargetRequest targetRequest) { + applyTargetRequest(store, identity, restore, targetRequest); + continue; + } + if (request instanceof TerrainColliderRequest terrainRequest) { + restore.recordSoftSkip("Terrain request authoring is deferred: " + + terrainRequest.sourceKey()); + continue; + } + restore.recordSoftSkip("Unsupported PhysicsStore request " + + request.getClass().getName()); + } + } + + private static void applyTargetRequest(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull BodyTargetRequest request) { + Ref bodyRef = PhysicsStoreSystemSupport.refForUuid(identity, + request.bodyUuid()); + if (bodyRef == null) { + restore.recordSoftSkip("Target request body is missing: " + request.bodyUuid()); + return; + } + TargetComponent target = new TargetComponent(); + target.setActive(true); + target.setPosition(request.position()); + target.setRotation(request.rotation()); + target.setLinearVelocity(request.linearVelocity()); + target.setAngularVelocity(request.angularVelocity()); + store.putComponent(bodyRef, TargetComponent.getComponentType(), target); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java new file mode 100644 index 00000000..d282091d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -0,0 +1,104 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * Binds SpaceComponent rows to backend runtime spaces. + */ +public final class SpaceBindingSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), + new SystemDependency<>(Order.BEFORE, BodyBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> bindChunk(runtime, identity, chunk); + store.forEachChunk(systemIndex, collector); + } + + private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); + if (space == null) { + continue; + } + UUID spaceUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(spaceUuid) + || runtime.getSpaceHandle(spaceUuid) != null) { + continue; + } + bindSpace(runtime, identity, chunk.getReferenceTo(index), spaceUuid, space); + } + } + + private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Ref ref, + @Nonnull UUID spaceUuid, + @Nonnull SpaceComponent space) { + BackendId backendId = space.getBackendId(); + if (backendId.value().isBlank()) { + return; + } + PhysicsBackendRuntime backendRuntime = runtime.getRuntime(backendId); + if (backendRuntime == null) { + backendRuntime = Impulse.createRuntime(backendId); + runtime.putRuntime(backendId, backendRuntime); + } + BackendSpaceHandle handle = new BackendSpaceHandle(backendRuntime.createSpace(SpaceId.next())); + Vector3f gravity = space.getGravity(); + backendRuntime.setGravity(handle.value(), gravity.x, gravity.y, gravity.z); + runtime.putSpaceBinding(spaceUuid, backendId, handle); + identity.putSpaceHandle(handle, ref); + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java new file mode 100644 index 00000000..003eb249 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -0,0 +1,44 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Submits the next backend step from PhysicsStore.tick(). + */ +public final class StepSubmissionSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, PersistenceCaptureSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isPending() || restore.isFailed()) { + return; + } + float safeDt = Float.isFinite(dt) ? Math.max(dt, 0.0f) : 0.0f; + if (safeDt <= 0.0f) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + backendRuntime.step(spaceHandle.value(), safeDt)); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java new file mode 100644 index 00000000..04e8351e --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -0,0 +1,107 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Applies copied kinematic target state to bound backend bodies before step submission. + */ +public final class TargetBindingSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class), + new SystemDependency<>(Order.BEFORE, CompletedStepPublicationSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> applyTargets(runtime, chunk); + store.forEachChunk(systemIndex, collector); + } + + private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + TargetComponent target = chunk.getComponent(index, TargetComponent.getComponentType()); + if (target == null || !target.isActive()) { + continue; + } + UUID bodyUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + if (bodyHandle == null || spaceHandle == null) { + continue; + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (backendRuntime == null) { + continue; + } + Vector3f position = target.getPosition(); + Quaternionf rotation = target.getRotation(); + Vector3f linearVelocity = target.getLinearVelocity(); + Vector3f angularVelocity = target.getAngularVelocity(); + backendRuntime.setBodyTransform(spaceHandle.value(), + bodyHandle.value(), + position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w); + backendRuntime.setBodyVelocity(spaceHandle.value(), + bodyHandle.value(), + linearVelocity.x, + linearVelocity.y, + linearVelocity.z, + angularVelocity.x, + angularVelocity.y, + angularVelocity.z); + } + } + + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull BackendSpaceHandle spaceHandle) { + final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; + runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { + if (handle.value() == spaceHandle.value()) { + resolved[0] = backendRuntime; + } + }); + return resolved[0]; + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java new file mode 100644 index 00000000..6179f897 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -0,0 +1,35 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Reserved for voxel terrain payload binding owned by ChunkStore request producers. + */ +public final class TerrainColliderBindingSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, JointBindingSystem.class), + new SystemDependency<>(Order.BEFORE, TargetBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + /* + * Terrain rows currently carry source metadata but not the compact voxel payload. Worker D + * owns the request producer and payload handoff, so backend terrain creation stays here. + */ + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} From f61e4d1ab364acff959e48a131727e7aae2d7510 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 14:25:29 +0200 Subject: [PATCH 003/534] feat(core): add physics store model contracts Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 1 + .../impulse/core/ImpulsePlugin.java | 2 + .../persistence/PersistentBodyDto.java | 211 +++++++++++++ .../PersistentBodyRuntimeStateDto.java | 103 +++++++ .../persistence/PersistentColliderDto.java | 160 ++++++++++ .../persistence/PersistentJointDto.java | 244 +++++++++++++++ .../persistence/PersistentMaterialDto.java | 66 ++++ .../PersistentPhysicsStorePreflight.java | 240 +++++++++++++++ .../PersistentPhysicsStoreResource.java | 279 +++++++++++++++++ .../persistence/PersistentShapeDto.java | 173 +++++++++++ .../persistence/PersistentSpaceDto.java | 74 +++++ .../PersistentTerrainColliderDto.java | 136 +++++++++ .../PhysicsStorePersistenceValidation.java | 75 +++++ .../PhysicsStoreRegistration.java | 130 ++++++++ .../resources/PhysicsDebugResource.java | 59 ++++ .../PhysicsIdentityIndexResource.java | 101 +++++++ .../resources/PhysicsProfilingResource.java | 97 ++++++ .../PhysicsRequestQueueResource.java | 59 ++++ .../PhysicsRestoreStatusResource.java | 82 +++++ .../resources/PhysicsRuntimeResource.java | 120 ++++++++ .../resources/PhysicsSnapshotResource.java | 46 +++ .../physicsstore/PhysicsPersistentRef.java | 48 +++ .../core/plugin/physicsstore/PhysicsRef.java | 62 ++++ .../physicsstore/PhysicsStoreAccess.java | 45 +++ .../physicsstore/PhysicsStoreTypes.java | 274 +++++++++++++++++ .../components/BodyComponent.java | 97 ++++++ .../components/ColliderComponent.java | 171 +++++++++++ .../components/CollisionFilterComponent.java | 73 +++++ .../components/DynamicsComponent.java | 123 ++++++++ .../components/JointComponent.java | 284 ++++++++++++++++++ .../components/MaterialComponent.java | 68 +++++ .../components/ShapeComponent.java | 192 ++++++++++++ .../components/SpaceComponent.java | 85 ++++++ .../components/TargetComponent.java | 123 ++++++++ .../components/TerrainColliderComponent.java | 158 ++++++++++ .../components/UuidComponent.java | 58 ++++ .../requests/BodyTargetRequest.java | 65 ++++ .../requests/PhysicsStoreRequest.java | 13 + .../requests/TerrainColliderRequest.java | 58 ++++ .../snapshots/PhysicsStoreBodySnapshot.java | 55 ++++ .../snapshots/PhysicsStoreSnapshotFrame.java | 20 ++ impulse-core/src/module-info/module-info.java | 4 + 42 files changed, 4534 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentColliderDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentMaterialDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentShapeDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentTerrainColliderDto.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStorePersistenceValidation.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index 661595dd..94d42f11 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -26,6 +26,7 @@ val moduleInfoModulePath by configurations.creating { dependencies { implementation(project(":impulse-api")) + compileOnly(project(":impulse-early-plugin")) testImplementation(testFixtures(project(":impulse-api"))) testImplementation(libs.objenesis) testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 04e8f5e3..2824f2e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -29,6 +29,7 @@ import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerLaneScheduler; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.body.PhysicsBodyIdentityCleanupSystem; @@ -143,6 +144,7 @@ public BackendId getDefaultBackendId() { @Override protected void setup() { PhysicsStoreEarlyPluginProbe.requireAvailable(); + PhysicsStoreRegistration.register(this); ImpulseSubPluginRegistration.register(this); discoverBackends(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyDto.java new file mode 100644 index 00000000..1b389c20 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyDto.java @@ -0,0 +1,211 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +public final class PersistentBodyDto { + + private static final UUID[] EMPTY_UUIDS = new UUID[0]; + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentBodyDto.class, PersistentBodyDto::new) + .append(new KeyedCodec<>("BodyUuid", Codec.UUID_BINARY), + (dto, value) -> dto.bodyUuid = value, + PersistentBodyDto::getBodyUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), + (dto, value) -> dto.spaceUuid = value, + PersistentBodyDto::getSpaceUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Kind", new EnumCodec<>(PhysicsBodyKind.class), false), + (dto, value) -> dto.kind = value != null ? value : PhysicsBodyKind.BODY, + PersistentBodyDto::getKind) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("PersistenceMode", + new EnumCodec<>(PhysicsBodyPersistenceMode.class), + false), + (dto, value) -> dto.persistenceMode = value != null + ? value + : PhysicsBodyPersistenceMode.RUNTIME_ONLY, + PersistentBodyDto::getPersistenceMode) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("BodyType", new EnumCodec<>(PhysicsBodyType.class), false), + (dto, value) -> dto.bodyType = value != null ? value : PhysicsBodyType.DYNAMIC, + PersistentBodyDto::getBodyType) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Mass", Codec.FLOAT, false), + (dto, value) -> dto.mass = value != null ? value : 1.0f, + PersistentBodyDto::getMass) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted body mass must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("LinearDamping", Codec.FLOAT, false), + (dto, value) -> dto.linearDamping = value != null ? value : 0.0f, + PersistentBodyDto::getLinearDamping) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted body linear damping must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("AngularDamping", Codec.FLOAT, false), + (dto, value) -> dto.angularDamping = value != null ? value : 0.0f, + PersistentBodyDto::getAngularDamping) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted body angular damping must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("ContinuousCollision", Codec.BOOLEAN, false), + (dto, value) -> dto.continuousCollisionEnabled = value != null && value, + PersistentBodyDto::isContinuousCollisionEnabled) + .add() + .append(new KeyedCodec<>("ColliderUuids", + new ArrayCodec<>(Codec.UUID_BINARY, UUID[]::new), + false), + (dto, value) -> dto.colliderUuids = copyUuids(value), + PersistentBodyDto::getColliderUuids) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("RuntimeState", PersistentBodyRuntimeStateDto.CODEC, false), + (dto, value) -> dto.runtimeState = value != null + ? value.copy() + : new PersistentBodyRuntimeStateDto(), + PersistentBodyDto::getRuntimeState) + .addValidator(Validators.nonNull()) + .add() + .build(); + + @Nonnull + private UUID bodyUuid = new UUID(0L, 0L); + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private PhysicsBodyKind kind = PhysicsBodyKind.BODY; + @Nonnull + private PhysicsBodyPersistenceMode persistenceMode = PhysicsBodyPersistenceMode.RUNTIME_ONLY; + @Nonnull + private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; + private float mass = 1.0f; + private float linearDamping; + private float angularDamping; + private boolean continuousCollisionEnabled; + @Nonnull + private UUID[] colliderUuids = EMPTY_UUIDS; + @Nonnull + private PersistentBodyRuntimeStateDto runtimeState = new PersistentBodyRuntimeStateDto(); + + public PersistentBodyDto() { + } + + public PersistentBodyDto(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull PhysicsBodyType bodyType, + float mass, + float linearDamping, + float angularDamping, + boolean continuousCollisionEnabled, + @Nonnull UUID[] colliderUuids, + @Nonnull PersistentBodyRuntimeStateDto runtimeState) { + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.kind = Objects.requireNonNull(kind, "kind"); + this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); + this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); + this.mass = mass; + this.linearDamping = linearDamping; + this.angularDamping = angularDamping; + this.continuousCollisionEnabled = continuousCollisionEnabled; + this.colliderUuids = copyUuids(colliderUuids); + this.runtimeState = Objects.requireNonNull(runtimeState, "runtimeState").copy(); + } + + @Nonnull + public UUID getBodyUuid() { + return bodyUuid; + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + @Nonnull + public PhysicsBodyKind getKind() { + return kind; + } + + @Nonnull + public PhysicsBodyPersistenceMode getPersistenceMode() { + return persistenceMode; + } + + @Nonnull + public PhysicsBodyType getBodyType() { + return bodyType; + } + + public float getMass() { + return mass; + } + + public float getLinearDamping() { + return linearDamping; + } + + public float getAngularDamping() { + return angularDamping; + } + + public boolean isContinuousCollisionEnabled() { + return continuousCollisionEnabled; + } + + @Nonnull + public UUID[] getColliderUuids() { + return copyUuids(colliderUuids); + } + + @Nonnull + public PersistentBodyRuntimeStateDto getRuntimeState() { + return runtimeState.copy(); + } + + @Nonnull + public PersistentBodyDto copy() { + return new PersistentBodyDto(bodyUuid, + spaceUuid, + kind, + persistenceMode, + bodyType, + mass, + linearDamping, + angularDamping, + continuousCollisionEnabled, + colliderUuids, + runtimeState); + } + + @Nonnull + private static UUID[] copyUuids(UUID[] values) { + if (values == null || values.length == 0) { + return EMPTY_UUIDS; + } + return Arrays.copyOf(values, values.length); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java new file mode 100644 index 00000000..65757fc1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java @@ -0,0 +1,103 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; +import java.util.Objects; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Dynamic runtime body state overlaid from the latest completed backend snapshot. + */ +public final class PersistentBodyRuntimeStateDto { + + private static final Vector3f ZERO = new Vector3f(); + private static final Quaternionf IDENTITY = new Quaternionf(); + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentBodyRuntimeStateDto.class, PersistentBodyRuntimeStateDto::new) + .append(new KeyedCodec<>("Position", Vector3fUtil.CODEC, false), + (dto, value) -> dto.position.set(value != null ? value : ZERO), + PersistentBodyRuntimeStateDto::getPosition) + .add() + .append(new KeyedCodec<>("Rotation", ImpulseCodecs.QUATERNIONF, false), + (dto, value) -> dto.rotation.set(value != null ? value : IDENTITY), + PersistentBodyRuntimeStateDto::getRotation) + .add() + .append(new KeyedCodec<>("LinearVelocity", Vector3fUtil.CODEC, false), + (dto, value) -> dto.linearVelocity.set(value != null ? value : ZERO), + PersistentBodyRuntimeStateDto::getLinearVelocity) + .add() + .append(new KeyedCodec<>("AngularVelocity", Vector3fUtil.CODEC, false), + (dto, value) -> dto.angularVelocity.set(value != null ? value : ZERO), + PersistentBodyRuntimeStateDto::getAngularVelocity) + .add() + .append(new KeyedCodec<>("Sleeping", Codec.BOOLEAN, false), + (dto, value) -> dto.sleeping = value != null && value, + PersistentBodyRuntimeStateDto::isSleeping) + .add() + .build(); + + @Nonnull + private final Vector3f position = new Vector3f(); + @Nonnull + private final Quaternionf rotation = new Quaternionf(); + @Nonnull + private final Vector3f linearVelocity = new Vector3f(); + @Nonnull + private final Vector3f angularVelocity = new Vector3f(); + private boolean sleeping; + + public PersistentBodyRuntimeStateDto() { + } + + public PersistentBodyRuntimeStateDto(@Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + boolean sleeping) { + this.position.set(Objects.requireNonNull(position, "position")); + this.rotation.set(Objects.requireNonNull(rotation, "rotation")); + this.linearVelocity.set(Objects.requireNonNull(linearVelocity, "linearVelocity")); + this.angularVelocity.set(Objects.requireNonNull(angularVelocity, "angularVelocity")); + this.sleeping = sleeping; + } + + @Nonnull + public Vector3f getPosition() { + return new Vector3f(position); + } + + @Nonnull + public Quaternionf getRotation() { + return new Quaternionf(rotation); + } + + @Nonnull + public Vector3f getLinearVelocity() { + return new Vector3f(linearVelocity); + } + + @Nonnull + public Vector3f getAngularVelocity() { + return new Vector3f(angularVelocity); + } + + public boolean isSleeping() { + return sleeping; + } + + @Nonnull + public PersistentBodyRuntimeStateDto copy() { + return new PersistentBodyRuntimeStateDto(position, + rotation, + linearVelocity, + angularVelocity, + sleeping); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentColliderDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentColliderDto.java new file mode 100644 index 00000000..25971a7f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentColliderDto.java @@ -0,0 +1,160 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public final class PersistentColliderDto { + + private static final Vector3f ZERO = new Vector3f(); + private static final Quaternionf IDENTITY = new Quaternionf(); + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentColliderDto.class, PersistentColliderDto::new) + .append(new KeyedCodec<>("ColliderUuid", Codec.UUID_BINARY), + (dto, value) -> dto.colliderUuid = value, + PersistentColliderDto::getColliderUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("BodyUuid", Codec.UUID_BINARY), + (dto, value) -> dto.bodyUuid = value, + PersistentColliderDto::getBodyUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("ShapeUuid", Codec.UUID_BINARY), + (dto, value) -> dto.shapeUuid = value, + PersistentColliderDto::getShapeUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("MaterialUuid", Codec.UUID_BINARY), + (dto, value) -> dto.materialUuid = value, + PersistentColliderDto::getMaterialUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("LocalPosition", Vector3fUtil.CODEC, false), + (dto, value) -> dto.localPosition.set(value != null ? value : ZERO), + PersistentColliderDto::getLocalPosition) + .addValidator(PhysicsStorePersistenceValidation.finiteVector( + "Persisted collider local position must be finite")) + .add() + .append(new KeyedCodec<>("LocalRotation", ImpulseCodecs.QUATERNIONF, false), + (dto, value) -> dto.localRotation.set(value != null ? value : IDENTITY), + PersistentColliderDto::getLocalRotation) + .add() + .append(new KeyedCodec<>("Sensor", Codec.BOOLEAN, false), + (dto, value) -> dto.sensor = value != null && value, + PersistentColliderDto::isSensor) + .add() + .append(new KeyedCodec<>("CollisionGroup", Codec.INTEGER, false), + (dto, value) -> dto.collisionGroup = value != null ? value : 0, + PersistentColliderDto::getCollisionGroup) + .add() + .append(new KeyedCodec<>("CollisionMask", Codec.INTEGER, false), + (dto, value) -> dto.collisionMask = value != null ? value : -1, + PersistentColliderDto::getCollisionMask) + .add() + .build(); + + @Nonnull + private UUID colliderUuid = new UUID(0L, 0L); + @Nonnull + private UUID bodyUuid = new UUID(0L, 0L); + @Nonnull + private UUID shapeUuid = new UUID(0L, 0L); + @Nonnull + private UUID materialUuid = new UUID(0L, 0L); + @Nonnull + private final Vector3f localPosition = new Vector3f(); + @Nonnull + private final Quaternionf localRotation = new Quaternionf(); + private boolean sensor; + private int collisionGroup; + private int collisionMask = -1; + + public PersistentColliderDto() { + } + + public PersistentColliderDto(@Nonnull UUID colliderUuid, + @Nonnull UUID bodyUuid, + @Nonnull UUID shapeUuid, + @Nonnull UUID materialUuid, + @Nonnull Vector3f localPosition, + @Nonnull Quaternionf localRotation, + boolean sensor, + int collisionGroup, + int collisionMask) { + this.colliderUuid = Objects.requireNonNull(colliderUuid, "colliderUuid"); + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); + this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); + this.localPosition.set(Objects.requireNonNull(localPosition, "localPosition")); + this.localRotation.set(Objects.requireNonNull(localRotation, "localRotation")); + this.sensor = sensor; + this.collisionGroup = collisionGroup; + this.collisionMask = collisionMask; + } + + @Nonnull + public UUID getColliderUuid() { + return colliderUuid; + } + + @Nonnull + public UUID getBodyUuid() { + return bodyUuid; + } + + @Nonnull + public UUID getShapeUuid() { + return shapeUuid; + } + + @Nonnull + public UUID getMaterialUuid() { + return materialUuid; + } + + @Nonnull + public Vector3f getLocalPosition() { + return new Vector3f(localPosition); + } + + @Nonnull + public Quaternionf getLocalRotation() { + return new Quaternionf(localRotation); + } + + public boolean isSensor() { + return sensor; + } + + public int getCollisionGroup() { + return collisionGroup; + } + + public int getCollisionMask() { + return collisionMask; + } + + @Nonnull + public PersistentColliderDto copy() { + return new PersistentColliderDto(colliderUuid, + bodyUuid, + shapeUuid, + materialUuid, + localPosition, + localRotation, + sensor, + collisionGroup, + collisionMask); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java new file mode 100644 index 00000000..dbf0951a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java @@ -0,0 +1,244 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +public final class PersistentJointDto { + + private static final Vector3f ZERO = new Vector3f(); + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentJointDto.class, PersistentJointDto::new) + .append(new KeyedCodec<>("JointUuid", Codec.UUID_BINARY), + (dto, value) -> dto.jointUuid = value, + PersistentJointDto::getJointUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), + (dto, value) -> dto.spaceUuid = value, + PersistentJointDto::getSpaceUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("BodyAUuid", Codec.UUID_BINARY), + (dto, value) -> dto.bodyAUuid = value, + PersistentJointDto::getBodyAUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("BodyBUuid", Codec.UUID_BINARY), + (dto, value) -> dto.bodyBUuid = value, + PersistentJointDto::getBodyBUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Type", new EnumCodec<>(JointType.class), false), + (dto, value) -> dto.type = value != null ? value : JointType.FIXED, + PersistentJointDto::getType) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("AnchorA", Vector3fUtil.CODEC, false), + (dto, value) -> dto.anchorA.set(value != null ? value : ZERO), + PersistentJointDto::getAnchorA) + .addValidator(PhysicsStorePersistenceValidation.finiteVector( + "Persisted joint anchor A must be finite")) + .add() + .append(new KeyedCodec<>("AnchorB", Vector3fUtil.CODEC, false), + (dto, value) -> dto.anchorB.set(value != null ? value : ZERO), + PersistentJointDto::getAnchorB) + .addValidator(PhysicsStorePersistenceValidation.finiteVector( + "Persisted joint anchor B must be finite")) + .add() + .append(new KeyedCodec<>("Axis", Vector3fUtil.CODEC, false), + (dto, value) -> dto.axis.set(value != null ? value : ZERO), + PersistentJointDto::getAxis) + .addValidator(PhysicsStorePersistenceValidation.finiteVector( + "Persisted joint axis must be finite")) + .add() + .append(new KeyedCodec<>("LowerLimit", Codec.FLOAT, false), + (dto, value) -> dto.lowerLimit = value != null ? value : 0.0f, + PersistentJointDto::getLowerLimit) + .addValidator(PhysicsStorePersistenceValidation.finiteFloat( + "Persisted joint lower limit must be finite")) + .add() + .append(new KeyedCodec<>("UpperLimit", Codec.FLOAT, false), + (dto, value) -> dto.upperLimit = value != null ? value : 0.0f, + PersistentJointDto::getUpperLimit) + .addValidator(PhysicsStorePersistenceValidation.finiteFloat( + "Persisted joint upper limit must be finite")) + .add() + .append(new KeyedCodec<>("Enabled", Codec.BOOLEAN, false), + (dto, value) -> dto.enabled = value == null || value, + PersistentJointDto::isEnabled) + .add() + .append(new KeyedCodec<>("MotorEnabled", Codec.BOOLEAN, false), + (dto, value) -> dto.motorEnabled = value != null && value, + PersistentJointDto::isMotorEnabled) + .add() + .append(new KeyedCodec<>("MotorTargetVelocity", Codec.FLOAT, false), + (dto, value) -> dto.motorTargetVelocity = value != null ? value : 0.0f, + PersistentJointDto::getMotorTargetVelocity) + .addValidator(PhysicsStorePersistenceValidation.finiteFloat( + "Persisted joint motor target velocity must be finite")) + .add() + .append(new KeyedCodec<>("MotorMaxForce", Codec.FLOAT, false), + (dto, value) -> dto.motorMaxForce = value != null ? value : 0.0f, + PersistentJointDto::getMotorMaxForce) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted joint motor max force must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("SpringRestLength", Codec.FLOAT, false), + (dto, value) -> dto.springRestLength = value != null ? value : 0.0f, + PersistentJointDto::getSpringRestLength) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted joint spring rest length must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("SpringStiffness", Codec.FLOAT, false), + (dto, value) -> dto.springStiffness = value != null ? value : 0.0f, + PersistentJointDto::getSpringStiffness) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted joint spring stiffness must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("SpringDamping", Codec.FLOAT, false), + (dto, value) -> dto.springDamping = value != null ? value : 0.0f, + PersistentJointDto::getSpringDamping) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted joint spring damping must be finite and >= 0")) + .add() + .build(); + + @Nonnull + private UUID jointUuid = new UUID(0L, 0L); + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private UUID bodyAUuid = new UUID(0L, 0L); + @Nonnull + private UUID bodyBUuid = new UUID(0L, 0L); + @Nonnull + private JointType type = JointType.FIXED; + @Nonnull + private final Vector3f anchorA = new Vector3f(); + @Nonnull + private final Vector3f anchorB = new Vector3f(); + @Nonnull + private final Vector3f axis = new Vector3f(); + private float lowerLimit; + private float upperLimit; + private boolean enabled = true; + private boolean motorEnabled; + private float motorTargetVelocity; + private float motorMaxForce; + private float springRestLength; + private float springStiffness; + private float springDamping; + + public PersistentJointDto() { + } + + @Nonnull + public UUID getJointUuid() { + return jointUuid; + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + @Nonnull + public UUID getBodyAUuid() { + return bodyAUuid; + } + + @Nonnull + public UUID getBodyBUuid() { + return bodyBUuid; + } + + @Nonnull + public JointType getType() { + return type; + } + + @Nonnull + public Vector3f getAnchorA() { + return new Vector3f(anchorA); + } + + @Nonnull + public Vector3f getAnchorB() { + return new Vector3f(anchorB); + } + + @Nonnull + public Vector3f getAxis() { + return new Vector3f(axis); + } + + public float getLowerLimit() { + return lowerLimit; + } + + public float getUpperLimit() { + return upperLimit; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isMotorEnabled() { + return motorEnabled; + } + + public float getMotorTargetVelocity() { + return motorTargetVelocity; + } + + public float getMotorMaxForce() { + return motorMaxForce; + } + + public float getSpringRestLength() { + return springRestLength; + } + + public float getSpringStiffness() { + return springStiffness; + } + + public float getSpringDamping() { + return springDamping; + } + + @Nonnull + public PersistentJointDto copy() { + PersistentJointDto copy = new PersistentJointDto(); + copy.jointUuid = Objects.requireNonNull(jointUuid, "jointUuid"); + copy.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + copy.bodyAUuid = Objects.requireNonNull(bodyAUuid, "bodyAUuid"); + copy.bodyBUuid = Objects.requireNonNull(bodyBUuid, "bodyBUuid"); + copy.type = type; + copy.anchorA.set(anchorA); + copy.anchorB.set(anchorB); + copy.axis.set(axis); + copy.lowerLimit = lowerLimit; + copy.upperLimit = upperLimit; + copy.enabled = enabled; + copy.motorEnabled = motorEnabled; + copy.motorTargetVelocity = motorTargetVelocity; + copy.motorMaxForce = motorMaxForce; + copy.springRestLength = springRestLength; + copy.springStiffness = springStiffness; + copy.springDamping = springDamping; + return copy; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentMaterialDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentMaterialDto.java new file mode 100644 index 00000000..093955d0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentMaterialDto.java @@ -0,0 +1,66 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +public final class PersistentMaterialDto { + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentMaterialDto.class, PersistentMaterialDto::new) + .append(new KeyedCodec<>("MaterialUuid", Codec.UUID_BINARY), + (dto, value) -> dto.materialUuid = value, + PersistentMaterialDto::getMaterialUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Friction", Codec.FLOAT, false), + (dto, value) -> dto.friction = value != null ? value : 0.5f, + PersistentMaterialDto::getFriction) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted material friction must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("Restitution", Codec.FLOAT, false), + (dto, value) -> dto.restitution = value != null ? value : 0.0f, + PersistentMaterialDto::getRestitution) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted material restitution must be finite and >= 0")) + .add() + .build(); + + @Nonnull + private UUID materialUuid = new UUID(0L, 0L); + private float friction = 0.5f; + private float restitution; + + public PersistentMaterialDto() { + } + + public PersistentMaterialDto(@Nonnull UUID materialUuid, float friction, float restitution) { + this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); + this.friction = friction; + this.restitution = restitution; + } + + @Nonnull + public UUID getMaterialUuid() { + return materialUuid; + } + + public float getFriction() { + return friction; + } + + public float getRestitution() { + return restitution; + } + + @Nonnull + public PersistentMaterialDto copy() { + return new PersistentMaterialDto(materialUuid, friction, restitution); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java new file mode 100644 index 00000000..c7f4162f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java @@ -0,0 +1,240 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Preflight validation for persisted PhysicsStore DTOs before backend mutation. + */ +public final class PersistentPhysicsStorePreflight { + + private static final UUID NIL_UUID = new UUID(0L, 0L); + + private PersistentPhysicsStorePreflight() { + } + + @Nonnull + public static Result validate(@Nonnull PersistentPhysicsStoreResource resource) { + List errors = new ArrayList<>(); + if (resource.getSchemaVersion() != PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION) { + errors.add("Malformed PhysicsStore schema version: " + resource.getSchemaVersion()); + } + + Set spaces = collectSpaces(resource.getSpaces(), errors); + Set bodies = collectBodies(resource.getBodies(), spaces, errors); + Set shapes = collectShapes(resource.getShapes(), errors); + Set materials = collectMaterials(resource.getMaterials(), errors); + Set colliders = collectColliders(resource.getColliders(), + bodies, + shapes, + materials, + errors); + validateBodyColliderRefs(resource.getBodies(), colliders, errors); + validateJoints(resource.getJoints(), spaces, bodies, errors); + validateTerrain(resource.getTerrainColliders(), spaces, errors); + return new Result(errors.isEmpty(), errors); + } + + @Nonnull + private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, + @Nonnull List errors) { + Set seen = new HashSet<>(); + for (PersistentSpaceDto space : spaces) { + UUID uuid = space.getSpaceUuid(); + requireUuid("space", uuid, errors); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore space UUID " + uuid); + } + if (space.getBackendId().isBlank()) { + errors.add("PhysicsStore space " + uuid + " has blank backend id"); + } + if (!PhysicsStorePersistenceValidation.isFinite(space.getGravity())) { + errors.add("PhysicsStore space " + uuid + " has non-finite gravity"); + } + } + return seen; + } + + @Nonnull + private static Set collectBodies(@Nonnull PersistentBodyDto[] bodies, + @Nonnull Set spaces, + @Nonnull List errors) { + Set seen = new HashSet<>(); + for (PersistentBodyDto body : bodies) { + UUID uuid = body.getBodyUuid(); + requireUuid("body", uuid, errors); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore body UUID " + uuid); + } + if (!spaces.contains(body.getSpaceUuid())) { + errors.add("Body " + uuid + " references missing space " + body.getSpaceUuid()); + } + if (!Float.isFinite(body.getMass()) || body.getMass() < 0.0f) { + errors.add("Body " + uuid + " has invalid mass"); + } + if (!Float.isFinite(body.getLinearDamping()) || body.getLinearDamping() < 0.0f) { + errors.add("Body " + uuid + " has invalid linear damping"); + } + if (!Float.isFinite(body.getAngularDamping()) || body.getAngularDamping() < 0.0f) { + errors.add("Body " + uuid + " has invalid angular damping"); + } + PersistentBodyRuntimeStateDto runtime = body.getRuntimeState(); + if (!PhysicsStorePersistenceValidation.isFinite(runtime.getPosition()) + || !PhysicsStorePersistenceValidation.isFinite(runtime.getRotation()) + || !PhysicsStorePersistenceValidation.isFinite(runtime.getLinearVelocity()) + || !PhysicsStorePersistenceValidation.isFinite(runtime.getAngularVelocity())) { + errors.add("Body " + uuid + " has non-finite runtime state"); + } + } + return seen; + } + + @Nonnull + private static Set collectShapes(@Nonnull PersistentShapeDto[] shapes, + @Nonnull List errors) { + Set seen = new HashSet<>(); + for (PersistentShapeDto shape : shapes) { + UUID uuid = shape.getShapeUuid(); + requireUuid("shape", uuid, errors); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore shape UUID " + uuid); + } + if (!Float.isFinite(shape.getGroundY())) { + errors.add("Shape " + uuid + " has non-finite groundY"); + } + } + return seen; + } + + @Nonnull + private static Set collectMaterials(@Nonnull PersistentMaterialDto[] materials, + @Nonnull List errors) { + Set seen = new HashSet<>(); + for (PersistentMaterialDto material : materials) { + UUID uuid = material.getMaterialUuid(); + requireUuid("material", uuid, errors); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore material UUID " + uuid); + } + if (!Float.isFinite(material.getFriction()) || material.getFriction() < 0.0f) { + errors.add("Material " + uuid + " has invalid friction"); + } + if (!Float.isFinite(material.getRestitution()) || material.getRestitution() < 0.0f) { + errors.add("Material " + uuid + " has invalid restitution"); + } + } + return seen; + } + + @Nonnull + private static Set collectColliders(@Nonnull PersistentColliderDto[] colliders, + @Nonnull Set bodies, + @Nonnull Set shapes, + @Nonnull Set materials, + @Nonnull List errors) { + Set seen = new HashSet<>(); + for (PersistentColliderDto collider : colliders) { + UUID uuid = collider.getColliderUuid(); + requireUuid("collider", uuid, errors); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore collider UUID " + uuid); + } + if (!bodies.contains(collider.getBodyUuid())) { + errors.add("Collider " + uuid + " references missing body " + + collider.getBodyUuid()); + } + if (!shapes.contains(collider.getShapeUuid())) { + errors.add("Collider " + uuid + " references missing shape " + + collider.getShapeUuid()); + } + if (!materials.contains(collider.getMaterialUuid())) { + errors.add("Collider " + uuid + " references missing material " + + collider.getMaterialUuid()); + } + } + return seen; + } + + private static void validateBodyColliderRefs(@Nonnull PersistentBodyDto[] bodies, + @Nonnull Set colliders, + @Nonnull List errors) { + for (PersistentBodyDto body : bodies) { + for (UUID colliderUuid : body.getColliderUuids()) { + if (!colliders.contains(colliderUuid)) { + errors.add("Body " + body.getBodyUuid() + + " references missing collider " + colliderUuid); + } + } + } + } + + private static void validateJoints(@Nonnull PersistentJointDto[] joints, + @Nonnull Set spaces, + @Nonnull Set bodies, + @Nonnull List errors) { + Set seen = new HashSet<>(); + for (PersistentJointDto joint : joints) { + UUID uuid = joint.getJointUuid(); + requireUuid("joint", uuid, errors); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore joint UUID " + uuid); + } + if (!spaces.contains(joint.getSpaceUuid())) { + errors.add("Joint " + uuid + " references missing space " + + joint.getSpaceUuid()); + } + if (!bodies.contains(joint.getBodyAUuid())) { + errors.add("Joint " + uuid + " references missing body A " + + joint.getBodyAUuid()); + } + if (!bodies.contains(joint.getBodyBUuid())) { + errors.add("Joint " + uuid + " references missing body B " + + joint.getBodyBUuid()); + } + } + } + + private static void validateTerrain(@Nonnull PersistentTerrainColliderDto[] terrainColliders, + @Nonnull Set spaces, + @Nonnull List errors) { + Set seen = new HashSet<>(); + for (PersistentTerrainColliderDto terrain : terrainColliders) { + UUID uuid = terrain.getTerrainColliderUuid(); + requireUuid("terrain collider", uuid, errors); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore terrain collider UUID " + uuid); + } + if (!spaces.contains(terrain.getSpaceUuid())) { + errors.add("Terrain collider " + uuid + " references missing space " + + terrain.getSpaceUuid()); + } + if (terrain.getSourceKey().isBlank()) { + errors.add("Terrain collider " + uuid + " has blank source key"); + } + } + } + + private static void requireUuid(@Nonnull String kind, + @Nonnull UUID uuid, + @Nonnull List errors) { + if (NIL_UUID.equals(uuid)) { + errors.add("PhysicsStore " + kind + " UUID cannot be nil"); + } + } + + public record Result(boolean valid, @Nonnull List errors) { + + public Result { + errors = List.copyOf(errors); + } + + @Nonnull + public static Result success() { + return new Result(true, List.of()); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java new file mode 100644 index 00000000..620bb90b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java @@ -0,0 +1,279 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Arrays; +import javax.annotation.Nonnull; + +/** + * Canonical compact DTO persistence for PhysicsStore rows. + */ +public final class PersistentPhysicsStoreResource implements Resource { + + public static final int CURRENT_SCHEMA_VERSION = 1; + private static final PersistentSpaceDto[] EMPTY_SPACES = new PersistentSpaceDto[0]; + private static final PersistentBodyDto[] EMPTY_BODIES = new PersistentBodyDto[0]; + private static final PersistentColliderDto[] EMPTY_COLLIDERS = new PersistentColliderDto[0]; + private static final PersistentShapeDto[] EMPTY_SHAPES = new PersistentShapeDto[0]; + private static final PersistentMaterialDto[] EMPTY_MATERIALS = new PersistentMaterialDto[0]; + private static final PersistentJointDto[] EMPTY_JOINTS = new PersistentJointDto[0]; + private static final PersistentTerrainColliderDto[] EMPTY_TERRAIN_COLLIDERS = + new PersistentTerrainColliderDto[0]; + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentPhysicsStoreResource.class, + PersistentPhysicsStoreResource::new) + .append(new KeyedCodec<>("SchemaVersion", Codec.INTEGER, false), + PersistentPhysicsStoreResource::setSchemaVersion, + PersistentPhysicsStoreResource::getSchemaVersion) + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(CURRENT_SCHEMA_VERSION, CURRENT_SCHEMA_VERSION)) + .add() + .append(new KeyedCodec<>("Spaces", + new ArrayCodec<>(PersistentSpaceDto.CODEC, PersistentSpaceDto[]::new), + false), + (resource, value) -> resource.spaces = copySpaces(value), + PersistentPhysicsStoreResource::getSpaces) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("Bodies", + new ArrayCodec<>(PersistentBodyDto.CODEC, PersistentBodyDto[]::new), + false), + (resource, value) -> resource.bodies = copyBodies(value), + PersistentPhysicsStoreResource::getBodies) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("Colliders", + new ArrayCodec<>(PersistentColliderDto.CODEC, PersistentColliderDto[]::new), + false), + (resource, value) -> resource.colliders = copyColliders(value), + PersistentPhysicsStoreResource::getColliders) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("Shapes", + new ArrayCodec<>(PersistentShapeDto.CODEC, PersistentShapeDto[]::new), + false), + (resource, value) -> resource.shapes = copyShapes(value), + PersistentPhysicsStoreResource::getShapes) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("Materials", + new ArrayCodec<>(PersistentMaterialDto.CODEC, PersistentMaterialDto[]::new), + false), + (resource, value) -> resource.materials = copyMaterials(value), + PersistentPhysicsStoreResource::getMaterials) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("Joints", + new ArrayCodec<>(PersistentJointDto.CODEC, PersistentJointDto[]::new), + false), + (resource, value) -> resource.joints = copyJoints(value), + PersistentPhysicsStoreResource::getJoints) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("TerrainColliders", + new ArrayCodec<>(PersistentTerrainColliderDto.CODEC, + PersistentTerrainColliderDto[]::new), + false), + (resource, value) -> resource.terrainColliders = copyTerrainColliders(value), + PersistentPhysicsStoreResource::getTerrainColliders) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .build(); + + private int schemaVersion = CURRENT_SCHEMA_VERSION; + @Nonnull + private PersistentSpaceDto[] spaces = EMPTY_SPACES; + @Nonnull + private PersistentBodyDto[] bodies = EMPTY_BODIES; + @Nonnull + private PersistentColliderDto[] colliders = EMPTY_COLLIDERS; + @Nonnull + private PersistentShapeDto[] shapes = EMPTY_SHAPES; + @Nonnull + private PersistentMaterialDto[] materials = EMPTY_MATERIALS; + @Nonnull + private PersistentJointDto[] joints = EMPTY_JOINTS; + @Nonnull + private PersistentTerrainColliderDto[] terrainColliders = EMPTY_TERRAIN_COLLIDERS; + + public PersistentPhysicsStoreResource() { + } + + public int getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(int schemaVersion) { + if (schemaVersion != CURRENT_SCHEMA_VERSION) { + throw new IllegalArgumentException("Schema version must be " + + CURRENT_SCHEMA_VERSION); + } + this.schemaVersion = schemaVersion; + } + + @Nonnull + public PersistentSpaceDto[] getSpaces() { + return copySpaces(spaces); + } + + public void setSpaces(@Nonnull PersistentSpaceDto[] spaces) { + this.spaces = copySpaces(spaces); + } + + @Nonnull + public PersistentBodyDto[] getBodies() { + return copyBodies(bodies); + } + + public void setBodies(@Nonnull PersistentBodyDto[] bodies) { + this.bodies = copyBodies(bodies); + } + + @Nonnull + public PersistentColliderDto[] getColliders() { + return copyColliders(colliders); + } + + public void setColliders(@Nonnull PersistentColliderDto[] colliders) { + this.colliders = copyColliders(colliders); + } + + @Nonnull + public PersistentShapeDto[] getShapes() { + return copyShapes(shapes); + } + + public void setShapes(@Nonnull PersistentShapeDto[] shapes) { + this.shapes = copyShapes(shapes); + } + + @Nonnull + public PersistentMaterialDto[] getMaterials() { + return copyMaterials(materials); + } + + public void setMaterials(@Nonnull PersistentMaterialDto[] materials) { + this.materials = copyMaterials(materials); + } + + @Nonnull + public PersistentJointDto[] getJoints() { + return copyJoints(joints); + } + + public void setJoints(@Nonnull PersistentJointDto[] joints) { + this.joints = copyJoints(joints); + } + + @Nonnull + public PersistentTerrainColliderDto[] getTerrainColliders() { + return copyTerrainColliders(terrainColliders); + } + + public void setTerrainColliders(@Nonnull PersistentTerrainColliderDto[] terrainColliders) { + this.terrainColliders = copyTerrainColliders(terrainColliders); + } + + @Nonnull + public PersistentPhysicsStorePreflight.Result preflight() { + return PersistentPhysicsStorePreflight.validate(this); + } + + @Nonnull + @Override + public PersistentPhysicsStoreResource clone() { + PersistentPhysicsStoreResource copy = new PersistentPhysicsStoreResource(); + copy.schemaVersion = schemaVersion; + copy.spaces = copySpaces(spaces); + copy.bodies = copyBodies(bodies); + copy.colliders = copyColliders(colliders); + copy.shapes = copyShapes(shapes); + copy.materials = copyMaterials(materials); + copy.joints = copyJoints(joints); + copy.terrainColliders = copyTerrainColliders(terrainColliders); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.persistentStoreResourceType(); + } + + @Nonnull + private static PersistentSpaceDto[] copySpaces(PersistentSpaceDto[] values) { + if (values == null || values.length == 0) { + return EMPTY_SPACES; + } + return Arrays.stream(values).map(PersistentSpaceDto::copy).toArray(PersistentSpaceDto[]::new); + } + + @Nonnull + private static PersistentBodyDto[] copyBodies(PersistentBodyDto[] values) { + if (values == null || values.length == 0) { + return EMPTY_BODIES; + } + return Arrays.stream(values).map(PersistentBodyDto::copy).toArray(PersistentBodyDto[]::new); + } + + @Nonnull + private static PersistentColliderDto[] copyColliders(PersistentColliderDto[] values) { + if (values == null || values.length == 0) { + return EMPTY_COLLIDERS; + } + return Arrays.stream(values).map(PersistentColliderDto::copy) + .toArray(PersistentColliderDto[]::new); + } + + @Nonnull + private static PersistentShapeDto[] copyShapes(PersistentShapeDto[] values) { + if (values == null || values.length == 0) { + return EMPTY_SHAPES; + } + return Arrays.stream(values).map(PersistentShapeDto::copy) + .toArray(PersistentShapeDto[]::new); + } + + @Nonnull + private static PersistentMaterialDto[] copyMaterials(PersistentMaterialDto[] values) { + if (values == null || values.length == 0) { + return EMPTY_MATERIALS; + } + return Arrays.stream(values).map(PersistentMaterialDto::copy) + .toArray(PersistentMaterialDto[]::new); + } + + @Nonnull + private static PersistentJointDto[] copyJoints(PersistentJointDto[] values) { + if (values == null || values.length == 0) { + return EMPTY_JOINTS; + } + return Arrays.stream(values).map(PersistentJointDto::copy) + .toArray(PersistentJointDto[]::new); + } + + @Nonnull + private static PersistentTerrainColliderDto[] copyTerrainColliders( + PersistentTerrainColliderDto[] values) { + if (values == null || values.length == 0) { + return EMPTY_TERRAIN_COLLIDERS; + } + return Arrays.stream(values).map(PersistentTerrainColliderDto::copy) + .toArray(PersistentTerrainColliderDto[]::new); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentShapeDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentShapeDto.java new file mode 100644 index 00000000..a25443b0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentShapeDto.java @@ -0,0 +1,173 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.ShapeType; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +public final class PersistentShapeDto { + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentShapeDto.class, PersistentShapeDto::new) + .append(new KeyedCodec<>("ShapeUuid", Codec.UUID_BINARY), + (dto, value) -> dto.shapeUuid = value, + PersistentShapeDto::getShapeUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("ShapeType", new EnumCodec<>(ShapeType.class)), + (dto, value) -> dto.shapeType = value, + PersistentShapeDto::getShapeType) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("HalfExtentX", Codec.FLOAT, false), + (dto, value) -> dto.halfExtentX = value != null ? value : 0.0f, + PersistentShapeDto::getHalfExtentX) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted shape half extent X must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("HalfExtentY", Codec.FLOAT, false), + (dto, value) -> dto.halfExtentY = value != null ? value : 0.0f, + PersistentShapeDto::getHalfExtentY) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted shape half extent Y must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("HalfExtentZ", Codec.FLOAT, false), + (dto, value) -> dto.halfExtentZ = value != null ? value : 0.0f, + PersistentShapeDto::getHalfExtentZ) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted shape half extent Z must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("Radius", Codec.FLOAT, false), + (dto, value) -> dto.radius = value != null ? value : 0.0f, + PersistentShapeDto::getRadius) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted shape radius must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("HalfHeight", Codec.FLOAT, false), + (dto, value) -> dto.halfHeight = value != null ? value : 0.0f, + PersistentShapeDto::getHalfHeight) + .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( + "Persisted shape half height must be finite and >= 0")) + .add() + .append(new KeyedCodec<>("Axis", new EnumCodec<>(PhysicsAxis.class), false), + (dto, value) -> dto.axis = value != null ? value : PhysicsAxis.Y, + PersistentShapeDto::getAxis) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("GroundY", Codec.FLOAT, false), + (dto, value) -> dto.groundY = value != null ? value : 0.0f, + PersistentShapeDto::getGroundY) + .addValidator(PhysicsStorePersistenceValidation.finiteFloat( + "Persisted shape ground Y must be finite")) + .add() + .append(new KeyedCodec<>("ResourceKey", Codec.STRING, false), + (dto, value) -> dto.resourceKey = value != null ? value : "", + PersistentShapeDto::getResourceKey) + .add() + .build(); + + @Nonnull + private UUID shapeUuid = new UUID(0L, 0L); + @Nonnull + private ShapeType shapeType = ShapeType.BOX; + private float halfExtentX; + private float halfExtentY; + private float halfExtentZ; + private float radius; + private float halfHeight; + @Nonnull + private PhysicsAxis axis = PhysicsAxis.Y; + private float groundY; + @Nonnull + private String resourceKey = ""; + + public PersistentShapeDto() { + } + + public PersistentShapeDto(@Nonnull UUID shapeUuid, + @Nonnull ShapeType shapeType, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + @Nonnull PhysicsAxis axis, + float groundY, + @Nonnull String resourceKey) { + this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); + this.shapeType = Objects.requireNonNull(shapeType, "shapeType"); + this.halfExtentX = halfExtentX; + this.halfExtentY = halfExtentY; + this.halfExtentZ = halfExtentZ; + this.radius = radius; + this.halfHeight = halfHeight; + this.axis = Objects.requireNonNull(axis, "axis"); + this.groundY = groundY; + this.resourceKey = Objects.requireNonNull(resourceKey, "resourceKey"); + } + + @Nonnull + public UUID getShapeUuid() { + return shapeUuid; + } + + @Nonnull + public ShapeType getShapeType() { + return shapeType; + } + + public float getHalfExtentX() { + return halfExtentX; + } + + public float getHalfExtentY() { + return halfExtentY; + } + + public float getHalfExtentZ() { + return halfExtentZ; + } + + public float getRadius() { + return radius; + } + + public float getHalfHeight() { + return halfHeight; + } + + @Nonnull + public PhysicsAxis getAxis() { + return axis; + } + + public float getGroundY() { + return groundY; + } + + @Nonnull + public String getResourceKey() { + return resourceKey; + } + + @Nonnull + public PersistentShapeDto copy() { + return new PersistentShapeDto(shapeUuid, + shapeType, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axis, + groundY, + resourceKey); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java new file mode 100644 index 00000000..4cf248bd --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java @@ -0,0 +1,74 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +public final class PersistentSpaceDto { + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentSpaceDto.class, PersistentSpaceDto::new) + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), + (dto, value) -> dto.spaceUuid = value, + PersistentSpaceDto::getSpaceUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("BackendId", Codec.STRING), + (dto, value) -> dto.backendId = value, + PersistentSpaceDto::getBackendId) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Gravity", Vector3fUtil.CODEC), + (dto, value) -> dto.gravity.set(value), + PersistentSpaceDto::getGravity) + .addValidator(Validators.nonNull()) + .addValidator(PhysicsStorePersistenceValidation.finiteVector( + "Persisted PhysicsStore space gravity must be finite")) + .add() + .build(); + + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private String backendId = ""; + @Nonnull + private final Vector3f gravity = new Vector3f(0.0f, -9.81f, 0.0f); + + public PersistentSpaceDto() { + } + + public PersistentSpaceDto(@Nonnull UUID spaceUuid, + @Nonnull String backendId, + @Nonnull Vector3f gravity) { + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.backendId = Objects.requireNonNull(backendId, "backendId"); + this.gravity.set(Objects.requireNonNull(gravity, "gravity")); + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + @Nonnull + public String getBackendId() { + return backendId; + } + + @Nonnull + public Vector3f getGravity() { + return new Vector3f(gravity); + } + + @Nonnull + public PersistentSpaceDto copy() { + return new PersistentSpaceDto(spaceUuid, backendId, gravity); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentTerrainColliderDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentTerrainColliderDto.java new file mode 100644 index 00000000..8068d377 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentTerrainColliderDto.java @@ -0,0 +1,136 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +public final class PersistentTerrainColliderDto { + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(PersistentTerrainColliderDto.class, PersistentTerrainColliderDto::new) + .append(new KeyedCodec<>("TerrainColliderUuid", Codec.UUID_BINARY), + (dto, value) -> dto.terrainColliderUuid = value, + PersistentTerrainColliderDto::getTerrainColliderUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), + (dto, value) -> dto.spaceUuid = value, + PersistentTerrainColliderDto::getSpaceUuid) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("SourceKey", Codec.STRING, false), + (dto, value) -> dto.sourceKey = value != null ? value : "", + PersistentTerrainColliderDto::getSourceKey) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("ChunkX", Codec.INTEGER, false), + (dto, value) -> dto.chunkX = value != null ? value : 0, + PersistentTerrainColliderDto::getChunkX) + .add() + .append(new KeyedCodec<>("SectionY", Codec.INTEGER, false), + (dto, value) -> dto.sectionY = value != null ? value : 0, + PersistentTerrainColliderDto::getSectionY) + .add() + .append(new KeyedCodec<>("ChunkZ", Codec.INTEGER, false), + (dto, value) -> dto.chunkZ = value != null ? value : 0, + PersistentTerrainColliderDto::getChunkZ) + .add() + .append(new KeyedCodec<>("PayloadResourceKey", Codec.STRING, false), + (dto, value) -> dto.payloadResourceKey = value != null ? value : "", + PersistentTerrainColliderDto::getPayloadResourceKey) + .add() + .append(new KeyedCodec<>("Retained", Codec.BOOLEAN, false), + (dto, value) -> dto.retained = value == null || value, + PersistentTerrainColliderDto::isRetained) + .add() + .build(); + + @Nonnull + private UUID terrainColliderUuid = new UUID(0L, 0L); + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private String sourceKey = ""; + private int chunkX; + private int sectionY; + private int chunkZ; + @Nonnull + private String payloadResourceKey = ""; + private boolean retained = true; + + public PersistentTerrainColliderDto() { + } + + public PersistentTerrainColliderDto(@Nonnull UUID terrainColliderUuid, + @Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + int chunkX, + int sectionY, + int chunkZ, + @Nonnull String payloadResourceKey, + boolean retained) { + this.terrainColliderUuid = Objects.requireNonNull(terrainColliderUuid, + "terrainColliderUuid"); + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); + this.chunkX = chunkX; + this.sectionY = sectionY; + this.chunkZ = chunkZ; + this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, + "payloadResourceKey"); + this.retained = retained; + } + + @Nonnull + public UUID getTerrainColliderUuid() { + return terrainColliderUuid; + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + @Nonnull + public String getSourceKey() { + return sourceKey; + } + + public int getChunkX() { + return chunkX; + } + + public int getSectionY() { + return sectionY; + } + + public int getChunkZ() { + return chunkZ; + } + + @Nonnull + public String getPayloadResourceKey() { + return payloadResourceKey; + } + + public boolean isRetained() { + return retained; + } + + @Nonnull + public PersistentTerrainColliderDto copy() { + return new PersistentTerrainColliderDto(terrainColliderUuid, + spaceUuid, + sourceKey, + chunkX, + sectionY, + chunkZ, + payloadResourceKey, + retained); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStorePersistenceValidation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStorePersistenceValidation.java new file mode 100644 index 00000000..7f5c077f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStorePersistenceValidation.java @@ -0,0 +1,75 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +final class PhysicsStorePersistenceValidation { + + private PhysicsStorePersistenceValidation() { + } + + @Nonnull + static Validator finiteFloat(@Nonnull String message) { + return new Validator<>() { + @Override + public void accept(Float value, ValidationResults results) { + if (value != null && !Float.isFinite(value)) { + results.fail(message); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema schema) { + } + }; + } + + @Nonnull + static Validator nonNegativeFiniteFloat(@Nonnull String message) { + return new Validator<>() { + @Override + public void accept(Float value, ValidationResults results) { + if (value != null && (!Float.isFinite(value) || value < 0.0f)) { + results.fail(message); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema schema) { + } + }; + } + + @Nonnull + static Validator finiteVector(@Nonnull String message) { + return new Validator<>() { + @Override + public void accept(Vector3f value, ValidationResults results) { + if (value != null && !isFinite(value)) { + results.fail(message); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema schema) { + } + }; + } + + static boolean isFinite(@Nonnull Vector3f value) { + return Float.isFinite(value.x) && Float.isFinite(value.y) && Float.isFinite(value.z); + } + + static boolean isFinite(@Nonnull Quaternionf value) { + return Float.isFinite(value.x) + && Float.isFinite(value.y) + && Float.isFinite(value.z) + && Float.isFinite(value.w) + && value.lengthSquared() > 0.0f; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java new file mode 100644 index 00000000..fbbee32d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -0,0 +1,130 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.registration; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.annotation.Nonnull; + +/** + * Registers authoritative PhysicsStore ECS types after the early plugin has patched Hytale. + */ +public final class PhysicsStoreRegistration { + + private static final String REGISTRY_METHOD = "getPhysicsStoreRegistry"; + + private PhysicsStoreRegistration() { + } + + public static void register(@Nonnull PluginBase plugin) { + ComponentRegistryProxy registry = physicsStoreRegistry(plugin); + + PhysicsStoreTypes.setUuidComponentType(registry.registerComponent(UuidComponent.class, + "Uuid", + UuidComponent.CODEC)); + PhysicsStoreTypes.setSpaceComponentType(registry.registerComponent(SpaceComponent.class, + "Space", + SpaceComponent.CODEC)); + PhysicsStoreTypes.setBodyComponentType(registry.registerComponent(BodyComponent.class, + "Body", + BodyComponent.CODEC)); + PhysicsStoreTypes.setDynamicsComponentType(registry.registerComponent(DynamicsComponent.class, + "Dynamics", + DynamicsComponent.CODEC)); + PhysicsStoreTypes.setColliderComponentType(registry.registerComponent(ColliderComponent.class, + "Collider", + ColliderComponent.CODEC)); + PhysicsStoreTypes.setShapeComponentType(registry.registerComponent(ShapeComponent.class, + "Shape", + ShapeComponent.CODEC)); + PhysicsStoreTypes.setMaterialComponentType(registry.registerComponent(MaterialComponent.class, + "Material", + MaterialComponent.CODEC)); + PhysicsStoreTypes.setCollisionFilterComponentType(registry.registerComponent( + CollisionFilterComponent.class, + "CollisionFilter", + CollisionFilterComponent.CODEC)); + PhysicsStoreTypes.setJointComponentType(registry.registerComponent(JointComponent.class, + "Joint", + JointComponent.CODEC)); + PhysicsStoreTypes.setTargetComponentType(registry.registerComponent(TargetComponent.class, + "Target", + TargetComponent.CODEC)); + PhysicsStoreTypes.setTerrainColliderComponentType(registry.registerComponent( + TerrainColliderComponent.class, + "TerrainCollider", + TerrainColliderComponent.CODEC)); + + PhysicsStoreTypes.setRuntimeResourceType(registry.registerResource( + PhysicsRuntimeResource.class, + PhysicsRuntimeResource::new)); + PhysicsStoreTypes.setRequestQueueResourceType(registry.registerResource( + PhysicsRequestQueueResource.class, + PhysicsRequestQueueResource::new)); + PhysicsStoreTypes.setIdentityIndexResourceType(registry.registerResource( + PhysicsIdentityIndexResource.class, + PhysicsIdentityIndexResource::new)); + PhysicsStoreTypes.setSnapshotResourceType(registry.registerResource( + PhysicsSnapshotResource.class, + PhysicsSnapshotResource::new)); + PhysicsStoreTypes.setPersistentStoreResourceType(registry.registerResource( + PersistentPhysicsStoreResource.class, + "PersistentPhysicsStore", + PersistentPhysicsStoreResource.CODEC)); + PhysicsStoreTypes.setRestoreStatusResourceType(registry.registerResource( + PhysicsRestoreStatusResource.class, + PhysicsRestoreStatusResource::new)); + PhysicsStoreTypes.setProfilingResourceType(registry.registerResource( + PhysicsProfilingResource.class, + PhysicsProfilingResource::new)); + PhysicsStoreTypes.setDebugResourceType(registry.registerResource( + PhysicsDebugResource.class, + PhysicsDebugResource::new)); + } + + @Nonnull + @SuppressWarnings("unchecked") + private static ComponentRegistryProxy physicsStoreRegistry( + @Nonnull PluginBase plugin) { + try { + Method method = plugin.getClass().getMethod(REGISTRY_METHOD); + return (ComponentRegistryProxy) method.invoke(plugin); + } catch (NoSuchMethodException exception) { + throw new IllegalStateException("Impulse requires the PhysicsStore early plugin to " + + "patch PluginBase." + REGISTRY_METHOD + "()", exception); + } catch (IllegalAccessException exception) { + throw new IllegalStateException("Cannot access patched PhysicsStore registry method", + exception); + } catch (InvocationTargetException exception) { + Throwable cause = exception.getCause(); + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + if (cause instanceof Error error) { + throw error; + } + throw new IllegalStateException("PhysicsStore registry method failed", cause); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java new file mode 100644 index 00000000..8987f695 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java @@ -0,0 +1,59 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import javax.annotation.Nonnull; + +/** + * Runtime-only debug toggles owned by PhysicsStore. + */ +public final class PhysicsDebugResource implements Resource { + + private boolean debugBodiesEnabled; + private boolean debugContactsEnabled; + private boolean debugJointsEnabled; + + public PhysicsDebugResource() { + } + + public boolean isDebugBodiesEnabled() { + return debugBodiesEnabled; + } + + public void setDebugBodiesEnabled(boolean debugBodiesEnabled) { + this.debugBodiesEnabled = debugBodiesEnabled; + } + + public boolean isDebugContactsEnabled() { + return debugContactsEnabled; + } + + public void setDebugContactsEnabled(boolean debugContactsEnabled) { + this.debugContactsEnabled = debugContactsEnabled; + } + + public boolean isDebugJointsEnabled() { + return debugJointsEnabled; + } + + public void setDebugJointsEnabled(boolean debugJointsEnabled) { + this.debugJointsEnabled = debugJointsEnabled; + } + + @Nonnull + @Override + public PhysicsDebugResource clone() { + PhysicsDebugResource copy = new PhysicsDebugResource(); + copy.debugBodiesEnabled = debugBodiesEnabled; + copy.debugContactsEnabled = debugContactsEnabled; + copy.debugJointsEnabled = debugJointsEnabled; + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.debugResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java new file mode 100644 index 00000000..f73a2d3f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java @@ -0,0 +1,101 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime identity indexes for UUID boundaries and backend handle hot paths. + */ +public final class PhysicsIdentityIndexResource implements Resource { + + @Nonnull + private final Map> refsByUuid = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Int2ObjectOpenHashMap> spaceRefsByHandle = + new Int2ObjectOpenHashMap<>(); + @Nonnull + private final Long2ObjectOpenHashMap> bodyRefsByHandle = + new Long2ObjectOpenHashMap<>(); + @Nonnull + private final Long2ObjectOpenHashMap> jointRefsByHandle = + new Long2ObjectOpenHashMap<>(); + + public PhysicsIdentityIndexResource() { + } + + public void putUuid(@Nonnull UUID uuid, @Nonnull Ref ref) { + refsByUuid.put(uuid, ref); + } + + @Nullable + public Ref getByUuid(@Nonnull UUID uuid) { + return refsByUuid.get(uuid); + } + + public void removeUuid(@Nonnull UUID uuid, @Nonnull Ref ref) { + refsByUuid.remove(uuid, ref); + } + + public void putSpaceHandle(@Nonnull BackendSpaceHandle handle, @Nonnull Ref ref) { + spaceRefsByHandle.put(handle.value(), ref); + } + + @Nullable + public Ref getBySpaceHandle(@Nonnull BackendSpaceHandle handle) { + return spaceRefsByHandle.get(handle.value()); + } + + public void putBodyHandle(@Nonnull BackendBodyHandle handle, @Nonnull Ref ref) { + bodyRefsByHandle.put(handle.value(), ref); + } + + @Nullable + public Ref getByBodyHandle(@Nonnull BackendBodyHandle handle) { + return bodyRefsByHandle.get(handle.value()); + } + + public void putJointHandle(@Nonnull BackendJointHandle handle, @Nonnull Ref ref) { + jointRefsByHandle.put(handle.value(), ref); + } + + @Nullable + public Ref getByJointHandle(@Nonnull BackendJointHandle handle) { + return jointRefsByHandle.get(handle.value()); + } + + public void clear() { + refsByUuid.clear(); + spaceRefsByHandle.clear(); + bodyRefsByHandle.clear(); + jointRefsByHandle.clear(); + } + + @Nonnull + @Override + public PhysicsIdentityIndexResource clone() { + PhysicsIdentityIndexResource copy = new PhysicsIdentityIndexResource(); + copy.refsByUuid.putAll(refsByUuid); + copy.spaceRefsByHandle.putAll(spaceRefsByHandle); + copy.bodyRefsByHandle.putAll(bodyRefsByHandle); + copy.jointRefsByHandle.putAll(jointRefsByHandle); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.identityIndexResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java new file mode 100644 index 00000000..3d27cacc --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java @@ -0,0 +1,97 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import javax.annotation.Nonnull; + +/** + * Runtime-only PhysicsStore profiling counters split by phase. + */ +public final class PhysicsProfilingResource implements Resource { + + private boolean enabled; + private long requestDrainNanos; + private long bindingNanos; + private long snapshotNanos; + private long persistenceCaptureNanos; + private long stepSubmitNanos; + private int queuedRequests; + private int publishedBodies; + + public PhysicsProfilingResource() { + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void recordLatest(long requestDrainNanos, + long bindingNanos, + long snapshotNanos, + long persistenceCaptureNanos, + long stepSubmitNanos, + int queuedRequests, + int publishedBodies) { + this.requestDrainNanos = requestDrainNanos; + this.bindingNanos = bindingNanos; + this.snapshotNanos = snapshotNanos; + this.persistenceCaptureNanos = persistenceCaptureNanos; + this.stepSubmitNanos = stepSubmitNanos; + this.queuedRequests = queuedRequests; + this.publishedBodies = publishedBodies; + } + + public long getRequestDrainNanos() { + return requestDrainNanos; + } + + public long getBindingNanos() { + return bindingNanos; + } + + public long getSnapshotNanos() { + return snapshotNanos; + } + + public long getPersistenceCaptureNanos() { + return persistenceCaptureNanos; + } + + public long getStepSubmitNanos() { + return stepSubmitNanos; + } + + public int getQueuedRequests() { + return queuedRequests; + } + + public int getPublishedBodies() { + return publishedBodies; + } + + @Nonnull + @Override + public PhysicsProfilingResource clone() { + PhysicsProfilingResource copy = new PhysicsProfilingResource(); + copy.enabled = enabled; + copy.recordLatest(requestDrainNanos, + bindingNanos, + snapshotNanos, + persistenceCaptureNanos, + stepSubmitNanos, + queuedRequests, + publishedBodies); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.profilingResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java new file mode 100644 index 00000000..1143f526 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java @@ -0,0 +1,59 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import javax.annotation.Nonnull; + +/** + * Copied request queue drained by PhysicsStore.tick(). + */ +public final class PhysicsRequestQueueResource implements Resource { + + @Nonnull + private final Queue requests = new ArrayDeque<>(); + + public PhysicsRequestQueueResource() { + } + + public void enqueue(@Nonnull PhysicsStoreRequest request) { + requests.add(request); + } + + @Nonnull + public List drain() { + List drained = new ArrayList<>(requests.size()); + PhysicsStoreRequest request; + while ((request = requests.poll()) != null) { + drained.add(request); + } + return drained; + } + + public int size() { + return requests.size(); + } + + public void clear() { + requests.clear(); + } + + @Nonnull + @Override + public PhysicsRequestQueueResource clone() { + PhysicsRequestQueueResource copy = new PhysicsRequestQueueResource(); + copy.requests.addAll(requests); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.requestQueueResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java new file mode 100644 index 00000000..3373a9c1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java @@ -0,0 +1,82 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import javax.annotation.Nonnull; + +/** + * Runtime-only restore status and skip accounting. + */ +public final class PhysicsRestoreStatusResource implements Resource { + + private boolean pending; + private boolean failed; + @Nonnull + private String failureMessage = ""; + @Nonnull + private final Object2IntMap softSkipsByReason = new Object2IntLinkedOpenHashMap<>(); + + public PhysicsRestoreStatusResource() { + } + + public boolean isPending() { + return pending; + } + + public void markPending() { + pending = true; + failed = false; + failureMessage = ""; + softSkipsByReason.clear(); + } + + public boolean isFailed() { + return failed; + } + + @Nonnull + public String getFailureMessage() { + return failureMessage; + } + + public void markFailed(@Nonnull String failureMessage) { + pending = false; + failed = true; + this.failureMessage = failureMessage; + } + + public void markComplete() { + pending = false; + failed = false; + failureMessage = ""; + } + + public void recordSoftSkip(@Nonnull String reason) { + softSkipsByReason.mergeInt(reason, 1, Integer::sum); + } + + @Nonnull + public Object2IntMap getSoftSkipsByReason() { + return new Object2IntLinkedOpenHashMap<>(softSkipsByReason); + } + + @Nonnull + @Override + public PhysicsRestoreStatusResource clone() { + PhysicsRestoreStatusResource copy = new PhysicsRestoreStatusResource(); + copy.pending = pending; + copy.failed = failed; + copy.failureMessage = failureMessage; + copy.softSkipsByReason.putAll(softSkipsByReason); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.restoreStatusResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java new file mode 100644 index 00000000..a3751576 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -0,0 +1,120 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime-only backend bindings for PhysicsStore spaces, bodies, and joints. + */ +public final class PhysicsRuntimeResource implements Resource { + + @Nonnull + private final Map runtimesByBackend = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map spaceHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map bodyHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map jointHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + private boolean started; + + public PhysicsRuntimeResource() { + } + + public boolean isStarted() { + return started; + } + + public void setStarted(boolean started) { + this.started = started; + } + + public void putRuntime(@Nonnull BackendId backendId, @Nonnull PhysicsBackendRuntime runtime) { + runtimesByBackend.put(backendId, runtime); + } + + @Nullable + public PhysicsBackendRuntime getRuntime(@Nonnull BackendId backendId) { + return runtimesByBackend.get(backendId); + } + + public void putSpaceHandle(@Nonnull UUID spaceUuid, @Nonnull BackendSpaceHandle handle) { + spaceHandlesByUuid.put(spaceUuid, handle); + } + + @Nullable + public BackendSpaceHandle getSpaceHandle(@Nonnull UUID spaceUuid) { + return spaceHandlesByUuid.get(spaceUuid); + } + + public void removeSpaceHandle(@Nonnull UUID spaceUuid) { + spaceHandlesByUuid.remove(spaceUuid); + } + + public void putBodyHandle(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle handle) { + bodyHandlesByUuid.put(bodyUuid, handle); + } + + @Nullable + public BackendBodyHandle getBodyHandle(@Nonnull UUID bodyUuid) { + return bodyHandlesByUuid.get(bodyUuid); + } + + public void removeBodyHandle(@Nonnull UUID bodyUuid) { + bodyHandlesByUuid.remove(bodyUuid); + } + + public void putJointHandle(@Nonnull UUID jointUuid, @Nonnull BackendJointHandle handle) { + jointHandlesByUuid.put(jointUuid, handle); + } + + @Nullable + public BackendJointHandle getJointHandle(@Nonnull UUID jointUuid) { + return jointHandlesByUuid.get(jointUuid); + } + + public void removeJointHandle(@Nonnull UUID jointUuid) { + jointHandlesByUuid.remove(jointUuid); + } + + public void clear() { + runtimesByBackend.clear(); + spaceHandlesByUuid.clear(); + bodyHandlesByUuid.clear(); + jointHandlesByUuid.clear(); + started = false; + } + + @Nonnull + @Override + public PhysicsRuntimeResource clone() { + PhysicsRuntimeResource copy = new PhysicsRuntimeResource(); + copy.runtimesByBackend.putAll(runtimesByBackend); + copy.spaceHandlesByUuid.putAll(spaceHandlesByUuid); + copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); + copy.jointHandlesByUuid.putAll(jointHandlesByUuid); + copy.started = started; + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.runtimeResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java new file mode 100644 index 00000000..45f6a8d8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -0,0 +1,46 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import javax.annotation.Nonnull; + +/** + * Latest copied PhysicsStore snapshot frame for projection, debug, and queries. + */ +public final class PhysicsSnapshotResource implements Resource { + + @Nonnull + private PhysicsStoreSnapshotFrame latestFrame = PhysicsStoreSnapshotFrame.EMPTY; + + public PhysicsSnapshotResource() { + } + + @Nonnull + public PhysicsStoreSnapshotFrame getLatestFrame() { + return latestFrame; + } + + public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { + latestFrame = frame; + } + + public void clear() { + latestFrame = PhysicsStoreSnapshotFrame.EMPTY; + } + + @Nonnull + @Override + public PhysicsSnapshotResource clone() { + PhysicsSnapshotResource copy = new PhysicsSnapshotResource(); + copy.latestFrame = latestFrame; + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.snapshotResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java new file mode 100644 index 00000000..8cd9070d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java @@ -0,0 +1,48 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Durable reference to a PhysicsStore row. + */ +public final class PhysicsPersistentRef { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PhysicsPersistentRef.class, + PhysicsPersistentRef::new) + .append(new KeyedCodec<>("Uuid", Codec.UUID_BINARY, false), + (ref, value) -> ref.uuid = value != null ? value : UUID.randomUUID(), + PhysicsPersistentRef::getUuid) + .add() + .build(); + + @Nonnull + private UUID uuid = UUID.randomUUID(); + + public PhysicsPersistentRef() { + } + + public PhysicsPersistentRef(@Nonnull UUID uuid) { + this.uuid = Objects.requireNonNull(uuid, "uuid"); + } + + @Nonnull + public UUID getUuid() { + return uuid; + } + + public void setUuid(@Nonnull UUID uuid) { + this.uuid = Objects.requireNonNull(uuid, "uuid"); + } + + @Nonnull + public PhysicsPersistentRef copy() { + return new PhysicsPersistentRef(uuid); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java new file mode 100644 index 00000000..d967d0fb --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java @@ -0,0 +1,62 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime-resolved PhysicsStore reference paired with its durable UUID. + */ +public final class PhysicsRef { + + @Nonnull + private final UUID uuid; + @Nullable + private final Ref ref; + + private PhysicsRef(@Nonnull UUID uuid, @Nullable Ref ref) { + this.uuid = Objects.requireNonNull(uuid, "uuid"); + this.ref = ref; + } + + @Nonnull + public static PhysicsRef persistent(@Nonnull UUID uuid) { + return new PhysicsRef(uuid, null); + } + + @Nonnull + public static PhysicsRef resolved(@Nonnull UUID uuid, @Nonnull Ref ref) { + return new PhysicsRef(uuid, Objects.requireNonNull(ref, "ref")); + } + + @Nonnull + public UUID getUuid() { + return uuid; + } + + @Nonnull + public PhysicsPersistentRef toPersistentRef() { + return new PhysicsPersistentRef(uuid); + } + + public boolean isResolved() { + return ref != null && ref.isValid(); + } + + @Nullable + public Ref getRef() { + return ref; + } + + @Nonnull + public Ref requireRef() { + Ref current = ref; + if (current == null || !current.isValid()) { + throw new IllegalStateException("PhysicsStore reference " + uuid + " is not resolved"); + } + return current; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java new file mode 100644 index 00000000..fc836e8b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java @@ -0,0 +1,45 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Access to the early-plugin-injected PhysicsStore on a Hytale world. + */ +public final class PhysicsStoreAccess { + + @Nonnull + private static final MethodHandle WORLD_GET_PHYSICS_STORE = findWorldAccessor(); + + private PhysicsStoreAccess() { + } + + @Nonnull + public static PhysicsStore require(@Nonnull World world) { + Objects.requireNonNull(world, "world"); + try { + return (PhysicsStore) WORLD_GET_PHYSICS_STORE.invoke(world); + } catch (RuntimeException | Error exception) { + throw exception; + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to access World.getPhysicsStore()", throwable); + } + } + + @Nonnull + private static MethodHandle findWorldAccessor() { + try { + return MethodHandles.publicLookup() + .findVirtual(World.class, "getPhysicsStore", MethodType.methodType(PhysicsStore.class)); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new IllegalStateException("Impulse requires the PhysicsStore early plugin. " + + "Install impulse-early-plugin as a Hytale early plugin before using PhysicsStore APIs.", + exception); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java new file mode 100644 index 00000000..0431f0b8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -0,0 +1,274 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Registered Hytale ECS type handles for the authoritative PhysicsStore. + */ +public final class PhysicsStoreTypes { + + @Nullable + private static ComponentType uuidComponentType; + @Nullable + private static ComponentType spaceComponentType; + @Nullable + private static ComponentType bodyComponentType; + @Nullable + private static ComponentType dynamicsComponentType; + @Nullable + private static ComponentType colliderComponentType; + @Nullable + private static ComponentType shapeComponentType; + @Nullable + private static ComponentType materialComponentType; + @Nullable + private static ComponentType collisionFilterComponentType; + @Nullable + private static ComponentType jointComponentType; + @Nullable + private static ComponentType targetComponentType; + @Nullable + private static ComponentType terrainColliderComponentType; + + @Nullable + private static ResourceType runtimeResourceType; + @Nullable + private static ResourceType requestQueueResourceType; + @Nullable + private static ResourceType identityIndexResourceType; + @Nullable + private static ResourceType snapshotResourceType; + @Nullable + private static ResourceType persistentStoreResourceType; + @Nullable + private static ResourceType restoreStatusResourceType; + @Nullable + private static ResourceType profilingResourceType; + @Nullable + private static ResourceType debugResourceType; + + private PhysicsStoreTypes() { + } + + public static void setUuidComponentType( + @Nonnull ComponentType type) { + uuidComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setSpaceComponentType( + @Nonnull ComponentType type) { + spaceComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setBodyComponentType( + @Nonnull ComponentType type) { + bodyComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setDynamicsComponentType( + @Nonnull ComponentType type) { + dynamicsComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setColliderComponentType( + @Nonnull ComponentType type) { + colliderComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setShapeComponentType( + @Nonnull ComponentType type) { + shapeComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setMaterialComponentType( + @Nonnull ComponentType type) { + materialComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setCollisionFilterComponentType( + @Nonnull ComponentType type) { + collisionFilterComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setJointComponentType( + @Nonnull ComponentType type) { + jointComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setTargetComponentType( + @Nonnull ComponentType type) { + targetComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setTerrainColliderComponentType( + @Nonnull ComponentType type) { + terrainColliderComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setRuntimeResourceType( + @Nonnull ResourceType type) { + runtimeResourceType = Objects.requireNonNull(type, "type"); + } + + public static void setRequestQueueResourceType( + @Nonnull ResourceType type) { + requestQueueResourceType = Objects.requireNonNull(type, "type"); + } + + public static void setIdentityIndexResourceType( + @Nonnull ResourceType type) { + identityIndexResourceType = Objects.requireNonNull(type, "type"); + } + + public static void setSnapshotResourceType( + @Nonnull ResourceType type) { + snapshotResourceType = Objects.requireNonNull(type, "type"); + } + + public static void setPersistentStoreResourceType( + @Nonnull ResourceType type) { + persistentStoreResourceType = Objects.requireNonNull(type, "type"); + } + + public static void setRestoreStatusResourceType( + @Nonnull ResourceType type) { + restoreStatusResourceType = Objects.requireNonNull(type, "type"); + } + + public static void setProfilingResourceType( + @Nonnull ResourceType type) { + profilingResourceType = Objects.requireNonNull(type, "type"); + } + + public static void setDebugResourceType( + @Nonnull ResourceType type) { + debugResourceType = Objects.requireNonNull(type, "type"); + } + + @Nonnull + public static ComponentType uuidComponentType() { + return require(uuidComponentType, "UuidComponent"); + } + + @Nonnull + public static ComponentType spaceComponentType() { + return require(spaceComponentType, "SpaceComponent"); + } + + @Nonnull + public static ComponentType bodyComponentType() { + return require(bodyComponentType, "BodyComponent"); + } + + @Nonnull + public static ComponentType dynamicsComponentType() { + return require(dynamicsComponentType, "DynamicsComponent"); + } + + @Nonnull + public static ComponentType colliderComponentType() { + return require(colliderComponentType, "ColliderComponent"); + } + + @Nonnull + public static ComponentType shapeComponentType() { + return require(shapeComponentType, "ShapeComponent"); + } + + @Nonnull + public static ComponentType materialComponentType() { + return require(materialComponentType, "MaterialComponent"); + } + + @Nonnull + public static ComponentType collisionFilterComponentType() { + return require(collisionFilterComponentType, "CollisionFilterComponent"); + } + + @Nonnull + public static ComponentType jointComponentType() { + return require(jointComponentType, "JointComponent"); + } + + @Nonnull + public static ComponentType targetComponentType() { + return require(targetComponentType, "TargetComponent"); + } + + @Nonnull + public static ComponentType terrainColliderComponentType() { + return require(terrainColliderComponentType, "TerrainColliderComponent"); + } + + @Nonnull + public static ResourceType runtimeResourceType() { + return require(runtimeResourceType, "PhysicsRuntimeResource"); + } + + @Nonnull + public static ResourceType requestQueueResourceType() { + return require(requestQueueResourceType, "PhysicsRequestQueueResource"); + } + + @Nonnull + public static ResourceType identityIndexResourceType() { + return require(identityIndexResourceType, "PhysicsIdentityIndexResource"); + } + + @Nonnull + public static ResourceType snapshotResourceType() { + return require(snapshotResourceType, "PhysicsSnapshotResource"); + } + + @Nonnull + public static ResourceType persistentStoreResourceType() { + return require(persistentStoreResourceType, "PersistentPhysicsStoreResource"); + } + + @Nonnull + public static ResourceType restoreStatusResourceType() { + return require(restoreStatusResourceType, "PhysicsRestoreStatusResource"); + } + + @Nonnull + public static ResourceType profilingResourceType() { + return require(profilingResourceType, "PhysicsProfilingResource"); + } + + @Nonnull + public static ResourceType debugResourceType() { + return require(debugResourceType, "PhysicsDebugResource"); + } + + @Nonnull + private static T require(@Nullable T type, @Nonnull String name) { + if (type == null) { + throw new IllegalStateException("PhysicsStore " + name + " is not registered"); + } + return type; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java new file mode 100644 index 00000000..eb5f349f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java @@ -0,0 +1,97 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Authored body identity, kind, and persistence policy. + */ +public final class BodyComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + BodyComponent.class, + BodyComponent::new) + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY, false), + (component, value) -> component.spaceUuid = value, + BodyComponent::getSpaceUuid) + .add() + .append(new KeyedCodec<>("Kind", new EnumCodec<>(PhysicsBodyKind.class), false), + (component, value) -> component.kind = value != null ? value : PhysicsBodyKind.BODY, + BodyComponent::getKind) + .add() + .append(new KeyedCodec<>("PersistenceMode", new EnumCodec<>(PhysicsBodyPersistenceMode.class), false), + (component, value) -> component.persistenceMode = value != null + ? value + : PhysicsBodyPersistenceMode.RUNTIME_ONLY, + BodyComponent::getPersistenceMode) + .add() + .build(); + + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private PhysicsBodyKind kind = PhysicsBodyKind.BODY; + @Nonnull + private PhysicsBodyPersistenceMode persistenceMode = PhysicsBodyPersistenceMode.RUNTIME_ONLY; + + public BodyComponent() { + } + + public BodyComponent(@Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.kind = Objects.requireNonNull(kind, "kind"); + this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + public void setSpaceUuid(@Nonnull UUID spaceUuid) { + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + } + + @Nonnull + public PhysicsBodyKind getKind() { + return kind; + } + + public void setKind(@Nonnull PhysicsBodyKind kind) { + this.kind = Objects.requireNonNull(kind, "kind"); + } + + @Nonnull + public PhysicsBodyPersistenceMode getPersistenceMode() { + return persistenceMode; + } + + public void setPersistenceMode(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { + this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.bodyComponentType(); + } + + @Nonnull + @Override + public BodyComponent clone() { + return new BodyComponent(spaceUuid, kind, persistenceMode); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java new file mode 100644 index 00000000..b07ea199 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java @@ -0,0 +1,171 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Collider row that binds a body to shape, material, and filter rows. + */ +public final class ColliderComponent implements Component { + + private static final Vector3f ZERO = new Vector3f(); + private static final Quaternionf IDENTITY = new Quaternionf(); + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ColliderComponent.class, + ColliderComponent::new) + .append(new KeyedCodec<>("BodyUuid", Codec.UUID_BINARY, false), + (component, value) -> component.bodyUuid = value, + ColliderComponent::getBodyUuid) + .add() + .append(new KeyedCodec<>("ShapeUuid", Codec.UUID_BINARY, false), + (component, value) -> component.shapeUuid = value, + ColliderComponent::getShapeUuid) + .add() + .append(new KeyedCodec<>("MaterialUuid", Codec.UUID_BINARY, false), + (component, value) -> component.materialUuid = value, + ColliderComponent::getMaterialUuid) + .add() + .append(new KeyedCodec<>("FilterUuid", Codec.UUID_BINARY, false), + (component, value) -> component.filterUuid = value, + ColliderComponent::getFilterUuid) + .add() + .append(new KeyedCodec<>("LocalPosition", Vector3fUtil.CODEC, false), + (component, value) -> component.localPosition.set(value != null ? value : ZERO), + ColliderComponent::getLocalPosition) + .add() + .append(new KeyedCodec<>("LocalRotation", ImpulseCodecs.QUATERNIONF, false), + (component, value) -> component.localRotation.set(value != null ? value : IDENTITY), + ColliderComponent::getLocalRotation) + .add() + .append(new KeyedCodec<>("Sensor", Codec.BOOLEAN, false), + (component, value) -> component.sensor = value != null && value, + ColliderComponent::isSensor) + .add() + .build(); + + @Nonnull + private UUID bodyUuid = new UUID(0L, 0L); + @Nonnull + private UUID shapeUuid = new UUID(0L, 0L); + @Nonnull + private UUID materialUuid = new UUID(0L, 0L); + @Nonnull + private UUID filterUuid = new UUID(0L, 0L); + @Nonnull + private final Vector3f localPosition = new Vector3f(); + @Nonnull + private final Quaternionf localRotation = new Quaternionf(); + private boolean sensor; + + public ColliderComponent() { + } + + public ColliderComponent(@Nonnull UUID bodyUuid, + @Nonnull UUID shapeUuid, + @Nonnull UUID materialUuid, + @Nonnull UUID filterUuid, + @Nonnull Vector3f localPosition, + @Nonnull Quaternionf localRotation, + boolean sensor) { + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); + this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); + this.filterUuid = Objects.requireNonNull(filterUuid, "filterUuid"); + this.localPosition.set(Objects.requireNonNull(localPosition, "localPosition")); + this.localRotation.set(Objects.requireNonNull(localRotation, "localRotation")); + this.sensor = sensor; + } + + @Nonnull + public UUID getBodyUuid() { + return bodyUuid; + } + + public void setBodyUuid(@Nonnull UUID bodyUuid) { + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + } + + @Nonnull + public UUID getShapeUuid() { + return shapeUuid; + } + + public void setShapeUuid(@Nonnull UUID shapeUuid) { + this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); + } + + @Nonnull + public UUID getMaterialUuid() { + return materialUuid; + } + + public void setMaterialUuid(@Nonnull UUID materialUuid) { + this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); + } + + @Nonnull + public UUID getFilterUuid() { + return filterUuid; + } + + public void setFilterUuid(@Nonnull UUID filterUuid) { + this.filterUuid = Objects.requireNonNull(filterUuid, "filterUuid"); + } + + @Nonnull + public Vector3f getLocalPosition() { + return new Vector3f(localPosition); + } + + public void setLocalPosition(@Nonnull Vector3f localPosition) { + this.localPosition.set(Objects.requireNonNull(localPosition, "localPosition")); + } + + @Nonnull + public Quaternionf getLocalRotation() { + return new Quaternionf(localRotation); + } + + public void setLocalRotation(@Nonnull Quaternionf localRotation) { + this.localRotation.set(Objects.requireNonNull(localRotation, "localRotation")); + } + + public boolean isSensor() { + return sensor; + } + + public void setSensor(boolean sensor) { + this.sensor = sensor; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.colliderComponentType(); + } + + @Nonnull + @Override + public ColliderComponent clone() { + return new ColliderComponent(bodyUuid, + shapeUuid, + materialUuid, + filterUuid, + localPosition, + localRotation, + sensor); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java new file mode 100644 index 00000000..49c11d73 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java @@ -0,0 +1,73 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import javax.annotation.Nonnull; + +/** + * Collision group/mask row referenced by colliders. + */ +public final class CollisionFilterComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + CollisionFilterComponent.class, + CollisionFilterComponent::new) + .append(new KeyedCodec<>("CollisionGroup", Codec.INTEGER, false), + (component, value) -> component.collisionGroup = value != null + ? value + : PhysicsCollisionFilters.DYNAMIC_BODY, + CollisionFilterComponent::getCollisionGroup) + .add() + .append(new KeyedCodec<>("CollisionMask", Codec.INTEGER, false), + (component, value) -> component.collisionMask = value != null + ? value + : PhysicsCollisionFilters.ALL, + CollisionFilterComponent::getCollisionMask) + .add() + .build(); + + private int collisionGroup = PhysicsCollisionFilters.DYNAMIC_BODY; + private int collisionMask = PhysicsCollisionFilters.ALL; + + public CollisionFilterComponent() { + } + + public CollisionFilterComponent(int collisionGroup, int collisionMask) { + this.collisionGroup = collisionGroup; + this.collisionMask = collisionMask; + } + + public int getCollisionGroup() { + return collisionGroup; + } + + public void setCollisionGroup(int collisionGroup) { + this.collisionGroup = collisionGroup; + } + + public int getCollisionMask() { + return collisionMask; + } + + public void setCollisionMask(int collisionMask) { + this.collisionMask = collisionMask; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.collisionFilterComponentType(); + } + + @Nonnull + @Override + public CollisionFilterComponent clone() { + return new CollisionFilterComponent(collisionGroup, collisionMask); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java new file mode 100644 index 00000000..502f7246 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java @@ -0,0 +1,123 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Authored motion mode, mass, damping, and CCD flags for a body row. + */ +public final class DynamicsComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + DynamicsComponent.class, + DynamicsComponent::new) + .append(new KeyedCodec<>("BodyType", new EnumCodec<>(PhysicsBodyType.class), false), + (component, value) -> component.bodyType = value != null ? value : PhysicsBodyType.DYNAMIC, + DynamicsComponent::getBodyType) + .add() + .append(new KeyedCodec<>("Mass", Codec.FLOAT, false), + (component, value) -> component.mass = value != null ? value : 1.0f, + DynamicsComponent::getMass) + .add() + .append(new KeyedCodec<>("LinearDamping", Codec.FLOAT, false), + (component, value) -> component.linearDamping = value != null ? value : 0.0f, + DynamicsComponent::getLinearDamping) + .add() + .append(new KeyedCodec<>("AngularDamping", Codec.FLOAT, false), + (component, value) -> component.angularDamping = value != null ? value : 0.0f, + DynamicsComponent::getAngularDamping) + .add() + .append(new KeyedCodec<>("ContinuousCollision", Codec.BOOLEAN, false), + (component, value) -> component.continuousCollisionEnabled = value != null && value, + DynamicsComponent::isContinuousCollisionEnabled) + .add() + .build(); + + @Nonnull + private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; + private float mass = 1.0f; + private float linearDamping; + private float angularDamping; + private boolean continuousCollisionEnabled; + + public DynamicsComponent() { + } + + public DynamicsComponent(@Nonnull PhysicsBodyType bodyType, + float mass, + float linearDamping, + float angularDamping, + boolean continuousCollisionEnabled) { + this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); + this.mass = mass; + this.linearDamping = linearDamping; + this.angularDamping = angularDamping; + this.continuousCollisionEnabled = continuousCollisionEnabled; + } + + @Nonnull + public PhysicsBodyType getBodyType() { + return bodyType; + } + + public void setBodyType(@Nonnull PhysicsBodyType bodyType) { + this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); + } + + public float getMass() { + return mass; + } + + public void setMass(float mass) { + this.mass = mass; + } + + public float getLinearDamping() { + return linearDamping; + } + + public void setLinearDamping(float linearDamping) { + this.linearDamping = linearDamping; + } + + public float getAngularDamping() { + return angularDamping; + } + + public void setAngularDamping(float angularDamping) { + this.angularDamping = angularDamping; + } + + public boolean isContinuousCollisionEnabled() { + return continuousCollisionEnabled; + } + + public void setContinuousCollisionEnabled(boolean continuousCollisionEnabled) { + this.continuousCollisionEnabled = continuousCollisionEnabled; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.dynamicsComponentType(); + } + + @Nonnull + @Override + public DynamicsComponent clone() { + return new DynamicsComponent(bodyType, + mass, + linearDamping, + angularDamping, + continuousCollisionEnabled); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java new file mode 100644 index 00000000..2cb53e05 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java @@ -0,0 +1,284 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * Authored joint row keyed by durable endpoint body UUIDs. + */ +public final class JointComponent implements Component { + + private static final Vector3f ZERO = new Vector3f(); + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + JointComponent.class, + JointComponent::new) + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY, false), + (component, value) -> component.spaceUuid = value, + JointComponent::getSpaceUuid) + .add() + .append(new KeyedCodec<>("BodyAUuid", Codec.UUID_BINARY, false), + (component, value) -> component.bodyAUuid = value, + JointComponent::getBodyAUuid) + .add() + .append(new KeyedCodec<>("BodyBUuid", Codec.UUID_BINARY, false), + (component, value) -> component.bodyBUuid = value, + JointComponent::getBodyBUuid) + .add() + .append(new KeyedCodec<>("Type", new EnumCodec<>(JointType.class), false), + (component, value) -> component.type = value != null ? value : JointType.FIXED, + JointComponent::getType) + .add() + .append(new KeyedCodec<>("AnchorA", Vector3fUtil.CODEC, false), + (component, value) -> component.anchorA.set(value != null ? value : ZERO), + JointComponent::getAnchorA) + .add() + .append(new KeyedCodec<>("AnchorB", Vector3fUtil.CODEC, false), + (component, value) -> component.anchorB.set(value != null ? value : ZERO), + JointComponent::getAnchorB) + .add() + .append(new KeyedCodec<>("Axis", Vector3fUtil.CODEC, false), + (component, value) -> component.axis.set(value != null ? value : ZERO), + JointComponent::getAxis) + .add() + .append(new KeyedCodec<>("LowerLimit", Codec.FLOAT, false), + (component, value) -> component.lowerLimit = value != null ? value : 0.0f, + JointComponent::getLowerLimit) + .add() + .append(new KeyedCodec<>("UpperLimit", Codec.FLOAT, false), + (component, value) -> component.upperLimit = value != null ? value : 0.0f, + JointComponent::getUpperLimit) + .add() + .append(new KeyedCodec<>("Enabled", Codec.BOOLEAN, false), + (component, value) -> component.enabled = value == null || value, + JointComponent::isEnabled) + .add() + .append(new KeyedCodec<>("MotorEnabled", Codec.BOOLEAN, false), + (component, value) -> component.motorEnabled = value != null && value, + JointComponent::isMotorEnabled) + .add() + .append(new KeyedCodec<>("MotorTargetVelocity", Codec.FLOAT, false), + (component, value) -> component.motorTargetVelocity = value != null ? value : 0.0f, + JointComponent::getMotorTargetVelocity) + .add() + .append(new KeyedCodec<>("MotorMaxForce", Codec.FLOAT, false), + (component, value) -> component.motorMaxForce = value != null ? value : 0.0f, + JointComponent::getMotorMaxForce) + .add() + .append(new KeyedCodec<>("SpringRestLength", Codec.FLOAT, false), + (component, value) -> component.springRestLength = value != null ? value : 0.0f, + JointComponent::getSpringRestLength) + .add() + .append(new KeyedCodec<>("SpringStiffness", Codec.FLOAT, false), + (component, value) -> component.springStiffness = value != null ? value : 0.0f, + JointComponent::getSpringStiffness) + .add() + .append(new KeyedCodec<>("SpringDamping", Codec.FLOAT, false), + (component, value) -> component.springDamping = value != null ? value : 0.0f, + JointComponent::getSpringDamping) + .add() + .build(); + + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private UUID bodyAUuid = new UUID(0L, 0L); + @Nonnull + private UUID bodyBUuid = new UUID(0L, 0L); + @Nonnull + private JointType type = JointType.FIXED; + @Nonnull + private final Vector3f anchorA = new Vector3f(); + @Nonnull + private final Vector3f anchorB = new Vector3f(); + @Nonnull + private final Vector3f axis = new Vector3f(); + private float lowerLimit; + private float upperLimit; + private boolean enabled = true; + private boolean motorEnabled; + private float motorTargetVelocity; + private float motorMaxForce; + private float springRestLength; + private float springStiffness; + private float springDamping; + + public JointComponent() { + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + public void setSpaceUuid(@Nonnull UUID spaceUuid) { + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + } + + @Nonnull + public UUID getBodyAUuid() { + return bodyAUuid; + } + + public void setBodyAUuid(@Nonnull UUID bodyAUuid) { + this.bodyAUuid = Objects.requireNonNull(bodyAUuid, "bodyAUuid"); + } + + @Nonnull + public UUID getBodyBUuid() { + return bodyBUuid; + } + + public void setBodyBUuid(@Nonnull UUID bodyBUuid) { + this.bodyBUuid = Objects.requireNonNull(bodyBUuid, "bodyBUuid"); + } + + @Nonnull + public JointType getType() { + return type; + } + + public void setType(@Nonnull JointType type) { + this.type = Objects.requireNonNull(type, "type"); + } + + @Nonnull + public Vector3f getAnchorA() { + return new Vector3f(anchorA); + } + + public void setAnchorA(@Nonnull Vector3f anchorA) { + this.anchorA.set(Objects.requireNonNull(anchorA, "anchorA")); + } + + @Nonnull + public Vector3f getAnchorB() { + return new Vector3f(anchorB); + } + + public void setAnchorB(@Nonnull Vector3f anchorB) { + this.anchorB.set(Objects.requireNonNull(anchorB, "anchorB")); + } + + @Nonnull + public Vector3f getAxis() { + return new Vector3f(axis); + } + + public void setAxis(@Nonnull Vector3f axis) { + this.axis.set(Objects.requireNonNull(axis, "axis")); + } + + public float getLowerLimit() { + return lowerLimit; + } + + public void setLowerLimit(float lowerLimit) { + this.lowerLimit = lowerLimit; + } + + public float getUpperLimit() { + return upperLimit; + } + + public void setUpperLimit(float upperLimit) { + this.upperLimit = upperLimit; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isMotorEnabled() { + return motorEnabled; + } + + public void setMotorEnabled(boolean motorEnabled) { + this.motorEnabled = motorEnabled; + } + + public float getMotorTargetVelocity() { + return motorTargetVelocity; + } + + public void setMotorTargetVelocity(float motorTargetVelocity) { + this.motorTargetVelocity = motorTargetVelocity; + } + + public float getMotorMaxForce() { + return motorMaxForce; + } + + public void setMotorMaxForce(float motorMaxForce) { + this.motorMaxForce = motorMaxForce; + } + + public float getSpringRestLength() { + return springRestLength; + } + + public void setSpringRestLength(float springRestLength) { + this.springRestLength = springRestLength; + } + + public float getSpringStiffness() { + return springStiffness; + } + + public void setSpringStiffness(float springStiffness) { + this.springStiffness = springStiffness; + } + + public float getSpringDamping() { + return springDamping; + } + + public void setSpringDamping(float springDamping) { + this.springDamping = springDamping; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.jointComponentType(); + } + + @Nonnull + @Override + public JointComponent clone() { + JointComponent copy = new JointComponent(); + copy.spaceUuid = spaceUuid; + copy.bodyAUuid = bodyAUuid; + copy.bodyBUuid = bodyBUuid; + copy.type = type; + copy.anchorA.set(anchorA); + copy.anchorB.set(anchorB); + copy.axis.set(axis); + copy.lowerLimit = lowerLimit; + copy.upperLimit = upperLimit; + copy.enabled = enabled; + copy.motorEnabled = motorEnabled; + copy.motorTargetVelocity = motorTargetVelocity; + copy.motorMaxForce = motorMaxForce; + copy.springRestLength = springRestLength; + copy.springStiffness = springStiffness; + copy.springDamping = springDamping; + return copy; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java new file mode 100644 index 00000000..c6cf0065 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java @@ -0,0 +1,68 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import javax.annotation.Nonnull; + +/** + * Physical material row referenced by colliders. + */ +public final class MaterialComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + MaterialComponent.class, + MaterialComponent::new) + .append(new KeyedCodec<>("Friction", Codec.FLOAT, false), + (component, value) -> component.friction = value != null ? value : 0.5f, + MaterialComponent::getFriction) + .add() + .append(new KeyedCodec<>("Restitution", Codec.FLOAT, false), + (component, value) -> component.restitution = value != null ? value : 0.0f, + MaterialComponent::getRestitution) + .add() + .build(); + + private float friction = 0.5f; + private float restitution; + + public MaterialComponent() { + } + + public MaterialComponent(float friction, float restitution) { + this.friction = friction; + this.restitution = restitution; + } + + public float getFriction() { + return friction; + } + + public void setFriction(float friction) { + this.friction = friction; + } + + public float getRestitution() { + return restitution; + } + + public void setRestitution(float restitution) { + this.restitution = restitution; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.materialComponentType(); + } + + @Nonnull + @Override + public MaterialComponent clone() { + return new MaterialComponent(friction, restitution); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java new file mode 100644 index 00000000..14590d7d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java @@ -0,0 +1,192 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Authored collision shape row shared by one or more colliders. + */ +public final class ShapeComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ShapeComponent.class, + ShapeComponent::new) + .append(new KeyedCodec<>("ShapeType", new EnumCodec<>(ShapeType.class), false), + (component, value) -> component.shapeType = value != null ? value : ShapeType.BOX, + ShapeComponent::getShapeType) + .add() + .append(new KeyedCodec<>("HalfExtentX", Codec.FLOAT, false), + (component, value) -> component.halfExtentX = value != null ? value : 0.5f, + ShapeComponent::getHalfExtentX) + .add() + .append(new KeyedCodec<>("HalfExtentY", Codec.FLOAT, false), + (component, value) -> component.halfExtentY = value != null ? value : 0.5f, + ShapeComponent::getHalfExtentY) + .add() + .append(new KeyedCodec<>("HalfExtentZ", Codec.FLOAT, false), + (component, value) -> component.halfExtentZ = value != null ? value : 0.5f, + ShapeComponent::getHalfExtentZ) + .add() + .append(new KeyedCodec<>("Radius", Codec.FLOAT, false), + (component, value) -> component.radius = value != null ? value : 0.5f, + ShapeComponent::getRadius) + .add() + .append(new KeyedCodec<>("HalfHeight", Codec.FLOAT, false), + (component, value) -> component.halfHeight = value != null ? value : 0.5f, + ShapeComponent::getHalfHeight) + .add() + .append(new KeyedCodec<>("Axis", new EnumCodec<>(PhysicsAxis.class), false), + (component, value) -> component.axis = value != null ? value : PhysicsAxis.Y, + ShapeComponent::getAxis) + .add() + .append(new KeyedCodec<>("GroundY", Codec.FLOAT, false), + (component, value) -> component.groundY = value != null ? value : 0.0f, + ShapeComponent::getGroundY) + .add() + .append(new KeyedCodec<>("ResourceKey", Codec.STRING, false), + (component, value) -> component.resourceKey = value != null ? value : "", + ShapeComponent::getResourceKey) + .add() + .build(); + + @Nonnull + private ShapeType shapeType = ShapeType.BOX; + private float halfExtentX = 0.5f; + private float halfExtentY = 0.5f; + private float halfExtentZ = 0.5f; + private float radius = 0.5f; + private float halfHeight = 0.5f; + @Nonnull + private PhysicsAxis axis = PhysicsAxis.Y; + private float groundY; + @Nonnull + private String resourceKey = ""; + + public ShapeComponent() { + } + + public ShapeComponent(@Nonnull ShapeType shapeType, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + @Nonnull PhysicsAxis axis, + float groundY, + @Nonnull String resourceKey) { + this.shapeType = Objects.requireNonNull(shapeType, "shapeType"); + this.halfExtentX = halfExtentX; + this.halfExtentY = halfExtentY; + this.halfExtentZ = halfExtentZ; + this.radius = radius; + this.halfHeight = halfHeight; + this.axis = Objects.requireNonNull(axis, "axis"); + this.groundY = groundY; + this.resourceKey = Objects.requireNonNull(resourceKey, "resourceKey"); + } + + @Nonnull + public ShapeType getShapeType() { + return shapeType; + } + + public void setShapeType(@Nonnull ShapeType shapeType) { + this.shapeType = Objects.requireNonNull(shapeType, "shapeType"); + } + + public float getHalfExtentX() { + return halfExtentX; + } + + public void setHalfExtentX(float halfExtentX) { + this.halfExtentX = halfExtentX; + } + + public float getHalfExtentY() { + return halfExtentY; + } + + public void setHalfExtentY(float halfExtentY) { + this.halfExtentY = halfExtentY; + } + + public float getHalfExtentZ() { + return halfExtentZ; + } + + public void setHalfExtentZ(float halfExtentZ) { + this.halfExtentZ = halfExtentZ; + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + } + + public float getHalfHeight() { + return halfHeight; + } + + public void setHalfHeight(float halfHeight) { + this.halfHeight = halfHeight; + } + + @Nonnull + public PhysicsAxis getAxis() { + return axis; + } + + public void setAxis(@Nonnull PhysicsAxis axis) { + this.axis = Objects.requireNonNull(axis, "axis"); + } + + public float getGroundY() { + return groundY; + } + + public void setGroundY(float groundY) { + this.groundY = groundY; + } + + @Nonnull + public String getResourceKey() { + return resourceKey; + } + + public void setResourceKey(@Nonnull String resourceKey) { + this.resourceKey = Objects.requireNonNull(resourceKey, "resourceKey"); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.shapeComponentType(); + } + + @Nonnull + @Override + public ShapeComponent clone() { + return new ShapeComponent(shapeType, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axis, + groundY, + resourceKey); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java new file mode 100644 index 00000000..2b8c4b2b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java @@ -0,0 +1,85 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * Authored backend and gravity definition for one physics space row. + */ +public final class SpaceComponent implements Component { + + private static final Vector3f DEFAULT_GRAVITY = new Vector3f(0.0f, -9.81f, 0.0f); + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpaceComponent.class, + SpaceComponent::new) + .append(new KeyedCodec<>("BackendId", Codec.STRING, false), + (component, value) -> component.backendId = value != null && !value.isBlank() + ? value + : "", + SpaceComponent::getBackendIdValue) + .add() + .append(new KeyedCodec<>("Gravity", Vector3fUtil.CODEC, false), + (component, value) -> component.gravity.set(value != null ? value : DEFAULT_GRAVITY), + SpaceComponent::getGravity) + .add() + .build(); + + @Nonnull + private String backendId = ""; + @Nonnull + private final Vector3f gravity = new Vector3f(DEFAULT_GRAVITY); + + public SpaceComponent() { + } + + public SpaceComponent(@Nonnull BackendId backendId, @Nonnull Vector3f gravity) { + this.backendId = Objects.requireNonNull(backendId, "backendId").value(); + this.gravity.set(Objects.requireNonNull(gravity, "gravity")); + } + + @Nonnull + public BackendId getBackendId() { + return new BackendId(backendId); + } + + public void setBackendId(@Nonnull BackendId backendId) { + this.backendId = Objects.requireNonNull(backendId, "backendId").value(); + } + + @Nonnull + public String getBackendIdValue() { + return backendId; + } + + @Nonnull + public Vector3f getGravity() { + return new Vector3f(gravity); + } + + public void setGravity(@Nonnull Vector3f gravity) { + this.gravity.set(Objects.requireNonNull(gravity, "gravity")); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.spaceComponentType(); + } + + @Nonnull + @Override + public SpaceComponent clone() { + return new SpaceComponent(getBackendId(), gravity); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java new file mode 100644 index 00000000..ccef029d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java @@ -0,0 +1,123 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Kinematic target state applied by PhysicsStore systems to a bound body. + */ +public final class TargetComponent implements Component { + + private static final Vector3f ZERO = new Vector3f(); + private static final Quaternionf IDENTITY = new Quaternionf(); + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + TargetComponent.class, + TargetComponent::new) + .append(new KeyedCodec<>("Active", Codec.BOOLEAN, false), + (component, value) -> component.active = value != null && value, + TargetComponent::isActive) + .add() + .append(new KeyedCodec<>("Position", Vector3fUtil.CODEC, false), + (component, value) -> component.position.set(value != null ? value : ZERO), + TargetComponent::getPosition) + .add() + .append(new KeyedCodec<>("Rotation", ImpulseCodecs.QUATERNIONF, false), + (component, value) -> component.rotation.set(value != null ? value : IDENTITY), + TargetComponent::getRotation) + .add() + .append(new KeyedCodec<>("LinearVelocity", Vector3fUtil.CODEC, false), + (component, value) -> component.linearVelocity.set(value != null ? value : ZERO), + TargetComponent::getLinearVelocity) + .add() + .append(new KeyedCodec<>("AngularVelocity", Vector3fUtil.CODEC, false), + (component, value) -> component.angularVelocity.set(value != null ? value : ZERO), + TargetComponent::getAngularVelocity) + .add() + .build(); + + private boolean active; + @Nonnull + private final Vector3f position = new Vector3f(); + @Nonnull + private final Quaternionf rotation = new Quaternionf(); + @Nonnull + private final Vector3f linearVelocity = new Vector3f(); + @Nonnull + private final Vector3f angularVelocity = new Vector3f(); + + public TargetComponent() { + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Nonnull + public Vector3f getPosition() { + return new Vector3f(position); + } + + public void setPosition(@Nonnull Vector3f position) { + this.position.set(position); + } + + @Nonnull + public Quaternionf getRotation() { + return new Quaternionf(rotation); + } + + public void setRotation(@Nonnull Quaternionf rotation) { + this.rotation.set(rotation); + } + + @Nonnull + public Vector3f getLinearVelocity() { + return new Vector3f(linearVelocity); + } + + public void setLinearVelocity(@Nonnull Vector3f linearVelocity) { + this.linearVelocity.set(linearVelocity); + } + + @Nonnull + public Vector3f getAngularVelocity() { + return new Vector3f(angularVelocity); + } + + public void setAngularVelocity(@Nonnull Vector3f angularVelocity) { + this.angularVelocity.set(angularVelocity); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.targetComponentType(); + } + + @Nonnull + @Override + public TargetComponent clone() { + TargetComponent copy = new TargetComponent(); + copy.active = active; + copy.position.set(position); + copy.rotation.set(rotation); + copy.linearVelocity.set(linearVelocity); + copy.angularVelocity.set(angularVelocity); + return copy; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java new file mode 100644 index 00000000..260432b0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java @@ -0,0 +1,158 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Terrain collider row mirrored from ChunkStore terrain source data. + */ +public final class TerrainColliderComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + TerrainColliderComponent.class, + TerrainColliderComponent::new) + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY, false), + (component, value) -> component.spaceUuid = value, + TerrainColliderComponent::getSpaceUuid) + .add() + .append(new KeyedCodec<>("SourceKey", Codec.STRING, false), + (component, value) -> component.sourceKey = value != null ? value : "", + TerrainColliderComponent::getSourceKey) + .add() + .append(new KeyedCodec<>("ChunkX", Codec.INTEGER, false), + (component, value) -> component.chunkX = value != null ? value : 0, + TerrainColliderComponent::getChunkX) + .add() + .append(new KeyedCodec<>("SectionY", Codec.INTEGER, false), + (component, value) -> component.sectionY = value != null ? value : 0, + TerrainColliderComponent::getSectionY) + .add() + .append(new KeyedCodec<>("ChunkZ", Codec.INTEGER, false), + (component, value) -> component.chunkZ = value != null ? value : 0, + TerrainColliderComponent::getChunkZ) + .add() + .append(new KeyedCodec<>("PayloadResourceKey", Codec.STRING, false), + (component, value) -> component.payloadResourceKey = value != null ? value : "", + TerrainColliderComponent::getPayloadResourceKey) + .add() + .append(new KeyedCodec<>("Retained", Codec.BOOLEAN, false), + (component, value) -> component.retained = value == null || value, + TerrainColliderComponent::isRetained) + .add() + .build(); + + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private String sourceKey = ""; + private int chunkX; + private int sectionY; + private int chunkZ; + @Nonnull + private String payloadResourceKey = ""; + private boolean retained = true; + + public TerrainColliderComponent() { + } + + public TerrainColliderComponent(@Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + int chunkX, + int sectionY, + int chunkZ, + @Nonnull String payloadResourceKey, + boolean retained) { + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); + this.chunkX = chunkX; + this.sectionY = sectionY; + this.chunkZ = chunkZ; + this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); + this.retained = retained; + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + public void setSpaceUuid(@Nonnull UUID spaceUuid) { + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + } + + @Nonnull + public String getSourceKey() { + return sourceKey; + } + + public void setSourceKey(@Nonnull String sourceKey) { + this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); + } + + public int getChunkX() { + return chunkX; + } + + public void setChunkX(int chunkX) { + this.chunkX = chunkX; + } + + public int getSectionY() { + return sectionY; + } + + public void setSectionY(int sectionY) { + this.sectionY = sectionY; + } + + public int getChunkZ() { + return chunkZ; + } + + public void setChunkZ(int chunkZ) { + this.chunkZ = chunkZ; + } + + @Nonnull + public String getPayloadResourceKey() { + return payloadResourceKey; + } + + public void setPayloadResourceKey(@Nonnull String payloadResourceKey) { + this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); + } + + public boolean isRetained() { + return retained; + } + + public void setRetained(boolean retained) { + this.retained = retained; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.terrainColliderComponentType(); + } + + @Nonnull + @Override + public TerrainColliderComponent clone() { + return new TerrainColliderComponent(spaceUuid, + sourceKey, + chunkX, + sectionY, + chunkZ, + payloadResourceKey, + retained); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java new file mode 100644 index 00000000..8d763463 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java @@ -0,0 +1,58 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Durable identity for one PhysicsStore row. + */ +public final class UuidComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + UuidComponent.class, + UuidComponent::new) + .append(new KeyedCodec<>("Uuid", Codec.UUID_BINARY, false), + (component, value) -> component.uuid = value != null ? value : UUID.randomUUID(), + UuidComponent::getUuid) + .add() + .build(); + + @Nonnull + private UUID uuid = UUID.randomUUID(); + + public UuidComponent() { + } + + public UuidComponent(@Nonnull UUID uuid) { + this.uuid = Objects.requireNonNull(uuid, "uuid"); + } + + @Nonnull + public UUID getUuid() { + return uuid; + } + + public void setUuid(@Nonnull UUID uuid) { + this.uuid = Objects.requireNonNull(uuid, "uuid"); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.uuidComponentType(); + } + + @Nonnull + @Override + public UuidComponent clone() { + return new UuidComponent(uuid); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java new file mode 100644 index 00000000..070fc354 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java @@ -0,0 +1,65 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Copied kinematic target request from gameplay/control systems. + */ +public record BodyTargetRequest(@Nonnull UUID requestUuid, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity) implements PhysicsStoreRequest { + + public BodyTargetRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + position = new Vector3f(Objects.requireNonNull(position, "position")); + rotation = new Quaternionf(Objects.requireNonNull(rotation, "rotation")); + linearVelocity = new Vector3f(Objects.requireNonNull(linearVelocity, "linearVelocity")); + angularVelocity = new Vector3f(Objects.requireNonNull(angularVelocity, "angularVelocity")); + } + + @Nonnull + public static BodyTargetRequest of(@Nonnull UUID bodyUuid, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity) { + return new BodyTargetRequest(UUID.randomUUID(), + bodyUuid, + position, + rotation, + linearVelocity, + angularVelocity); + } + + @Nonnull + @Override + public Vector3f position() { + return new Vector3f(position); + } + + @Nonnull + @Override + public Quaternionf rotation() { + return new Quaternionf(rotation); + } + + @Nonnull + @Override + public Vector3f linearVelocity() { + return new Vector3f(linearVelocity); + } + + @Nonnull + @Override + public Vector3f angularVelocity() { + return new Vector3f(angularVelocity); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java new file mode 100644 index 00000000..290efa74 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java @@ -0,0 +1,13 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request boundary for cross-store PhysicsStore communication. + */ +public interface PhysicsStoreRequest { + + @Nonnull + UUID requestUuid(); +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java new file mode 100644 index 00000000..1fbefe40 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java @@ -0,0 +1,58 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied terrain collider request emitted from chunk/world-collision code. + */ +public record TerrainColliderRequest(@Nonnull UUID requestUuid, + @Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + int chunkX, + int sectionY, + int chunkZ, + @Nonnull String payloadResourceKey, + boolean remove) implements PhysicsStoreRequest { + + public TerrainColliderRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(sourceKey, "sourceKey"); + Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); + } + + @Nonnull + public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + int chunkX, + int sectionY, + int chunkZ, + @Nonnull String payloadResourceKey) { + return new TerrainColliderRequest(UUID.randomUUID(), + spaceUuid, + sourceKey, + chunkX, + sectionY, + chunkZ, + payloadResourceKey, + false); + } + + @Nonnull + public static TerrainColliderRequest remove(@Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + int chunkX, + int sectionY, + int chunkZ) { + return new TerrainColliderRequest(UUID.randomUUID(), + spaceUuid, + sourceKey, + chunkX, + sectionY, + chunkZ, + "", + true); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java new file mode 100644 index 00000000..7e7586e1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java @@ -0,0 +1,55 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; + +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Copied body snapshot published out of PhysicsStore for projection and queries. + */ +public record PhysicsStoreBodySnapshot(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + boolean sleeping) { + + public PhysicsStoreBodySnapshot { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(bodyType, "bodyType"); + position = new Vector3f(Objects.requireNonNull(position, "position")); + rotation = new Quaternionf(Objects.requireNonNull(rotation, "rotation")); + linearVelocity = new Vector3f(Objects.requireNonNull(linearVelocity, "linearVelocity")); + angularVelocity = new Vector3f(Objects.requireNonNull(angularVelocity, "angularVelocity")); + } + + @Nonnull + @Override + public Vector3f position() { + return new Vector3f(position); + } + + @Nonnull + @Override + public Quaternionf rotation() { + return new Quaternionf(rotation); + } + + @Nonnull + @Override + public Vector3f linearVelocity() { + return new Vector3f(linearVelocity); + } + + @Nonnull + @Override + public Vector3f angularVelocity() { + return new Vector3f(angularVelocity); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java new file mode 100644 index 00000000..a0325ff8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java @@ -0,0 +1,20 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; + +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Immutable copied snapshot frame published by PhysicsStore after a completed backend step. + */ +public record PhysicsStoreSnapshotFrame(long sequence, + float dt, + @Nonnull List bodies) { + + public static final PhysicsStoreSnapshotFrame EMPTY = + new PhysicsStoreSnapshotFrame(0L, 0.0f, List.of()); + + public PhysicsStoreSnapshotFrame { + bodies = List.copyOf(Objects.requireNonNull(bodies, "bodies")); + } +} diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 71a0fb41..d0085c38 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -12,6 +12,10 @@ exports dev.hytalemodding.impulse.core.plugin.modules.control; exports dev.hytalemodding.impulse.core.plugin.modules.worldcollision; exports dev.hytalemodding.impulse.core.plugin.persistence; + exports dev.hytalemodding.impulse.core.plugin.physicsstore; + exports dev.hytalemodding.impulse.core.plugin.physicsstore.components; + exports dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + exports dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; exports dev.hytalemodding.impulse.core.plugin.resources; exports dev.hytalemodding.impulse.core.plugin.settings; exports dev.hytalemodding.impulse.core.plugin.simulation; From 28ba4da2fa0b33cfe01610ad2fb1182ca4da742f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 14:43:38 +0200 Subject: [PATCH 004/534] refactor(core): project entities from physics store snapshots Signed-off-by: Blovien --- .../systems/sync/PhysicsSyncSystem.java | 63 +++++++++++++++++++ .../PhysicsBodyAttachmentComponent.java | 32 +++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 0d271eb6..deff5841 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -15,10 +15,12 @@ import com.hypixel.hytale.server.core.modules.entity.system.TransformSystems; import com.hypixel.hytale.server.core.modules.entity.system.UpdateLocationSystems; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; @@ -27,9 +29,15 @@ import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; @@ -77,6 +85,9 @@ public class PhysicsSyncSystem extends EntityTickingSystem { ThreadLocal.withInitial(List::of); @Nonnull private final ThreadLocal syncNanos = ThreadLocal.withInitial(() -> 0L); + @Nonnull + private final ThreadLocal> physicsStoreSnapshots = + ThreadLocal.withInitial(Map::of); /** * Hytale may run entity ticks in parallel. Each tick task needs independent temporary objects @@ -101,6 +112,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { ? profiling.beginSyncSample() : null; long startNanos = collector != null ? System.nanoTime() : 0L; try { + physicsStoreSnapshots.set(collectPhysicsStoreSnapshots(store)); super.tick(dt, systemIndex, store); } finally { if (collector != null) { @@ -108,6 +120,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } playerInterests.remove(); syncNanos.remove(); + physicsStoreSnapshots.remove(); } } @@ -130,6 +143,18 @@ public void tick(float dt, if (collector != null) { collector.incrementBodiesInspected(); } + PhysicsStoreBodySnapshot physicsStoreSnapshot = + physicsStoreSnapshots.get().get(attachment.getPhysicsBodyUuidOrLegacy()); + if (physicsStoreSnapshot != null) { + if (!PhysicsTransformAuthority.shouldApplyBodyTransform(attachment)) { + return; + } + applyPhysicsStoreSnapshot(transform, attachment, physicsStoreSnapshot, local); + if (collector != null) { + collector.incrementBodiesSynced(); + } + return; + } PhysicsBodyRegistrationView registration = resource.getBodyRegistrationView(attachment.getBodyKey()); if (registration == null) { @@ -255,6 +280,44 @@ public void tick(float dt, } } + @Nonnull + private static Map collectPhysicsStoreSnapshots( + @Nonnull Store store) { + PhysicsStore physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()); + PhysicsSnapshotResource snapshotResource = physicsStore.getStore().getResource( + PhysicsSnapshotResource.getResourceType()); + PhysicsStoreSnapshotFrame frame = snapshotResource.getLatestFrame(); + if (frame.bodies().isEmpty()) { + return Map.of(); + } + Map snapshots = new Object2ObjectOpenHashMap<>(); + for (PhysicsStoreBodySnapshot body : frame.bodies()) { + snapshots.put(body.bodyUuid(), body); + } + return snapshots; + } + + private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transform, + @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull PhysicsStoreBodySnapshot snapshot, + @Nonnull Scratch scratch) { + scratch.position.set(snapshot.position()); + scratch.rotation.set(snapshot.rotation()); + PhysicsVisualPoseMath.visualPositionFromBodyPose(scratch.position, + scratch.rotation, + attachment.resolveVisualOriginOffsetY(0.0f), + attachment.getLocalPositionOffset(), + scratch.visualPosition, + scratch.worldOffset); + scratch.visualRotation.set(scratch.rotation); + scratch.visualRotation.mul(attachment.getLocalRotationOffset()); + transform.getPosition().set(scratch.visualPosition.x, + scratch.visualPosition.y, + scratch.visualPosition.z); + scratch.visualRotation.getEulerAnglesYXZ(scratch.euler); + transform.getRotation().set(scratch.euler.x, scratch.euler.y, scratch.euler.z); + } + private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { double dx = from.x - to.x; double dy = from.y - to.y; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java index bf7f06a2..3bb0d3f9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java @@ -41,6 +41,10 @@ public class PhysicsBodyAttachmentComponent implements Component { : RigidBodyKey.random(), PhysicsBodyAttachmentComponent::getBodyKeyValue) .add() + .append(new KeyedCodec<>("PhysicsBodyUuid", Codec.UUID_BINARY, false), + (component, value) -> component.physicsBodyUuid = value, + PhysicsBodyAttachmentComponent::getPhysicsBodyUuid) + .add() .append(new KeyedCodec<>("SpaceId", Codec.INTEGER, false), (component, value) -> component.spaceId = value != null && value > 0 ? new SpaceId(value) @@ -79,6 +83,9 @@ public class PhysicsBodyAttachmentComponent implements Component { private RigidBodyKey bodyKey = RigidBodyKey.random(); + @Nullable + private UUID physicsBodyUuid; + @Setter @Getter @Nullable @@ -151,6 +158,13 @@ public PhysicsBodyAttachmentComponent(@Nonnull RigidBodyKey bodyKey, this.visualOriginOffsetY = normalizeVisualOriginOffsetY(visualOriginOffsetY); } + @Nonnull + public static PhysicsBodyAttachmentComponent physicsStoreEntity(@Nonnull UUID physicsBodyUuid) { + PhysicsBodyAttachmentComponent component = externalEntity(RigidBodyKey.of(physicsBodyUuid), null); + component.setPhysicsBodyUuid(physicsBodyUuid); + return component; + } + /** * Creates the normal attachment for a plugin-owned gameplay or visual entity. * @@ -230,6 +244,20 @@ public void setBodyKey(@Nonnull RigidBodyKey bodyKey) { this.bodyKey = bodyKey; } + @Nullable + public UUID getPhysicsBodyUuid() { + return physicsBodyUuid; + } + + public void setPhysicsBodyUuid(@Nullable UUID physicsBodyUuid) { + this.physicsBodyUuid = physicsBodyUuid; + } + + @Nonnull + public UUID getPhysicsBodyUuidOrLegacy() { + return physicsBodyUuid != null ? physicsBodyUuid : bodyKey.value(); + } + @Nonnull public TransformAuthority getTransformAuthority() { return transformAuthority; @@ -274,13 +302,15 @@ private Integer getSpaceIdValue() { @Nonnull @Override public PhysicsBodyAttachmentComponent clone() { - return new PhysicsBodyAttachmentComponent(bodyKey, + PhysicsBodyAttachmentComponent copy = new PhysicsBodyAttachmentComponent(bodyKey, spaceId, transformAuthority, lifecycle, localPositionOffset, localRotationOffset, visualOriginOffsetY); + copy.physicsBodyUuid = physicsBodyUuid; + return copy; } private static float normalizeVisualOriginOffsetY(@Nullable Float value) { From 4a4b74ac78db0605365838be475f313043a29651 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 14:51:38 +0200 Subject: [PATCH 005/534] refactor(core): gate legacy physics runtime systems Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 41 ------------------- .../ImpulseWorldCollisionPlugin.java | 23 +++-------- 2 files changed, 6 insertions(+), 58 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 2824f2e2..335d80a3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -14,7 +14,6 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.plugin.PluginManager; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; @@ -32,20 +31,9 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; -import dev.hytalemodding.impulse.core.internal.systems.body.PhysicsBodyIdentityCleanupSystem; -import dev.hytalemodding.impulse.core.internal.systems.body.RigidBodyLifecycleCleanupSystem; -import dev.hytalemodding.impulse.core.internal.systems.body.RigidBodyReconciliationSystem; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PersistentPhysicsBodyHydrationSystem; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PersistentPhysicsJointHydrationSystem; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PersistentPhysicsSpaceBootstrapSystem; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PersistentPhysicsWorldSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PhysicsRuntimeHolderSystem; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; -import dev.hytalemodding.impulse.core.internal.systems.owner.PhysicsOwnerLifecycleSystem; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; @@ -124,8 +112,6 @@ public final class ImpulsePlugin extends JavaPlugin { private BackendId defaultBackendId; private PhysicsOwnerLaneScheduler physicsOwnerLaneScheduler; - private PhysicsStepSystem physicsStepSystem; - private PhysicsOwnerLifecycleSystem physicsOwnerLifecycleSystem; public ImpulsePlugin(@Nonnull JavaPluginInit init) { super(init); @@ -161,14 +147,6 @@ protected void start() { @Override protected void shutdown() { ImpulseCommandContributionRegistry.unregister(); - if (physicsStepSystem != null) { - physicsStepSystem.close(); - physicsStepSystem = null; - } - if (physicsOwnerLifecycleSystem != null) { - physicsOwnerLifecycleSystem.close(); - physicsOwnerLifecycleSystem = null; - } if (physicsOwnerLaneScheduler != null) { physicsOwnerLaneScheduler.close(); physicsOwnerLaneScheduler = null; @@ -377,26 +355,12 @@ record ConfiguredPositiveInt(int value, } private void registerSystems() { - ComponentRegistryProxy chunkRegistry = getChunkStoreRegistry(); - physicsStepSystem = new PhysicsStepSystem(); - chunkRegistry.registerSystem(physicsStepSystem); - ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); persistenceRestoreGroup = entityRegistry.registerSystemGroup(); - entityRegistry.registerSystem(createPhysicsOwnerLifecycleSystem()); - entityRegistry.registerSystem(new PersistentPhysicsSpaceBootstrapSystem()); - entityRegistry.registerSystem(new PersistentPhysicsBodyHydrationSystem()); - entityRegistry.registerSystem(new PersistentPhysicsJointHydrationSystem()); - entityRegistry.registerSystem(new PhysicsRuntimeHolderSystem()); - entityRegistry.registerSystem(new PhysicsBodyIdentityCleanupSystem()); - entityRegistry.registerSystem(new RigidBodyLifecycleCleanupSystem()); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); entityRegistry.registerSystem(new PhysicsSyncSystem()); entityRegistry.registerSystem(new PhysicsDebugSystem()); entityRegistry.registerSystem(new PhysicsDetachedVisualMaterializationSystem()); - entityRegistry.registerSystem(new PhysicsSnapshotPublicationSystem()); - entityRegistry.registerSystem(new PersistentPhysicsWorldSyncSystem()); - entityRegistry.registerSystem(new RigidBodyReconciliationSystem()); } private void registerCommands() { @@ -404,9 +368,4 @@ private void registerCommands() { ImpulseCommandContributionRegistry.register(commandRegistry); } - @Nonnull - private PhysicsOwnerLifecycleSystem createPhysicsOwnerLifecycleSystem() { - physicsOwnerLifecycleSystem = new PhysicsOwnerLifecycleSystem(); - return physicsOwnerLifecycleSystem; - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java index e325c13d..eb3dd424 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java @@ -1,15 +1,11 @@ package dev.hytalemodding.impulse.core.plugin.modules.worldcollision; -import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands.WorldCollisionCommandContributions; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsChunkBoundarySystem; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsCollisionLodSystem; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsWorldCollisionStreamingSystem; +import java.util.logging.Level; import javax.annotation.Nonnull; /** @@ -17,28 +13,21 @@ */ public final class ImpulseWorldCollisionPlugin extends JavaPlugin { + private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); + public ImpulseWorldCollisionPlugin(@Nonnull JavaPluginInit init) { super(init); } @Override protected void setup() { - ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - WorldCollisionProfilingResource.setResourceType(entityRegistry.registerResource( - WorldCollisionProfilingResource.class, - WorldCollisionProfilingResource::new)); - entityRegistry.registerSystem(new PhysicsWorldCollisionStreamingSystem()); - entityRegistry.registerSystem(new PhysicsCollisionLodSystem()); - entityRegistry.registerSystem(new PhysicsChunkBoundarySystem()); - - WorldCollisionCommandContributions.register(); - WorldCollisionLifecycle.enable(); + LOGGER.at(Level.INFO).log("Impulse world-collision legacy EntityStore systems are " + + "disabled while authoritative PhysicsStore terrain binding is being migrated."); } @Override protected void shutdown() { WorldCollisionLifecycle.disable(); - WorldCollisionCommandContributions.unregister(); WorldCollisionProfilingResource.clearResourceType(); } } From 2a939dfbfa0f0f47abd3b98b572f1ff3b598fd76 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:13:57 +0200 Subject: [PATCH 006/534] feat(core): bind physics store terrain payloads Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 6 +- .../resources/PhysicsRuntimeResource.java | 86 +++++++ .../PhysicsTerrainPayloadResource.java | 54 ++++ .../systems/IdentityIndexSystem.java | 2 +- .../systems/PersistenceHydrationSystem.java | 3 +- .../systems/RequestDrainSystem.java | 105 +++++++- .../systems/TerrainColliderBindingSystem.java | 243 +++++++++++++++++- .../physicsstore/PhysicsStoreTypes.java | 13 + .../requests/TerrainColliderPayload.java | 70 +++++ .../requests/TerrainColliderRequest.java | 20 +- 10 files changed, 588 insertions(+), 14 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderPayload.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index bedcee7c..52cf024e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -11,6 +11,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.ColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.CompletedStepPublicationSystem; @@ -100,6 +101,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setSnapshotResourceType(registry.registerResource( PhysicsSnapshotResource.class, PhysicsSnapshotResource::new)); + PhysicsStoreTypes.setTerrainPayloadResourceType(registry.registerResource( + PhysicsTerrainPayloadResource.class, + PhysicsTerrainPayloadResource::new)); PhysicsStoreTypes.setPersistentStoreResourceType(registry.registerResource( PersistentPhysicsStoreResource.class, "PersistentPhysicsStore", @@ -114,8 +118,8 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsDebugResource.class, PhysicsDebugResource::new)); - registry.registerSystem(new RequestDrainSystem()); registry.registerSystem(new PersistenceHydrationSystem()); + registry.registerSystem(new RequestDrainSystem()); registry.registerSystem(new IdentityIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new BodyBindingSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 5e61f754..7e953d1d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -43,6 +43,18 @@ public final class PhysicsRuntimeResource implements Resource { private final Map jointHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map terrainBodyHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map terrainVoxelBodyHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map terrainSpaceHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map terrainPayloadKeysByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap bodyHandlesBySpaceHandle = new Int2ObjectOpenHashMap<>(); private boolean started; @@ -89,6 +101,7 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { backendIdsBySpaceUuid.remove(spaceUuid); if (removed != null) { bodyHandlesBySpaceHandle.remove(removed.value()); + removeTerrainHandlesForSpace(removed); } } @@ -138,6 +151,57 @@ public void removeJointHandle(@Nonnull UUID jointUuid) { jointHandlesByUuid.remove(jointUuid); } + public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle, + boolean voxelTerrainBody) { + terrainSpaceHandlesByUuid.put(terrainUuid, spaceHandle); + terrainBodyHandlesByUuid.computeIfAbsent(terrainUuid, _ -> new LongArrayList()) + .add(handle.value()); + if (voxelTerrainBody) { + terrainVoxelBodyHandlesByUuid.put(terrainUuid, handle); + } + } + + public void markTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String payloadKey) { + terrainPayloadKeysByUuid.put(terrainUuid, payloadKey); + } + + public boolean isTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String payloadKey) { + return payloadKey.equals(terrainPayloadKeysByUuid.get(terrainUuid)); + } + + public boolean hasTerrainBodyHandles(@Nonnull UUID terrainUuid) { + LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); + return bodyHandles != null && !bodyHandles.isEmpty(); + } + + @Nullable + public BackendSpaceHandle getTerrainSpaceHandle(@Nonnull UUID terrainUuid) { + return terrainSpaceHandlesByUuid.get(terrainUuid); + } + + @Nullable + public BackendBodyHandle getTerrainVoxelBodyHandle(@Nonnull UUID terrainUuid) { + return terrainVoxelBodyHandlesByUuid.get(terrainUuid); + } + + public void forEachTerrainBodyHandle(@Nonnull UUID terrainUuid, + @Nonnull LongConsumer consumer) { + LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); + if (bodyHandles == null) { + return; + } + bodyHandles.forEach(consumer); + } + + public void removeTerrainHandles(@Nonnull UUID terrainUuid) { + terrainBodyHandlesByUuid.remove(terrainUuid); + terrainVoxelBodyHandlesByUuid.remove(terrainUuid); + terrainSpaceHandlesByUuid.remove(terrainUuid); + terrainPayloadKeysByUuid.remove(terrainUuid); + } + public void forEachSpaceBinding(@Nonnull SpaceBindingConsumer consumer) { spaceHandlesByUuid.forEach((spaceUuid, spaceHandle) -> { BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); @@ -164,6 +228,10 @@ public void clear() { bodyHandlesByUuid.clear(); bodySpaceHandlesByUuid.clear(); jointHandlesByUuid.clear(); + terrainBodyHandlesByUuid.clear(); + terrainVoxelBodyHandlesByUuid.clear(); + terrainSpaceHandlesByUuid.clear(); + terrainPayloadKeysByUuid.clear(); bodyHandlesBySpaceHandle.clear(); started = false; } @@ -178,6 +246,11 @@ public PhysicsRuntimeResource clone() { copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); copy.jointHandlesByUuid.putAll(jointHandlesByUuid); + terrainBodyHandlesByUuid.forEach((terrainUuid, bodyHandles) -> + copy.terrainBodyHandlesByUuid.put(terrainUuid, new LongArrayList(bodyHandles))); + copy.terrainVoxelBodyHandlesByUuid.putAll(terrainVoxelBodyHandlesByUuid); + copy.terrainSpaceHandlesByUuid.putAll(terrainSpaceHandlesByUuid); + copy.terrainPayloadKeysByUuid.putAll(terrainPayloadKeysByUuid); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); copy.started = started; @@ -197,4 +270,17 @@ void accept(@Nonnull UUID spaceUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime runtime); } + + private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandle) { + terrainSpaceHandlesByUuid.entrySet().removeIf(entry -> { + if (entry.getValue().value() != spaceHandle.value()) { + return false; + } + UUID terrainUuid = entry.getKey(); + terrainBodyHandlesByUuid.remove(terrainUuid); + terrainVoxelBodyHandlesByUuid.remove(terrainUuid); + terrainPayloadKeysByUuid.remove(terrainUuid); + return true; + }); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java new file mode 100644 index 00000000..606c9f8d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java @@ -0,0 +1,54 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime-only copied terrain payloads keyed by TerrainColliderComponent payload keys. + */ +public final class PhysicsTerrainPayloadResource implements Resource { + + @Nonnull + private final Map payloadsByKey = + new Object2ObjectOpenHashMap<>(); + + public PhysicsTerrainPayloadResource() { + } + + public void put(@Nonnull String key, @Nonnull TerrainColliderPayload payload) { + payloadsByKey.put(key, payload); + } + + @Nullable + public TerrainColliderPayload get(@Nonnull String key) { + return payloadsByKey.get(key); + } + + public void remove(@Nonnull String key) { + payloadsByKey.remove(key); + } + + public void clear() { + payloadsByKey.clear(); + } + + @Nonnull + @Override + public PhysicsTerrainPayloadResource clone() { + PhysicsTerrainPayloadResource copy = new PhysicsTerrainPayloadResource(); + copy.payloadsByKey.putAll(payloadsByKey); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.terrainPayloadResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java index dcf8afbc..edbdf610 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java @@ -24,7 +24,7 @@ public final class IdentityIndexSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class), + new SystemDependency<>(Order.AFTER, RequestDrainSystem.class), new SystemDependency<>(Order.BEFORE, SpaceBindingSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index 4c7925ba..c5641ef9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -41,8 +41,7 @@ public final class PersistenceHydrationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, RequestDrainSystem.class), - new SystemDependency<>(Order.BEFORE, IdentityIndexSystem.class) + new SystemDependency<>(Order.BEFORE, RequestDrainSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index 853db659..df84e25e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -1,5 +1,7 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; @@ -10,13 +12,21 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Applies copied boundary requests before backend reconciliation. @@ -24,7 +34,8 @@ public final class RequestDrainSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.BEFORE, PersistenceHydrationSystem.class) + new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class), + new SystemDependency<>(Order.BEFORE, IdentityIndexSystem.class) ); @Override @@ -39,14 +50,21 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsIdentityIndexResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); + PhysicsTerrainPayloadResource terrainPayloads = store.getResource( + PhysicsTerrainPayloadResource.getResourceType()); + Map> terrainRefsThisDrain = new Object2ObjectOpenHashMap<>(); for (PhysicsStoreRequest request : requests) { if (request instanceof BodyTargetRequest targetRequest) { applyTargetRequest(store, identity, restore, targetRequest); continue; } if (request instanceof TerrainColliderRequest terrainRequest) { - restore.recordSoftSkip("Terrain request authoring is deferred: " - + terrainRequest.sourceKey()); + applyTerrainRequest(store, + identity, + terrainPayloads, + terrainRefsThisDrain, + restore, + terrainRequest); continue; } restore.recordSoftSkip("Unsupported PhysicsStore request " @@ -73,6 +91,87 @@ private static void applyTargetRequest(@Nonnull Store store, store.putComponent(bodyRef, TargetComponent.getComponentType(), target); } + private static void applyTerrainRequest(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull Map> terrainRefsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull TerrainColliderRequest request) { + UUID terrainUuid = request.terrainColliderUuid(); + Ref ref = terrainRefsThisDrain.get(terrainUuid); + if (ref == null) { + ref = PhysicsStoreSystemSupport.refForUuid(identity, terrainUuid); + } + if (request.remove()) { + if (ref != null) { + TerrainColliderComponent existing = store.getComponent(ref, + TerrainColliderComponent.getComponentType()); + if (existing != null) { + removePayload(terrainPayloads, existing.getPayloadResourceKey()); + } + store.putComponent(ref, + TerrainColliderComponent.getComponentType(), + removedTerrainComponent(request)); + } + removePayload(terrainPayloads, request.payloadResourceKey()); + return; + } + TerrainColliderPayload payload = request.payload(); + if (payload == null || payload.isEmpty()) { + restore.recordSoftSkip("Terrain upsert payload is missing: " + request.sourceKey()); + return; + } + terrainPayloads.put(request.payloadResourceKey(), payload); + TerrainColliderComponent component = activeTerrainComponent(request); + if (ref != null) { + TerrainColliderComponent existing = store.getComponent(ref, + TerrainColliderComponent.getComponentType()); + if (existing != null + && !existing.getPayloadResourceKey().equals(component.getPayloadResourceKey())) { + removePayload(terrainPayloads, existing.getPayloadResourceKey()); + } + store.putComponent(ref, TerrainColliderComponent.getComponentType(), component); + terrainRefsThisDrain.put(terrainUuid, ref); + return; + } + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(terrainUuid)); + holder.addComponent(TerrainColliderComponent.getComponentType(), component); + terrainRefsThisDrain.put(terrainUuid, store.addEntity(holder, AddReason.SPAWN)); + } + + @Nonnull + private static TerrainColliderComponent activeTerrainComponent( + @Nonnull TerrainColliderRequest request) { + return terrainComponent(request, request.payloadResourceKey(), true); + } + + @Nonnull + private static TerrainColliderComponent removedTerrainComponent( + @Nonnull TerrainColliderRequest request) { + return terrainComponent(request, request.payloadResourceKey(), false); + } + + @Nonnull + private static TerrainColliderComponent terrainComponent(@Nonnull TerrainColliderRequest request, + @Nullable String payloadResourceKey, + boolean retained) { + return new TerrainColliderComponent(request.spaceUuid(), + request.sourceKey(), + request.chunkX(), + request.sectionY(), + request.chunkZ(), + payloadResourceKey != null ? payloadResourceKey : "", + retained); + } + + private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nullable String payloadResourceKey) { + if (payloadResourceKey != null && !payloadResourceKey.isBlank()) { + terrainPayloads.remove(payloadResourceKey); + } + } + @Nonnull @Override public Set> getDependencies() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 6179f897..b1dc7a79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -1,18 +1,40 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.TerrainNeighbor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** - * Reserved for voxel terrain payload binding owned by ChunkStore request producers. + * Binds retained terrain collider rows to backend terrain bodies. */ -public final class TerrainColliderBindingSystem extends TickingSystem { +public final class TerrainColliderBindingSystem extends TickingSystem + implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, JointBindingSystem.class), @@ -21,10 +43,219 @@ public final class TerrainColliderBindingSystem extends TickingSystem store) { - /* - * Terrain rows currently carry source metadata but not the compact voxel payload. Worker D - * owns the request producer and payload handoff, so backend terrain creation stays here. - */ + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsTerrainPayloadResource payloads = store.getResource( + PhysicsTerrainPayloadResource.getResourceType()); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> bindChunk(runtime, payloads, restore, chunk); + store.forEachChunk(systemIndex, collector); + } + + private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + TerrainColliderComponent terrain = chunk.getComponent(index, + TerrainColliderComponent.getComponentType()); + if (terrain == null) { + continue; + } + UUID terrainUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(terrainUuid)) { + continue; + } + if (!terrain.isRetained()) { + removeTerrain(runtime, terrainUuid); + continue; + } + if (runtime.isTerrainPayloadBound(terrainUuid, terrain.getPayloadResourceKey())) { + continue; + } + TerrainColliderPayload payload = payloads.get(terrain.getPayloadResourceKey()); + if (payload == null || payload.isEmpty()) { + restore.recordSoftSkip("Terrain payload is missing: " + terrain.getSourceKey()); + continue; + } + bindTerrain(runtime, restore, terrainUuid, terrain, payload); + } + } + + private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull UUID terrainUuid, + @Nonnull TerrainColliderComponent terrain, + @Nonnull TerrainColliderPayload payload) { + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(terrain.getSpaceUuid()); + if (spaceHandle == null) { + restore.recordSoftSkip("Terrain references unbound space: " + terrain.getSourceKey()); + return; + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, terrain.getSpaceUuid()); + if (backendRuntime == null) { + restore.recordSoftSkip("Terrain references missing backend runtime: " + + terrain.getSourceKey()); + return; + } + if (runtime.hasTerrainBodyHandles(terrainUuid)) { + removeTerrain(runtime, terrainUuid); + } + try { + boolean nativeVoxel = payload.nativeVoxelTerrainEnabled() + && payload.hasFullCubeVoxels() + && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); + if (nativeVoxel) { + addVoxelTerrain(runtime, backendRuntime, spaceHandle, terrainUuid, terrain, payload); + } else { + for (BoxPayload box : payload.mergedFullCubeBoxes()) { + addStaticBox(runtime, backendRuntime, spaceHandle, terrainUuid, box, payload); + } + } + for (BoxPayload box : payload.detailBoxes()) { + addStaticBox(runtime, backendRuntime, spaceHandle, terrainUuid, box, payload); + } + if (!runtime.hasTerrainBodyHandles(terrainUuid)) { + restore.recordSoftSkip("Terrain payload produced no backend bodies: " + + terrain.getSourceKey()); + return; + } + runtime.markTerrainPayloadBound(terrainUuid, terrain.getPayloadResourceKey()); + stitchNeighbors(runtime, backendRuntime, spaceHandle, terrainUuid, terrain, payload); + } catch (RuntimeException exception) { + removeTerrain(runtime, terrainUuid); + throw exception; + } + } + + private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull UUID terrainUuid, + @Nonnull TerrainColliderComponent terrain, + @Nonnull TerrainColliderPayload payload) { + long bodyId = backendRuntime.createVoxelTerrain(spaceHandle.value(), + payload.voxelSizeX(), + payload.voxelSizeY(), + payload.voxelSizeZ(), + payload.voxelCoordinates(), + terrain.getChunkX() << ChunkUtil.BITS, + terrain.getSectionY() << ChunkUtil.BITS, + terrain.getChunkZ() << ChunkUtil.BITS, + payload.friction(), + payload.restitution(), + payload.collisionGroup(), + payload.collisionMask()); + runtime.putTerrainBodyHandle(terrainUuid, spaceHandle, new BackendBodyHandle(bodyId), true); + } + + private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull UUID terrainUuid, + @Nonnull BoxPayload box, + @Nonnull TerrainColliderPayload payload) { + if (box.halfX() <= 0.0 || box.halfY() <= 0.0 || box.halfZ() <= 0.0) { + return; + } + long bodyId = backendRuntime.createBody(spaceHandle.value(), + BackendRuntimeCodes.SHAPE_BOX, + (float) box.halfX(), + (float) box.halfY(), + (float) box.halfZ(), + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 0.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC), + (float) box.centerX(), + (float) box.centerY(), + (float) box.centerZ(), + 0.0f, + 0.0f, + 0.0f, + 1.0f); + backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, payload.friction()); + backendRuntime.setBodyRestitution(spaceHandle.value(), bodyId, payload.restitution()); + backendRuntime.setBodyCollisionFilter(spaceHandle.value(), + bodyId, + payload.collisionGroup(), + payload.collisionMask()); + runtime.putTerrainBodyHandle(terrainUuid, + spaceHandle, + new BackendBodyHandle(bodyId), + false); + } + + private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull UUID terrainUuid, + @Nonnull TerrainColliderComponent terrain, + @Nonnull TerrainColliderPayload payload) { + BackendBodyHandle voxelBody = runtime.getTerrainVoxelBodyHandle(terrainUuid); + if (voxelBody == null) { + return; + } + for (TerrainNeighbor neighbor : payload.neighbors()) { + UUID neighborUuid = TerrainColliderRequest.terrainColliderUuid(terrain.getSpaceUuid(), + neighbor.sourceKey()); + BackendBodyHandle neighborBody = runtime.getTerrainVoxelBodyHandle(neighborUuid); + BackendSpaceHandle neighborSpace = runtime.getTerrainSpaceHandle(neighborUuid); + if (neighborBody == null + || neighborSpace == null + || neighborSpace.value() != spaceHandle.value()) { + continue; + } + backendRuntime.combineVoxelTerrains(spaceHandle.value(), + voxelBody.value(), + neighborBody.value(), + neighbor.shiftX(), + neighbor.shiftY(), + neighbor.shiftZ()); + } + } + + private static void removeTerrain(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID terrainUuid) { + BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(terrainUuid); + if (spaceHandle == null) { + runtime.removeTerrainHandles(terrainUuid); + return; + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (backendRuntime != null) { + runtime.forEachTerrainBodyHandle(terrainUuid, + bodyId -> backendRuntime.removeBody(spaceHandle.value(), bodyId)); + } + runtime.removeTerrainHandles(terrainUuid); + } + + @Nullable + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID spaceUuid) { + var backendId = runtime.getSpaceBackendId(spaceUuid); + return backendId != null ? runtime.getRuntime(backendId) : null; + } + + @Nullable + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull BackendSpaceHandle spaceHandle) { + final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; + runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { + if (handle.value() == spaceHandle.value()) { + resolved[0] = backendRuntime; + } + }); + return resolved[0]; + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index 0431f0b8..bbc41977 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -10,6 +10,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -63,6 +64,8 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType snapshotResourceType; @Nullable + private static ResourceType terrainPayloadResourceType; + @Nullable private static ResourceType persistentStoreResourceType; @Nullable private static ResourceType restoreStatusResourceType; @@ -149,6 +152,11 @@ public static void setSnapshotResourceType( snapshotResourceType = Objects.requireNonNull(type, "type"); } + public static void setTerrainPayloadResourceType( + @Nonnull ResourceType type) { + terrainPayloadResourceType = Objects.requireNonNull(type, "type"); + } + public static void setPersistentStoreResourceType( @Nonnull ResourceType type) { persistentStoreResourceType = Objects.requireNonNull(type, "type"); @@ -244,6 +252,11 @@ public static ResourceType snapshotResour return require(snapshotResourceType, "PhysicsSnapshotResource"); } + @Nonnull + public static ResourceType terrainPayloadResourceType() { + return require(terrainPayloadResourceType, "PhysicsTerrainPayloadResource"); + } + @Nonnull public static ResourceType persistentStoreResourceType() { return require(persistentStoreResourceType, "PersistentPhysicsStoreResource"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderPayload.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderPayload.java new file mode 100644 index 00000000..de1a1126 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderPayload.java @@ -0,0 +1,70 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Copied terrain payload carried across the ChunkStore to PhysicsStore boundary. + */ +public record TerrainColliderPayload(float voxelSizeX, + float voxelSizeY, + float voxelSizeZ, + @Nonnull int[] voxelCoordinates, + @Nonnull List mergedFullCubeBoxes, + @Nonnull List detailBoxes, + boolean nativeVoxelTerrainEnabled, + float friction, + float restitution, + int collisionGroup, + int collisionMask, + @Nonnull List neighbors) { + + public TerrainColliderPayload { + voxelCoordinates = voxelCoordinates != null + ? Arrays.copyOf(voxelCoordinates, voxelCoordinates.length) + : new int[0]; + mergedFullCubeBoxes = mergedFullCubeBoxes != null + ? List.copyOf(mergedFullCubeBoxes) + : List.of(); + detailBoxes = detailBoxes != null ? List.copyOf(detailBoxes) : List.of(); + neighbors = neighbors != null ? List.copyOf(neighbors) : List.of(); + } + + @Nonnull + @Override + public int[] voxelCoordinates() { + return Arrays.copyOf(voxelCoordinates, voxelCoordinates.length); + } + + public boolean hasFullCubeVoxels() { + return voxelCoordinates.length > 0; + } + + public boolean isEmpty() { + return voxelCoordinates.length == 0 && mergedFullCubeBoxes.isEmpty() + && detailBoxes.isEmpty(); + } + + /** + * Axis-aligned static terrain box in world coordinates. + */ + public record BoxPayload(double centerX, + double centerY, + double centerZ, + double halfX, + double halfY, + double halfZ) { + } + + /** + * Neighbor terrain source used for optional native-voxel stitching. + */ + public record TerrainNeighbor(@Nonnull String sourceKey, int shiftX, int shiftY, int shiftZ) { + + public TerrainNeighbor { + Objects.requireNonNull(sourceKey, "sourceKey"); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java index 1fbefe40..760fc121 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java @@ -1,8 +1,10 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; +import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Copied terrain collider request emitted from chunk/world-collision code. @@ -14,6 +16,7 @@ public record TerrainColliderRequest(@Nonnull UUID requestUuid, int sectionY, int chunkZ, @Nonnull String payloadResourceKey, + @Nullable TerrainColliderPayload payload, boolean remove) implements PhysicsStoreRequest { public TerrainColliderRequest { @@ -23,13 +26,19 @@ public record TerrainColliderRequest(@Nonnull UUID requestUuid, Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); } + @Nonnull + public UUID terrainColliderUuid() { + return terrainColliderUuid(spaceUuid, sourceKey); + } + @Nonnull public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, int chunkX, int sectionY, int chunkZ, - @Nonnull String payloadResourceKey) { + @Nonnull String payloadResourceKey, + @Nonnull TerrainColliderPayload payload) { return new TerrainColliderRequest(UUID.randomUUID(), spaceUuid, sourceKey, @@ -37,6 +46,7 @@ public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, sectionY, chunkZ, payloadResourceKey, + payload, false); } @@ -53,6 +63,14 @@ public static TerrainColliderRequest remove(@Nonnull UUID spaceUuid, sectionY, chunkZ, "", + null, true); } + + @Nonnull + public static UUID terrainColliderUuid(@Nonnull UUID spaceUuid, + @Nonnull String sourceKey) { + String key = spaceUuid + "|" + sourceKey; + return UUID.nameUUIDFromBytes(key.getBytes(StandardCharsets.UTF_8)); + } } From b615bd277507b5ee8ec6f1c0eabcaf5e8a6f3909 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 14:55:43 +0200 Subject: [PATCH 007/534] feat(core): capture physics store persistence rows Signed-off-by: Blovien --- .../persistence/PersistentJointDto.java | 36 ++ .../systems/PersistenceCaptureSystem.java | 377 +++++++++++++++++- 2 files changed, 407 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java index dbf0951a..8e66b2c3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java @@ -143,6 +143,42 @@ public final class PersistentJointDto { public PersistentJointDto() { } + public PersistentJointDto(@Nonnull UUID jointUuid, + @Nonnull UUID spaceUuid, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid, + @Nonnull JointType type, + @Nonnull Vector3f anchorA, + @Nonnull Vector3f anchorB, + @Nonnull Vector3f axis, + float lowerLimit, + float upperLimit, + boolean enabled, + boolean motorEnabled, + float motorTargetVelocity, + float motorMaxForce, + float springRestLength, + float springStiffness, + float springDamping) { + this.jointUuid = Objects.requireNonNull(jointUuid, "jointUuid"); + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.bodyAUuid = Objects.requireNonNull(bodyAUuid, "bodyAUuid"); + this.bodyBUuid = Objects.requireNonNull(bodyBUuid, "bodyBUuid"); + this.type = Objects.requireNonNull(type, "type"); + this.anchorA.set(Objects.requireNonNull(anchorA, "anchorA")); + this.anchorB.set(Objects.requireNonNull(anchorB, "anchorB")); + this.axis.set(Objects.requireNonNull(axis, "axis")); + this.lowerLimit = lowerLimit; + this.upperLimit = upperLimit; + this.enabled = enabled; + this.motorEnabled = motorEnabled; + this.motorTargetVelocity = motorTargetVelocity; + this.motorMaxForce = motorMaxForce; + this.springRestLength = springRestLength; + this.springStiffness = springStiffness; + this.springDamping = springDamping; + } + @Nonnull public UUID getJointUuid() { return jointUuid; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index c66905b4..75354a41 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -1,18 +1,57 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyRuntimeStateDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentColliderDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentJointDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentMaterialDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentShapeDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentTerrainColliderDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; /** * Captures serializable PhysicsStore rows into compact DTO resources. */ -public final class PersistenceCaptureSystem extends TickingSystem { +public final class PersistenceCaptureSystem extends TickingSystem + implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, CompletedStepPublicationSystem.class), @@ -21,11 +60,33 @@ public final class PersistenceCaptureSystem extends TickingSystem @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { - /* - * DTO capture is intentionally after completed snapshot publication and before next-step - * submission. The capture body is filled once Worker D finishes projection/authoring - * migration so the DTO wire format is not populated from legacy EntityStore state. - */ + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isPending() || restore.isFailed()) { + return; + } + Capture capture = new Capture(snapshotBodiesByUuid(store)); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> capture.collectChunk(chunk); + store.forEachChunk(systemIndex, collector); + capture.writeTo(store.getResource(PersistentPhysicsStoreResource.getResourceType())); + } + + @Nonnull + private static Map snapshotBodiesByUuid( + @Nonnull Store store) { + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + Map bodies = new Object2ObjectOpenHashMap<>(); + for (PhysicsStoreBodySnapshot body : snapshots.getLatestFrame().bodies()) { + bodies.put(body.bodyUuid(), body); + } + return bodies; + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; } @Nonnull @@ -33,4 +94,308 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) public Set> getDependencies() { return DEPENDENCIES; } + + private static final class Capture { + + @Nonnull + private final Map snapshotsByBodyUuid; + @Nonnull + private final List spaceRows = new ArrayList<>(); + @Nonnull + private final List bodyRows = new ArrayList<>(); + @Nonnull + private final List colliderRows = new ArrayList<>(); + @Nonnull + private final List shapeRows = new ArrayList<>(); + @Nonnull + private final List materialRows = new ArrayList<>(); + @Nonnull + private final List jointRows = new ArrayList<>(); + @Nonnull + private final List terrainRows = new ArrayList<>(); + @Nonnull + private final Map filtersByUuid = + new Object2ObjectOpenHashMap<>(); + + private Capture(@Nonnull Map snapshotsByBodyUuid) { + this.snapshotsByBodyUuid = snapshotsByBodyUuid; + } + + private void collectChunk(@Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + UUID uuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(uuid)) { + continue; + } + collectRow(uuid, chunk, index); + } + } + + private void collectRow(@Nonnull UUID uuid, + @Nonnull ArchetypeChunk chunk, + int index) { + SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); + if (space != null) { + spaceRows.add(new SpaceRow(uuid, space)); + } + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body != null) { + bodyRows.add(new BodyRow(uuid, + body, + chunk.getComponent(index, DynamicsComponent.getComponentType()), + chunk.getComponent(index, TargetComponent.getComponentType()))); + } + ColliderComponent collider = chunk.getComponent(index, ColliderComponent.getComponentType()); + if (collider != null) { + colliderRows.add(new ColliderRow(uuid, collider)); + } + ShapeComponent shape = chunk.getComponent(index, ShapeComponent.getComponentType()); + if (shape != null) { + shapeRows.add(new ShapeRow(uuid, shape)); + } + MaterialComponent material = chunk.getComponent(index, MaterialComponent.getComponentType()); + if (material != null) { + materialRows.add(new MaterialRow(uuid, material)); + } + CollisionFilterComponent filter = chunk.getComponent(index, + CollisionFilterComponent.getComponentType()); + if (filter != null) { + filtersByUuid.put(uuid, filter); + } + JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); + if (joint != null) { + jointRows.add(new JointRow(uuid, joint)); + } + TerrainColliderComponent terrain = chunk.getComponent(index, + TerrainColliderComponent.getComponentType()); + if (terrain != null) { + terrainRows.add(new TerrainColliderRow(uuid, terrain)); + } + } + + private void writeTo(@Nonnull PersistentPhysicsStoreResource persistent) { + ObjectOpenHashSet bodyUuids = persistentBodyUuids(); + ObjectOpenHashSet shapeUuids = new ObjectOpenHashSet<>(); + ObjectOpenHashSet materialUuids = new ObjectOpenHashSet<>(); + Map> colliderUuidsByBodyUuid = + new Object2ObjectOpenHashMap<>(); + + for (ColliderRow row : colliderRows) { + if (!bodyUuids.contains(row.collider().getBodyUuid())) { + continue; + } + colliderUuidsByBodyUuid + .computeIfAbsent(row.collider().getBodyUuid(), _ -> new ArrayList<>()) + .add(row.uuid()); + shapeUuids.add(row.collider().getShapeUuid()); + materialUuids.add(row.collider().getMaterialUuid()); + } + + persistent.setSpaces(spaceDtos()); + persistent.setBodies(bodyDtos(colliderUuidsByBodyUuid)); + persistent.setColliders(colliderDtos(bodyUuids)); + persistent.setShapes(shapeDtos(shapeUuids)); + persistent.setMaterials(materialDtos(materialUuids)); + persistent.setJoints(jointDtos(bodyUuids)); + persistent.setTerrainColliders(terrainDtos()); + } + + @Nonnull + private ObjectOpenHashSet persistentBodyUuids() { + ObjectOpenHashSet bodyUuids = new ObjectOpenHashSet<>(); + for (BodyRow row : bodyRows) { + if (row.body().getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) { + bodyUuids.add(row.uuid()); + } + } + return bodyUuids; + } + + @Nonnull + private PersistentSpaceDto[] spaceDtos() { + return spaceRows.stream() + .map(row -> new PersistentSpaceDto(row.uuid(), + row.space().getBackendIdValue(), + row.space().getGravity())) + .sorted(Comparator.comparing(PersistentSpaceDto::getSpaceUuid)) + .toArray(PersistentSpaceDto[]::new); + } + + @Nonnull + private PersistentBodyDto[] bodyDtos( + @Nonnull Map> colliderUuidsByBodyUuid) { + return bodyRows.stream() + .filter(row -> row.body().getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) + .map(row -> bodyDto(row, + colliderUuidsByBodyUuid.getOrDefault(row.uuid(), List.of()))) + .sorted(Comparator.comparing(PersistentBodyDto::getBodyUuid)) + .toArray(PersistentBodyDto[]::new); + } + + @Nonnull + private PersistentBodyDto bodyDto(@Nonnull BodyRow row, + @Nonnull List colliderUuids) { + DynamicsComponent dynamics = row.dynamics() != null + ? row.dynamics() + : new DynamicsComponent(); + return new PersistentBodyDto(row.uuid(), + row.body().getSpaceUuid(), + row.body().getKind(), + row.body().getPersistenceMode(), + dynamics.getBodyType(), + dynamics.getMass(), + dynamics.getLinearDamping(), + dynamics.getAngularDamping(), + dynamics.isContinuousCollisionEnabled(), + colliderUuids.stream().sorted().toArray(UUID[]::new), + runtimeState(row.uuid(), row.target())); + } + + @Nonnull + private PersistentBodyRuntimeStateDto runtimeState(@Nonnull UUID bodyUuid, + @Nullable TargetComponent target) { + PhysicsStoreBodySnapshot snapshot = snapshotsByBodyUuid.get(bodyUuid); + if (snapshot != null) { + return new PersistentBodyRuntimeStateDto(snapshot.position(), + snapshot.rotation(), + snapshot.linearVelocity(), + snapshot.angularVelocity(), + snapshot.sleeping()); + } + if (target != null && target.isActive()) { + return new PersistentBodyRuntimeStateDto(target.getPosition(), + target.getRotation(), + target.getLinearVelocity(), + target.getAngularVelocity(), + false); + } + return new PersistentBodyRuntimeStateDto(new Vector3f(), + new Quaternionf(), + new Vector3f(), + new Vector3f(), + false); + } + + @Nonnull + private PersistentColliderDto[] colliderDtos(@Nonnull Set bodyUuids) { + return colliderRows.stream() + .filter(row -> bodyUuids.contains(row.collider().getBodyUuid())) + .map(this::colliderDto) + .sorted(Comparator.comparing(PersistentColliderDto::getColliderUuid)) + .toArray(PersistentColliderDto[]::new); + } + + @Nonnull + private PersistentColliderDto colliderDto(@Nonnull ColliderRow row) { + CollisionFilterComponent filter = filtersByUuid.get(row.collider().getFilterUuid()); + CollisionFilterComponent resolvedFilter = filter != null + ? filter + : new CollisionFilterComponent(); + return new PersistentColliderDto(row.uuid(), + row.collider().getBodyUuid(), + row.collider().getShapeUuid(), + row.collider().getMaterialUuid(), + row.collider().getLocalPosition(), + row.collider().getLocalRotation(), + row.collider().isSensor(), + resolvedFilter.getCollisionGroup(), + resolvedFilter.getCollisionMask()); + } + + @Nonnull + private PersistentShapeDto[] shapeDtos(@Nonnull Set shapeUuids) { + return shapeRows.stream() + .filter(row -> shapeUuids.contains(row.uuid())) + .map(row -> new PersistentShapeDto(row.uuid(), + row.shape().getShapeType(), + row.shape().getHalfExtentX(), + row.shape().getHalfExtentY(), + row.shape().getHalfExtentZ(), + row.shape().getRadius(), + row.shape().getHalfHeight(), + row.shape().getAxis(), + row.shape().getGroundY(), + row.shape().getResourceKey())) + .sorted(Comparator.comparing(PersistentShapeDto::getShapeUuid)) + .toArray(PersistentShapeDto[]::new); + } + + @Nonnull + private PersistentMaterialDto[] materialDtos(@Nonnull Set materialUuids) { + return materialRows.stream() + .filter(row -> materialUuids.contains(row.uuid())) + .map(row -> new PersistentMaterialDto(row.uuid(), + row.material().getFriction(), + row.material().getRestitution())) + .sorted(Comparator.comparing(PersistentMaterialDto::getMaterialUuid)) + .toArray(PersistentMaterialDto[]::new); + } + + @Nonnull + private PersistentJointDto[] jointDtos(@Nonnull Set bodyUuids) { + return jointRows.stream() + .filter(row -> bodyUuids.contains(row.joint().getBodyAUuid())) + .filter(row -> bodyUuids.contains(row.joint().getBodyBUuid())) + .map(row -> new PersistentJointDto(row.uuid(), + row.joint().getSpaceUuid(), + row.joint().getBodyAUuid(), + row.joint().getBodyBUuid(), + row.joint().getType(), + row.joint().getAnchorA(), + row.joint().getAnchorB(), + row.joint().getAxis(), + row.joint().getLowerLimit(), + row.joint().getUpperLimit(), + row.joint().isEnabled(), + row.joint().isMotorEnabled(), + row.joint().getMotorTargetVelocity(), + row.joint().getMotorMaxForce(), + row.joint().getSpringRestLength(), + row.joint().getSpringStiffness(), + row.joint().getSpringDamping())) + .sorted(Comparator.comparing(PersistentJointDto::getJointUuid)) + .toArray(PersistentJointDto[]::new); + } + + @Nonnull + private PersistentTerrainColliderDto[] terrainDtos() { + return terrainRows.stream() + .filter(row -> row.terrain().isRetained()) + .map(row -> new PersistentTerrainColliderDto(row.uuid(), + row.terrain().getSpaceUuid(), + row.terrain().getSourceKey(), + row.terrain().getChunkX(), + row.terrain().getSectionY(), + row.terrain().getChunkZ(), + row.terrain().getPayloadResourceKey(), + row.terrain().isRetained())) + .sorted(Comparator.comparing(PersistentTerrainColliderDto::getTerrainColliderUuid)) + .toArray(PersistentTerrainColliderDto[]::new); + } + } + + private record SpaceRow(@Nonnull UUID uuid, @Nonnull SpaceComponent space) { + } + + private record BodyRow(@Nonnull UUID uuid, + @Nonnull BodyComponent body, + @Nullable DynamicsComponent dynamics, + @Nullable TargetComponent target) { + } + + private record ColliderRow(@Nonnull UUID uuid, @Nonnull ColliderComponent collider) { + } + + private record ShapeRow(@Nonnull UUID uuid, @Nonnull ShapeComponent shape) { + } + + private record MaterialRow(@Nonnull UUID uuid, @Nonnull MaterialComponent material) { + } + + private record JointRow(@Nonnull UUID uuid, @Nonnull JointComponent joint) { + } + + private record TerrainColliderRow(@Nonnull UUID uuid, + @Nonnull TerrainColliderComponent terrain) { + } } From 036218ec9bd9daa71ad6895948f80b82434bad08 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:16:49 +0200 Subject: [PATCH 008/534] feat(core): add physics store terrain request factory Signed-off-by: Blovien --- .../PhysicsStoreTerrainRequests.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequests.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequests.java new file mode 100644 index 00000000..008aa248 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequests.java @@ -0,0 +1,126 @@ +package dev.hytalemodding.impulse.core.internal.modules.worldcollision; + +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.TerrainNeighbor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Converts generated world-collision sections into copied PhysicsStore terrain requests. + */ +public final class PhysicsStoreTerrainRequests { + + private static final int ADJACENT_SECTION_VOXEL_SHIFT = 16; + + private PhysicsStoreTerrainRequests() { + } + + @Nonnull + public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, + int chunkX, + int sectionY, + int chunkZ, + long neighborhoodSignature, + @Nonnull SectionCollisionGeometry geometry, + @Nonnull WorldCollisionBuildOptions buildOptions) { + String sourceKey = sourceKey(chunkX, sectionY, chunkZ); + return TerrainColliderRequest.upsert(spaceUuid, + sourceKey, + chunkX, + sectionY, + chunkZ, + payloadKey(sourceKey, neighborhoodSignature, buildOptions), + payload(geometry, buildOptions, adjacentNeighbors(chunkX, sectionY, chunkZ))); + } + + @Nonnull + public static TerrainColliderRequest remove(@Nonnull UUID spaceUuid, + int chunkX, + int sectionY, + int chunkZ) { + return TerrainColliderRequest.remove(spaceUuid, + sourceKey(chunkX, sectionY, chunkZ), + chunkX, + sectionY, + chunkZ); + } + + @Nonnull + public static String sourceKey(int chunkX, int sectionY, int chunkZ) { + return "chunk:" + chunkX + ":" + sectionY + ":" + chunkZ; + } + + @Nonnull + private static String payloadKey(@Nonnull String sourceKey, + long neighborhoodSignature, + @Nonnull WorldCollisionBuildOptions buildOptions) { + return sourceKey + ":" + + Long.toUnsignedString(neighborhoodSignature) + + ":" + + Integer.toUnsignedString(buildOptions.hashCode()); + } + + @Nonnull + private static TerrainColliderPayload payload(@Nonnull SectionCollisionGeometry geometry, + @Nonnull WorldCollisionBuildOptions buildOptions, + @Nonnull List neighbors) { + return new TerrainColliderPayload(1.0f, + 1.0f, + 1.0f, + geometry.fullCubeVoxels(), + boxes(geometry.mergedFullCubeBoxes()), + boxes(geometry.detailBoxes()), + buildOptions.nativeVoxelTerrainEnabled(), + buildOptions.terrainFriction(), + buildOptions.terrainRestitution(), + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL, + neighbors); + } + + @Nonnull + private static List boxes(@Nonnull List boxes) { + return boxes.stream() + .map(box -> new BoxPayload(box.centerX(), + box.centerY(), + box.centerZ(), + box.halfX(), + box.halfY(), + box.halfZ())) + .toList(); + } + + @Nonnull + private static List adjacentNeighbors(int chunkX, int sectionY, int chunkZ) { + return List.of( + new TerrainNeighbor(sourceKey(chunkX - 1, sectionY, chunkZ), + -ADJACENT_SECTION_VOXEL_SHIFT, + 0, + 0), + new TerrainNeighbor(sourceKey(chunkX + 1, sectionY, chunkZ), + ADJACENT_SECTION_VOXEL_SHIFT, + 0, + 0), + new TerrainNeighbor(sourceKey(chunkX, sectionY - 1, chunkZ), + 0, + -ADJACENT_SECTION_VOXEL_SHIFT, + 0), + new TerrainNeighbor(sourceKey(chunkX, sectionY + 1, chunkZ), + 0, + ADJACENT_SECTION_VOXEL_SHIFT, + 0), + new TerrainNeighbor(sourceKey(chunkX, sectionY, chunkZ - 1), + 0, + 0, + -ADJACENT_SECTION_VOXEL_SHIFT), + new TerrainNeighbor(sourceKey(chunkX, sectionY, chunkZ + 1), + 0, + 0, + ADJACENT_SECTION_VOXEL_SHIFT)); + } +} From 423c687c83c1fd1e98ff650fefc1b3a8cd5d7d47 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 14:58:59 +0200 Subject: [PATCH 009/534] feat(core): hydrate physics store persistence rows Signed-off-by: Blovien --- .../PhysicsRestoreStatusResource.java | 12 ++ .../systems/BodyBindingSystem.java | 6 +- .../systems/PersistenceHydrationSystem.java | 182 +++++++++++++++++- 3 files changed, 194 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java index 3373a9c1..58077388 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java @@ -15,6 +15,7 @@ public final class PhysicsRestoreStatusResource implements Resource store, } DynamicsComponent bodyDynamics = dynamics != null ? dynamics : new DynamicsComponent(); TargetComponent initialTarget = target != null ? target : new TargetComponent(); - Vector3f position = initialTarget.isActive() ? initialTarget.getPosition() : new Vector3f(); - Quaternionf rotation = initialTarget.isActive() - ? initialTarget.getRotation() - : new Quaternionf(); + Vector3f position = target != null ? initialTarget.getPosition() : new Vector3f(); + Quaternionf rotation = target != null ? initialTarget.getRotation() : new Quaternionf(); PhysicsBodyType bodyType = bodyDynamics.getBodyType(); float mass = bodyType == PhysicsBodyType.DYNAMIC ? bodyDynamics.getMass() : 0.0f; long bodyId = backendRuntime.createBody(spaceHandle.value(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index 240f5c13..4c7925ba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -1,19 +1,42 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyRuntimeStateDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentColliderDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentJointDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentMaterialDto; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStorePreflight; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentShapeDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentTerrainColliderDto; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; /** - * Preflights persisted PhysicsStore DTOs before backend mutation is allowed. + * Rehydrates persisted PhysicsStore DTOs into ECS rows before backend mutation is allowed. */ public final class PersistenceHydrationSystem extends TickingSystem { @@ -26,7 +49,7 @@ public final class PersistenceHydrationSystem extends TickingSystem store) { PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); - if (restore.isFailed()) { + if (restore.isFailed() || restore.isHydrated()) { return; } PersistentPhysicsStoreResource persistent = store.getResource( @@ -36,7 +59,162 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) restore.markFailed(String.join("; ", result.errors())); return; } + hydrateRows(store, persistent); restore.markComplete(); + restore.markHydrated(); + } + + private static void hydrateRows(@Nonnull Store store, + @Nonnull PersistentPhysicsStoreResource persistent) { + for (PersistentSpaceDto dto : persistent.getSpaces()) { + addSpace(store, dto); + } + for (PersistentShapeDto dto : persistent.getShapes()) { + addShape(store, dto); + } + for (PersistentMaterialDto dto : persistent.getMaterials()) { + addMaterial(store, dto); + } + for (PersistentBodyDto dto : persistent.getBodies()) { + addBody(store, dto); + } + for (PersistentColliderDto dto : persistent.getColliders()) { + addCollider(store, dto); + } + for (PersistentJointDto dto : persistent.getJoints()) { + addJoint(store, dto); + } + for (PersistentTerrainColliderDto dto : persistent.getTerrainColliders()) { + addTerrainCollider(store, dto); + } + } + + private static void addSpace(@Nonnull Store store, + @Nonnull PersistentSpaceDto dto) { + Holder holder = row(store, dto.getSpaceUuid()); + holder.addComponent(SpaceComponent.getComponentType(), + new SpaceComponent(new BackendId(dto.getBackendId()), dto.getGravity())); + add(store, holder); + } + + private static void addShape(@Nonnull Store store, + @Nonnull PersistentShapeDto dto) { + Holder holder = row(store, dto.getShapeUuid()); + holder.addComponent(ShapeComponent.getComponentType(), + new ShapeComponent(dto.getShapeType(), + dto.getHalfExtentX(), + dto.getHalfExtentY(), + dto.getHalfExtentZ(), + dto.getRadius(), + dto.getHalfHeight(), + dto.getAxis(), + dto.getGroundY(), + dto.getResourceKey())); + add(store, holder); + } + + private static void addMaterial(@Nonnull Store store, + @Nonnull PersistentMaterialDto dto) { + Holder holder = row(store, dto.getMaterialUuid()); + holder.addComponent(MaterialComponent.getComponentType(), + new MaterialComponent(dto.getFriction(), dto.getRestitution())); + add(store, holder); + } + + private static void addBody(@Nonnull Store store, + @Nonnull PersistentBodyDto dto) { + Holder holder = row(store, dto.getBodyUuid()); + holder.addComponent(BodyComponent.getComponentType(), + new BodyComponent(dto.getSpaceUuid(), + dto.getKind(), + dto.getPersistenceMode())); + holder.addComponent(DynamicsComponent.getComponentType(), + new DynamicsComponent(dto.getBodyType(), + dto.getMass(), + dto.getLinearDamping(), + dto.getAngularDamping(), + dto.isContinuousCollisionEnabled())); + holder.addComponent(TargetComponent.getComponentType(), + inactiveTarget(dto.getRuntimeState())); + add(store, holder); + } + + private static void addCollider(@Nonnull Store store, + @Nonnull PersistentColliderDto dto) { + Holder holder = row(store, dto.getColliderUuid()); + holder.addComponent(ColliderComponent.getComponentType(), + new ColliderComponent(dto.getBodyUuid(), + dto.getShapeUuid(), + dto.getMaterialUuid(), + dto.getColliderUuid(), + dto.getLocalPosition(), + dto.getLocalRotation(), + dto.isSensor())); + holder.addComponent(CollisionFilterComponent.getComponentType(), + new CollisionFilterComponent(dto.getCollisionGroup(), dto.getCollisionMask())); + add(store, holder); + } + + private static void addJoint(@Nonnull Store store, + @Nonnull PersistentJointDto dto) { + Holder holder = row(store, dto.getJointUuid()); + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(dto.getSpaceUuid()); + joint.setBodyAUuid(dto.getBodyAUuid()); + joint.setBodyBUuid(dto.getBodyBUuid()); + joint.setType(dto.getType()); + joint.setAnchorA(dto.getAnchorA()); + joint.setAnchorB(dto.getAnchorB()); + joint.setAxis(dto.getAxis()); + joint.setLowerLimit(dto.getLowerLimit()); + joint.setUpperLimit(dto.getUpperLimit()); + joint.setEnabled(dto.isEnabled()); + joint.setMotorEnabled(dto.isMotorEnabled()); + joint.setMotorTargetVelocity(dto.getMotorTargetVelocity()); + joint.setMotorMaxForce(dto.getMotorMaxForce()); + joint.setSpringRestLength(dto.getSpringRestLength()); + joint.setSpringStiffness(dto.getSpringStiffness()); + joint.setSpringDamping(dto.getSpringDamping()); + holder.addComponent(JointComponent.getComponentType(), joint); + add(store, holder); + } + + private static void addTerrainCollider(@Nonnull Store store, + @Nonnull PersistentTerrainColliderDto dto) { + Holder holder = row(store, dto.getTerrainColliderUuid()); + holder.addComponent(TerrainColliderComponent.getComponentType(), + new TerrainColliderComponent(dto.getSpaceUuid(), + dto.getSourceKey(), + dto.getChunkX(), + dto.getSectionY(), + dto.getChunkZ(), + dto.getPayloadResourceKey(), + dto.isRetained())); + add(store, holder); + } + + @Nonnull + private static TargetComponent inactiveTarget(@Nonnull PersistentBodyRuntimeStateDto dto) { + TargetComponent target = new TargetComponent(); + target.setActive(false); + target.setPosition(dto.getPosition()); + target.setRotation(dto.getRotation()); + target.setLinearVelocity(dto.getLinearVelocity()); + target.setAngularVelocity(dto.getAngularVelocity()); + return target; + } + + @Nonnull + private static Holder row(@Nonnull Store store, + @Nonnull UUID uuid) { + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(uuid)); + return holder; + } + + private static void add(@Nonnull Store store, + @Nonnull Holder holder) { + store.addEntity(holder, AddReason.LOAD); } @Nonnull From cc31cef7766245ef7975f73a12d275342f73333b Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:22:53 +0200 Subject: [PATCH 010/534] feat(core): add physics store world collision settings Signed-off-by: Blovien --- .../PersistentPhysicsStorePreflight.java | 29 +++ .../persistence/PersistentSpaceDto.java | 127 ++++++++++++- .../PhysicsStoreRegistration.java | 5 + .../systems/PersistenceCaptureSystem.java | 30 +++- .../systems/PersistenceHydrationSystem.java | 9 + .../physicsstore/PhysicsStoreTypes.java | 13 ++ .../components/WorldCollisionComponent.java | 167 ++++++++++++++++++ 7 files changed, 374 insertions(+), 6 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java index c7f4162f..3136c9cf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -55,6 +56,34 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, if (!PhysicsStorePersistenceValidation.isFinite(space.getGravity())) { errors.add("PhysicsStore space " + uuid + " has non-finite gravity"); } + if (space.getWorldCollisionRadius() < 1 + || space.getWorldCollisionRadius() + > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS) { + errors.add("PhysicsStore space " + uuid + + " has invalid world collision radius"); + } + if (space.getWorldCollisionBodyRadius() < 1 + || space.getWorldCollisionBodyRadius() + > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS) { + errors.add("PhysicsStore space " + uuid + + " has invalid world collision body radius"); + } + if (space.getWorldCollisionTtlTicks() < 1 + || space.getWorldCollisionTtlTicks() + > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS) { + errors.add("PhysicsStore space " + uuid + + " has invalid world collision TTL"); + } + if (!Float.isFinite(space.getTerrainFriction()) + || space.getTerrainFriction() < 0.0f) { + errors.add("PhysicsStore space " + uuid + + " has invalid terrain friction"); + } + if (!Float.isFinite(space.getTerrainRestitution()) + || space.getTerrainRestitution() < 0.0f) { + errors.add("PhysicsStore space " + uuid + + " has invalid terrain restitution"); + } } return seen; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java index 4cf248bd..c21dfbf8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java @@ -3,8 +3,11 @@ import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -32,6 +35,48 @@ public final class PersistentSpaceDto { .addValidator(PhysicsStorePersistenceValidation.finiteVector( "Persisted PhysicsStore space gravity must be finite")) .add() + .append(new KeyedCodec<>("WorldCollisionMode", + new EnumCodec<>(WorldCollisionMode.class), + false), + (dto, value) -> dto.worldCollisionMode = value != null + ? value + : WorldCollisionMode.NONE, + PersistentSpaceDto::getWorldCollisionMode) + .add() + .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), + (dto, value) -> dto.nativeVoxelTerrainEnabled = value != null && value, + PersistentSpaceDto::isNativeVoxelTerrainEnabled) + .add() + .append(new KeyedCodec<>("WorldCollisionRadius", Codec.INTEGER, false), + (dto, value) -> dto.worldCollisionRadius = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, + PersistentSpaceDto::getWorldCollisionRadius) + .add() + .append(new KeyedCodec<>("WorldCollisionBodyRadius", Codec.INTEGER, false), + (dto, value) -> dto.worldCollisionBodyRadius = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS, + PersistentSpaceDto::getWorldCollisionBodyRadius) + .add() + .append(new KeyedCodec<>("WorldCollisionTtlTicks", Codec.INTEGER, false), + (dto, value) -> dto.worldCollisionTtlTicks = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS, + PersistentSpaceDto::getWorldCollisionTtlTicks) + .add() + .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), + (dto, value) -> dto.terrainFriction = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, + PersistentSpaceDto::getTerrainFriction) + .add() + .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), + (dto, value) -> dto.terrainRestitution = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, + PersistentSpaceDto::getTerrainRestitution) + .add() .build(); @Nonnull @@ -40,6 +85,18 @@ public final class PersistentSpaceDto { private String backendId = ""; @Nonnull private final Vector3f gravity = new Vector3f(0.0f, -9.81f, 0.0f); + @Nonnull + private WorldCollisionMode worldCollisionMode = WorldCollisionMode.NONE; + private boolean nativeVoxelTerrainEnabled = + PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private int worldCollisionRadius = + PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS; + private int worldCollisionBodyRadius = + PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS; + private int worldCollisionTtlTicks = + PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS; + private float terrainFriction = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION; + private float terrainRestitution = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION; public PersistentSpaceDto() { } @@ -47,9 +104,39 @@ public PersistentSpaceDto() { public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity) { + this(spaceUuid, + backendId, + gravity, + WorldCollisionMode.NONE, + PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED, + PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, + PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS, + PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS, + PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, + PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION); + } + + public PersistentSpaceDto(@Nonnull UUID spaceUuid, + @Nonnull String backendId, + @Nonnull Vector3f gravity, + @Nonnull WorldCollisionMode worldCollisionMode, + boolean nativeVoxelTerrainEnabled, + int worldCollisionRadius, + int worldCollisionBodyRadius, + int worldCollisionTtlTicks, + float terrainFriction, + float terrainRestitution) { this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); this.backendId = Objects.requireNonNull(backendId, "backendId"); this.gravity.set(Objects.requireNonNull(gravity, "gravity")); + this.worldCollisionMode = Objects.requireNonNull(worldCollisionMode, + "worldCollisionMode"); + this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + this.worldCollisionRadius = worldCollisionRadius; + this.worldCollisionBodyRadius = worldCollisionBodyRadius; + this.worldCollisionTtlTicks = worldCollisionTtlTicks; + this.terrainFriction = terrainFriction; + this.terrainRestitution = terrainRestitution; } @Nonnull @@ -67,8 +154,46 @@ public Vector3f getGravity() { return new Vector3f(gravity); } + @Nonnull + public WorldCollisionMode getWorldCollisionMode() { + return worldCollisionMode; + } + + public boolean isNativeVoxelTerrainEnabled() { + return nativeVoxelTerrainEnabled; + } + + public int getWorldCollisionRadius() { + return worldCollisionRadius; + } + + public int getWorldCollisionBodyRadius() { + return worldCollisionBodyRadius; + } + + public int getWorldCollisionTtlTicks() { + return worldCollisionTtlTicks; + } + + public float getTerrainFriction() { + return terrainFriction; + } + + public float getTerrainRestitution() { + return terrainRestitution; + } + @Nonnull public PersistentSpaceDto copy() { - return new PersistentSpaceDto(spaceUuid, backendId, gravity); + return new PersistentSpaceDto(spaceUuid, + backendId, + gravity, + worldCollisionMode, + nativeVoxelTerrainEnabled, + worldCollisionRadius, + worldCollisionBodyRadius, + worldCollisionTtlTicks, + terrainFriction, + terrainRestitution); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 52cf024e..2a70ca32 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -36,6 +36,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.annotation.Nonnull; @@ -88,6 +89,10 @@ public static void register(@Nonnull PluginBase plugin) { TerrainColliderComponent.class, "TerrainCollider", TerrainColliderComponent.CODEC)); + PhysicsStoreTypes.setWorldCollisionComponentType(registry.registerComponent( + WorldCollisionComponent.class, + "WorldCollision", + WorldCollisionComponent.CODEC)); PhysicsStoreTypes.setRuntimeResourceType(registry.registerResource( PhysicsRuntimeResource.class, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index 75354a41..f0aece3c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -32,6 +32,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -136,7 +137,9 @@ private void collectRow(@Nonnull UUID uuid, int index) { SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); if (space != null) { - spaceRows.add(new SpaceRow(uuid, space)); + spaceRows.add(new SpaceRow(uuid, + space, + chunk.getComponent(index, WorldCollisionComponent.getComponentType()))); } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); if (body != null) { @@ -214,13 +217,28 @@ private ObjectOpenHashSet persistentBodyUuids() { @Nonnull private PersistentSpaceDto[] spaceDtos() { return spaceRows.stream() - .map(row -> new PersistentSpaceDto(row.uuid(), - row.space().getBackendIdValue(), - row.space().getGravity())) + .map(this::spaceDto) .sorted(Comparator.comparing(PersistentSpaceDto::getSpaceUuid)) .toArray(PersistentSpaceDto[]::new); } + @Nonnull + private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { + WorldCollisionComponent worldCollision = row.worldCollision() != null + ? row.worldCollision() + : new WorldCollisionComponent(); + return new PersistentSpaceDto(row.uuid(), + row.space().getBackendIdValue(), + row.space().getGravity(), + worldCollision.getMode(), + worldCollision.isNativeVoxelTerrainEnabled(), + worldCollision.getRadius(), + worldCollision.getBodyRadius(), + worldCollision.getTtlTicks(), + worldCollision.getTerrainFriction(), + worldCollision.getTerrainRestitution()); + } + @Nonnull private PersistentBodyDto[] bodyDtos( @Nonnull Map> colliderUuidsByBodyUuid) { @@ -374,7 +392,9 @@ private PersistentTerrainColliderDto[] terrainDtos() { } } - private record SpaceRow(@Nonnull UUID uuid, @Nonnull SpaceComponent space) { + private record SpaceRow(@Nonnull UUID uuid, + @Nonnull SpaceComponent space, + @Nullable WorldCollisionComponent worldCollision) { } private record BodyRow(@Nonnull UUID uuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index c5641ef9..eca363bc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -31,6 +31,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; @@ -93,6 +94,14 @@ private static void addSpace(@Nonnull Store store, Holder holder = row(store, dto.getSpaceUuid()); holder.addComponent(SpaceComponent.getComponentType(), new SpaceComponent(new BackendId(dto.getBackendId()), dto.getGravity())); + holder.addComponent(WorldCollisionComponent.getComponentType(), + new WorldCollisionComponent(dto.getWorldCollisionMode(), + dto.isNativeVoxelTerrainEnabled(), + dto.getWorldCollisionRadius(), + dto.getWorldCollisionBodyRadius(), + dto.getWorldCollisionTtlTicks(), + dto.getTerrainFriction(), + dto.getTerrainRestitution())); add(store, holder); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index bbc41977..e6a5032b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -23,6 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -54,6 +55,8 @@ public final class PhysicsStoreTypes { private static ComponentType targetComponentType; @Nullable private static ComponentType terrainColliderComponentType; + @Nullable + private static ComponentType worldCollisionComponentType; @Nullable private static ResourceType runtimeResourceType; @@ -132,6 +135,11 @@ public static void setTerrainColliderComponentType( terrainColliderComponentType = Objects.requireNonNull(type, "type"); } + public static void setWorldCollisionComponentType( + @Nonnull ComponentType type) { + worldCollisionComponentType = Objects.requireNonNull(type, "type"); + } + public static void setRuntimeResourceType( @Nonnull ResourceType type) { runtimeResourceType = Objects.requireNonNull(type, "type"); @@ -232,6 +240,11 @@ public static ComponentType terrainColli return require(terrainColliderComponentType, "TerrainColliderComponent"); } + @Nonnull + public static ComponentType worldCollisionComponentType() { + return require(worldCollisionComponentType, "WorldCollisionComponent"); + } + @Nonnull public static ResourceType runtimeResourceType() { return require(runtimeResourceType, "PhysicsRuntimeResource"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java new file mode 100644 index 00000000..2db877c2 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java @@ -0,0 +1,167 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Authored world-collision streaming settings for one PhysicsStore space row. + */ +public final class WorldCollisionComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + WorldCollisionComponent.class, + WorldCollisionComponent::new) + .append(new KeyedCodec<>("Mode", new EnumCodec<>(WorldCollisionMode.class), false), + (component, value) -> component.mode = value != null ? value : WorldCollisionMode.NONE, + WorldCollisionComponent::getMode) + .add() + .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), + (component, value) -> component.nativeVoxelTerrainEnabled = value != null && value, + WorldCollisionComponent::isNativeVoxelTerrainEnabled) + .add() + .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), + (component, value) -> component.radius = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, + WorldCollisionComponent::getRadius) + .add() + .append(new KeyedCodec<>("BodyRadius", Codec.INTEGER, false), + (component, value) -> component.bodyRadius = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS, + WorldCollisionComponent::getBodyRadius) + .add() + .append(new KeyedCodec<>("TtlTicks", Codec.INTEGER, false), + (component, value) -> component.ttlTicks = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS, + WorldCollisionComponent::getTtlTicks) + .add() + .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), + (component, value) -> component.terrainFriction = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, + WorldCollisionComponent::getTerrainFriction) + .add() + .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), + (component, value) -> component.terrainRestitution = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, + WorldCollisionComponent::getTerrainRestitution) + .add() + .build(); + + @Nonnull + private WorldCollisionMode mode = WorldCollisionMode.NONE; + private boolean nativeVoxelTerrainEnabled = + PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private int radius = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS; + private int bodyRadius = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS; + private int ttlTicks = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS; + private float terrainFriction = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION; + private float terrainRestitution = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION; + + public WorldCollisionComponent() { + } + + public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this.mode = Objects.requireNonNull(mode, "mode"); + this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + this.radius = radius; + this.bodyRadius = bodyRadius; + this.ttlTicks = ttlTicks; + this.terrainFriction = terrainFriction; + this.terrainRestitution = terrainRestitution; + } + + @Nonnull + public WorldCollisionMode getMode() { + return mode; + } + + public void setMode(@Nonnull WorldCollisionMode mode) { + this.mode = Objects.requireNonNull(mode, "mode"); + } + + public boolean isNativeVoxelTerrainEnabled() { + return nativeVoxelTerrainEnabled; + } + + public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { + this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + } + + public int getRadius() { + return radius; + } + + public void setRadius(int radius) { + this.radius = radius; + } + + public int getBodyRadius() { + return bodyRadius; + } + + public void setBodyRadius(int bodyRadius) { + this.bodyRadius = bodyRadius; + } + + public int getTtlTicks() { + return ttlTicks; + } + + public void setTtlTicks(int ttlTicks) { + this.ttlTicks = ttlTicks; + } + + public float getTerrainFriction() { + return terrainFriction; + } + + public void setTerrainFriction(float terrainFriction) { + this.terrainFriction = terrainFriction; + } + + public float getTerrainRestitution() { + return terrainRestitution; + } + + public void setTerrainRestitution(float terrainRestitution) { + this.terrainRestitution = terrainRestitution; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.worldCollisionComponentType(); + } + + @Nonnull + @Override + public WorldCollisionComponent clone() { + return new WorldCollisionComponent(mode, + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } +} From 2ba4838170b9d9ed9f516a5a6238774c2d770ee3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:29:42 +0200 Subject: [PATCH 011/534] feat(core): index physics store world collision spaces Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 6 ++ .../PhysicsWorldCollisionIndexResource.java | 74 ++++++++++++++++ .../systems/WorldCollisionIndexSystem.java | 85 +++++++++++++++++++ .../physicsstore/PhysicsStoreTypes.java | 13 +++ 4 files changed, 178 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 2a70ca32..9c9ee16b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -12,6 +12,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.ColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.CompletedStepPublicationSystem; @@ -24,6 +25,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.WorldCollisionIndexSystem; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -109,6 +111,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setTerrainPayloadResourceType(registry.registerResource( PhysicsTerrainPayloadResource.class, PhysicsTerrainPayloadResource::new)); + PhysicsStoreTypes.setWorldCollisionIndexResourceType(registry.registerResource( + PhysicsWorldCollisionIndexResource.class, + PhysicsWorldCollisionIndexResource::new)); PhysicsStoreTypes.setPersistentStoreResourceType(registry.registerResource( PersistentPhysicsStoreResource.class, "PersistentPhysicsStore", @@ -126,6 +131,7 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new PersistenceHydrationSystem()); registry.registerSystem(new RequestDrainSystem()); registry.registerSystem(new IdentityIndexSystem()); + registry.registerSystem(new WorldCollisionIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new BodyBindingSystem()); registry.registerSystem(new ColliderBindingSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java new file mode 100644 index 00000000..ff3bf1ff --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java @@ -0,0 +1,74 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.TerrainColliderMode; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Runtime-only copied world-collision settings indexed by PhysicsStore space UUID. + */ +public final class PhysicsWorldCollisionIndexResource implements Resource { + + @Nonnull + private final Map settingsBySpaceUuid = + new Object2ObjectOpenHashMap<>(); + + public PhysicsWorldCollisionIndexResource() { + } + + public void replaceAll(@Nonnull Map settings) { + settingsBySpaceUuid.clear(); + settingsBySpaceUuid.putAll(settings); + } + + @Nonnull + public List streamingSpaces() { + return settingsBySpaceUuid.values().stream() + .filter(settings -> settings.mode() == WorldCollisionMode.STREAMING) + .toList(); + } + + public void clear() { + settingsBySpaceUuid.clear(); + } + + @Nonnull + @Override + public PhysicsWorldCollisionIndexResource clone() { + PhysicsWorldCollisionIndexResource copy = new PhysicsWorldCollisionIndexResource(); + copy.settingsBySpaceUuid.putAll(settingsBySpaceUuid); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.worldCollisionIndexResourceType(); + } + + public record SpaceWorldCollisionSettings(@Nonnull UUID spaceUuid, + @Nonnull WorldCollisionMode mode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + + @Nonnull + public WorldCollisionBuildOptions buildOptions() { + return new WorldCollisionBuildOptions( + TerrainColliderMode.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled), + terrainFriction, + terrainRestitution); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java new file mode 100644 index 00000000..09a044c0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java @@ -0,0 +1,85 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +/** + * Publishes copied world-collision settings for PhysicsStore space rows. + */ +public final class WorldCollisionIndexSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), + new SystemDependency<>(Order.BEFORE, SpaceBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + Map settingsBySpaceUuid = + new Object2ObjectOpenHashMap<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectChunk(settingsBySpaceUuid, chunk); + store.forEachChunk(systemIndex, collector); + store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()) + .replaceAll(settingsBySpaceUuid); + } + + private static void collectChunk( + @Nonnull Map settingsBySpaceUuid, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); + if (space == null) { + continue; + } + UUID spaceUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(spaceUuid)) { + continue; + } + WorldCollisionComponent worldCollision = chunk.getComponent(index, + WorldCollisionComponent.getComponentType()); + WorldCollisionComponent settings = worldCollision != null + ? worldCollision + : new WorldCollisionComponent(); + settingsBySpaceUuid.put(spaceUuid, new SpaceWorldCollisionSettings(spaceUuid, + settings.getMode(), + settings.isNativeVoxelTerrainEnabled(), + settings.getRadius(), + settings.getBodyRadius(), + settings.getTtlTicks(), + settings.getTerrainFriction(), + settings.getTerrainRestitution())); + } + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index e6a5032b..38babc73 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -11,6 +11,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -69,6 +70,8 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType terrainPayloadResourceType; @Nullable + private static ResourceType worldCollisionIndexResourceType; + @Nullable private static ResourceType persistentStoreResourceType; @Nullable private static ResourceType restoreStatusResourceType; @@ -165,6 +168,11 @@ public static void setTerrainPayloadResourceType( terrainPayloadResourceType = Objects.requireNonNull(type, "type"); } + public static void setWorldCollisionIndexResourceType( + @Nonnull ResourceType type) { + worldCollisionIndexResourceType = Objects.requireNonNull(type, "type"); + } + public static void setPersistentStoreResourceType( @Nonnull ResourceType type) { persistentStoreResourceType = Objects.requireNonNull(type, "type"); @@ -270,6 +278,11 @@ public static ResourceType terrainP return require(terrainPayloadResourceType, "PhysicsTerrainPayloadResource"); } + @Nonnull + public static ResourceType worldCollisionIndexResourceType() { + return require(worldCollisionIndexResourceType, "PhysicsWorldCollisionIndexResource"); + } + @Nonnull public static ResourceType persistentStoreResourceType() { return require(persistentStoreResourceType, "PersistentPhysicsStoreResource"); From 885dae58f355a8711b6ab3814d1079e8c7825a13 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:38:08 +0200 Subject: [PATCH 012/534] feat(core): emit physics store terrain requests Signed-off-by: Blovien --- .../PhysicsStoreTerrainRequestCache.java | 607 ++++++++++++++++++ ...sicsStoreWorldCollisionProducerSystem.java | 363 +++++++++++ .../PhysicsRequestQueueResource.java | 10 +- .../ImpulseWorldCollisionPlugin.java | 12 +- 4 files changed, 985 insertions(+), 7 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequestCache.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequestCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequestCache.java new file mode 100644 index 00000000..795d6fda --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequestCache.java @@ -0,0 +1,607 @@ +package dev.hytalemodding.impulse.core.internal.modules.worldcollision; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.MissingSectionReason; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import it.unimi.dsi.fastutil.longs.Long2LongMap; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3d; + +/** + * Section cache for PhysicsStore terrain request producers. + */ +public final class PhysicsStoreTerrainRequestCache { + + private static final int ACTIVE_BODY_STREAMING_INTERVAL_TICKS = 4; + private static final int SLEEPING_BODY_STREAMING_INTERVAL_TICKS = 20; + private static final int MISSING_BLOCK_CHUNK_RETRY_TICKS = 10; + private static final int MISSING_BLOCK_SECTION_RETRY_TICKS = 5; + private static final long BODY_TARGET_REFRESH_PENDING = Long.MIN_VALUE; + + @Nonnull + private final Object2ObjectMap spaces = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final ShapeTemplateCache shapeTemplates = new ShapeTemplateCache(); + @Nonnull + private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); + + @Nonnull + public synchronized WorldVoxelCollisionCache.BuildStats ensureAround(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsRequestQueueResource queue, + @Nonnull Vector3d center, + int radius, + long tick, + @Nullable Snapshot profiling, + @Nullable LongSet visitedSections, + @Nullable StreamingTargetDiagnostic targetDiagnostic, + @Nonnull WorldCollisionBuildOptions buildOptions) { + long start = profiling != null ? System.nanoTime() : 0L; + if (profiling != null) { + profiling.incrementEnsureCalls(); + } + + int minX = (int) Math.floor(center.x) - radius; + int maxX = (int) Math.floor(center.x) + radius; + int minY = Math.max(0, (int) Math.floor(center.y) - radius); + int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, (int) Math.floor(center.y) + radius); + int minZ = (int) Math.floor(center.z) - radius; + int maxZ = (int) Math.floor(center.z) + radius; + + int minChunkX = ChunkUtil.chunkCoordinate(minX); + int maxChunkX = ChunkUtil.chunkCoordinate(maxX); + int minSectionY = ChunkUtil.indexSection(minY); + int maxSectionY = ChunkUtil.indexSection(maxY); + int minChunkZ = ChunkUtil.chunkCoordinate(minZ); + int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); + + WorldVoxelCollisionCache.BuildStats total = WorldVoxelCollisionCache.BuildStats.empty(); + for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { + for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { + long key = packSectionKey(chunkX, sectionY, chunkZ); + if (visitedSections != null && !visitedSections.add(key)) { + if (profiling != null) { + profiling.incrementDuplicateSkips(); + } + continue; + } + total = total.plus(ensureSection(world, + spaceUuid, + queue, + chunkX, + sectionY, + chunkZ, + tick, + profiling, + targetDiagnostic, + buildOptions)); + } + } + } + if (profiling != null) { + profiling.addEnsureAroundNanos(System.nanoTime() - start); + } + return total; + } + + public synchronized int pruneUnused(@Nonnull UUID spaceUuid, + @Nonnull PhysicsRequestQueueResource queue, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + long start = profiling != null ? System.nanoTime() : 0L; + SpaceCollisionCache cache = spaces.get(spaceUuid); + if (cache == null) { + return 0; + } + + int removedBodies = 0; + int removedSections = 0; + Iterator> iterator = + cache.sections.long2ObjectEntrySet().iterator(); + while (iterator.hasNext()) { + CachedSection section = iterator.next().getValue(); + if (currentTick - section.lastUsedTick <= ttlTicks) { + continue; + } + removedBodies += removeSection(spaceUuid, queue, section); + removedSections++; + iterator.remove(); + } + pruneExpiredMissingBackoffs(cache, currentTick); + if (cache.isEmpty()) { + spaces.remove(spaceUuid); + } + if (profiling != null) { + profiling.addTtlPrune(removedSections, removedBodies); + profiling.addPruneUnusedNanos(System.nanoTime() - start); + } + return removedBodies; + } + + public synchronized int pruneUnloaded(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsRequestQueueResource queue, + @Nullable Snapshot profiling) { + long start = profiling != null ? System.nanoTime() : 0L; + SpaceCollisionCache cache = spaces.get(spaceUuid); + if (cache == null) { + return 0; + } + + int removedBodies = 0; + int removedSections = 0; + Iterator> iterator = + cache.sections.long2ObjectEntrySet().iterator(); + while (iterator.hasNext()) { + CachedSection section = iterator.next().getValue(); + if (blockChunk(world, section.chunkX, section.chunkZ) != null) { + continue; + } + removedBodies += removeSection(spaceUuid, queue, section); + removedSections++; + iterator.remove(); + } + if (cache.isEmpty()) { + spaces.remove(spaceUuid); + } + if (profiling != null) { + profiling.addUnloadedPrune(removedSections, removedBodies); + profiling.addPruneUnloadedNanos(System.nanoTime() - start); + } + return removedBodies; + } + + public synchronized void retainSpaces(@Nonnull Set retainedSpaces, + @Nonnull PhysicsRequestQueueResource queue) { + Iterator> iterator = + spaces.object2ObjectEntrySet().iterator(); + while (iterator.hasNext()) { + Object2ObjectMap.Entry entry = iterator.next(); + if (retainedSpaces.contains(entry.getKey())) { + continue; + } + removeAllSections(entry.getKey(), queue, entry.getValue()); + iterator.remove(); + } + } + + @Nonnull + public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + SpaceCollisionCache cache = spaces.computeIfAbsent(spaceUuid, _ -> new SpaceCollisionCache()); + CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyUuid); + if (target == null) { + cache.bodyTargets.put(bodyUuid, new CachedBodyStreamingTarget(bounds, + sleeping, + currentTick, + BODY_TARGET_REFRESH_PENDING)); + if (profiling != null) { + profiling.incrementBodyTargetFirstSeen(); + } + return TargetRefreshDecision.refresh(TargetRefreshReason.FIRST_SEEN); + } + + target.lastSeenTick = currentTick; + target.sleeping = sleeping; + if (profiling != null) { + profiling.incrementBodyTargetCacheHits(); + } + + if (!target.bounds.equals(bounds)) { + target.bounds = bounds; + if (profiling != null) { + profiling.incrementBodyTargetBoundsChanged(); + } + return TargetRefreshDecision.refresh(TargetRefreshReason.BOUNDS_CHANGED); + } + if (target.lastRefreshTick == BODY_TARGET_REFRESH_PENDING) { + return TargetRefreshDecision.refresh(TargetRefreshReason.PENDING_APPLY); + } + + int interval = sleeping ? sleepingBodyStreamingInterval(ttlTicks) + : ACTIVE_BODY_STREAMING_INTERVAL_TICKS; + if (currentTick == 1L || currentTick - target.lastRefreshTick >= interval) { + if (profiling != null) { + if (sleeping) { + profiling.incrementBodyTargetSleepingRefreshes(); + } else { + profiling.incrementBodyTargetActiveRefreshes(); + } + } + return TargetRefreshDecision.refresh(sleeping + ? TargetRefreshReason.SLEEPING_INTERVAL + : TargetRefreshReason.ACTIVE_INTERVAL); + } + + if (profiling != null) { + if (sleeping) { + profiling.incrementBodyTargetSleepingStableSkips(); + } else { + profiling.incrementBodyTargetActiveStableSkips(); + } + } + return TargetRefreshDecision.skip(); + } + + public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick) { + SpaceCollisionCache cache = spaces.computeIfAbsent(spaceUuid, _ -> new SpaceCollisionCache()); + CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyUuid); + if (target == null) { + cache.bodyTargets.put(bodyUuid, new CachedBodyStreamingTarget(bounds, + sleeping, + currentTick, + currentTick)); + return; + } + target.bounds = bounds; + target.sleeping = sleeping; + target.lastSeenTick = currentTick; + target.lastRefreshTick = currentTick; + } + + public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + SpaceCollisionCache cache = spaces.get(spaceUuid); + if (cache == null) { + return 0; + } + long maxAge = Math.max(1L, ttlTicks) * 2L; + int removed = 0; + Iterator> iterator = + cache.bodyTargets.object2ObjectEntrySet().iterator(); + while (iterator.hasNext()) { + CachedBodyStreamingTarget target = iterator.next().getValue(); + if (currentTick - target.lastSeenTick <= maxAge) { + continue; + } + iterator.remove(); + removed++; + } + pruneExpiredMissingBackoffs(cache, currentTick); + if (cache.isEmpty()) { + spaces.remove(spaceUuid); + } + if (removed > 0 && profiling != null) { + profiling.addBodyTargetsPruned(removed); + } + return removed; + } + + @Nonnull + private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsRequestQueueResource queue, + int chunkX, + int sectionY, + int chunkZ, + long tick, + @Nullable Snapshot profiling, + @Nullable StreamingTargetDiagnostic targetDiagnostic, + @Nonnull WorldCollisionBuildOptions buildOptions) { + long start = profiling != null ? System.nanoTime() : 0L; + if (profiling != null) { + profiling.incrementSectionRequests(); + } + + SpaceCollisionCache cache = spaces.computeIfAbsent(spaceUuid, _ -> new SpaceCollisionCache()); + long chunkKey = ChunkUtil.indexChunk(chunkX, chunkZ); + long sectionKey = packSectionKey(chunkX, sectionY, chunkZ); + if (isBackedOff(cache.missingBlockChunkBackoffs, chunkKey, tick)) { + recordMissingBackoff(profiling, + MissingSectionReason.BLOCK_CHUNK, + chunkX, + sectionY, + chunkZ, + targetDiagnostic, + start); + return WorldVoxelCollisionCache.BuildStats.empty(); + } + if (blockChunk(world, chunkX, chunkZ) == null) { + cache.missingBlockChunkBackoffs.put(chunkKey, tick + MISSING_BLOCK_CHUNK_RETRY_TICKS); + recordMissing(profiling, + MissingSectionReason.BLOCK_CHUNK, + chunkX, + sectionY, + chunkZ, + targetDiagnostic, + start); + return WorldVoxelCollisionCache.BuildStats.empty(); + } + cache.missingBlockChunkBackoffs.remove(chunkKey); + + if (isBackedOff(cache.missingBlockSectionBackoffs, sectionKey, tick)) { + recordMissingBackoff(profiling, + MissingSectionReason.BLOCK_SECTION, + chunkX, + sectionY, + chunkZ, + targetDiagnostic, + start); + return WorldVoxelCollisionCache.BuildStats.empty(); + } + BlockSection section = ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); + if (section == null) { + cache.missingBlockSectionBackoffs.put(sectionKey, + tick + MISSING_BLOCK_SECTION_RETRY_TICKS); + recordMissing(profiling, + MissingSectionReason.BLOCK_SECTION, + chunkX, + sectionY, + chunkZ, + targetDiagnostic, + start); + return WorldVoxelCollisionCache.BuildStats.empty(); + } + cache.missingBlockSectionBackoffs.remove(sectionKey); + + long neighborhoodSignature = sectionBuilder.neighborhoodSignature(world, + section, + chunkX, + sectionY, + chunkZ); + CachedSection cached = cache.sections.get(sectionKey); + if (cached != null + && cached.neighborhoodSignature == neighborhoodSignature + && cached.buildOptions.equals(buildOptions)) { + cached.lastUsedTick = tick; + if (profiling != null) { + profiling.incrementSectionCacheHits(); + profiling.addEnsureSectionNanos(System.nanoTime() - start); + } + return WorldVoxelCollisionCache.BuildStats.empty(); + } + + SectionCollisionGeometry geometry = sectionBuilder.build(world, + section, + chunkX, + sectionY, + chunkZ); + CachedSection built = new CachedSection(chunkX, + sectionY, + chunkZ, + tick, + neighborhoodSignature, + buildOptions, + bodyCount(geometry, buildOptions), + buildOptions.nativeVoxelTerrainEnabled() && geometry.hasFullCubeVoxels()); + int removedBodies = cached != null ? removeSection(spaceUuid, queue, cached) : 0; + if (built.bodyCount > 0) { + queue.enqueue(PhysicsStoreTerrainRequests.upsert(spaceUuid, + chunkX, + sectionY, + chunkZ, + neighborhoodSignature, + geometry, + buildOptions)); + } + cache.sections.put(sectionKey, built); + WorldVoxelCollisionCache.BuildStats stats = new WorldVoxelCollisionCache.BuildStats( + geometry.scannedBlocks(), + geometry.solidBlocks(), + geometry.culledInteriorBlocks(), + geometry.mergedFullCubeBoxes().size(), + geometry.detailBoxCount(), + built.bodyCount, + removedBodies, + cached == null ? 1 : 0, + cached == null ? 0 : 1, + built.voxelTerrain ? 1 : 0); + if (profiling != null) { + profiling.addBuildStats(stats); + profiling.addEnsureSectionNanos(System.nanoTime() - start); + } + return stats; + } + + private static int bodyCount(@Nonnull SectionCollisionGeometry geometry, + @Nonnull WorldCollisionBuildOptions buildOptions) { + int fullCubeBodyCount = buildOptions.nativeVoxelTerrainEnabled() + && geometry.hasFullCubeVoxels() + ? 1 + : geometry.mergedFullCubeBoxes().size(); + return fullCubeBodyCount + geometry.detailBoxes().size(); + } + + private static int removeSection(@Nonnull UUID spaceUuid, + @Nonnull PhysicsRequestQueueResource queue, + @Nonnull CachedSection section) { + if (section.bodyCount <= 0) { + return 0; + } + queue.enqueue(PhysicsStoreTerrainRequests.remove(spaceUuid, + section.chunkX, + section.sectionY, + section.chunkZ)); + return section.bodyCount; + } + + private static void removeAllSections(@Nonnull UUID spaceUuid, + @Nonnull PhysicsRequestQueueResource queue, + @Nonnull SpaceCollisionCache cache) { + for (CachedSection section : cache.sections.values()) { + removeSection(spaceUuid, queue, section); + } + cache.sections.clear(); + } + + private static void recordMissingBackoff(@Nullable Snapshot profiling, + @Nonnull MissingSectionReason reason, + int chunkX, + int sectionY, + int chunkZ, + @Nullable StreamingTargetDiagnostic targetDiagnostic, + long start) { + if (profiling == null) { + return; + } + profiling.incrementMissingBackoffSkip(reason); + recordMissing(profiling, reason, chunkX, sectionY, chunkZ, targetDiagnostic, start); + } + + private static void recordMissing(@Nullable Snapshot profiling, + @Nonnull MissingSectionReason reason, + int chunkX, + int sectionY, + int chunkZ, + @Nullable StreamingTargetDiagnostic targetDiagnostic, + long start) { + if (profiling == null) { + return; + } + profiling.recordMissingSection(reason, chunkX, sectionY, chunkZ, targetDiagnostic); + profiling.addEnsureSectionNanos(System.nanoTime() - start); + } + + private static boolean isBackedOff(@Nonnull Long2LongMap backoffs, long key, long tick) { + return backoffs.containsKey(key) && backoffs.get(key) > tick; + } + + private static void pruneExpiredMissingBackoffs(@Nonnull SpaceCollisionCache cache, long tick) { + cache.missingBlockChunkBackoffs.long2LongEntrySet().removeIf(entry -> entry.getLongValue() <= tick); + cache.missingBlockSectionBackoffs.long2LongEntrySet().removeIf(entry -> entry.getLongValue() <= tick); + } + + private static int sleepingBodyStreamingInterval(int ttlTicks) { + int ttlBound = Math.max(1, ttlTicks / 4); + return Math.max(ACTIVE_BODY_STREAMING_INTERVAL_TICKS, + Math.min(SLEEPING_BODY_STREAMING_INTERVAL_TICKS, ttlBound)); + } + + private static long packSectionKey(int chunkX, int sectionY, int chunkZ) { + long x = ((long) chunkX & 0x3FFFFFL) << 42; + long y = ((long) sectionY & 0x3FFL) << 32; + long z = (long) chunkZ & 0xFFFFFFFFL; + return x | y | z; + } + + @Nullable + private static BlockChunk blockChunk(@Nonnull World world, int chunkX, int chunkZ) { + Ref chunkRef = world.getChunkStore() + .getChunkReference(ChunkUtil.indexChunk(chunkX, chunkZ)); + if (chunkRef == null || !chunkRef.isValid()) { + return null; + } + Store store = world.getChunkStore().getStore(); + return store.getComponentConcurrent(chunkRef, BlockChunk.getComponentType()); + } + + private static final class SpaceCollisionCache { + + private final Long2ObjectMap sections = new Long2ObjectOpenHashMap<>(); + private final Long2LongMap missingBlockChunkBackoffs = new Long2LongOpenHashMap(); + private final Long2LongMap missingBlockSectionBackoffs = new Long2LongOpenHashMap(); + private final Object2ObjectMap bodyTargets = + new Object2ObjectOpenHashMap<>(); + + private boolean isEmpty() { + return sections.isEmpty() + && missingBlockChunkBackoffs.isEmpty() + && missingBlockSectionBackoffs.isEmpty() + && bodyTargets.isEmpty(); + } + } + + private static final class CachedSection { + + private final int chunkX; + private final int sectionY; + private final int chunkZ; + private final long neighborhoodSignature; + @Nonnull + private final WorldCollisionBuildOptions buildOptions; + private final int bodyCount; + private final boolean voxelTerrain; + private long lastUsedTick; + + private CachedSection(int chunkX, + int sectionY, + int chunkZ, + long lastUsedTick, + long neighborhoodSignature, + @Nonnull WorldCollisionBuildOptions buildOptions, + int bodyCount, + boolean voxelTerrain) { + this.chunkX = chunkX; + this.sectionY = sectionY; + this.chunkZ = chunkZ; + this.lastUsedTick = lastUsedTick; + this.neighborhoodSignature = neighborhoodSignature; + this.buildOptions = buildOptions; + this.bodyCount = bodyCount; + this.voxelTerrain = voxelTerrain; + } + } + + private static final class CachedBodyStreamingTarget { + + @Nonnull + private WorldCollisionStreamingBounds bounds; + private boolean sleeping; + private long lastSeenTick; + private long lastRefreshTick; + + private CachedBodyStreamingTarget(@Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long lastSeenTick, + long lastRefreshTick) { + this.bounds = bounds; + this.sleeping = sleeping; + this.lastSeenTick = lastSeenTick; + this.lastRefreshTick = lastRefreshTick; + } + } + + public enum TargetRefreshReason { + FIRST_SEEN, + BOUNDS_CHANGED, + PENDING_APPLY, + ACTIVE_INTERVAL, + SLEEPING_INTERVAL, + STABLE_SKIP + } + + public record TargetRefreshDecision(boolean refresh, @Nonnull TargetRefreshReason reason) { + + @Nonnull + private static TargetRefreshDecision refresh(@Nonnull TargetRefreshReason reason) { + return new TargetRefreshDecision(true, reason); + } + + @Nonnull + private static TargetRefreshDecision skip() { + return new TargetRefreshDecision(false, TargetRefreshReason.STABLE_SKIP); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java new file mode 100644 index 00000000..803e214e --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -0,0 +1,363 @@ +package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainRequestCache; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainRequestCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionStreamingBounds; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3d; +import org.joml.Vector3f; + +/** + * Produces copied PhysicsStore terrain requests from EntityStore and ChunkStore state. + */ +public final class PhysicsStoreWorldCollisionProducerSystem extends TickingSystem + implements QuerySystem { + + @Nullable + private static volatile ComponentType playerType; + @Nullable + private static volatile ComponentType transformType; + @Nullable + private static volatile Query query; + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, PhysicsSyncSystem.class) + ); + + @Nonnull + private final Map, StreamingState> statesByStore = + Collections.synchronizedMap(new WeakHashMap<>()); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + if (!WorldCollisionLifecycle.isEnabled()) { + return; + } + + WorldCollisionProfilingResource profiling = store.getResource( + WorldCollisionProfilingResource.getResourceType()); + Snapshot snapshot = profiling.isEnabled() ? profiling.beginTick() : null; + long tickStart = snapshot != null ? System.nanoTime() : 0L; + try { + World world = store.getExternalData().getWorld(); + PhysicsStore physicsStore = PhysicsStoreAccess.require(world); + Store physics = physicsStore.getStore(); + PhysicsRequestQueueResource queue = physics.getResource( + PhysicsRequestQueueResource.getResourceType()); + PhysicsWorldCollisionIndexResource worldCollisionIndex = physics.getResource( + PhysicsWorldCollisionIndexResource.getResourceType()); + PhysicsSnapshotResource snapshotResource = physics.getResource( + PhysicsSnapshotResource.getResourceType()); + + List spaces = worldCollisionIndex.streamingSpaces(); + StreamingState state = stateFor(store); + if (spaces.isEmpty()) { + state.cache().retainSpaces(Set.of(), queue); + return; + } + + List playerPositions = collectPlayerPositions(store, systemIndex); + if (snapshot != null) { + snapshot.setPlayerStreamingTargets(playerPositions.size()); + } + long currentTick = state.nextTick(); + PhysicsStoreTerrainRequestCache cache = state.cache(); + Set retainedSpaces = new ObjectOpenHashSet<>(); + for (SpaceWorldCollisionSettings settings : spaces) { + retainedSpaces.add(settings.spaceUuid()); + } + cache.retainSpaces(retainedSpaces, queue); + + PhysicsStoreSnapshotFrame physicsFrame = snapshotResource.getLatestFrame(); + for (SpaceWorldCollisionSettings settings : spaces) { + if (snapshot != null) { + snapshot.incrementStreamingSpaces(); + } + processSpace(world, + cache, + queue, + settings, + playerPositions, + physicsFrame, + currentTick, + snapshot); + } + } finally { + if (snapshot != null) { + snapshot.setTickNanos(System.nanoTime() - tickStart); + profiling.finishTick(snapshot); + } + } + } + + private static void processSpace(@Nonnull World world, + @Nonnull PhysicsStoreTerrainRequestCache cache, + @Nonnull PhysicsRequestQueueResource queue, + @Nonnull SpaceWorldCollisionSettings settings, + @Nonnull List playerPositions, + @Nonnull PhysicsStoreSnapshotFrame physicsFrame, + long currentTick, + @Nullable Snapshot snapshot) { + LongSet visitedSections = new LongOpenHashSet(); + for (Vector3d position : playerPositions) { + int sectionsBefore = visitedSections.size(); + cache.ensureAround(world, + settings.spaceUuid(), + queue, + position, + settings.radius(), + currentTick, + snapshot, + visitedSections, + snapshot != null ? StreamingTargetDiagnostic.player(position) : null, + settings.buildOptions()); + if (snapshot != null) { + snapshot.addPlayerSectionTargets(visitedSections.size() - sectionsBefore); + } + } + + for (BodyStreamingTarget target : collectDynamicBodyTargets(cache, + settings, + physicsFrame, + currentTick, + snapshot)) { + int sectionsBefore = visitedSections.size(); + cache.ensureAround(world, + settings.spaceUuid(), + queue, + target.position(), + settings.bodyRadius(), + currentTick, + snapshot, + visitedSections, + null, + settings.buildOptions()); + for (BodyStreamingRefresh refresh : target.refreshes()) { + cache.recordBodyTargetRefresh(settings.spaceUuid(), + refresh.bodyUuid(), + target.bounds(), + refresh.sleeping(), + currentTick); + } + if (snapshot != null) { + snapshot.addBodySectionTargets(visitedSections.size() - sectionsBefore); + } + } + + cache.pruneUnloaded(world, settings.spaceUuid(), queue, snapshot); + cache.pruneUnused(settings.spaceUuid(), queue, currentTick, settings.ttlTicks(), snapshot); + cache.pruneBodyStreamingTargets(settings.spaceUuid(), + currentTick, + settings.ttlTicks(), + snapshot); + } + + @Nonnull + private static List collectDynamicBodyTargets( + @Nonnull PhysicsStoreTerrainRequestCache cache, + @Nonnull SpaceWorldCollisionSettings settings, + @Nonnull PhysicsStoreSnapshotFrame physicsFrame, + long currentTick, + @Nullable Snapshot snapshot) { + Map uniqueTargets = + new Object2ObjectOpenHashMap<>(); + int spatialCandidates = 0; + int dynamicCandidates = 0; + for (PhysicsStoreBodySnapshot body : physicsFrame.bodies()) { + if (!body.spaceUuid().equals(settings.spaceUuid())) { + continue; + } + spatialCandidates++; + if (body.bodyType() != PhysicsBodyType.DYNAMIC) { + continue; + } + dynamicCandidates++; + Vector3f position = body.position(); + WorldCollisionStreamingBounds bounds = WorldCollisionStreamingBounds.from(position.x, + position.y, + position.z, + settings.bodyRadius()); + TargetRefreshDecision decision = cache.shouldRefreshBodyTarget(settings.spaceUuid(), + body.bodyUuid(), + bounds, + body.sleeping(), + currentTick, + settings.ttlTicks(), + snapshot); + if (!decision.refresh()) { + continue; + } + + BodyStreamingTarget target = uniqueTargets.get(bounds); + if (target == null) { + target = new BodyStreamingTarget(new Vector3d(position.x, position.y, position.z), + bounds, + new ArrayList<>()); + uniqueTargets.put(bounds, target); + } else if (snapshot != null) { + snapshot.incrementBodyTargetDedupeSkips(); + } + target.refreshes().add(new BodyStreamingRefresh(body.bodyUuid(), body.sleeping())); + } + + if (snapshot != null) { + snapshot.addBodySpatialIndexCandidates(spatialCandidates); + snapshot.addBodyStreamingCandidates(dynamicCandidates); + snapshot.addBodyStreamingTargets(uniqueTargets.size()); + } + return new ArrayList<>(uniqueTargets.values()); + } + + @Nonnull + private static List collectPlayerPositions(@Nonnull Store store, + int systemIndex) { + List playerPositions = new ArrayList<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectPlayerPositions(chunk, playerPositions); + store.forEachChunk(systemIndex, collector); + return List.copyOf(playerPositions); + } + + private static void collectPlayerPositions(@Nonnull ArchetypeChunk chunk, + @Nonnull List positions) { + for (int index = 0; index < chunk.size(); index++) { + TransformComponent transform = chunk.getComponent(index, transformType()); + if (transform != null) { + positions.add(new Vector3d(transform.getPosition())); + } + } + } + + @Nonnull + private StreamingState stateFor(@Nonnull Store store) { + synchronized (statesByStore) { + return statesByStore.computeIfAbsent(store, _ -> new StreamingState()); + } + } + + @Nonnull + @Override + public Query getQuery() { + return query(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Nonnull + private static Query query() { + Query resolved = query; + if (resolved != null) { + return resolved; + } + synchronized (PhysicsStoreWorldCollisionProducerSystem.class) { + resolved = query; + if (resolved == null) { + resolved = Query.and(playerType(), transformType()); + query = resolved; + } + } + return resolved; + } + + @Nonnull + private static ComponentType playerType() { + ComponentType resolved = playerType; + if (resolved != null) { + return resolved; + } + synchronized (PhysicsStoreWorldCollisionProducerSystem.class) { + resolved = playerType; + if (resolved == null) { + resolved = Player.getComponentType(); + playerType = resolved; + } + } + return resolved; + } + + @Nonnull + private static ComponentType transformType() { + ComponentType resolved = transformType; + if (resolved != null) { + return resolved; + } + synchronized (PhysicsStoreWorldCollisionProducerSystem.class) { + resolved = transformType; + if (resolved == null) { + resolved = TransformComponent.getComponentType(); + transformType = resolved; + } + } + return resolved; + } + + private record BodyStreamingTarget(@Nonnull Vector3d position, + @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull List refreshes) { + } + + private record BodyStreamingRefresh(@Nonnull UUID bodyUuid, + boolean sleeping) { + } + + private static final class StreamingState { + + @Nonnull + private final PhysicsStoreTerrainRequestCache cache = new PhysicsStoreTerrainRequestCache(); + private long tick; + + @Nonnull + private PhysicsStoreTerrainRequestCache cache() { + return cache; + } + + private synchronized long nextTick() { + return ++tick; + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java index 1143f526..8b2cf4e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java @@ -22,12 +22,12 @@ public final class PhysicsRequestQueueResource implements Resource public PhysicsRequestQueueResource() { } - public void enqueue(@Nonnull PhysicsStoreRequest request) { + public synchronized void enqueue(@Nonnull PhysicsStoreRequest request) { requests.add(request); } @Nonnull - public List drain() { + public synchronized List drain() { List drained = new ArrayList<>(requests.size()); PhysicsStoreRequest request; while ((request = requests.poll()) != null) { @@ -36,17 +36,17 @@ public List drain() { return drained; } - public int size() { + public synchronized int size() { return requests.size(); } - public void clear() { + public synchronized void clear() { requests.clear(); } @Nonnull @Override - public PhysicsRequestQueueResource clone() { + public synchronized PhysicsRequestQueueResource clone() { PhysicsRequestQueueResource copy = new PhysicsRequestQueueResource(); copy.requests.addAll(requests); return copy; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java index eb3dd424..e49f83dd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java @@ -1,9 +1,12 @@ package dev.hytalemodding.impulse.core.plugin.modules.worldcollision; +import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsStoreWorldCollisionProducerSystem; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -21,8 +24,13 @@ public ImpulseWorldCollisionPlugin(@Nonnull JavaPluginInit init) { @Override protected void setup() { - LOGGER.at(Level.INFO).log("Impulse world-collision legacy EntityStore systems are " - + "disabled while authoritative PhysicsStore terrain binding is being migrated."); + ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); + WorldCollisionProfilingResource.setResourceType(entityRegistry.registerResource( + WorldCollisionProfilingResource.class, + WorldCollisionProfilingResource::new)); + entityRegistry.registerSystem(new PhysicsStoreWorldCollisionProducerSystem()); + WorldCollisionLifecycle.enable(); + LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore request producer enabled."); } @Override From 20c4fc21e7366b22996c37b3ac7d98916b225371 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:41:08 +0200 Subject: [PATCH 013/534] fix(core): keep physics store sync projection isolated Signed-off-by: Blovien --- .../core/internal/systems/sync/PhysicsSyncSystem.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index deff5841..d59a6ead 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -51,9 +51,9 @@ * hydrated bodies, and hydrated joints are all settled before this system reads * body transforms.

* - *

Entities attach to body keys. Backend body destruction is explicit at the - * world resource boundary; missing generated visual proxies are removed, while - * gameplay entities merely lose the attachment.

+ *

Entities attach to PhysicsStore body UUIDs or legacy body keys. Backend body destruction is + * explicit at the world resource boundary; missing generated visual proxies are removed from the + * legacy path, while gameplay entities merely lose the attachment.

*/ public class PhysicsSyncSystem extends EntityTickingSystem { @@ -138,7 +138,6 @@ public void tick(float dt, } Scratch local = scratch.get(); - PhysicsWorldRuntimeResource resource = local.getResource(store); PhysicsRuntimeProfilingResource.SyncCollector collector = local.getSyncCollector(store); if (collector != null) { collector.incrementBodiesInspected(); @@ -155,6 +154,10 @@ public void tick(float dt, } return; } + if (attachment.getPhysicsBodyUuid() != null) { + return; + } + PhysicsWorldRuntimeResource resource = local.getResource(store); PhysicsBodyRegistrationView registration = resource.getBodyRegistrationView(attachment.getBodyKey()); if (registration == null) { From 52769f367bbff959a9059c94d2e6bdf77305aa5f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:47:43 +0200 Subject: [PATCH 014/534] feat(core): produce physics store target requests Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 2 + .../systems/RequestDrainSystem.java | 3 + .../systems/TargetBindingSystem.java | 49 +++--- ...icsStoreKinematicTargetProducerSystem.java | 147 ++++++++++++++++++ .../components/TargetComponent.java | 42 +++++ .../requests/BodyTargetRequest.java | 30 +++- 6 files changed, 250 insertions(+), 23 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 335d80a3..b2d26854 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -30,6 +30,7 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; +import dev.hytalemodding.impulse.core.internal.systems.body.PhysicsStoreKinematicTargetProducerSystem; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; @@ -358,6 +359,7 @@ private void registerSystems() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); persistenceRestoreGroup = entityRegistry.registerSystemGroup(); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); + entityRegistry.registerSystem(new PhysicsStoreKinematicTargetProducerSystem()); entityRegistry.registerSystem(new PhysicsSyncSystem()); entityRegistry.registerSystem(new PhysicsDebugSystem()); entityRegistry.registerSystem(new PhysicsDetachedVisualMaterializationSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index df84e25e..ae628395 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -88,6 +88,9 @@ private static void applyTargetRequest(@Nonnull Store store, target.setRotation(request.rotation()); target.setLinearVelocity(request.linearVelocity()); target.setAngularVelocity(request.angularVelocity()); + target.setTransformEnabled(request.transformEnabled()); + target.setVelocityEnabled(request.velocityEnabled()); + target.setActivate(request.activate()); store.putComponent(bodyRef, TargetComponent.getComponentType(), target); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index 04e8351e..72f69c33 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -58,27 +58,34 @@ private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, if (backendRuntime == null) { continue; } - Vector3f position = target.getPosition(); - Quaternionf rotation = target.getRotation(); - Vector3f linearVelocity = target.getLinearVelocity(); - Vector3f angularVelocity = target.getAngularVelocity(); - backendRuntime.setBodyTransform(spaceHandle.value(), - bodyHandle.value(), - position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w); - backendRuntime.setBodyVelocity(spaceHandle.value(), - bodyHandle.value(), - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z); + if (target.isTransformEnabled()) { + Vector3f position = target.getPosition(); + Quaternionf rotation = target.getRotation(); + backendRuntime.setBodyTransform(spaceHandle.value(), + bodyHandle.value(), + position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w); + } + if (target.isVelocityEnabled()) { + Vector3f linearVelocity = target.getLinearVelocity(); + Vector3f angularVelocity = target.getAngularVelocity(); + backendRuntime.setBodyVelocity(spaceHandle.value(), + bodyHandle.value(), + linearVelocity.x, + linearVelocity.y, + linearVelocity.z, + angularVelocity.x, + angularVelocity.y, + angularVelocity.z); + } + if (target.isActivate()) { + backendRuntime.activateBody(spaceHandle.value(), bodyHandle.value()); + } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java new file mode 100644 index 00000000..35bc18e7 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java @@ -0,0 +1,147 @@ +package dev.hytalemodding.impulse.core.internal.systems.body; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Emits copied PhysicsStore target requests from EntityStore kinematic target components. + */ +public final class PhysicsStoreKinematicTargetProducerSystem extends TickingSystem + implements QuerySystem { + + private static final ComponentType IDENTITY_TYPE = + PhysicsBodyIdentityComponent.getComponentType(); + private static final ComponentType TARGET_TYPE = + PhysicsBodyKinematicTargetComponent.getComponentType(); + private static final Query QUERY = Query.and(IDENTITY_TYPE, TARGET_TYPE); + private final Set> dependencies = Set.of( + new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), + new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) + ); + @Nonnull + private final Map, RigidBodyKinematicTargetState> statesByStore = + Collections.synchronizedMap(new WeakHashMap<>()); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsStore physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()); + Store physics = physicsStore.getStore(); + PhysicsIdentityIndexResource identity = physics.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsRequestQueueResource queue = physics.getResource( + PhysicsRequestQueueResource.getResourceType()); + RigidBodyKinematicTargetState targetState = stateFor(store); + targetState.beginTick(); + try { + BiConsumer, CommandBuffer> collector = + (chunk, _) -> produceChunk(physics, identity, queue, targetState, chunk); + store.forEachChunk(systemIndex, collector); + } finally { + targetState.finishTick(); + } + } + + private static void produceChunk(@Nonnull Store physics, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRequestQueueResource queue, + @Nonnull RigidBodyKinematicTargetState targetState, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + PhysicsBodyIdentityComponent bodyIdentity = chunk.getComponent(index, IDENTITY_TYPE); + PhysicsBodyKinematicTargetComponent target = chunk.getComponent(index, TARGET_TYPE); + if (bodyIdentity == null || target == null) { + continue; + } + RigidBodyKey bodyKey = bodyIdentity.getBodyKey(); + UUID bodyUuid = bodyKey.value(); + if (!hasPhysicsStoreBody(physics, identity, bodyUuid)) { + targetState.clear(bodyKey); + continue; + } + if (targetState.shouldSubmit(bodyKey, target)) { + queue.enqueue(request(bodyUuid, target)); + } + } + } + + private static boolean hasPhysicsStoreBody(@Nonnull Store physics, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid) { + Ref ref = identity.getByUuid(bodyUuid); + return ref != null + && ref.isValid() + && physics.getComponent(ref, BodyComponent.getComponentType()) != null; + } + + @Nonnull + private static BodyTargetRequest request(@Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyKinematicTargetComponent target) { + Vector3f position = target.getPosition(); + Quaternionf rotation = target.getRotation(); + Vector3f linearVelocity = target.getLinearVelocity(); + Vector3f angularVelocity = target.getAngularVelocity(); + return BodyTargetRequest.of(bodyUuid, + position, + rotation, + linearVelocity, + angularVelocity, + target.isTransformEnabled(), + target.isVelocityEnabled(), + target.isActivate()); + } + + @Nonnull + private RigidBodyKinematicTargetState stateFor(@Nonnull Store store) { + synchronized (statesByStore) { + RigidBodyKinematicTargetState state = statesByStore.get(store); + if (state == null) { + state = new RigidBodyKinematicTargetState(); + statesByStore.put(store, state); + } + return state; + } + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return dependencies; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java index ccef029d..c4fe9f26 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java @@ -45,9 +45,24 @@ public final class TargetComponent implements Component { (component, value) -> component.angularVelocity.set(value != null ? value : ZERO), TargetComponent::getAngularVelocity) .add() + .append(new KeyedCodec<>("TransformEnabled", Codec.BOOLEAN, false), + (component, value) -> component.transformEnabled = value == null || value, + TargetComponent::isTransformEnabled) + .add() + .append(new KeyedCodec<>("VelocityEnabled", Codec.BOOLEAN, false), + (component, value) -> component.velocityEnabled = value == null || value, + TargetComponent::isVelocityEnabled) + .add() + .append(new KeyedCodec<>("Activate", Codec.BOOLEAN, false), + (component, value) -> component.activate = value == null || value, + TargetComponent::isActivate) + .add() .build(); private boolean active; + private boolean transformEnabled = true; + private boolean velocityEnabled = true; + private boolean activate = true; @Nonnull private final Vector3f position = new Vector3f(); @Nonnull @@ -68,6 +83,30 @@ public void setActive(boolean active) { this.active = active; } + public boolean isTransformEnabled() { + return transformEnabled; + } + + public void setTransformEnabled(boolean transformEnabled) { + this.transformEnabled = transformEnabled; + } + + public boolean isVelocityEnabled() { + return velocityEnabled; + } + + public void setVelocityEnabled(boolean velocityEnabled) { + this.velocityEnabled = velocityEnabled; + } + + public boolean isActivate() { + return activate; + } + + public void setActivate(boolean activate) { + this.activate = activate; + } + @Nonnull public Vector3f getPosition() { return new Vector3f(position); @@ -114,6 +153,9 @@ public static ComponentType getComponentType() { public TargetComponent clone() { TargetComponent copy = new TargetComponent(); copy.active = active; + copy.transformEnabled = transformEnabled; + copy.velocityEnabled = velocityEnabled; + copy.activate = activate; copy.position.set(position); copy.rotation.set(rotation); copy.linearVelocity.set(linearVelocity); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java index 070fc354..2d4e7402 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java @@ -14,7 +14,10 @@ public record BodyTargetRequest(@Nonnull UUID requestUuid, @Nonnull Vector3f position, @Nonnull Quaternionf rotation, @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) implements PhysicsStoreRequest { + @Nonnull Vector3f angularVelocity, + boolean transformEnabled, + boolean velocityEnabled, + boolean activate) implements PhysicsStoreRequest { public BodyTargetRequest { Objects.requireNonNull(requestUuid, "requestUuid"); @@ -36,7 +39,30 @@ public static BodyTargetRequest of(@Nonnull UUID bodyUuid, position, rotation, linearVelocity, - angularVelocity); + angularVelocity, + true, + true, + true); + } + + @Nonnull + public static BodyTargetRequest of(@Nonnull UUID bodyUuid, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + boolean transformEnabled, + boolean velocityEnabled, + boolean activate) { + return new BodyTargetRequest(UUID.randomUUID(), + bodyUuid, + position, + rotation, + linearVelocity, + angularVelocity, + transformEnabled, + velocityEnabled, + activate); } @Nonnull From fa26a1418ebf1e0eacda854d70b9f814445ba4dd Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 15:50:54 +0200 Subject: [PATCH 015/534] feat(core): route control targets through physics store Signed-off-by: Blovien --- .../PhysicsKinematicControlSystem.java | 111 +++++++++++++++--- 1 file changed, 97 insertions(+), 14 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index b6a98d9e..17a1f3c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -18,12 +18,18 @@ import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -31,11 +37,13 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.UUID; import java.util.WeakHashMap; import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaterniond; +import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; @@ -52,6 +60,8 @@ public class PhysicsKinematicControlSystem extends EntityTickingSystem query; private final ThreadLocal scratch = ThreadLocal.withInitial(Scratch::new); + private static final Vector3f ZERO_VELOCITY = new Vector3f(); + private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); // Anchor updates are copied commands; keep one owner command in flight and at most one latest // queued target per session anchor. @Nonnull @@ -90,24 +100,12 @@ public void tick(float dt, return; } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); RigidBodyKey bodyKey = session.getBodyKey(); RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); Ref targetRef = session.getTargetRef(); - if (bodyKey == null - || anchorBodyKey == null - || !resource.hasPublishedOrPendingBodyRegistration(bodyKey) - || !resource.hasPublishedOrPendingBodyRegistration(anchorBodyKey) - || (targetRef != null && !targetRef.isValid())) { - stateFor(store).clear(anchorBodyKey); - PhysicsControlSessionCleanup.cleanup(resource, session); - commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); - return; - } - - if (session.getSpaceId() != null && !resource.hasSpace(session.getSpaceId())) { + if (bodyKey == null || anchorBodyKey == null || (targetRef != null && !targetRef.isValid())) { stateFor(store).clear(anchorBodyKey); - PhysicsControlSessionCleanup.cleanup(resource, session); + PhysicsControlSessionCleanup.cleanup(PhysicsWorldRuntimeResource.require(store), session); commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); return; } @@ -159,6 +157,30 @@ public void tick(float dt, return; } + PhysicsStoreControlTargets physicsStoreTargets = + resolvePhysicsStoreTargets(store, bodyKey, anchorBodyKey); + if (physicsStoreTargets != null) { + physicsStoreTargets.enqueue(readyUpdate); + state.trackSubmittedRequest(anchorBodyKey, readyUpdate); + return; + } + + PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); + if (!resource.hasPublishedOrPendingBodyRegistration(bodyKey) + || !resource.hasPublishedOrPendingBodyRegistration(anchorBodyKey)) { + stateFor(store).clear(anchorBodyKey); + PhysicsControlSessionCleanup.cleanup(resource, session); + commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); + return; + } + + if (session.getSpaceId() != null && !resource.hasSpace(session.getSpaceId())) { + stateFor(store).clear(anchorBodyKey); + PhysicsControlSessionCleanup.cleanup(resource, session); + commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); + return; + } + PhysicsMutationHandle handle = PhysicsMutationHandle.fromCompletion( "update kinematic control anchor", null, @@ -182,6 +204,36 @@ public void tick(float dt, state.trackPendingMutation(anchorBodyKey, handle, readyUpdate); } + @Nullable + private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( + @Nonnull Store store, + @Nonnull RigidBodyKey bodyKey, + @Nonnull RigidBodyKey anchorBodyKey) { + PhysicsStore physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()); + Store physics = physicsStore.getStore(); + PhysicsIdentityIndexResource identity = physics.getResource( + PhysicsIdentityIndexResource.getResourceType()); + UUID bodyUuid = bodyKey.value(); + UUID anchorBodyUuid = anchorBodyKey.value(); + if (!hasPhysicsStoreBody(physics, identity, bodyUuid) + || !hasPhysicsStoreBody(physics, identity, anchorBodyUuid)) { + return null; + } + return new PhysicsStoreControlTargets( + physics.getResource(PhysicsRequestQueueResource.getResourceType()), + bodyUuid, + anchorBodyUuid); + } + + private static boolean hasPhysicsStoreBody(@Nonnull Store physics, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid) { + Ref ref = identity.getByUuid(bodyUuid); + return ref != null + && ref.isValid() + && physics.getComponent(ref, BodyComponent.getComponentType()) != null; + } + private float eyeHeight(@Nonnull ArchetypeChunk chunk, int index, @Nonnull Ref ref, @@ -249,6 +301,30 @@ record ControlAnchorUpdate(@Nonnull RigidBodyKey bodyKey, } } + private record PhysicsStoreControlTargets(@Nonnull PhysicsRequestQueueResource queue, + @Nonnull UUID bodyUuid, + @Nonnull UUID anchorBodyUuid) { + + private void enqueue(@Nonnull ControlAnchorUpdate update) { + queue.enqueue(BodyTargetRequest.of(anchorBodyUuid, + update.target(), + IDENTITY_ROTATION, + update.releaseVelocity(), + ZERO_VELOCITY, + true, + true, + true)); + queue.enqueue(BodyTargetRequest.of(bodyUuid, + update.target(), + IDENTITY_ROTATION, + ZERO_VELOCITY, + ZERO_VELOCITY, + false, + false, + true)); + } + } + static final class ControlMutationState { /* @@ -317,6 +393,13 @@ synchronized void trackPendingMutation(@Nonnull RigidBodyKey bodyKey, handle.completion().whenComplete((ignored, _) -> clear(bodyKey, handle)); } + synchronized void trackSubmittedRequest(@Nonnull RigidBodyKey bodyKey, + @Nonnull ControlAnchorUpdate submittedUpdate) { + pendingMutations.remove(bodyKey); + queuedUpdates.remove(bodyKey); + submittedUpdates.put(bodyKey, submittedUpdate); + } + synchronized void clear(@Nullable RigidBodyKey bodyKey) { if (bodyKey != null) { pendingMutations.remove(bodyKey); From 65181123164cfd67d7e08840f72163e4fea588d2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 16:05:17 +0200 Subject: [PATCH 016/534] feat(core): add physics store structural requests Signed-off-by: Blovien --- .../PhysicsIdentityIndexResource.java | 12 + .../resources/PhysicsRuntimeResource.java | 15 +- .../systems/JointBindingSystem.java | 42 +- .../systems/RequestDrainSystem.java | 561 +++++++++++++++++- .../requests/BodyRemoveRequest.java | 31 + .../requests/BodyUpsertRequest.java | 76 +++ .../requests/JointRemoveRequest.java | 22 + .../requests/JointUpsertRequest.java | 26 + 8 files changed, 765 insertions(+), 20 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyUpsertRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java index e4ad47d0..0dba8b1e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java @@ -62,6 +62,10 @@ public Ref getBySpaceHandle(@Nonnull BackendSpaceHandle handle) { return spaceRefsByHandle.get(handle.value()); } + public void removeSpaceHandle(@Nonnull BackendSpaceHandle handle) { + spaceRefsByHandle.remove(handle.value()); + } + public void putBodyHandle(@Nonnull BackendBodyHandle handle, @Nonnull Ref ref) { bodyRefsByHandle.put(handle.value(), ref); } @@ -71,6 +75,10 @@ public Ref getByBodyHandle(@Nonnull BackendBodyHandle handle) { return bodyRefsByHandle.get(handle.value()); } + public void removeBodyHandle(@Nonnull BackendBodyHandle handle) { + bodyRefsByHandle.remove(handle.value()); + } + public void putJointHandle(@Nonnull BackendJointHandle handle, @Nonnull Ref ref) { jointRefsByHandle.put(handle.value(), ref); } @@ -80,6 +88,10 @@ public Ref getByJointHandle(@Nonnull BackendJointHandle handle) { return jointRefsByHandle.get(handle.value()); } + public void removeJointHandle(@Nonnull BackendJointHandle handle) { + jointRefsByHandle.remove(handle.value()); + } + public void clear() { refsByUuid.clear(); spaceRefsByHandle.clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 7e953d1d..866103f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -43,6 +43,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Map jointHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map jointSpaceHandlesByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Map terrainBodyHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull @@ -138,8 +141,11 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid) { } } - public void putJointHandle(@Nonnull UUID jointUuid, @Nonnull BackendJointHandle handle) { + public void putJointHandle(@Nonnull UUID jointUuid, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendJointHandle handle) { jointHandlesByUuid.put(jointUuid, handle); + jointSpaceHandlesByUuid.put(jointUuid, spaceHandle); } @Nullable @@ -147,8 +153,14 @@ public BackendJointHandle getJointHandle(@Nonnull UUID jointUuid) { return jointHandlesByUuid.get(jointUuid); } + @Nullable + public BackendSpaceHandle getJointSpaceHandle(@Nonnull UUID jointUuid) { + return jointSpaceHandlesByUuid.get(jointUuid); + } + public void removeJointHandle(@Nonnull UUID jointUuid) { jointHandlesByUuid.remove(jointUuid); + jointSpaceHandlesByUuid.remove(jointUuid); } public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, @@ -228,6 +240,7 @@ public void clear() { bodyHandlesByUuid.clear(); bodySpaceHandlesByUuid.clear(); jointHandlesByUuid.clear(); + jointSpaceHandlesByUuid.clear(); terrainBodyHandlesByUuid.clear(); terrainVoxelBodyHandlesByUuid.clear(); terrainSpaceHandlesByUuid.clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index 0d97be29..c5696224 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -63,8 +63,14 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, continue; } UUID jointUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (PhysicsStoreSystemSupport.isNil(jointUuid) - || runtime.getJointHandle(jointUuid) != null) { + if (PhysicsStoreSystemSupport.isNil(jointUuid)) { + continue; + } + if (!joint.isEnabled()) { + removeJoint(runtime, identity, jointUuid, joint); + continue; + } + if (runtime.getJointHandle(jointUuid) != null) { continue; } bindJoint(runtime, identity, restore, chunk.getReferenceTo(index), jointUuid, joint); @@ -80,12 +86,21 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); BackendBodyHandle bodyA = runtime.getBodyHandle(joint.getBodyAUuid()); BackendBodyHandle bodyB = runtime.getBodyHandle(joint.getBodyBUuid()); + BackendSpaceHandle bodyASpace = runtime.getBodySpaceHandle(joint.getBodyAUuid()); + BackendSpaceHandle bodyBSpace = runtime.getBodySpaceHandle(joint.getBodyBUuid()); var backendId = runtime.getSpaceBackendId(joint.getSpaceUuid()); PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; if (spaceHandle == null || bodyA == null || bodyB == null || backendRuntime == null) { restore.recordSoftSkip("Joint references unbound endpoint: " + jointUuid); return; } + if (bodyASpace == null + || bodyBSpace == null + || bodyASpace.value() != spaceHandle.value() + || bodyBSpace.value() != spaceHandle.value()) { + restore.recordSoftSkip("Joint endpoints are not in the joint space: " + jointUuid); + return; + } Vector3f anchorA = joint.getAnchorA(); Vector3f anchorB = joint.getAnchorB(); Vector3f axis = joint.getAxis(); @@ -111,10 +126,31 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, joint.getMotorTargetVelocity(), joint.getMotorMaxForce()); BackendJointHandle handle = new BackendJointHandle(jointId); - runtime.putJointHandle(jointUuid, handle); + runtime.putJointHandle(jointUuid, spaceHandle, handle); identity.putJointHandle(handle, jointRef); } + private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID jointUuid, + @Nonnull JointComponent joint) { + BackendJointHandle handle = runtime.getJointHandle(jointUuid); + if (handle == null) { + return; + } + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); + if (spaceHandle == null) { + spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); + } + var backendId = runtime.getSpaceBackendId(joint.getSpaceUuid()); + PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeJoint(spaceHandle.value(), handle.value()); + } + identity.removeJointHandle(handle); + runtime.removeJointHandle(jointUuid); + } + @Nonnull @Override public Query getQuery() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index ae628395..c54a3334 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -1,30 +1,54 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.BiConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -50,34 +74,274 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsIdentityIndexResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsTerrainPayloadResource terrainPayloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); - Map> terrainRefsThisDrain = new Object2ObjectOpenHashMap<>(); + Set structuralConflicts = structuralConflicts(requests, restore); + Map> refsThisDrain = new Object2ObjectOpenHashMap<>(); + + applyRemovals(store, + systemIndex, + identity, + runtime, + terrainPayloads, + refsThisDrain, + restore, + structuralConflicts, + requests); + applyUpserts(store, + systemIndex, + identity, + runtime, + terrainPayloads, + refsThisDrain, + restore, + structuralConflicts, + requests); + applyTargetRequests(store, identity, refsThisDrain, restore, requests); + recordUnsupported(restore, requests); + } + + private static void applyRemovals(@Nonnull Store store, + int systemIndex, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set structuralConflicts, + @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { - if (request instanceof BodyTargetRequest targetRequest) { - applyTargetRequest(store, identity, restore, targetRequest); + if (request instanceof BodyRemoveRequest bodyRequest) { + if (!structuralConflicts.contains(bodyRequest.bodyUuid())) { + applyBodyRemove(store, + systemIndex, + identity, + runtime, + refsThisDrain, + bodyRequest); + } continue; } - if (request instanceof TerrainColliderRequest terrainRequest) { + if (request instanceof JointRemoveRequest jointRequest) { + if (!structuralConflicts.contains(jointRequest.jointUuid())) { + applyJointRemove(store, identity, runtime, refsThisDrain, jointRequest); + } + continue; + } + if (request instanceof TerrainColliderRequest terrainRequest + && terrainRequest.remove() + && !structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { applyTerrainRequest(store, identity, terrainPayloads, - terrainRefsThisDrain, + refsThisDrain, restore, terrainRequest); + } + } + } + + private static void applyUpserts(@Nonnull Store store, + int systemIndex, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set structuralConflicts, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (request instanceof BodyUpsertRequest bodyRequest) { + if (!structuralConflicts.contains(bodyRequest.bodyUuid())) { + applyBodyUpsert(store, + systemIndex, + identity, + runtime, + refsThisDrain, + restore, + bodyRequest); + } continue; } - restore.recordSoftSkip("Unsupported PhysicsStore request " - + request.getClass().getName()); + if (request instanceof JointUpsertRequest jointRequest) { + if (!structuralConflicts.contains(jointRequest.jointUuid())) { + applyJointUpsert(store, + identity, + runtime, + refsThisDrain, + restore, + jointRequest); + } + continue; + } + if (request instanceof TerrainColliderRequest terrainRequest + && !terrainRequest.remove() + && !structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { + applyTerrainRequest(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + terrainRequest); + } } } + private static void applyTargetRequests(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (request instanceof BodyTargetRequest targetRequest) { + applyTargetRequest(store, identity, refsThisDrain, restore, targetRequest); + } + } + } + + private static void recordUnsupported(@Nonnull PhysicsRestoreStatusResource restore, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (!isSupportedRequest(request)) { + restore.recordSoftSkip("Unsupported PhysicsStore request " + + request.getClass().getName()); + } + } + } + + private static void applyBodyRemove(@Nonnull Store store, + int systemIndex, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull BodyRemoveRequest request) { + UUID bodyUuid = request.bodyUuid(); + List attachedJoints = collectAttachedJoints(store, systemIndex, bodyUuid); + List colliders = collectColliders(store, systemIndex, bodyUuid); + Set removedRows = new ObjectOpenHashSet<>(); + + for (JointRow joint : attachedJoints) { + removeJointBackend(runtime, identity, joint.uuid(), joint.joint()); + removeRow(store, identity, refsThisDrain, removedRows, joint.uuid(), joint.ref()); + } + removeBodyBackend(runtime, identity, bodyUuid); + removeRow(store, + identity, + refsThisDrain, + removedRows, + bodyUuid, + refForUuid(identity, refsThisDrain, bodyUuid)); + for (ColliderRow collider : colliders) { + removeRow(store, identity, refsThisDrain, removedRows, collider.uuid(), collider.ref()); + } + for (UUID ownedRowUuid : request.ownedRowUuids()) { + removeRow(store, + identity, + refsThisDrain, + removedRows, + ownedRowUuid, + refForUuid(identity, refsThisDrain, ownedRowUuid)); + } + } + + private static void applyBodyUpsert(@Nonnull Store store, + int systemIndex, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull BodyUpsertRequest request) { + if (!isValidBodyUpsert(request, restore)) { + return; + } + if (runtime.getBodyHandle(request.bodyUuid()) != null) { + for (JointRow joint : collectAttachedJoints(store, systemIndex, request.bodyUuid())) { + removeJointBackend(runtime, identity, joint.uuid(), joint.joint()); + } + removeBodyBackend(runtime, identity, request.bodyUuid()); + } + upsertComponentRow(store, + identity, + refsThisDrain, + request.shapeUuid(), + ShapeComponent.getComponentType(), + request.shape().clone()); + upsertComponentRow(store, + identity, + refsThisDrain, + request.materialUuid(), + MaterialComponent.getComponentType(), + request.material().clone()); + upsertComponentRow(store, + identity, + refsThisDrain, + request.filterUuid(), + CollisionFilterComponent.getComponentType(), + request.filter().clone()); + upsertComponentRow(store, + identity, + refsThisDrain, + request.colliderUuid(), + ColliderComponent.getComponentType(), + request.collider().clone()); + + Ref bodyRef = ensureRow(store, identity, refsThisDrain, request.bodyUuid()); + store.putComponent(bodyRef, BodyComponent.getComponentType(), request.body().clone()); + store.putComponent(bodyRef, DynamicsComponent.getComponentType(), request.dynamics().clone()); + if (request.target() != null) { + store.putComponent(bodyRef, TargetComponent.getComponentType(), request.target().clone()); + } else { + store.removeComponent(bodyRef, TargetComponent.getComponentType()); + } + } + + private static void applyJointRemove(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull JointRemoveRequest request) { + Ref ref = refForUuid(identity, refsThisDrain, request.jointUuid()); + JointComponent joint = PhysicsStoreSystemSupport.component(store, + ref, + JointComponent.getComponentType()); + removeJointBackend(runtime, identity, request.jointUuid(), joint); + removeRow(store, + identity, + refsThisDrain, + new ObjectOpenHashSet<>(), + request.jointUuid(), + ref); + } + + private static void applyJointUpsert(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull JointUpsertRequest request) { + if (!isValidJointUpsert(request, restore)) { + return; + } + Ref ref = refForUuid(identity, refsThisDrain, request.jointUuid()); + JointComponent existing = PhysicsStoreSystemSupport.component(store, + ref, + JointComponent.getComponentType()); + removeJointBackend(runtime, identity, request.jointUuid(), existing); + upsertComponentRow(store, + identity, + refsThisDrain, + request.jointUuid(), + JointComponent.getComponentType(), + request.joint().clone()); + } + private static void applyTargetRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull BodyTargetRequest request) { - Ref bodyRef = PhysicsStoreSystemSupport.refForUuid(identity, - request.bodyUuid()); + Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); if (bodyRef == null) { restore.recordSoftSkip("Target request body is missing: " + request.bodyUuid()); return; @@ -97,14 +361,11 @@ private static void applyTargetRequest(@Nonnull Store store, private static void applyTerrainRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull Map> terrainRefsThisDrain, + @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull TerrainColliderRequest request) { UUID terrainUuid = request.terrainColliderUuid(); - Ref ref = terrainRefsThisDrain.get(terrainUuid); - if (ref == null) { - ref = PhysicsStoreSystemSupport.refForUuid(identity, terrainUuid); - } + Ref ref = refForUuid(identity, refsThisDrain, terrainUuid); if (request.remove()) { if (ref != null) { TerrainColliderComponent existing = store.getComponent(ref, @@ -134,13 +395,271 @@ private static void applyTerrainRequest(@Nonnull Store store, removePayload(terrainPayloads, existing.getPayloadResourceKey()); } store.putComponent(ref, TerrainColliderComponent.getComponentType(), component); - terrainRefsThisDrain.put(terrainUuid, ref); + refsThisDrain.put(terrainUuid, ref); return; } Holder holder = store.getRegistry().newHolder(); holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(terrainUuid)); holder.addComponent(TerrainColliderComponent.getComponentType(), component); - terrainRefsThisDrain.put(terrainUuid, store.addEntity(holder, AddReason.SPAWN)); + refsThisDrain.put(terrainUuid, store.addEntity(holder, AddReason.SPAWN)); + } + + @Nonnull + private static > Ref upsertComponentRow( + @Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull UUID uuid, + @Nonnull ComponentType type, + @Nonnull C component) { + Ref ref = ensureRow(store, identity, refsThisDrain, uuid); + store.putComponent(ref, type, component); + return ref; + } + + @Nonnull + private static Ref ensureRow(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull UUID uuid) { + Ref ref = refForUuid(identity, refsThisDrain, uuid); + if (ref != null) { + return ref; + } + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(uuid)); + ref = store.addEntity(holder, AddReason.SPAWN); + refsThisDrain.put(uuid, ref); + return ref; + } + + private static void removeRow(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull Set removedRows, + @Nonnull UUID uuid, + @Nullable Ref ref) { + if (!removedRows.add(uuid)) { + return; + } + refsThisDrain.remove(uuid); + if (ref == null || !ref.isValid()) { + return; + } + identity.removeUuid(uuid, ref); + store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); + } + + private static void removeBodyBackend(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); + if (bodyHandle == null) { + return; + } + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); + } + identity.removeBodyHandle(bodyHandle); + runtime.removeBodyHandle(bodyUuid); + } + + private static void removeJointBackend(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID jointUuid, + @Nullable JointComponent joint) { + BackendJointHandle jointHandle = runtime.getJointHandle(jointUuid); + if (jointHandle == null) { + return; + } + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); + if (spaceHandle == null && joint != null) { + spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); + } + identity.removeJointHandle(jointHandle); + runtime.removeJointHandle(jointUuid); + } + + @Nullable + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nullable BackendSpaceHandle spaceHandle) { + if (spaceHandle == null) { + return null; + } + final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; + runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { + if (handle.value() == spaceHandle.value()) { + resolved[0] = backendRuntime; + } + }); + return resolved[0]; + } + + @Nonnull + private static List collectAttachedJoints(@Nonnull Store store, + int systemIndex, + @Nonnull UUID bodyUuid) { + List rows = new ArrayList<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> { + for (int index = 0; index < chunk.size(); index++) { + JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); + if (joint == null + || (!bodyUuid.equals(joint.getBodyAUuid()) + && !bodyUuid.equals(joint.getBodyBUuid()))) { + continue; + } + UUID jointUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (!PhysicsStoreSystemSupport.isNil(jointUuid)) { + rows.add(new JointRow(jointUuid, + chunk.getReferenceTo(index), + joint.clone())); + } + } + }; + store.forEachChunk(systemIndex, collector); + return rows; + } + + @Nonnull + private static List collectColliders(@Nonnull Store store, + int systemIndex, + @Nonnull UUID bodyUuid) { + List rows = new ArrayList<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> { + for (int index = 0; index < chunk.size(); index++) { + ColliderComponent collider = chunk.getComponent(index, + ColliderComponent.getComponentType()); + if (collider == null || !bodyUuid.equals(collider.getBodyUuid())) { + continue; + } + UUID colliderUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (!PhysicsStoreSystemSupport.isNil(colliderUuid)) { + rows.add(new ColliderRow(colliderUuid, + chunk.getReferenceTo(index), + collider.clone())); + } + } + }; + store.forEachChunk(systemIndex, collector); + return rows; + } + + @Nonnull + private static Set structuralConflicts( + @Nonnull List requests, + @Nonnull PhysicsRestoreStatusResource restore) { + Map operationsByUuid = new Object2ObjectOpenHashMap<>(); + Set conflicts = new ObjectOpenHashSet<>(); + for (PhysicsStoreRequest request : requests) { + UUID uuid = structuralUuid(request); + if (uuid == null) { + continue; + } + String operation = structuralOperation(request); + String previous = operationsByUuid.putIfAbsent(uuid, operation); + if (previous != null) { + conflicts.add(uuid); + } + } + for (UUID conflict : conflicts) { + restore.recordSoftSkip("Conflicting structural PhysicsStore requests for uuid: " + + conflict); + } + return conflicts; + } + + @Nullable + private static UUID structuralUuid(@Nonnull PhysicsStoreRequest request) { + if (request instanceof BodyUpsertRequest bodyRequest) { + return bodyRequest.bodyUuid(); + } + if (request instanceof BodyRemoveRequest bodyRequest) { + return bodyRequest.bodyUuid(); + } + if (request instanceof JointUpsertRequest jointRequest) { + return jointRequest.jointUuid(); + } + if (request instanceof JointRemoveRequest jointRequest) { + return jointRequest.jointUuid(); + } + if (request instanceof TerrainColliderRequest terrainRequest) { + return terrainRequest.terrainColliderUuid(); + } + return null; + } + + @Nonnull + private static String structuralOperation(@Nonnull PhysicsStoreRequest request) { + if (request instanceof TerrainColliderRequest terrainRequest) { + return terrainRequest.remove() ? "terrain-remove" : "terrain-upsert"; + } + return request.getClass().getName(); + } + + private static boolean isValidBodyUpsert(@Nonnull BodyUpsertRequest request, + @Nonnull PhysicsRestoreStatusResource restore) { + if (isNil(request.bodyUuid()) + || isNil(request.body().getSpaceUuid()) + || isNil(request.colliderUuid()) + || isNil(request.shapeUuid()) + || isNil(request.materialUuid()) + || isNil(request.filterUuid())) { + restore.recordSoftSkip("Body upsert contains nil UUIDs: " + request.bodyUuid()); + return false; + } + if (!request.bodyUuid().equals(request.collider().getBodyUuid()) + || !request.shapeUuid().equals(request.collider().getShapeUuid()) + || !request.materialUuid().equals(request.collider().getMaterialUuid()) + || !request.filterUuid().equals(request.collider().getFilterUuid())) { + restore.recordSoftSkip("Body upsert collider refs do not match request UUIDs: " + + request.bodyUuid()); + return false; + } + return true; + } + + private static boolean isValidJointUpsert(@Nonnull JointUpsertRequest request, + @Nonnull PhysicsRestoreStatusResource restore) { + if (isNil(request.jointUuid()) + || isNil(request.joint().getSpaceUuid()) + || isNil(request.joint().getBodyAUuid()) + || isNil(request.joint().getBodyBUuid())) { + restore.recordSoftSkip("Joint upsert contains nil UUIDs: " + request.jointUuid()); + return false; + } + return true; + } + + private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) { + return request instanceof BodyTargetRequest + || request instanceof TerrainColliderRequest + || request instanceof BodyUpsertRequest + || request instanceof BodyRemoveRequest + || request instanceof JointUpsertRequest + || request instanceof JointRemoveRequest; + } + + @Nullable + private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull UUID uuid) { + Ref ref = refsThisDrain.get(uuid); + if (ref != null && ref.isValid()) { + return ref; + } + return PhysicsStoreSystemSupport.refForUuid(identity, uuid); + } + + private static boolean isNil(@Nonnull UUID uuid) { + return PhysicsStoreSystemSupport.isNil(uuid); } @Nonnull @@ -180,4 +699,14 @@ private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrain public Set> getDependencies() { return DEPENDENCIES; } + + private record JointRow(@Nonnull UUID uuid, + @Nonnull Ref ref, + @Nonnull JointComponent joint) { + } + + private record ColliderRow(@Nonnull UUID uuid, + @Nonnull Ref ref, + @Nonnull ColliderComponent collider) { + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java new file mode 100644 index 00000000..e8ff81c0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java @@ -0,0 +1,31 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request that removes one body graph from PhysicsStore. + */ +public record BodyRemoveRequest(@Nonnull UUID requestUuid, + @Nonnull UUID bodyUuid, + @Nonnull List ownedRowUuids) implements PhysicsStoreRequest { + + public BodyRemoveRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + ownedRowUuids = List.copyOf(Objects.requireNonNull(ownedRowUuids, "ownedRowUuids")); + } + + @Nonnull + public static BodyRemoveRequest of(@Nonnull UUID bodyUuid) { + return owned(bodyUuid, List.of()); + } + + @Nonnull + public static BodyRemoveRequest owned(@Nonnull UUID bodyUuid, + @Nonnull List ownedRowUuids) { + return new BodyRemoveRequest(UUID.randomUUID(), bodyUuid, ownedRowUuids); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyUpsertRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyUpsertRequest.java new file mode 100644 index 00000000..114859cd --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyUpsertRequest.java @@ -0,0 +1,76 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Copied request that authors a single-body graph in PhysicsStore. + */ +public record BodyUpsertRequest(@Nonnull UUID requestUuid, + @Nonnull UUID bodyUuid, + @Nonnull BodyComponent body, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target, + @Nonnull UUID colliderUuid, + @Nonnull ColliderComponent collider, + @Nonnull UUID shapeUuid, + @Nonnull ShapeComponent shape, + @Nonnull UUID materialUuid, + @Nonnull MaterialComponent material, + @Nonnull UUID filterUuid, + @Nonnull CollisionFilterComponent filter) + implements PhysicsStoreRequest { + + public BodyUpsertRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + body = Objects.requireNonNull(body, "body").clone(); + dynamics = Objects.requireNonNull(dynamics, "dynamics").clone(); + target = target != null ? target.clone() : null; + Objects.requireNonNull(colliderUuid, "colliderUuid"); + collider = Objects.requireNonNull(collider, "collider").clone(); + Objects.requireNonNull(shapeUuid, "shapeUuid"); + shape = Objects.requireNonNull(shape, "shape").clone(); + Objects.requireNonNull(materialUuid, "materialUuid"); + material = Objects.requireNonNull(material, "material").clone(); + Objects.requireNonNull(filterUuid, "filterUuid"); + filter = Objects.requireNonNull(filter, "filter").clone(); + } + + @Nonnull + public static BodyUpsertRequest of(@Nonnull UUID bodyUuid, + @Nonnull BodyComponent body, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target, + @Nonnull UUID colliderUuid, + @Nonnull ColliderComponent collider, + @Nonnull UUID shapeUuid, + @Nonnull ShapeComponent shape, + @Nonnull UUID materialUuid, + @Nonnull MaterialComponent material, + @Nonnull UUID filterUuid, + @Nonnull CollisionFilterComponent filter) { + return new BodyUpsertRequest(UUID.randomUUID(), + bodyUuid, + body, + dynamics, + target, + colliderUuid, + collider, + shapeUuid, + shape, + materialUuid, + material, + filterUuid, + filter); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java new file mode 100644 index 00000000..e097a1ff --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java @@ -0,0 +1,22 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request that removes one joint row from PhysicsStore. + */ +public record JointRemoveRequest(@Nonnull UUID requestUuid, + @Nonnull UUID jointUuid) implements PhysicsStoreRequest { + + public JointRemoveRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(jointUuid, "jointUuid"); + } + + @Nonnull + public static JointRemoveRequest of(@Nonnull UUID jointUuid) { + return new JointRemoveRequest(UUID.randomUUID(), jointUuid); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java new file mode 100644 index 00000000..0081cf00 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java @@ -0,0 +1,26 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request that authors one joint row in PhysicsStore. + */ +public record JointUpsertRequest(@Nonnull UUID requestUuid, + @Nonnull UUID jointUuid, + @Nonnull JointComponent joint) implements PhysicsStoreRequest { + + public JointUpsertRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(jointUuid, "jointUuid"); + joint = Objects.requireNonNull(joint, "joint").clone(); + } + + @Nonnull + public static JointUpsertRequest of(@Nonnull UUID jointUuid, + @Nonnull JointComponent joint) { + return new JointUpsertRequest(UUID.randomUUID(), jointUuid, joint); + } +} From c582e95351ee5bff9c078a086308f33dd68559ba Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 16:23:19 +0200 Subject: [PATCH 017/534] feat(core): add physics store query indexes Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 4 + .../resources/PhysicsRuntimeResource.java | 52 +++++++++++- .../resources/PhysicsSnapshotResource.java | 19 +++++ ...hysicsSpaceCompatibilityIndexResource.java | 81 +++++++++++++++++++ .../systems/BodyBindingSystem.java | 5 ++ .../systems/SpaceBindingSystem.java | 19 ++++- .../systems/TerrainColliderBindingSystem.java | 9 ++- .../physicsstore/PhysicsStoreTypes.java | 15 ++++ 8 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 9c9ee16b..a5805bde 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -10,6 +10,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; @@ -99,6 +100,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setRuntimeResourceType(registry.registerResource( PhysicsRuntimeResource.class, PhysicsRuntimeResource::new)); + PhysicsStoreTypes.setSpaceCompatibilityIndexResourceType(registry.registerResource( + PhysicsSpaceCompatibilityIndexResource.class, + PhysicsSpaceCompatibilityIndexResource::new)); PhysicsStoreTypes.setRequestQueueResourceType(registry.registerResource( PhysicsRequestQueueResource.class, PhysicsRequestQueueResource::new)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 866103f7..e6ead575 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -4,16 +4,21 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.function.LongConsumer; import javax.annotation.Nonnull; @@ -60,6 +65,9 @@ public final class PhysicsRuntimeResource implements Resource { @Nonnull private final Int2ObjectOpenHashMap bodyHandlesBySpaceHandle = new Int2ObjectOpenHashMap<>(); + @Nonnull + private final Long2ObjectOpenHashMap bodyHitMetadataByHandle = + new Long2ObjectOpenHashMap<>(); private boolean started; public PhysicsRuntimeResource() { @@ -103,7 +111,10 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { BackendSpaceHandle removed = spaceHandlesByUuid.remove(spaceUuid); backendIdsBySpaceUuid.remove(spaceUuid); if (removed != null) { - bodyHandlesBySpaceHandle.remove(removed.value()); + LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); + if (bodyHandles != null) { + bodyHandles.forEach((long bodyHandle) -> bodyHitMetadataByHandle.remove(bodyHandle)); + } removeTerrainHandlesForSpace(removed); } } @@ -138,9 +149,27 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid) { bodyHandlesBySpaceHandle.remove(spaceHandle.value()); } } + bodyHitMetadataByHandle.remove(removed.value()); } } + public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, + @Nullable RigidBodyKey bodyKey, + @Nonnull PhysicsBodyType bodyType, + @Nonnull ShapeType shapeType) { + bodyHitMetadataByHandle.put(handle.value(), + new BodyHitMetadata(bodyKey, bodyType, shapeType)); + } + + @Nullable + public BodyHitMetadata getBodyHitMetadata(@Nonnull BackendBodyHandle handle) { + return bodyHitMetadataByHandle.get(handle.value()); + } + + public void removeBodyHitMetadata(@Nonnull BackendBodyHandle handle) { + bodyHitMetadataByHandle.remove(handle.value()); + } + public void putJointHandle(@Nonnull UUID jointUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendJointHandle handle) { @@ -208,6 +237,10 @@ public void forEachTerrainBodyHandle(@Nonnull UUID terrainUuid, } public void removeTerrainHandles(@Nonnull UUID terrainUuid) { + LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); + if (bodyHandles != null) { + bodyHandles.forEach((long bodyHandle) -> bodyHitMetadataByHandle.remove(bodyHandle)); + } terrainBodyHandlesByUuid.remove(terrainUuid); terrainVoxelBodyHandlesByUuid.remove(terrainUuid); terrainSpaceHandlesByUuid.remove(terrainUuid); @@ -246,6 +279,7 @@ public void clear() { terrainSpaceHandlesByUuid.clear(); terrainPayloadKeysByUuid.clear(); bodyHandlesBySpaceHandle.clear(); + bodyHitMetadataByHandle.clear(); started = false; } @@ -259,6 +293,7 @@ public PhysicsRuntimeResource clone() { copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); copy.jointHandlesByUuid.putAll(jointHandlesByUuid); + copy.jointSpaceHandlesByUuid.putAll(jointSpaceHandlesByUuid); terrainBodyHandlesByUuid.forEach((terrainUuid, bodyHandles) -> copy.terrainBodyHandlesByUuid.put(terrainUuid, new LongArrayList(bodyHandles))); copy.terrainVoxelBodyHandlesByUuid.putAll(terrainVoxelBodyHandlesByUuid); @@ -266,6 +301,7 @@ public PhysicsRuntimeResource clone() { copy.terrainPayloadKeysByUuid.putAll(terrainPayloadKeysByUuid); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); + copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); copy.started = started; return copy; } @@ -284,12 +320,26 @@ void accept(@Nonnull UUID spaceUuid, @Nonnull PhysicsBackendRuntime runtime); } + public record BodyHitMetadata(@Nullable RigidBodyKey bodyKey, + @Nonnull PhysicsBodyType bodyType, + @Nonnull ShapeType shapeType) { + + public BodyHitMetadata { + Objects.requireNonNull(bodyType, "bodyType"); + Objects.requireNonNull(shapeType, "shapeType"); + } + } + private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandle) { terrainSpaceHandlesByUuid.entrySet().removeIf(entry -> { if (entry.getValue().value() != spaceHandle.value()) { return false; } UUID terrainUuid = entry.getKey(); + LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); + if (bodyHandles != null) { + bodyHandles.forEach((long bodyHandle) -> bodyHitMetadataByHandle.remove(bodyHandle)); + } terrainBodyHandlesByUuid.remove(terrainUuid); terrainVoxelBodyHandlesByUuid.remove(terrainUuid); terrainPayloadKeysByUuid.remove(terrainUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index 45f6a8d8..36eb22db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -4,8 +4,13 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Latest copied PhysicsStore snapshot frame for projection, debug, and queries. @@ -14,6 +19,9 @@ public final class PhysicsSnapshotResource implements Resource { @Nonnull private PhysicsStoreSnapshotFrame latestFrame = PhysicsStoreSnapshotFrame.EMPTY; + @Nonnull + private final Map bodiesByUuid = + new Object2ObjectOpenHashMap<>(); public PhysicsSnapshotResource() { } @@ -23,12 +31,22 @@ public PhysicsStoreSnapshotFrame getLatestFrame() { return latestFrame; } + @Nullable + public PhysicsStoreBodySnapshot getBody(@Nonnull UUID bodyUuid) { + return bodiesByUuid.get(bodyUuid); + } + public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { latestFrame = frame; + bodiesByUuid.clear(); + for (PhysicsStoreBodySnapshot body : frame.bodies()) { + bodiesByUuid.put(body.bodyUuid(), body); + } } public void clear() { latestFrame = PhysicsStoreSnapshotFrame.EMPTY; + bodiesByUuid.clear(); } @Nonnull @@ -36,6 +54,7 @@ public void clear() { public PhysicsSnapshotResource clone() { PhysicsSnapshotResource copy = new PhysicsSnapshotResource(); copy.latestFrame = latestFrame; + copy.bodiesByUuid.putAll(bodiesByUuid); return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java new file mode 100644 index 00000000..a8074fff --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java @@ -0,0 +1,81 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime-only compatibility map for legacy SpaceId query and command boundaries. + */ +public final class PhysicsSpaceCompatibilityIndexResource implements Resource { + + @Nonnull + private final Int2ObjectOpenHashMap spaceUuidsByCompatId = + new Int2ObjectOpenHashMap<>(); + @Nonnull + private final Object2IntOpenHashMap compatIdsBySpaceUuid = + new Object2IntOpenHashMap<>(); + + public PhysicsSpaceCompatibilityIndexResource() { + compatIdsBySpaceUuid.defaultReturnValue(Integer.MIN_VALUE); + } + + public void putSpace(@Nonnull SpaceId spaceId, @Nonnull UUID spaceUuid) { + int compatibilityId = spaceId.value(); + UUID previousUuid = spaceUuidsByCompatId.put(compatibilityId, + Objects.requireNonNull(spaceUuid, "spaceUuid")); + if (previousUuid != null && !previousUuid.equals(spaceUuid)) { + compatIdsBySpaceUuid.removeInt(previousUuid); + } + int previousId = compatIdsBySpaceUuid.put(spaceUuid, compatibilityId); + if (previousId != Integer.MIN_VALUE && previousId != compatibilityId) { + spaceUuidsByCompatId.remove(previousId); + } + } + + @Nullable + public UUID getSpaceUuid(@Nonnull SpaceId spaceId) { + return spaceUuidsByCompatId.get(spaceId.value()); + } + + @Nullable + public SpaceId getSpaceId(@Nonnull UUID spaceUuid) { + int value = compatIdsBySpaceUuid.getInt(spaceUuid); + return value != Integer.MIN_VALUE ? new SpaceId(value) : null; + } + + public void removeBySpaceUuid(@Nonnull UUID spaceUuid) { + int value = compatIdsBySpaceUuid.removeInt(spaceUuid); + if (value != Integer.MIN_VALUE) { + spaceUuidsByCompatId.remove(value); + } + } + + public void clear() { + spaceUuidsByCompatId.clear(); + compatIdsBySpaceUuid.clear(); + } + + @Nonnull + @Override + public PhysicsSpaceCompatibilityIndexResource clone() { + PhysicsSpaceCompatibilityIndexResource copy = new PhysicsSpaceCompatibilityIndexResource(); + copy.spaceUuidsByCompatId.putAll(spaceUuidsByCompatId); + copy.compatIdsBySpaceUuid.putAll(compatIdsBySpaceUuid); + return copy; + } + + @Nonnull + public static ResourceType + getResourceType() { + return PhysicsStoreTypes.spaceCompatibilityIndexResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index 50ab134f..78997146 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -20,6 +20,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; @@ -198,6 +199,10 @@ private static void bindBody(@Nonnull Store store, backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); } runtime.putBodyHandle(bodyUuid, spaceHandle, bodyHandle); + runtime.putBodyHitMetadata(bodyHandle, + RigidBodyKey.of(bodyUuid), + bodyType, + shape.getShapeType()); identity.putBodyHandle(bodyHandle, bodyRef); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index d282091d..dccb0073 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import java.util.Set; @@ -45,14 +46,17 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource( PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindChunk(runtime, identity, chunk); + (chunk, _) -> bindChunk(runtime, compatibility, identity, chunk); store.forEachChunk(systemIndex, collector); } private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { @@ -65,11 +69,17 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, || runtime.getSpaceHandle(spaceUuid) != null) { continue; } - bindSpace(runtime, identity, chunk.getReferenceTo(index), spaceUuid, space); + bindSpace(runtime, + compatibility, + identity, + chunk.getReferenceTo(index), + spaceUuid, + space); } } private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref ref, @Nonnull UUID spaceUuid, @@ -83,10 +93,13 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, backendRuntime = Impulse.createRuntime(backendId); runtime.putRuntime(backendId, backendRuntime); } - BackendSpaceHandle handle = new BackendSpaceHandle(backendRuntime.createSpace(SpaceId.next())); + SpaceId compatibilitySpaceId = SpaceId.next(); + BackendSpaceHandle handle = new BackendSpaceHandle( + backendRuntime.createSpace(compatibilitySpaceId)); Vector3f gravity = space.getGravity(); backendRuntime.setGravity(handle.value(), gravity.x, gravity.y, gravity.z); runtime.putSpaceBinding(spaceUuid, backendId, handle); + compatibility.putSpace(compatibilitySpaceId, spaceUuid); identity.putSpaceHandle(handle, ref); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index b1dc7a79..35b43ec3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -12,6 +12,7 @@ import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; @@ -147,7 +148,9 @@ private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, payload.restitution(), payload.collisionGroup(), payload.collisionMask()); - runtime.putTerrainBodyHandle(terrainUuid, spaceHandle, new BackendBodyHandle(bodyId), true); + BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); + runtime.putTerrainBodyHandle(terrainUuid, spaceHandle, bodyHandle, true); + runtime.putBodyHitMetadata(bodyHandle, null, PhysicsBodyType.STATIC, ShapeType.VOXELS); } private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, @@ -183,10 +186,12 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, bodyId, payload.collisionGroup(), payload.collisionMask()); + BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); runtime.putTerrainBodyHandle(terrainUuid, spaceHandle, - new BackendBodyHandle(bodyId), + bodyHandle, false); + runtime.putBodyHitMetadata(bodyHandle, null, PhysicsBodyType.STATIC, ShapeType.BOX); } private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index 38babc73..b478824a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; @@ -62,6 +63,9 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType runtimeResourceType; @Nullable + private static ResourceType + spaceCompatibilityIndexResourceType; + @Nullable private static ResourceType requestQueueResourceType; @Nullable private static ResourceType identityIndexResourceType; @@ -148,6 +152,11 @@ public static void setRuntimeResourceType( runtimeResourceType = Objects.requireNonNull(type, "type"); } + public static void setSpaceCompatibilityIndexResourceType( + @Nonnull ResourceType type) { + spaceCompatibilityIndexResourceType = Objects.requireNonNull(type, "type"); + } + public static void setRequestQueueResourceType( @Nonnull ResourceType type) { requestQueueResourceType = Objects.requireNonNull(type, "type"); @@ -258,6 +267,12 @@ public static ResourceType runtimeResource return require(runtimeResourceType, "PhysicsRuntimeResource"); } + @Nonnull + public static ResourceType + spaceCompatibilityIndexResourceType() { + return require(spaceCompatibilityIndexResourceType, "PhysicsSpaceCompatibilityIndexResource"); + } + @Nonnull public static ResourceType requestQueueResourceType() { return require(requestQueueResourceType, "PhysicsRequestQueueResource"); From cf77cb9046297de3d246de3f6b06d7bf983e953c Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 16:29:54 +0200 Subject: [PATCH 018/534] feat(core): bridge physics store queries Signed-off-by: Blovien --- .../queries/PhysicsStoreQueryBridge.java | 342 ++++++++++++++++++ .../PhysicsWorldRuntimeResource.java | 36 +- .../owner/PhysicsOwnerLifecycleSystem.java | 7 +- 3 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java new file mode 100644 index 00000000..c348bfb2 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java @@ -0,0 +1,342 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.queries; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; +import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; +import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQueryHandle; +import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastAllQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3f; + +/** + * Internal compatibility adapter from legacy query DTOs to authoritative PhysicsStore state. + */ +public final class PhysicsStoreQueryBridge { + + private PhysicsStoreQueryBridge() { + } + + @Nonnull + public static Optional> tryQuery(@Nonnull Store store, + @Nonnull PhysicsQuery query) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(query, "query"); + Object result; + if (query instanceof RigidBodyStateQuery state) { + result = rigidBodyState(store, state); + } else if (query instanceof RaycastClosestQuery raycast) { + result = raycastClosest(store, raycast); + } else if (query instanceof RaycastClosestBatchQuery raycasts) { + result = raycastClosestBatch(store, raycasts); + } else if (query instanceof RaycastAllQuery raycast) { + result = raycastAll(store, raycast); + } else if (query instanceof SpaceBodyCountQuery count) { + result = spaceBodyCount(store, count); + } else if (query instanceof SpaceSummaryQuery summary) { + result = spaceSummary(store, summary); + } else { + return Optional.empty(); + } + if (result == null) { + return Optional.empty(); + } + @SuppressWarnings("unchecked") + R typed = (R) result; + return Optional.of(PhysicsQueryHandle.completed(query, typed)); + } + + @Nullable + private static Optional rigidBodyState(@Nonnull Store store, + @Nonnull RigidBodyStateQuery query) { + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsStoreBodySnapshot body = snapshots.getBody(query.bodyKey().value()); + if (body == null) { + return null; + } + return Optional.of(new RigidBodyStateView(query.bodyKey(), + body.bodyType(), + RigidBodyPose.of(body.position(), body.rotation()))); + } + + @Nullable + private static Optional raycastClosest(@Nonnull Store store, + @Nonnull RaycastClosestQuery query) { + SpaceQueryContext space = space(store, query.spaceId()); + if (space == null) { + return null; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + RayHitCapture hit = new RayHitCapture(runtime); + Vector3f from = query.from(); + Vector3f to = query.to(); + boolean hitFound = space.backendRuntime().raycastClosest(space.spaceHandle().value(), + from.x, + from.y, + from.z, + to.x, + to.y, + to.z, + hit); + return hitFound && hit.captured ? Optional.of(hit.view()) : Optional.empty(); + } + + @Nullable + private static RaycastClosestBatchResult raycastClosestBatch(@Nonnull Store store, + @Nonnull RaycastClosestBatchQuery query) { + SpaceQueryContext space = space(store, query.spaceId()); + if (space == null) { + return null; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + int rayCount = query.rayCount(); + RaycastHitView[] hits = new RaycastHitView[rayCount]; + Vector3f from = new Vector3f(); + Vector3f to = new Vector3f(); + for (int index = 0; index < rayCount; index++) { + RaycastSegment ray = query.ray(index); + ray.copyFrom(from); + ray.copyTo(to); + int rayIndex = index; + space.backendRuntime().raycastClosest(space.spaceHandle().value(), + from.x, + from.y, + from.z, + to.x, + to.y, + to.z, + (bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance) -> hits[rayIndex] = toView(runtime, + bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance)); + } + return new RaycastClosestBatchResult(hits); + } + + @Nullable + private static List raycastAll(@Nonnull Store store, + @Nonnull RaycastAllQuery query) { + SpaceQueryContext space = space(store, query.spaceId()); + if (space == null) { + return null; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + Vector3f from = query.from(); + Vector3f to = query.to(); + List hits = new ArrayList<>(); + space.backendRuntime().raycastAll(space.spaceHandle().value(), + from.x, + from.y, + from.z, + to.x, + to.y, + to.z, + (bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance) -> hits.add(toView(runtime, + bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance))); + return hits.isEmpty() ? List.of() : List.copyOf(hits); + } + + @Nullable + private static Integer spaceBodyCount(@Nonnull Store store, + @Nonnull SpaceBodyCountQuery query) { + SpaceQueryContext space = space(store, query.spaceId()); + return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : null; + } + + @Nullable + private static List spaceSummary(@Nonnull Store store, + @Nonnull SpaceSummaryQuery query) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + List summaries = new ArrayList<>(); + if (query.spaceId() != null) { + SpaceQueryContext space = space(runtime, compatibility, query.spaceId()); + if (space != null) { + summaries.add(summary(compatibility, space)); + } + return summaries.isEmpty() ? null : List.copyOf(summaries); + } + runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { + SpaceId spaceId = compatibility.getSpaceId(spaceUuid); + if (spaceId != null) { + summaries.add(new SpaceSummary(spaceId, + backendId, + backendRuntime.bodyCount(spaceHandle.value()), + backendRuntime.jointCount(spaceHandle.value()))); + } + }); + return summaries.isEmpty() ? null : List.copyOf(summaries); + } + + @Nonnull + private static SpaceSummary summary(@Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull SpaceQueryContext space) { + SpaceId spaceId = compatibility.getSpaceId(space.spaceUuid()); + if (spaceId == null) { + throw new IllegalStateException("PhysicsStore space has no compatibility SpaceId: " + + space.spaceUuid()); + } + return new SpaceSummary(spaceId, + space.backendId(), + space.backendRuntime().bodyCount(space.spaceHandle().value()), + space.backendRuntime().jointCount(space.spaceHandle().value())); + } + + @Nullable + private static SpaceQueryContext space(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + return space(runtime, compatibility, spaceId); + } + + @Nullable + private static SpaceQueryContext space(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = compatibility.getSpaceUuid(spaceId); + if (spaceUuid == null) { + return null; + } + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); + BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; + if (spaceHandle == null || backendId == null || backendRuntime == null) { + return null; + } + return new SpaceQueryContext(spaceUuid, backendId, spaceHandle, backendRuntime); + } + + @Nonnull + private static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, + long bodyId, + float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float fraction, + float distance) { + BodyHitMetadata metadata = runtime.getBodyHitMetadata(new BackendBodyHandle(bodyId)); + return new RaycastHitView(metadata != null ? metadata.bodyKey() : null, + metadata != null ? metadata.bodyType() : PhysicsBodyType.STATIC, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + metadata != null ? metadata.shapeType() : ShapeType.UNKNOWN, + fraction, + distance); + } + + private record SpaceQueryContext(@Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } + + private static final class RayHitCapture implements BackendRayHitSink { + + @Nonnull + private final PhysicsRuntimeResource runtime; + private boolean captured; + @Nullable + private RaycastHitView view; + + private RayHitCapture(@Nonnull PhysicsRuntimeResource runtime) { + this.runtime = runtime; + } + + @Override + public void accept(long bodyId, + float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float fraction, + float distance) { + view = toView(runtime, + bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance); + captured = true; + } + + @Nonnull + private RaycastHitView view() { + return Objects.requireNonNull(view, "view"); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index cf333fc1..279ee027 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; @@ -38,6 +39,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsWorldCollisionRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; +import dev.hytalemodding.impulse.core.internal.physicsstore.queries.PhysicsStoreQueryBridge; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -49,8 +51,9 @@ import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; @@ -68,6 +71,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.RejectedExecutionException; @@ -116,6 +120,8 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { private final AtomicLong visualInterestTick = new AtomicLong(); private final PhysicsOwnerGateway ownerGateway = new PhysicsOwnerGateway(); private final PhysicsSimulationExecutor simulationExecutor = new PhysicsSimulationExecutor(this); + @Nullable + private Store owningStore; public PhysicsWorldRuntimeResource() { ControlLifecycle.registerResource(this); @@ -140,6 +146,10 @@ public void attachOwnerExecutor(@Nonnull PhysicsOwnerHandle ownerExecutor) { ownerGateway.attachOwnerExecutor(ownerExecutor); } + public void attachEntityStore(@Nonnull Store store) { + owningStore = Objects.requireNonNull(store, "store"); + } + public void detachOwnerExecutor(@Nonnull PhysicsOwnerHandle ownerExecutor) { ownerGateway.detachOwnerExecutor(ownerExecutor); if (!ownerGateway.hasOwnerExecutor()) { @@ -147,6 +157,12 @@ public void detachOwnerExecutor(@Nonnull PhysicsOwnerHandle ownerExecutor) { } } + public void detachEntityStore(@Nonnull Store store) { + if (owningStore == store) { + owningStore = null; + } + } + public boolean canAccessLiveBackendDirectly() { return ownerGateway.canAccessLiveBackendDirectly(); } @@ -224,11 +240,29 @@ public PhysicsCommandHandle submitRecordedCommands(@Nonnull MutablePhysicsComman @Override public PhysicsQueryHandle query(@Nonnull PhysicsQuery query) { Objects.requireNonNull(query, "query"); + Optional> physicsStoreQuery = tryPhysicsStoreQuery(query); + if (physicsStoreQuery.isPresent()) { + return physicsStoreQuery.get(); + } CompletableFuture completion = ownerGateway.enqueueCall("execute physics query", () -> simulationExecutor.query(query)); return PhysicsQueryHandle.fromCompletion(query, completion); } + @Nonnull + private Optional> tryPhysicsStoreQuery(@Nonnull PhysicsQuery query) { + Store entityStore = owningStore; + if (entityStore == null) { + return Optional.empty(); + } + try { + PhysicsStore physicsStore = PhysicsStoreAccess.require(entityStore.getExternalData().getWorld()); + return PhysicsStoreQueryBridge.tryQuery(physicsStore.getStore(), query); + } catch (IllegalStateException exception) { + return Optional.empty(); + } + } + @Nonnull public CompletionStage queryInternal(@Nonnull PhysicsInternalQuery query) { Objects.requireNonNull(query, "query"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java index 200dfd7d..99310606 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java @@ -45,8 +45,10 @@ public PhysicsOwnerLifecycleSystem() { public void onSystemAddedToStore(@Nonnull Store store) { PhysicsOwnerResource owner = store.getResource(ownerResourceType); if (startOwner(owner, worldName(store))) { - PhysicsWorldRuntimeResource.require(store.getResource(physicsWorldResourceType)) - .attachOwnerExecutor(owner); + PhysicsWorldRuntimeResource runtime = + PhysicsWorldRuntimeResource.require(store.getResource(physicsWorldResourceType)); + runtime.attachEntityStore(store); + runtime.attachOwnerExecutor(owner); } } @@ -59,6 +61,7 @@ public void onSystemRemovedFromStore(@Nonnull Store store) { RuntimeException clearFailure = tryClearSpaces(physics, worldName); boolean closedOwner = closeOwner(owner); runtime.detachOwnerExecutor(owner); + runtime.detachEntityStore(store); // Retry if it failed before, this could happen. if (clearFailure != null && closedOwner) { From 3bf274f369b98b3113897bb8ac91b102ae705118 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 16:41:47 +0200 Subject: [PATCH 019/534] feat(examples): author simple bodies through physics store Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreAccess.java | 25 ++ .../commands/ExamplePhysicsUtils.java | 218 ++++++++++++++++++ 2 files changed, 243 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java index fc836e8b..5d0c9ef3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java @@ -1,12 +1,19 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Access to the early-plugin-injected PhysicsStore on a Hytale world. @@ -31,6 +38,24 @@ public static PhysicsStore require(@Nonnull World world) { } } + @Nullable + public static UUID resolveSpaceUuid(@Nonnull World world, + @Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); + Store store = require(world).getStore(); + PhysicsSpaceCompatibilityIndexResource index = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + return index.getSpaceUuid(spaceId); + } + + public static void enqueue(@Nonnull World world, + @Nonnull PhysicsStoreRequest request) { + Objects.requireNonNull(request, "request"); + Store store = require(world).getStore(); + store.getResource(PhysicsRequestQueueResource.getResourceType()) + .enqueue(request); + } + @Nonnull private static MethodHandle findWorldAccessor() { try { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 4ab4484e..3c2bbdbb 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -15,6 +15,7 @@ import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; @@ -31,15 +32,26 @@ import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -166,6 +178,19 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { + PendingBlockBody physicsStoreBody = linearVelocity == null + ? tryRecordPhysicsStoreBlockBody(store, + spaceId, + visualPosition, + blockType, + shape, + mass, + settings) + : null; + if (physicsStoreBody != null) { + return attachPhysicsStoreBlockBody(store, time, physicsStoreBody); + } + PendingBlockBodyCapture pending = new PendingBlockBodyCapture(); requireApplied(resource.submitCommands(0L, linearVelocity != null ? 2 : 1, commands -> pending.set(recordBlockBodySpawn(commands, @@ -179,6 +204,134 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, return attachRecordedBlockBody(store, time, pending.require()); } + @Nullable + private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d visualPosition, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(visualPosition, "visualPosition"); + Objects.requireNonNull(shape, "shape"); + Objects.requireNonNull(settings, "settings"); + + World world = store.getExternalData().getWorld(); + UUID spaceUuid; + try { + spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + } catch (IllegalStateException exception) { + return null; + } + if (spaceUuid == null) { + return null; + } + + RigidBodyKey bodyKey = RigidBodyKey.random(); + UUID bodyUuid = bodyKey.value(); + Vector3f bodyCenter = toVector3f(visualPosition); + try { + PhysicsStoreAccess.enqueue(world, + bodyUpsertRequest(spaceUuid, + bodyUuid, + bodyCenter, + shape, + mass, + settings)); + } catch (IllegalStateException exception) { + return null; + } + + return new PendingBlockBody(bodyKey, + spaceId, + blockType, + (float) visualPosition.x, + (float) visualPosition.y, + (float) visualPosition.z, + mass > 0.0f); + } + + @Nonnull + private static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings) { + UUID colliderUuid = bodyGraphUuid(bodyUuid, "collider"); + UUID shapeUuid = bodyGraphUuid(bodyUuid, "shape"); + UUID materialUuid = bodyGraphUuid(bodyUuid, "material"); + UUID filterUuid = bodyGraphUuid(bodyUuid, "filter"); + return BodyUpsertRequest.of(bodyUuid, + new BodyComponent(spaceUuid, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.PERSISTENT), + new DynamicsComponent(PhysicsBodyType.DYNAMIC, + mass, + settings.hasLinearDamping() ? settings.linearDamping() : 0.0f, + settings.hasAngularDamping() ? settings.angularDamping() : 0.0f, + false), + initialTarget(bodyCenter), + colliderUuid, + new ColliderComponent(bodyUuid, + shapeUuid, + materialUuid, + filterUuid, + new Vector3f(), + new Quaternionf(), + settings.hasSensor() && settings.sensor()), + shapeUuid, + new ShapeComponent(shape.type(), + shape.halfExtentX(), + shape.halfExtentY(), + shape.halfExtentZ(), + shape.radius(), + shape.halfHeight(), + shape.axis(), + shape.groundY(), + ""), + materialUuid, + new MaterialComponent(settings.hasFriction() ? settings.friction() : 0.5f, + settings.hasRestitution() ? settings.restitution() : 0.0f), + filterUuid, + collisionFilter(settings)); + } + + @Nonnull + private static TargetComponent initialTarget(@Nonnull Vector3f bodyCenter) { + TargetComponent target = new TargetComponent(); + // BodyBindingSystem reads this pose once; keeping it inactive avoids pinning dynamic bodies. + target.setActive(false); + target.setPosition(bodyCenter); + target.setRotation(new Quaternionf()); + target.setLinearVelocity(new Vector3f()); + target.setAngularVelocity(new Vector3f()); + target.setTransformEnabled(true); + target.setVelocityEnabled(false); + target.setActivate(true); + return target; + } + + @Nonnull + private static CollisionFilterComponent collisionFilter(@Nonnull RigidBodySpawnSettings settings) { + return new CollisionFilterComponent( + settings.hasCollisionFilter() + ? settings.collisionGroup() + : PhysicsCollisionFilters.DYNAMIC_BODY, + settings.hasCollisionFilter() + ? settings.collisionMask() + : PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); + } + + @Nonnull + private static UUID bodyGraphUuid(@Nonnull UUID bodyUuid, + @Nonnull String rowKind) { + return UUID.nameUUIDFromBytes(("impulse:physics-body:" + bodyUuid + ':' + rowKind) + .getBytes(StandardCharsets.UTF_8)); + } + @Nonnull public static PendingBlockBody recordBlockBodySpawn(@Nonnull PhysicsCommandRecorder commands, @Nonnull SpaceId spaceId, @@ -340,6 +493,22 @@ public static SpawnedBlockBody attachRecordedBlockBody(@Nonnull Store store, + @Nonnull TimeResource time, + @Nonnull PendingBlockBody pending) { + Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, + time, + pending.bodyKey(), + pending.bodyKey().value(), + pending.spaceId(), + pending.blockType(), + new Vector3d(pending.positionX(), pending.positionY(), pending.positionZ()), + pending.controllable()); + assert entity != null; + return new SpawnedBlockBody(pending.bodyKey(), pending.spaceId(), entity); + } + @Nonnull public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store store, @Nonnull TimeResource time, @@ -681,6 +850,55 @@ public static Holder attachedBlockEntityHolder(@Nonnull TimeResourc return holder; } + @Nullable + private static Ref spawnAttachedPhysicsStoreBlockEntity(@Nonnull Store store, + @Nonnull TimeResource time, + @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID physicsBodyUuid, + @Nonnull SpaceId spaceId, + @Nullable String blockType, + @Nonnull Vector3d visualPosition, + boolean controllable) { + Holder holder = attachedPhysicsStoreBlockEntityHolder(time, + bodyKey, + physicsBodyUuid, + spaceId, + blockType, + visualPosition, + new Vector3f(), + new Quaternionf(), + Float.NaN, + controllable); + return store.addEntity(holder, AddReason.SPAWN); + } + + @Nonnull + private static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, + @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID physicsBodyUuid, + @Nonnull SpaceId spaceId, + @Nullable String blockType, + @Nonnull Vector3d visualPosition, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY, + boolean controllable) { + Holder holder = blockEntityHolder(time, blockType, visualPosition); + PhysicsBodyAttachmentComponent attachment = PhysicsBodyAttachmentComponent.impulseOwnedVisual( + bodyKey, + spaceId, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); + attachment.setPhysicsBodyUuid(physicsBodyUuid); + holder.addComponent(ATTACHMENT_TYPE, attachment); + if (controllable && PhysicsControlSessions.isAvailable()) { + holder.addComponent(ImpulseControllableComponent.getComponentType(), + new ImpulseControllableComponent()); + } + return holder; + } + @Nonnull private static Holder blockEntityHolder(@Nonnull TimeResource time, @Nullable String blockType, From 91f3019169d363ef190a7402ea4a9e2cdc29015a Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 16:50:32 +0200 Subject: [PATCH 020/534] feat(core): apply physics store initial body state Signed-off-by: Blovien --- .../systems/BodyBindingSystem.java | 28 +++++++++ .../systems/PersistenceCaptureSystem.java | 4 +- .../systems/PersistenceHydrationSystem.java | 3 + .../commands/ExamplePhysicsUtils.java | 60 +++++++++++++------ .../commands/stress/StressShapesCommand.java | 6 +- 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index 78997146..d3e0056b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -198,6 +198,7 @@ private static void bindBody(@Nonnull Store store, && backendRuntime.supportsContinuousCollision(spaceHandle.value())) { backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); } + applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); runtime.putBodyHandle(bodyUuid, spaceHandle, bodyHandle); runtime.putBodyHitMetadata(bodyHandle, RigidBodyKey.of(bodyUuid), @@ -206,6 +207,33 @@ private static void bindBody(@Nonnull Store store, identity.putBodyHandle(bodyHandle, bodyRef); } + private static void applyInitialTargetState(@Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull PhysicsBodyType bodyType, + @Nullable TargetComponent target) { + if (target == null) { + return; + } + if (target.isVelocityEnabled()) { + Vector3f linearVelocity = target.getLinearVelocity(); + Vector3f angularVelocity = target.getAngularVelocity(); + backendRuntime.setBodyVelocity(spaceHandle.value(), + bodyHandle.value(), + linearVelocity.x, + linearVelocity.y, + linearVelocity.z, + angularVelocity.x, + angularVelocity.y, + angularVelocity.z); + } + if (target.isActivate()) { + backendRuntime.activateBody(spaceHandle.value(), bodyHandle.value()); + } else if (bodyType == PhysicsBodyType.DYNAMIC) { + backendRuntime.sleepBody(spaceHandle.value(), bodyHandle.value()); + } + } + @Nullable private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID spaceUuid) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index f0aece3c..e4d28e9b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -280,12 +280,12 @@ private PersistentBodyRuntimeStateDto runtimeState(@Nonnull UUID bodyUuid, snapshot.angularVelocity(), snapshot.sleeping()); } - if (target != null && target.isActive()) { + if (target != null) { return new PersistentBodyRuntimeStateDto(target.getPosition(), target.getRotation(), target.getLinearVelocity(), target.getAngularVelocity(), - false); + !target.isActive() && !target.isActivate()); } return new PersistentBodyRuntimeStateDto(new Vector3f(), new Quaternionf(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index eca363bc..59a44df0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -209,6 +209,9 @@ private static TargetComponent inactiveTarget(@Nonnull PersistentBodyRuntimeStat target.setRotation(dto.getRotation()); target.setLinearVelocity(dto.getLinearVelocity()); target.setAngularVelocity(dto.getAngularVelocity()); + target.setTransformEnabled(true); + target.setVelocityEnabled(true); + target.setActivate(!dto.isSleeping()); return target; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 3c2bbdbb..781ee11f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -178,19 +178,41 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - PendingBlockBody physicsStoreBody = linearVelocity == null - ? tryRecordPhysicsStoreBlockBody(store, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings) - : null; + PendingBlockBody physicsStoreBody = tryRecordPhysicsStoreBlockBody(store, + spaceId, + visualPosition, + blockType, + shape, + mass, + settings, + linearVelocity); if (physicsStoreBody != null) { return attachPhysicsStoreBlockBody(store, time, physicsStoreBody); } + return spawnBlockBodyLegacy(store, + time, + resource, + spaceId, + visualPosition, + blockType, + shape, + mass, + settings, + linearVelocity); + } + + @Nonnull + public static SpawnedBlockBody spawnBlockBodyLegacy(@Nonnull Store store, + @Nonnull TimeResource time, + @Nonnull PhysicsWorldResource resource, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d visualPosition, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity) { PendingBlockBodyCapture pending = new PendingBlockBodyCapture(); requireApplied(resource.submitCommands(0L, linearVelocity != null ? 2 : 1, commands -> pending.set(recordBlockBodySpawn(commands, @@ -211,7 +233,8 @@ private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store store, @Nonnull PhysicsAxis axis, @Nonnull Vector3d base, double xOffset) { - ExamplePhysicsUtils.spawnBlockBody(store, + ExamplePhysicsUtils.spawnBlockBodyLegacy(store, time, resource, spaceId, new Vector3d(base).add(xOffset, 0.0, 0.0), + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, shape(type, axis), 1.0f, - RigidBodySpawnSettings.material(0.6f, 0.25f)); + RigidBodySpawnSettings.material(0.6f, 0.25f), + null); } @Nonnull From f51f819ba17016745af14613adff017cfd663cf0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 17:10:35 +0200 Subject: [PATCH 021/534] feat(core): enqueue physics store request batches Signed-off-by: Blovien --- .../PhysicsRequestQueueResource.java | 10 ++++- .../physicsstore/PhysicsStoreAccess.java | 37 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java index 8b2cf4e6..9e5454b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java @@ -8,6 +8,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Queue; import javax.annotation.Nonnull; @@ -23,7 +24,14 @@ public PhysicsRequestQueueResource() { } public synchronized void enqueue(@Nonnull PhysicsStoreRequest request) { - requests.add(request); + requests.add(Objects.requireNonNull(request, "request")); + } + + public synchronized void enqueueAll(@Nonnull Iterable batch) { + Objects.requireNonNull(batch, "batch"); + for (PhysicsStoreRequest request : batch) { + requests.add(Objects.requireNonNull(request, "request")); + } } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java index 5d0c9ef3..99948c6a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java @@ -10,6 +10,8 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -20,8 +22,8 @@ */ public final class PhysicsStoreAccess { - @Nonnull - private static final MethodHandle WORLD_GET_PHYSICS_STORE = findWorldAccessor(); + @Nullable + private static volatile MethodHandle worldGetPhysicsStore; private PhysicsStoreAccess() { } @@ -30,7 +32,7 @@ private PhysicsStoreAccess() { public static PhysicsStore require(@Nonnull World world) { Objects.requireNonNull(world, "world"); try { - return (PhysicsStore) WORLD_GET_PHYSICS_STORE.invoke(world); + return (PhysicsStore) worldAccessor().invoke(world); } catch (RuntimeException | Error exception) { throw exception; } catch (Throwable throwable) { @@ -56,6 +58,35 @@ public static void enqueue(@Nonnull World world, .enqueue(request); } + public static void enqueueAll(@Nonnull World world, + @Nonnull Iterable requests) { + Objects.requireNonNull(requests, "requests"); + List copied = new ArrayList<>(); + for (PhysicsStoreRequest request : requests) { + copied.add(Objects.requireNonNull(request, "request")); + } + Store store = require(world).getStore(); + PhysicsRequestQueueResource queue = store.getResource( + PhysicsRequestQueueResource.getResourceType()); + queue.enqueueAll(copied); + } + + @Nonnull + private static MethodHandle worldAccessor() { + MethodHandle accessor = worldGetPhysicsStore; + if (accessor != null) { + return accessor; + } + synchronized (PhysicsStoreAccess.class) { + accessor = worldGetPhysicsStore; + if (accessor == null) { + accessor = findWorldAccessor(); + worldGetPhysicsStore = accessor; + } + return accessor; + } + } + @Nonnull private static MethodHandle findWorldAccessor() { try { From a207e24cca2fddc484b16a03d8459eb0e076e5c9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 17:11:01 +0200 Subject: [PATCH 022/534] feat(examples): author joint demo through physics store Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 16 +- .../examples/commands/JointsCommand.java | 246 +++++++++++++----- 2 files changed, 180 insertions(+), 82 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 781ee11f..08e5cda0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -190,16 +190,8 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, return attachPhysicsStoreBlockBody(store, time, physicsStoreBody); } - return spawnBlockBodyLegacy(store, - time, - resource, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - linearVelocity); + throw new IllegalStateException("Cannot spawn block body because the target space is not " + + "bound in PhysicsStore: " + spaceId.value()); } @Nonnull @@ -278,7 +270,7 @@ private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store store, + static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull PendingBlockBody pending) { Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index 84eabf8e..e9287ba8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -14,15 +14,21 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; +import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3d; import org.joml.Vector3f; @@ -60,16 +66,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-5.0, 5.0, 5.0); - List pendingBodies = new ArrayList<>(10); - ExamplePhysicsUtils.requireApplied(resource.submitCommands(Math.max(0L, world.getTick()), 16, commands -> { - createFixed(pendingBodies, commands, spaceId, new Vector3d(origin)); - createPoint(pendingBodies, commands, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); - createHinge(pendingBodies, commands, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); - createSlider(pendingBodies, commands, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); - createSpring(pendingBodies, commands, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); - }), "create joint demo"); + List pendingBodies = tryCreatePhysicsStoreDemo(world, + spaceId, + new Vector3d(origin)); + if (pendingBodies == null) { + ctx.sender().sendMessage(Message.raw( + "Cannot spawn joint demo because the target space is not bound in PhysicsStore.")); + return CompletableFuture.completedFuture(null); + } for (PendingBlockBody pending : pendingBodies) { - ExamplePhysicsUtils.attachRecordedBlockBody(store, time, pending); + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, pending); } ctx.sender().sendMessage(Message.raw( @@ -77,100 +83,200 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } + @Nullable + private static List tryCreatePhysicsStoreDemo(@Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d origin) { + UUID spaceUuid; + try { + spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + } catch (IllegalStateException exception) { + return null; + } + if (spaceUuid == null) { + return null; + } + + List pendingBodies = new ArrayList<>(10); + List requests = new ArrayList<>(15); + createFixed(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin)); + createPoint(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); + createHinge(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); + createSlider(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); + createSpring(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); + try { + PhysicsStoreAccess.enqueueAll(world, requests); + } catch (IllegalStateException exception) { + return null; + } + return pendingBodies; + } + private static void createFixed(@Nonnull List pendingBodies, - @Nonnull PhysicsCommandRecorder commands, + @Nonnull List requests, + @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, commands, spaceId, origin, 0.0f); - RigidBodyKey childKey = spawnBox(pendingBodies, commands, spaceId, + RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey childKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); - commands.joint(JointKey.random(), joint -> joint - .space(spaceId) - .bodies(anchorKey, childKey) - .fixed(new Vector3f(0.0f, -HALF_SIZE, 0.0f), - new Vector3f(0.0f, HALF_SIZE, 0.0f))); + requests.add(JointUpsertRequest.of(JointKey.random().value(), + joint(spaceUuid, + anchorKey, + childKey, + JointType.FIXED, + new Vector3f(0.0f, -HALF_SIZE, 0.0f), + new Vector3f(0.0f, HALF_SIZE, 0.0f), + new Vector3f()))); } private static void createPoint(@Nonnull List pendingBodies, - @Nonnull PhysicsCommandRecorder commands, + @Nonnull List requests, + @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, commands, spaceId, origin, 0.0f); - RigidBodyKey bobKey = spawnBox(pendingBodies, commands, spaceId, - new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); - commands.setBodyVelocity(bobKey, 1.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, true); - commands.joint(JointKey.random(), joint -> joint - .space(spaceId) - .bodies(anchorKey, bobKey) - .point(new Vector3f(0.0f, -HALF_SIZE, 0.0f), - new Vector3f(0.0f, HALF_SIZE, 0.0f))); + RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey bobKey = spawnBox(pendingBodies, + requests, + spaceUuid, + spaceId, + new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), + 1.0f, + new Vector3f(1.5f, 0.0f, 0.0f)); + requests.add(JointUpsertRequest.of(JointKey.random().value(), + joint(spaceUuid, + anchorKey, + bobKey, + JointType.POINT, + new Vector3f(0.0f, -HALF_SIZE, 0.0f), + new Vector3f(0.0f, HALF_SIZE, 0.0f), + new Vector3f()))); } private static void createHinge(@Nonnull List pendingBodies, - @Nonnull PhysicsCommandRecorder commands, + @Nonnull List requests, + @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, commands, spaceId, origin, 0.0f); - RigidBodyKey armKey = spawnBox(pendingBodies, commands, spaceId, + RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey armKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); - commands.joint(JointKey.random(), joint -> joint - .space(spaceId) - .bodies(anchorKey, armKey) - .hinge(new Vector3f(0.0f, -HALF_SIZE, 0.0f), - new Vector3f(0.0f, HALF_SIZE, 0.0f), - new Vector3f(0.0f, 0.0f, 1.0f)) - .limits(-1.2f, 1.2f) - .motor(1.5f, 3.0f)); + JointComponent joint = joint(spaceUuid, + anchorKey, + armKey, + JointType.HINGE, + new Vector3f(0.0f, -HALF_SIZE, 0.0f), + new Vector3f(0.0f, HALF_SIZE, 0.0f), + new Vector3f(0.0f, 0.0f, 1.0f)); + joint.setLowerLimit(-1.2f); + joint.setUpperLimit(1.2f); + joint.setMotorEnabled(true); + joint.setMotorTargetVelocity(1.5f); + joint.setMotorMaxForce(3.0f); + requests.add(JointUpsertRequest.of(JointKey.random().value(), joint)); } private static void createSlider(@Nonnull List pendingBodies, - @Nonnull PhysicsCommandRecorder commands, + @Nonnull List requests, + @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, commands, spaceId, origin, 0.0f); - RigidBodyKey blockKey = spawnBox(pendingBodies, commands, spaceId, + RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey blockKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(TOUCHING_SPACING, 0.0, 0.0), 1.0f); - commands.joint(JointKey.random(), joint -> joint - .space(spaceId) - .bodies(anchorKey, blockKey) - .slider(new Vector3f(HALF_SIZE, 0.0f, 0.0f), - new Vector3f(-HALF_SIZE, 0.0f, 0.0f), - new Vector3f(1.0f, 0.0f, 0.0f)) - .limits(-1.0f, 1.0f) - .motor(1.0f, 4.0f)); + JointComponent joint = joint(spaceUuid, + anchorKey, + blockKey, + JointType.SLIDER, + new Vector3f(HALF_SIZE, 0.0f, 0.0f), + new Vector3f(-HALF_SIZE, 0.0f, 0.0f), + new Vector3f(1.0f, 0.0f, 0.0f)); + joint.setLowerLimit(-1.0f); + joint.setUpperLimit(1.0f); + joint.setMotorEnabled(true); + joint.setMotorTargetVelocity(1.0f); + joint.setMotorMaxForce(4.0f); + requests.add(JointUpsertRequest.of(JointKey.random().value(), joint)); } private static void createSpring(@Nonnull List pendingBodies, - @Nonnull PhysicsCommandRecorder commands, + @Nonnull List requests, + @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, commands, spaceId, origin, 0.0f); - RigidBodyKey bobKey = spawnBox(pendingBodies, commands, spaceId, + RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey bobKey = spawnBox(pendingBodies, + requests, + spaceUuid, + spaceId, new Vector3d(origin).add(0.0, -(TOUCHING_SPACING + SPRING_REST_LENGTH), 0.0), - 1.0f); - commands.setBodyVelocity(bobKey, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, true); - commands.joint(JointKey.random(), joint -> joint - .space(spaceId) - .bodies(anchorKey, bobKey) - .spring(new Vector3f(0.0f, -HALF_SIZE, 0.0f), - new Vector3f(0.0f, HALF_SIZE, 0.0f), - SPRING_REST_LENGTH, - 20.0f, - 2.0f)); + 1.0f, + new Vector3f(1.0f, 0.0f, 0.0f)); + JointComponent joint = joint(spaceUuid, + anchorKey, + bobKey, + JointType.SPRING, + new Vector3f(0.0f, -HALF_SIZE, 0.0f), + new Vector3f(0.0f, HALF_SIZE, 0.0f), + new Vector3f()); + joint.setSpringRestLength(SPRING_REST_LENGTH); + joint.setSpringStiffness(20.0f); + joint.setSpringDamping(2.0f); + requests.add(JointUpsertRequest.of(JointKey.random().value(), joint)); } private static RigidBodyKey spawnBox(@Nonnull List pendingBodies, - @Nonnull PhysicsCommandRecorder commands, + @Nonnull List requests, + @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass) { - PendingBlockBody pending = ExamplePhysicsUtils.recordBlockBodySpawn(commands, - spaceId, - position, + return spawnBox(pendingBodies, requests, spaceUuid, spaceId, position, mass, null); + } + + private static RigidBodyKey spawnBox(@Nonnull List pendingBodies, + @Nonnull List requests, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d position, + float mass, + @Nullable Vector3f linearVelocity) { + RigidBodyKey bodyKey = RigidBodyKey.random(); + requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyKey.value(), + ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), mass, - RigidBodySpawnSettings.material(0.6f, 0.15f)); - pendingBodies.add(pending); - return pending.bodyKey(); + RigidBodySpawnSettings.material(0.6f, 0.15f), + linearVelocity)); + pendingBodies.add(new PendingBlockBody(bodyKey, + spaceId, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + (float) position.x, + (float) position.y, + (float) position.z, + mass > 0.0f)); + return bodyKey; } + + @Nonnull + private static JointComponent joint(@Nonnull UUID spaceUuid, + @Nonnull RigidBodyKey bodyA, + @Nonnull RigidBodyKey bodyB, + @Nonnull JointType type, + @Nonnull Vector3f anchorA, + @Nonnull Vector3f anchorB, + @Nonnull Vector3f axis) { + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(spaceUuid); + joint.setBodyAUuid(bodyA.value()); + joint.setBodyBUuid(bodyB.value()); + joint.setType(type); + joint.setAnchorA(anchorA); + joint.setAnchorB(anchorB); + joint.setAxis(axis); + joint.setEnabled(true); + return joint; + } + } From 929f823e5c9ac87d9c2fdfabafacefa6281d1be0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 17:28:43 +0200 Subject: [PATCH 023/534] refactor(core): make physics store attachments projection only Signed-off-by: Blovien --- .../CompletedStepPublicationSystem.java | 5 +- .../PhysicsBodyAttachmentIndexSystem.java | 17 ++++++- .../systems/sync/PhysicsSyncSystem.java | 49 +++++++++++-------- .../visual/GameplayAttachmentSnapshot.java | 4 +- ...csDetachedVisualMaterializationSystem.java | 13 +++++ .../PhysicsBodyAttachmentComponent.java | 4 ++ .../snapshots/PhysicsStoreBodySnapshot.java | 1 + 7 files changed, 69 insertions(+), 24 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 28856dd3..c76843e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -69,7 +69,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) _, _, _, - _, + centerOfMassOffsetY, _, _, _, @@ -94,6 +94,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) angularVelocityX, angularVelocityY, angularVelocityZ, + centerOfMassOffsetY, sleeping))); long nextSequence = snapshot.getLatestFrame().sequence() + 1L; snapshot.publish(new PhysicsStoreSnapshotFrame(nextSequence, dt, bodies)); @@ -117,6 +118,7 @@ private static void collectBodySnapshot(@Nonnull Store store, float angularVelocityX, float angularVelocityY, float angularVelocityZ, + float centerOfMassOffsetY, boolean sleeping) { Ref ref = identity.getByBodyHandle(new BackendBodyHandle(bodyId)); if (ref == null || !ref.isValid()) { @@ -134,6 +136,7 @@ private static void collectBodySnapshot(@Nonnull Store store, new Quaternionf(rotationX, rotationY, rotationZ, rotationW), new Vector3f(linearVelocityX, linearVelocityY, linearVelocityZ), new Vector3f(angularVelocityX, angularVelocityY, angularVelocityZ), + centerOfMassOffsetY, sleeping)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index 2fabfaa7..ba74fa6d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -28,6 +28,9 @@ public void onComponentAdded(@Nonnull Ref ref, @Nonnull PhysicsBodyAttachmentComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { + if (!component.usesLegacyBodyKey()) { + return; + } PhysicsWorldRuntimeResource.require( commandBuffer.getResource(PhysicsWorldResource.getResourceType())) .registerBodyAttachment(component.getBodyKey(), ref); @@ -39,11 +42,18 @@ public void onComponentSet(@Nonnull Ref ref, @Nonnull PhysicsBodyAttachmentComponent newComponent, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { + assert oldComponent != null; + boolean oldLegacy = oldComponent.usesLegacyBodyKey(); + boolean newLegacy = newComponent.usesLegacyBodyKey(); + if (!oldLegacy && !newLegacy) { + return; + } PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require( commandBuffer.getResource(PhysicsWorldResource.getResourceType())); - assert oldComponent != null; - if (!oldComponent.getBodyKey().equals(newComponent.getBodyKey())) { + if (oldLegacy && (!newLegacy || !oldComponent.getBodyKey().equals(newComponent.getBodyKey()))) { resource.unregisterBodyAttachment(oldComponent.getBodyKey(), ref); + } + if (newLegacy && (!oldLegacy || !oldComponent.getBodyKey().equals(newComponent.getBodyKey()))) { resource.registerBodyAttachment(newComponent.getBodyKey(), ref); } resource.clearBodySyncState(ref); @@ -54,6 +64,9 @@ public void onComponentRemoved(@Nonnull Ref ref, @Nonnull PhysicsBodyAttachmentComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { + if (!component.usesLegacyBodyKey()) { + return; + } PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require( commandBuffer.getResource(PhysicsWorldResource.getResourceType())); resource.unregisterBodyAttachment(component.getBodyKey(), ref); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index d59a6ead..4bc9215d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -3,7 +3,9 @@ import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -31,11 +33,8 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; @@ -86,8 +85,7 @@ public class PhysicsSyncSystem extends EntityTickingSystem { @Nonnull private final ThreadLocal syncNanos = ThreadLocal.withInitial(() -> 0L); @Nonnull - private final ThreadLocal> physicsStoreSnapshots = - ThreadLocal.withInitial(Map::of); + private final ThreadLocal physicsStoreSnapshots = new ThreadLocal<>(); /** * Hytale may run entity ticks in parallel. Each tick task needs independent temporary objects @@ -112,7 +110,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { ? profiling.beginSyncSample() : null; long startNanos = collector != null ? System.nanoTime() : 0L; try { - physicsStoreSnapshots.set(collectPhysicsStoreSnapshots(store)); + physicsStoreSnapshots.set(collectPhysicsStoreSnapshotResource(store)); super.tick(dt, systemIndex, store); } finally { if (collector != null) { @@ -142,8 +140,11 @@ public void tick(float dt, if (collector != null) { collector.incrementBodiesInspected(); } - PhysicsStoreBodySnapshot physicsStoreSnapshot = - physicsStoreSnapshots.get().get(attachment.getPhysicsBodyUuidOrLegacy()); + UUID physicsBodyUuid = attachment.getPhysicsBodyUuid(); + PhysicsSnapshotResource snapshotResource = physicsStoreSnapshots.get(); + PhysicsStoreBodySnapshot physicsStoreSnapshot = physicsBodyUuid != null + ? snapshotResource.getBody(physicsBodyUuid) + : null; if (physicsStoreSnapshot != null) { if (!PhysicsTransformAuthority.shouldApplyBodyTransform(attachment)) { return; @@ -155,6 +156,7 @@ public void tick(float dt, return; } if (attachment.getPhysicsBodyUuid() != null) { + clearMissingPhysicsStoreAttachment(entityRef, attachment, commandBuffer); return; } PhysicsWorldRuntimeResource resource = local.getResource(store); @@ -284,20 +286,11 @@ public void tick(float dt, } @Nonnull - private static Map collectPhysicsStoreSnapshots( + private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( @Nonnull Store store) { PhysicsStore physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()); - PhysicsSnapshotResource snapshotResource = physicsStore.getStore().getResource( + return physicsStore.getStore().getResource( PhysicsSnapshotResource.getResourceType()); - PhysicsStoreSnapshotFrame frame = snapshotResource.getLatestFrame(); - if (frame.bodies().isEmpty()) { - return Map.of(); - } - Map snapshots = new Object2ObjectOpenHashMap<>(); - for (PhysicsStoreBodySnapshot body : frame.bodies()) { - snapshots.put(body.bodyUuid(), body); - } - return snapshots; } private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transform, @@ -308,7 +301,7 @@ private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transf scratch.rotation.set(snapshot.rotation()); PhysicsVisualPoseMath.visualPositionFromBodyPose(scratch.position, scratch.rotation, - attachment.resolveVisualOriginOffsetY(0.0f), + attachment.resolveVisualOriginOffsetY(snapshot.centerOfMassOffsetY()), attachment.getLocalPositionOffset(), scratch.visualPosition, scratch.worldOffset); @@ -321,6 +314,22 @@ private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transf transform.getRotation().set(scratch.euler.x, scratch.euler.y, scratch.euler.z); } + private static void clearMissingPhysicsStoreAttachment(@Nonnull Ref entityRef, + @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull CommandBuffer commandBuffer) { + if (!attachment.shouldRemoveEntityWhenBodyMissing()) { + return; + } + commandBuffer.removeEntity(entityRef, + newHolder(commandBuffer.getStore()), + RemoveReason.REMOVE); + } + + @Nonnull + private static Holder newHolder(@Nonnull Store store) { + return store.getRegistry().newHolder(); + } + private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { double dx = from.x - to.x; double dy = from.y - to.y; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java index d9216dea..2cebf9ad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java @@ -64,7 +64,9 @@ private static Set collectGameplayAttachmentBodyKeys( (index, archetypeChunk, _) -> { PhysicsBodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); - if (attachment != null && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { + if (attachment != null + && attachment.usesLegacyBodyKey() + && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { bodyKeys.add(attachment.getBodyKey()); } }); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 24585ca6..033a53d3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -37,6 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -99,6 +100,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { private void tickMaterialization(@Nonnull Store store, @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { + if (hasAuthoritativePhysicsStore(store)) { + return; + } MaterializationState state = stateFor(store); PersistentPhysicsWorldResource persistent = store.getResource( PersistentPhysicsWorldResource.getResourceType()); @@ -148,6 +152,15 @@ private void tickMaterialization(@Nonnull Store store, } } + private static boolean hasAuthoritativePhysicsStore(@Nonnull Store store) { + try { + PhysicsStoreAccess.require(store.getExternalData().getWorld()); + return true; + } catch (IllegalStateException exception) { + return false; + } + } + private static boolean shouldPauseForRestore(@Nonnull MaterializationState state, @Nonnull PersistentPhysicsWorldResource persistent) { long restoreGeneration = persistent.runtimeRestoreGeneration(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java index 3bb0d3f9..3d7c2686 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java @@ -253,6 +253,10 @@ public void setPhysicsBodyUuid(@Nullable UUID physicsBodyUuid) { this.physicsBodyUuid = physicsBodyUuid; } + public boolean usesLegacyBodyKey() { + return physicsBodyUuid == null; + } + @Nonnull public UUID getPhysicsBodyUuidOrLegacy() { return physicsBodyUuid != null ? physicsBodyUuid : bodyKey.value(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java index 7e7586e1..1bd01817 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java @@ -17,6 +17,7 @@ public record PhysicsStoreBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull Quaternionf rotation, @Nonnull Vector3f linearVelocity, @Nonnull Vector3f angularVelocity, + float centerOfMassOffsetY, boolean sleeping) { public PhysicsStoreBodySnapshot { From 955ca616f9dcaab334ac90dcd77ecbb70b927c5c Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 17:39:47 +0200 Subject: [PATCH 024/534] feat(core): add physics store force and control requests Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 98 +++++++++ .../systems/RequestDrainSystem.java | 205 ++++++++++++++++++ .../systems/TargetBindingSystem.java | 57 +++++ .../requests/BodyActivationRequest.java | 34 +++ .../requests/BodyForceRequest.java | 129 +++++++++++ .../requests/BodyTypeRequest.java | 28 +++ 6 files changed, 551 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index e6ead575..f1958c9e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -17,6 +17,8 @@ import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -68,6 +70,8 @@ public final class PhysicsRuntimeResource implements Resource { @Nonnull private final Long2ObjectOpenHashMap bodyHitMetadataByHandle = new Long2ObjectOpenHashMap<>(); + @Nonnull + private final List pendingBodyOperations = new ArrayList<>(); private boolean started; public PhysicsRuntimeResource() { @@ -170,6 +174,20 @@ public void removeBodyHitMetadata(@Nonnull BackendBodyHandle handle) { bodyHitMetadataByHandle.remove(handle.value()); } + public void enqueuePendingBodyOperation(@Nonnull PendingBodyOperation operation) { + pendingBodyOperations.add(Objects.requireNonNull(operation, "operation")); + } + + @Nonnull + public List drainPendingBodyOperations() { + if (pendingBodyOperations.isEmpty()) { + return List.of(); + } + List drained = new ArrayList<>(pendingBodyOperations); + pendingBodyOperations.clear(); + return drained; + } + public void putJointHandle(@Nonnull UUID jointUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendJointHandle handle) { @@ -280,6 +298,7 @@ public void clear() { terrainPayloadKeysByUuid.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); + pendingBodyOperations.clear(); started = false; } @@ -302,6 +321,7 @@ public PhysicsRuntimeResource clone() { bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); + copy.pendingBodyOperations.addAll(pendingBodyOperations); copy.started = started; return copy; } @@ -330,6 +350,84 @@ public record BodyHitMetadata(@Nullable RigidBodyKey bodyKey, } } + public record PendingBodyOperation(@Nonnull Kind kind, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ) { + + public PendingBodyOperation { + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(spaceHandle, "spaceHandle"); + Objects.requireNonNull(bodyHandle, "bodyHandle"); + } + + @Nonnull + public static PendingBodyOperation wake(@Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle) { + return empty(Kind.WAKE, spaceHandle, bodyHandle); + } + + @Nonnull + public static PendingBodyOperation sleep(@Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle) { + return empty(Kind.SLEEP, spaceHandle, bodyHandle); + } + + @Nonnull + public static PendingBodyOperation vector(@Nonnull Kind kind, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ) { + return new PendingBodyOperation(kind, + spaceHandle, + bodyHandle, + x, + y, + z, + hasOffset, + offsetX, + offsetY, + offsetZ); + } + + @Nonnull + private static PendingBodyOperation empty(@Nonnull Kind kind, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle) { + return new PendingBodyOperation(kind, + spaceHandle, + bodyHandle, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f); + } + + public enum Kind { + WAKE, + SLEEP, + IMPULSE, + TORQUE_IMPULSE, + FORCE, + TORQUE + } + } + private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandle) { terrainSpaceHandlesByUuid.entrySet().removeIf(entry -> { if (entry.getValue().value() != spaceHandle.value()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index c54a3334..a90313aa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -14,11 +14,14 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; @@ -33,8 +36,11 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; @@ -98,7 +104,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) restore, structuralConflicts, requests); + applyBodyTypeRequests(store, identity, runtime, refsThisDrain, restore, requests); applyTargetRequests(store, identity, refsThisDrain, restore, requests); + enqueueRuntimeBodyRequests(store, identity, runtime, refsThisDrain, restore, requests); recordUnsupported(restore, requests); } @@ -358,6 +366,141 @@ private static void applyTargetRequest(@Nonnull Store store, store.putComponent(bodyRef, TargetComponent.getComponentType(), target); } + private static void applyBodyTypeRequests(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (request instanceof BodyTypeRequest typeRequest) { + applyBodyTypeRequest(store, identity, runtime, refsThisDrain, restore, typeRequest); + } + } + } + + private static void enqueueRuntimeBodyRequests(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (request instanceof BodyActivationRequest activationRequest) { + enqueueBodyActivationRequest(identity, runtime, refsThisDrain, restore, activationRequest); + continue; + } + if (request instanceof BodyForceRequest forceRequest) { + enqueueBodyForceRequest(store, identity, runtime, refsThisDrain, restore, forceRequest); + } + } + } + + private static void applyBodyTypeRequest(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull BodyTypeRequest request) { + Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); + if (bodyRef == null) { + restore.recordSoftSkip("Body type request body is missing: " + request.bodyUuid()); + return; + } + DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, + bodyRef, + DynamicsComponent.getComponentType()); + DynamicsComponent updated = dynamics != null ? dynamics.clone() : new DynamicsComponent(); + updated.setBodyType(request.bodyType()); + store.putComponent(bodyRef, DynamicsComponent.getComponentType(), updated); + + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, + request.bodyUuid(), + restore, + "Body type request", + false); + if (binding == null) { + return; + } + binding.backendRuntime().setBodyType(binding.spaceHandle().value(), + binding.bodyHandle().value(), + BackendRuntimeCodes.bodyTypeCode(request.bodyType())); + updateBodyHitMetadata(runtime, binding.bodyHandle(), request.bodyType()); + if (request.activate()) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(binding.spaceHandle(), + binding.bodyHandle())); + } + } + + private static void enqueueBodyActivationRequest( + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull BodyActivationRequest request) { + if (refForUuid(identity, refsThisDrain, request.bodyUuid()) == null) { + restore.recordSoftSkip("Activation request body is missing: " + request.bodyUuid()); + return; + } + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, + request.bodyUuid(), + restore, + "Activation request", + true); + if (binding == null) { + return; + } + if (request.action() == BodyActivationRequest.Action.WAKE) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(binding.spaceHandle(), + binding.bodyHandle())); + } else { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.sleep(binding.spaceHandle(), + binding.bodyHandle())); + } + } + + private static void enqueueBodyForceRequest(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull BodyForceRequest request) { + Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); + if (bodyRef == null) { + restore.recordSoftSkip("Force request body is missing: " + request.bodyUuid()); + return; + } + DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, + bodyRef, + DynamicsComponent.getComponentType()); + if (dynamics == null || dynamics.getBodyType() != PhysicsBodyType.DYNAMIC) { + restore.recordSoftSkip("Force request target is not dynamic: " + request.bodyUuid()); + return; + } + if (!hasFiniteVector(request)) { + restore.recordSoftSkip("Force request contains non-finite values: " + request.bodyUuid()); + return; + } + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, + request.bodyUuid(), + restore, + "Force request", + true); + if (binding == null) { + return; + } + runtime.enqueuePendingBodyOperation(PendingBodyOperation.vector(pendingKind(request), + binding.spaceHandle(), + binding.bodyHandle(), + request.x(), + request.y(), + request.z(), + request.hasOffset(), + request.offsetX(), + request.offsetY(), + request.offsetZ())); + } + private static void applyTerrainRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @@ -604,6 +747,60 @@ private static String structuralOperation(@Nonnull PhysicsStoreRequest request) return request.getClass().getName(); } + @Nullable + private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull String requestName, + boolean requireBound) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + if (bodyHandle == null || spaceHandle == null) { + if (requireBound) { + restore.recordSoftSkip(requestName + " body is unbound: " + bodyUuid); + } + return null; + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (backendRuntime == null) { + restore.recordSoftSkip(requestName + " backend runtime is missing: " + bodyUuid); + return null; + } + return new RuntimeBodyBinding(spaceHandle, bodyHandle, backendRuntime); + } + + private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull PhysicsBodyType bodyType) { + PhysicsRuntimeResource.BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyHandle); + if (metadata != null) { + runtime.putBodyHitMetadata(bodyHandle, + metadata.bodyKey(), + bodyType, + metadata.shapeType()); + } + } + + private static boolean hasFiniteVector(@Nonnull BodyForceRequest request) { + return Float.isFinite(request.x()) + && Float.isFinite(request.y()) + && Float.isFinite(request.z()) + && (!request.hasOffset() + || (Float.isFinite(request.offsetX()) + && Float.isFinite(request.offsetY()) + && Float.isFinite(request.offsetZ()))); + } + + @Nonnull + private static PendingBodyOperation.Kind pendingKind(@Nonnull BodyForceRequest request) { + return switch (request.kind()) { + case IMPULSE -> PendingBodyOperation.Kind.IMPULSE; + case TORQUE_IMPULSE -> PendingBodyOperation.Kind.TORQUE_IMPULSE; + case FORCE -> PendingBodyOperation.Kind.FORCE; + case TORQUE -> PendingBodyOperation.Kind.TORQUE; + }; + } + private static boolean isValidBodyUpsert(@Nonnull BodyUpsertRequest request, @Nonnull PhysicsRestoreStatusResource restore) { if (isNil(request.bodyUuid()) @@ -640,6 +837,9 @@ private static boolean isValidJointUpsert(@Nonnull JointUpsertRequest request, private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) { return request instanceof BodyTargetRequest + || request instanceof BodyActivationRequest + || request instanceof BodyForceRequest + || request instanceof BodyTypeRequest || request instanceof TerrainColliderRequest || request instanceof BodyUpsertRequest || request instanceof BodyRemoveRequest @@ -709,4 +909,9 @@ private record ColliderRow(@Nonnull UUID uuid, @Nonnull Ref ref, @Nonnull ColliderComponent collider) { } + + private record RuntimeBodyBinding(@Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index 72f69c33..32827778 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -12,6 +12,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; @@ -39,6 +40,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) BiConsumer, CommandBuffer> collector = (chunk, _) -> applyTargets(runtime, chunk); store.forEachChunk(systemIndex, collector); + applyPendingBodyOperations(runtime); } private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, @@ -89,6 +91,61 @@ private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, } } + private static void applyPendingBodyOperations(@Nonnull PhysicsRuntimeResource runtime) { + for (PendingBodyOperation operation : runtime.drainPendingBodyOperations()) { + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, operation.spaceHandle()); + if (backendRuntime == null) { + continue; + } + int spaceId = operation.spaceHandle().value(); + long bodyId = operation.bodyHandle().value(); + switch (operation.kind()) { + case WAKE -> backendRuntime.activateBody(spaceId, bodyId); + case SLEEP -> backendRuntime.sleepBody(spaceId, bodyId); + case IMPULSE -> applyImpulse(backendRuntime, spaceId, bodyId, operation, false); + case TORQUE_IMPULSE -> applyImpulse(backendRuntime, spaceId, bodyId, operation, true); + case FORCE -> applyForce(backendRuntime, spaceId, bodyId, operation, false); + case TORQUE -> applyForce(backendRuntime, spaceId, bodyId, operation, true); + } + } + } + + private static void applyImpulse(@Nonnull PhysicsBackendRuntime backendRuntime, + int spaceId, + long bodyId, + @Nonnull PendingBodyOperation operation, + boolean torque) { + backendRuntime.applyBodyImpulse(spaceId, + bodyId, + operation.x(), + operation.y(), + operation.z(), + operation.hasOffset(), + operation.offsetX(), + operation.offsetY(), + operation.offsetZ(), + torque); + backendRuntime.activateBody(spaceId, bodyId); + } + + private static void applyForce(@Nonnull PhysicsBackendRuntime backendRuntime, + int spaceId, + long bodyId, + @Nonnull PendingBodyOperation operation, + boolean torque) { + backendRuntime.applyBodyForce(spaceId, + bodyId, + operation.x(), + operation.y(), + operation.z(), + operation.hasOffset(), + operation.offsetX(), + operation.offsetY(), + operation.offsetZ(), + torque); + backendRuntime.activateBody(spaceId, bodyId); + } + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull BackendSpaceHandle spaceHandle) { final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java new file mode 100644 index 00000000..e06fdc82 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java @@ -0,0 +1,34 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request that explicitly wakes or sleeps a bound PhysicsStore body. + */ +public record BodyActivationRequest(@Nonnull UUID requestUuid, + @Nonnull UUID bodyUuid, + @Nonnull Action action) implements PhysicsStoreRequest { + + public BodyActivationRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(action, "action"); + } + + @Nonnull + public static BodyActivationRequest wake(@Nonnull UUID bodyUuid) { + return new BodyActivationRequest(UUID.randomUUID(), bodyUuid, Action.WAKE); + } + + @Nonnull + public static BodyActivationRequest sleep(@Nonnull UUID bodyUuid) { + return new BodyActivationRequest(UUID.randomUUID(), bodyUuid, Action.SLEEP); + } + + public enum Action { + WAKE, + SLEEP + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java new file mode 100644 index 00000000..f29077a7 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java @@ -0,0 +1,129 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied transient force or impulse request for an already bound PhysicsStore body. + */ +public record BodyForceRequest(@Nonnull UUID requestUuid, + @Nonnull UUID bodyUuid, + @Nonnull Kind kind, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ) implements PhysicsStoreRequest { + + public BodyForceRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(kind, "kind"); + } + + @Nonnull + public static BodyForceRequest impulse(@Nonnull UUID bodyUuid, float x, float y, float z) { + return new BodyForceRequest(UUID.randomUUID(), + bodyUuid, + Kind.IMPULSE, + x, + y, + z, + false, + 0.0f, + 0.0f, + 0.0f); + } + + @Nonnull + public static BodyForceRequest impulseAt(@Nonnull UUID bodyUuid, + float x, + float y, + float z, + float offsetX, + float offsetY, + float offsetZ) { + return new BodyForceRequest(UUID.randomUUID(), + bodyUuid, + Kind.IMPULSE, + x, + y, + z, + true, + offsetX, + offsetY, + offsetZ); + } + + @Nonnull + public static BodyForceRequest torqueImpulse(@Nonnull UUID bodyUuid, float x, float y, float z) { + return new BodyForceRequest(UUID.randomUUID(), + bodyUuid, + Kind.TORQUE_IMPULSE, + x, + y, + z, + false, + 0.0f, + 0.0f, + 0.0f); + } + + @Nonnull + public static BodyForceRequest force(@Nonnull UUID bodyUuid, float x, float y, float z) { + return new BodyForceRequest(UUID.randomUUID(), + bodyUuid, + Kind.FORCE, + x, + y, + z, + false, + 0.0f, + 0.0f, + 0.0f); + } + + @Nonnull + public static BodyForceRequest forceAt(@Nonnull UUID bodyUuid, + float x, + float y, + float z, + float offsetX, + float offsetY, + float offsetZ) { + return new BodyForceRequest(UUID.randomUUID(), + bodyUuid, + Kind.FORCE, + x, + y, + z, + true, + offsetX, + offsetY, + offsetZ); + } + + @Nonnull + public static BodyForceRequest torque(@Nonnull UUID bodyUuid, float x, float y, float z) { + return new BodyForceRequest(UUID.randomUUID(), + bodyUuid, + Kind.TORQUE, + x, + y, + z, + false, + 0.0f, + 0.0f, + 0.0f); + } + + public enum Kind { + IMPULSE, + TORQUE_IMPULSE, + FORCE, + TORQUE + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java new file mode 100644 index 00000000..c788fe7b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java @@ -0,0 +1,28 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request that changes a PhysicsStore body's canonical motion type. + */ +public record BodyTypeRequest(@Nonnull UUID requestUuid, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyType bodyType, + boolean activate) implements PhysicsStoreRequest { + + public BodyTypeRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(bodyType, "bodyType"); + } + + @Nonnull + public static BodyTypeRequest of(@Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyType bodyType, + boolean activate) { + return new BodyTypeRequest(UUID.randomUUID(), bodyUuid, bodyType, activate); + } +} From eb16eda275ff6d77866eefd0501577283c00c55b Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 17:49:18 +0200 Subject: [PATCH 025/534] feat(examples): author force demo through physics store Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 34 ++-- .../systems/RequestDrainSystem.java | 33 ++-- .../systems/TargetBindingSystem.java | 27 +++- .../requests/BodyActivationRequest.java | 2 +- .../requests/BodyForceRequest.java | 2 +- .../examples/commands/ForcesCommand.java | 146 ++++++++++-------- 6 files changed, 147 insertions(+), 97 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index f1958c9e..8ad8ca36 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -351,8 +351,9 @@ public record BodyHitMetadata(@Nullable RigidBodyKey bodyKey, } public record PendingBodyOperation(@Nonnull Kind kind, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle, + @Nonnull UUID bodyUuid, + @Nullable BackendSpaceHandle spaceHandle, + @Nullable BackendBodyHandle bodyHandle, float x, float y, float z, @@ -363,26 +364,28 @@ public record PendingBodyOperation(@Nonnull Kind kind, public PendingBodyOperation { Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(spaceHandle, "spaceHandle"); - Objects.requireNonNull(bodyHandle, "bodyHandle"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); } @Nonnull - public static PendingBodyOperation wake(@Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle) { - return empty(Kind.WAKE, spaceHandle, bodyHandle); + public static PendingBodyOperation wake(@Nonnull UUID bodyUuid, + @Nullable BackendSpaceHandle spaceHandle, + @Nullable BackendBodyHandle bodyHandle) { + return empty(Kind.WAKE, bodyUuid, spaceHandle, bodyHandle); } @Nonnull - public static PendingBodyOperation sleep(@Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle) { - return empty(Kind.SLEEP, spaceHandle, bodyHandle); + public static PendingBodyOperation sleep(@Nonnull UUID bodyUuid, + @Nullable BackendSpaceHandle spaceHandle, + @Nullable BackendBodyHandle bodyHandle) { + return empty(Kind.SLEEP, bodyUuid, spaceHandle, bodyHandle); } @Nonnull public static PendingBodyOperation vector(@Nonnull Kind kind, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle, + @Nonnull UUID bodyUuid, + @Nullable BackendSpaceHandle spaceHandle, + @Nullable BackendBodyHandle bodyHandle, float x, float y, float z, @@ -391,6 +394,7 @@ public static PendingBodyOperation vector(@Nonnull Kind kind, float offsetY, float offsetZ) { return new PendingBodyOperation(kind, + bodyUuid, spaceHandle, bodyHandle, x, @@ -404,9 +408,11 @@ public static PendingBodyOperation vector(@Nonnull Kind kind, @Nonnull private static PendingBodyOperation empty(@Nonnull Kind kind, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle) { + @Nonnull UUID bodyUuid, + @Nullable BackendSpaceHandle spaceHandle, + @Nullable BackendBodyHandle bodyHandle) { return new PendingBodyOperation(kind, + bodyUuid, spaceHandle, bodyHandle, 0.0f, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index a90313aa..aafd49e5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -420,6 +420,11 @@ private static void applyBodyTypeRequest(@Nonnull Store store, "Body type request", false); if (binding == null) { + if (request.activate()) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(request.bodyUuid(), + null, + null)); + } return; } binding.backendRuntime().setBodyType(binding.spaceHandle().value(), @@ -427,7 +432,8 @@ private static void applyBodyTypeRequest(@Nonnull Store store, BackendRuntimeCodes.bodyTypeCode(request.bodyType())); updateBodyHitMetadata(runtime, binding.bodyHandle(), request.bodyType()); if (request.activate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(binding.spaceHandle(), + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(request.bodyUuid(), + binding.spaceHandle(), binding.bodyHandle())); } } @@ -446,16 +452,15 @@ private static void enqueueBodyActivationRequest( request.bodyUuid(), restore, "Activation request", - true); - if (binding == null) { - return; - } + false); if (request.action() == BodyActivationRequest.Action.WAKE) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(binding.spaceHandle(), - binding.bodyHandle())); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(request.bodyUuid(), + binding != null ? binding.spaceHandle() : null, + binding != null ? binding.bodyHandle() : null)); } else { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.sleep(binding.spaceHandle(), - binding.bodyHandle())); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.sleep(request.bodyUuid(), + binding != null ? binding.spaceHandle() : null, + binding != null ? binding.bodyHandle() : null)); } } @@ -485,13 +490,11 @@ private static void enqueueBodyForceRequest(@Nonnull Store store, request.bodyUuid(), restore, "Force request", - true); - if (binding == null) { - return; - } + false); runtime.enqueuePendingBodyOperation(PendingBodyOperation.vector(pendingKind(request), - binding.spaceHandle(), - binding.bodyHandle(), + request.bodyUuid(), + binding != null ? binding.spaceHandle() : null, + binding != null ? binding.bodyHandle() : null, request.x(), request.y(), request.z(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index 32827778..683d1a4f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; @@ -37,10 +38,12 @@ public final class TargetBindingSystem extends TickingSystem @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); BiConsumer, CommandBuffer> collector = (chunk, _) -> applyTargets(runtime, chunk); store.forEachChunk(systemIndex, collector); - applyPendingBodyOperations(runtime); + applyPendingBodyOperations(runtime, restore); } private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, @@ -91,14 +94,28 @@ private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, } } - private static void applyPendingBodyOperations(@Nonnull PhysicsRuntimeResource runtime) { + private static void applyPendingBodyOperations(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore) { for (PendingBodyOperation operation : runtime.drainPendingBodyOperations()) { - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, operation.spaceHandle()); + BackendSpaceHandle spaceHandle = operation.spaceHandle(); + BackendBodyHandle bodyHandle = operation.bodyHandle(); + if (spaceHandle == null || bodyHandle == null) { + spaceHandle = runtime.getBodySpaceHandle(operation.bodyUuid()); + bodyHandle = runtime.getBodyHandle(operation.bodyUuid()); + } + if (spaceHandle == null || bodyHandle == null) { + restore.recordSoftSkip("Pending body operation body is unbound: " + + operation.bodyUuid()); + continue; + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); if (backendRuntime == null) { + restore.recordSoftSkip("Pending body operation backend runtime is missing: " + + operation.bodyUuid()); continue; } - int spaceId = operation.spaceHandle().value(); - long bodyId = operation.bodyHandle().value(); + int spaceId = spaceHandle.value(); + long bodyId = bodyHandle.value(); switch (operation.kind()) { case WAKE -> backendRuntime.activateBody(spaceId, bodyId); case SLEEP -> backendRuntime.sleepBody(spaceId, bodyId); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java index e06fdc82..71f678a6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java @@ -5,7 +5,7 @@ import javax.annotation.Nonnull; /** - * Copied request that explicitly wakes or sleeps a bound PhysicsStore body. + * Copied request that explicitly wakes or sleeps a PhysicsStore body after backend binding. */ public record BodyActivationRequest(@Nonnull UUID requestUuid, @Nonnull UUID bodyUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java index f29077a7..6940a1e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java @@ -5,7 +5,7 @@ import javax.annotation.Nonnull; /** - * Copied transient force or impulse request for an already bound PhysicsStore body. + * Copied transient force or impulse request for a PhysicsStore body after backend binding. */ public record BodyForceRequest(@Nonnull UUID requestUuid, @Nonnull UUID bodyUuid, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index fb0abd7e..caf094ec 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -13,14 +13,20 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsRecipes; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3d; import org.joml.Vector3f; @@ -59,82 +65,100 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d offCenterPosition = new Vector3d(origin).add(2.0, 0.0, 0.0); Vector3d torquePosition = new Vector3d(origin).add(4.0, 0.0, 0.0); Vector3d forcePosition = new Vector3d(origin).add(6.0, 0.0, 0.0); - ForceDemoBodies bodies = new ForceDemoBodies(); - ExamplePhysicsUtils.requireApplied(resource.submitCommands(Math.max(0L, world.getTick()), 8, commands -> { - bodies.central = spawnBox(commands, spaceId, centralPosition); - commands.compose(PhysicsRecipes.applyImpulse(bodies.central.bodyKey(), - new Vector3f(4.0f, 2.0f, 0.0f))); - bodies.offCenter = spawnBox(commands, spaceId, offCenterPosition); - commands.applyBodyImpulse(bodies.offCenter.bodyKey(), 3.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f); - bodies.torque = spawnBox(commands, spaceId, torquePosition); - commands.applyBodyTorqueImpulse(bodies.torque.bodyKey(), 0.0f, 0.0f, 8.0f); - bodies.force = spawnBox(commands, spaceId, forcePosition); - commands.compose(PhysicsRecipes.applyForce(bodies.force.bodyKey(), - new Vector3f(30.0f, 0.0f, 0.0f))); - }), "apply force demo"); - PendingBlockBody central = bodies.requireCentral(); - PendingBlockBody offCenter = bodies.requireOffCenter(); - PendingBlockBody torque = bodies.requireTorque(); - PendingBlockBody force = bodies.requireForce(); + ForceDemoBodies bodies = tryCreatePhysicsStoreDemo(world, + spaceId, + centralPosition, + offCenterPosition, + torquePosition, + forcePosition); + if (bodies == null) { + ctx.sender().sendMessage(Message.raw( + "Cannot spawn force demo because the target space is not bound in PhysicsStore.")); + return CompletableFuture.completedFuture(null); + } + PendingBlockBody central = bodies.central(); + PendingBlockBody offCenter = bodies.offCenter(); + PendingBlockBody torque = bodies.torque(); + PendingBlockBody force = bodies.force(); drawArrow(world, centralPosition, new Vector3d(2.0, 1.0, 0.0), DebugUtils.COLOR_GREEN); drawArrow(world, offCenterPosition, new Vector3d(2.0, 0.0, 0.0), DebugUtils.COLOR_YELLOW); drawArrow(world, torquePosition, new Vector3d(0.0, 0.0, 2.0), DebugUtils.COLOR_MAGENTA); drawArrow(world, forcePosition, new Vector3d(2.0, 0.0, 0.0), DebugUtils.COLOR_CYAN); - ExamplePhysicsUtils.attachRecordedBlockBody(store, time, central); - ExamplePhysicsUtils.attachRecordedBlockBody(store, time, offCenter); - ExamplePhysicsUtils.attachRecordedBlockBody(store, time, torque); - ExamplePhysicsUtils.attachRecordedBlockBody(store, time, force); + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, central); + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, offCenter); + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, torque); + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, force); ctx.sender().sendMessage(Message.raw( "Spawned force demo: central impulse, off-center impulse, torque, and force.")); return CompletableFuture.completedFuture(null); } - private static PendingBlockBody spawnBox(@Nonnull PhysicsCommandRecorder commandBuffer, + @Nullable + private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, @Nonnull SpaceId spaceId, - @Nonnull Vector3d position) { - return ExamplePhysicsUtils.recordBlockBodySpawn(commandBuffer, - spaceId, - position, - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - RigidBodySpawnSettings.material(0.5f, 0.25f)); - } - - private static final class ForceDemoBodies { - - private PendingBlockBody central; - private PendingBlockBody offCenter; - private PendingBlockBody torque; - private PendingBlockBody force; - - @Nonnull - private PendingBlockBody requireCentral() { - return require(central, "central"); + @Nonnull Vector3d centralPosition, + @Nonnull Vector3d offCenterPosition, + @Nonnull Vector3d torquePosition, + @Nonnull Vector3d forcePosition) { + UUID spaceUuid; + try { + spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + } catch (IllegalStateException exception) { + return null; } - - @Nonnull - private PendingBlockBody requireOffCenter() { - return require(offCenter, "off-center"); + if (spaceUuid == null) { + return null; } - @Nonnull - private PendingBlockBody requireTorque() { - return require(torque, "torque"); + List requests = new ArrayList<>(8); + PendingBlockBody central = spawnBox(requests, spaceUuid, spaceId, centralPosition); + requests.add(BodyForceRequest.impulse(central.bodyKey().value(), 4.0f, 2.0f, 0.0f)); + PendingBlockBody offCenter = spawnBox(requests, spaceUuid, spaceId, offCenterPosition); + requests.add(BodyForceRequest.impulseAt(offCenter.bodyKey().value(), + 3.5f, + 0.0f, + 0.0f, + 0.0f, + 0.5f, + 0.5f)); + PendingBlockBody torque = spawnBox(requests, spaceUuid, spaceId, torquePosition); + requests.add(BodyForceRequest.torqueImpulse(torque.bodyKey().value(), 0.0f, 0.0f, 8.0f)); + PendingBlockBody force = spawnBox(requests, spaceUuid, spaceId, forcePosition); + requests.add(BodyForceRequest.force(force.bodyKey().value(), 30.0f, 0.0f, 0.0f)); + try { + PhysicsStoreAccess.enqueueAll(world, requests); + } catch (IllegalStateException exception) { + return null; } + return new ForceDemoBodies(central, offCenter, torque, force); + } - @Nonnull - private PendingBlockBody requireForce() { - return require(force, "force"); - } + private static PendingBlockBody spawnBox(@Nonnull List requests, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d position) { + RigidBodyKey bodyKey = RigidBodyKey.random(); + requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyKey.value(), + ExamplePhysicsUtils.toVector3f(position), + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 1.0f, + RigidBodySpawnSettings.material(0.5f, 0.25f), + null)); + return new PendingBlockBody(bodyKey, + spaceId, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + (float) position.x, + (float) position.y, + (float) position.z, + true); + } - @Nonnull - private static PendingBlockBody require(PendingBlockBody body, @Nonnull String name) { - if (body == null) { - throw new IllegalStateException("Missing " + name + " force demo body"); - } - return body; - } + private record ForceDemoBodies(@Nonnull PendingBlockBody central, + @Nonnull PendingBlockBody offCenter, + @Nonnull PendingBlockBody torque, + @Nonnull PendingBlockBody force) { } private static void drawArrow(@Nonnull World world, From 1acb3ca8b778799ff446ab2b7f83627f8b87bea5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 18:02:39 +0200 Subject: [PATCH 026/534] feat(examples): author grab control through physics store Signed-off-by: Blovien --- .../modules/control/ControlLifecycle.java | 2 +- .../systems/PhysicsControlSessionCleanup.java | 96 +----------- .../PhysicsKinematicControlSystem.java | 128 +--------------- .../PhysicsStoreControlSessionRequests.java | 82 ++++++++++ .../control/PhysicsControlSessions.java | 50 +----- .../plugin/physicsstore/BodyGraphUuids.java | 49 ++++++ .../commands/ExamplePhysicsUtils.java | 17 +-- .../examples/commands/GrabCommand.java | 144 ++++++++++++++---- 8 files changed, 269 insertions(+), 299 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java index 3ffba361..dbe248df 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java @@ -163,7 +163,7 @@ private static void cleanupStoreOnOwnerThread(@Nonnull Store store, } }); for (SessionCleanupTarget target : sessions) { - PhysicsControlSessionCleanup.cleanupAndWait(store, resource, target.session()); + PhysicsControlSessionCleanup.cleanup(store, resource, target.session()); store.removeComponent(target.ref(), sessionType); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java index ed980f05..e74b917b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java @@ -2,17 +2,10 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; public final class PhysicsControlSessionCleanup { @@ -21,104 +14,29 @@ private PhysicsControlSessionCleanup() { public static void cleanup(@Nonnull Store store, @Nonnull PhysicsControlSessionComponent session) { - cleanup(store, PhysicsWorldRuntimeResource.require(store), session, false); + cleanupInternal(store, PhysicsWorldRuntimeResource.require(store), session); } - public static void cleanup(@Nonnull PhysicsWorldResource resource, - @Nonnull PhysicsControlSessionComponent session) { - cleanup(PhysicsWorldRuntimeResource.require(resource), session); - } - - public static void cleanup(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsControlSessionComponent session) { - cleanup(null, resource, session, false); - } - - public static void cleanupAndWait(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsControlSessionComponent session) { - cleanup(null, resource, session, true); - } - - public static void cleanupAndWait(@Nonnull Store store, + public static void cleanup(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull PhysicsControlSessionComponent session) { - cleanup(store, resource, session, true); + cleanupInternal(store, resource, session); } - private static void cleanup(@Nullable Store store, + private static void cleanupInternal(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsControlSessionComponent session, - boolean waitForCompletion) { - if (store != null) { - PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyKey()); - } + @Nonnull PhysicsControlSessionComponent session) { + PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyKey()); if (!session.isActive()) { return; } RigidBodyKey bodyKey = session.getBodyKey(); - RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); - JointKey controlJointKey = session.getControlJointKey(); - boolean restoreBody = bodyKey != null - && resource.hasPublishedOrPendingBodyRegistration(bodyKey); if (bodyKey != null) { resource.clearControlledBody(bodyKey); } - if (shouldReleaseJoint(controlJointKey, session.getSpaceId(), anchorBodyKey, bodyKey) - || restoreBody - || anchorBodyKey != null) { - if (waitForCompletion) { - resource.rejectSynchronousCompletionCallbackWait("cleanup control session"); - } - // Tick cleanup queues this work; subplugin shutdown waits for it. - PhysicsCommandHandle handle = - resource.submitCommands(0L, 5, commands -> { - if (session.getSpaceId() != null && bodyKey != null && anchorBodyKey != null) { - commands.destroyJointBetween(controlJointKey, session.getSpaceId(), anchorBodyKey, bodyKey); - } else if (controlJointKey != null) { - commands.destroyJoint(controlJointKey); - } - if (restoreBody) { - PhysicsBodyType originalBodyType = session.getOriginalBodyType(); - commands.setBodyType(bodyKey, originalBodyType); - if (originalBodyType == PhysicsBodyType.DYNAMIC) { - Vector3f releaseVelocity = session.getReleaseVelocity(); - commands.setBodyVelocity(bodyKey, - releaseVelocity.x, - releaseVelocity.y, - releaseVelocity.z, - 0.0f, - 0.0f, - 0.0f, - true); - } else { - commands.setBodyVelocity(bodyKey, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - true); - } - } - if (anchorBodyKey != null) { - commands.destroyBody(anchorBodyKey); - } - }); - if (waitForCompletion) { - handle.completionSummary().toCompletableFuture().join(); - } - } + PhysicsStoreControlSessionRequests.enqueueRelease(store, session); session.deactivate(); } - - private static boolean shouldReleaseJoint(@Nullable JointKey controlJointKey, - @Nullable SpaceId spaceId, - @Nullable RigidBodyKey anchorBodyKey, - @Nullable RigidBodyKey bodyKey) { - return (spaceId != null && anchorBodyKey != null && bodyKey != null) - || controlJointKey != null; - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 17a1f3c9..7ffa0f96 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -10,7 +10,6 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.tick.EntityTickingSystem; -import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.math.vector.Rotation3f; import com.hypixel.hytale.math.vector.Vector3dUtil; import com.hypixel.hytale.server.core.asset.type.model.config.Model; @@ -19,7 +18,6 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; @@ -30,7 +28,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Collections; @@ -39,7 +36,6 @@ import java.util.Set; import java.util.UUID; import java.util.WeakHashMap; -import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaterniond; @@ -49,8 +45,6 @@ public class PhysicsKinematicControlSystem extends EntityTickingSystem { - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) ); @@ -62,8 +56,7 @@ public class PhysicsKinematicControlSystem extends EntityTickingSystem scratch = ThreadLocal.withInitial(Scratch::new); private static final Vector3f ZERO_VELOCITY = new Vector3f(); private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); - // Anchor updates are copied commands; keep one owner command in flight and at most one latest - // queued target per session anchor. + // Anchor updates are copied PhysicsStore requests; avoid resubmitting unchanged targets. @Nonnull private static final Map, ControlMutationState> STATES_BY_STORE = Collections.synchronizedMap(new WeakHashMap<>()); @@ -105,7 +98,7 @@ public void tick(float dt, Ref targetRef = session.getTargetRef(); if (bodyKey == null || anchorBodyKey == null || (targetRef != null && !targetRef.isValid())) { stateFor(store).clear(anchorBodyKey); - PhysicsControlSessionCleanup.cleanup(PhysicsWorldRuntimeResource.require(store), session); + PhysicsControlSessionCleanup.cleanup(store, session); commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); return; } @@ -165,43 +158,9 @@ public void tick(float dt, return; } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - if (!resource.hasPublishedOrPendingBodyRegistration(bodyKey) - || !resource.hasPublishedOrPendingBodyRegistration(anchorBodyKey)) { - stateFor(store).clear(anchorBodyKey); - PhysicsControlSessionCleanup.cleanup(resource, session); - commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); - return; - } - - if (session.getSpaceId() != null && !resource.hasSpace(session.getSpaceId())) { - stateFor(store).clear(anchorBodyKey); - PhysicsControlSessionCleanup.cleanup(resource, session); - commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); - return; - } - - PhysicsMutationHandle handle = PhysicsMutationHandle.fromCompletion( - "update kinematic control anchor", - null, - resource.submitCommands(0L, 4, commands -> commands - .setBodyType(readyUpdate.anchorBodyKey(), PhysicsBodyType.KINEMATIC) - .setBodyPosition(readyUpdate.anchorBodyKey(), - readyUpdate.target().x, - readyUpdate.target().y, - readyUpdate.target().z, - false) - .setBodyVelocity(readyUpdate.anchorBodyKey(), - readyUpdate.releaseVelocity().x, - readyUpdate.releaseVelocity().y, - readyUpdate.releaseVelocity().z, - 0.0f, - 0.0f, - 0.0f, - true) - .activateBody(readyUpdate.bodyKey())) - .completionSummary()); - state.trackPendingMutation(anchorBodyKey, handle, readyUpdate); + stateFor(store).clear(anchorBodyKey); + PhysicsControlSessionCleanup.cleanup(store, session); + commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); } @Nullable @@ -327,95 +286,31 @@ private void enqueue(@Nonnull ControlAnchorUpdate update) { static final class ControlMutationState { - /* - * Coalescing happens on the tick thread: completion callbacks only clear the in-flight - * marker. The next tick decides whether the latest queued target still belongs to an active - * session and is different enough to submit. - * - * FIXME: Replace this coalescing workaround when strict control scheduling lands. - */ - @Nonnull - private final Object2ObjectMap> pendingMutations = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Object2ObjectMap queuedUpdates = - new Object2ObjectOpenHashMap<>(); @Nonnull private final Object2ObjectMap submittedUpdates = new Object2ObjectOpenHashMap<>(); - synchronized boolean hasPendingMutation(@Nonnull RigidBodyKey bodyKey) { - PhysicsMutationHandle handle = pendingMutations.get(bodyKey); - if (handle == null) { - return false; - } - if (!handle.isDone()) { - return true; - } - pendingMutations.remove(bodyKey); - return false; - } - @Nullable synchronized ControlAnchorUpdate selectReadyUpdate(@Nonnull RigidBodyKey bodyKey, @Nonnull ControlAnchorUpdate currentUpdate) { - PhysicsMutationHandle handle = pendingMutations.get(bodyKey); - if (handle != null && !handle.isDone()) { - queuedUpdates.put(bodyKey, currentUpdate); - return null; - } - if (handle != null) { - pendingMutations.remove(bodyKey); - } - - ControlAnchorUpdate queuedUpdate = queuedUpdates.remove(bodyKey); ControlAnchorUpdate submittedUpdate = submittedUpdates.get(bodyKey); - if (queuedUpdate != null) { - if (!sameTarget(queuedUpdate, submittedUpdate)) { - return queuedUpdate; - } - if (sameTarget(currentUpdate, submittedUpdate)) { - return null; - } + if (sameTarget(currentUpdate, submittedUpdate)) { + return null; } return currentUpdate; } - synchronized void trackPendingMutation(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsMutationHandle handle, - @Nonnull ControlAnchorUpdate submittedUpdate) { - submittedUpdates.put(bodyKey, submittedUpdate); - if (handle.isDone()) { - logImmediateFailure(handle); - return; - } - pendingMutations.put(bodyKey, handle); - handle.completion().whenComplete((ignored, _) -> clear(bodyKey, handle)); - } - synchronized void trackSubmittedRequest(@Nonnull RigidBodyKey bodyKey, @Nonnull ControlAnchorUpdate submittedUpdate) { - pendingMutations.remove(bodyKey); - queuedUpdates.remove(bodyKey); submittedUpdates.put(bodyKey, submittedUpdate); } synchronized void clear(@Nullable RigidBodyKey bodyKey) { if (bodyKey != null) { - pendingMutations.remove(bodyKey); - queuedUpdates.remove(bodyKey); submittedUpdates.remove(bodyKey); } } - private synchronized void clear(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsMutationHandle expectedHandle) { - PhysicsMutationHandle current = pendingMutations.get(bodyKey); - if (current == expectedHandle) { - pendingMutations.remove(bodyKey); - } - } - private static boolean sameTarget(@Nonnull ControlAnchorUpdate first, @Nullable ControlAnchorUpdate second) { return second != null @@ -423,15 +318,6 @@ private static boolean sameTarget(@Nonnull ControlAnchorUpdate first, && Float.compare(first.target().y, second.target().y) == 0 && Float.compare(first.target().z, second.target().z) == 0; } - - private static void logImmediateFailure(@Nonnull PhysicsMutationHandle handle) { - Throwable failure = handle.failure(); - if (failure != null) { - LOGGER.at(Level.WARNING).log( - "Kinematic control anchor update could not be queued: %s", - failure.getMessage()); - } - } } private static final class Scratch { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java new file mode 100644 index 00000000..161f4332 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java @@ -0,0 +1,82 @@ +package dev.hytalemodding.impulse.core.internal.modules.control.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyGraphUuids; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Copied PhysicsStore request batches for kinematic control lifecycle cleanup. + */ +public final class PhysicsStoreControlSessionRequests { + + private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); + private static final Vector3f ZERO = new Vector3f(); + + private PhysicsStoreControlSessionRequests() { + } + + public static void enqueueRelease(@Nonnull Store store, + @Nonnull PhysicsControlSessionComponent session) { + List requests = releaseRequests(session); + if (!requests.isEmpty()) { + PhysicsStoreAccess.enqueueAll(store.getExternalData().getWorld(), requests); + } + } + + @Nonnull + static List releaseRequests( + @Nonnull PhysicsControlSessionComponent session) { + ArrayList requests = new ArrayList<>(4); + JointKey controlJointKey = session.getControlJointKey(); + if (controlJointKey != null) { + requests.add(JointRemoveRequest.of(controlJointKey.value())); + } + + RigidBodyKey bodyKey = session.getBodyKey(); + if (bodyKey != null) { + UUID bodyUuid = bodyKey.value(); + PhysicsBodyType originalBodyType = session.getOriginalBodyType(); + requests.add(BodyTypeRequest.of(bodyUuid, originalBodyType, true)); + requests.add(BodyTargetRequest.of(bodyUuid, + ZERO, + IDENTITY_ROTATION, + releaseVelocity(session), + ZERO, + false, + true, + true)); + } + + RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); + if (anchorBodyKey != null) { + UUID anchorBodyUuid = anchorBodyKey.value(); + requests.add(BodyRemoveRequest.owned(anchorBodyUuid, + BodyGraphUuids.privateOwnedRows(anchorBodyUuid))); + } + return requests; + } + + @Nonnull + private static Vector3f releaseVelocity(@Nonnull PhysicsControlSessionComponent session) { + if (session.getOriginalBodyType() == PhysicsBodyType.DYNAMIC) { + return session.getReleaseVelocity(); + } + return new Vector3f(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 7270a573..9c7e478a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -9,10 +9,10 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; +import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionRequests; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -146,46 +146,11 @@ private static void releaseSession(@Nonnull PhysicsWorldRuntimeResource resource @Nonnull PhysicsControlSessionComponent session) { RigidBodyKey bodyKey = session.getBodyKey(); RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); - JointKey controlJointKey = session.getControlJointKey(); - SpaceId spaceId = session.getSpaceId(); PhysicsKinematicControlSystem.clearMutationState(store, anchorBodyKey); if (bodyKey != null) { resource.clearControlledBody(bodyKey); } - boolean releaseJoint = (spaceId != null && anchorBodyKey != null && bodyKey != null) - || controlJointKey != null; - boolean restoreBody = bodyKey != null && resource.getBodyRegistrationView(bodyKey) != null; - if (releaseJoint || restoreBody || anchorBodyKey != null) { - resource.rejectSynchronousCompletionCallbackWait("release control session"); - /* - * Explicit release/start helpers keep synchronous semantics so command handlers can - * report replacement state immediately. Tick-driven cleanup uses - * PhysicsControlSessionCleanup and does not join the owner lane. - */ - resource.submitCommands(0L, 4, commands -> { - addJointReleaseCommand(commands, controlJointKey, spaceId, anchorBodyKey, bodyKey); - if (restoreBody) { - PhysicsBodyType originalBodyType = session.getOriginalBodyType(); - commands.setBodyType(bodyKey, originalBodyType); - if (originalBodyType == PhysicsBodyType.DYNAMIC) { - Vector3f releaseVelocity = session.getReleaseVelocity(); - commands.setBodyVelocity(bodyKey, - releaseVelocity.x, - releaseVelocity.y, - releaseVelocity.z, - 0.0f, - 0.0f, - 0.0f, - true); - } else { - commands.setBodyVelocity(bodyKey, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, true); - } - } - if (anchorBodyKey != null) { - commands.destroyBody(anchorBodyKey); - } - }).completionSummary().toCompletableFuture().join(); - } + PhysicsStoreControlSessionRequests.enqueueRelease(store, session); session.deactivate(); store.removeComponent(controllerRef, sessionType); @@ -200,15 +165,4 @@ private static void requireAvailable() { } } - private static void addJointReleaseCommand(@Nonnull PhysicsCommandRecorder commands, - @Nullable JointKey controlJointKey, - @Nullable SpaceId spaceId, - @Nullable RigidBodyKey anchorBodyKey, - @Nullable RigidBodyKey bodyKey) { - if (spaceId != null && anchorBodyKey != null && bodyKey != null) { - commands.destroyJointBetween(controlJointKey, spaceId, anchorBodyKey, bodyKey); - } else if (controlJointKey != null) { - commands.destroyJoint(controlJointKey); - } - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java new file mode 100644 index 00000000..08c1584d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java @@ -0,0 +1,49 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Deterministic private row UUIDs for one-body PhysicsStore graphs. + */ +public final class BodyGraphUuids { + + private BodyGraphUuids() { + } + + @Nonnull + public static UUID collider(@Nonnull UUID bodyUuid) { + return row(bodyUuid, "collider"); + } + + @Nonnull + public static UUID shape(@Nonnull UUID bodyUuid) { + return row(bodyUuid, "shape"); + } + + @Nonnull + public static UUID material(@Nonnull UUID bodyUuid) { + return row(bodyUuid, "material"); + } + + @Nonnull + public static UUID filter(@Nonnull UUID bodyUuid) { + return row(bodyUuid, "filter"); + } + + @Nonnull + public static List privateOwnedRows(@Nonnull UUID bodyUuid) { + return List.of(shape(bodyUuid), material(bodyUuid), filter(bodyUuid)); + } + + @Nonnull + private static UUID row(@Nonnull UUID bodyUuid, @Nonnull String rowKind) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(rowKind, "rowKind"); + return UUID.nameUUIDFromBytes(("impulse:physics-body:" + bodyUuid + ':' + rowKind) + .getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 08e5cda0..9e18b6fa 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -32,6 +32,7 @@ import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyGraphUuids; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -47,7 +48,6 @@ import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; @@ -277,10 +277,10 @@ static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - UUID colliderUuid = bodyGraphUuid(bodyUuid, "collider"); - UUID shapeUuid = bodyGraphUuid(bodyUuid, "shape"); - UUID materialUuid = bodyGraphUuid(bodyUuid, "material"); - UUID filterUuid = bodyGraphUuid(bodyUuid, "filter"); + UUID colliderUuid = BodyGraphUuids.collider(bodyUuid); + UUID shapeUuid = BodyGraphUuids.shape(bodyUuid); + UUID materialUuid = BodyGraphUuids.material(bodyUuid); + UUID filterUuid = BodyGraphUuids.filter(bodyUuid); return BodyUpsertRequest.of(bodyUuid, new BodyComponent(spaceUuid, PhysicsBodyKind.BODY, @@ -343,13 +343,6 @@ private static CollisionFilterComponent collisionFilter(@Nonnull RigidBodySpawnS : PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); } - @Nonnull - private static UUID bodyGraphUuid(@Nonnull UUID bodyUuid, - @Nonnull String rowKind) { - return UUID.nameUUIDFromBytes(("impulse:physics-body:" + bodyUuid + ':' + rowKind) - .getBytes(StandardCharsets.UTF_8)); - } - @Nonnull public static PendingBlockBody recordBlockBodySpawn(@Nonnull PhysicsCommandRecorder commands, @Nonnull SpaceId spaceId, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 587ee72e..08feae10 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -12,23 +12,42 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyGraphUuids; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastAllQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -104,10 +123,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - GrabPhysicsState physicsState = createGrabControl(resource, + GrabPhysicsState physicsState = createGrabControl(world, + resource, selectedSpaceId, - selection, - Math.max(0L, world.getTick())); + selection); if (physicsState == null) { ctx.sender().sendMessage(Message.raw("Selected physics body no longer exists.")); return CompletableFuture.completedFuture(null); @@ -131,10 +150,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } @Nullable - private static GrabPhysicsState createGrabControl(@Nonnull PhysicsWorldResource resource, + private static GrabPhysicsState createGrabControl(@Nonnull World world, + @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId selectedSpaceId, - @Nonnull HitSelection selection, - long serverTick) { + @Nonnull HitSelection selection) { RigidBodyStateView selectedState = resource.query(new RigidBodyStateQuery(selection.bodyKey())) .completion() .toCompletableFuture() @@ -143,6 +162,15 @@ private static GrabPhysicsState createGrabControl(@Nonnull PhysicsWorldResource if (selectedState == null) { return null; } + UUID spaceUuid; + try { + spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, selectedSpaceId); + } catch (IllegalStateException exception) { + return null; + } + if (spaceUuid == null) { + return null; + } Vector3f hitPoint = new Vector3f(selection.point()); Vector3f bodyLocalHit = new Vector3f(hitPoint).sub(selectedState.pose().position()); @@ -151,32 +179,92 @@ private static GrabPhysicsState createGrabControl(@Nonnull PhysicsWorldResource RigidBodyKey anchorBodyKey = RigidBodyKey.random(); JointKey controlJointKey = JointKey.random(); - boolean rejected = resource.submitCommands(serverTick, commands -> { - commands.spawnBody(anchorBodyKey, spawn -> spawn - .space(selectedSpaceId) - .sphere(0.08f) - .mass(1.0f) - .kinematic() - .position(hitPoint) - .settings(RigidBodySpawnSettings.defaults().withSensor(true).withCollisionFilter(1, 0)) - .temporary() - .runtimeOnly()); - commands.joint(controlJointKey, joint -> joint - .space(selectedSpaceId) - .bodies(anchorBodyKey, selection.bodyKey()) - .point(new Vector3f(), bodyLocalHit)); - commands.activateBody(selection.bodyKey()); - }) - .firstRejected() - .toCompletableFuture() - .join() - .isPresent(); - if (rejected) { + List requests = new ArrayList<>(3); + requests.add(anchorBodyUpsertRequest(spaceUuid, anchorBodyKey.value(), hitPoint)); + requests.add(JointUpsertRequest.of(controlJointKey.value(), + controlJoint(spaceUuid, anchorBodyKey, selection.bodyKey(), bodyLocalHit))); + requests.add(BodyActivationRequest.wake(selection.bodyKey().value())); + try { + PhysicsStoreAccess.enqueueAll(world, requests); + } catch (IllegalStateException exception) { return null; } return new GrabPhysicsState(selectedState.bodyType(), anchorBodyKey, controlJointKey, hitPoint); } + @Nonnull + private static BodyUpsertRequest anchorBodyUpsertRequest(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f hitPoint) { + UUID colliderUuid = BodyGraphUuids.collider(bodyUuid); + UUID shapeUuid = BodyGraphUuids.shape(bodyUuid); + UUID materialUuid = BodyGraphUuids.material(bodyUuid); + UUID filterUuid = BodyGraphUuids.filter(bodyUuid); + return BodyUpsertRequest.of(bodyUuid, + new BodyComponent(spaceUuid, + PhysicsBodyKind.TEMPORARY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY), + new DynamicsComponent(PhysicsBodyType.KINEMATIC, + 1.0f, + 0.0f, + 0.0f, + false), + initialAnchorTarget(hitPoint), + colliderUuid, + new ColliderComponent(bodyUuid, + shapeUuid, + materialUuid, + filterUuid, + new Vector3f(), + new Quaternionf(), + true), + shapeUuid, + new ShapeComponent(ShapeType.SPHERE, + 0.0f, + 0.0f, + 0.0f, + 0.08f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + materialUuid, + new MaterialComponent(0.5f, 0.0f), + filterUuid, + new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, 0)); + } + + @Nonnull + private static TargetComponent initialAnchorTarget(@Nonnull Vector3f hitPoint) { + TargetComponent target = new TargetComponent(); + target.setActive(false); + target.setPosition(hitPoint); + target.setRotation(new Quaternionf()); + target.setLinearVelocity(new Vector3f()); + target.setAngularVelocity(new Vector3f()); + target.setTransformEnabled(true); + target.setVelocityEnabled(false); + target.setActivate(true); + return target; + } + + @Nonnull + private static JointComponent controlJoint(@Nonnull UUID spaceUuid, + @Nonnull RigidBodyKey anchorBodyKey, + @Nonnull RigidBodyKey bodyKey, + @Nonnull Vector3f bodyLocalHit) { + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(spaceUuid); + joint.setBodyAUuid(anchorBodyKey.value()); + joint.setBodyBUuid(bodyKey.value()); + joint.setType(JointType.POINT); + joint.setAnchorA(new Vector3f()); + joint.setAnchorB(bodyLocalHit); + joint.setAxis(new Vector3f()); + joint.setEnabled(true); + return joint; + } + @Nullable private static HitSelection findControllableHit(@Nonnull PhysicsWorldResource resource, @Nonnull Store store, From 08a238ea3be006ba6e7abe16743ff9154119c299 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 18:16:04 +0200 Subject: [PATCH 027/534] refactor(core): index physics store projections Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 7 + .../PhysicsProjectionIndexResource.java | 149 ++++++++++++++++++ .../PhysicsBodyAttachmentIndexSystem.java | 73 +++++++++ .../systems/sync/PhysicsSyncSystem.java | 15 +- ...csDetachedVisualMaterializationSystem.java | 38 +++++ .../PhysicsBodyAttachmentComponent.java | 8 +- 6 files changed, 274 insertions(+), 16 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index b2d26854..f9bc5dc0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerLaneScheduler; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; @@ -97,6 +98,9 @@ public final class ImpulsePlugin extends JavaPlugin { @Getter private ResourceType physicsRuntimeProfilingResourceType; + @Getter + private ResourceType physicsProjectionIndexResourceType; + @Getter private ResourceType physicsOwnerResourceType; @@ -312,6 +316,9 @@ private void registerComponents() { physicsRuntimeProfilingResourceType = entityRegistry.registerResource( PhysicsRuntimeProfilingResource.class, PhysicsRuntimeProfilingResource::new); + physicsProjectionIndexResourceType = entityRegistry.registerResource( + PhysicsProjectionIndexResource.class, + PhysicsProjectionIndexResource::new); ConfiguredPositiveInt ownerPoolSize = configuredPositiveIntDetails(OWNER_POOL_SIZE_PROPERTY, PhysicsOwnerLaneScheduler.DEFAULT_POOL_SIZE); logOwnerPoolSize(ownerPoolSize); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java new file mode 100644 index 00000000..1caf3d4b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -0,0 +1,149 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.ImpulsePlugin; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime-only EntityStore projection index for authoritative PhysicsStore attachments. + */ +public final class PhysicsProjectionIndexResource implements Resource { + + private final Map>> bodyAttachments = + new Object2ObjectOpenHashMap<>(); + private final Map> generatedVisualProxies = + new Object2ObjectOpenHashMap<>(); + + public synchronized void registerAttachment(@Nonnull UUID bodyUuid, + @Nonnull Ref attachment) { + bodyAttachments.computeIfAbsent(bodyUuid, _ -> new ObjectOpenHashSet<>()) + .add(attachment); + } + + public synchronized void unregisterAttachment(@Nonnull UUID bodyUuid, + @Nonnull Ref attachment) { + Set> attachments = bodyAttachments.get(bodyUuid); + if (attachments == null) { + return; + } + attachments.remove(attachment); + if (attachments.isEmpty()) { + bodyAttachments.remove(bodyUuid); + } + } + + @Nonnull + public Collection> getAttachments(@Nonnull UUID bodyUuid) { + List> liveAttachments = new ArrayList<>(); + synchronized (this) { + Set> attachments = bodyAttachments.get(bodyUuid); + if (attachments == null || attachments.isEmpty()) { + return List.of(); + } + for (Iterator> iterator = attachments.iterator(); iterator.hasNext();) { + Ref attachment = iterator.next(); + if (attachment != null && attachment.isValid()) { + liveAttachments.add(attachment); + } else { + iterator.remove(); + } + } + if (attachments.isEmpty()) { + bodyAttachments.remove(bodyUuid); + } + } + return liveAttachments; + } + + public boolean hasAttachments(@Nonnull UUID bodyUuid) { + synchronized (this) { + Set> attachments = bodyAttachments.get(bodyUuid); + if (attachments == null || attachments.isEmpty()) { + return false; + } + boolean hasLiveAttachment = false; + for (Iterator> iterator = attachments.iterator(); iterator.hasNext();) { + Ref attachment = iterator.next(); + if (attachment != null && attachment.isValid()) { + hasLiveAttachment = true; + } else { + iterator.remove(); + } + } + if (attachments.isEmpty()) { + bodyAttachments.remove(bodyUuid); + } + return hasLiveAttachment; + } + } + + @Nullable + public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid) { + synchronized (this) { + Ref proxy = generatedVisualProxies.get(bodyUuid); + if (proxy != null && proxy.isValid()) { + return proxy; + } + generatedVisualProxies.remove(bodyUuid); + return null; + } + } + + public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nonnull Ref proxy) { + synchronized (this) { + generatedVisualProxies.put(bodyUuid, proxy); + } + } + + public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid) { + synchronized (this) { + generatedVisualProxies.remove(bodyUuid); + } + } + + public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nonnull Ref expectedProxy) { + synchronized (this) { + Ref proxy = generatedVisualProxies.get(bodyUuid); + if (sameRef(proxy, expectedProxy)) { + generatedVisualProxies.remove(bodyUuid); + } + } + } + + @Nonnull + @Override + public PhysicsProjectionIndexResource clone() { + PhysicsProjectionIndexResource copy = new PhysicsProjectionIndexResource(); + synchronized (this) { + for (Map.Entry>> entry : bodyAttachments.entrySet()) { + copy.bodyAttachments.put(entry.getKey(), new ObjectOpenHashSet<>(entry.getValue())); + } + copy.generatedVisualProxies.putAll(generatedVisualProxies); + } + return copy; + } + + public static ResourceType getResourceType() { + return ImpulsePlugin.get().getPhysicsProjectionIndexResourceType(); + } + + private static boolean sameRef(@Nullable Ref first, + @Nonnull Ref second) { + return first != null && (first == second || first.equals(second)); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index ba74fa6d..75d43e2a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -7,9 +7,12 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefChangeSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -29,6 +32,7 @@ public void onComponentAdded(@Nonnull Ref ref, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { if (!component.usesLegacyBodyKey()) { + registerPhysicsStoreAttachment(ref, component, commandBuffer); return; } PhysicsWorldRuntimeResource.require( @@ -46,8 +50,12 @@ public void onComponentSet(@Nonnull Ref ref, boolean oldLegacy = oldComponent.usesLegacyBodyKey(); boolean newLegacy = newComponent.usesLegacyBodyKey(); if (!oldLegacy && !newLegacy) { + updatePhysicsStoreAttachment(ref, oldComponent, newComponent, commandBuffer); return; } + if (!oldLegacy) { + unregisterPhysicsStoreAttachment(ref, oldComponent, commandBuffer); + } PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require( commandBuffer.getResource(PhysicsWorldResource.getResourceType())); if (oldLegacy && (!newLegacy || !oldComponent.getBodyKey().equals(newComponent.getBodyKey()))) { @@ -56,6 +64,9 @@ public void onComponentSet(@Nonnull Ref ref, if (newLegacy && (!oldLegacy || !oldComponent.getBodyKey().equals(newComponent.getBodyKey()))) { resource.registerBodyAttachment(newComponent.getBodyKey(), ref); } + if (!newLegacy) { + registerPhysicsStoreAttachment(ref, newComponent, commandBuffer); + } resource.clearBodySyncState(ref); } @@ -65,6 +76,7 @@ public void onComponentRemoved(@Nonnull Ref ref, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { if (!component.usesLegacyBodyKey()) { + unregisterPhysicsStoreAttachment(ref, component, commandBuffer); return; } PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require( @@ -73,6 +85,67 @@ public void onComponentRemoved(@Nonnull Ref ref, resource.clearBodySyncState(ref); } + private static void updatePhysicsStoreAttachment(@Nonnull Ref ref, + @Nonnull PhysicsBodyAttachmentComponent oldComponent, + @Nonnull PhysicsBodyAttachmentComponent newComponent, + @Nonnull CommandBuffer commandBuffer) { + UUID oldUuid = oldComponent.getPhysicsBodyUuid(); + UUID newUuid = newComponent.getPhysicsBodyUuid(); + if (oldUuid == null || newUuid == null) { + return; + } + boolean sameUuid = oldUuid.equals(newUuid); + boolean oldGeneratedProxy = oldComponent.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY; + boolean newGeneratedProxy = newComponent.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY; + if (sameUuid && oldGeneratedProxy == newGeneratedProxy) { + return; + } + PhysicsProjectionIndexResource resource = commandBuffer.getResource( + PhysicsProjectionIndexResource.getResourceType()); + if (!sameUuid) { + resource.unregisterAttachment(oldUuid, ref); + resource.registerAttachment(newUuid, ref); + } + if (!sameUuid || oldGeneratedProxy != newGeneratedProxy) { + if (oldGeneratedProxy) { + resource.clearGeneratedVisualProxy(oldUuid, ref); + } + if (newGeneratedProxy) { + resource.setGeneratedVisualProxy(newUuid, ref); + } + } + } + + private static void registerPhysicsStoreAttachment(@Nonnull Ref ref, + @Nonnull PhysicsBodyAttachmentComponent component, + @Nonnull CommandBuffer commandBuffer) { + UUID bodyUuid = component.getPhysicsBodyUuid(); + if (bodyUuid == null) { + return; + } + PhysicsProjectionIndexResource resource = commandBuffer.getResource( + PhysicsProjectionIndexResource.getResourceType()); + resource.registerAttachment(bodyUuid, ref); + if (component.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { + resource.setGeneratedVisualProxy(bodyUuid, ref); + } + } + + private static void unregisterPhysicsStoreAttachment(@Nonnull Ref ref, + @Nonnull PhysicsBodyAttachmentComponent component, + @Nonnull CommandBuffer commandBuffer) { + UUID bodyUuid = component.getPhysicsBodyUuid(); + if (bodyUuid == null) { + return; + } + PhysicsProjectionIndexResource resource = commandBuffer.getResource( + PhysicsProjectionIndexResource.getResourceType()); + resource.unregisterAttachment(bodyUuid, ref); + if (component.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { + resource.clearGeneratedVisualProxy(bodyUuid, ref); + } + } + @Nonnull @Override public ComponentType componentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 4bc9215d..53988a18 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -3,9 +3,7 @@ import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -317,17 +315,8 @@ private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transf private static void clearMissingPhysicsStoreAttachment(@Nonnull Ref entityRef, @Nonnull PhysicsBodyAttachmentComponent attachment, @Nonnull CommandBuffer commandBuffer) { - if (!attachment.shouldRemoveEntityWhenBodyMissing()) { - return; - } - commandBuffer.removeEntity(entityRef, - newHolder(commandBuffer.getStore()), - RemoveReason.REMOVE); - } - - @Nonnull - private static Holder newHolder(@Nonnull Store store) { - return store.getRegistry().newHolder(); + // PhysicsStore snapshot publication is intentionally one completed frame behind request + // ingestion. Absence from the latest frame is not enough evidence that the body row is gone. } private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 033a53d3..f578dc70 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.NonSerialized; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -67,6 +68,9 @@ */ public class PhysicsDetachedVisualMaterializationSystem extends TickingSystem { + private static final ComponentType GENERATED_PROXY_TYPE = + GeneratedVisualProxyComponent.getComponentType(); + private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) @@ -101,6 +105,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { private void tickMaterialization(@Nonnull Store store, @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { if (hasAuthoritativePhysicsStore(store)) { + MaterializationState state = stateFor(store); + clearCachedMaterializationState(state); + removeLegacyGeneratedVisualProxies(store); return; } MaterializationState state = stateFor(store); @@ -161,6 +168,37 @@ private static boolean hasAuthoritativePhysicsStore(@Nonnull Store } } + private static void clearCachedMaterializationState(@Nonnull MaterializationState state) { + state.cachedInterests = List.of(); + state.cachedMaterializationTargets.clear(); + state.visualInterestRefreshCooldown = 0; + state.materializationCandidateRefreshCooldown = 0; + state.materializedVisibilityCheckCooldown = 0; + } + + private static void removeLegacyGeneratedVisualProxies(@Nonnull Store store) { + ComponentType attachmentType = + PhysicsBodyAttachmentComponent.getComponentType(); + store.forEachEntityParallel(attachmentType, + (index, archetypeChunk, commandBuffer) -> { + PhysicsBodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + attachmentType); + if (attachment == null + || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY + || !attachment.usesLegacyBodyKey()) { + return; + } + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + }); + store.forEachEntityParallel(GENERATED_PROXY_TYPE, + (index, archetypeChunk, commandBuffer) -> { + if (archetypeChunk.getComponent(index, attachmentType) != null) { + return; + } + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + }); + } + private static boolean shouldPauseForRestore(@Nonnull MaterializationState state, @Nonnull PersistentPhysicsWorldResource persistent) { long restoreGeneration = persistent.runtimeRestoreGeneration(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java index 3d7c2686..7c9a63ee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java @@ -21,10 +21,12 @@ import org.joml.Vector3f; /** - * Runtime attachment from a Hytale entity to an Impulse body key. + * Runtime attachment from a Hytale entity to an Impulse body. * - *

The entity is a gameplay or visual representation. It does not own backend - * body destruction; removing the entity only removes this attachment unless the + *

Authoritative PhysicsStore rows use {@code PhysicsBodyUuid}; {@code BodyId} + * and {@code SpaceId} remain compatibility metadata for legacy rows. The entity + * is a gameplay or visual representation. It does not own backend body + * destruction; removing the entity only removes this attachment unless the * lifecycle marks it as a disposable Impulse visual.

*/ public class PhysicsBodyAttachmentComponent implements Component { From c8d90846f4e89e2d86b81854502a1735a782927d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 18:25:45 +0200 Subject: [PATCH 028/534] fix(core): block legacy physics mutations under physics store Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 43 +++++++++++++++++++ .../PhysicsStoreEarlyPluginProbe.java | 9 ++++ 2 files changed, 52 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 279ee027..d8aade51 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -40,6 +40,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; import dev.hytalemodding.impulse.core.internal.physicsstore.queries.PhysicsStoreQueryBridge; +import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -215,6 +216,7 @@ public PhysicsCommandHandle submitCommands(long submittedServerTick, @Nonnull public PhysicsCommandHandle submitRecordedCommands(@Nonnull MutablePhysicsCommandContext context) { + requireLegacyMutationAllowed("submit recorded physics commands"); Objects.requireNonNull(context, "context"); RecordedPhysicsCommandBatch batch = context.freezeInternal(lifecycleState.nextCommandBatchSequence()); @@ -282,6 +284,20 @@ public void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { ownerGateway.assertCanAccessLiveBackendDirectly(operation); } + private void requireLegacyMutationAllowed(@Nonnull String operation) { + if (!isAuthoritativePhysicsStoreActive()) { + return; + } + throw new IllegalStateException("Legacy PhysicsWorldResource mutation is disabled while " + + "authoritative PhysicsStore is active: " + operation + + ". Route this operation through PhysicsStore requests or a PhysicsStore-backed " + + "compatibility bridge."); + } + + private boolean isAuthoritativePhysicsStoreActive() { + return PhysicsStoreEarlyPluginProbe.isAvailable(); + } + public void runOwnerMutation(@Nonnull String operation, @Nonnull PhysicsOwnerMutation mutation) { ownerGateway.run(operation, mutation); @@ -317,6 +333,7 @@ public PhysicsWorldSettings getWorldSettings() { @Override public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { + requireLegacyMutationAllowed("set physics world settings"); PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); runOwnerMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); } @@ -325,6 +342,7 @@ public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { @Override public PhysicsMutationHandle setWorldSettingsAsync( @Nonnull PhysicsWorldSettings settings) { + requireLegacyMutationAllowed("set physics world settings"); PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); return enqueueOwnerMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); @@ -361,6 +379,7 @@ public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull SpaceId spaceId, @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { + requireLegacyMutationAllowed("create physics space"); callOwner("create physics space", () -> createSpaceDirect(backendId, spaceId, worldName, settings)); return spaceId; @@ -381,6 +400,7 @@ public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backen @Nonnull SpaceId spaceId, @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { + requireLegacyMutationAllowed("create physics space"); return enqueueOwnerMutation("create physics space", spaceId, () -> createSpaceDirect(backendId, spaceId, worldName, settings)); @@ -628,6 +648,7 @@ public WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { + requireLegacyMutationAllowed("rebuild world collision"); requireWorldCollisionLifecycleEnabled(); return callOwner("rebuild world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); @@ -649,6 +670,7 @@ public WorldCollisionBuildStats refreshWorldCollisionAround(@Nonnull World world @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { + requireLegacyMutationAllowed("refresh world collision"); requireWorldCollisionLifecycleEnabled(); return callOwner("refresh world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); @@ -671,6 +693,7 @@ public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World worl @Nonnull Iterable centers, int radius, long tick) { + requireLegacyMutationAllowed("ensure world collision"); Objects.requireNonNull(centers, "centers"); requireWorldCollisionLifecycleEnabled(); return callOwner("ensure world collision", () -> { @@ -690,6 +713,7 @@ public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World worl @Override public int clearWorldCollision(@Nonnull SpaceId spaceId) { + requireLegacyMutationAllowed("clear world collision"); return callOwner("clear world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); return collisionRuntime.clear(space); @@ -707,6 +731,9 @@ public WorldCollisionStats getWorldCollisionStats() { } public void disableWorldCollisionLifecycle() { + if (isAuthoritativePhysicsStoreActive()) { + return; + } try { runOwnerMutation("disable world collision lifecycle", this::disableWorldCollisionLifecycleDirect); } catch (RejectedExecutionException ignored) { @@ -817,6 +844,7 @@ public void removeSpace(@Nonnull SpaceId spaceId) { @Override public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { + requireLegacyMutationAllowed("remove physics space"); runOwnerMutation("remove physics space", () -> removeSpaceDirect(spaceId, worldName)); } @@ -824,6 +852,7 @@ public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { @Override public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, @Nonnull String worldName) { + requireLegacyMutationAllowed("remove physics space"); return enqueueOwnerMutation("remove physics space", spaceId, () -> removeSpaceDirect(spaceId, worldName)); @@ -857,12 +886,14 @@ private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldNa @Override public void clearAllSpaces(@Nonnull String worldName) { + requireLegacyMutationAllowed("clear physics spaces"); runOwnerMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); } @Nonnull @Override public PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName) { + requireLegacyMutationAllowed("clear physics spaces"); return enqueueOwnerMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); } @@ -897,6 +928,7 @@ private static RuntimeException collectFailure(@Nullable RuntimeException failur */ @Nonnull public PhysicsRuntimeResetResult resetRuntimeStateKeepingSpaces(@Nonnull String worldName) { + requireLegacyMutationAllowed("reset physics runtime state"); return callOwner("reset physics runtime state", () -> resetRuntimeStateKeepingSpacesDirect(worldName)); } @@ -924,6 +956,7 @@ public PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { @Override public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { + requireLegacyMutationAllowed("set physics space settings"); PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); runOwnerMutation("set physics space settings", () -> setSpaceSettingsDirect(spaceId, requested)); } @@ -932,6 +965,7 @@ public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSett @Override public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { + requireLegacyMutationAllowed("set physics space settings"); PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); return enqueueOwnerMutation("set physics space settings", spaceId, @@ -999,22 +1033,26 @@ private RigidBodyKey addBodyDirect(@Nonnull RigidBodyKey bodyKey, @Override public void destroyBody(@Nonnull RigidBodyKey bodyKey) { + requireLegacyMutationAllowed("destroy physics body"); destroyBody(bodyKey, true); } @Nonnull @Override public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey) { + requireLegacyMutationAllowed("destroy physics body"); return destroyBodyAsync(bodyKey, true); } public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { + requireLegacyMutationAllowed("destroy physics body"); runOwnerMutation("destroy physics body", () -> destroyBodyDirect(bodyKey, removeFromSpace)); } @Nonnull public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { + requireLegacyMutationAllowed("destroy physics body"); return enqueueOwnerMutation("destroy physics body", bodyKey, () -> destroyBodyDirect(bodyKey, removeFromSpace)); @@ -1154,6 +1192,7 @@ private JointKey addJointDirect(@Nonnull JointKey jointKey, } public boolean removeJoint(@Nonnull JointKey jointKey) { + requireLegacyMutationAllowed("remove physics joint"); return callOwner("remove physics joint", () -> removeJointDirect(jointKey)); } @@ -1318,12 +1357,14 @@ public void clearSyntheticVisualInterests() { @Override public void clearBodies() { + requireLegacyMutationAllowed("clear physics bodies"); runOwnerMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); } @Nonnull @Override public PhysicsMutationHandle clearBodiesAsync() { + requireLegacyMutationAllowed("clear physics bodies"); return enqueueOwnerMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); } @@ -1437,12 +1478,14 @@ public void clearChunkBoundaryPauseState(@Nonnull RigidBodyKey bodyKey) { } public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { + requireLegacyMutationAllowed("clear physics body runtime state"); runOwnerMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyKey)); } @Nonnull public PhysicsMutationHandle clearBodyRuntimeStateAsync( @Nonnull RigidBodyKey bodyKey) { + requireLegacyMutationAllowed("clear physics body runtime state"); return enqueueOwnerMutation("clear physics body runtime state", bodyKey, () -> clearBodyRuntimeStateDirect(bodyKey)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java index be2bcf6f..782b71c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java @@ -34,6 +34,15 @@ public static void requireAvailable() { requireField(WorldConfigSaveSystem.class, WORLD_RESOURCE_SAVE_MARKER); } + public static boolean isAvailable() { + try { + requireAvailable(); + return true; + } catch (IllegalStateException exception) { + return false; + } + } + @Nonnull private static Class requireClass(@Nonnull String className) { try { From 4cf92bc0837a94c14bd4dbda0c4de0535e19f63a Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 18:41:45 +0200 Subject: [PATCH 029/534] feat(core): author physics store spaces through requests Signed-off-by: Blovien --- ...hysicsSpaceCompatibilityIndexResource.java | 17 ++ .../systems/RequestDrainSystem.java | 201 ++++++++++++++++++ .../systems/SpaceBindingSystem.java | 6 +- .../PhysicsWorldRuntimeResource.java | 72 +++++++ .../physicsstore/PhysicsStoreAccess.java | 201 ++++++++++++++++++ .../requests/SpaceRemoveRequest.java | 22 ++ .../requests/SpaceSettingsRequest.java | 39 ++++ .../requests/SpaceUpsertRequest.java | 53 +++++ 8 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java index a8074fff..197f4329 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java @@ -7,6 +7,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.ArrayList; +import java.util.Collection; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -41,6 +43,10 @@ public void putSpace(@Nonnull SpaceId spaceId, @Nonnull UUID spaceUuid) { } } + public boolean hasSpace(@Nonnull SpaceId spaceId) { + return spaceUuidsByCompatId.containsKey(spaceId.value()); + } + @Nullable public UUID getSpaceUuid(@Nonnull SpaceId spaceId) { return spaceUuidsByCompatId.get(spaceId.value()); @@ -59,6 +65,17 @@ public void removeBySpaceUuid(@Nonnull UUID spaceUuid) { } } + @Nonnull + public Collection spaceIds() { + ArrayList ids = new ArrayList<>(spaceUuidsByCompatId.size()); + spaceUuidsByCompatId.keySet().forEach((int value) -> ids.add(new SpaceId(value))); + return ids; + } + + public int size() { + return spaceUuidsByCompatId.size(); + } + public void clear() { spaceUuidsByCompatId.clear(); compatIdsBySpaceUuid.clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index aafd49e5..ce801c08 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -14,7 +14,9 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; @@ -22,6 +24,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; @@ -33,9 +36,11 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; @@ -45,6 +50,9 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -81,6 +89,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsTerrainPayloadResource terrainPayloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); Set structuralConflicts = structuralConflicts(requests, restore); @@ -95,6 +105,28 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) restore, structuralConflicts, requests); + applySpaceRemovals(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + structuralConflicts, + requests); + applySpaceUpserts(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + structuralConflicts, + requests); + applySpaceSettings(store, + identity, + refsThisDrain, + restore, + structuralConflicts, + requests); applyUpserts(store, systemIndex, identity, @@ -110,6 +142,64 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) recordUnsupported(restore, requests); } + private static void applySpaceRemovals(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set structuralConflicts, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (request instanceof SpaceRemoveRequest spaceRequest + && !structuralConflicts.contains(spaceRequest.spaceUuid())) { + applySpaceRemove(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + spaceRequest); + } + } + } + + private static void applySpaceUpserts(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set structuralConflicts, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (request instanceof SpaceUpsertRequest spaceRequest + && !structuralConflicts.contains(spaceRequest.spaceUuid())) { + applySpaceUpsert(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + spaceRequest); + } + } + } + + private static void applySpaceSettings(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set structuralConflicts, + @Nonnull List requests) { + for (PhysicsStoreRequest request : requests) { + if (request instanceof SpaceSettingsRequest settingsRequest + && !structuralConflicts.contains(settingsRequest.spaceUuid())) { + applySpaceSettings(store, identity, refsThisDrain, restore, settingsRequest); + } + } + } + private static void applyRemovals(@Nonnull Store store, int systemIndex, @Nonnull PhysicsIdentityIndexResource identity, @@ -218,6 +308,71 @@ private static void recordUnsupported(@Nonnull PhysicsRestoreStatusResource rest } } + private static void applySpaceRemove(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull SpaceRemoveRequest request) { + UUID spaceUuid = request.spaceUuid(); + BackendSpaceHandle handle = runtime.getSpaceHandle(spaceUuid); + if (handle != null && !removeSpaceBackend(runtime, identity, restore, spaceUuid, handle)) { + return; + } + compatibility.removeBySpaceUuid(spaceUuid); + removeRow(store, + identity, + refsThisDrain, + new ObjectOpenHashSet<>(), + spaceUuid, + refForUuid(identity, refsThisDrain, spaceUuid)); + } + + private static void applySpaceUpsert(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull SpaceUpsertRequest request) { + if (isNil(request.spaceUuid())) { + restore.recordSoftSkip("Space upsert contains nil UUID: " + request.spaceUuid()); + return; + } + if (request.space().getBackendIdValue().isBlank()) { + restore.recordSoftSkip("Space upsert backend id is blank: " + request.spaceUuid()); + return; + } + if (runtime.getSpaceHandle(request.spaceUuid()) != null) { + restore.recordSoftSkip("Space upsert target is already bound: " + request.spaceUuid()); + return; + } + Ref ref = ensureRow(store, identity, refsThisDrain, request.spaceUuid()); + store.putComponent(ref, SpaceComponent.getComponentType(), request.space().clone()); + store.putComponent(ref, + WorldCollisionComponent.getComponentType(), + request.worldCollision().clone()); + compatibility.putSpace(request.compatibilitySpaceId(), request.spaceUuid()); + SpaceId.reserveAtLeast(request.compatibilitySpaceId().value()); + } + + private static void applySpaceSettings(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull SpaceSettingsRequest request) { + Ref ref = refForUuid(identity, refsThisDrain, request.spaceUuid()); + if (ref == null) { + restore.recordSoftSkip("Space settings request target is missing: " + + request.spaceUuid()); + return; + } + store.putComponent(ref, + WorldCollisionComponent.getComponentType(), + request.worldCollision().clone()); + } + private static void applyBodyRemove(@Nonnull Store store, int systemIndex, @Nonnull PhysicsIdentityIndexResource identity, @@ -632,6 +787,31 @@ private static void removeJointBackend(@Nonnull PhysicsRuntimeResource runtime, runtime.removeJointHandle(jointUuid); } + private static boolean removeSpaceBackend(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull UUID spaceUuid, + @Nonnull BackendSpaceHandle handle) { + BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + PhysicsBackendRuntime backendRuntime = backendId != null + ? runtime.getRuntime(backendId) + : null; + if (backendRuntime == null) { + restore.recordSoftSkip("Space remove backend runtime is missing: " + spaceUuid); + return false; + } + int bodyCount = backendRuntime.bodyCount(handle.value()); + int jointCount = backendRuntime.jointCount(handle.value()); + if (bodyCount > 0 || jointCount > 0) { + restore.recordSoftSkip("Space remove target is not empty: " + spaceUuid); + return false; + } + backendRuntime.destroySpace(handle.value()); + identity.removeSpaceHandle(handle); + runtime.removeSpaceHandle(spaceUuid); + return true; + } + @Nullable private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nullable BackendSpaceHandle spaceHandle) { @@ -736,6 +916,15 @@ private static UUID structuralUuid(@Nonnull PhysicsStoreRequest request) { if (request instanceof JointRemoveRequest jointRequest) { return jointRequest.jointUuid(); } + if (request instanceof SpaceUpsertRequest spaceRequest) { + return spaceRequest.spaceUuid(); + } + if (request instanceof SpaceRemoveRequest spaceRequest) { + return spaceRequest.spaceUuid(); + } + if (request instanceof SpaceSettingsRequest spaceRequest) { + return spaceRequest.spaceUuid(); + } if (request instanceof TerrainColliderRequest terrainRequest) { return terrainRequest.terrainColliderUuid(); } @@ -747,6 +936,15 @@ private static String structuralOperation(@Nonnull PhysicsStoreRequest request) if (request instanceof TerrainColliderRequest terrainRequest) { return terrainRequest.remove() ? "terrain-remove" : "terrain-upsert"; } + if (request instanceof SpaceUpsertRequest) { + return "space-upsert"; + } + if (request instanceof SpaceRemoveRequest) { + return "space-remove"; + } + if (request instanceof SpaceSettingsRequest) { + return "space-settings"; + } return request.getClass().getName(); } @@ -843,6 +1041,9 @@ private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) || request instanceof BodyActivationRequest || request instanceof BodyForceRequest || request instanceof BodyTypeRequest + || request instanceof SpaceUpsertRequest + || request instanceof SpaceRemoveRequest + || request instanceof SpaceSettingsRequest || request instanceof TerrainColliderRequest || request instanceof BodyUpsertRequest || request instanceof BodyRemoveRequest diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index dccb0073..ac44a7b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -93,7 +93,11 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, backendRuntime = Impulse.createRuntime(backendId); runtime.putRuntime(backendId, backendRuntime); } - SpaceId compatibilitySpaceId = SpaceId.next(); + SpaceId compatibilitySpaceId = compatibility.getSpaceId(spaceUuid); + if (compatibilitySpaceId == null) { + compatibilitySpaceId = SpaceId.next(); + } + SpaceId.reserveAtLeast(compatibilitySpaceId.value()); BackendSpaceHandle handle = new BackendSpaceHandle( backendRuntime.createSpace(compatibilitySpaceId)); Vector3f gravity = space.getGravity(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index d8aade51..5422ded0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -298,6 +298,24 @@ private boolean isAuthoritativePhysicsStoreActive() { return PhysicsStoreEarlyPluginProbe.isAvailable(); } + @Nonnull + private World requireAuthoritativeWorld(@Nonnull String operation) { + Store entityStore = owningStore; + if (entityStore == null) { + throw new IllegalStateException("Cannot " + operation + + " through authoritative PhysicsStore before this resource is attached to an " + + "EntityStore"); + } + return entityStore.getExternalData().getWorld(); + } + + @Nonnull + private IllegalStateException authoritativeFenceUnavailable(@Nonnull String operation) { + return new IllegalStateException("Cannot " + operation + + " through authoritative PhysicsStore yet because request completion fences are not " + + "implemented. Use the synchronous enqueueing facade or add Worker F request fences."); + } + public void runOwnerMutation(@Nonnull String operation, @Nonnull PhysicsOwnerMutation mutation) { ownerGateway.run(operation, mutation); @@ -379,6 +397,13 @@ public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull SpaceId spaceId, @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { + if (isAuthoritativePhysicsStoreActive()) { + PhysicsStoreAccess.enqueueSpaceUpsert(requireAuthoritativeWorld("create physics space"), + spaceId, + backendId, + settings); + return spaceId; + } requireLegacyMutationAllowed("create physics space"); callOwner("create physics space", () -> createSpaceDirect(backendId, spaceId, worldName, settings)); @@ -400,6 +425,11 @@ public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backen @Nonnull SpaceId spaceId, @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { + if (isAuthoritativePhysicsStoreActive()) { + return PhysicsMutationHandle.failed("create physics space", + spaceId, + authoritativeFenceUnavailable("create physics space asynchronously")); + } requireLegacyMutationAllowed("create physics space"); return enqueueOwnerMutation("create physics space", spaceId, @@ -428,6 +458,10 @@ public PhysicsSpaceBinding getSpaceBinding(@Nonnull SpaceId spaceId) { @Override public boolean hasSpace(@Nonnull SpaceId spaceId) { + if (isAuthoritativePhysicsStoreActive()) { + return PhysicsStoreAccess.hasSpace(requireAuthoritativeWorld("check physics space"), + spaceId); + } return spaceRuntime.getBinding(spaceId) != null; } @@ -444,11 +478,17 @@ public Collection getSpaceBindings() { @Nonnull @Override public Collection getSpaceIds() { + if (isAuthoritativePhysicsStoreActive()) { + return PhysicsStoreAccess.spaceIds(requireAuthoritativeWorld("list physics spaces")); + } return spaceRuntime.getSpaceIds(); } @Override public int getSpaceCount() { + if (isAuthoritativePhysicsStoreActive()) { + return PhysicsStoreAccess.spaceCount(requireAuthoritativeWorld("count physics spaces")); + } return spaceRuntime.getSpaceCount(); } @@ -844,6 +884,11 @@ public void removeSpace(@Nonnull SpaceId spaceId) { @Override public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { + if (isAuthoritativePhysicsStoreActive()) { + PhysicsStoreAccess.enqueueSpaceRemove(requireAuthoritativeWorld("remove physics space"), + spaceId); + return; + } requireLegacyMutationAllowed("remove physics space"); runOwnerMutation("remove physics space", () -> removeSpaceDirect(spaceId, worldName)); } @@ -852,6 +897,11 @@ public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { @Override public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, @Nonnull String worldName) { + if (isAuthoritativePhysicsStoreActive()) { + return PhysicsMutationHandle.failed("remove physics space", + spaceId, + authoritativeFenceUnavailable("remove physics space asynchronously")); + } requireLegacyMutationAllowed("remove physics space"); return enqueueOwnerMutation("remove physics space", spaceId, @@ -946,6 +996,16 @@ private PhysicsRuntimeResetResult resetRuntimeStateKeepingSpacesDirect(@Nonnull @Nonnull @Override public PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { + if (isAuthoritativePhysicsStoreActive()) { + PhysicsSpaceSettings settings = PhysicsStoreAccess.getSpaceSettings( + requireAuthoritativeWorld("read physics space settings"), + spaceId); + if (settings == null) { + throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() + + " is not registered"); + } + return settings; + } return spaceRuntime.getSpaceSettings(spaceId); } @@ -956,6 +1016,13 @@ public PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { @Override public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { + if (isAuthoritativePhysicsStoreActive()) { + PhysicsStoreAccess.enqueueSpaceSettings( + requireAuthoritativeWorld("set physics space settings"), + spaceId, + settings); + return; + } requireLegacyMutationAllowed("set physics space settings"); PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); runOwnerMutation("set physics space settings", () -> setSpaceSettingsDirect(spaceId, requested)); @@ -965,6 +1032,11 @@ public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSett @Override public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { + if (isAuthoritativePhysicsStoreActive()) { + return PhysicsMutationHandle.failed("set physics space settings", + spaceId, + authoritativeFenceUnavailable("set physics space settings asynchronously")); + } requireLegacyMutationAllowed("set physics space settings"); PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); return enqueueOwnerMutation("set physics space settings", diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java index 99948c6a..8d767dc4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java @@ -1,16 +1,32 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -50,6 +66,76 @@ public static UUID resolveSpaceUuid(@Nonnull World world, return index.getSpaceUuid(spaceId); } + public static boolean hasSpace(@Nonnull World world, @Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); + Store store = require(world).getStore(); + return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .hasSpace(spaceId); + } + + @Nonnull + public static Collection spaceIds(@Nonnull World world) { + Store store = require(world).getStore(); + return List.copyOf(store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .spaceIds()); + } + + public static int spaceCount(@Nonnull World world) { + Store store = require(world).getStore(); + return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).size(); + } + + @Nullable + public static PhysicsSpaceSettings getSpaceSettings(@Nonnull World world, + @Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); + Store store = require(world).getStore(); + UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(spaceId); + if (spaceUuid == null) { + return null; + } + Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + if (ref == null || !ref.isValid()) { + return null; + } + SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); + if (space == null) { + return null; + } + WorldCollisionComponent worldCollision = store.getComponent(ref, + WorldCollisionComponent.getComponentType()); + return toSpaceSettings(worldCollision); + } + + @Nonnull + public static UUID enqueueSpaceUpsert(@Nonnull World world, + @Nonnull SpaceId compatibilitySpaceId, + @Nonnull BackendId backendId, + @Nonnull PhysicsSpaceSettings settings) { + Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); + Objects.requireNonNull(backendId, "backendId"); + requireRepresentedSpaceSettings(settings); + Impulse.getRuntimeProvider(backendId); + UUID spaceUuid = UUID.randomUUID(); + enqueue(world, SpaceUpsertRequest.of(spaceUuid, compatibilitySpaceId, backendId, settings)); + return spaceUuid; + } + + public static void enqueueSpaceRemove(@Nonnull World world, @Nonnull SpaceId spaceId) { + UUID spaceUuid = requireSpaceUuid(world, spaceId); + enqueue(world, SpaceRemoveRequest.of(spaceUuid)); + } + + public static void enqueueSpaceSettings(@Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsSpaceSettings settings) { + requireRepresentedSpaceSettings(settings); + UUID spaceUuid = requireSpaceUuid(world, spaceId); + enqueue(world, SpaceSettingsRequest.of(spaceUuid, settings)); + } + public static void enqueue(@Nonnull World world, @Nonnull PhysicsStoreRequest request) { Objects.requireNonNull(request, "request"); @@ -87,6 +173,121 @@ private static MethodHandle worldAccessor() { } } + @Nonnull + private static UUID requireSpaceUuid(@Nonnull World world, @Nonnull SpaceId spaceId) { + UUID spaceUuid = resolveSpaceUuid(world, spaceId); + if (spaceUuid == null) { + throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() + + " is not registered"); + } + return spaceUuid; + } + + @Nonnull + private static PhysicsSpaceSettings toSpaceSettings( + @Nullable WorldCollisionComponent worldCollision) { + PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); + if (worldCollision == null) { + return settings; + } + PhysicsWorldCollisionSettings target = settings.getWorldCollisionSettings(); + target.setWorldCollisionMode(worldCollision.getMode()); + target.setNativeVoxelTerrainEnabled(worldCollision.isNativeVoxelTerrainEnabled()); + target.setWorldCollisionRadius(worldCollision.getRadius()); + target.setWorldCollisionBodyRadius(worldCollision.getBodyRadius()); + target.setWorldCollisionTtlTicks(worldCollision.getTtlTicks()); + target.setTerrainMaterial(worldCollision.getTerrainFriction(), + worldCollision.getTerrainRestitution()); + return settings; + } + + private static void requireRepresentedSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { + Objects.requireNonNull(settings, "settings"); + PhysicsSpaceSettings defaults = PhysicsSpaceSettings.defaults(); + if (!solverSettingsEqual(settings.getSolverSettings(), defaults.getSolverSettings()) + || !visualSyncSettingsEqual(settings.getVisualSyncSettings(), + defaults.getVisualSyncSettings()) + || !visualMaterializationSettingsEqual(settings.getVisualMaterializationSettings(), + defaults.getVisualMaterializationSettings()) + || !collisionLodSettingsEqual(settings.getCollisionLodSettings(), + defaults.getCollisionLodSettings()) + || !settings.getExtensionSettings().isEmpty()) { + throw new IllegalArgumentException("Authoritative PhysicsStore space requests currently " + + "support world-collision settings only; solver, visual sync, visual " + + "materialization, collision LOD, and extension settings need a dedicated " + + "PhysicsStore space-settings model before they can be applied."); + } + } + + private static boolean solverSettingsEqual(@Nonnull PhysicsSolverSettings left, + @Nonnull PhysicsSolverSettings right) { + return left.getSolverIterations() == right.getSolverIterations() + && left.getStabilizationIterations() == right.getStabilizationIterations() + && Float.compare(left.getDynamicSleepLinearThreshold(), + right.getDynamicSleepLinearThreshold()) == 0 + && Float.compare(left.getDynamicSleepAngularThreshold(), + right.getDynamicSleepAngularThreshold()) == 0 + && Float.compare(left.getDynamicSleepTimeUntilSleep(), + right.getDynamicSleepTimeUntilSleep()) == 0; + } + + private static boolean visualSyncSettingsEqual(@Nonnull PhysicsVisualSyncSettings left, + @Nonnull PhysicsVisualSyncSettings right) { + return left.getVisualFullSyncRadius() == right.getVisualFullSyncRadius() + && left.getVisualMaxSyncRadius() == right.getVisualMaxSyncRadius() + && left.isVisualFarSyncCutoffEnabled() == right.isVisualFarSyncCutoffEnabled() + && left.getVisualMidSyncIntervalTicks() == right.getVisualMidSyncIntervalTicks() + && left.getVisualFarSyncIntervalTicks() == right.getVisualFarSyncIntervalTicks() + && left.getVisualOcclusionMode() == right.getVisualOcclusionMode() + && left.getVisualOcclusionRaycastsPerTick() + == right.getVisualOcclusionRaycastsPerTick() + && left.getVisualOcclusionCacheTicks() == right.getVisualOcclusionCacheTicks() + && left.isVisualSnapshotPredictionEnabled() + == right.isVisualSnapshotPredictionEnabled() + && Float.compare(left.getVisualSnapshotPredictionMaxSeconds(), + right.getVisualSnapshotPredictionMaxSeconds()) == 0 + && left.isVisualSnapshotSmoothingEnabled() + == right.isVisualSnapshotSmoothingEnabled() + && Float.compare(left.getVisualSnapshotSmoothingRate(), + right.getVisualSnapshotSmoothingRate()) == 0 + && left.isEntityVisualSyncCullingEnabled() + == right.isEntityVisualSyncCullingEnabled() + && left.isVisualVisibilityCullingEnabled() == right.isVisualVisibilityCullingEnabled(); + } + + private static boolean visualMaterializationSettingsEqual( + @Nonnull PhysicsVisualMaterializationSettings left, + @Nonnull PhysicsVisualMaterializationSettings right) { + return left.isDetachedVisualMaterializationEnabled() + == right.isDetachedVisualMaterializationEnabled() + && left.getDetachedVisualMaterializationRadius() + == right.getDetachedVisualMaterializationRadius() + && left.getDetachedVisualDematerializationRadius() + == right.getDetachedVisualDematerializationRadius() + && left.getDetachedVisualMaxSpawnsPerTick() + == right.getDetachedVisualMaxSpawnsPerTick() + && left.getDetachedVisualMaxMaterialized() + == right.getDetachedVisualMaxMaterialized() + && left.getDetachedVisualInterestRefreshIntervalTicks() + == right.getDetachedVisualInterestRefreshIntervalTicks() + && left.getDetachedVisualCandidateRefreshIntervalTicks() + == right.getDetachedVisualCandidateRefreshIntervalTicks() + && left.getDetachedVisualVisibilityCheckIntervalTicks() + == right.getDetachedVisualVisibilityCheckIntervalTicks() + && left.getDetachedVisualBlockType().equals(right.getDetachedVisualBlockType()); + } + + private static boolean collisionLodSettingsEqual(@Nonnull PhysicsCollisionLodSettings left, + @Nonnull PhysicsCollisionLodSettings right) { + return left.isCollisionLodEnabled() == right.isCollisionLodEnabled() + && left.getCollisionLodNearRadius() == right.getCollisionLodNearRadius() + && left.getCollisionLodMidRadius() == right.getCollisionLodMidRadius() + && left.getCollisionLodHysteresis() == right.getCollisionLodHysteresis() + && left.getCollisionLodRefreshIntervalTicks() + == right.getCollisionLodRefreshIntervalTicks() + && left.isCollisionLodFarSleepEnabled() == right.isCollisionLodFarSleepEnabled(); + } + @Nonnull private static MethodHandle findWorldAccessor() { try { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java new file mode 100644 index 00000000..6b62a968 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java @@ -0,0 +1,22 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request that removes one empty PhysicsStore space row and backend binding. + */ +public record SpaceRemoveRequest(@Nonnull UUID requestUuid, + @Nonnull UUID spaceUuid) implements PhysicsStoreRequest { + + public SpaceRemoveRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + } + + @Nonnull + public static SpaceRemoveRequest of(@Nonnull UUID spaceUuid) { + return new SpaceRemoveRequest(UUID.randomUUID(), spaceUuid); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java new file mode 100644 index 00000000..e42698f3 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java @@ -0,0 +1,39 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Copied request that updates represented PhysicsStore settings for one space. + */ +public record SpaceSettingsRequest(@Nonnull UUID requestUuid, + @Nonnull UUID spaceUuid, + @Nonnull WorldCollisionComponent worldCollision) + implements PhysicsStoreRequest { + + public SpaceSettingsRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + worldCollision = Objects.requireNonNull(worldCollision, "worldCollision").clone(); + } + + @Nonnull + public static SpaceSettingsRequest of(@Nonnull UUID spaceUuid, + @Nonnull PhysicsSpaceSettings settings) { + PhysicsWorldCollisionSettings worldCollisionSettings = + Objects.requireNonNull(settings, "settings").getWorldCollisionSettings(); + return new SpaceSettingsRequest(UUID.randomUUID(), + spaceUuid, + new WorldCollisionComponent(worldCollisionSettings.getWorldCollisionMode(), + worldCollisionSettings.isNativeVoxelTerrainEnabled(), + worldCollisionSettings.getWorldCollisionRadius(), + worldCollisionSettings.getWorldCollisionBodyRadius(), + worldCollisionSettings.getWorldCollisionTtlTicks(), + worldCollisionSettings.getTerrainFriction(), + worldCollisionSettings.getTerrainRestitution())); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java new file mode 100644 index 00000000..5edb081e --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java @@ -0,0 +1,53 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * Copied request that authors one PhysicsStore space row and its compatibility token. + */ +public record SpaceUpsertRequest(@Nonnull UUID requestUuid, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId compatibilitySpaceId, + @Nonnull SpaceComponent space, + @Nonnull WorldCollisionComponent worldCollision) + implements PhysicsStoreRequest { + + public SpaceUpsertRequest { + Objects.requireNonNull(requestUuid, "requestUuid"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); + space = Objects.requireNonNull(space, "space").clone(); + worldCollision = Objects.requireNonNull(worldCollision, "worldCollision").clone(); + } + + @Nonnull + public static SpaceUpsertRequest of(@Nonnull UUID spaceUuid, + @Nonnull SpaceId compatibilitySpaceId, + @Nonnull BackendId backendId, + @Nonnull PhysicsSpaceSettings settings) { + PhysicsWorldCollisionSettings worldCollisionSettings = + Objects.requireNonNull(settings, "settings").getWorldCollisionSettings(); + WorldCollisionComponent worldCollision = new WorldCollisionComponent( + worldCollisionSettings.getWorldCollisionMode(), + worldCollisionSettings.isNativeVoxelTerrainEnabled(), + worldCollisionSettings.getWorldCollisionRadius(), + worldCollisionSettings.getWorldCollisionBodyRadius(), + worldCollisionSettings.getWorldCollisionTtlTicks(), + worldCollisionSettings.getTerrainFriction(), + worldCollisionSettings.getTerrainRestitution()); + return new SpaceUpsertRequest(UUID.randomUUID(), + spaceUuid, + compatibilitySpaceId, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), + worldCollision); + } +} From 4fd46bfb31e0539040e0711ecdea32619d4b4466 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 19:08:11 +0200 Subject: [PATCH 030/534] feat(core): represent physics store space settings Signed-off-by: Blovien --- .../PersistentPhysicsStorePreflight.java | 6 + .../persistence/PersistentSpaceDto.java | 182 ++++++++++- .../PhysicsStoreRegistration.java | 27 ++ .../resources/PhysicsRuntimeResource.java | 23 ++ .../PhysicsWorldCollisionIndexResource.java | 2 + .../systems/PersistenceCaptureSystem.java | 38 ++- .../systems/PersistenceHydrationSystem.java | 23 +- .../systems/RequestDrainSystem.java | 59 +++- .../systems/SpaceBindingSystem.java | 16 +- .../SpaceSettingsApplicationSystem.java | 128 ++++++++ .../systems/WorldCollisionIndexSystem.java | 1 + .../physicsstore/PhysicsStoreAccess.java | 152 +++------ .../physicsstore/PhysicsStoreTypes.java | 71 +++++ .../CollisionLodSettingsComponent.java | 148 +++++++++ .../ExtensionSettingsComponent.java | 190 ++++++++++++ .../components/SolverSettingsComponent.java | 153 +++++++++ ...isualMaterializationSettingsComponent.java | 231 ++++++++++++++ .../VisualSyncSettingsComponent.java | 292 ++++++++++++++++++ .../components/WorldCollisionComponent.java | 70 +++++ .../requests/SpaceSettingsRequest.java | 37 ++- .../requests/SpaceUpsertRequest.java | 39 ++- 21 files changed, 1743 insertions(+), 145 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java index 3136c9cf..896f934a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java @@ -84,6 +84,12 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, errors.add("PhysicsStore space " + uuid + " has invalid terrain restitution"); } + try { + space.toSettings(); + } catch (RuntimeException exception) { + errors.add("PhysicsStore space " + uuid + " has invalid space settings: " + + exception.getMessage()); + } } return seen; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java index c21dfbf8..31372d4c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java @@ -7,6 +7,14 @@ import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; import java.util.UUID; @@ -43,6 +51,14 @@ public final class PersistentSpaceDto { : WorldCollisionMode.NONE, PersistentSpaceDto::getWorldCollisionMode) .add() + .append(new KeyedCodec<>("EntityChunkBoundaryMode", + new EnumCodec<>(EntityChunkBoundaryMode.class), + false), + (dto, value) -> dto.entityChunkBoundaryMode = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + PersistentSpaceDto::getEntityChunkBoundaryMode) + .add() .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), (dto, value) -> dto.nativeVoxelTerrainEnabled = value != null && value, PersistentSpaceDto::isNativeVoxelTerrainEnabled) @@ -77,6 +93,42 @@ public final class PersistentSpaceDto { : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, PersistentSpaceDto::getTerrainRestitution) .add() + .append(new KeyedCodec<>("SolverSettings", SolverSettingsComponent.CODEC, false), + (dto, value) -> dto.solverSettings = value != null + ? value.clone() + : new SolverSettingsComponent(), + PersistentSpaceDto::getSolverSettings) + .add() + .append(new KeyedCodec<>("VisualSyncSettings", VisualSyncSettingsComponent.CODEC, false), + (dto, value) -> dto.visualSyncSettings = value != null + ? value.clone() + : new VisualSyncSettingsComponent(), + PersistentSpaceDto::getVisualSyncSettings) + .add() + .append(new KeyedCodec<>("VisualMaterializationSettings", + VisualMaterializationSettingsComponent.CODEC, + false), + (dto, value) -> dto.visualMaterializationSettings = value != null + ? value.clone() + : new VisualMaterializationSettingsComponent(), + PersistentSpaceDto::getVisualMaterializationSettings) + .add() + .append(new KeyedCodec<>("CollisionLodSettings", + CollisionLodSettingsComponent.CODEC, + false), + (dto, value) -> dto.collisionLodSettings = value != null + ? value.clone() + : new CollisionLodSettingsComponent(), + PersistentSpaceDto::getCollisionLodSettings) + .add() + .append(new KeyedCodec<>("ExtensionSettings", + ExtensionSettingsComponent.CODEC, + false), + (dto, value) -> dto.extensionSettings = value != null + ? value.clone() + : new ExtensionSettingsComponent(), + PersistentSpaceDto::getExtensionSettings) + .add() .build(); @Nonnull @@ -87,6 +139,9 @@ public final class PersistentSpaceDto { private final Vector3f gravity = new Vector3f(0.0f, -9.81f, 0.0f); @Nonnull private WorldCollisionMode worldCollisionMode = WorldCollisionMode.NONE; + @Nonnull + private EntityChunkBoundaryMode entityChunkBoundaryMode = + PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelTerrainEnabled = PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; private int worldCollisionRadius = @@ -97,6 +152,18 @@ public final class PersistentSpaceDto { PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS; private float terrainFriction = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION; private float terrainRestitution = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION; + @Nonnull + private SolverSettingsComponent solverSettings = new SolverSettingsComponent(); + @Nonnull + private VisualSyncSettingsComponent visualSyncSettings = new VisualSyncSettingsComponent(); + @Nonnull + private VisualMaterializationSettingsComponent visualMaterializationSettings = + new VisualMaterializationSettingsComponent(); + @Nonnull + private CollisionLodSettingsComponent collisionLodSettings = + new CollisionLodSettingsComponent(); + @Nonnull + private ExtensionSettingsComponent extensionSettings = new ExtensionSettingsComponent(); public PersistentSpaceDto() { } @@ -108,12 +175,18 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, backendId, gravity, WorldCollisionMode.NONE, + PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED, PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS, PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS, PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION); + PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, + new SolverSettingsComponent(), + new VisualSyncSettingsComponent(), + new VisualMaterializationSettingsComponent(), + new CollisionLodSettingsComponent(), + new ExtensionSettingsComponent()); } public PersistentSpaceDto(@Nonnull UUID spaceUuid, @@ -126,17 +199,62 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, int worldCollisionTtlTicks, float terrainFriction, float terrainRestitution) { + this(spaceUuid, + backendId, + gravity, + worldCollisionMode, + PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + nativeVoxelTerrainEnabled, + worldCollisionRadius, + worldCollisionBodyRadius, + worldCollisionTtlTicks, + terrainFriction, + terrainRestitution, + new SolverSettingsComponent(), + new VisualSyncSettingsComponent(), + new VisualMaterializationSettingsComponent(), + new CollisionLodSettingsComponent(), + new ExtensionSettingsComponent()); + } + + public PersistentSpaceDto(@Nonnull UUID spaceUuid, + @Nonnull String backendId, + @Nonnull Vector3f gravity, + @Nonnull WorldCollisionMode worldCollisionMode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, + boolean nativeVoxelTerrainEnabled, + int worldCollisionRadius, + int worldCollisionBodyRadius, + int worldCollisionTtlTicks, + float terrainFriction, + float terrainRestitution, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); this.backendId = Objects.requireNonNull(backendId, "backendId"); this.gravity.set(Objects.requireNonNull(gravity, "gravity")); this.worldCollisionMode = Objects.requireNonNull(worldCollisionMode, "worldCollisionMode"); + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; this.worldCollisionRadius = worldCollisionRadius; this.worldCollisionBodyRadius = worldCollisionBodyRadius; this.worldCollisionTtlTicks = worldCollisionTtlTicks; this.terrainFriction = terrainFriction; this.terrainRestitution = terrainRestitution; + this.solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); + this.visualSyncSettings = Objects.requireNonNull(visualSyncSettings, + "visualSyncSettings").clone(); + this.visualMaterializationSettings = Objects.requireNonNull(visualMaterializationSettings, + "visualMaterializationSettings").clone(); + this.collisionLodSettings = Objects.requireNonNull(collisionLodSettings, + "collisionLodSettings").clone(); + this.extensionSettings = Objects.requireNonNull(extensionSettings, + "extensionSettings").clone(); } @Nonnull @@ -159,6 +277,11 @@ public WorldCollisionMode getWorldCollisionMode() { return worldCollisionMode; } + @Nonnull + public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { + return entityChunkBoundaryMode; + } + public boolean isNativeVoxelTerrainEnabled() { return nativeVoxelTerrainEnabled; } @@ -183,17 +306,72 @@ public float getTerrainRestitution() { return terrainRestitution; } + @Nonnull + public WorldCollisionComponent getWorldCollision() { + return new WorldCollisionComponent(worldCollisionMode, + entityChunkBoundaryMode, + nativeVoxelTerrainEnabled, + worldCollisionRadius, + worldCollisionBodyRadius, + worldCollisionTtlTicks, + terrainFriction, + terrainRestitution); + } + + @Nonnull + public SolverSettingsComponent getSolverSettings() { + return solverSettings.clone(); + } + + @Nonnull + public VisualSyncSettingsComponent getVisualSyncSettings() { + return visualSyncSettings.clone(); + } + + @Nonnull + public VisualMaterializationSettingsComponent getVisualMaterializationSettings() { + return visualMaterializationSettings.clone(); + } + + @Nonnull + public CollisionLodSettingsComponent getCollisionLodSettings() { + return collisionLodSettings.clone(); + } + + @Nonnull + public ExtensionSettingsComponent getExtensionSettings() { + return extensionSettings.clone(); + } + + @Nonnull + public PhysicsSpaceSettings toSettings() { + PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); + getWorldCollision().copyTo(settings); + solverSettings.copyTo(settings); + visualSyncSettings.copyTo(settings); + visualMaterializationSettings.copyTo(settings); + collisionLodSettings.copyTo(settings); + extensionSettings.copyTo(settings); + return settings; + } + @Nonnull public PersistentSpaceDto copy() { return new PersistentSpaceDto(spaceUuid, backendId, gravity, worldCollisionMode, + entityChunkBoundaryMode, nativeVoxelTerrainEnabled, worldCollisionRadius, worldCollisionBodyRadius, worldCollisionTtlTicks, terrainFriction, - terrainRestitution); + terrainRestitution, + solverSettings, + visualSyncSettings, + visualMaterializationSettings, + collisionLodSettings, + extensionSettings); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index a5805bde..254eafb1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -23,6 +23,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.RequestDrainSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; @@ -30,15 +31,20 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -96,6 +102,26 @@ public static void register(@Nonnull PluginBase plugin) { WorldCollisionComponent.class, "WorldCollision", WorldCollisionComponent.CODEC)); + PhysicsStoreTypes.setSolverSettingsComponentType(registry.registerComponent( + SolverSettingsComponent.class, + "SolverSettings", + SolverSettingsComponent.CODEC)); + PhysicsStoreTypes.setVisualSyncSettingsComponentType(registry.registerComponent( + VisualSyncSettingsComponent.class, + "VisualSyncSettings", + VisualSyncSettingsComponent.CODEC)); + PhysicsStoreTypes.setVisualMaterializationSettingsComponentType(registry.registerComponent( + VisualMaterializationSettingsComponent.class, + "VisualMaterializationSettings", + VisualMaterializationSettingsComponent.CODEC)); + PhysicsStoreTypes.setCollisionLodSettingsComponentType(registry.registerComponent( + CollisionLodSettingsComponent.class, + "CollisionLodSettings", + CollisionLodSettingsComponent.CODEC)); + PhysicsStoreTypes.setExtensionSettingsComponentType(registry.registerComponent( + ExtensionSettingsComponent.class, + "ExtensionSettings", + ExtensionSettingsComponent.CODEC)); PhysicsStoreTypes.setRuntimeResourceType(registry.registerResource( PhysicsRuntimeResource.class, @@ -137,6 +163,7 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new IdentityIndexSystem()); registry.registerSystem(new WorldCollisionIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); + registry.registerSystem(new SpaceSettingsApplicationSystem()); registry.registerSystem(new BodyBindingSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 8ad8ca36..50ed32b4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -17,10 +17,12 @@ import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.function.LongConsumer; import javax.annotation.Nonnull; @@ -72,6 +74,8 @@ public final class PhysicsRuntimeResource implements Resource { new Long2ObjectOpenHashMap<>(); @Nonnull private final List pendingBodyOperations = new ArrayList<>(); + @Nonnull + private final ObjectOpenHashSet pendingSpaceSettings = new ObjectOpenHashSet<>(); private boolean started; public PhysicsRuntimeResource() { @@ -114,6 +118,7 @@ public BackendId getSpaceBackendId(@Nonnull UUID spaceUuid) { public void removeSpaceHandle(@Nonnull UUID spaceUuid) { BackendSpaceHandle removed = spaceHandlesByUuid.remove(spaceUuid); backendIdsBySpaceUuid.remove(spaceUuid); + pendingSpaceSettings.remove(spaceUuid); if (removed != null) { LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); if (bodyHandles != null) { @@ -178,6 +183,24 @@ public void enqueuePendingBodyOperation(@Nonnull PendingBodyOperation operation) pendingBodyOperations.add(Objects.requireNonNull(operation, "operation")); } + public void markSpaceSettingsPending(@Nonnull UUID spaceUuid) { + pendingSpaceSettings.add(Objects.requireNonNull(spaceUuid, "spaceUuid")); + } + + public void clearPendingSpaceSettings(@Nonnull UUID spaceUuid) { + pendingSpaceSettings.remove(spaceUuid); + } + + @Nonnull + public Set drainPendingSpaceSettings() { + if (pendingSpaceSettings.isEmpty()) { + return Set.of(); + } + Set drained = new ObjectOpenHashSet<>(pendingSpaceSettings); + pendingSpaceSettings.clear(); + return drained; + } + @Nonnull public List drainPendingBodyOperations() { if (pendingBodyOperations.isEmpty()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java index ff3bf1ff..2e25e4e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java @@ -7,6 +7,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.TerrainColliderMode; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; import java.util.Map; @@ -56,6 +57,7 @@ public static ResourceType get public record SpaceWorldCollisionSettings(@Nonnull UUID spaceUuid, @Nonnull WorldCollisionMode mode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, int radius, int bodyRadius, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index e4d28e9b..9ba4cec7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -24,14 +24,19 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -139,7 +144,13 @@ private void collectRow(@Nonnull UUID uuid, if (space != null) { spaceRows.add(new SpaceRow(uuid, space, - chunk.getComponent(index, WorldCollisionComponent.getComponentType()))); + chunk.getComponent(index, WorldCollisionComponent.getComponentType()), + chunk.getComponent(index, SolverSettingsComponent.getComponentType()), + chunk.getComponent(index, VisualSyncSettingsComponent.getComponentType()), + chunk.getComponent(index, + VisualMaterializationSettingsComponent.getComponentType()), + chunk.getComponent(index, CollisionLodSettingsComponent.getComponentType()), + chunk.getComponent(index, ExtensionSettingsComponent.getComponentType()))); } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); if (body != null) { @@ -231,12 +242,28 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { row.space().getBackendIdValue(), row.space().getGravity(), worldCollision.getMode(), + worldCollision.getEntityChunkBoundaryMode(), worldCollision.isNativeVoxelTerrainEnabled(), worldCollision.getRadius(), worldCollision.getBodyRadius(), worldCollision.getTtlTicks(), worldCollision.getTerrainFriction(), - worldCollision.getTerrainRestitution()); + worldCollision.getTerrainRestitution(), + row.solverSettings() != null + ? row.solverSettings() + : new SolverSettingsComponent(), + row.visualSyncSettings() != null + ? row.visualSyncSettings() + : new VisualSyncSettingsComponent(), + row.visualMaterializationSettings() != null + ? row.visualMaterializationSettings() + : new VisualMaterializationSettingsComponent(), + row.collisionLodSettings() != null + ? row.collisionLodSettings() + : new CollisionLodSettingsComponent(), + row.extensionSettings() != null + ? row.extensionSettings() + : new ExtensionSettingsComponent()); } @Nonnull @@ -394,7 +421,12 @@ private PersistentTerrainColliderDto[] terrainDtos() { private record SpaceRow(@Nonnull UUID uuid, @Nonnull SpaceComponent space, - @Nullable WorldCollisionComponent worldCollision) { + @Nullable WorldCollisionComponent worldCollision, + @Nullable SolverSettingsComponent solverSettings, + @Nullable VisualSyncSettingsComponent visualSyncSettings, + @Nullable VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nullable CollisionLodSettingsComponent collisionLodSettings, + @Nullable ExtensionSettingsComponent extensionSettings) { } private record BodyRow(@Nonnull UUID uuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index 59a44df0..6c6891f3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -22,15 +22,20 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.util.Set; import java.util.UUID; @@ -95,13 +100,17 @@ private static void addSpace(@Nonnull Store store, holder.addComponent(SpaceComponent.getComponentType(), new SpaceComponent(new BackendId(dto.getBackendId()), dto.getGravity())); holder.addComponent(WorldCollisionComponent.getComponentType(), - new WorldCollisionComponent(dto.getWorldCollisionMode(), - dto.isNativeVoxelTerrainEnabled(), - dto.getWorldCollisionRadius(), - dto.getWorldCollisionBodyRadius(), - dto.getWorldCollisionTtlTicks(), - dto.getTerrainFriction(), - dto.getTerrainRestitution())); + dto.getWorldCollision()); + holder.addComponent(SolverSettingsComponent.getComponentType(), + dto.getSolverSettings()); + holder.addComponent(VisualSyncSettingsComponent.getComponentType(), + dto.getVisualSyncSettings()); + holder.addComponent(VisualMaterializationSettingsComponent.getComponentType(), + dto.getVisualMaterializationSettings()); + holder.addComponent(CollisionLodSettingsComponent.getComponentType(), + dto.getCollisionLodSettings()); + holder.addComponent(ExtensionSettingsComponent.getComponentType(), + dto.getExtensionSettings()); add(store, holder); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index ce801c08..a3c617dd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -31,15 +31,20 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; @@ -122,6 +127,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) structuralConflicts, requests); applySpaceSettings(store, + runtime, identity, refsThisDrain, restore, @@ -187,6 +193,7 @@ private static void applySpaceUpserts(@Nonnull Store store, } private static void applySpaceSettings(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @@ -195,7 +202,12 @@ private static void applySpaceSettings(@Nonnull Store store, for (PhysicsStoreRequest request : requests) { if (request instanceof SpaceSettingsRequest settingsRequest && !structuralConflicts.contains(settingsRequest.spaceUuid())) { - applySpaceSettings(store, identity, refsThisDrain, restore, settingsRequest); + applySpaceSettings(store, + runtime, + identity, + refsThisDrain, + restore, + settingsRequest); } } } @@ -353,11 +365,14 @@ private static void applySpaceUpsert(@Nonnull Store store, store.putComponent(ref, WorldCollisionComponent.getComponentType(), request.worldCollision().clone()); + putSpaceSettingsComponents(store, ref, request); compatibility.putSpace(request.compatibilitySpaceId(), request.spaceUuid()); SpaceId.reserveAtLeast(request.compatibilitySpaceId().value()); + runtime.markSpaceSettingsPending(request.spaceUuid()); } private static void applySpaceSettings(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @@ -371,6 +386,48 @@ private static void applySpaceSettings(@Nonnull Store store, store.putComponent(ref, WorldCollisionComponent.getComponentType(), request.worldCollision().clone()); + putSpaceSettingsComponents(store, ref, request); + runtime.markSpaceSettingsPending(request.spaceUuid()); + } + + private static void putSpaceSettingsComponents(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull SpaceUpsertRequest request) { + store.putComponent(ref, + SolverSettingsComponent.getComponentType(), + request.solverSettings().clone()); + store.putComponent(ref, + VisualSyncSettingsComponent.getComponentType(), + request.visualSyncSettings().clone()); + store.putComponent(ref, + VisualMaterializationSettingsComponent.getComponentType(), + request.visualMaterializationSettings().clone()); + store.putComponent(ref, + CollisionLodSettingsComponent.getComponentType(), + request.collisionLodSettings().clone()); + store.putComponent(ref, + ExtensionSettingsComponent.getComponentType(), + request.extensionSettings().clone()); + } + + private static void putSpaceSettingsComponents(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull SpaceSettingsRequest request) { + store.putComponent(ref, + SolverSettingsComponent.getComponentType(), + request.solverSettings().clone()); + store.putComponent(ref, + VisualSyncSettingsComponent.getComponentType(), + request.visualSyncSettings().clone()); + store.putComponent(ref, + VisualMaterializationSettingsComponent.getComponentType(), + request.visualMaterializationSettings().clone()); + store.putComponent(ref, + CollisionLodSettingsComponent.getComponentType(), + request.collisionLodSettings().clone()); + store.putComponent(ref, + ExtensionSettingsComponent.getComponentType(), + request.extensionSettings().clone()); } private static void applyBodyRemove(@Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index ac44a7b2..4d3b4120 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -20,11 +20,14 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3f; /** @@ -74,7 +77,9 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, identity, chunk.getReferenceTo(index), spaceUuid, - space); + space, + chunk.getComponent(index, SolverSettingsComponent.getComponentType()), + chunk.getComponent(index, ExtensionSettingsComponent.getComponentType())); } } @@ -83,7 +88,9 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref ref, @Nonnull UUID spaceUuid, - @Nonnull SpaceComponent space) { + @Nonnull SpaceComponent space, + @Nullable SolverSettingsComponent solverSettings, + @Nullable ExtensionSettingsComponent extensionSettings) { BackendId backendId = space.getBackendId(); if (backendId.value().isBlank()) { return; @@ -103,6 +110,11 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, Vector3f gravity = space.getGravity(); backendRuntime.setGravity(handle.value(), gravity.x, gravity.y, gravity.z); runtime.putSpaceBinding(spaceUuid, backendId, handle); + SpaceSettingsApplicationSystem.applyBackendSettings(backendRuntime, + handle, + solverSettings != null ? solverSettings : new SolverSettingsComponent(), + extensionSettings); + runtime.clearPendingSpaceSettings(spaceUuid); compatibility.putSpace(compatibilitySpaceId, spaceUuid); identity.putSpaceHandle(handle, ref); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java new file mode 100644 index 00000000..9107772c --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java @@ -0,0 +1,128 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; +import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; +import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettingValue; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; +import dev.hytalemodding.impulse.api.BackendId; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Applies changed backend-facing space settings after space binding and before body binding. + */ +public final class SpaceSettingsApplicationSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), + new SystemDependency<>(Order.BEFORE, BodyBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + Set pending = runtime.drainPendingSpaceSettings(); + if (pending.isEmpty()) { + return; + } + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + for (UUID spaceUuid : pending) { + Ref ref = identity.getByUuid(spaceUuid); + if (ref == null || !ref.isValid()) { + continue; + } + applyIfBound(store, runtime, ref, spaceUuid); + } + } + + static boolean applyIfBound(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull Ref ref, + @Nonnull UUID spaceUuid) { + BackendSpaceHandle handle = runtime.getSpaceHandle(spaceUuid); + if (handle == null) { + return false; + } + PhysicsBackendRuntime backendRuntime = backendRuntime(runtime, spaceUuid); + if (backendRuntime == null) { + return false; + } + SolverSettingsComponent solverSettings = store.getComponent(ref, + SolverSettingsComponent.getComponentType()); + ExtensionSettingsComponent extensionSettings = store.getComponent(ref, + ExtensionSettingsComponent.getComponentType()); + applyBackendSettings(backendRuntime, + handle, + solverSettings != null ? solverSettings : new SolverSettingsComponent(), + extensionSettings); + return true; + } + + static void applyBackendSettings(@Nonnull PhysicsBackendRuntime runtime, + @Nonnull BackendSpaceHandle handle, + @Nonnull SolverSettingsComponent solverSettings, + @Nullable ExtensionSettingsComponent extensionSettings) { + if (runtime.supportsSolverTuning(handle.value())) { + runtime.applySolverTuning(handle.value(), + new PhysicsSolverTuning(solverSettings.getSolverIterations(), + solverSettings.getStabilizationIterations())); + } + if (runtime.supportsActivationTuning(handle.value())) { + runtime.applyActivationTuning(handle.value(), + new PhysicsActivationTuning(solverSettings.getDynamicSleepLinearThreshold(), + solverSettings.getDynamicSleepAngularThreshold(), + solverSettings.getDynamicSleepTimeUntilSleep())); + } + if (extensionSettings == null) { + return; + } + Map> settingsByExtension = + extensionSettings.asMap(); + for (PhysicsBackendExtensionId extensionId : settingsByExtension.keySet()) { + Map settings = settingsByExtension.get(extensionId); + runtime.applyExtensionSettings(handle.value(), + new PhysicsCapabilityId(extensionId.value()), + consumer -> settings.forEach((key, value) -> consumer.accept(key, value.value()))); + } + } + + @Nullable + private static PhysicsBackendRuntime backendRuntime(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID spaceUuid) { + BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + if (backendId == null) { + return null; + } + return runtime.getRuntime(backendId); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java index 09a044c0..3e3c7b1b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java @@ -62,6 +62,7 @@ private static void collectChunk( : new WorldCollisionComponent(); settingsBySpaceUuid.put(spaceUuid, new SpaceWorldCollisionSettings(spaceUuid, settings.getMode(), + settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelTerrainEnabled(), settings.getRadius(), settings.getBodyRadius(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java index 8d767dc4..e6a01d7d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java @@ -10,18 +10,18 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -106,7 +106,22 @@ public static PhysicsSpaceSettings getSpaceSettings(@Nonnull World world, } WorldCollisionComponent worldCollision = store.getComponent(ref, WorldCollisionComponent.getComponentType()); - return toSpaceSettings(worldCollision); + SolverSettingsComponent solverSettings = store.getComponent(ref, + SolverSettingsComponent.getComponentType()); + VisualSyncSettingsComponent visualSyncSettings = store.getComponent(ref, + VisualSyncSettingsComponent.getComponentType()); + VisualMaterializationSettingsComponent visualMaterializationSettings = + store.getComponent(ref, VisualMaterializationSettingsComponent.getComponentType()); + CollisionLodSettingsComponent collisionLodSettings = store.getComponent(ref, + CollisionLodSettingsComponent.getComponentType()); + ExtensionSettingsComponent extensionSettings = store.getComponent(ref, + ExtensionSettingsComponent.getComponentType()); + return toSpaceSettings(worldCollision, + solverSettings, + visualSyncSettings, + visualMaterializationSettings, + collisionLodSettings, + extensionSettings); } @Nonnull @@ -116,7 +131,7 @@ public static UUID enqueueSpaceUpsert(@Nonnull World world, @Nonnull PhysicsSpaceSettings settings) { Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); Objects.requireNonNull(backendId, "backendId"); - requireRepresentedSpaceSettings(settings); + Objects.requireNonNull(settings, "settings"); Impulse.getRuntimeProvider(backendId); UUID spaceUuid = UUID.randomUUID(); enqueue(world, SpaceUpsertRequest.of(spaceUuid, compatibilitySpaceId, backendId, settings)); @@ -131,7 +146,7 @@ public static void enqueueSpaceRemove(@Nonnull World world, @Nonnull SpaceId spa public static void enqueueSpaceSettings(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { - requireRepresentedSpaceSettings(settings); + Objects.requireNonNull(settings, "settings"); UUID spaceUuid = requireSpaceUuid(world, spaceId); enqueue(world, SpaceSettingsRequest.of(spaceUuid, settings)); } @@ -185,107 +200,32 @@ private static UUID requireSpaceUuid(@Nonnull World world, @Nonnull SpaceId spac @Nonnull private static PhysicsSpaceSettings toSpaceSettings( - @Nullable WorldCollisionComponent worldCollision) { + @Nullable WorldCollisionComponent worldCollision, + @Nullable SolverSettingsComponent solverSettings, + @Nullable VisualSyncSettingsComponent visualSyncSettings, + @Nullable VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nullable CollisionLodSettingsComponent collisionLodSettings, + @Nullable ExtensionSettingsComponent extensionSettings) { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - if (worldCollision == null) { - return settings; + if (worldCollision != null) { + worldCollision.copyTo(settings); } - PhysicsWorldCollisionSettings target = settings.getWorldCollisionSettings(); - target.setWorldCollisionMode(worldCollision.getMode()); - target.setNativeVoxelTerrainEnabled(worldCollision.isNativeVoxelTerrainEnabled()); - target.setWorldCollisionRadius(worldCollision.getRadius()); - target.setWorldCollisionBodyRadius(worldCollision.getBodyRadius()); - target.setWorldCollisionTtlTicks(worldCollision.getTtlTicks()); - target.setTerrainMaterial(worldCollision.getTerrainFriction(), - worldCollision.getTerrainRestitution()); - return settings; - } - - private static void requireRepresentedSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { - Objects.requireNonNull(settings, "settings"); - PhysicsSpaceSettings defaults = PhysicsSpaceSettings.defaults(); - if (!solverSettingsEqual(settings.getSolverSettings(), defaults.getSolverSettings()) - || !visualSyncSettingsEqual(settings.getVisualSyncSettings(), - defaults.getVisualSyncSettings()) - || !visualMaterializationSettingsEqual(settings.getVisualMaterializationSettings(), - defaults.getVisualMaterializationSettings()) - || !collisionLodSettingsEqual(settings.getCollisionLodSettings(), - defaults.getCollisionLodSettings()) - || !settings.getExtensionSettings().isEmpty()) { - throw new IllegalArgumentException("Authoritative PhysicsStore space requests currently " - + "support world-collision settings only; solver, visual sync, visual " - + "materialization, collision LOD, and extension settings need a dedicated " - + "PhysicsStore space-settings model before they can be applied."); + if (solverSettings != null) { + solverSettings.copyTo(settings); } - } - - private static boolean solverSettingsEqual(@Nonnull PhysicsSolverSettings left, - @Nonnull PhysicsSolverSettings right) { - return left.getSolverIterations() == right.getSolverIterations() - && left.getStabilizationIterations() == right.getStabilizationIterations() - && Float.compare(left.getDynamicSleepLinearThreshold(), - right.getDynamicSleepLinearThreshold()) == 0 - && Float.compare(left.getDynamicSleepAngularThreshold(), - right.getDynamicSleepAngularThreshold()) == 0 - && Float.compare(left.getDynamicSleepTimeUntilSleep(), - right.getDynamicSleepTimeUntilSleep()) == 0; - } - - private static boolean visualSyncSettingsEqual(@Nonnull PhysicsVisualSyncSettings left, - @Nonnull PhysicsVisualSyncSettings right) { - return left.getVisualFullSyncRadius() == right.getVisualFullSyncRadius() - && left.getVisualMaxSyncRadius() == right.getVisualMaxSyncRadius() - && left.isVisualFarSyncCutoffEnabled() == right.isVisualFarSyncCutoffEnabled() - && left.getVisualMidSyncIntervalTicks() == right.getVisualMidSyncIntervalTicks() - && left.getVisualFarSyncIntervalTicks() == right.getVisualFarSyncIntervalTicks() - && left.getVisualOcclusionMode() == right.getVisualOcclusionMode() - && left.getVisualOcclusionRaycastsPerTick() - == right.getVisualOcclusionRaycastsPerTick() - && left.getVisualOcclusionCacheTicks() == right.getVisualOcclusionCacheTicks() - && left.isVisualSnapshotPredictionEnabled() - == right.isVisualSnapshotPredictionEnabled() - && Float.compare(left.getVisualSnapshotPredictionMaxSeconds(), - right.getVisualSnapshotPredictionMaxSeconds()) == 0 - && left.isVisualSnapshotSmoothingEnabled() - == right.isVisualSnapshotSmoothingEnabled() - && Float.compare(left.getVisualSnapshotSmoothingRate(), - right.getVisualSnapshotSmoothingRate()) == 0 - && left.isEntityVisualSyncCullingEnabled() - == right.isEntityVisualSyncCullingEnabled() - && left.isVisualVisibilityCullingEnabled() == right.isVisualVisibilityCullingEnabled(); - } - - private static boolean visualMaterializationSettingsEqual( - @Nonnull PhysicsVisualMaterializationSettings left, - @Nonnull PhysicsVisualMaterializationSettings right) { - return left.isDetachedVisualMaterializationEnabled() - == right.isDetachedVisualMaterializationEnabled() - && left.getDetachedVisualMaterializationRadius() - == right.getDetachedVisualMaterializationRadius() - && left.getDetachedVisualDematerializationRadius() - == right.getDetachedVisualDematerializationRadius() - && left.getDetachedVisualMaxSpawnsPerTick() - == right.getDetachedVisualMaxSpawnsPerTick() - && left.getDetachedVisualMaxMaterialized() - == right.getDetachedVisualMaxMaterialized() - && left.getDetachedVisualInterestRefreshIntervalTicks() - == right.getDetachedVisualInterestRefreshIntervalTicks() - && left.getDetachedVisualCandidateRefreshIntervalTicks() - == right.getDetachedVisualCandidateRefreshIntervalTicks() - && left.getDetachedVisualVisibilityCheckIntervalTicks() - == right.getDetachedVisualVisibilityCheckIntervalTicks() - && left.getDetachedVisualBlockType().equals(right.getDetachedVisualBlockType()); - } - - private static boolean collisionLodSettingsEqual(@Nonnull PhysicsCollisionLodSettings left, - @Nonnull PhysicsCollisionLodSettings right) { - return left.isCollisionLodEnabled() == right.isCollisionLodEnabled() - && left.getCollisionLodNearRadius() == right.getCollisionLodNearRadius() - && left.getCollisionLodMidRadius() == right.getCollisionLodMidRadius() - && left.getCollisionLodHysteresis() == right.getCollisionLodHysteresis() - && left.getCollisionLodRefreshIntervalTicks() - == right.getCollisionLodRefreshIntervalTicks() - && left.isCollisionLodFarSleepEnabled() == right.isCollisionLodFarSleepEnabled(); + if (visualSyncSettings != null) { + visualSyncSettings.copyTo(settings); + } + if (visualMaterializationSettings != null) { + visualMaterializationSettings.copyTo(settings); + } + if (collisionLodSettings != null) { + collisionLodSettings.copyTo(settings); + } + if (extensionSettings != null) { + extensionSettings.copyTo(settings); + } + return settings; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index b478824a..242249db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -16,15 +16,20 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.util.Objects; import javax.annotation.Nonnull; @@ -59,6 +64,17 @@ public final class PhysicsStoreTypes { private static ComponentType terrainColliderComponentType; @Nullable private static ComponentType worldCollisionComponentType; + @Nullable + private static ComponentType solverSettingsComponentType; + @Nullable + private static ComponentType visualSyncSettingsComponentType; + @Nullable + private static ComponentType + visualMaterializationSettingsComponentType; + @Nullable + private static ComponentType collisionLodSettingsComponentType; + @Nullable + private static ComponentType extensionSettingsComponentType; @Nullable private static ResourceType runtimeResourceType; @@ -147,6 +163,31 @@ public static void setWorldCollisionComponentType( worldCollisionComponentType = Objects.requireNonNull(type, "type"); } + public static void setSolverSettingsComponentType( + @Nonnull ComponentType type) { + solverSettingsComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setVisualSyncSettingsComponentType( + @Nonnull ComponentType type) { + visualSyncSettingsComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setVisualMaterializationSettingsComponentType( + @Nonnull ComponentType type) { + visualMaterializationSettingsComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setCollisionLodSettingsComponentType( + @Nonnull ComponentType type) { + collisionLodSettingsComponentType = Objects.requireNonNull(type, "type"); + } + + public static void setExtensionSettingsComponentType( + @Nonnull ComponentType type) { + extensionSettingsComponentType = Objects.requireNonNull(type, "type"); + } + public static void setRuntimeResourceType( @Nonnull ResourceType type) { runtimeResourceType = Objects.requireNonNull(type, "type"); @@ -262,6 +303,36 @@ public static ComponentType worldCollisio return require(worldCollisionComponentType, "WorldCollisionComponent"); } + @Nonnull + public static ComponentType solverSettingsComponentType() { + return require(solverSettingsComponentType, "SolverSettingsComponent"); + } + + @Nonnull + public static ComponentType + visualSyncSettingsComponentType() { + return require(visualSyncSettingsComponentType, "VisualSyncSettingsComponent"); + } + + @Nonnull + public static ComponentType + visualMaterializationSettingsComponentType() { + return require(visualMaterializationSettingsComponentType, + "VisualMaterializationSettingsComponent"); + } + + @Nonnull + public static ComponentType + collisionLodSettingsComponentType() { + return require(collisionLodSettingsComponentType, "CollisionLodSettingsComponent"); + } + + @Nonnull + public static ComponentType + extensionSettingsComponentType() { + return require(extensionSettingsComponentType, "ExtensionSettingsComponent"); + } + @Nonnull public static ResourceType runtimeResourceType() { return require(runtimeResourceType, "PhysicsRuntimeResource"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java new file mode 100644 index 00000000..12a1c41f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java @@ -0,0 +1,148 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import javax.annotation.Nonnull; + +/** + * Authored collision LOD policy for one PhysicsStore space row. + */ +public final class CollisionLodSettingsComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + CollisionLodSettingsComponent.class, + CollisionLodSettingsComponent::new) + .append(new KeyedCodec<>("CollisionLodEnabled", Codec.BOOLEAN, false), + (component, value) -> component.collisionLodEnabled = value != null + ? value + : PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_ENABLED, + CollisionLodSettingsComponent::isCollisionLodEnabled) + .add() + .append(new KeyedCodec<>("CollisionLodNearRadius", Codec.INTEGER, false), + (component, value) -> component.collisionLodNearRadius = value != null + ? value + : PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_NEAR_RADIUS, + CollisionLodSettingsComponent::getCollisionLodNearRadius) + .add() + .append(new KeyedCodec<>("CollisionLodMidRadius", Codec.INTEGER, false), + (component, value) -> component.collisionLodMidRadius = value != null + ? value + : PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_MID_RADIUS, + CollisionLodSettingsComponent::getCollisionLodMidRadius) + .add() + .append(new KeyedCodec<>("CollisionLodHysteresis", Codec.INTEGER, false), + (component, value) -> component.collisionLodHysteresis = value != null + ? value + : PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_HYSTERESIS, + CollisionLodSettingsComponent::getCollisionLodHysteresis) + .add() + .append(new KeyedCodec<>("CollisionLodRefreshIntervalTicks", Codec.INTEGER, false), + (component, value) -> component.collisionLodRefreshIntervalTicks = value != null + ? value + : PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS, + CollisionLodSettingsComponent::getCollisionLodRefreshIntervalTicks) + .add() + .append(new KeyedCodec<>("CollisionLodFarSleepEnabled", Codec.BOOLEAN, false), + (component, value) -> component.collisionLodFarSleepEnabled = value != null + ? value + : PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED, + CollisionLodSettingsComponent::isCollisionLodFarSleepEnabled) + .add() + .build(); + + private boolean collisionLodEnabled = + PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_ENABLED; + private int collisionLodNearRadius = + PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_NEAR_RADIUS; + private int collisionLodMidRadius = + PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_MID_RADIUS; + private int collisionLodHysteresis = + PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_HYSTERESIS; + private int collisionLodRefreshIntervalTicks = + PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS; + private boolean collisionLodFarSleepEnabled = + PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED; + + public CollisionLodSettingsComponent() { + } + + public CollisionLodSettingsComponent(@Nonnull PhysicsCollisionLodSettings settings) { + collisionLodEnabled = settings.isCollisionLodEnabled(); + collisionLodNearRadius = settings.getCollisionLodNearRadius(); + collisionLodMidRadius = settings.getCollisionLodMidRadius(); + collisionLodHysteresis = settings.getCollisionLodHysteresis(); + collisionLodRefreshIntervalTicks = settings.getCollisionLodRefreshIntervalTicks(); + collisionLodFarSleepEnabled = settings.isCollisionLodFarSleepEnabled(); + } + + private CollisionLodSettingsComponent(boolean collisionLodEnabled, + int collisionLodNearRadius, + int collisionLodMidRadius, + int collisionLodHysteresis, + int collisionLodRefreshIntervalTicks, + boolean collisionLodFarSleepEnabled) { + this.collisionLodEnabled = collisionLodEnabled; + this.collisionLodNearRadius = collisionLodNearRadius; + this.collisionLodMidRadius = collisionLodMidRadius; + this.collisionLodHysteresis = collisionLodHysteresis; + this.collisionLodRefreshIntervalTicks = collisionLodRefreshIntervalTicks; + this.collisionLodFarSleepEnabled = collisionLodFarSleepEnabled; + } + + public boolean isCollisionLodEnabled() { + return collisionLodEnabled; + } + + public int getCollisionLodNearRadius() { + return collisionLodNearRadius; + } + + public int getCollisionLodMidRadius() { + return collisionLodMidRadius; + } + + public int getCollisionLodHysteresis() { + return collisionLodHysteresis; + } + + public int getCollisionLodRefreshIntervalTicks() { + return collisionLodRefreshIntervalTicks; + } + + public boolean isCollisionLodFarSleepEnabled() { + return collisionLodFarSleepEnabled; + } + + public void copyTo(@Nonnull PhysicsSpaceSettings settings) { + PhysicsCollisionLodSettings target = settings.getCollisionLodSettings(); + target.setCollisionLodEnabled(collisionLodEnabled); + target.setCollisionLodRadii(collisionLodNearRadius, collisionLodMidRadius); + target.setCollisionLodHysteresis(collisionLodHysteresis); + target.setCollisionLodRefreshIntervalTicks(collisionLodRefreshIntervalTicks); + target.setCollisionLodFarSleepEnabled(collisionLodFarSleepEnabled); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.collisionLodSettingsComponentType(); + } + + @Nonnull + @Override + public CollisionLodSettingsComponent clone() { + return new CollisionLodSettingsComponent(collisionLodEnabled, + collisionLodNearRadius, + collisionLodMidRadius, + collisionLodHysteresis, + collisionLodRefreshIntervalTicks, + collisionLodFarSleepEnabled); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java new file mode 100644 index 00000000..10c2faf5 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java @@ -0,0 +1,190 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettingValue; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Authored backend extension settings for one PhysicsStore space row. + */ +public final class ExtensionSettingsComponent implements Component { + + private static final Entry[] EMPTY_ENTRIES = new Entry[0]; + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ExtensionSettingsComponent.class, + ExtensionSettingsComponent::new) + .append(new KeyedCodec<>("Settings", + new ArrayCodec<>(Entry.CODEC, Entry[]::new), + false), + (component, value) -> component.entries = copyEntries(value), + ExtensionSettingsComponent::entries) + .add() + .build(); + + @Nonnull + private Entry[] entries = EMPTY_ENTRIES; + + public ExtensionSettingsComponent() { + } + + public ExtensionSettingsComponent(@Nonnull PhysicsExtensionSettings settings) { + entries = entriesFrom(settings); + } + + private ExtensionSettingsComponent(@Nonnull Entry[] entries) { + this.entries = copyEntries(entries); + } + + @Nonnull + public Entry[] entries() { + return copyEntries(entries); + } + + public void copyTo(@Nonnull PhysicsSpaceSettings settings) { + copyTo(settings.getExtensionSettings()); + } + + public void copyTo(@Nonnull PhysicsExtensionSettings settings) { + for (Entry entry : entries) { + entry.copyTo(settings); + } + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.extensionSettingsComponentType(); + } + + @Nonnull + @Override + public ExtensionSettingsComponent clone() { + return new ExtensionSettingsComponent(entries); + } + + @Nonnull + private static Entry[] entriesFrom(@Nonnull PhysicsExtensionSettings settings) { + return settings.asMap().entrySet().stream() + .flatMap(entry -> entry.getValue().entrySet().stream() + .map(setting -> new Entry(entry.getKey().value(), + setting.getKey(), + setting.getValue().kind(), + setting.getValue().value()))) + .toArray(Entry[]::new); + } + + @Nonnull + private static Entry[] copyEntries(Entry[] entries) { + if (entries == null || entries.length == 0) { + return EMPTY_ENTRIES; + } + return Arrays.stream(entries) + .map(Entry::clone) + .toArray(Entry[]::new); + } + + @Nonnull + public Map> asMap() { + PhysicsExtensionSettings settings = new PhysicsExtensionSettings(); + copyTo(settings); + return settings.asMap(); + } + + /** + * One capability-keyed extension setting. + */ + public static final class Entry { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Entry.class, + Entry::new) + .append(new KeyedCodec<>("CapabilityId", Codec.STRING), + (entry, value) -> entry.capabilityId = value, + Entry::capabilityId) + .add() + .append(new KeyedCodec<>("Key", Codec.STRING), + (entry, value) -> entry.key = value, + Entry::key) + .add() + .append(new KeyedCodec<>("Kind", + new EnumCodec<>(PhysicsExtensionSettingValue.Kind.class)), + (entry, value) -> entry.kind = value, + Entry::kind) + .add() + .append(new KeyedCodec<>("Value", Codec.STRING), + (entry, value) -> entry.value = value, + Entry::value) + .add() + .build(); + + @Nonnull + private String capabilityId = ""; + @Nonnull + private String key = ""; + @Nonnull + private PhysicsExtensionSettingValue.Kind kind = + PhysicsExtensionSettingValue.Kind.STRING; + @Nonnull + private String value = ""; + + public Entry() { + } + + private Entry(@Nonnull String capabilityId, + @Nonnull String key, + @Nonnull PhysicsExtensionSettingValue.Kind kind, + @Nonnull String value) { + this.capabilityId = Objects.requireNonNull(capabilityId, "capabilityId"); + this.key = Objects.requireNonNull(key, "key"); + this.kind = Objects.requireNonNull(kind, "kind"); + this.value = Objects.requireNonNull(value, "value"); + } + + @Nonnull + public String capabilityId() { + return capabilityId; + } + + @Nonnull + public String key() { + return key; + } + + @Nonnull + public PhysicsExtensionSettingValue.Kind kind() { + return kind; + } + + @Nonnull + public String value() { + return value; + } + + private void copyTo(@Nonnull PhysicsExtensionSettings settings) { + settings.set(new PhysicsBackendExtensionId(capabilityId), + key, + new PhysicsExtensionSettingValue(kind, value)); + } + + @Nonnull + @Override + public Entry clone() { + return new Entry(capabilityId, key, kind, value); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java new file mode 100644 index 00000000..62909efb --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java @@ -0,0 +1,153 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import javax.annotation.Nonnull; + +/** + * Authored backend solver and activation tuning for one PhysicsStore space row. + */ +public final class SolverSettingsComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SolverSettingsComponent.class, + SolverSettingsComponent::new) + .append(new KeyedCodec<>("SolverIterations", Codec.INTEGER, false), + (component, value) -> component.solverIterations = value != null + ? value + : PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS, + SolverSettingsComponent::getSolverIterations) + .add() + .append(new KeyedCodec<>("StabilizationIterations", Codec.INTEGER, false), + (component, value) -> component.stabilizationIterations = value != null + ? value + : PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS, + SolverSettingsComponent::getStabilizationIterations) + .add() + .append(new KeyedCodec<>("DynamicSleepLinearThreshold", Codec.FLOAT, false), + (component, value) -> component.dynamicSleepLinearThreshold = value != null + ? value + : PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_LINEAR_THRESHOLD, + SolverSettingsComponent::getDynamicSleepLinearThreshold) + .add() + .append(new KeyedCodec<>("DynamicSleepAngularThreshold", Codec.FLOAT, false), + (component, value) -> component.dynamicSleepAngularThreshold = value != null + ? value + : PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_ANGULAR_THRESHOLD, + SolverSettingsComponent::getDynamicSleepAngularThreshold) + .add() + .append(new KeyedCodec<>("DynamicSleepTimeUntilSleep", Codec.FLOAT, false), + (component, value) -> component.dynamicSleepTimeUntilSleep = value != null + ? value + : PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_TIME_UNTIL_SLEEP, + SolverSettingsComponent::getDynamicSleepTimeUntilSleep) + .add() + .build(); + + private int solverIterations = PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS; + private int stabilizationIterations = PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS; + private float dynamicSleepLinearThreshold = + PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_LINEAR_THRESHOLD; + private float dynamicSleepAngularThreshold = + PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_ANGULAR_THRESHOLD; + private float dynamicSleepTimeUntilSleep = + PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_TIME_UNTIL_SLEEP; + + public SolverSettingsComponent() { + } + + public SolverSettingsComponent(@Nonnull PhysicsSolverSettings settings) { + solverIterations = settings.getSolverIterations(); + stabilizationIterations = settings.getStabilizationIterations(); + dynamicSleepLinearThreshold = settings.getDynamicSleepLinearThreshold(); + dynamicSleepAngularThreshold = settings.getDynamicSleepAngularThreshold(); + dynamicSleepTimeUntilSleep = settings.getDynamicSleepTimeUntilSleep(); + } + + public SolverSettingsComponent(int solverIterations, + int stabilizationIterations, + float dynamicSleepLinearThreshold, + float dynamicSleepAngularThreshold, + float dynamicSleepTimeUntilSleep) { + this.solverIterations = solverIterations; + this.stabilizationIterations = stabilizationIterations; + this.dynamicSleepLinearThreshold = dynamicSleepLinearThreshold; + this.dynamicSleepAngularThreshold = dynamicSleepAngularThreshold; + this.dynamicSleepTimeUntilSleep = dynamicSleepTimeUntilSleep; + } + + public int getSolverIterations() { + return solverIterations; + } + + public void setSolverIterations(int solverIterations) { + this.solverIterations = solverIterations; + } + + public int getStabilizationIterations() { + return stabilizationIterations; + } + + public void setStabilizationIterations(int stabilizationIterations) { + this.stabilizationIterations = stabilizationIterations; + } + + public float getDynamicSleepLinearThreshold() { + return dynamicSleepLinearThreshold; + } + + public void setDynamicSleepLinearThreshold(float dynamicSleepLinearThreshold) { + this.dynamicSleepLinearThreshold = dynamicSleepLinearThreshold; + } + + public float getDynamicSleepAngularThreshold() { + return dynamicSleepAngularThreshold; + } + + public void setDynamicSleepAngularThreshold(float dynamicSleepAngularThreshold) { + this.dynamicSleepAngularThreshold = dynamicSleepAngularThreshold; + } + + public float getDynamicSleepTimeUntilSleep() { + return dynamicSleepTimeUntilSleep; + } + + public void setDynamicSleepTimeUntilSleep(float dynamicSleepTimeUntilSleep) { + this.dynamicSleepTimeUntilSleep = dynamicSleepTimeUntilSleep; + } + + public void copyTo(@Nonnull PhysicsSpaceSettings settings) { + copyTo(settings.getSolverSettings()); + } + + public void copyTo(@Nonnull PhysicsSolverSettings settings) { + settings.setSolverIterations(solverIterations); + settings.setStabilizationIterations(stabilizationIterations); + settings.setDynamicSleepTuning(dynamicSleepLinearThreshold, + dynamicSleepAngularThreshold, + dynamicSleepTimeUntilSleep); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.solverSettingsComponentType(); + } + + @Nonnull + @Override + public SolverSettingsComponent clone() { + return new SolverSettingsComponent(solverIterations, + stabilizationIterations, + dynamicSleepLinearThreshold, + dynamicSleepAngularThreshold, + dynamicSleepTimeUntilSleep); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java new file mode 100644 index 00000000..238e121b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java @@ -0,0 +1,231 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Authored detached visual materialization policy for one PhysicsStore space row. + */ +public final class VisualMaterializationSettingsComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(VisualMaterializationSettingsComponent.class, + VisualMaterializationSettingsComponent::new) + .append(new KeyedCodec<>("DetachedVisualMaterializationEnabled", Codec.BOOLEAN, false), + (component, value) -> component.detachedVisualMaterializationEnabled = + value != null + ? value + : PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED, + VisualMaterializationSettingsComponent::isDetachedVisualMaterializationEnabled) + .add() + .append(new KeyedCodec<>("DetachedVisualMaterializationRadius", Codec.INTEGER, false), + (component, value) -> component.detachedVisualMaterializationRadius = + value != null + ? value + : PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS, + VisualMaterializationSettingsComponent::getDetachedVisualMaterializationRadius) + .add() + .append(new KeyedCodec<>("DetachedVisualDematerializationRadius", Codec.INTEGER, false), + (component, value) -> component.detachedVisualDematerializationRadius = + value != null + ? value + : PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS, + VisualMaterializationSettingsComponent::getDetachedVisualDematerializationRadius) + .add() + .append(new KeyedCodec<>("DetachedVisualMaxSpawnsPerTick", Codec.INTEGER, false), + (component, value) -> component.detachedVisualMaxSpawnsPerTick = value != null + ? value + : PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK, + VisualMaterializationSettingsComponent::getDetachedVisualMaxSpawnsPerTick) + .add() + .append(new KeyedCodec<>("DetachedVisualMaxMaterialized", Codec.INTEGER, false), + (component, value) -> component.detachedVisualMaxMaterialized = value != null + ? value + : PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED, + VisualMaterializationSettingsComponent::getDetachedVisualMaxMaterialized) + .add() + .append(new KeyedCodec<>("DetachedVisualInterestRefreshIntervalTicks", Codec.INTEGER, false), + (component, value) -> component.detachedVisualInterestRefreshIntervalTicks = + value != null + ? value + : PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS, + VisualMaterializationSettingsComponent::getDetachedVisualInterestRefreshIntervalTicks) + .add() + .append(new KeyedCodec<>("DetachedVisualCandidateRefreshIntervalTicks", Codec.INTEGER, false), + (component, value) -> component.detachedVisualCandidateRefreshIntervalTicks = + value != null + ? value + : PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS, + VisualMaterializationSettingsComponent::getDetachedVisualCandidateRefreshIntervalTicks) + .add() + .append(new KeyedCodec<>("DetachedVisualVisibilityCheckIntervalTicks", Codec.INTEGER, false), + (component, value) -> component.detachedVisualVisibilityCheckIntervalTicks = + value != null + ? value + : PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS, + VisualMaterializationSettingsComponent::getDetachedVisualVisibilityCheckIntervalTicks) + .add() + .append(new KeyedCodec<>("DetachedVisualBlockType", Codec.STRING, false), + (component, value) -> component.detachedVisualBlockType = value != null + ? value + : PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE, + VisualMaterializationSettingsComponent::getDetachedVisualBlockType) + .add() + .build(); + + private boolean detachedVisualMaterializationEnabled = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED; + private int detachedVisualMaterializationRadius = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS; + private int detachedVisualDematerializationRadius = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS; + private int detachedVisualMaxSpawnsPerTick = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK; + private int detachedVisualMaxMaterialized = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED; + private int detachedVisualInterestRefreshIntervalTicks = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS; + private int detachedVisualCandidateRefreshIntervalTicks = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS; + private int detachedVisualVisibilityCheckIntervalTicks = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS; + @Nonnull + private String detachedVisualBlockType = + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; + + public VisualMaterializationSettingsComponent() { + } + + public VisualMaterializationSettingsComponent( + @Nonnull PhysicsVisualMaterializationSettings settings) { + detachedVisualMaterializationEnabled = + settings.isDetachedVisualMaterializationEnabled(); + detachedVisualMaterializationRadius = + settings.getDetachedVisualMaterializationRadius(); + detachedVisualDematerializationRadius = + settings.getDetachedVisualDematerializationRadius(); + detachedVisualMaxSpawnsPerTick = settings.getDetachedVisualMaxSpawnsPerTick(); + detachedVisualMaxMaterialized = settings.getDetachedVisualMaxMaterialized(); + detachedVisualInterestRefreshIntervalTicks = + settings.getDetachedVisualInterestRefreshIntervalTicks(); + detachedVisualCandidateRefreshIntervalTicks = + settings.getDetachedVisualCandidateRefreshIntervalTicks(); + detachedVisualVisibilityCheckIntervalTicks = + settings.getDetachedVisualVisibilityCheckIntervalTicks(); + detachedVisualBlockType = settings.getDetachedVisualBlockType(); + } + + private VisualMaterializationSettingsComponent(boolean detachedVisualMaterializationEnabled, + int detachedVisualMaterializationRadius, + int detachedVisualDematerializationRadius, + int detachedVisualMaxSpawnsPerTick, + int detachedVisualMaxMaterialized, + int detachedVisualInterestRefreshIntervalTicks, + int detachedVisualCandidateRefreshIntervalTicks, + int detachedVisualVisibilityCheckIntervalTicks, + @Nonnull String detachedVisualBlockType) { + this.detachedVisualMaterializationEnabled = detachedVisualMaterializationEnabled; + this.detachedVisualMaterializationRadius = detachedVisualMaterializationRadius; + this.detachedVisualDematerializationRadius = detachedVisualDematerializationRadius; + this.detachedVisualMaxSpawnsPerTick = detachedVisualMaxSpawnsPerTick; + this.detachedVisualMaxMaterialized = detachedVisualMaxMaterialized; + this.detachedVisualInterestRefreshIntervalTicks = detachedVisualInterestRefreshIntervalTicks; + this.detachedVisualCandidateRefreshIntervalTicks = + detachedVisualCandidateRefreshIntervalTicks; + this.detachedVisualVisibilityCheckIntervalTicks = + detachedVisualVisibilityCheckIntervalTicks; + this.detachedVisualBlockType = Objects.requireNonNull(detachedVisualBlockType, + "detachedVisualBlockType"); + } + + public boolean isDetachedVisualMaterializationEnabled() { + return detachedVisualMaterializationEnabled; + } + + public int getDetachedVisualMaterializationRadius() { + return detachedVisualMaterializationRadius; + } + + public int getDetachedVisualDematerializationRadius() { + return detachedVisualDematerializationRadius; + } + + public int getDetachedVisualMaxSpawnsPerTick() { + return detachedVisualMaxSpawnsPerTick; + } + + public int getDetachedVisualMaxMaterialized() { + return detachedVisualMaxMaterialized; + } + + public int getDetachedVisualInterestRefreshIntervalTicks() { + return detachedVisualInterestRefreshIntervalTicks; + } + + public int getDetachedVisualCandidateRefreshIntervalTicks() { + return detachedVisualCandidateRefreshIntervalTicks; + } + + public int getDetachedVisualVisibilityCheckIntervalTicks() { + return detachedVisualVisibilityCheckIntervalTicks; + } + + @Nonnull + public String getDetachedVisualBlockType() { + return detachedVisualBlockType; + } + + public void copyTo(@Nonnull PhysicsSpaceSettings settings) { + PhysicsVisualMaterializationSettings target = + settings.getVisualMaterializationSettings(); + target.setDetachedVisualMaterializationEnabled(detachedVisualMaterializationEnabled); + target.setDetachedVisualRadii(detachedVisualMaterializationRadius, + detachedVisualDematerializationRadius); + target.setDetachedVisualMaxSpawnsPerTick(detachedVisualMaxSpawnsPerTick); + target.setDetachedVisualMaxMaterialized(detachedVisualMaxMaterialized); + target.setDetachedVisualInterestRefreshIntervalTicks( + detachedVisualInterestRefreshIntervalTicks); + target.setDetachedVisualCandidateRefreshIntervalTicks( + detachedVisualCandidateRefreshIntervalTicks); + target.setDetachedVisualVisibilityCheckIntervalTicks( + detachedVisualVisibilityCheckIntervalTicks); + target.setDetachedVisualBlockType(detachedVisualBlockType); + } + + @Nonnull + public static ComponentType + getComponentType() { + return PhysicsStoreTypes.visualMaterializationSettingsComponentType(); + } + + @Nonnull + @Override + public VisualMaterializationSettingsComponent clone() { + return new VisualMaterializationSettingsComponent( + detachedVisualMaterializationEnabled, + detachedVisualMaterializationRadius, + detachedVisualDematerializationRadius, + detachedVisualMaxSpawnsPerTick, + detachedVisualMaxMaterialized, + detachedVisualInterestRefreshIntervalTicks, + detachedVisualCandidateRefreshIntervalTicks, + detachedVisualVisibilityCheckIntervalTicks, + detachedVisualBlockType); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java new file mode 100644 index 00000000..ebbc447a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java @@ -0,0 +1,292 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Authored visual synchronization policy for one PhysicsStore space row. + */ +public final class VisualSyncSettingsComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + VisualSyncSettingsComponent.class, + VisualSyncSettingsComponent::new) + .append(new KeyedCodec<>("VisualFullSyncRadius", Codec.INTEGER, false), + (component, value) -> component.visualFullSyncRadius = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_FULL_SYNC_RADIUS, + VisualSyncSettingsComponent::getVisualFullSyncRadius) + .add() + .append(new KeyedCodec<>("VisualMaxSyncRadius", Codec.INTEGER, false), + (component, value) -> component.visualMaxSyncRadius = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_MAX_SYNC_RADIUS, + VisualSyncSettingsComponent::getVisualMaxSyncRadius) + .add() + .append(new KeyedCodec<>("VisualFarSyncCutoffEnabled", Codec.BOOLEAN, false), + (component, value) -> component.visualFarSyncCutoffEnabled = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED, + VisualSyncSettingsComponent::isVisualFarSyncCutoffEnabled) + .add() + .append(new KeyedCodec<>("VisualMidSyncIntervalTicks", Codec.INTEGER, false), + (component, value) -> component.visualMidSyncIntervalTicks = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS, + VisualSyncSettingsComponent::getVisualMidSyncIntervalTicks) + .add() + .append(new KeyedCodec<>("VisualFarSyncIntervalTicks", Codec.INTEGER, false), + (component, value) -> component.visualFarSyncIntervalTicks = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS, + VisualSyncSettingsComponent::getVisualFarSyncIntervalTicks) + .add() + .append(new KeyedCodec<>("VisualOcclusionMode", + new EnumCodec<>(VisualOcclusionMode.class), + false), + (component, value) -> component.visualOcclusionMode = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_MODE, + VisualSyncSettingsComponent::getVisualOcclusionMode) + .add() + .append(new KeyedCodec<>("VisualOcclusionRaycastsPerTick", Codec.INTEGER, false), + (component, value) -> component.visualOcclusionRaycastsPerTick = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK, + VisualSyncSettingsComponent::getVisualOcclusionRaycastsPerTick) + .add() + .append(new KeyedCodec<>("VisualOcclusionCacheTicks", Codec.INTEGER, false), + (component, value) -> component.visualOcclusionCacheTicks = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS, + VisualSyncSettingsComponent::getVisualOcclusionCacheTicks) + .add() + .append(new KeyedCodec<>("VisualSnapshotPredictionEnabled", Codec.BOOLEAN, false), + (component, value) -> component.visualSnapshotPredictionEnabled = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED, + VisualSyncSettingsComponent::isVisualSnapshotPredictionEnabled) + .add() + .append(new KeyedCodec<>("VisualSnapshotPredictionMaxSeconds", Codec.FLOAT, false), + (component, value) -> component.visualSnapshotPredictionMaxSeconds = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS, + VisualSyncSettingsComponent::getVisualSnapshotPredictionMaxSeconds) + .add() + .append(new KeyedCodec<>("VisualSnapshotSmoothingEnabled", Codec.BOOLEAN, false), + (component, value) -> component.visualSnapshotSmoothingEnabled = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED, + VisualSyncSettingsComponent::isVisualSnapshotSmoothingEnabled) + .add() + .append(new KeyedCodec<>("VisualSnapshotSmoothingRate", Codec.FLOAT, false), + (component, value) -> component.visualSnapshotSmoothingRate = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE, + VisualSyncSettingsComponent::getVisualSnapshotSmoothingRate) + .add() + .append(new KeyedCodec<>("EntityVisualSyncCullingEnabled", Codec.BOOLEAN, false), + (component, value) -> component.entityVisualSyncCullingEnabled = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED, + VisualSyncSettingsComponent::isEntityVisualSyncCullingEnabled) + .add() + .append(new KeyedCodec<>("VisualVisibilityCullingEnabled", Codec.BOOLEAN, false), + (component, value) -> component.visualVisibilityCullingEnabled = value != null + ? value + : PhysicsVisualSyncSettings.DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED, + VisualSyncSettingsComponent::isVisualVisibilityCullingEnabled) + .add() + .build(); + + private int visualFullSyncRadius = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_FULL_SYNC_RADIUS; + private int visualMaxSyncRadius = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_MAX_SYNC_RADIUS; + private boolean visualFarSyncCutoffEnabled = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED; + private int visualMidSyncIntervalTicks = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS; + private int visualFarSyncIntervalTicks = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS; + @Nonnull + private VisualOcclusionMode visualOcclusionMode = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_MODE; + private int visualOcclusionRaycastsPerTick = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK; + private int visualOcclusionCacheTicks = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS; + private boolean visualSnapshotPredictionEnabled = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED; + private float visualSnapshotPredictionMaxSeconds = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS; + private boolean visualSnapshotSmoothingEnabled = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED; + private float visualSnapshotSmoothingRate = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE; + private boolean entityVisualSyncCullingEnabled = + PhysicsVisualSyncSettings.DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED; + private boolean visualVisibilityCullingEnabled = + PhysicsVisualSyncSettings.DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED; + + public VisualSyncSettingsComponent() { + } + + public VisualSyncSettingsComponent(@Nonnull PhysicsVisualSyncSettings settings) { + visualFullSyncRadius = settings.getVisualFullSyncRadius(); + visualMaxSyncRadius = settings.getVisualMaxSyncRadius(); + visualFarSyncCutoffEnabled = settings.isVisualFarSyncCutoffEnabled(); + visualMidSyncIntervalTicks = settings.getVisualMidSyncIntervalTicks(); + visualFarSyncIntervalTicks = settings.getVisualFarSyncIntervalTicks(); + visualOcclusionMode = settings.getVisualOcclusionMode(); + visualOcclusionRaycastsPerTick = settings.getVisualOcclusionRaycastsPerTick(); + visualOcclusionCacheTicks = settings.getVisualOcclusionCacheTicks(); + visualSnapshotPredictionEnabled = settings.isVisualSnapshotPredictionEnabled(); + visualSnapshotPredictionMaxSeconds = settings.getVisualSnapshotPredictionMaxSeconds(); + visualSnapshotSmoothingEnabled = settings.isVisualSnapshotSmoothingEnabled(); + visualSnapshotSmoothingRate = settings.getVisualSnapshotSmoothingRate(); + entityVisualSyncCullingEnabled = settings.isEntityVisualSyncCullingEnabled(); + visualVisibilityCullingEnabled = settings.isVisualVisibilityCullingEnabled(); + } + + private VisualSyncSettingsComponent(int visualFullSyncRadius, + int visualMaxSyncRadius, + boolean visualFarSyncCutoffEnabled, + int visualMidSyncIntervalTicks, + int visualFarSyncIntervalTicks, + @Nonnull VisualOcclusionMode visualOcclusionMode, + int visualOcclusionRaycastsPerTick, + int visualOcclusionCacheTicks, + boolean visualSnapshotPredictionEnabled, + float visualSnapshotPredictionMaxSeconds, + boolean visualSnapshotSmoothingEnabled, + float visualSnapshotSmoothingRate, + boolean entityVisualSyncCullingEnabled, + boolean visualVisibilityCullingEnabled) { + this.visualFullSyncRadius = visualFullSyncRadius; + this.visualMaxSyncRadius = visualMaxSyncRadius; + this.visualFarSyncCutoffEnabled = visualFarSyncCutoffEnabled; + this.visualMidSyncIntervalTicks = visualMidSyncIntervalTicks; + this.visualFarSyncIntervalTicks = visualFarSyncIntervalTicks; + this.visualOcclusionMode = Objects.requireNonNull(visualOcclusionMode, + "visualOcclusionMode"); + this.visualOcclusionRaycastsPerTick = visualOcclusionRaycastsPerTick; + this.visualOcclusionCacheTicks = visualOcclusionCacheTicks; + this.visualSnapshotPredictionEnabled = visualSnapshotPredictionEnabled; + this.visualSnapshotPredictionMaxSeconds = visualSnapshotPredictionMaxSeconds; + this.visualSnapshotSmoothingEnabled = visualSnapshotSmoothingEnabled; + this.visualSnapshotSmoothingRate = visualSnapshotSmoothingRate; + this.entityVisualSyncCullingEnabled = entityVisualSyncCullingEnabled; + this.visualVisibilityCullingEnabled = visualVisibilityCullingEnabled; + } + + public int getVisualFullSyncRadius() { + return visualFullSyncRadius; + } + + public int getVisualMaxSyncRadius() { + return visualMaxSyncRadius; + } + + public boolean isVisualFarSyncCutoffEnabled() { + return visualFarSyncCutoffEnabled; + } + + public int getVisualMidSyncIntervalTicks() { + return visualMidSyncIntervalTicks; + } + + public int getVisualFarSyncIntervalTicks() { + return visualFarSyncIntervalTicks; + } + + @Nonnull + public VisualOcclusionMode getVisualOcclusionMode() { + return visualOcclusionMode; + } + + public int getVisualOcclusionRaycastsPerTick() { + return visualOcclusionRaycastsPerTick; + } + + public int getVisualOcclusionCacheTicks() { + return visualOcclusionCacheTicks; + } + + public boolean isVisualSnapshotPredictionEnabled() { + return visualSnapshotPredictionEnabled; + } + + public float getVisualSnapshotPredictionMaxSeconds() { + return visualSnapshotPredictionMaxSeconds; + } + + public boolean isVisualSnapshotSmoothingEnabled() { + return visualSnapshotSmoothingEnabled; + } + + public float getVisualSnapshotSmoothingRate() { + return visualSnapshotSmoothingRate; + } + + public boolean isEntityVisualSyncCullingEnabled() { + return entityVisualSyncCullingEnabled; + } + + public boolean isVisualVisibilityCullingEnabled() { + return visualVisibilityCullingEnabled; + } + + public void copyTo(@Nonnull PhysicsSpaceSettings settings) { + PhysicsVisualSyncSettings target = settings.getVisualSyncSettings(); + target.setVisualSyncRadii(visualFullSyncRadius, visualMaxSyncRadius); + target.setVisualFarSyncCutoffEnabled(visualFarSyncCutoffEnabled); + target.setVisualMidSyncIntervalTicks(visualMidSyncIntervalTicks); + target.setVisualFarSyncIntervalTicks(visualFarSyncIntervalTicks); + target.setVisualOcclusionMode(visualOcclusionMode); + target.setVisualOcclusionRaycastsPerTick(visualOcclusionRaycastsPerTick); + target.setVisualOcclusionCacheTicks(visualOcclusionCacheTicks); + target.setVisualSnapshotPredictionEnabled(visualSnapshotPredictionEnabled); + target.setVisualSnapshotPredictionMaxSeconds(visualSnapshotPredictionMaxSeconds); + target.setVisualSnapshotSmoothingEnabled(visualSnapshotSmoothingEnabled); + target.setVisualSnapshotSmoothingRate(visualSnapshotSmoothingRate); + target.setEntityVisualSyncCullingEnabled(entityVisualSyncCullingEnabled); + target.setVisualVisibilityCullingEnabled(visualVisibilityCullingEnabled); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.visualSyncSettingsComponentType(); + } + + @Nonnull + @Override + public VisualSyncSettingsComponent clone() { + return new VisualSyncSettingsComponent(visualFullSyncRadius, + visualMaxSyncRadius, + visualFarSyncCutoffEnabled, + visualMidSyncIntervalTicks, + visualFarSyncIntervalTicks, + visualOcclusionMode, + visualOcclusionRaycastsPerTick, + visualOcclusionCacheTicks, + visualSnapshotPredictionEnabled, + visualSnapshotPredictionMaxSeconds, + visualSnapshotSmoothingEnabled, + visualSnapshotSmoothingRate, + entityVisualSyncCullingEnabled, + visualVisibilityCullingEnabled); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java index 2db877c2..44f6f0fb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java @@ -9,6 +9,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -30,6 +32,14 @@ public final class WorldCollisionComponent implements Component { (component, value) -> component.nativeVoxelTerrainEnabled = value != null && value, WorldCollisionComponent::isNativeVoxelTerrainEnabled) .add() + .append(new KeyedCodec<>("EntityChunkBoundaryMode", + new EnumCodec<>(EntityChunkBoundaryMode.class), + false), + (component, value) -> component.entityChunkBoundaryMode = value != null + ? value + : PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + WorldCollisionComponent::getEntityChunkBoundaryMode) + .add() .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), (component, value) -> component.radius = value != null ? value @@ -64,6 +74,9 @@ public final class WorldCollisionComponent implements Component { @Nonnull private WorldCollisionMode mode = WorldCollisionMode.NONE; + @Nonnull + private EntityChunkBoundaryMode entityChunkBoundaryMode = + PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelTerrainEnabled = PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; private int radius = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS; @@ -75,7 +88,36 @@ public final class WorldCollisionComponent implements Component { public WorldCollisionComponent() { } + public WorldCollisionComponent(@Nonnull PhysicsWorldCollisionSettings settings) { + this(settings.getWorldCollisionMode(), + settings.getEntityChunkBoundaryMode(), + settings.isNativeVoxelTerrainEnabled(), + settings.getWorldCollisionRadius(), + settings.getWorldCollisionBodyRadius(), + settings.getWorldCollisionTtlTicks(), + settings.getTerrainFriction(), + settings.getTerrainRestitution()); + } + public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this(mode, + PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } + + public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, int radius, int bodyRadius, @@ -83,6 +125,8 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, float terrainFriction, float terrainRestitution) { this.mode = Objects.requireNonNull(mode, "mode"); + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; this.radius = radius; this.bodyRadius = bodyRadius; @@ -100,6 +144,17 @@ public void setMode(@Nonnull WorldCollisionMode mode) { this.mode = Objects.requireNonNull(mode, "mode"); } + @Nonnull + public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { + return entityChunkBoundaryMode; + } + + public void setEntityChunkBoundaryMode( + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); + } + public boolean isNativeVoxelTerrainEnabled() { return nativeVoxelTerrainEnabled; } @@ -148,6 +203,20 @@ public void setTerrainRestitution(float terrainRestitution) { this.terrainRestitution = terrainRestitution; } + public void copyTo(@Nonnull PhysicsSpaceSettings settings) { + copyTo(settings.getWorldCollisionSettings()); + } + + public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { + settings.setWorldCollisionMode(mode); + settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); + settings.setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); + settings.setWorldCollisionRadius(radius); + settings.setWorldCollisionBodyRadius(bodyRadius); + settings.setWorldCollisionTtlTicks(ttlTicks); + settings.setTerrainMaterial(terrainFriction, terrainRestitution); + } + @Nonnull public static ComponentType getComponentType() { return PhysicsStoreTypes.worldCollisionComponentType(); @@ -157,6 +226,7 @@ public static ComponentType getComponentT @Override public WorldCollisionComponent clone() { return new WorldCollisionComponent(mode, + entityChunkBoundaryMode, nativeVoxelTerrainEnabled, radius, bodyRadius, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java index e42698f3..d3d77350 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java @@ -1,8 +1,12 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -12,28 +16,39 @@ */ public record SpaceSettingsRequest(@Nonnull UUID requestUuid, @Nonnull UUID spaceUuid, - @Nonnull WorldCollisionComponent worldCollision) + @Nonnull WorldCollisionComponent worldCollision, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) implements PhysicsStoreRequest { public SpaceSettingsRequest { Objects.requireNonNull(requestUuid, "requestUuid"); Objects.requireNonNull(spaceUuid, "spaceUuid"); worldCollision = Objects.requireNonNull(worldCollision, "worldCollision").clone(); + solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); + visualSyncSettings = Objects.requireNonNull(visualSyncSettings, + "visualSyncSettings").clone(); + visualMaterializationSettings = Objects.requireNonNull(visualMaterializationSettings, + "visualMaterializationSettings").clone(); + collisionLodSettings = Objects.requireNonNull(collisionLodSettings, + "collisionLodSettings").clone(); + extensionSettings = Objects.requireNonNull(extensionSettings, "extensionSettings").clone(); } @Nonnull public static SpaceSettingsRequest of(@Nonnull UUID spaceUuid, @Nonnull PhysicsSpaceSettings settings) { - PhysicsWorldCollisionSettings worldCollisionSettings = - Objects.requireNonNull(settings, "settings").getWorldCollisionSettings(); + Objects.requireNonNull(settings, "settings"); return new SpaceSettingsRequest(UUID.randomUUID(), spaceUuid, - new WorldCollisionComponent(worldCollisionSettings.getWorldCollisionMode(), - worldCollisionSettings.isNativeVoxelTerrainEnabled(), - worldCollisionSettings.getWorldCollisionRadius(), - worldCollisionSettings.getWorldCollisionBodyRadius(), - worldCollisionSettings.getWorldCollisionTtlTicks(), - worldCollisionSettings.getTerrainFriction(), - worldCollisionSettings.getTerrainRestitution())); + new WorldCollisionComponent(settings.getWorldCollisionSettings()), + new SolverSettingsComponent(settings.getSolverSettings()), + new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), + new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), + new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), + new ExtensionSettingsComponent(settings.getExtensionSettings())); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java index 5edb081e..8fef2e40 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java @@ -2,10 +2,14 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -18,7 +22,12 @@ public record SpaceUpsertRequest(@Nonnull UUID requestUuid, @Nonnull UUID spaceUuid, @Nonnull SpaceId compatibilitySpaceId, @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent worldCollision) + @Nonnull WorldCollisionComponent worldCollision, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) implements PhysicsStoreRequest { public SpaceUpsertRequest { @@ -27,6 +36,14 @@ public record SpaceUpsertRequest(@Nonnull UUID requestUuid, Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); space = Objects.requireNonNull(space, "space").clone(); worldCollision = Objects.requireNonNull(worldCollision, "worldCollision").clone(); + solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); + visualSyncSettings = Objects.requireNonNull(visualSyncSettings, + "visualSyncSettings").clone(); + visualMaterializationSettings = Objects.requireNonNull(visualMaterializationSettings, + "visualMaterializationSettings").clone(); + collisionLodSettings = Objects.requireNonNull(collisionLodSettings, + "collisionLodSettings").clone(); + extensionSettings = Objects.requireNonNull(extensionSettings, "extensionSettings").clone(); } @Nonnull @@ -34,20 +51,16 @@ public static SpaceUpsertRequest of(@Nonnull UUID spaceUuid, @Nonnull SpaceId compatibilitySpaceId, @Nonnull BackendId backendId, @Nonnull PhysicsSpaceSettings settings) { - PhysicsWorldCollisionSettings worldCollisionSettings = - Objects.requireNonNull(settings, "settings").getWorldCollisionSettings(); - WorldCollisionComponent worldCollision = new WorldCollisionComponent( - worldCollisionSettings.getWorldCollisionMode(), - worldCollisionSettings.isNativeVoxelTerrainEnabled(), - worldCollisionSettings.getWorldCollisionRadius(), - worldCollisionSettings.getWorldCollisionBodyRadius(), - worldCollisionSettings.getWorldCollisionTtlTicks(), - worldCollisionSettings.getTerrainFriction(), - worldCollisionSettings.getTerrainRestitution()); + Objects.requireNonNull(settings, "settings"); return new SpaceUpsertRequest(UUID.randomUUID(), spaceUuid, compatibilitySpaceId, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), - worldCollision); + new WorldCollisionComponent(settings.getWorldCollisionSettings()), + new SolverSettingsComponent(settings.getSolverSettings()), + new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), + new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), + new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), + new ExtensionSettingsComponent(settings.getExtensionSettings())); } } From 3e691bf0bb7c1e931a47a56415e3474ca686ef3b Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 19:22:49 +0200 Subject: [PATCH 031/534] fix(core): harden physics store query bridge Signed-off-by: Blovien --- .../queries/PhysicsStoreQueryBridge.java | 101 +++++++++++++++--- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java index c348bfb2..0f78e2d3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java @@ -18,15 +18,20 @@ import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; +import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.simulation.query.CcdSupportQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQueryHandle; import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastAllQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.RuntimeJointCountQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.SolverCapabilityQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.query.UnsupportedCcdSpacesQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; import java.util.ArrayList; @@ -64,36 +69,41 @@ public static Optional> tryQuery(@Nonnull Store rigidBodyState(@Nonnull Store store, @Nonnull RigidBodyStateQuery query) { PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); PhysicsStoreBodySnapshot body = snapshots.getBody(query.bodyKey().value()); if (body == null) { - return null; + return Optional.empty(); } return Optional.of(new RigidBodyStateView(query.bodyKey(), body.bodyType(), RigidBodyPose.of(body.position(), body.rotation()))); } - @Nullable + @Nonnull private static Optional raycastClosest(@Nonnull Store store, @Nonnull RaycastClosestQuery query) { SpaceQueryContext space = space(store, query.spaceId()); if (space == null) { - return null; + return Optional.empty(); } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); RayHitCapture hit = new RayHitCapture(runtime); @@ -110,12 +120,12 @@ private static Optional raycastClosest(@Nonnull Store store, @Nonnull RaycastClosestBatchQuery query) { SpaceQueryContext space = space(store, query.spaceId()); if (space == null) { - return null; + return new RaycastClosestBatchResult(new RaycastHitView[query.rayCount()]); } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); int rayCount = query.rayCount(); @@ -156,12 +166,12 @@ private static RaycastClosestBatchResult raycastClosestBatch(@Nonnull Store raycastAll(@Nonnull Store store, @Nonnull RaycastAllQuery query) { SpaceQueryContext space = space(store, query.spaceId()); if (space == null) { - return null; + return List.of(); } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); Vector3f from = query.from(); @@ -195,14 +205,13 @@ private static List raycastAll(@Nonnull Store stor return hits.isEmpty() ? List.of() : List.copyOf(hits); } - @Nullable - private static Integer spaceBodyCount(@Nonnull Store store, + private static int spaceBodyCount(@Nonnull Store store, @Nonnull SpaceBodyCountQuery query) { SpaceQueryContext space = space(store, query.spaceId()); - return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : null; + return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; } - @Nullable + @Nonnull private static List spaceSummary(@Nonnull Store store, @Nonnull SpaceSummaryQuery query) { PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); @@ -214,7 +223,7 @@ private static List spaceSummary(@Nonnull Store stor if (space != null) { summaries.add(summary(compatibility, space)); } - return summaries.isEmpty() ? null : List.copyOf(summaries); + return summaries.isEmpty() ? List.of() : List.copyOf(summaries); } runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { SpaceId spaceId = compatibility.getSpaceId(spaceUuid); @@ -225,7 +234,65 @@ private static List spaceSummary(@Nonnull Store stor backendRuntime.jointCount(spaceHandle.value()))); } }); - return summaries.isEmpty() ? null : List.copyOf(summaries); + return summaries.isEmpty() ? List.of() : List.copyOf(summaries); + } + + private static boolean ccdSupported(@Nonnull Store store, + @Nonnull CcdSupportQuery query) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + boolean[] supported = {false}; + runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { + if (backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + supported[0] = true; + } + }); + return supported[0]; + } + + @Nonnull + private static List unsupportedCcdSpaces(@Nonnull Store store, + @Nonnull UnsupportedCcdSpacesQuery query) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + List spaces = new ArrayList<>(); + runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { + if (backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + return; + } + spaces.add(summary(compatibility, + new SpaceQueryContext(spaceUuid, backendId, spaceHandle, backendRuntime))); + }); + return spaces.isEmpty() ? List.of() : List.copyOf(spaces); + } + + @Nonnull + private static SolverCapabilitySummary solverCapability(@Nonnull Store store, + @Nonnull SolverCapabilityQuery query) { + SpaceQueryContext space = requireSpace(store, query.spaceId()); + return new SolverCapabilitySummary(query.spaceId(), + space.backendId().value(), + space.backendRuntime().supportsSolverTuning(space.spaceHandle().value()), + space.backendRuntime().supportsActivationTuning(space.spaceHandle().value())); + } + + private static int runtimeJointCount(@Nonnull Store store, + @Nonnull RuntimeJointCountQuery query) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + int[] count = {0}; + runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> + count[0] += backendRuntime.jointCount(spaceHandle.value())); + return count[0]; + } + + @Nonnull + private static SpaceQueryContext requireSpace(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + SpaceQueryContext space = space(store, spaceId); + if (space == null) { + throw new IllegalArgumentException("Physics space id=" + spaceId + " is not registered"); + } + return space; } @Nonnull From d41e73acd64fa29f180eb24e8039c0b8c4d36c45 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 19:32:06 +0200 Subject: [PATCH 032/534] perf(core): cache physics store snapshot metadata Signed-off-by: Blovien --- .../queries/PhysicsStoreQueryBridge.java | 3 +- .../resources/PhysicsRuntimeResource.java | 34 +++++++++++++++++-- .../systems/BodyBindingSystem.java | 2 +- .../CompletedStepPublicationSystem.java | 27 ++++----------- 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java index 0f78e2d3..964844fb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java @@ -12,7 +12,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; @@ -346,7 +345,7 @@ private static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, float normalZ, float fraction, float distance) { - BodyHitMetadata metadata = runtime.getBodyHitMetadata(new BackendBodyHandle(bodyId)); + BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyId); return new RaycastHitView(metadata != null ? metadata.bodyKey() : null, metadata != null ? metadata.bodyType() : PhysicsBodyType.STATIC, pointX, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 50ed32b4..900d2abd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -73,6 +73,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Long2ObjectOpenHashMap bodyHitMetadataByHandle = new Long2ObjectOpenHashMap<>(); @Nonnull + private final Long2ObjectOpenHashMap bodySnapshotMetadataByHandle = + new Long2ObjectOpenHashMap<>(); + @Nonnull private final List pendingBodyOperations = new ArrayList<>(); @Nonnull private final ObjectOpenHashSet pendingSpaceSettings = new ObjectOpenHashSet<>(); @@ -122,19 +125,24 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { if (removed != null) { LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); if (bodyHandles != null) { - bodyHandles.forEach((long bodyHandle) -> bodyHitMetadataByHandle.remove(bodyHandle)); + bodyHandles.forEach((long bodyHandle) -> { + bodyHitMetadataByHandle.remove(bodyHandle); + bodySnapshotMetadataByHandle.remove(bodyHandle); + }); } removeTerrainHandlesForSpace(removed); } } public void putBodyHandle(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle handle) { bodyHandlesByUuid.put(bodyUuid, handle); bodySpaceHandlesByUuid.put(bodyUuid, spaceHandle); bodyHandlesBySpaceHandle.computeIfAbsent(spaceHandle.value(), _ -> new LongArrayList()) .add(handle.value()); + bodySnapshotMetadataByHandle.put(handle.value(), new BodySnapshotMetadata(bodyUuid, spaceUuid)); } @Nullable @@ -159,6 +167,7 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid) { } } bodyHitMetadataByHandle.remove(removed.value()); + bodySnapshotMetadataByHandle.remove(removed.value()); } } @@ -172,13 +181,23 @@ public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, @Nullable public BodyHitMetadata getBodyHitMetadata(@Nonnull BackendBodyHandle handle) { - return bodyHitMetadataByHandle.get(handle.value()); + return getBodyHitMetadata(handle.value()); + } + + @Nullable + public BodyHitMetadata getBodyHitMetadata(long bodyHandle) { + return bodyHitMetadataByHandle.get(bodyHandle); } public void removeBodyHitMetadata(@Nonnull BackendBodyHandle handle) { bodyHitMetadataByHandle.remove(handle.value()); } + @Nullable + public BodySnapshotMetadata getBodySnapshotMetadata(long bodyHandle) { + return bodySnapshotMetadataByHandle.get(bodyHandle); + } + public void enqueuePendingBodyOperation(@Nonnull PendingBodyOperation operation) { pendingBodyOperations.add(Objects.requireNonNull(operation, "operation")); } @@ -321,6 +340,7 @@ public void clear() { terrainPayloadKeysByUuid.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); + bodySnapshotMetadataByHandle.clear(); pendingBodyOperations.clear(); started = false; } @@ -344,6 +364,7 @@ public PhysicsRuntimeResource clone() { bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); + copy.bodySnapshotMetadataByHandle.putAll(bodySnapshotMetadataByHandle); copy.pendingBodyOperations.addAll(pendingBodyOperations); copy.started = started; return copy; @@ -373,6 +394,15 @@ public record BodyHitMetadata(@Nullable RigidBodyKey bodyKey, } } + public record BodySnapshotMetadata(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + + public BodySnapshotMetadata { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + } + } + public record PendingBodyOperation(@Nonnull Kind kind, @Nonnull UUID bodyUuid, @Nullable BackendSpaceHandle spaceHandle, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index d3e0056b..837a96fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -199,7 +199,7 @@ private static void bindBody(@Nonnull Store store, backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); } applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); - runtime.putBodyHandle(bodyUuid, spaceHandle, bodyHandle); + runtime.putBodyHandle(bodyUuid, body.getSpaceUuid(), spaceHandle, bodyHandle); runtime.putBodyHitMetadata(bodyHandle, RigidBodyKey.of(bodyUuid), bodyType, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index c76843e6..51925e36 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; -import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -8,12 +7,9 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import java.util.ArrayList; @@ -36,8 +32,6 @@ public final class CompletedStepPublicationSystem extends TickingSystem store) { PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsSnapshotResource snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()); List bodies = new ArrayList<>(); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> @@ -76,8 +70,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) _, _, _, - _) -> collectBodySnapshot(store, - identity, + _) -> collectBodySnapshot(runtime, bodies, bodyId, bodyTypeCode, @@ -100,8 +93,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) snapshot.publish(new PhysicsStoreSnapshotFrame(nextSequence, dt, bodies)); } - private static void collectBodySnapshot(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, + private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, @Nonnull List bodies, long bodyId, int bodyTypeCode, @@ -120,17 +112,12 @@ private static void collectBodySnapshot(@Nonnull Store store, float angularVelocityZ, float centerOfMassOffsetY, boolean sleeping) { - Ref ref = identity.getByBodyHandle(new BackendBodyHandle(bodyId)); - if (ref == null || !ref.isValid()) { + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + if (metadata == null) { return; } - UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); - BodyComponent body = store.getComponent(ref, BodyComponent.getComponentType()); - if (uuid == null || body == null) { - return; - } - bodies.add(new PhysicsStoreBodySnapshot(uuid.getUuid(), - body.getSpaceUuid(), + bodies.add(new PhysicsStoreBodySnapshot(metadata.bodyUuid(), + metadata.spaceUuid(), BackendRuntimeCodes.bodyType(bodyTypeCode), new Vector3f(positionX, positionY, positionZ), new Quaternionf(rotationX, rotationY, rotationZ, rotationW), From 3010672431ce6a532cca215e47559280617ce467 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 19:39:32 +0200 Subject: [PATCH 033/534] feat(examples): batch author bodies through physics store Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 9e18b6fa..21b204ca 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -48,8 +48,10 @@ import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.List; import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; @@ -595,34 +597,37 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store - commands.spawnBodies(batch.size(), - spaceId, + World world = store.getExternalData().getWorld(); + UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + if (spaceUuid == null) { + throw new IllegalStateException("Cannot spawn block body batch because the target space is not " + + "bound in PhysicsStore: " + spaceId.value()); + } + + List requests = new ArrayList<>(batch.size()); + for (int i = 0; i < batch.size(); i++) { + RigidBodyKey bodyKey = batch.bodyKey(i); + requests.add(bodyUpsertRequest(spaceUuid, + bodyKey.value(), + new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, mass, - PhysicsBodyType.DYNAMIC, settings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT, - spawns -> { - for (int i = 0; i < batch.size(); i++) { - spawns.body(batch.bodyKeyMostSignificantBits(i), - batch.bodyKeyLeastSignificantBits(i), - batch.positionX(i), - batch.positionY(i), - batch.positionZ(i)); - } - })), "spawn attached block bodies"); + null)); + } + + long commandStartNanos = System.nanoTime(); + PhysicsStoreAccess.enqueueAll(world, requests); long commandApplyNanos = System.nanoTime() - commandStartNanos; long entityAttachStartNanos = System.nanoTime(); SpawnedBlockBody[] spawned = collectBodies ? new SpawnedBlockBody[batch.size()] : null; for (int i = 0; i < batch.size(); i++) { RigidBodyKey bodyKey = batch.bodyKey(i); - Ref entity = spawnAttachedBlockEntity(store, + Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, time, bodyKey, + bodyKey.value(), spaceId, blockType, new Vector3d(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), From 08a02a4de882ead8afb3b608cee60ceeeef6f0ad Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 19:52:11 +0200 Subject: [PATCH 034/534] feat(examples): author raw stress bodies through physics store Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 89 ++++++++++++++++++- .../stress/StressBenchmarkCommand.java | 53 +++++------ .../commands/stress/StressBodiesCommand.java | 64 +++++-------- 3 files changed, 131 insertions(+), 75 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 21b204ca..74e2c93a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -279,14 +279,35 @@ static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { + return bodyUpsertRequest(spaceUuid, + bodyUuid, + bodyCenter, + shape, + mass, + settings, + linearVelocity, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.PERSISTENT); + } + + @Nonnull + private static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { UUID colliderUuid = BodyGraphUuids.collider(bodyUuid); UUID shapeUuid = BodyGraphUuids.shape(bodyUuid); UUID materialUuid = BodyGraphUuids.material(bodyUuid); UUID filterUuid = BodyGraphUuids.filter(bodyUuid); return BodyUpsertRequest.of(bodyUuid, new BodyComponent(spaceUuid, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT), + kind, + persistenceMode), new DynamicsComponent(PhysicsBodyType.DYNAMIC, mass, settings.hasLinearDamping() ? settings.linearDamping() : 0.0f, @@ -318,6 +339,59 @@ static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, collisionFilter(settings)); } + @Nonnull + public static BodyRequestBatchTiming enqueueDynamicBodyBatchMeasured(@Nonnull World world, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Consumer recipe) { + Objects.requireNonNull(world, "world"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(shape, "shape"); + Objects.requireNonNull(settings, "settings"); + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(persistenceMode, "persistenceMode"); + + long setupStartNanos = System.nanoTime(); + BlockBodyBatchRecorder batch = new BlockBodyBatchRecorder(expectedBodies); + Objects.requireNonNull(recipe, "recipe").accept(batch); + batch.seal(); + if (batch.isEmpty()) { + return new BodyRequestBatchTiming(0, 0L, 0L); + } + + UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + if (spaceUuid == null) { + throw new IllegalStateException("Cannot enqueue dynamic body batch because the target space is not " + + "bound in PhysicsStore: " + spaceId.value()); + } + + List requests = new ArrayList<>(batch.size()); + for (int i = 0; i < batch.size(); i++) { + RigidBodyKey bodyKey = batch.bodyKey(i); + requests.add(bodyUpsertRequest(spaceUuid, + bodyKey.value(), + new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), + shape, + mass, + settings, + null, + kind, + persistenceMode)); + } + + long requestStartNanos = System.nanoTime(); + PhysicsStoreAccess.enqueueAll(world, requests); + long requestEnqueueNanos = System.nanoTime() - requestStartNanos; + return new BodyRequestBatchTiming(batch.size(), + System.nanoTime() - setupStartNanos, + requestEnqueueNanos); + } + @Nonnull private static TargetComponent initialTarget(@Nonnull Vector3f bodyCenter, @Nullable Vector3f linearVelocity) { @@ -994,6 +1068,17 @@ public record BlockBodyBatchTiming(int count, } } + public record BodyRequestBatchTiming(int count, + long setupWallNanos, + long requestEnqueueNanos) { + + public BodyRequestBatchTiming { + count = Math.max(0, count); + setupWallNanos = Math.max(0L, setupWallNanos); + requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); + } + } + public record PendingBlockBody(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @Nullable String blockType, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 3082463e..8f5f8d0c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -11,11 +11,9 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -88,7 +86,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, long serverTick = Math.max(0L, world.getTick()); BenchmarkSpawnTiming timing = switch (request.mode()) { - case RAW -> spawnRaw(resource, spaceId, layout, request.count(), serverTick); + case RAW -> spawnRaw(world, spaceId, layout, request.count()); case ENTITY -> spawnEntities(store, resource, spaceId, @@ -97,22 +95,19 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, request.blockType(), serverTick); }; - int afterBodies = resource.query(new SpaceBodyCountQuery(spaceId)) - .completion() - .toCompletableFuture() - .join(); if (timing.spawned() > 0) { - ctx.sender().sendMessage(Message.raw("Spawned " + timing.spawned() + " " + ctx.sender().sendMessage(Message.raw("Queued " + timing.spawned() + " " + request.mode().label() + " benchmark bodies: setupWallMs=" + millis(timing.setupWallNanos()) - + " commandApplyMs=" + millis(timing.commandApplyNanos()) + + " requestEnqueueMs=" + millis(timing.requestEnqueueNanos()) + (timing.entityAttachNanos() > 0L ? " entityAttachMs=" + millis(timing.entityAttachNanos()) : "") + " (" + microsPerBody(timing.setupWallNanos(), timing.spawned()) - + " us/body). Space bodies: " + beforeBodies + " -> " + afterBodies + + " us/body). Space bodies before enqueue: " + beforeBodies + (request.mode() == BenchmarkMode.ENTITY ? ". blockType=" + request.blockType() : "") + + ". Body-count updates are visible after PhysicsStore drains the queued requests" + ". This command measures raw setup/entity attachment; use /impulse-examples stress bodies" + " for detached/detached-view scalability scenarios" + ". For clean comparisons run /impulse clean, /impulse-world-collision perf reset," @@ -145,35 +140,32 @@ private BenchmarkRequest parseRequest(@Nonnull CommandContext ctx) { } @Nonnull - private static BenchmarkSpawnTiming spawnRaw(@Nonnull PhysicsWorldResource resource, + private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull BenchmarkLayout layout, - int count, - long serverTick) { + int count) { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - long bodyKeyRunId = RigidBodyKey.random().mostSignificantBits(); - long commandStartNanos = System.nanoTime(); - ExamplePhysicsUtils.requireApplied(resource.submitCommands(serverTick, 1, commands -> - commands.spawnBodies(count, + ExamplePhysicsUtils.BodyRequestBatchTiming timing = + ExamplePhysicsUtils.enqueueDynamicBodyBatchMeasured(world, spaceId, + count, box, 1.0f, - PhysicsBodyType.DYNAMIC, spawnSettings, PhysicsBodyKind.TEMPORARY, PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { + bodies -> { for (int i = 0; i < count; i++) { - spawns.body(bodyKeyRunId, - i + 1L, - layout.positionX(i), + bodies.addBody(layout.positionX(i), layout.positionY(i), layout.positionZ(i)); } - })), "spawn raw benchmark physics bodies"); - long commandApplyNanos = System.nanoTime() - commandStartNanos; - return new BenchmarkSpawnTiming(count, commandApplyNanos, 0L); + }); + return new BenchmarkSpawnTiming(timing.count(), + timing.setupWallNanos(), + timing.requestEnqueueNanos(), + 0L); } @Nonnull @@ -205,6 +197,7 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st } }); return new BenchmarkSpawnTiming(timing.count(), + timing.commandApplyNanos() + timing.entityAttachNanos(), timing.commandApplyNanos(), timing.entityAttachNanos()); } @@ -271,17 +264,15 @@ private record BenchmarkRequest(BenchmarkMode mode, int count, @Nonnull String b } private record BenchmarkSpawnTiming(int spawned, - long commandApplyNanos, + long setupWallNanos, + long requestEnqueueNanos, long entityAttachNanos) { private BenchmarkSpawnTiming { - commandApplyNanos = Math.max(0L, commandApplyNanos); + setupWallNanos = Math.max(0L, setupWallNanos); + requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } - - private long setupWallNanos() { - return commandApplyNanos + entityAttachNanos; - } } private record BenchmarkLayout(Vector3d origin, int side) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 8316b3ec..f1afa7f1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -11,12 +11,10 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -192,38 +190,31 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, layout.positionZ(i)); } }); - timing = new StressSpawnTiming(batchTiming.commandApplyNanos(), - batchTiming.entityAttachNanos(), - 0L); + timing = new StressSpawnTiming(batchTiming.commandApplyNanos() + batchTiming.entityAttachNanos(), + batchTiming.commandApplyNanos(), + batchTiming.entityAttachNanos()); } else { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = detachedSpawnSettings(collisionPolicy); - long bodyKeyRunId = RigidBodyKey.random().mostSignificantBits(); - long commandStartNanos = System.nanoTime(); - ExamplePhysicsUtils.requireApplied(resource.submitCommands(serverTick, 1, commands -> - commands.spawnBodies(count, + ExamplePhysicsUtils.BodyRequestBatchTiming batchTiming = + ExamplePhysicsUtils.enqueueDynamicBodyBatchMeasured(world, spaceId, + count, box, 1.0f, - PhysicsBodyType.DYNAMIC, spawnSettings, PhysicsBodyKind.BODY, PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { + bodies -> { for (int i = 0; i < count; i++) { - spawns.body(bodyKeyRunId, - i + 1L, - layout.positionX(i), + bodies.addBody(layout.positionX(i), layout.positionY(i), layout.positionZ(i)); } - })), "spawn detached stress bodies"); - timing = new StressSpawnTiming(System.nanoTime() - commandStartNanos, 0L, 0L); - } - if (mode.usesDetachedBodies()) { - long snapshotStartNanos = System.nanoTime(); - resource.refreshBodySnapshots(); - timing = timing.withSnapshotRefreshNanos(System.nanoTime() - snapshotStartNanos); + }); + timing = new StressSpawnTiming(batchTiming.setupWallNanos(), + batchTiming.requestEnqueueNanos(), + 0L); } PhysicsWorldCollisionSettings worldCollisionSettings = settings.getWorldCollisionSettings(); @@ -233,14 +224,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsCollisionLodSettings collisionLodSettings = settings.getCollisionLodSettings(); PhysicsWorldSettings worldSettings = resource.getWorldSettings(); - ctx.sender().sendMessage(Message.raw("Spawned " + count + ctx.sender().sendMessage(Message.raw("Queued " + count + " stress bodies: setupWallMs=" + millis(prewarmNanos + timing.setupWallNanos()) + " prewarmMs=" + millis(prewarmNanos) - + " commandApplyMs=" + millis(timing.commandApplyNanos()) - + (timing.snapshotRefreshNanos() > 0L - ? " snapshotRefreshMs=" + millis(timing.snapshotRefreshNanos()) - : "") + + " requestEnqueueMs=" + millis(timing.requestEnqueueNanos()) + (timing.entityAttachNanos() > 0L ? " entityAttachMs=" + millis(timing.entityAttachNanos()) : "") @@ -254,6 +242,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " maxStepDt=" + String.format(Locale.ROOT, "%.3f", worldSettings.getMaxStepDt()) + " visuals=" + mode.visualDescription() + (mode == StressMode.ENTITY ? " blockType=" + visualSettings.blockType() : "") + + (mode.usesDetachedBodies() + ? " body-count and detached-view snapshots update after PhysicsStore drains queued requests" + : "") + (mode == StressMode.DETACHED_VIEW ? " visualProxyCap=" + visualMaterializationSettings.getDetachedVisualMaxMaterialized() @@ -600,25 +591,14 @@ private record StressVisualSettings(int materializationRadius, boolean smoothingEnabled) { } - private record StressSpawnTiming(long commandApplyNanos, - long entityAttachNanos, - long snapshotRefreshNanos) { + private record StressSpawnTiming(long setupWallNanos, + long requestEnqueueNanos, + long entityAttachNanos) { private StressSpawnTiming { - commandApplyNanos = Math.max(0L, commandApplyNanos); + setupWallNanos = Math.max(0L, setupWallNanos); + requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); - snapshotRefreshNanos = Math.max(0L, snapshotRefreshNanos); - } - - @Nonnull - private StressSpawnTiming withSnapshotRefreshNanos(long snapshotRefreshNanos) { - return new StressSpawnTiming(commandApplyNanos, - entityAttachNanos, - snapshotRefreshNanos); - } - - private long setupWallNanos() { - return commandApplyNanos + entityAttachNanos + snapshotRefreshNanos; } } From 7250811fb791d77463bf9139146539b5c1283aea Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 20:20:40 +0200 Subject: [PATCH 035/534] feat(core): add physics store request fences Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 15 + .../PhysicsRequestQueueResource.java | 138 ++++- .../systems/RequestDrainSystem.java | 537 +++++++++++++----- .../physicsstore/PhysicsStoreAccess.java | 16 + .../PhysicsStoreRequestFenceHandle.java | 33 ++ .../PhysicsStoreRequestFenceResult.java | 47 ++ .../impulse/early/PhysicsStoreHooks.java | 40 +- 7 files changed, 671 insertions(+), 155 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 254eafb1..7aee15c3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -1,8 +1,10 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.registration; import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; @@ -48,6 +50,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.function.Consumer; import javax.annotation.Nonnull; /** @@ -56,12 +59,16 @@ public final class PhysicsStoreRegistration { private static final String REGISTRY_METHOD = "getPhysicsStoreRegistry"; + @Nonnull + private static final Consumer SHUTDOWN_CLEANUP = + PhysicsStoreRegistration::clearRuntimeStateBeforeShutdown; private PhysicsStoreRegistration() { } public static void register(@Nonnull PluginBase plugin) { ComponentRegistryProxy registry = physicsStoreRegistry(plugin); + PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); PhysicsStoreTypes.setUuidComponentType(registry.registerComponent(UuidComponent.class, "Uuid", @@ -174,6 +181,14 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new StepSubmissionSystem()); } + private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physicsStore) { + Store store = physicsStore.getStore(); + if (store.isShutdown()) { + return; + } + store.getResource(PhysicsRequestQueueResource.getResourceType()).clear(); + } + @Nonnull @SuppressWarnings("unchecked") private static ComponentRegistryProxy physicsStoreRegistry( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java index 9e5454b2..1e132dce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java @@ -5,12 +5,20 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Copied request queue drained by PhysicsStore.tick(). @@ -18,38 +26,98 @@ public final class PhysicsRequestQueueResource implements Resource { @Nonnull - private final Queue requests = new ArrayDeque<>(); + private final Queue requests = new ArrayDeque<>(); + @Nonnull + private final Map pendingFences = new Object2ObjectLinkedOpenHashMap<>(); public PhysicsRequestQueueResource() { } public synchronized void enqueue(@Nonnull PhysicsStoreRequest request) { - requests.add(Objects.requireNonNull(request, "request")); + requests.add(QueuedRequest.unfenced(request)); } public synchronized void enqueueAll(@Nonnull Iterable batch) { Objects.requireNonNull(batch, "batch"); for (PhysicsStoreRequest request : batch) { - requests.add(Objects.requireNonNull(request, "request")); + requests.add(QueuedRequest.unfenced(request)); + } + } + + @Nonnull + public synchronized PhysicsStoreRequestFenceHandle enqueueAllFenced( + @Nonnull Iterable batch, + long submittedServerTick) { + Objects.requireNonNull(batch, "batch"); + UUID fenceUuid = UUID.randomUUID(); + CompletableFuture completion = new CompletableFuture<>(); + int count = 0; + for (PhysicsStoreRequest request : batch) { + requests.add(QueuedRequest.fenced(request, fenceUuid, submittedServerTick)); + count++; + } + if (count == 0) { + completion.complete(new PhysicsStoreRequestFenceResult(fenceUuid, + submittedServerTick, + submittedServerTick, + 0, + 0, + 0, + 0, + 0)); + } else { + pendingFences.put(fenceUuid, + new PendingFence(fenceUuid, submittedServerTick, count, completion)); } + return new PhysicsStoreRequestFenceHandle(fenceUuid, completion.minimalCompletionStage()); } @Nonnull - public synchronized List drain() { - List drained = new ArrayList<>(requests.size()); - PhysicsStoreRequest request; + public synchronized List drain() { + List drained = new ArrayList<>(requests.size()); + QueuedRequest request; while ((request = requests.poll()) != null) { drained.add(request); } return drained; } + public void completeFences(@Nonnull Collection results) { + Objects.requireNonNull(results, "results"); + List completions = new ArrayList<>(); + synchronized (this) { + for (PhysicsStoreRequestFenceResult result : results) { + PendingFence fence = pendingFences.remove(result.fenceUuid()); + if (fence != null) { + completions.add(new FenceCompletion(fence.completion(), result)); + } + } + } + complete(completions); + } + public synchronized int size() { return requests.size(); } - public synchronized void clear() { - requests.clear(); + public void clear() { + List completions = new ArrayList<>(); + synchronized (this) { + requests.clear(); + for (PendingFence fence : pendingFences.values()) { + completions.add(new FenceCompletion(fence.completion(), + new PhysicsStoreRequestFenceResult(fence.fenceUuid(), + fence.submittedServerTick(), + fence.submittedServerTick(), + fence.acceptedCount(), + 0, + 0, + 0, + fence.acceptedCount()))); + } + pendingFences.clear(); + } + complete(completions); } @Nonnull @@ -64,4 +132,58 @@ public synchronized PhysicsRequestQueueResource clone() { public static ResourceType getResourceType() { return PhysicsStoreTypes.requestQueueResourceType(); } + + private static void complete(@Nonnull Iterable completions) { + for (FenceCompletion completion : completions) { + completion.completion().complete(completion.result()); + } + } + + public record QueuedRequest(@Nonnull PhysicsStoreRequest request, + @Nullable UUID fenceUuid, + long submittedServerTick) { + + public QueuedRequest { + Objects.requireNonNull(request, "request"); + submittedServerTick = Math.max(0L, submittedServerTick); + } + + @Nonnull + private static QueuedRequest unfenced(@Nonnull PhysicsStoreRequest request) { + return new QueuedRequest(Objects.requireNonNull(request, "request"), null, 0L); + } + + @Nonnull + private static QueuedRequest fenced(@Nonnull PhysicsStoreRequest request, + @Nonnull UUID fenceUuid, + long submittedServerTick) { + return new QueuedRequest(Objects.requireNonNull(request, "request"), + Objects.requireNonNull(fenceUuid, "fenceUuid"), + submittedServerTick); + } + } + + private record PendingFence(@Nonnull UUID fenceUuid, + long submittedServerTick, + int acceptedCount, + @Nonnull CompletableFuture + completion) { + + private PendingFence { + Objects.requireNonNull(fenceUuid, "fenceUuid"); + submittedServerTick = Math.max(0L, submittedServerTick); + acceptedCount = Math.max(0, acceptedCount); + Objects.requireNonNull(completion, "completion"); + } + } + + private record FenceCompletion( + @Nonnull CompletableFuture completion, + @Nonnull PhysicsStoreRequestFenceResult result) { + + private FenceCompletion { + Objects.requireNonNull(completion, "completion"); + Objects.requireNonNull(result, "result"); + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index a3c617dd..9f3255fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -21,6 +21,7 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource.QueuedRequest; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; @@ -55,6 +56,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; @@ -62,7 +64,11 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -85,10 +91,12 @@ public final class RequestDrainSystem extends TickingSystem { public void tick(float dt, int systemIndex, @Nonnull Store store) { PhysicsRequestQueueResource queue = store.getResource( PhysicsRequestQueueResource.getResourceType()); - List requests = queue.drain(); - if (requests.isEmpty()) { + List queuedRequests = queue.drain(); + if (queuedRequests.isEmpty()) { return; } + List requests = requests(queuedRequests); + RequestFenceTracker fences = new RequestFenceTracker(queuedRequests); PhysicsIdentityIndexResource identity = store.getResource( PhysicsIdentityIndexResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( @@ -101,51 +109,63 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) Set structuralConflicts = structuralConflicts(requests, restore); Map> refsThisDrain = new Object2ObjectOpenHashMap<>(); - applyRemovals(store, - systemIndex, - identity, - runtime, - terrainPayloads, - refsThisDrain, - restore, - structuralConflicts, - requests); - applySpaceRemovals(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - structuralConflicts, - requests); - applySpaceUpserts(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - structuralConflicts, - requests); - applySpaceSettings(store, - runtime, - identity, - refsThisDrain, - restore, - structuralConflicts, - requests); - applyUpserts(store, - systemIndex, - identity, - runtime, - terrainPayloads, - refsThisDrain, - restore, - structuralConflicts, - requests); - applyBodyTypeRequests(store, identity, runtime, refsThisDrain, restore, requests); - applyTargetRequests(store, identity, refsThisDrain, restore, requests); - enqueueRuntimeBodyRequests(store, identity, runtime, refsThisDrain, restore, requests); - recordUnsupported(restore, requests); + try { + applyRemovals(store, + systemIndex, + identity, + runtime, + terrainPayloads, + refsThisDrain, + restore, + fences, + structuralConflicts, + requests); + applySpaceRemovals(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + fences, + structuralConflicts, + requests); + applySpaceUpserts(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + fences, + structuralConflicts, + requests); + applySpaceSettings(store, + runtime, + identity, + refsThisDrain, + restore, + fences, + structuralConflicts, + requests); + applyUpserts(store, + systemIndex, + identity, + runtime, + terrainPayloads, + refsThisDrain, + restore, + fences, + structuralConflicts, + requests); + applyBodyTypeRequests(store, identity, runtime, refsThisDrain, restore, fences, requests); + applyTargetRequests(store, identity, refsThisDrain, restore, fences, requests); + enqueueRuntimeBodyRequests(store, identity, runtime, refsThisDrain, restore, fences, requests); + recordUnsupported(restore, fences, requests); + } catch (RuntimeException | Error exception) { + fences.failUnfinished(); + queue.completeFences(fences.results(currentServerTick(store))); + throw exception; + } + queue.completeFences(fences.results(currentServerTick(store))); } private static void applySpaceRemovals(@Nonnull Store store, @@ -154,18 +174,24 @@ private static void applySpaceRemovals(@Nonnull Store store, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull Set structuralConflicts, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { - if (request instanceof SpaceRemoveRequest spaceRequest - && !structuralConflicts.contains(spaceRequest.spaceUuid())) { - applySpaceRemove(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - spaceRequest); + if (request instanceof SpaceRemoveRequest spaceRequest) { + if (structuralConflicts.contains(spaceRequest.spaceUuid())) { + fences.rejected(request); + continue; + } + trackRequest(fences, + request, + applySpaceRemove(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + spaceRequest)); } } } @@ -176,18 +202,24 @@ private static void applySpaceUpserts(@Nonnull Store store, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull Set structuralConflicts, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { - if (request instanceof SpaceUpsertRequest spaceRequest - && !structuralConflicts.contains(spaceRequest.spaceUuid())) { - applySpaceUpsert(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - spaceRequest); + if (request instanceof SpaceUpsertRequest spaceRequest) { + if (structuralConflicts.contains(spaceRequest.spaceUuid())) { + fences.rejected(request); + continue; + } + trackRequest(fences, + request, + applySpaceUpsert(store, + identity, + runtime, + compatibility, + refsThisDrain, + restore, + spaceRequest)); } } } @@ -197,17 +229,23 @@ private static void applySpaceSettings(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull Set structuralConflicts, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { - if (request instanceof SpaceSettingsRequest settingsRequest - && !structuralConflicts.contains(settingsRequest.spaceUuid())) { - applySpaceSettings(store, - runtime, - identity, - refsThisDrain, - restore, - settingsRequest); + if (request instanceof SpaceSettingsRequest settingsRequest) { + if (structuralConflicts.contains(settingsRequest.spaceUuid())) { + fences.rejected(request); + continue; + } + trackRequest(fences, + request, + applySpaceSettings(store, + runtime, + identity, + refsThisDrain, + restore, + settingsRequest)); } } } @@ -219,35 +257,49 @@ private static void applyRemovals(@Nonnull Store store, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull Set structuralConflicts, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { if (request instanceof BodyRemoveRequest bodyRequest) { - if (!structuralConflicts.contains(bodyRequest.bodyUuid())) { - applyBodyRemove(store, - systemIndex, - identity, - runtime, - refsThisDrain, - bodyRequest); + if (structuralConflicts.contains(bodyRequest.bodyUuid())) { + fences.rejected(request); + } else { + trackRequest(fences, + request, + applyBodyRemove(store, + systemIndex, + identity, + runtime, + refsThisDrain, + bodyRequest)); } continue; } if (request instanceof JointRemoveRequest jointRequest) { - if (!structuralConflicts.contains(jointRequest.jointUuid())) { - applyJointRemove(store, identity, runtime, refsThisDrain, jointRequest); + if (structuralConflicts.contains(jointRequest.jointUuid())) { + fences.rejected(request); + } else { + trackRequest(fences, + request, + applyJointRemove(store, identity, runtime, refsThisDrain, jointRequest)); } continue; } if (request instanceof TerrainColliderRequest terrainRequest - && terrainRequest.remove() - && !structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { - applyTerrainRequest(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - terrainRequest); + && terrainRequest.remove()) { + if (structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { + fences.rejected(request); + } else { + trackRequest(fences, + request, + applyTerrainRequest(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + terrainRequest)); + } } } } @@ -259,41 +311,55 @@ private static void applyUpserts(@Nonnull Store store, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull Set structuralConflicts, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { if (request instanceof BodyUpsertRequest bodyRequest) { - if (!structuralConflicts.contains(bodyRequest.bodyUuid())) { - applyBodyUpsert(store, - systemIndex, - identity, - runtime, - refsThisDrain, - restore, - bodyRequest); + if (structuralConflicts.contains(bodyRequest.bodyUuid())) { + fences.rejected(request); + } else { + trackRequest(fences, + request, + applyBodyUpsert(store, + systemIndex, + identity, + runtime, + refsThisDrain, + restore, + bodyRequest)); } continue; } if (request instanceof JointUpsertRequest jointRequest) { - if (!structuralConflicts.contains(jointRequest.jointUuid())) { - applyJointUpsert(store, - identity, - runtime, - refsThisDrain, - restore, - jointRequest); + if (structuralConflicts.contains(jointRequest.jointUuid())) { + fences.rejected(request); + } else { + trackRequest(fences, + request, + applyJointUpsert(store, + identity, + runtime, + refsThisDrain, + restore, + jointRequest)); } continue; } if (request instanceof TerrainColliderRequest terrainRequest - && !terrainRequest.remove() - && !structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { - applyTerrainRequest(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - terrainRequest); + && !terrainRequest.remove()) { + if (structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { + fences.rejected(request); + } else { + trackRequest(fences, + request, + applyTerrainRequest(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + terrainRequest)); + } } } } @@ -302,25 +368,31 @@ private static void applyTargetRequests(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { if (request instanceof BodyTargetRequest targetRequest) { - applyTargetRequest(store, identity, refsThisDrain, restore, targetRequest); + trackRequest(fences, + request, + applyTargetRequest(store, identity, refsThisDrain, restore, targetRequest)); } } } private static void recordUnsupported(@Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { if (!isSupportedRequest(request)) { restore.recordSoftSkip("Unsupported PhysicsStore request " + request.getClass().getName()); + fences.rejected(request); } } } - private static void applySpaceRemove(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applySpaceRemove(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @@ -330,7 +402,7 @@ private static void applySpaceRemove(@Nonnull Store store, UUID spaceUuid = request.spaceUuid(); BackendSpaceHandle handle = runtime.getSpaceHandle(spaceUuid); if (handle != null && !removeSpaceBackend(runtime, identity, restore, spaceUuid, handle)) { - return; + return RequestApplicationStatus.SOFT_SKIPPED; } compatibility.removeBySpaceUuid(spaceUuid); removeRow(store, @@ -339,9 +411,11 @@ private static void applySpaceRemove(@Nonnull Store store, new ObjectOpenHashSet<>(), spaceUuid, refForUuid(identity, refsThisDrain, spaceUuid)); + return RequestApplicationStatus.APPLIED; } - private static void applySpaceUpsert(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applySpaceUpsert(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @@ -350,15 +424,15 @@ private static void applySpaceUpsert(@Nonnull Store store, @Nonnull SpaceUpsertRequest request) { if (isNil(request.spaceUuid())) { restore.recordSoftSkip("Space upsert contains nil UUID: " + request.spaceUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } if (request.space().getBackendIdValue().isBlank()) { restore.recordSoftSkip("Space upsert backend id is blank: " + request.spaceUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } if (runtime.getSpaceHandle(request.spaceUuid()) != null) { restore.recordSoftSkip("Space upsert target is already bound: " + request.spaceUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } Ref ref = ensureRow(store, identity, refsThisDrain, request.spaceUuid()); store.putComponent(ref, SpaceComponent.getComponentType(), request.space().clone()); @@ -369,9 +443,11 @@ private static void applySpaceUpsert(@Nonnull Store store, compatibility.putSpace(request.compatibilitySpaceId(), request.spaceUuid()); SpaceId.reserveAtLeast(request.compatibilitySpaceId().value()); runtime.markSpaceSettingsPending(request.spaceUuid()); + return RequestApplicationStatus.APPLIED; } - private static void applySpaceSettings(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applySpaceSettings(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, @@ -381,13 +457,14 @@ private static void applySpaceSettings(@Nonnull Store store, if (ref == null) { restore.recordSoftSkip("Space settings request target is missing: " + request.spaceUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } store.putComponent(ref, WorldCollisionComponent.getComponentType(), request.worldCollision().clone()); putSpaceSettingsComponents(store, ref, request); runtime.markSpaceSettingsPending(request.spaceUuid()); + return RequestApplicationStatus.APPLIED; } private static void putSpaceSettingsComponents(@Nonnull Store store, @@ -430,7 +507,8 @@ private static void putSpaceSettingsComponents(@Nonnull Store stor request.extensionSettings().clone()); } - private static void applyBodyRemove(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applyBodyRemove(@Nonnull Store store, int systemIndex, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @@ -463,9 +541,11 @@ private static void applyBodyRemove(@Nonnull Store store, ownedRowUuid, refForUuid(identity, refsThisDrain, ownedRowUuid)); } + return RequestApplicationStatus.APPLIED; } - private static void applyBodyUpsert(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applyBodyUpsert(@Nonnull Store store, int systemIndex, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @@ -473,7 +553,7 @@ private static void applyBodyUpsert(@Nonnull Store store, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull BodyUpsertRequest request) { if (!isValidBodyUpsert(request, restore)) { - return; + return RequestApplicationStatus.SOFT_SKIPPED; } if (runtime.getBodyHandle(request.bodyUuid()) != null) { for (JointRow joint : collectAttachedJoints(store, systemIndex, request.bodyUuid())) { @@ -514,9 +594,11 @@ private static void applyBodyUpsert(@Nonnull Store store, } else { store.removeComponent(bodyRef, TargetComponent.getComponentType()); } + return RequestApplicationStatus.APPLIED; } - private static void applyJointRemove(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applyJointRemove(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @@ -532,16 +614,18 @@ private static void applyJointRemove(@Nonnull Store store, new ObjectOpenHashSet<>(), request.jointUuid(), ref); + return RequestApplicationStatus.APPLIED; } - private static void applyJointUpsert(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applyJointUpsert(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull JointUpsertRequest request) { if (!isValidJointUpsert(request, restore)) { - return; + return RequestApplicationStatus.SOFT_SKIPPED; } Ref ref = refForUuid(identity, refsThisDrain, request.jointUuid()); JointComponent existing = PhysicsStoreSystemSupport.component(store, @@ -554,9 +638,11 @@ private static void applyJointUpsert(@Nonnull Store store, request.jointUuid(), JointComponent.getComponentType(), request.joint().clone()); + return RequestApplicationStatus.APPLIED; } - private static void applyTargetRequest(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applyTargetRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @@ -564,7 +650,7 @@ private static void applyTargetRequest(@Nonnull Store store, Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); if (bodyRef == null) { restore.recordSoftSkip("Target request body is missing: " + request.bodyUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } TargetComponent target = new TargetComponent(); target.setActive(true); @@ -576,6 +662,7 @@ private static void applyTargetRequest(@Nonnull Store store, target.setVelocityEnabled(request.velocityEnabled()); target.setActivate(request.activate()); store.putComponent(bodyRef, TargetComponent.getComponentType(), target); + return RequestApplicationStatus.APPLIED; } private static void applyBodyTypeRequests(@Nonnull Store store, @@ -583,10 +670,18 @@ private static void applyBodyTypeRequests(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { if (request instanceof BodyTypeRequest typeRequest) { - applyBodyTypeRequest(store, identity, runtime, refsThisDrain, restore, typeRequest); + trackRequest(fences, + request, + applyBodyTypeRequest(store, + identity, + runtime, + refsThisDrain, + restore, + typeRequest)); } } } @@ -596,19 +691,34 @@ private static void enqueueRuntimeBodyRequests(@Nonnull Store stor @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull RequestFenceTracker fences, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { if (request instanceof BodyActivationRequest activationRequest) { - enqueueBodyActivationRequest(identity, runtime, refsThisDrain, restore, activationRequest); + trackRequest(fences, + request, + enqueueBodyActivationRequest(identity, + runtime, + refsThisDrain, + restore, + activationRequest)); continue; } if (request instanceof BodyForceRequest forceRequest) { - enqueueBodyForceRequest(store, identity, runtime, refsThisDrain, restore, forceRequest); + trackRequest(fences, + request, + enqueueBodyForceRequest(store, + identity, + runtime, + refsThisDrain, + restore, + forceRequest)); } } } - private static void applyBodyTypeRequest(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applyBodyTypeRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @@ -617,7 +727,7 @@ private static void applyBodyTypeRequest(@Nonnull Store store, Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); if (bodyRef == null) { restore.recordSoftSkip("Body type request body is missing: " + request.bodyUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, bodyRef, @@ -637,7 +747,7 @@ private static void applyBodyTypeRequest(@Nonnull Store store, null, null)); } - return; + return RequestApplicationStatus.APPLIED; } binding.backendRuntime().setBodyType(binding.spaceHandle().value(), binding.bodyHandle().value(), @@ -648,9 +758,11 @@ private static void applyBodyTypeRequest(@Nonnull Store store, binding.spaceHandle(), binding.bodyHandle())); } + return RequestApplicationStatus.APPLIED; } - private static void enqueueBodyActivationRequest( + @Nonnull + private static RequestApplicationStatus enqueueBodyActivationRequest( @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @@ -658,7 +770,7 @@ private static void enqueueBodyActivationRequest( @Nonnull BodyActivationRequest request) { if (refForUuid(identity, refsThisDrain, request.bodyUuid()) == null) { restore.recordSoftSkip("Activation request body is missing: " + request.bodyUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } RuntimeBodyBinding binding = runtimeBodyBinding(runtime, request.bodyUuid(), @@ -674,9 +786,11 @@ private static void enqueueBodyActivationRequest( binding != null ? binding.spaceHandle() : null, binding != null ? binding.bodyHandle() : null)); } + return RequestApplicationStatus.APPLIED; } - private static void enqueueBodyForceRequest(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus enqueueBodyForceRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @@ -685,18 +799,18 @@ private static void enqueueBodyForceRequest(@Nonnull Store store, Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); if (bodyRef == null) { restore.recordSoftSkip("Force request body is missing: " + request.bodyUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, bodyRef, DynamicsComponent.getComponentType()); if (dynamics == null || dynamics.getBodyType() != PhysicsBodyType.DYNAMIC) { restore.recordSoftSkip("Force request target is not dynamic: " + request.bodyUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } if (!hasFiniteVector(request)) { restore.recordSoftSkip("Force request contains non-finite values: " + request.bodyUuid()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } RuntimeBodyBinding binding = runtimeBodyBinding(runtime, request.bodyUuid(), @@ -714,9 +828,11 @@ private static void enqueueBodyForceRequest(@Nonnull Store store, request.offsetX(), request.offsetY(), request.offsetZ())); + return RequestApplicationStatus.APPLIED; } - private static void applyTerrainRequest(@Nonnull Store store, + @Nonnull + private static RequestApplicationStatus applyTerrainRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull Map> refsThisDrain, @@ -736,12 +852,12 @@ private static void applyTerrainRequest(@Nonnull Store store, removedTerrainComponent(request)); } removePayload(terrainPayloads, request.payloadResourceKey()); - return; + return RequestApplicationStatus.APPLIED; } TerrainColliderPayload payload = request.payload(); if (payload == null || payload.isEmpty()) { restore.recordSoftSkip("Terrain upsert payload is missing: " + request.sourceKey()); - return; + return RequestApplicationStatus.SOFT_SKIPPED; } terrainPayloads.put(request.payloadResourceKey(), payload); TerrainColliderComponent component = activeTerrainComponent(request); @@ -754,12 +870,13 @@ private static void applyTerrainRequest(@Nonnull Store store, } store.putComponent(ref, TerrainColliderComponent.getComponentType(), component); refsThisDrain.put(terrainUuid, ref); - return; + return RequestApplicationStatus.APPLIED; } Holder holder = store.getRegistry().newHolder(); holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(terrainUuid)); holder.addComponent(TerrainColliderComponent.getComponentType(), component); refsThisDrain.put(terrainUuid, store.addEntity(holder, AddReason.SPAWN)); + return RequestApplicationStatus.APPLIED; } @Nonnull @@ -1108,6 +1225,29 @@ private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) || request instanceof JointRemoveRequest; } + @Nonnull + private static List requests(@Nonnull List queuedRequests) { + List requests = new ArrayList<>(queuedRequests.size()); + for (QueuedRequest queuedRequest : queuedRequests) { + requests.add(queuedRequest.request()); + } + return requests; + } + + private static void trackRequest(@Nonnull RequestFenceTracker fences, + @Nonnull PhysicsStoreRequest request, + @Nonnull RequestApplicationStatus status) { + if (status == RequestApplicationStatus.APPLIED) { + fences.applied(request); + } else { + fences.softSkipped(request); + } + } + + private static long currentServerTick(@Nonnull Store store) { + return Math.max(0L, store.getExternalData().getWorld().getTick()); + } + @Nullable private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, @@ -1175,4 +1315,109 @@ private record RuntimeBodyBinding(@Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } + + private enum RequestApplicationStatus { + APPLIED, + SOFT_SKIPPED + } + + private static final class RequestFenceTracker { + + @Nonnull + private final Map countsByFenceUuid = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map> countsByRequest = + new IdentityHashMap<>(); + + private RequestFenceTracker(@Nonnull List queuedRequests) { + for (QueuedRequest queuedRequest : queuedRequests) { + UUID fenceUuid = queuedRequest.fenceUuid(); + if (fenceUuid == null) { + continue; + } + FenceCounts counts = countsByFenceUuid.computeIfAbsent(fenceUuid, + uuid -> new FenceCounts(uuid, queuedRequest.submittedServerTick())); + counts.accepted++; + countsByRequest.computeIfAbsent(queuedRequest.request(), + _ -> new ArrayDeque<>()).addLast(counts); + } + } + + private void applied(@Nonnull PhysicsStoreRequest request) { + FenceCounts counts = countsFor(request); + if (counts != null) { + counts.applied++; + } + } + + private void softSkipped(@Nonnull PhysicsStoreRequest request) { + FenceCounts counts = countsFor(request); + if (counts != null) { + counts.softSkipped++; + } + } + + private void rejected(@Nonnull PhysicsStoreRequest request) { + FenceCounts counts = countsFor(request); + if (counts != null) { + counts.rejected++; + } + } + + @Nullable + private FenceCounts countsFor(@Nonnull PhysicsStoreRequest request) { + Deque counts = countsByRequest.get(request); + if (counts == null) { + return null; + } + FenceCounts next = counts.pollFirst(); + if (counts.isEmpty()) { + countsByRequest.remove(request); + } + return next; + } + + private void failUnfinished() { + for (FenceCounts counts : countsByFenceUuid.values()) { + int finished = counts.applied + counts.softSkipped + counts.rejected + counts.failed; + if (finished < counts.accepted) { + counts.failed += counts.accepted - finished; + } + } + } + + @Nonnull + private Collection results(long consumedServerTick) { + List results = + new ArrayList<>(countsByFenceUuid.size()); + for (FenceCounts counts : countsByFenceUuid.values()) { + results.add(new PhysicsStoreRequestFenceResult(counts.fenceUuid, + counts.submittedServerTick, + consumedServerTick, + counts.accepted, + counts.applied, + counts.softSkipped, + counts.rejected, + counts.failed)); + } + return results; + } + } + + private static final class FenceCounts { + + @Nonnull + private final UUID fenceUuid; + private final long submittedServerTick; + private int accepted; + private int applied; + private int softSkipped; + private int rejected; + private int failed; + + private FenceCounts(@Nonnull UUID fenceUuid, long submittedServerTick) { + this.fenceUuid = fenceUuid; + this.submittedServerTick = Math.max(0L, submittedServerTick); + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java index e6a01d7d..33c27a85 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; @@ -172,6 +173,21 @@ public static void enqueueAll(@Nonnull World world, queue.enqueueAll(copied); } + @Nonnull + public static PhysicsStoreRequestFenceHandle enqueueAllFenced(@Nonnull World world, + @Nonnull Iterable requests, + long submittedServerTick) { + Objects.requireNonNull(requests, "requests"); + List copied = new ArrayList<>(); + for (PhysicsStoreRequest request : requests) { + copied.add(Objects.requireNonNull(request, "request")); + } + Store store = require(world).getStore(); + PhysicsRequestQueueResource queue = store.getResource( + PhysicsRequestQueueResource.getResourceType()); + return queue.enqueueAllFenced(copied, submittedServerTick); + } + @Nonnull private static MethodHandle worldAccessor() { MethodHandle accessor = worldGetPhysicsStore; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java new file mode 100644 index 00000000..0c769b0d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java @@ -0,0 +1,33 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; + +/** + * Runtime-only handle completed when PhysicsStore drains and applies a queued request batch. + */ +public final class PhysicsStoreRequestFenceHandle { + + @Nonnull + private final UUID fenceUuid; + @Nonnull + private final CompletionStage completion; + + public PhysicsStoreRequestFenceHandle(@Nonnull UUID fenceUuid, + @Nonnull CompletionStage completion) { + this.fenceUuid = Objects.requireNonNull(fenceUuid, "fenceUuid"); + this.completion = Objects.requireNonNull(completion, "completion"); + } + + @Nonnull + public UUID fenceUuid() { + return fenceUuid; + } + + @Nonnull + public CompletionStage completion() { + return completion; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java new file mode 100644 index 00000000..c123fc3d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java @@ -0,0 +1,47 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Runtime-only completion summary for one queued PhysicsStore request batch. + */ +public record PhysicsStoreRequestFenceResult(@Nonnull UUID fenceUuid, + long submittedServerTick, + long consumedServerTick, + int acceptedCount, + int appliedCount, + int softSkippedCount, + int rejectedCount, + int failedCount) { + + public PhysicsStoreRequestFenceResult { + Objects.requireNonNull(fenceUuid, "fenceUuid"); + submittedServerTick = Math.max(0L, submittedServerTick); + consumedServerTick = Math.max(0L, consumedServerTick); + acceptedCount = Math.max(0, acceptedCount); + appliedCount = Math.max(0, appliedCount); + softSkippedCount = Math.max(0, softSkippedCount); + rejectedCount = Math.max(0, rejectedCount); + failedCount = Math.max(0, failedCount); + } + + public boolean allApplied() { + return acceptedCount == appliedCount + && softSkippedCount == 0 + && rejectedCount == 0 + && failedCount == 0; + } + + public boolean hasProblems() { + return softSkippedCount > 0 + || rejectedCount > 0 + || failedCount > 0 + || acceptedCount != appliedCount + softSkippedCount + rejectedCount + failedCount; + } + + public long consumedServerTickLatency() { + return Math.max(0L, consumedServerTick - submittedServerTick); + } +} diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java index 26ca5623..d3161dd8 100644 --- a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java @@ -4,14 +4,29 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; import javax.annotation.Nonnull; public final class PhysicsStoreHooks { + @Nonnull + private static final Set> SHUTDOWN_HOOKS = + new CopyOnWriteArraySet<>(); + private PhysicsStoreHooks() { } + public static void registerShutdownHook(@Nonnull Consumer hook) { + SHUTDOWN_HOOKS.add(Objects.requireNonNull(hook, "hook")); + } + + public static void unregisterShutdownHook(@Nonnull Consumer hook) { + SHUTDOWN_HOOKS.remove(Objects.requireNonNull(hook, "hook")); + } + public static void start(@Nonnull PhysicsStore physicsStore, @Nonnull IResourceStorage resourceStorage) { Objects.requireNonNull(physicsStore, "physicsStore") @@ -37,6 +52,29 @@ public static CompletableFuture saveResources(@Nonnull PhysicsStore physic } public static void shutdown(@Nonnull PhysicsStore physicsStore) { - Objects.requireNonNull(physicsStore, "physicsStore").shutdown(); + PhysicsStore checked = Objects.requireNonNull(physicsStore, "physicsStore"); + RuntimeException hookFailure = null; + for (Consumer hook : SHUTDOWN_HOOKS) { + try { + hook.accept(checked); + } catch (RuntimeException exception) { + if (hookFailure == null) { + hookFailure = exception; + } else { + hookFailure.addSuppressed(exception); + } + } + } + try { + checked.shutdown(); + } catch (RuntimeException exception) { + if (hookFailure != null) { + exception.addSuppressed(hookFailure); + } + throw exception; + } + if (hookFailure != null) { + throw hookFailure; + } } } From 97d95601436f4c1dde01ecf353b93d997bddcd82 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 20:34:39 +0200 Subject: [PATCH 036/534] feat(examples): report raw body requests through physics store fences Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 103 ++++++++++++++-- .../stress/StressRawBodiesCommand.java | 112 +++++++++++------- 2 files changed, 166 insertions(+), 49 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 74e2c93a..48ad1360 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -42,6 +42,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; @@ -341,6 +342,69 @@ private static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, @Nonnull public static BodyRequestBatchTiming enqueueDynamicBodyBatchMeasured(@Nonnull World world, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Consumer recipe) { + DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(world, + spaceId, + expectedBodies, + shape, + mass, + settings, + kind, + persistenceMode, + recipe); + if (plan.isEmpty()) { + return new BodyRequestBatchTiming(0, plan.setupWallNanos(), 0L); + } + + long requestStartNanos = System.nanoTime(); + PhysicsStoreAccess.enqueueAll(world, plan.requests()); + long requestEnqueueNanos = System.nanoTime() - requestStartNanos; + return new BodyRequestBatchTiming(plan.count(), + plan.setupWallNanos(), + requestEnqueueNanos); + } + + @Nonnull + public static FencedBodyRequestBatchTiming enqueueDynamicBodyBatchFencedMeasured(@Nonnull World world, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + long submittedServerTick, + @Nonnull Consumer recipe) { + DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(world, + spaceId, + expectedBodies, + shape, + mass, + settings, + kind, + persistenceMode, + recipe); + + long requestStartNanos = System.nanoTime(); + PhysicsStoreRequestFenceHandle fence = PhysicsStoreAccess.enqueueAllFenced(world, + plan.requests(), + Math.max(0L, submittedServerTick)); + long requestEnqueueNanos = System.nanoTime() - requestStartNanos; + return new FencedBodyRequestBatchTiming(plan.count(), + plan.setupWallNanos(), + requestEnqueueNanos, + fence); + } + + @Nonnull + private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, @Nonnull SpaceId spaceId, int expectedBodies, @Nonnull PhysicsShapeSpec shape, @@ -361,7 +425,7 @@ public static BodyRequestBatchTiming enqueueDynamicBodyBatchMeasured(@Nonnull Wo Objects.requireNonNull(recipe, "recipe").accept(batch); batch.seal(); if (batch.isEmpty()) { - return new BodyRequestBatchTiming(0, 0L, 0L); + return new DynamicBodyBatchPlan(List.of(), 0L); } UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); @@ -384,12 +448,7 @@ public static BodyRequestBatchTiming enqueueDynamicBodyBatchMeasured(@Nonnull Wo persistenceMode)); } - long requestStartNanos = System.nanoTime(); - PhysicsStoreAccess.enqueueAll(world, requests); - long requestEnqueueNanos = System.nanoTime() - requestStartNanos; - return new BodyRequestBatchTiming(batch.size(), - System.nanoTime() - setupStartNanos, - requestEnqueueNanos); + return new DynamicBodyBatchPlan(requests, System.nanoTime() - setupStartNanos); } @Nonnull @@ -1079,6 +1138,36 @@ public record BodyRequestBatchTiming(int count, } } + public record FencedBodyRequestBatchTiming(int count, + long setupWallNanos, + long requestEnqueueNanos, + @Nonnull PhysicsStoreRequestFenceHandle fence) { + + public FencedBodyRequestBatchTiming { + count = Math.max(0, count); + setupWallNanos = Math.max(0L, setupWallNanos); + requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); + Objects.requireNonNull(fence, "fence"); + } + } + + private record DynamicBodyBatchPlan(@Nonnull List requests, + long setupWallNanos) { + + DynamicBodyBatchPlan { + requests = List.copyOf(Objects.requireNonNull(requests, "requests")); + setupWallNanos = Math.max(0L, setupWallNanos); + } + + private int count() { + return requests.size(); + } + + private boolean isEmpty() { + return requests.isEmpty(); + } + } + public record PendingBlockBody(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @Nullable String blockType, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index 804b11d5..b61e116e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -10,19 +10,18 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.FencedBodyRequestBatchTiming; import java.util.Locale; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -77,51 +76,80 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - long bodyKeyRunId = RigidBodyKey.random().mostSignificantBits(); + long submittedServerTick = Math.max(0L, world.getTick()); long commandStartNanos = System.nanoTime(); - PhysicsCommandHandle handle = - resource.submitCommands(Math.max(0L, world.getTick()), 1, commands -> - commands.spawnBodies(count, - spaceId, - box, - 1.0f, - PhysicsBodyType.DYNAMIC, - spawnSettings, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { - for (int i = 0; i < count; i++) { - int x = i % side; - int z = (i / side) % side; - int y = i / (side * side); + FencedBodyRequestBatchTiming timing = ExamplePhysicsUtils.enqueueDynamicBodyBatchFencedMeasured(world, + spaceId, + count, + box, + 1.0f, + spawnSettings, + PhysicsBodyKind.TEMPORARY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY, + submittedServerTick, + spawns -> { + for (int i = 0; i < count; i++) { + int x = i % side; + int z = (i / side) % side; + int y = i / (side * side); - spawns.body(bodyKeyRunId, - i + 1L, - (float) (originX + x * SPACING), - (float) (originY + y * SPACING), - (float) (originZ + z * SPACING)); - } - })); - ExamplePhysicsUtils.requireApplied(handle, "spawn raw stress physics bodies"); - PhysicsEventFrame eventFrame = resource.getLatestEventFrame(); - boolean capturedSnapshotIncluded = handle.isIncludedInLatestCapturedSnapshot(eventFrame); - long capturedSnapshotTickLatency = handle.capturedSnapshotServerTickLatency(eventFrame); - long commandApplyNanos = System.nanoTime() - commandStartNanos; + spawns.addBody((float) (originX + x * SPACING), + (float) (originY + y * SPACING), + (float) (originZ + z * SPACING)); + } + }); + long fenceStartNanos = System.nanoTime(); - ctx.sender().sendMessage(Message.raw("Spawned " + count - + " raw physics bodies without entities: commandApplyMs=" - + millis(commandApplyNanos) - + " latestCapturedSnapshotIncluded=" + capturedSnapshotIncluded - + " latestCapturedSnapshotFrame=" + eventFrame.latestCapturedSnapshotFrameEpoch() - + " latestCapturedSnapshotTick=" + eventFrame.latestCapturedSnapshotServerTick() - + " capturedSnapshotTickLatency=" - + (capturedSnapshotIncluded ? Long.toString(capturedSnapshotTickLatency) : "pending") - + ". Use this to separate backend cost from entity/render cost.")); - return CompletableFuture.completedFuture(null); + return timing.fence() + .completion() + .thenAccept(result -> ctx.sender().sendMessage(Message.raw(successMessage(timing, + result, + System.nanoTime() - fenceStartNanos, + System.nanoTime() - commandStartNanos)))) + .exceptionally(failure -> { + ctx.sender().sendMessage(Message.raw("Failed to drain raw physics body requests: " + + failureMessage(failure))); + return null; + }) + .toCompletableFuture(); } private static String millis(long nanos) { return String.format(Locale.ROOT, "%.3f", nanos / 1_000_000.0); } + @Nonnull + private static String successMessage(@Nonnull FencedBodyRequestBatchTiming timing, + @Nonnull PhysicsStoreRequestFenceResult result, + long fenceWaitNanos, + long totalWallNanos) { + return "PhysicsStore drained raw body requests for " + timing.count() + + " physics-only bodies: setupWallMs=" + millis(timing.setupWallNanos()) + + " requestEnqueueMs=" + millis(timing.requestEnqueueNanos()) + + " fenceWaitMs=" + millis(fenceWaitNanos) + + " totalWallMs=" + millis(totalWallNanos) + + " accepted=" + result.acceptedCount() + + " applied=" + result.appliedCount() + + " softSkipped=" + result.softSkippedCount() + + " rejected=" + result.rejectedCount() + + " failed=" + result.failedCount() + + " submittedTick=" + result.submittedServerTick() + + " consumedTick=" + result.consumedServerTick() + + " consumedTickLatency=" + result.consumedServerTickLatency() + + " allApplied=" + result.allApplied() + + " hasProblems=" + result.hasProblems() + + " fence=" + result.fenceUuid() + + ". This reports request drain/application only."; + } + + @Nonnull + private static String failureMessage(@Nonnull Throwable failure) { + Throwable unwrapped = failure instanceof CompletionException && failure.getCause() != null + ? failure.getCause() + : failure; + return unwrapped.getMessage() != null + ? unwrapped.getMessage() + : unwrapped.getClass().getSimpleName(); + } + } From c582721ae024282ab357d05034dff0b717bb141e Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 20:40:05 +0200 Subject: [PATCH 037/534] feat(examples): author stress joints through physics store Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 4 +- .../commands/stress/StressJointsCommand.java | 209 ++++++++++-------- 2 files changed, 118 insertions(+), 95 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 48ad1360..c8963a1f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -273,7 +273,7 @@ private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store store, + public static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull PendingBlockBody pending) { Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 13f4abac..8ac0059e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -14,15 +14,24 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3d; +import org.joml.Vector3f; /** * Builds separate joint rows so backend differences are easier to isolate. @@ -77,11 +86,23 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + UUID spaceUuid; + try { + spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + } catch (IllegalStateException exception) { + spaceUuid = null; + } + if (spaceUuid == null) { + ctx.sender().sendMessage(Message.raw( + "Cannot queue stress joint demo because the target space is not bound in PhysicsStore.")); + return CompletableFuture.completedFuture(null); + } Vector3d origin = new Vector3d(playerPos).add(-totalJoints * 0.1, 7.0, 5.0); - long serverTick = Math.max(0L, world.getTick()); int createdJoints = 0; int createdBodies = 0; + List pendingBodies = new ArrayList<>(totalJoints + ROWS); + List requests = new ArrayList<>(totalJoints * 2 + ROWS); int baseJointsPerRow = totalJoints / ROWS; int remainder = totalJoints % ROWS; for (int row = 0; row < ROWS; row++) { @@ -91,26 +112,40 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } Vector3d rowOrigin = new Vector3d(origin).add(0.0, 0.0, row * ROW_SPACING); - createdBodies += createRow(store, time, resource, spaceId, rowOrigin, rowJoints, row, - blockType, serverTick); + createdBodies += appendRow(pendingBodies, + requests, + spaceUuid, + spaceId, + rowOrigin, + rowJoints, + row, + blockType); createdJoints += rowJoints; } + try { + PhysicsStoreAccess.enqueueAll(world, requests); + } catch (IllegalStateException exception) { + ctx.sender().sendMessage(Message.raw("Cannot queue stress joint demo: " + exception.getMessage())); + return CompletableFuture.completedFuture(null); + } + for (PendingBlockBody pendingBody : pendingBodies) { + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, pendingBody); + } - ctx.sender().sendMessage(Message.raw("Spawned " + createdJoints - + " stress joints as separate fixed/point/hinge/slider/spring rows with " - + createdBodies + " bodies. blockType=" + blockType + ".")); + ctx.sender().sendMessage(Message.raw("Queued " + createdJoints + + " stress joints across fixed/point/hinge/slider/spring rows with " + + createdBodies + " bodies and attached visuals. blockType=" + blockType + ".")); return CompletableFuture.completedFuture(null); } - private static int createRow(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, + private static int appendRow(@Nonnull List pendingBodies, + @Nonnull List requests, + @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin, int jointCount, int jointType, - @Nonnull String blockType, - long serverTick) { + @Nonnull String blockType) { double spacing = jointType == 4 ? TOUCHING_SPACING + SPRING_REST_LENGTH : TOUCHING_SPACING; int bodyCount = jointCount + 1; @@ -128,50 +163,28 @@ private static int createRow(@Nonnull Store store, positions[positionOffset] = (float) (origin.x + i * spacing); positions[positionOffset + 1] = (float) origin.y; positions[positionOffset + 2] = (float) origin.z; - } - - ExamplePhysicsUtils.requireApplied(resource.submitCommands(serverTick, bodyCount + jointCount, commands -> { - for (int i = 0; i < bodyCount; i++) { - int positionOffset = i * 3; - float mass = i == 0 ? 0.0f : 1.0f; - commands.spawnBody(bodyKeys[i], spawn -> spawn - .space(spaceId) - .shape(box) - .mass(mass) - .dynamic() - .position(positions[positionOffset], - positions[positionOffset + 1], - positions[positionOffset + 2]) - .settings(spawnSettings) - .persistent()); - } - - for (int i = 0; i < jointCount; i++) { - RigidBodyKey current = bodyKeys[i + 1]; - createJoint(commands, - JointKey.of(jointKeyRunId, i + 1L), - spaceId, - bodyKeys[i], - current, - jointType); - if (jointType == 1 && i % 5 == 0) { - commands.setBodyVelocity(current, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, true); - } else if (jointType == 4 && i % 3 == 0) { - commands.setBodyVelocity(current, 0.4f, 0.0f, 0.8f, 0.0f, 0.0f, 0.0f, true); - } - } - }), "create stress joint row"); - for (int i = 0; i < bodyCount; i++) { - int positionOffset = i * 3; - ExamplePhysicsUtils.spawnAttachedBlockEntity(store, - time, + float mass = i == 0 ? 0.0f : 1.0f; + requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyKey.value(), + new Vector3f(positions[positionOffset], + positions[positionOffset + 1], + positions[positionOffset + 2]), + box, + mass, + spawnSettings, + initialVelocity(jointType, i))); + pendingBodies.add(new PendingBlockBody( bodyKeys[i], spaceId, blockType, - new Vector3d(positions[positionOffset], - positions[positionOffset + 1], - positions[positionOffset + 2]), - i > 0); + positions[positionOffset], + positions[positionOffset + 1], + positions[positionOffset + 2], + i > 0)); + } + for (int i = 0; i < jointCount; i++) { + requests.add(JointUpsertRequest.of(JointKey.of(jointKeyRunId, i + 1L).value(), + joint(spaceUuid, bodyKeys[i], bodyKeys[i + 1], jointType))); } return bodyCount; } @@ -183,9 +196,8 @@ private String blockType(@Nonnull CommandContext ctx) { : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; } - private static void createJoint(@Nonnull PhysicsCommandRecorder commands, - @Nonnull JointKey jointKey, - @Nonnull SpaceId spaceId, + @Nonnull + private static JointComponent joint(@Nonnull UUID spaceUuid, @Nonnull RigidBodyKey previousKey, @Nonnull RigidBodyKey currentKey, int jointType) { @@ -196,43 +208,54 @@ private static void createJoint(@Nonnull PhysicsCommandRecorder commands, case 3 -> JointType.SLIDER; default -> JointType.SPRING; }; - commands.joint(jointKey, joint -> { - joint.space(spaceId).bodies(previousKey, currentKey); - switch (type) { - case FIXED -> joint.fixed(HALF_SIZE, 0.0f, 0.0f, -HALF_SIZE, 0.0f, 0.0f); - case POINT -> joint.point(HALF_SIZE, 0.0f, 0.0f, -HALF_SIZE, 0.0f, 0.0f); - case HINGE -> joint.hinge(HALF_SIZE, - 0.0f, - 0.0f, - -HALF_SIZE, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f) - .limits(-0.8f, 0.8f) - .motor(0.6f, 2.0f); - case SLIDER -> joint.slider(HALF_SIZE, - 0.0f, - 0.0f, - -HALF_SIZE, - 0.0f, - 0.0f, - 1.0f, - 0.0f, - 0.0f) - .limits(-0.35f, 0.35f) - .motor(0.4f, 2.0f); - case SPRING -> joint.spring(HALF_SIZE, - 0.0f, - 0.0f, - -HALF_SIZE, - 0.0f, - 0.0f, - SPRING_REST_LENGTH, - 18.0f, - 2.0f); + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(spaceUuid); + joint.setBodyAUuid(previousKey.value()); + joint.setBodyBUuid(currentKey.value()); + joint.setType(type); + joint.setAnchorA(new Vector3f(HALF_SIZE, 0.0f, 0.0f)); + joint.setAnchorB(new Vector3f(-HALF_SIZE, 0.0f, 0.0f)); + joint.setEnabled(true); + switch (type) { + case FIXED, POINT -> { + } + case HINGE -> { + joint.setAxis(new Vector3f(0.0f, 0.0f, 1.0f)); + joint.setLowerLimit(-0.8f); + joint.setUpperLimit(0.8f); + joint.setMotorEnabled(true); + joint.setMotorTargetVelocity(0.6f); + joint.setMotorMaxForce(2.0f); + } + case SLIDER -> { + joint.setAxis(new Vector3f(1.0f, 0.0f, 0.0f)); + joint.setLowerLimit(-0.35f); + joint.setUpperLimit(0.35f); + joint.setMotorEnabled(true); + joint.setMotorTargetVelocity(0.4f); + joint.setMotorMaxForce(2.0f); } - }); + case SPRING -> { + joint.setSpringRestLength(SPRING_REST_LENGTH); + joint.setSpringStiffness(18.0f); + joint.setSpringDamping(2.0f); + } + } + return joint; + } + + @Nullable + private static Vector3f initialVelocity(int jointType, int bodyIndex) { + if (bodyIndex <= 0) { + return null; + } + int jointIndex = bodyIndex - 1; + if (jointType == 1 && jointIndex % 5 == 0) { + return new Vector3f(0.0f, 0.0f, 1.0f); + } + if (jointType == 4 && jointIndex % 3 == 0) { + return new Vector3f(0.4f, 0.0f, 0.8f); + } + return null; } } From 11dd980f45324cb045febe01dc32409bcf0b0dcd Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 20:59:09 +0200 Subject: [PATCH 038/534] feat(examples): route ecs explosive demos through physics store Signed-off-by: Blovien --- .../impulse/examples/commands/EcsCommand.java | 118 ++++++++++++------ .../commands/ExamplePhysicsUtils.java | 2 +- .../explosive/ExplosiveBlockRuntime.java | 65 ++++++---- 3 files changed, 122 insertions(+), 63 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index 1029d2b4..2aace07d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -20,11 +20,17 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; @@ -32,9 +38,11 @@ import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; import org.joml.Vector3i; @@ -103,20 +111,19 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - RigidBodyKey bodyKey = RigidBodyKey.random(); Vector3d spawn = new Vector3d(playerPos).add(0.0, 5.0, 0.0); TimeResource time = store.getResource(TimeResource.getResourceType()); - ExamplePhysicsUtils.spawnPhysicsBodyBlockEntity(store, + ExamplePhysicsUtils.SpawnedBlockBody body = ExamplePhysicsUtils.spawnBlockBody(store, time, - bodyKey, + ExamplePhysicsUtils.resource(store), spaceId, spawn, blockType(ctx), - PhysicsBodyType.DYNAMIC, + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, - null); + RigidBodySpawnSettings.material(0.5f, 0.2f)); - ctx.sender().sendMessage(Message.raw("Queued ECS-authored physics body " + bodyKey + ctx.sender().sendMessage(Message.raw("Queued ECS-authored physics body " + body.bodyKey() + " in space " + spaceId.value() + ".")); return CompletableFuture.completedFuture(null); } @@ -164,15 +171,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); Vector3d impulse = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(strength); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); - ExamplePhysicsUtils.requireApplied(resource.submitCommands(0L, - 1, - commands -> commands.applyBodyImpulse(hit.bodyKey(), + PhysicsStoreAccess.enqueue(world, + BodyForceRequest.impulse(hit.bodyKey().value(), (float) impulse.x, (float) impulse.y, - (float) impulse.z)), "ecs recorder impulse"); + (float) impulse.z)); - ctx.sender().sendMessage(Message.raw("Applied ECS recorder impulse to " + ctx.sender().sendMessage(Message.raw("Queued ECS impulse request for " + hit.bodyKey() + ".")); return CompletableFuture.completedFuture(null); } @@ -201,18 +206,44 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } RigidBodyKey bodyKey = RigidBodyKey.random(); + UUID bodyUuid = bodyKey.value(); Vector3d spawn = new Vector3d(playerPos).add(0.0, 2.0, 0.0); - PhysicsBodyKinematicTargetComponent target = ExamplePhysicsUtils.kinematicTargetAt(spawn); + UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + if (spaceUuid == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } + Vector3f targetPosition = vector(spawn); + BodyUpsertRequest bodyRequest = ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyUuid, + targetPosition, + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 0.0f, + RigidBodySpawnSettings.material(0.5f, 0.2f), + null); + List requests = List.of(bodyRequest, + BodyTypeRequest.of(bodyUuid, PhysicsBodyType.KINEMATIC, true), + BodyTargetRequest.of(bodyUuid, + targetPosition, + new Quaternionf(), + new Vector3f(), + new Vector3f(), + true, + false, + true)); + PhysicsStoreAccess.enqueueAll(world, requests); + TimeResource time = store.getResource(TimeResource.getResourceType()); - ExamplePhysicsUtils.spawnPhysicsBodyBlockEntity(store, + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, - bodyKey, - spaceId, - spawn, - ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, - PhysicsBodyType.KINEMATIC, - 0.0f, - target); + new ExamplePhysicsUtils.PendingBlockBody(bodyKey, + spaceId, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + (float) spawn.x, + (float) spawn.y, + (float) spawn.z, + false)); ctx.sender().sendMessage(Message.raw("Queued ECS kinematic platform " + bodyKey + " in space " + spaceId.value() + ".")); @@ -378,7 +409,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); - ensureContactEvents(resource); + boolean contactEventsEnabled = contactEventsEnabled(resource); + UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + if (spaceUuid == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, spaceId, List.of(spawn), @@ -386,6 +423,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Math.max(0L, world.getTick())); RigidBodyKey bodyKey = RigidBodyKey.random(); + UUID bodyUuid = bodyKey.value(); + PhysicsStoreAccess.enqueue(world, + ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyUuid, + vector(spawn), + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 1.0f, + RigidBodySpawnSettings.material(SOURCE_FRICTION, SOURCE_RESTITUTION), + null)); TimeResource time = store.getResource(TimeResource.getResourceType()); ExplosiveBlockComponent settings = new ExplosiveBlockComponent(blockType, 0, @@ -394,16 +440,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, maxFragments, strength, verticalLift); - Holder holder = ExamplePhysicsUtils.physicsBodyBlockEntityHolder(time, + Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, bodyKey, + bodyUuid, spaceId, - spawn, blockType, - PhysicsBodyType.DYNAMIC, - 1.0f, - SOURCE_FRICTION, - SOURCE_RESTITUTION, - null); + spawn, + new Vector3f(), + new Quaternionf(), + Float.NaN, + true); holder.addComponent(ExplosiveBlockComponent.getComponentType(), settings); holder.addComponent(ExplosiveFuseComponent.getComponentType(), new ExplosiveFuseComponent()); store.addEntity(holder, AddReason.SPAWN); @@ -413,6 +459,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " radius=" + radius + " maxFragments=" + maxFragments + " collisionBodies=" + stats.buildStats().colliderBodies() + + " contactEvents=" + contactEventsEnabled + ".")); return CompletableFuture.completedFuture(null); } @@ -426,13 +473,8 @@ private static BlockType blockType(@Nonnull String blockTypeId) { return BlockType.getAssetMap().getAsset(ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE); } - private static void ensureContactEvents(@Nonnull PhysicsWorldResource resource) { - PhysicsWorldSettings settings = resource.getWorldSettings(); - if (settings.getEventCollectionMode() == PhysicsEventCollectionMode.CONTACTS) { - return; - } - settings.setEventCollectionMode(PhysicsEventCollectionMode.CONTACTS); - resource.setWorldSettings(settings); + private static boolean contactEventsEnabled(@Nonnull PhysicsWorldResource resource) { + return resource.getWorldSettings().getEventCollectionMode() == PhysicsEventCollectionMode.CONTACTS; } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index c8963a1f..76a501fa 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -1022,7 +1022,7 @@ private static Ref spawnAttachedPhysicsStoreBlockEntity(@Nonnull St } @Nonnull - private static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, + public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, @Nonnull RigidBodyKey bodyKey, @Nonnull UUID physicsBodyUuid, @Nonnull SpaceId spaceId, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 28a035cf..023f5361 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -19,6 +19,10 @@ import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -30,6 +34,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -108,6 +113,12 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @Nonnull ExplosiveBlockComponent settings) { + UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + if (spaceUuid == null) { + throw new IllegalStateException("Cannot spawn explosive fragments because PhysicsStore " + + "space id=" + spaceId.value() + " is not bound"); + } + ExplosionUtils.performExplosion(DAMAGE_SOURCE, new Vector3d(center), new Rotation3f(), @@ -135,30 +146,35 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e Math.max(8, maxGroupCollisionRadius(groups) + 4), Math.max(0L, world.getTick())); - List pending = new ArrayList<>(groups.size()); Vector3f centerF = toVector3f(center); - ExamplePhysicsUtils.requireApplied(resource.submitCommands( - Math.max(0L, world.getTick()), - groups.size() * 2, - commands -> { - for (int i = 0; i < groups.size(); i++) { - FragmentGroup group = groups.get(i); - PendingBlockBody body = ExamplePhysicsUtils.recordBlockBodySpawnAtBodyCenter(commands, - spaceId, - group.center(), - group.blockType(), - group.shape(), - group.mass(), - FRAGMENT_SETTINGS); - pending.add(body); - Vector3f impulse = ExplosiveBlockPolicy.outwardImpulse(centerF, - toVector3f(group.center()), - settings.getImpulseStrength(), - settings.getVerticalLift()) - .mul(group.mass()); - commands.applyBodyImpulse(body.bodyKey(), impulse.x, impulse.y, impulse.z); - } - }), "spawn explosive block fragments"); + List pending = new ArrayList<>(groups.size()); + List requests = new ArrayList<>(groups.size() * 2); + for (FragmentGroup group : groups) { + RigidBodyKey bodyKey = RigidBodyKey.random(); + UUID bodyUuid = bodyKey.value(); + Vector3d groupCenter = group.center(); + requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyUuid, + toVector3f(groupCenter), + group.shape(), + group.mass(), + FRAGMENT_SETTINGS, + null)); + Vector3f impulse = ExplosiveBlockPolicy.outwardImpulse(centerF, + toVector3f(groupCenter), + settings.getImpulseStrength(), + settings.getVerticalLift()) + .mul(group.mass()); + requests.add(BodyForceRequest.impulse(bodyUuid, impulse.x, impulse.y, impulse.z)); + pending.add(new PendingBlockBody(bodyKey, + spaceId, + group.blockType(), + (float) groupCenter.x, + (float) groupCenter.y, + (float) groupCenter.z, + group.mass() > 0.0f)); + } + PhysicsStoreAccess.enqueueAll(world, requests); for (int i = 0; i < groups.size(); i++) { spawnGroupVisuals(time, fragmentSpawner, groups.get(i), pending.get(i)); @@ -173,8 +189,9 @@ private static void spawnGroupVisuals(@Nonnull TimeResource time, boolean controllableAssigned = false; for (FragmentVisual visual : group.visualBlocks()) { boolean controllable = body.controllable() && !controllableAssigned; - Holder holder = ExamplePhysicsUtils.attachedBlockEntityHolder(time, + Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, body.bodyKey(), + body.bodyKey().value(), body.spaceId(), visual.blockType(), visual.position(), From e957da6ed863a13b4c3b12b851bfe339bb363d1d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 21:02:39 +0200 Subject: [PATCH 039/534] feat(examples): author stress shapes through physics store Signed-off-by: Blovien --- .../impulse/examples/commands/stress/StressShapesCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index 683cd2b8..b1f5acde 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -83,7 +83,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, base, 4.8); } - ctx.sender().sendMessage(Message.raw("Spawned " + sets + " mixed shape sets (" + ctx.sender().sendMessage(Message.raw("Queued " + sets + " mixed shape sets (" + (sets * 5) + " bodies).")); return CompletableFuture.completedFuture(null); } @@ -96,7 +96,7 @@ private static void spawn(@Nonnull Store store, @Nonnull PhysicsAxis axis, @Nonnull Vector3d base, double xOffset) { - ExamplePhysicsUtils.spawnBlockBodyLegacy(store, + ExamplePhysicsUtils.spawnBlockBody(store, time, resource, spaceId, From 22674b5c1fde69dcf9f899b66453fe8d53417137 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 21:18:05 +0200 Subject: [PATCH 040/534] fix(examples): arm explosive fuses from physics store snapshots Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreAccess.java | 10 +++++ .../systems/ExplosiveFuseTickSystem.java | 43 ++++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java index 33c27a85..f9ba7074 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; @@ -22,6 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -86,6 +88,14 @@ public static int spaceCount(@Nonnull World world) { return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).size(); } + @Nullable + public static PhysicsStoreBodySnapshot getBodySnapshot(@Nonnull World world, + @Nonnull UUID bodyUuid) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Store store = require(world).getStore(); + return store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyUuid); + } + @Nullable public static PhysicsSpaceSettings getSpaceSettings(@Nonnull World world, @Nonnull SpaceId spaceId) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 8b88e058..01e340c5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -16,13 +16,17 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3d; +import org.joml.Vector3f; public final class ExplosiveFuseTickSystem extends EntityTickingSystem implements QuerySystem { @@ -64,7 +68,7 @@ public void tick(float dt, Ref ref = chunk.getReferenceTo(index); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - PhysicsBodySnapshot snapshot = bodySnapshot(resource, attachment.getBodyKey()); + BodyMotionSnapshot snapshot = bodySnapshot(store, resource, attachment); Vector3d currentCenter = explosionCenter(snapshot, transform); if (!fuse.isArmed()) { ExplosiveFuseComponent updated = fuse.clone(); @@ -100,7 +104,7 @@ private static long currentTick(@Nonnull Store store) { } @Nonnull - private static Vector3d explosionCenter(@Nullable PhysicsBodySnapshot snapshot, + private static Vector3d explosionCenter(@Nullable BodyMotionSnapshot snapshot, @Nonnull TransformComponent transform) { if (snapshot != null) { return ExplosiveBlockRuntime.sourceExplosionCenter(new Vector3d(snapshot.positionX(), @@ -111,15 +115,44 @@ private static Vector3d explosionCenter(@Nullable PhysicsBodySnapshot snapshot, } @Nullable - private static PhysicsBodySnapshot bodySnapshot(@Nonnull PhysicsWorldResource resource, - @Nonnull RigidBodyKey bodyKey) { + private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store, + @Nonnull PhysicsWorldResource resource, + @Nonnull PhysicsBodyAttachmentComponent attachment) { + UUID physicsBodyUuid = attachment.getPhysicsBodyUuid(); + if (physicsBodyUuid != null) { + PhysicsStoreBodySnapshot snapshot = + PhysicsStoreAccess.getBodySnapshot(store.getExternalData().getWorld(), physicsBodyUuid); + return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; + } + RigidBodyKey bodyKey = attachment.getBodyKey(); if (resource.getBodyRegistrationView(bodyKey) != null) { try { - return resource.getBodySnapshot(bodyKey); + return BodyMotionSnapshot.from(resource.getBodySnapshot(bodyKey)); } catch (IllegalArgumentException ignored) { // Fall back to the last synced entity transform if the source body was destroyed. } } return null; } + + private record BodyMotionSnapshot(float positionX, + float positionY, + float positionZ, + float linearVelocityY) { + + @Nonnull + private static BodyMotionSnapshot from(@Nonnull PhysicsStoreBodySnapshot snapshot) { + Vector3f position = snapshot.position(); + Vector3f velocity = snapshot.linearVelocity(); + return new BodyMotionSnapshot(position.x, position.y, position.z, velocity.y); + } + + @Nonnull + private static BodyMotionSnapshot from(@Nonnull PhysicsBodySnapshot snapshot) { + return new BodyMotionSnapshot(snapshot.positionX(), + snapshot.positionY(), + snapshot.positionZ(), + snapshot.linearVelocityY()); + } + } } From 7ec0abb451360c4506832857384177ccf2ef577f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 21:25:59 +0200 Subject: [PATCH 041/534] fix(core): demote legacy physics persistence writes Signed-off-by: Blovien --- .../PersistentPhysicsWorldResource.java | 13 +-- .../PersistentPhysicsStorePreflight.java | 9 ++ .../PhysicsRestoreStatusResource.java | 2 +- .../systems/SpaceBindingSystem.java | 13 ++- .../persistence/PhysicsPersistence.java | 99 +++++++++---------- .../examples/commands/PersistenceCommand.java | 65 ++++-------- 6 files changed, 95 insertions(+), 106 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java index 1e67abb3..cbf4a4e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java @@ -13,7 +13,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PersistentPhysicsWorldSyncSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceResource; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceSyncResult; @@ -186,13 +185,11 @@ public void setSchemaVersion(int schemaVersion) { @Override public PhysicsPersistenceSyncResult saveRuntimeSnapshot(@Nonnull Store store, @Nonnull PhysicsWorldResource runtime) { - PersistentPhysicsWorldSyncSystem.SyncResult result = - PersistentPhysicsWorldSyncSystem.syncRuntimeSnapshot(store, this, runtime); - return new PhysicsPersistenceSyncResult(result.synced(), - result.spaces(), - result.bodies(), - result.joints(), - result.skippedReason()); + return new PhysicsPersistenceSyncResult(false, + getSpaceCount(), + getBodyCount(), + getJointCount(), + "legacy-persistence-import-only"); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java index 896f934a..c3b21618 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java @@ -1,5 +1,7 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.ArrayList; import java.util.HashSet; @@ -52,6 +54,13 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, } if (space.getBackendId().isBlank()) { errors.add("PhysicsStore space " + uuid + " has blank backend id"); + } else { + try { + Impulse.getRuntimeProvider(new BackendId(space.getBackendId())); + } catch (RuntimeException exception) { + errors.add("PhysicsStore space " + uuid + + " references unavailable backend id " + space.getBackendId()); + } } if (!PhysicsStorePersistenceValidation.isFinite(space.getGravity())) { errors.add("PhysicsStore space " + uuid + " has non-finite gravity"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java index 58077388..318a059f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java @@ -67,7 +67,7 @@ public void markHydrated() { } public void recordSoftSkip(@Nonnull String reason) { - softSkipsByReason.mergeInt(reason, 1, Integer::sum); + softSkipsByReason.putIfAbsent(reason, 1); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index 4d3b4120..a08c5128 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -54,13 +54,14 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsIdentityIndexResource identity = store.getResource( PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindChunk(runtime, compatibility, identity, chunk); + (chunk, _) -> bindChunk(runtime, compatibility, identity, restore, chunk); store.forEachChunk(systemIndex, collector); } private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); @@ -75,6 +76,7 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, bindSpace(runtime, compatibility, identity, + restore, chunk.getReferenceTo(index), spaceUuid, space, @@ -86,6 +88,7 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Ref ref, @Nonnull UUID spaceUuid, @Nonnull SpaceComponent space, @@ -97,7 +100,13 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, } PhysicsBackendRuntime backendRuntime = runtime.getRuntime(backendId); if (backendRuntime == null) { - backendRuntime = Impulse.createRuntime(backendId); + try { + backendRuntime = Impulse.createRuntime(backendId); + } catch (RuntimeException exception) { + restore.markFailed("PhysicsStore space " + spaceUuid + + " references unavailable backend id " + backendId.value()); + return; + } runtime.putRuntime(backendId, backendRuntime); } SpaceId compatibilitySpaceId = compatibility.getSpaceId(spaceUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 0ad4d4e0..1fc74779 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -2,9 +2,14 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RuntimeJointCountQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; +import java.util.List; import javax.annotation.Nonnull; /** @@ -13,91 +18,83 @@ public final class PhysicsPersistence { public static final int CURRENT_SCHEMA_VERSION = - PhysicsPersistenceResource.CURRENT_SCHEMA_VERSION; + PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION; private PhysicsPersistence() { } @Nonnull public static SaveResult saveRuntimeSnapshot(@Nonnull Store store) { - PhysicsPersistenceResource persistent = persistent(store); - PhysicsWorldResource runtime = runtime(store); - PhysicsPersistenceSyncResult result = persistent.saveRuntimeSnapshot(store, runtime); - return new SaveResult(result.synced(), - persistent.getSchemaVersion(), - result.spaces(), - result.bodies(), - result.joints(), - result.skippedReason()); + Status status = status(store); + return new SaveResult(false, + status.schemaVersion(), + status.storedSpaces(), + status.storedBodies(), + status.storedJoints(), + "authoritative-physics-store-auto-capture"); } @Nonnull public static RestoreRequestResult requestRuntimeRestore(@Nonnull Store store) { - PhysicsPersistenceResource persistent = persistent(store); Status status = status(store); - if (persistent.isRuntimeRestorePending()) { - return new RestoreRequestResult(false, "already-pending", status); - } - if (persistent.getSchemaVersion() != CURRENT_SCHEMA_VERSION) { - return new RestoreRequestResult(false, "schema-mismatch", status); - } - - persistent.markRuntimeRestorePending(); - return new RestoreRequestResult(true, "", status(store)); + return new RestoreRequestResult(false, "authoritative-physics-store-auto-restore", status); } @Nonnull public static Status status(@Nonnull Store store) { - PhysicsPersistenceResource persistent = persistent(store); PhysicsWorldResource runtime = runtime(store); - return new Status(runtime.getSpaceCount(), - runtime.getBodyRegistrationCount(PhysicsBodyPersistenceMode.PERSISTENT), - runtime.getBodyRegistrationCount(PhysicsBodyPersistenceMode.RUNTIME_ONLY), - countRuntimeJoints(runtime), + Store physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()) + .getStore(); + PersistentPhysicsStoreResource persistent = physicsStore.getResource( + PersistentPhysicsStoreResource.getResourceType()); + PhysicsRestoreStatusResource restore = physicsStore.getResource( + PhysicsRestoreStatusResource.getResourceType()); + List summaries = spaceSummaries(runtime); + int runtimeBodies = summaries.stream().mapToInt(SpaceSummary::bodyCount).sum(); + int runtimeJoints = summaries.stream().mapToInt(SpaceSummary::jointCount).sum(); + return new Status(Math.max(PhysicsStoreAccess.spaceCount(store.getExternalData().getWorld()), + summaries.size()), + runtimeBodies, + 0, + runtimeJoints, persistent.getSchemaVersion(), - persistent.getSpaceCount(), - persistent.getBodyCount(), - persistent.getJointCount(), - restoreState(persistent), - restoreMessage(persistent)); + persistent.getSpaces().length, + persistent.getBodies().length, + persistent.getJoints().length, + restoreState(restore), + restoreMessage(restore)); } - private static int countRuntimeJoints(@Nonnull PhysicsWorldResource runtime) { - return runtime.query(new RuntimeJointCountQuery()) + @Nonnull + private static List spaceSummaries(@Nonnull PhysicsWorldResource runtime) { + return runtime.query(new SpaceSummaryQuery(null)) .completion() .toCompletableFuture() .join(); } @Nonnull - private static RestoreState restoreState(@Nonnull PhysicsPersistenceResource persistent) { - if (persistent.hasRuntimeRestoreFailed()) { + private static RestoreState restoreState(@Nonnull PhysicsRestoreStatusResource restore) { + if (restore.isFailed()) { return RestoreState.FAILED; } - if (!persistent.isRuntimeRestorePending()) { - return RestoreState.IDLE; + if (restore.isPending()) { + return RestoreState.PENDING_SPACES; } - return persistent.isRuntimeSpaceBootstrapComplete() - ? RestoreState.PENDING_BODIES_AND_JOINTS - : RestoreState.PENDING_SPACES; + return RestoreState.IDLE; } @Nonnull - private static String restoreMessage(@Nonnull PhysicsPersistenceResource persistent) { - if (persistent.hasRuntimeRestoreFailed()) { - return persistent.runtimeRestoreFailureSummary(); + private static String restoreMessage(@Nonnull PhysicsRestoreStatusResource restore) { + if (restore.isFailed()) { + return restore.getFailureMessage(); } - if (persistent.hasRuntimeRestoreSkips()) { - return persistent.runtimeRestoreSummary(); + if (!restore.getSoftSkipsByReason().isEmpty()) { + return "PhysicsStore restore soft skips: " + restore.getSoftSkipsByReason(); } return ""; } - @Nonnull - private static PhysicsPersistenceResource persistent(@Nonnull Store store) { - return store.getResource(PhysicsPersistenceResource.getResourceType()); - } - @Nonnull private static PhysicsWorldResource runtime(@Nonnull Store store) { return store.getResource(PhysicsWorldResource.getResourceType()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java index fb8fd169..45c4334b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java @@ -3,8 +3,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; -import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; -import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; @@ -30,7 +28,7 @@ public PersistenceCommand() { private static final class SaveCommand extends AbstractWorldCommand { private SaveCommand() { - super("save", "Synchronize current runtime physics into Hytale world persistence", false); + super("save", "Report authoritative PhysicsStore persistence capture status", false); } @Override @@ -39,8 +37,14 @@ protected void execute(@Nonnull CommandContext ctx, @Nonnull Store store) { SaveResult result = PhysicsPersistence.saveRuntimeSnapshot(store); if (!result.synced()) { - ctx.sendMessage(Message.raw("Cannot save Impulse world persistence while " - + result.skippedReason() + ".")); + ctx.sendMessage(Message.raw("Manual Impulse persistence save is disabled in " + + "authoritative PhysicsStore mode; PhysicsStore captures canonical state " + + "automatically. reason=" + result.skippedReason() + + ", stored schema=" + result.schemaVersion() + + ", spaces=" + result.spaces() + + ", persistentBodies=" + result.bodies() + + ", joints=" + result.joints() + + ".")); return; } @@ -55,13 +59,8 @@ protected void execute(@Nonnull CommandContext ctx, private static final class LoadCommand extends AbstractWorldCommand { - private final OptionalArg confirmArg = withOptionalArg( - "confirm", - "Required: true, because restore resets runtime physics before hydration rebuilds it", - ArgTypes.STRING); - private LoadCommand() { - super("load", "Restore runtime physics from Hytale world persistence", false); + super("load", "Report authoritative PhysicsStore persistence restore status", false); } @Override @@ -75,20 +74,17 @@ protected void execute(@Nonnull CommandContext ctx, return; } - if (!confirmRestore(ctx, world, status)) { - return; - } - RestoreRequestResult result = PhysicsPersistence.requestRuntimeRestore(store); if (!result.queued()) { - if ("schema-mismatch".equals(result.skippedReason())) { - ctx.sendMessage(Message.raw("Cannot load Impulse world persistence schema " - + status.schemaVersion() + "; expected schema " - + PhysicsPersistence.CURRENT_SCHEMA_VERSION + ".")); - } else { - ctx.sendMessage(Message.raw("Cannot queue Impulse persistence restore: " - + result.skippedReason() + ".")); - } + ctx.sendMessage(Message.raw("Manual Impulse persistence restore is disabled in " + + "authoritative PhysicsStore mode; PhysicsStore restores automatically from " + + "PersistentPhysicsStore during world startup. reason=" + + result.skippedReason() + + ", stored schema=" + status.schemaVersion() + + ", spaces=" + status.storedSpaces() + + ", persistentBodies=" + status.storedBodies() + + ", joints=" + status.storedJoints() + + ".")); return; } @@ -100,23 +96,6 @@ protected void execute(@Nonnull CommandContext ctx, + ", joints=" + status.storedJoints() + ". Runtime physics will reset and hydrate on the next tick.")); } - - private boolean confirmRestore(@Nonnull CommandContext ctx, - @Nonnull World world, - @Nonnull Status status) { - if (confirmArg.provided(ctx) && "true".equalsIgnoreCase(confirmArg.get(ctx).trim())) { - return true; - } - - ctx.sendMessage(Message.raw("Loading Impulse world persistence for " + world.getName() - + " resets runtime spaces, bodies, joints, generated visual proxies, and control sessions, " - + "then rebuilds from the stored world resource (schema=" + status.schemaVersion() - + ", spaces=" + status.storedSpaces() - + ", persistentBodies=" + status.storedBodies() - + ", joints=" + status.storedJoints() - + "). Re-run with --confirm=true to continue.")); - return false; - } } private static final class StatusCommand extends AbstractWorldCommand { @@ -133,12 +112,10 @@ protected void execute(@Nonnull CommandContext ctx, ctx.sendMessage(Message.raw("Impulse persistence status for " + world.getName() + ": runtime spaces=" + status.runtimeSpaces() - + ", persistentBodies=" + + ", runtimeBodies=" + status.runtimePersistentBodies() - + ", runtimeOnlyBodies=" - + status.runtimeOnlyBodies() + ", joints=" + status.runtimeJoints() - + "; stored schema=" + status.schemaVersion() + + "; PhysicsStore schema=" + status.schemaVersion() + ", spaces=" + status.storedSpaces() + ", bodies=" + status.storedBodies() + ", joints=" + status.storedJoints() From 895f091a7302984acc8a5b42ed428318bc6de0a2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 21:36:21 +0200 Subject: [PATCH 042/534] fix(core): register physics systems without forward dependencies Signed-off-by: Blovien --- .../java/dev/hytalemodding/impulse/core/ImpulsePlugin.java | 2 +- .../internal/physicsstore/systems/BodyBindingSystem.java | 2 +- .../physicsstore/systems/ColliderBindingSystem.java | 3 +-- .../systems/CompletedStepPublicationSystem.java | 3 +-- .../internal/physicsstore/systems/IdentityIndexSystem.java | 3 +-- .../internal/physicsstore/systems/JointBindingSystem.java | 3 +-- .../physicsstore/systems/PersistenceCaptureSystem.java | 3 +-- .../physicsstore/systems/PersistenceHydrationSystem.java | 6 +----- .../internal/physicsstore/systems/RequestDrainSystem.java | 3 +-- .../internal/physicsstore/systems/SpaceBindingSystem.java | 2 +- .../systems/SpaceSettingsApplicationSystem.java | 3 +-- .../internal/physicsstore/systems/TargetBindingSystem.java | 3 +-- .../physicsstore/systems/TerrainColliderBindingSystem.java | 3 +-- .../physicsstore/systems/WorldCollisionIndexSystem.java | 3 +-- .../body/PhysicsStoreKinematicTargetProducerSystem.java | 5 +---- .../core/internal/systems/sync/PhysicsSyncSystem.java | 4 ++++ .../visual/PhysicsDetachedVisualMaterializationSystem.java | 5 +---- 17 files changed, 20 insertions(+), 36 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index f9bc5dc0..e6493045 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -367,9 +367,9 @@ private void registerSystems() { persistenceRestoreGroup = entityRegistry.registerSystemGroup(); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); entityRegistry.registerSystem(new PhysicsStoreKinematicTargetProducerSystem()); + entityRegistry.registerSystem(new PhysicsDetachedVisualMaterializationSystem()); entityRegistry.registerSystem(new PhysicsSyncSystem()); entityRegistry.registerSystem(new PhysicsDebugSystem()); - entityRegistry.registerSystem(new PhysicsDetachedVisualMaterializationSystem()); } private void registerCommands() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index 837a96fd..17c37c31 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -46,7 +46,7 @@ public final class BodyBindingSystem extends TickingSystem private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), - new SystemDependency<>(Order.BEFORE, ColliderBindingSystem.class) + new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java index 7c767839..2db4894e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java @@ -15,8 +15,7 @@ public final class ColliderBindingSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, BodyBindingSystem.class), - new SystemDependency<>(Order.BEFORE, JointBindingSystem.class) + new SystemDependency<>(Order.AFTER, BodyBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 51925e36..6302850b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -25,8 +25,7 @@ public final class CompletedStepPublicationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), - new SystemDependency<>(Order.BEFORE, PersistenceCaptureSystem.class) + new SystemDependency<>(Order.AFTER, TargetBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java index edbdf610..a65dd8c5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java @@ -24,8 +24,7 @@ public final class IdentityIndexSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, RequestDrainSystem.class), - new SystemDependency<>(Order.BEFORE, SpaceBindingSystem.class) + new SystemDependency<>(Order.AFTER, RequestDrainSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index c5696224..a8bd4c31 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -34,8 +34,7 @@ public final class JointBindingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, ColliderBindingSystem.class), - new SystemDependency<>(Order.BEFORE, TerrainColliderBindingSystem.class) + new SystemDependency<>(Order.AFTER, ColliderBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index 9ba4cec7..e7cf8ce6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -60,8 +60,7 @@ public final class PersistenceCaptureSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, CompletedStepPublicationSystem.class), - new SystemDependency<>(Order.BEFORE, StepSubmissionSystem.class) + new SystemDependency<>(Order.AFTER, CompletedStepPublicationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index 6c6891f3..eecb4994 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -4,8 +4,6 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; @@ -46,9 +44,7 @@ */ public final class PersistenceHydrationSystem extends TickingSystem { - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.BEFORE, RequestDrainSystem.class) - ); + private static final Set> DEPENDENCIES = Set.of(); @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index 9f3255fd..4c3eb99f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -83,8 +83,7 @@ public final class RequestDrainSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class), - new SystemDependency<>(Order.BEFORE, IdentityIndexSystem.class) + new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index a08c5128..42e05c44 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -38,7 +38,7 @@ public final class SpaceBindingSystem extends TickingSystem private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), - new SystemDependency<>(Order.BEFORE, BodyBindingSystem.class) + new SystemDependency<>(Order.AFTER, WorldCollisionIndexSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java index 9107772c..f9dc42f8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java @@ -32,8 +32,7 @@ public final class SpaceSettingsApplicationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), - new SystemDependency<>(Order.BEFORE, BodyBindingSystem.class) + new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index 683d1a4f..6b48ea49 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -31,8 +31,7 @@ public final class TargetBindingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class), - new SystemDependency<>(Order.BEFORE, CompletedStepPublicationSystem.class) + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 35b43ec3..3afc4e74 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -38,8 +38,7 @@ public final class TerrainColliderBindingSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, JointBindingSystem.class), - new SystemDependency<>(Order.BEFORE, TargetBindingSystem.class) + new SystemDependency<>(Order.AFTER, JointBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java index 3e3c7b1b..ad5c906c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java @@ -28,8 +28,7 @@ public final class WorldCollisionIndexSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), - new SystemDependency<>(Order.BEFORE, SpaceBindingSystem.class) + new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java index 35bc18e7..4902ac4b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.dependency.SystemGroupDependency; import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.QuerySystem; @@ -17,7 +16,6 @@ import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; @@ -46,8 +44,7 @@ public final class PhysicsStoreKinematicTargetProducerSystem extends TickingSyst PhysicsBodyKinematicTargetComponent.getComponentType(); private static final Query QUERY = Query.and(IDENTITY_TYPE, TARGET_TYPE); private final Set> dependencies = Set.of( - new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), - new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) + new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()) ); @Nonnull private final Map, RigidBodyKinematicTargetState> statesByStore = diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 53988a18..66cd5240 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -24,7 +24,9 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.internal.systems.body.PhysicsStoreKinematicTargetProducerSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.GeneratedProxyLifecycle; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; @@ -62,6 +64,8 @@ public class PhysicsSyncSystem extends EntityTickingSystem { private static final Query QUERY = Query.and(ATTACHMENT_TYPE, TRANSFORM_TYPE); private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), + new SystemDependency<>(Order.AFTER, PhysicsStoreKinematicTargetProducerSystem.class), + new SystemDependency<>(Order.AFTER, PhysicsDetachedVisualMaterializationSystem.class), new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class), new SystemDependency<>(Order.BEFORE, UpdateLocationSystems.TickingSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index f578dc70..6e7d49cd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -9,7 +9,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.dependency.SystemGroupDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.math.util.ChunkUtil; @@ -31,7 +30,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; @@ -72,8 +70,7 @@ public class PhysicsDetachedVisualMaterializationSystem extends TickingSystem> dependencies = Set.of( - new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), - new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) + new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()) ); /** From 82626ef4a4443719162dd9c5175405530d6d9a98 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 21:50:41 +0200 Subject: [PATCH 043/534] fix(core): harden physics store restore status Signed-off-by: Blovien --- .../PersistentPhysicsStorePreflight.java | 8 -- .../PhysicsRestoreStatusResource.java | 2 +- .../systems/BodyBindingSystem.java | 93 +++++++++++-------- .../systems/JointBindingSystem.java | 61 +++++++----- .../systems/SpaceBindingSystem.java | 36 ++++--- .../SpaceSettingsApplicationSystem.java | 8 +- .../systems/TerrainColliderBindingSystem.java | 6 +- .../persistence/PhysicsPersistence.java | 30 +++++- 8 files changed, 155 insertions(+), 89 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java index c3b21618..54aa2620 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java @@ -231,14 +231,6 @@ private static void validateJoints(@Nonnull PersistentJointDto[] joints, errors.add("Joint " + uuid + " references missing space " + joint.getSpaceUuid()); } - if (!bodies.contains(joint.getBodyAUuid())) { - errors.add("Joint " + uuid + " references missing body A " - + joint.getBodyAUuid()); - } - if (!bodies.contains(joint.getBodyBUuid())) { - errors.add("Joint " + uuid + " references missing body B " - + joint.getBodyBUuid()); - } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java index 318a059f..9903e95d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java @@ -67,7 +67,7 @@ public void markHydrated() { } public void recordSoftSkip(@Nonnull String reason) { - softSkipsByReason.putIfAbsent(reason, 1); + softSkipsByReason.put(reason, softSkipsByReason.getInt(reason) + 1); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index 17c37c31..f58fc8eb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -164,47 +164,60 @@ private static void bindBody(@Nonnull Store store, Quaternionf rotation = target != null ? initialTarget.getRotation() : new Quaternionf(); PhysicsBodyType bodyType = bodyDynamics.getBodyType(); float mass = bodyType == PhysicsBodyType.DYNAMIC ? bodyDynamics.getMass() : 0.0f; - long bodyId = backendRuntime.createBody(spaceHandle.value(), - BackendRuntimeCodes.shapeTypeCode(shape.getShapeType()), - shape.getHalfExtentX(), - shape.getHalfExtentY(), - shape.getHalfExtentZ(), - shape.getRadius(), - shape.getHalfHeight(), - BackendRuntimeCodes.axisCode(shape.getAxis()), - shape.getGroundY(), - mass, - BackendRuntimeCodes.bodyTypeCode(bodyType), - position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w); - BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); - backendRuntime.setBodyDamping(spaceHandle.value(), - bodyId, - bodyDynamics.getLinearDamping(), - bodyDynamics.getAngularDamping()); - backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, material.getFriction()); - backendRuntime.setBodyRestitution(spaceHandle.value(), bodyId, material.getRestitution()); - backendRuntime.setBodyCollisionFilter(spaceHandle.value(), - bodyId, - filter.getCollisionGroup(), - filter.getCollisionMask()); - backendRuntime.setBodySensor(spaceHandle.value(), bodyId, colliderRow.collider().isSensor()); - if (bodyDynamics.isContinuousCollisionEnabled() - && backendRuntime.supportsContinuousCollision(spaceHandle.value())) { - backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); + long bodyId = Long.MIN_VALUE; + try { + bodyId = backendRuntime.createBody(spaceHandle.value(), + BackendRuntimeCodes.shapeTypeCode(shape.getShapeType()), + shape.getHalfExtentX(), + shape.getHalfExtentY(), + shape.getHalfExtentZ(), + shape.getRadius(), + shape.getHalfHeight(), + BackendRuntimeCodes.axisCode(shape.getAxis()), + shape.getGroundY(), + mass, + BackendRuntimeCodes.bodyTypeCode(bodyType), + position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w); + BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); + backendRuntime.setBodyDamping(spaceHandle.value(), + bodyId, + bodyDynamics.getLinearDamping(), + bodyDynamics.getAngularDamping()); + backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, material.getFriction()); + backendRuntime.setBodyRestitution(spaceHandle.value(), bodyId, material.getRestitution()); + backendRuntime.setBodyCollisionFilter(spaceHandle.value(), + bodyId, + filter.getCollisionGroup(), + filter.getCollisionMask()); + backendRuntime.setBodySensor(spaceHandle.value(), bodyId, colliderRow.collider().isSensor()); + if (bodyDynamics.isContinuousCollisionEnabled() + && backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); + } + applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); + runtime.putBodyHandle(bodyUuid, body.getSpaceUuid(), spaceHandle, bodyHandle); + runtime.putBodyHitMetadata(bodyHandle, + RigidBodyKey.of(bodyUuid), + bodyType, + shape.getShapeType()); + identity.putBodyHandle(bodyHandle, bodyRef); + } catch (RuntimeException exception) { + if (bodyId != Long.MIN_VALUE) { + try { + backendRuntime.removeBody(spaceHandle.value(), bodyId); + } catch (RuntimeException ignored) { + // Preserve the original backend failure as the restore status. + } + } + restore.markFailed("PhysicsStore body " + bodyUuid + + " failed backend binding: " + exception.getMessage()); } - applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); - runtime.putBodyHandle(bodyUuid, body.getSpaceUuid(), spaceHandle, bodyHandle); - runtime.putBodyHitMetadata(bodyHandle, - RigidBodyKey.of(bodyUuid), - bodyType, - shape.getShapeType()); - identity.putBodyHandle(bodyHandle, bodyRef); } private static void applyInitialTargetState(@Nonnull PhysicsBackendRuntime backendRuntime, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index a8bd4c31..0fb99f9e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -103,30 +103,43 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, Vector3f anchorA = joint.getAnchorA(); Vector3f anchorB = joint.getAnchorB(); Vector3f axis = joint.getAxis(); - long jointId = backendRuntime.createJoint(spaceHandle.value(), - BackendRuntimeCodes.jointTypeCode(BackendJointType.valueOf(joint.getType().name())), - bodyA.value(), - bodyB.value(), - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z, - joint.getSpringRestLength(), - joint.getSpringStiffness(), - joint.getSpringDamping(), - joint.getLowerLimit(), - joint.getUpperLimit(), - joint.isMotorEnabled(), - joint.getMotorTargetVelocity(), - joint.getMotorMaxForce()); - BackendJointHandle handle = new BackendJointHandle(jointId); - runtime.putJointHandle(jointUuid, spaceHandle, handle); - identity.putJointHandle(handle, jointRef); + long jointId = Long.MIN_VALUE; + try { + jointId = backendRuntime.createJoint(spaceHandle.value(), + BackendRuntimeCodes.jointTypeCode(BackendJointType.valueOf(joint.getType().name())), + bodyA.value(), + bodyB.value(), + anchorA.x, + anchorA.y, + anchorA.z, + anchorB.x, + anchorB.y, + anchorB.z, + axis.x, + axis.y, + axis.z, + joint.getSpringRestLength(), + joint.getSpringStiffness(), + joint.getSpringDamping(), + joint.getLowerLimit(), + joint.getUpperLimit(), + joint.isMotorEnabled(), + joint.getMotorTargetVelocity(), + joint.getMotorMaxForce()); + BackendJointHandle handle = new BackendJointHandle(jointId); + runtime.putJointHandle(jointUuid, spaceHandle, handle); + identity.putJointHandle(handle, jointRef); + } catch (RuntimeException exception) { + if (jointId != Long.MIN_VALUE) { + try { + backendRuntime.removeJoint(spaceHandle.value(), jointId); + } catch (RuntimeException ignored) { + // Preserve the original backend failure as the restore status. + } + } + restore.markFailed("PhysicsStore joint " + jointUuid + + " failed backend binding: " + exception.getMessage()); + } } private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index 42e05c44..20a2b4f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -114,18 +114,30 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, compatibilitySpaceId = SpaceId.next(); } SpaceId.reserveAtLeast(compatibilitySpaceId.value()); - BackendSpaceHandle handle = new BackendSpaceHandle( - backendRuntime.createSpace(compatibilitySpaceId)); - Vector3f gravity = space.getGravity(); - backendRuntime.setGravity(handle.value(), gravity.x, gravity.y, gravity.z); - runtime.putSpaceBinding(spaceUuid, backendId, handle); - SpaceSettingsApplicationSystem.applyBackendSettings(backendRuntime, - handle, - solverSettings != null ? solverSettings : new SolverSettingsComponent(), - extensionSettings); - runtime.clearPendingSpaceSettings(spaceUuid); - compatibility.putSpace(compatibilitySpaceId, spaceUuid); - identity.putSpaceHandle(handle, ref); + BackendSpaceHandle handle = null; + try { + handle = new BackendSpaceHandle(backendRuntime.createSpace(compatibilitySpaceId)); + Vector3f gravity = space.getGravity(); + backendRuntime.setGravity(handle.value(), gravity.x, gravity.y, gravity.z); + SpaceSettingsApplicationSystem.applyBackendSettings(backendRuntime, + handle, + solverSettings != null ? solverSettings : new SolverSettingsComponent(), + extensionSettings); + runtime.putSpaceBinding(spaceUuid, backendId, handle); + runtime.clearPendingSpaceSettings(spaceUuid); + compatibility.putSpace(compatibilitySpaceId, spaceUuid); + identity.putSpaceHandle(handle, ref); + } catch (RuntimeException exception) { + if (handle != null) { + try { + backendRuntime.destroySpace(handle.value()); + } catch (RuntimeException ignored) { + // Preserve the original backend failure as the restore status. + } + } + restore.markFailed("PhysicsStore space " + spaceUuid + + " failed backend binding: " + exception.getMessage()); + } } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java index f9dc42f8..5daddaf7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java @@ -54,7 +54,13 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (ref == null || !ref.isValid()) { continue; } - applyIfBound(store, runtime, ref, spaceUuid); + try { + applyIfBound(store, runtime, ref, spaceUuid); + } catch (RuntimeException exception) { + restore.markFailed("PhysicsStore space " + spaceUuid + + " failed backend settings application: " + exception.getMessage()); + return; + } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 3afc4e74..c2b361c7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -48,6 +48,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsTerrainPayloadResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } BiConsumer, CommandBuffer> collector = (chunk, _) -> bindChunk(runtime, payloads, restore, chunk); store.forEachChunk(systemIndex, collector); @@ -125,7 +128,8 @@ private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, stitchNeighbors(runtime, backendRuntime, spaceHandle, terrainUuid, terrain, payload); } catch (RuntimeException exception) { removeTerrain(runtime, terrainUuid); - throw exception; + restore.markFailed("PhysicsStore terrain " + terrain.getSourceKey() + + " failed backend binding: " + exception.getMessage()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 1fc74779..ecc9d113 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; @@ -47,6 +48,8 @@ public static Status status(@Nonnull Store store) { .getStore(); PersistentPhysicsStoreResource persistent = physicsStore.getResource( PersistentPhysicsStoreResource.getResourceType()); + PersistentPhysicsWorldResource legacy = store.getResource( + PersistentPhysicsWorldResource.getResourceType()); PhysicsRestoreStatusResource restore = physicsStore.getResource( PhysicsRestoreStatusResource.getResourceType()); List summaries = spaceSummaries(runtime); @@ -62,7 +65,7 @@ public static Status status(@Nonnull Store store) { persistent.getBodies().length, persistent.getJoints().length, restoreState(restore), - restoreMessage(restore)); + restoreMessage(restore, persistent, legacy)); } @Nonnull @@ -85,16 +88,39 @@ private static RestoreState restoreState(@Nonnull PhysicsRestoreStatusResource r } @Nonnull - private static String restoreMessage(@Nonnull PhysicsRestoreStatusResource restore) { + private static String restoreMessage(@Nonnull PhysicsRestoreStatusResource restore, + @Nonnull PersistentPhysicsStoreResource persistent, + @Nonnull PersistentPhysicsWorldResource legacy) { if (restore.isFailed()) { return restore.getFailureMessage(); } if (!restore.getSoftSkipsByReason().isEmpty()) { return "PhysicsStore restore soft skips: " + restore.getSoftSkipsByReason(); } + if (hasLegacyData(legacy)) { + String legacyCounts = "legacy PersistentPhysicsWorld spaces=" + legacy.getSpaceCount() + + ", bodies=" + legacy.getBodyCount() + + ", joints=" + legacy.getJointCount(); + if (hasAuthoritativeData(persistent)) { + return legacyCounts + + " ignored because PersistentPhysicsStore contains authoritative state."; + } + return legacyCounts + + " present, but legacy import into PersistentPhysicsStore is deferred."; + } return ""; } + private static boolean hasAuthoritativeData(@Nonnull PersistentPhysicsStoreResource persistent) { + return persistent.getSpaces().length > 0 + || persistent.getBodies().length > 0 + || persistent.getJoints().length > 0; + } + + private static boolean hasLegacyData(@Nonnull PersistentPhysicsWorldResource legacy) { + return legacy.getSpaceCount() > 0 || legacy.getBodyCount() > 0 || legacy.getJointCount() > 0; + } + @Nonnull private static PhysicsWorldResource runtime(@Nonnull Store store) { return store.getResource(PhysicsWorldResource.getResourceType()); From 7ff5dc2233b042381a4e1cd7ecf587731c29abd5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 21:54:25 +0200 Subject: [PATCH 044/534] fix(core): clean physics store backend shutdown Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 35 +++++++- .../resources/PhysicsRuntimeResource.java | 89 +++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 7aee15c3..3a338dff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -52,6 +52,7 @@ import java.lang.reflect.Method; import java.util.function.Consumer; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Registers authoritative PhysicsStore ECS types after the early plugin has patched Hytale. @@ -186,7 +187,39 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic if (store.isShutdown()) { return; } - store.getResource(PhysicsRequestQueueResource.getResourceType()).clear(); + RuntimeException failure = null; + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsRequestQueueResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsSnapshotResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()).clear()); + if (failure != null) { + throw failure; + } + } + + @Nullable + private static RuntimeException runShutdownCleanup(@Nullable RuntimeException failure, + @Nonnull Runnable cleanup) { + try { + cleanup.run(); + return failure; + } catch (RuntimeException exception) { + if (failure == null) { + return exception; + } + failure.addSuppressed(exception); + return failure; + } } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 900d2abd..8e2646d7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -342,9 +342,98 @@ public void clear() { bodyHitMetadataByHandle.clear(); bodySnapshotMetadataByHandle.clear(); pendingBodyOperations.clear(); + pendingSpaceSettings.clear(); started = false; } + public void destroyBackendBindings() { + RuntimeException failure = null; + for (Map.Entry entry + : new ArrayList<>(jointHandlesByUuid.entrySet())) { + BackendSpaceHandle spaceHandle = jointSpaceHandlesByUuid.get(entry.getKey()); + PhysicsBackendRuntime runtime = runtimeForSpaceHandle(spaceHandle); + if (spaceHandle == null || runtime == null) { + continue; + } + try { + runtime.removeJoint(spaceHandle.value(), entry.getValue().value()); + } catch (RuntimeException exception) { + failure = appendShutdownFailure(failure, exception); + } + } + for (Map.Entry entry + : new ArrayList<>(terrainBodyHandlesByUuid.entrySet())) { + BackendSpaceHandle spaceHandle = terrainSpaceHandlesByUuid.get(entry.getKey()); + PhysicsBackendRuntime runtime = runtimeForSpaceHandle(spaceHandle); + if (spaceHandle == null || runtime == null) { + continue; + } + LongList bodyHandles = new LongArrayList(entry.getValue()); + for (int index = 0; index < bodyHandles.size(); index++) { + try { + runtime.removeBody(spaceHandle.value(), bodyHandles.getLong(index)); + } catch (RuntimeException exception) { + failure = appendShutdownFailure(failure, exception); + } + } + } + for (Map.Entry entry + : new ArrayList<>(bodyHandlesByUuid.entrySet())) { + BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.get(entry.getKey()); + PhysicsBackendRuntime runtime = runtimeForSpaceHandle(spaceHandle); + if (spaceHandle == null || runtime == null) { + continue; + } + try { + runtime.removeBody(spaceHandle.value(), entry.getValue().value()); + } catch (RuntimeException exception) { + failure = appendShutdownFailure(failure, exception); + } + } + for (Map.Entry entry + : new ArrayList<>(spaceHandlesByUuid.entrySet())) { + BackendId backendId = backendIdsBySpaceUuid.get(entry.getKey()); + PhysicsBackendRuntime runtime = backendId != null ? runtimesByBackend.get(backendId) : null; + if (runtime == null) { + continue; + } + try { + runtime.destroySpace(entry.getValue().value()); + } catch (RuntimeException exception) { + failure = appendShutdownFailure(failure, exception); + } + } + clear(); + if (failure != null) { + throw failure; + } + } + + @Nullable + private PhysicsBackendRuntime runtimeForSpaceHandle(@Nullable BackendSpaceHandle target) { + if (target == null) { + return null; + } + for (Map.Entry entry : spaceHandlesByUuid.entrySet()) { + if (entry.getValue().value() != target.value()) { + continue; + } + BackendId backendId = backendIdsBySpaceUuid.get(entry.getKey()); + return backendId != null ? runtimesByBackend.get(backendId) : null; + } + return null; + } + + @Nonnull + private static RuntimeException appendShutdownFailure(@Nullable RuntimeException failure, + @Nonnull RuntimeException exception) { + if (failure == null) { + return exception; + } + failure.addSuppressed(exception); + return failure; + } + @Nonnull @Override public PhysicsRuntimeResource clone() { From 97ff519bf28779eaa3f37b45db1a30cf541c6bbc Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 21:57:56 +0200 Subject: [PATCH 045/534] fix(core): avoid legacy runtime stats in physics store mode Signed-off-by: Blovien --- .../commands/perf/PerfStatsCommand.java | 69 +++---------------- .../WorldCollisionPerfReportCommand.java | 48 ++----------- 2 files changed, 14 insertions(+), 103 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java index 5be7c9d4..b9b96d11 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java @@ -6,9 +6,6 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsSpaceRuntimeStatsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsSpaceRuntimeStatsView; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; @@ -26,7 +23,6 @@ protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(resource); List spaces = resource.query(new SpaceSummaryQuery(null)) .completion() .toCompletableFuture() @@ -42,86 +38,37 @@ protected void execute(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Impulse runtime stats for world " + world.getName() + ": spaces=" + spaces.size())); for (SpaceSummary space : spaces) { - SpaceStats stats = SpaceStats.from(runtime.queryInternal( - new PhysicsSpaceRuntimeStatsQuery(space.spaceId())) - .toCompletableFuture() - .join()); + SpaceStats stats = SpaceStats.from(space); totals.add(stats); ctx.sender().sendMessage(Message.raw("Space " + space.spaceId().value() + " backend=" + space.backendId().value() + " bodies=" + stats.bodies - + " dynamic=" + stats.dynamicBodies - + " awake=" + stats.awakeDynamicBodies - + " sleeping=" + stats.sleepingDynamicBodies - + " static=" + stats.staticBodies - + " kinematic=" + stats.kinematicBodies - + " owned=" + stats.entityOwnedBodies - + " detached=" + stats.detachedBodies - + " planes=" + stats.planeBodies - + " raw=" + stats.rawBodies + " joints=" + stats.joints - + " contacts=" + stats.contacts)); + + " classification=unavailable" + + " contacts=unavailable")); } ctx.sender().sendMessage(Message.raw("Totals: bodies=" + totals.bodies - + " dynamic=" + totals.dynamicBodies - + " awake=" + totals.awakeDynamicBodies - + " sleeping=" + totals.sleepingDynamicBodies - + " static=" + totals.staticBodies - + " kinematic=" + totals.kinematicBodies - + " owned=" + totals.entityOwnedBodies - + " detached=" + totals.detachedBodies - + " planes=" + totals.planeBodies - + " raw=" + totals.rawBodies + " joints=" + totals.joints - + " contacts=" + totals.contacts)); + + " classification=unavailable" + + " contacts=unavailable")); } private static final class SpaceStats { private int bodies; - private int dynamicBodies; - private int awakeDynamicBodies; - private int sleepingDynamicBodies; - private int staticBodies; - private int kinematicBodies; - private int entityOwnedBodies; - private int detachedBodies; - private int planeBodies; - private int rawBodies; private int joints; - private int contacts; @Nonnull - private static SpaceStats from(@Nonnull PhysicsSpaceRuntimeStatsView view) { + private static SpaceStats from(@Nonnull SpaceSummary summary) { SpaceStats stats = new SpaceStats(); - stats.bodies = view.bodies(); - stats.dynamicBodies = view.dynamicBodies(); - stats.awakeDynamicBodies = view.awakeDynamicBodies(); - stats.sleepingDynamicBodies = view.sleepingDynamicBodies(); - stats.staticBodies = view.staticBodies(); - stats.kinematicBodies = view.kinematicBodies(); - stats.entityOwnedBodies = view.entityOwnedBodies(); - stats.detachedBodies = view.detachedBodies(); - stats.planeBodies = view.planeBodies(); - stats.rawBodies = view.rawBodies(); - stats.joints = view.joints(); - stats.contacts = view.contacts(); + stats.bodies = summary.bodyCount(); + stats.joints = summary.jointCount(); return stats; } private void add(@Nonnull SpaceStats stats) { bodies += stats.bodies; - dynamicBodies += stats.dynamicBodies; - awakeDynamicBodies += stats.awakeDynamicBodies; - sleepingDynamicBodies += stats.sleepingDynamicBodies; - staticBodies += stats.staticBodies; - kinematicBodies += stats.kinematicBodies; - entityOwnedBodies += stats.entityOwnedBodies; - detachedBodies += stats.detachedBodies; - planeBodies += stats.planeBodies; - rawBodies += stats.rawBodies; joints += stats.joints; - contacts += stats.contacts; } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index 9da9b615..35417ded 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -7,15 +7,10 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.diagnostics.PhysicsEntityDiagnostics; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.VisualSnapshot; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsSpaceRuntimeStatsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsSpaceRuntimeStatsView; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.events.PhysicsCommandBatchEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -569,7 +564,6 @@ private record RuntimeFootprint(int spaces, @Nonnull private static RuntimeFootprint collect(@Nonnull PhysicsWorldResource resource) { - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(resource); int spaces = 0; int backendBodies = 0; int backendJoints = 0; @@ -589,46 +583,16 @@ private static RuntimeFootprint collect(@Nonnull PhysicsWorldResource resource) .toCompletableFuture() .join(); for (SpaceSummary summary : summaries) { - PhysicsSpaceRuntimeStatsView stats = runtime.queryInternal( - new PhysicsSpaceRuntimeStatsQuery(summary.spaceId())) - .toCompletableFuture() - .join(); spaces++; - backendBodies += stats.bodies(); - backendJoints += stats.joints(); - if (stats.runtimeStatsAvailable()) { - runtimeStatsSpaces++; - runtimeBodies += stats.runtimeBodyCount(); - runtimeColliders += stats.runtimeColliderCount(); - runtimeActiveBodies += stats.runtimeActiveBodyCount(); - runtimeContactPairs += stats.runtimeContactPairCount(); - runtimeContactManifolds += stats.runtimeContactManifoldCount(); - runtimeContactPoints += stats.runtimeContactPointCount(); - runtimeDynamicDynamicContactPairs += stats.runtimeDynamicDynamicContactPairCount(); - runtimeTerrainContactPairs += stats.runtimeTerrainContactPairCount(); - runtimeActiveIslands += stats.runtimeActiveIslandCount(); - runtimeJoints += stats.runtimeJointCount(); - } - } - - int detachedBodies = 0; - int detachedVisualProxies = 0; - for (PhysicsBodyRegistrationView registration : resource.getBodyRegistrationViews()) { - if (registration.kind() != PhysicsBodyKind.BODY - || resource.hasBodyAttachments(registration.bodyKey())) { - continue; - } - detachedBodies++; - if (runtime.getGeneratedVisualProxy(registration.bodyKey()) != null) { - detachedVisualProxies++; - } + backendBodies += summary.bodyCount(); + backendJoints += summary.jointCount(); } return new RuntimeFootprint(spaces, backendBodies, backendJoints, - detachedBodies, - detachedVisualProxies, + 0, + 0, runtimeStatsSpaces, runtimeBodies, runtimeColliders, @@ -647,8 +611,8 @@ private String summary() { return "spaces=" + spaces + " backendBodies=" + backendBodies + " backendJoints=" + backendJoints - + " detachedBodies=" + detachedBodies - + " detachedVisualProxies=" + detachedVisualProxies; + + " detachedBodies=unavailable" + + " detachedVisualProxies=unavailable"; } private boolean hasRuntimeStats() { From a0f4d062eb4e5e184bc26b0bb0e72ac61e0e62d5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 23:35:10 +0200 Subject: [PATCH 046/534] refactor(physics): move body authoring to PhysicsStore requests Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 70 +--- .../core/internal/commands/CleanCommand.java | 14 +- .../crucible/ImpulseLiveCrucibleTests.java | 12 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 6 +- .../diagnostics/PhysicsEntityDiagnostics.java | 14 +- .../PhysicsBodyIdentityCleanupSystem.java | 89 ----- ...icsStoreKinematicTargetProducerSystem.java | 144 -------- .../systems/body/RigidBodyCommandBatch.java | 148 -------- .../body/RigidBodyKinematicTargetState.java | 108 ------ .../body/RigidBodyLifecycleCleanupSystem.java | 87 ----- .../body/RigidBodyReconciliationPolicy.java | 23 -- .../body/RigidBodyReconciliationSystem.java | 331 ------------------ .../systems/body/RigidBodySpawnPlan.java | 76 ---- .../systems/debug/PhysicsDebugRenderer.java | 6 +- .../systems/debug/PhysicsDebugSystem.java | 10 +- ...PersistentPhysicsSpaceBootstrapSystem.java | 10 +- .../PhysicsRuntimeHolderSystem.java | 10 +- .../PhysicsBodyAttachmentIndexSystem.java | 92 ++--- .../systems/sync/PhysicsSyncSystem.java | 160 +-------- .../sync/PhysicsTransformAuthority.java | 6 +- .../visual/GameplayAttachmentSnapshot.java | 13 +- .../visual/GeneratedProxyLifecycle.java | 15 +- ...csDetachedVisualMaterializationSystem.java | 41 ++- .../PhysicsBodyCollisionComponent.java | 70 ---- .../PhysicsBodyComponentValues.java | 65 ---- .../PhysicsBodyDynamicsComponent.java | 89 ----- .../PhysicsBodyIdentityComponent.java | 112 ------ .../PhysicsBodyKinematicTargetComponent.java | 130 ------- .../PhysicsBodyLifecycleComponent.java | 127 ------- .../PhysicsBodyMaterialComponent.java | 57 --- .../components/PhysicsBodyShapeComponent.java | 177 ---------- .../PhysicsBodySpawnRequests.java | 137 ++++++++ .../projection/BodyAttachmentComponent.java} | 174 ++++----- .../impulse/examples/commands/EcsCommand.java | 1 - .../commands/ExamplePhysicsUtils.java | 195 ++--------- .../examples/commands/GrabCommand.java | 10 +- .../explosive/ExplosiveBlockRuntime.java | 1 - ...nchmarkEntityRemovalDiagnosticsSystem.java | 8 +- .../systems/ExplosiveFuseContactSystem.java | 10 +- .../systems/ExplosiveFuseTickSystem.java | 43 +-- 40 files changed, 364 insertions(+), 2527 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatch.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyKinematicTargetState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyLifecycleCleanupSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationPolicy.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlan.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyCollisionComponent.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentValues.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyDynamicsComponent.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyIdentityComponent.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyKinematicTargetComponent.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyLifecycleComponent.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyMaterialComponent.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyShapeComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{components/PhysicsBodyAttachmentComponent.java => physicsstore/projection/BodyAttachmentComponent.java} (57%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index e6493045..4f9f4e09 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -31,21 +31,13 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.internal.systems.body.PhysicsStoreKinematicTargetProducerSystem; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyLifecycleComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.nio.file.Path; import java.util.ArrayList; @@ -63,28 +55,7 @@ public final class ImpulsePlugin extends JavaPlugin { static final String OWNER_POOL_SIZE_PROPERTY = "impulse.ownerPool.size"; @Getter - private ComponentType physicsBodyAttachmentComponentType; - - @Getter - private ComponentType physicsBodyIdentityComponentType; - - @Getter - private ComponentType physicsBodyShapeComponentType; - - @Getter - private ComponentType physicsBodyDynamicsComponentType; - - @Getter - private ComponentType physicsBodyMaterialComponentType; - - @Getter - private ComponentType physicsBodyCollisionComponentType; - - @Getter - private ComponentType physicsBodyKinematicTargetComponentType; - - @Getter - private ComponentType physicsBodyLifecycleComponentType; + private ComponentType bodyAttachmentComponentType; @Getter private ComponentType generatedVisualProxyComponentType; @@ -273,38 +244,10 @@ private String getAvailableBackendIds() { private void registerComponents() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - physicsBodyAttachmentComponentType = entityRegistry.registerComponent( - PhysicsBodyAttachmentComponent.class, - "PhysicsBodyAttachment", - PhysicsBodyAttachmentComponent.CODEC); - physicsBodyIdentityComponentType = entityRegistry.registerComponent( - PhysicsBodyIdentityComponent.class, - "PhysicsBodyIdentity", - PhysicsBodyIdentityComponent.CODEC); - physicsBodyShapeComponentType = entityRegistry.registerComponent( - PhysicsBodyShapeComponent.class, - "PhysicsBodyShape", - PhysicsBodyShapeComponent.CODEC); - physicsBodyDynamicsComponentType = entityRegistry.registerComponent( - PhysicsBodyDynamicsComponent.class, - "PhysicsBodyDynamics", - PhysicsBodyDynamicsComponent.CODEC); - physicsBodyMaterialComponentType = entityRegistry.registerComponent( - PhysicsBodyMaterialComponent.class, - "PhysicsBodyMaterial", - PhysicsBodyMaterialComponent.CODEC); - physicsBodyCollisionComponentType = entityRegistry.registerComponent( - PhysicsBodyCollisionComponent.class, - "PhysicsBodyCollision", - PhysicsBodyCollisionComponent.CODEC); - physicsBodyKinematicTargetComponentType = entityRegistry.registerComponent( - PhysicsBodyKinematicTargetComponent.class, - "PhysicsBodyKinematicTarget", - PhysicsBodyKinematicTargetComponent.CODEC); - physicsBodyLifecycleComponentType = entityRegistry.registerComponent( - PhysicsBodyLifecycleComponent.class, - "PhysicsBodyLifecycle", - PhysicsBodyLifecycleComponent.CODEC); + bodyAttachmentComponentType = entityRegistry.registerComponent( + BodyAttachmentComponent.class, + "BodyAttachment", + BodyAttachmentComponent.CODEC); generatedVisualProxyComponentType = entityRegistry.registerComponent( GeneratedVisualProxyComponent.class, "GeneratedVisualProxy", @@ -366,7 +309,6 @@ private void registerSystems() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); persistenceRestoreGroup = entityRegistry.registerSystemGroup(); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); - entityRegistry.registerSystem(new PhysicsStoreKinematicTargetProducerSystem()); entityRegistry.registerSystem(new PhysicsDetachedVisualMaterializationSystem()); entityRegistry.registerSystem(new PhysicsSyncSystem()); entityRegistry.registerSystem(new PhysicsDebugSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 756d34e2..e4ef26bc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerArray; @@ -73,8 +73,8 @@ protected void execute(@Nonnull CommandContext context, private static void cleanAll(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { - ComponentType attachmentType = - PhysicsBodyAttachmentComponent.getComponentType(); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); ComponentType generatedProxyType = GeneratedVisualProxyComponent.getComponentType(); AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); @@ -140,18 +140,18 @@ private void cleanWithinRadius(@Nonnull CommandContext context, resource.refreshBodySnapshots(); Set selectedBodyKeys = selectBodyKeysNear(resource, center, radius); double radiusSquared = (double) radius * radius; - ComponentType attachmentType = - PhysicsBodyAttachmentComponent.getComponentType(); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); ComponentType generatedProxyType = GeneratedVisualProxyComponent.getComponentType(); AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, commandBuffer) -> { - PhysicsBodyAttachmentComponent attachment = + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); assert attachment != null; - if (!selectedBodyKeys.contains(attachment.getBodyKey())) { + if (!selectedBodyKeys.contains(RigidBodyKey.of(attachment.getBodyUuid()))) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 570fcbfc..899b187c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -14,9 +14,9 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -44,8 +44,8 @@ final class ImpulseLiveCrucibleTests { TransformComponent.getComponentType(); private static final ComponentType DESPAWN_TYPE = DespawnComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private ImpulseLiveCrucibleTests() { } @@ -176,7 +176,7 @@ private static Ref spawnLiveBlockBody(Store store, new Vector3d(visualPosition)); holder.removeComponent(DESPAWN_TYPE); holder.addComponent(ATTACHMENT_TYPE, - new PhysicsBodyAttachmentComponent(bodyKey, + new BodyAttachmentComponent(bodyKey.value(), spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 0100c42f..4eca0608 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; @@ -81,8 +81,8 @@ final class ImpulseRapierBodyBenchmarkCrucibleTests { private static final float BODY_VOID_Y = -128.0f; private static final double DETACHED_SPACING = 1.5; private static final Vector3d ORIGIN = new Vector3d(0.0, 128.0, 0.0); - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private ImpulseRapierBodyBenchmarkCrucibleTests() { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java index 277a2f9f..18bd2aa9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java @@ -7,8 +7,8 @@ import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems.Visible; import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerArray; import javax.annotation.Nonnull; @@ -18,8 +18,8 @@ */ public final class PhysicsEntityDiagnostics { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final ComponentType TRANSFORM_TYPE = TransformComponent.getComponentType(); private static final ComponentType NETWORK_ID_TYPE = @@ -68,7 +68,7 @@ public static Snapshot collect(@Nonnull Store store) { } private static EntityFootprint collectBodyFootprint(@Nonnull Store store, - @Nonnull ComponentType attachmentType, + @Nonnull ComponentType attachmentType, @Nonnull ComponentType transformType, @Nonnull ComponentType networkIdType, @Nonnull ComponentType visibleType) { @@ -98,12 +98,12 @@ private static EntityFootprint collectBodyFootprint(@Nonnull Store } private static VisualFootprint collectVisualFootprint(@Nonnull Store store, - @Nonnull ComponentType attachmentType, + @Nonnull ComponentType attachmentType, @Nonnull ComponentType transformType, @Nonnull ComponentType networkIdType) { AtomicIntegerArray counters = new AtomicIntegerArray(VISUAL_COUNTERS); store.forEachEntityParallel(attachmentType, (index, chunk, _) -> { - PhysicsBodyAttachmentComponent attachment = chunk.getComponent(index, attachmentType); + BodyAttachmentComponent attachment = chunk.getComponent(index, attachmentType); if (attachment == null || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystem.java deleted file mode 100644 index 50830543..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystem.java +++ /dev/null @@ -1,89 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.RefChangeSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Destroys entity-authored backend bodies when their durable ECS identity leaves the entity. - */ -public final class PhysicsBodyIdentityCleanupSystem - extends RefChangeSystem { - - @Nonnull - private final ComponentType identityType; - @Nonnull - private final ResourceType physicsWorldType; - @Nonnull - private final Query query; - - public PhysicsBodyIdentityCleanupSystem() { - this(PhysicsBodyIdentityComponent.getComponentType(), PhysicsWorldResource.getResourceType()); - } - - PhysicsBodyIdentityCleanupSystem( - @Nonnull ComponentType identityType, - @Nonnull ResourceType physicsWorldType) { - this.identityType = Objects.requireNonNull(identityType, "identityType"); - this.physicsWorldType = Objects.requireNonNull(physicsWorldType, "physicsWorldType"); - this.query = identityType; - } - - @Override - public void onComponentAdded(@Nonnull Ref ref, - @Nonnull PhysicsBodyIdentityComponent component, - @Nonnull Store store, - @Nonnull CommandBuffer commandBuffer) { - } - - @Override - public void onComponentSet(@Nonnull Ref ref, - @Nullable PhysicsBodyIdentityComponent oldComponent, - @Nonnull PhysicsBodyIdentityComponent newComponent, - @Nonnull Store store, - @Nonnull CommandBuffer commandBuffer) { - if (oldComponent != null && !sameIdentity(oldComponent, newComponent)) { - PhysicsWorldRuntimeResource.require(store.getResource(physicsWorldType)) - .destroyBody(oldComponent.getBodyKey()); - } - } - - @Override - public void onComponentRemoved(@Nonnull Ref ref, - @Nonnull PhysicsBodyIdentityComponent component, - @Nonnull Store store, - @Nonnull CommandBuffer commandBuffer) { - PhysicsWorldRuntimeResource.require(store.getResource(physicsWorldType)) - .destroyBody(component.getBodyKey()); - } - - @Nonnull - @Override - public ComponentType componentType() { - return identityType; - } - - @Nonnull - @Override - public Query getQuery() { - return query; - } - - private static boolean sameIdentity(@Nonnull PhysicsBodyIdentityComponent first, - @Nonnull PhysicsBodyIdentityComponent second) { - return first.getBodyKey().equals(second.getBodyKey()) - && Objects.equals(first.getSpaceId(), second.getSpaceId()) - && first.getPersistenceMode() == second.getPersistenceMode(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java deleted file mode 100644 index 4902ac4b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsStoreKinematicTargetProducerSystem.java +++ /dev/null @@ -1,144 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemGroupDependency; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.QuerySystem; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.WeakHashMap; -import java.util.function.BiConsumer; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Emits copied PhysicsStore target requests from EntityStore kinematic target components. - */ -public final class PhysicsStoreKinematicTargetProducerSystem extends TickingSystem - implements QuerySystem { - - private static final ComponentType IDENTITY_TYPE = - PhysicsBodyIdentityComponent.getComponentType(); - private static final ComponentType TARGET_TYPE = - PhysicsBodyKinematicTargetComponent.getComponentType(); - private static final Query QUERY = Query.and(IDENTITY_TYPE, TARGET_TYPE); - private final Set> dependencies = Set.of( - new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()) - ); - @Nonnull - private final Map, RigidBodyKinematicTargetState> statesByStore = - Collections.synchronizedMap(new WeakHashMap<>()); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsStore physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()); - Store physics = physicsStore.getStore(); - PhysicsIdentityIndexResource identity = physics.getResource( - PhysicsIdentityIndexResource.getResourceType()); - PhysicsRequestQueueResource queue = physics.getResource( - PhysicsRequestQueueResource.getResourceType()); - RigidBodyKinematicTargetState targetState = stateFor(store); - targetState.beginTick(); - try { - BiConsumer, CommandBuffer> collector = - (chunk, _) -> produceChunk(physics, identity, queue, targetState, chunk); - store.forEachChunk(systemIndex, collector); - } finally { - targetState.finishTick(); - } - } - - private static void produceChunk(@Nonnull Store physics, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRequestQueueResource queue, - @Nonnull RigidBodyKinematicTargetState targetState, - @Nonnull ArchetypeChunk chunk) { - for (int index = 0; index < chunk.size(); index++) { - PhysicsBodyIdentityComponent bodyIdentity = chunk.getComponent(index, IDENTITY_TYPE); - PhysicsBodyKinematicTargetComponent target = chunk.getComponent(index, TARGET_TYPE); - if (bodyIdentity == null || target == null) { - continue; - } - RigidBodyKey bodyKey = bodyIdentity.getBodyKey(); - UUID bodyUuid = bodyKey.value(); - if (!hasPhysicsStoreBody(physics, identity, bodyUuid)) { - targetState.clear(bodyKey); - continue; - } - if (targetState.shouldSubmit(bodyKey, target)) { - queue.enqueue(request(bodyUuid, target)); - } - } - } - - private static boolean hasPhysicsStoreBody(@Nonnull Store physics, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID bodyUuid) { - Ref ref = identity.getByUuid(bodyUuid); - return ref != null - && ref.isValid() - && physics.getComponent(ref, BodyComponent.getComponentType()) != null; - } - - @Nonnull - private static BodyTargetRequest request(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodyKinematicTargetComponent target) { - Vector3f position = target.getPosition(); - Quaternionf rotation = target.getRotation(); - Vector3f linearVelocity = target.getLinearVelocity(); - Vector3f angularVelocity = target.getAngularVelocity(); - return BodyTargetRequest.of(bodyUuid, - position, - rotation, - linearVelocity, - angularVelocity, - target.isTransformEnabled(), - target.isVelocityEnabled(), - target.isActivate()); - } - - @Nonnull - private RigidBodyKinematicTargetState stateFor(@Nonnull Store store) { - synchronized (statesByStore) { - RigidBodyKinematicTargetState state = statesByStore.get(store); - if (state == null) { - state = new RigidBodyKinematicTargetState(); - statesByStore.put(store, state); - } - return state; - } - } - - @Nonnull - @Override - public Query getQuery() { - return QUERY; - } - - @Nonnull - @Override - public Set> getDependencies() { - return dependencies; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatch.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatch.java deleted file mode 100644 index 9fb8f368..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatch.java +++ /dev/null @@ -1,148 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectSet; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Collects one entity-store tick of rigid-body ECS work into a single command submission. - */ -final class RigidBodyCommandBatch { - - @Nonnull - private final List spawns = new ArrayList<>(); - @Nonnull - private final List kinematicTargets = new ArrayList<>(); - @Nonnull - private final ObjectSet pendingBodyKeys = new ObjectOpenHashSet<>(); - - void addSpawn(@Nonnull RigidBodySpawnPlan plan, @Nonnull Vector3f position) { - spawns.add(new SpawnCommand(plan, position)); - pendingBodyKeys.add(plan.bodyKey()); - } - - void addKinematicTarget(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyKinematicTargetComponent target) { - kinematicTargets.add(new KinematicTargetCommand(bodyKey, target)); - } - - boolean hasPendingBody(@Nonnull RigidBodyKey bodyKey) { - return pendingBodyKeys.contains(bodyKey); - } - - boolean hasKinematicTargets() { - return !kinematicTargets.isEmpty(); - } - - @Nonnull - List kinematicTargetKeys() { - List keys = new ArrayList<>(kinematicTargets.size()); - for (KinematicTargetCommand target : kinematicTargets) { - keys.add(target.bodyKey()); - } - return keys; - } - - int expectedOperations() { - int operations = spawns.size(); - for (KinematicTargetCommand target : kinematicTargets) { - operations += target.operationCount(); - } - return operations; - } - - @Nullable - PhysicsCommandHandle submit(@Nonnull PhysicsWorldRuntimeResource resource) { - int expectedOperations = expectedOperations(); - if (expectedOperations == 0) { - return null; - } - return resource.submitCommands(0L, expectedOperations, this::record); - } - - void record(@Nonnull PhysicsCommandRecorder commands) { - for (SpawnCommand spawn : spawns) { - RigidBodySpawnPlan plan = spawn.plan(); - commands.spawnBody(plan.bodyKey(), recorder -> recorder - .space(plan.spaceId()) - .shape(plan.requireShape()) - .mass(plan.mass()) - .type(plan.bodyType()) - .position(spawn.position()) - .settings(plan.settings()) - .kind(PhysicsBodyKind.BODY) - .persistence(plan.persistenceMode())); - } - for (KinematicTargetCommand target : kinematicTargets) { - target.record(commands); - } - } - - private record SpawnCommand(@Nonnull RigidBodySpawnPlan plan, - @Nonnull Vector3f position) { - - private SpawnCommand { - position = new Vector3f(position); - } - } - - private record KinematicTargetCommand(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean transformEnabled, - boolean velocityEnabled, - boolean activate) { - - private KinematicTargetCommand(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyKinematicTargetComponent target) { - this(bodyKey, - target.getPosition(), - target.getRotation(), - target.getLinearVelocity(), - target.getAngularVelocity(), - target.isTransformEnabled(), - target.isVelocityEnabled(), - target.isActivate()); - } - - private KinematicTargetCommand { - position = new Vector3f(position); - rotation = new Quaternionf(rotation); - linearVelocity = new Vector3f(linearVelocity); - angularVelocity = new Vector3f(angularVelocity); - } - - int operationCount() { - int count = 0; - if (transformEnabled) { - count++; - } - if (velocityEnabled) { - count++; - } - return count; - } - - void record(@Nonnull PhysicsCommandRecorder commands) { - if (transformEnabled) { - commands.setBodyTransform(bodyKey, position, rotation, activate); - } - if (velocityEnabled) { - commands.setBodyVelocity(bodyKey, linearVelocity, angularVelocity, activate); - } - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyKinematicTargetState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyKinematicTargetState.java deleted file mode 100644 index ac83df01..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyKinematicTargetState.java +++ /dev/null @@ -1,108 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.Collection; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Tracks the last kinematic target submitted from ECS so unchanged targets do not resubmit. - */ -final class RigidBodyKinematicTargetState { - - @Nonnull - private final Object2ObjectMap submittedTargets = - new Object2ObjectOpenHashMap<>(); - private long generation; - - synchronized void beginTick() { - generation++; - } - - synchronized boolean shouldSubmit(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyKinematicTargetComponent target) { - TargetSnapshot snapshot = TargetSnapshot.copyOf(target); - if (!snapshot.hasOperations()) { - submittedTargets.remove(bodyKey); - return false; - } - TargetEntry previous = submittedTargets.get(bodyKey); - if (previous != null && snapshot.equals(previous.snapshot())) { - submittedTargets.put(bodyKey, new TargetEntry(snapshot, generation)); - return false; - } - submittedTargets.put(bodyKey, new TargetEntry(snapshot, generation)); - return true; - } - - synchronized void finishTick() { - submittedTargets.values().removeIf(entry -> entry.generation() != generation); - } - - synchronized void clear(@Nonnull RigidBodyKey bodyKey) { - submittedTargets.remove(bodyKey); - } - - synchronized void clearAll(@Nonnull Collection bodyKeys) { - for (RigidBodyKey bodyKey : bodyKeys) { - submittedTargets.remove(bodyKey); - } - } - - synchronized int trackedTargetCount() { - return submittedTargets.size(); - } - - private record TargetEntry(@Nonnull TargetSnapshot snapshot, long generation) { - } - - private record TargetSnapshot(float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - float linearVelocityX, - float linearVelocityY, - float linearVelocityZ, - float angularVelocityX, - float angularVelocityY, - float angularVelocityZ, - boolean transformEnabled, - boolean velocityEnabled, - boolean activate) { - - @Nonnull - static TargetSnapshot copyOf(@Nonnull PhysicsBodyKinematicTargetComponent target) { - Vector3f position = target.getPosition(); - Quaternionf rotation = target.getRotation(); - Vector3f linearVelocity = target.getLinearVelocity(); - Vector3f angularVelocity = target.getAngularVelocity(); - return new TargetSnapshot(position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z, - target.isTransformEnabled(), - target.isVelocityEnabled(), - target.isActivate()); - } - - boolean hasOperations() { - return transformEnabled || velocityEnabled; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyLifecycleCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyLifecycleCleanupSystem.java deleted file mode 100644 index 2c22c951..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyLifecycleCleanupSystem.java +++ /dev/null @@ -1,87 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.RefChangeSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyLifecycleComponent; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Applies explicit backend destruction when ECS ownership is removed. - */ -public class RigidBodyLifecycleCleanupSystem - extends RefChangeSystem { - - private static final ComponentType LIFECYCLE_TYPE = - PhysicsBodyLifecycleComponent.getComponentType(); - private static final Query QUERY = LIFECYCLE_TYPE; - - @Override - public void onComponentAdded(@Nonnull Ref ref, - @Nonnull PhysicsBodyLifecycleComponent component, - @Nonnull Store store, - @Nonnull CommandBuffer commandBuffer) { - } - - @Override - public void onComponentSet(@Nonnull Ref ref, - @Nullable PhysicsBodyLifecycleComponent oldComponent, - @Nonnull PhysicsBodyLifecycleComponent newComponent, - @Nonnull Store store, - @Nonnull CommandBuffer commandBuffer) { - if (oldComponent != null - && shouldDestroy(oldComponent) - && !sameBody(oldComponent, newComponent)) { - destroy(store, oldComponent); - } - } - - @Override - public void onComponentRemoved(@Nonnull Ref ref, - @Nonnull PhysicsBodyLifecycleComponent component, - @Nonnull Store store, - @Nonnull CommandBuffer commandBuffer) { - if (shouldDestroy(component)) { - destroy(store, component); - } - } - - @Nonnull - @Override - public ComponentType componentType() { - return LIFECYCLE_TYPE; - } - - @Nonnull - @Override - public Query getQuery() { - return QUERY; - } - - private static boolean shouldDestroy(@Nonnull PhysicsBodyLifecycleComponent component) { - return component.getBodyKey() != null - && component.getState() != PhysicsBodyLifecycleComponent.State.FAILED - && component.getState() != PhysicsBodyLifecycleComponent.State.DESTROYED; - } - - private static boolean sameBody(@Nonnull PhysicsBodyLifecycleComponent first, - @Nonnull PhysicsBodyLifecycleComponent second) { - RigidBodyKey firstKey = first.getBodyKey(); - return firstKey != null && firstKey.equals(second.getBodyKey()); - } - - private static void destroy(@Nonnull Store store, - @Nonnull PhysicsBodyLifecycleComponent component) { - RigidBodyKey bodyKey = component.getBodyKey(); - if (bodyKey != null) { - PhysicsWorldRuntimeResource.require(store).destroyBody(bodyKey); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationPolicy.java deleted file mode 100644 index 0a4d5dab..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationPolicy.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyLifecycleComponent; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -final class RigidBodyReconciliationPolicy { - - private RigidBodyReconciliationPolicy() { - } - - static boolean shouldSuppressBodyReconciliation(@Nullable PhysicsBodyLifecycleComponent lifecycle, - @Nullable PersistentPhysicsWorldResource persistent, - @Nonnull RigidBodyKey bodyKey) { - if (lifecycle != null - && lifecycle.getState() == PhysicsBodyLifecycleComponent.State.DESTROYED) { - return true; - } - return persistent != null && persistent.isRuntimeBodySkipped(bodyKey); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationSystem.java deleted file mode 100644 index aa6c150d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyReconciliationSystem.java +++ /dev/null @@ -1,331 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.dependency.SystemGroupDependency; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.QuerySystem; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyLifecycleComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.function.BiConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3d; -import org.joml.Vector3f; - -/** - * Reconciles ECS rigid-body components into existing physics commands. - */ -public class RigidBodyReconciliationSystem extends TickingSystem - implements QuerySystem { - - private static final ComponentType IDENTITY_TYPE = - PhysicsBodyIdentityComponent.getComponentType(); - private static final ComponentType SHAPE_TYPE = - PhysicsBodyShapeComponent.getComponentType(); - private static final ComponentType DYNAMICS_TYPE = - PhysicsBodyDynamicsComponent.getComponentType(); - private static final ComponentType MATERIAL_TYPE = - PhysicsBodyMaterialComponent.getComponentType(); - private static final ComponentType COLLISION_TYPE = - PhysicsBodyCollisionComponent.getComponentType(); - private static final ComponentType KINEMATIC_TARGET_TYPE = - PhysicsBodyKinematicTargetComponent.getComponentType(); - private static final ComponentType LIFECYCLE_TYPE = - PhysicsBodyLifecycleComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); - - private static final Query QUERY = IDENTITY_TYPE; - - private final Set> dependencies = Set.of( - new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), - new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) - ); - @Nonnull - private final Map, RigidBodyKinematicTargetState> kinematicStatesByStore = - Collections.synchronizedMap(new WeakHashMap<>()); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - RigidBodyCommandBatch physicsBatch = new RigidBodyCommandBatch(); - RigidBodyKinematicTargetState kinematicState = kinematicStateFor(store); - PersistentPhysicsWorldResource persistent = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); - - kinematicState.beginTick(); - try { - BiConsumer, CommandBuffer> collector = - (chunk, commandBuffer) -> reconcileChunk(chunk, - commandBuffer, - resource, - persistent, - physicsBatch, - kinematicState); - store.forEachChunk(systemIndex, collector); - } finally { - kinematicState.finishTick(); - } - - submitBatch(physicsBatch, resource, kinematicState); - } - - private static void reconcileChunk(@Nonnull ArchetypeChunk chunk, - @Nonnull CommandBuffer commandBuffer, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nullable PersistentPhysicsWorldResource persistent, - @Nonnull RigidBodyCommandBatch physicsBatch, - @Nonnull RigidBodyKinematicTargetState kinematicState) { - for (int index = 0; index < chunk.size(); index++) { - reconcileEntity(index, - chunk, - commandBuffer, - resource, - persistent, - physicsBatch, - kinematicState); - } - } - - private static void reconcileEntity( - int index, - @Nonnull ArchetypeChunk chunk, - @Nonnull CommandBuffer commandBuffer, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nullable PersistentPhysicsWorldResource persistent, - @Nonnull RigidBodyCommandBatch physicsBatch, - @Nonnull RigidBodyKinematicTargetState kinematicState) { - PhysicsBodyIdentityComponent identity = chunk.getComponent(index, IDENTITY_TYPE); - if (identity == null) { - return; - } - - Ref ref = chunk.getReferenceTo(index); - PhysicsBodyLifecycleComponent lifecycle = chunk.getComponent(index, LIFECYCLE_TYPE); - RigidBodyKey bodyKey = identity.getBodyKey(); - if (RigidBodyReconciliationPolicy.shouldSuppressBodyReconciliation(lifecycle, persistent, bodyKey)) { - kinematicState.clear(bodyKey); - clearAttachment(ref, chunk.getComponent(index, ATTACHMENT_TYPE), commandBuffer); - if (lifecycle == null || lifecycle.getState() != PhysicsBodyLifecycleComponent.State.DESTROYED) { - commandBuffer.putComponent(ref, - LIFECYCLE_TYPE, - PhysicsBodyLifecycleComponent.failed(bodyKey, - "Persisted body restore skipped")); - } - return; - } - - boolean registeredOrPending = resource.hasPublishedOrPendingBodyRegistration(bodyKey) - || physicsBatch.hasPendingBody(bodyKey); - - if (registeredOrPending) { - reconcileExistingBody(ref, - chunk, - index, - commandBuffer, - resource, - physicsBatch, - kinematicState, - identity, - bodyKey, - lifecycle); - return; - } - - RigidBodySpawnPlan plan; - try { - plan = createPlan(index, chunk, identity); - } catch (IllegalArgumentException exception) { - kinematicState.clear(bodyKey); - commandBuffer.putComponent(ref, - LIFECYCLE_TYPE, - PhysicsBodyLifecycleComponent.failed(bodyKey, exception.getMessage())); - clearAttachment(ref, chunk.getComponent(index, ATTACHMENT_TYPE), commandBuffer); - return; - } - - physicsBatch.addSpawn(plan, spawnPosition(chunk, index)); - commandBuffer.putComponent(ref, - LIFECYCLE_TYPE, - PhysicsBodyLifecycleComponent.pending(plan.bodyKey())); - reconcileAttachment(ref, - plan, - chunk.getComponent(index, ATTACHMENT_TYPE), - commandBuffer); - } - - private static void reconcileExistingBody(@Nonnull Ref ref, - @Nonnull ArchetypeChunk chunk, - int index, - @Nonnull CommandBuffer commandBuffer, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyCommandBatch physicsBatch, - @Nonnull RigidBodyKinematicTargetState kinematicState, - @Nonnull PhysicsBodyIdentityComponent identity, - @Nonnull RigidBodyKey bodyKey, - @Nullable PhysicsBodyLifecycleComponent lifecycle) { - RigidBodySpawnPlan plan; - try { - plan = createPlan(index, chunk, identity); - } catch (IllegalArgumentException exception) { - commandBuffer.putComponent(ref, - LIFECYCLE_TYPE, - PhysicsBodyLifecycleComponent.failed(bodyKey, exception.getMessage())); - kinematicState.clear(bodyKey); - clearAttachment(ref, chunk.getComponent(index, ATTACHMENT_TYPE), commandBuffer); - return; - } - PhysicsBodyLifecycleComponent.State state = resource.getBodyRegistrationView(bodyKey) != null - ? PhysicsBodyLifecycleComponent.State.CREATED - : PhysicsBodyLifecycleComponent.State.PENDING; - if (lifecycle == null - || lifecycle.getState() != state - || !bodyKey.equals(lifecycle.getBodyKey())) { - PhysicsBodyLifecycleComponent updated = state == PhysicsBodyLifecycleComponent.State.CREATED - ? PhysicsBodyLifecycleComponent.created(bodyKey) - : PhysicsBodyLifecycleComponent.pending(bodyKey); - commandBuffer.putComponent(ref, LIFECYCLE_TYPE, updated); - } - reconcileAttachment(ref, - plan, - chunk.getComponent(index, ATTACHMENT_TYPE), - commandBuffer); - reconcileKinematicTarget(physicsBatch, - kinematicState, - bodyKey, - chunk.getComponent(index, KINEMATIC_TARGET_TYPE)); - } - - @Nonnull - private static RigidBodySpawnPlan createPlan(int index, - @Nonnull ArchetypeChunk chunk, - @Nonnull PhysicsBodyIdentityComponent identity) { - return RigidBodySpawnPlan.create(identity, - chunk.getComponent(index, SHAPE_TYPE), - chunk.getComponent(index, DYNAMICS_TYPE), - chunk.getComponent(index, MATERIAL_TYPE), - chunk.getComponent(index, COLLISION_TYPE)); - } - - @Nonnull - private static Vector3f spawnPosition(@Nonnull ArchetypeChunk chunk, int index) { - PhysicsBodyKinematicTargetComponent target = chunk.getComponent(index, KINEMATIC_TARGET_TYPE); - if (target != null && target.isTransformEnabled()) { - return new Vector3f(target.getPosition()); - } - TransformComponent transform = chunk.getComponent(index, TRANSFORM_TYPE); - if (transform == null) { - return new Vector3f(); - } - Vector3d position = transform.getPosition(); - return new Vector3f((float) position.x, (float) position.y, (float) position.z); - } - - private static void reconcileAttachment(@Nonnull Ref ref, - @Nonnull RigidBodySpawnPlan plan, - @Nullable PhysicsBodyAttachmentComponent current, - @Nonnull CommandBuffer commandBuffer) { - if (current != null - && current.getBodyKey().equals(plan.bodyKey()) - && plan.spaceId().equals(current.getSpaceId()) - && current.getLifecycle() - == PhysicsBodyAttachmentComponent.AttachmentLifecycle.EXTERNAL_ENTITY) { - return; - } - commandBuffer.putComponent(ref, - ATTACHMENT_TYPE, - PhysicsBodyAttachmentComponent.externalEntity(plan.bodyKey(), plan.spaceId())); - } - - private static void clearAttachment(@Nonnull Ref ref, - @Nullable PhysicsBodyAttachmentComponent current, - @Nonnull CommandBuffer commandBuffer) { - if (current != null) { - commandBuffer.removeComponent(ref, ATTACHMENT_TYPE); - } - } - - private static void reconcileKinematicTarget(@Nonnull RigidBodyCommandBatch physicsBatch, - @Nonnull RigidBodyKinematicTargetState kinematicState, - @Nonnull RigidBodyKey bodyKey, - @Nullable PhysicsBodyKinematicTargetComponent target) { - if (target == null || (!target.isTransformEnabled() && !target.isVelocityEnabled())) { - kinematicState.clear(bodyKey); - return; - } - if (kinematicState.shouldSubmit(bodyKey, target)) { - physicsBatch.addKinematicTarget(bodyKey, target); - } - } - - private static void submitBatch(@Nonnull RigidBodyCommandBatch physicsBatch, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyKinematicTargetState kinematicState) { - boolean hasKinematicTargets = physicsBatch.hasKinematicTargets(); - List kinematicTargetKeys = hasKinematicTargets - ? physicsBatch.kinematicTargetKeys() - : List.of(); - PhysicsCommandHandle handle = physicsBatch.submit(resource); - if (handle == null || !hasKinematicTargets) { - return; - } - handle.completionSummary().whenComplete((completion, failure) -> { - if (failure != null || !completion.allApplied()) { - kinematicState.clearAll(kinematicTargetKeys); - } - }); - } - - @Nonnull - private RigidBodyKinematicTargetState kinematicStateFor(@Nonnull Store store) { - synchronized (kinematicStatesByStore) { - RigidBodyKinematicTargetState state = kinematicStatesByStore.get(store); - if (state == null) { - state = new RigidBodyKinematicTargetState(); - kinematicStatesByStore.put(store, state); - } - return state; - } - } - - @Nonnull - @Override - public Query getQuery() { - return QUERY; - } - - @Nonnull - @Override - public Set> getDependencies() { - return dependencies; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlan.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlan.java deleted file mode 100644 index 70d13767..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlan.java +++ /dev/null @@ -1,76 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyComponentValues; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -record RigidBodySpawnPlan(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - @Nonnull PhysicsBodyType bodyType, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - - private static final String MISSING_SPACE = - "PhysicsBodyIdentityComponent must hold a positive explicit SpaceId"; - - RigidBodySpawnPlan { - Objects.requireNonNull(bodyKey, "bodyKey"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(shape, "shape"); - Objects.requireNonNull(bodyType, "bodyType"); - Objects.requireNonNull(settings, "settings"); - Objects.requireNonNull(persistenceMode, "persistenceMode"); - } - - @Nonnull - static RigidBodySpawnPlan create(@Nonnull PhysicsBodyIdentityComponent identity, - @Nullable PhysicsBodyShapeComponent shape, - @Nullable PhysicsBodyDynamicsComponent dynamics, - @Nullable PhysicsBodyMaterialComponent material, - @Nullable PhysicsBodyCollisionComponent collision) { - Objects.requireNonNull(identity, "identity"); - if (!PhysicsBodyComponentValues.hasExplicitSpace(identity)) { - throw new IllegalArgumentException(MISSING_SPACE); - } - if (shape == null) { - throw new IllegalArgumentException("PhysicsBodyShapeComponent is required"); - } - if (dynamics == null) { - throw new IllegalArgumentException("PhysicsBodyDynamicsComponent is required"); - } - if (material == null) { - throw new IllegalArgumentException("PhysicsBodyMaterialComponent is required"); - } - if (collision == null) { - throw new IllegalArgumentException("PhysicsBodyCollisionComponent is required"); - } - assert identity.getSpaceId() != null; - return new RigidBodySpawnPlan( - identity.getBodyKey(), - identity.getSpaceId(), - PhysicsBodyComponentValues.toShapeSpec(shape), - dynamics.getBodyType(), - dynamics.getMass(), - PhysicsBodyComponentValues.toSpawnSettings(dynamics, material, collision), - identity.getPersistenceMode()); - } - - @Nonnull - PhysicsShapeSpec requireShape() { - return shape; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java index 9ac69dec..3c14cb3c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import java.util.Collection; import javax.annotation.Nonnull; import org.joml.Matrix4d; @@ -262,7 +262,7 @@ static void renderWorldCollisionBox(@Nonnull Collection viewers, static Vector3d centerFromSyncedTransform(@Nonnull PhysicsBodySnapshot snapshot, @Nonnull Vector3d transformPosition, - @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull BodyAttachmentComponent attachment, @Nonnull Quaterniond bodyRotation) { return PhysicsVisualPoseMath.bodyCenterFromVisualPose(transformPosition, bodyRotation, @@ -275,7 +275,7 @@ static Vector3d centerFromSyncedTransform(@Nonnull PhysicsBodySnapshot snapshot, static BodyDebugPose bodyPoseFromSyncedTransform(@Nonnull PhysicsBodySnapshot snapshot, @Nonnull Vector3d transformPosition, @Nonnull Quaterniond transformRotation, - @Nonnull PhysicsBodyAttachmentComponent attachment) { + @Nonnull BodyAttachmentComponent attachment) { Quaterniond bodyRotation = new Quaterniond(transformRotation); if (!isIdentity(attachment.getLocalRotationOffset())) { bodyRotation.mul(new Quaterniond(attachment.getLocalRotationOffset()).invert()).normalize(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 88d7ea83..31853b9c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -17,8 +17,8 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -60,8 +60,8 @@ */ public class PhysicsDebugSystem extends TickingSystem { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final ComponentType TRANSFORM_TYPE = TransformComponent.getComponentType(); @@ -227,7 +227,7 @@ private static int renderEntityBodies(@Nonnull Collection viewers, if (!attachmentRef.isValid()) { continue; } - PhysicsBodyAttachmentComponent attachment = store.getComponent(attachmentRef, + BodyAttachmentComponent attachment = store.getComponent(attachmentRef, ATTACHMENT_TYPE); TransformComponent transform = store.getComponent(attachmentRef, TRANSFORM_TYPE); if (attachment == null diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java index 3ed89068..d490f3cd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java @@ -18,8 +18,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -44,8 +44,8 @@ */ public class PersistentPhysicsSpaceBootstrapSystem extends TickingSystem { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final ComponentType GENERATED_PROXY_TYPE = GeneratedVisualProxyComponent.getComponentType(); @@ -135,7 +135,7 @@ private static void stripRuntimePhysicsStateForRestore(@Nonnull Store { - PhysicsBodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, ATTACHMENT_TYPE); if (attachment == null || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java index f82d8050..aca5c06e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java @@ -8,8 +8,8 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.HolderSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import javax.annotation.Nonnull; /** @@ -18,8 +18,8 @@ */ public class PhysicsRuntimeHolderSystem extends HolderSystem { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final Query QUERY = ATTACHMENT_TYPE; @Override @@ -40,7 +40,7 @@ public void onEntityRemoved(@Nonnull Holder holder, private static void cleanupHolder(@Nonnull Holder holder, @Nonnull Store store) { - PhysicsBodyAttachmentComponent attachment = holder.getComponent(ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = holder.getComponent(ATTACHMENT_TYPE); if (attachment == null || attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { holder.tryRemoveComponent(ATTACHMENT_TYPE); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index 75d43e2a..af98d8cc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -8,10 +8,8 @@ import com.hypixel.hytale.component.system.RefChangeSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -20,80 +18,44 @@ * Keeps the body-key to entity-ref attachment index in sync with ECS component changes. */ public class PhysicsBodyAttachmentIndexSystem - extends RefChangeSystem { + extends RefChangeSystem { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final Query QUERY = ATTACHMENT_TYPE; @Override public void onComponentAdded(@Nonnull Ref ref, - @Nonnull PhysicsBodyAttachmentComponent component, + @Nonnull BodyAttachmentComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { - if (!component.usesLegacyBodyKey()) { - registerPhysicsStoreAttachment(ref, component, commandBuffer); - return; - } - PhysicsWorldRuntimeResource.require( - commandBuffer.getResource(PhysicsWorldResource.getResourceType())) - .registerBodyAttachment(component.getBodyKey(), ref); + registerAttachment(ref, component, commandBuffer); } @Override public void onComponentSet(@Nonnull Ref ref, - @Nullable PhysicsBodyAttachmentComponent oldComponent, - @Nonnull PhysicsBodyAttachmentComponent newComponent, + @Nullable BodyAttachmentComponent oldComponent, + @Nonnull BodyAttachmentComponent newComponent, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { assert oldComponent != null; - boolean oldLegacy = oldComponent.usesLegacyBodyKey(); - boolean newLegacy = newComponent.usesLegacyBodyKey(); - if (!oldLegacy && !newLegacy) { - updatePhysicsStoreAttachment(ref, oldComponent, newComponent, commandBuffer); - return; - } - if (!oldLegacy) { - unregisterPhysicsStoreAttachment(ref, oldComponent, commandBuffer); - } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require( - commandBuffer.getResource(PhysicsWorldResource.getResourceType())); - if (oldLegacy && (!newLegacy || !oldComponent.getBodyKey().equals(newComponent.getBodyKey()))) { - resource.unregisterBodyAttachment(oldComponent.getBodyKey(), ref); - } - if (newLegacy && (!oldLegacy || !oldComponent.getBodyKey().equals(newComponent.getBodyKey()))) { - resource.registerBodyAttachment(newComponent.getBodyKey(), ref); - } - if (!newLegacy) { - registerPhysicsStoreAttachment(ref, newComponent, commandBuffer); - } - resource.clearBodySyncState(ref); + updateAttachment(ref, oldComponent, newComponent, commandBuffer); } @Override public void onComponentRemoved(@Nonnull Ref ref, - @Nonnull PhysicsBodyAttachmentComponent component, + @Nonnull BodyAttachmentComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { - if (!component.usesLegacyBodyKey()) { - unregisterPhysicsStoreAttachment(ref, component, commandBuffer); - return; - } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require( - commandBuffer.getResource(PhysicsWorldResource.getResourceType())); - resource.unregisterBodyAttachment(component.getBodyKey(), ref); - resource.clearBodySyncState(ref); + unregisterAttachment(ref, component, commandBuffer); } - private static void updatePhysicsStoreAttachment(@Nonnull Ref ref, - @Nonnull PhysicsBodyAttachmentComponent oldComponent, - @Nonnull PhysicsBodyAttachmentComponent newComponent, + private static void updateAttachment(@Nonnull Ref ref, + @Nonnull BodyAttachmentComponent oldComponent, + @Nonnull BodyAttachmentComponent newComponent, @Nonnull CommandBuffer commandBuffer) { - UUID oldUuid = oldComponent.getPhysicsBodyUuid(); - UUID newUuid = newComponent.getPhysicsBodyUuid(); - if (oldUuid == null || newUuid == null) { - return; - } + UUID oldUuid = oldComponent.getBodyUuid(); + UUID newUuid = newComponent.getBodyUuid(); boolean sameUuid = oldUuid.equals(newUuid); boolean oldGeneratedProxy = oldComponent.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY; boolean newGeneratedProxy = newComponent.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY; @@ -116,13 +78,10 @@ private static void updatePhysicsStoreAttachment(@Nonnull Ref ref, } } - private static void registerPhysicsStoreAttachment(@Nonnull Ref ref, - @Nonnull PhysicsBodyAttachmentComponent component, + private static void registerAttachment(@Nonnull Ref ref, + @Nonnull BodyAttachmentComponent component, @Nonnull CommandBuffer commandBuffer) { - UUID bodyUuid = component.getPhysicsBodyUuid(); - if (bodyUuid == null) { - return; - } + UUID bodyUuid = component.getBodyUuid(); PhysicsProjectionIndexResource resource = commandBuffer.getResource( PhysicsProjectionIndexResource.getResourceType()); resource.registerAttachment(bodyUuid, ref); @@ -131,13 +90,10 @@ private static void registerPhysicsStoreAttachment(@Nonnull Ref ref } } - private static void unregisterPhysicsStoreAttachment(@Nonnull Ref ref, - @Nonnull PhysicsBodyAttachmentComponent component, + private static void unregisterAttachment(@Nonnull Ref ref, + @Nonnull BodyAttachmentComponent component, @Nonnull CommandBuffer commandBuffer) { - UUID bodyUuid = component.getPhysicsBodyUuid(); - if (bodyUuid == null) { - return; - } + UUID bodyUuid = component.getBodyUuid(); PhysicsProjectionIndexResource resource = commandBuffer.getResource( PhysicsProjectionIndexResource.getResourceType()); resource.unregisterAttachment(bodyUuid, ref); @@ -148,7 +104,7 @@ private static void unregisterPhysicsStoreAttachment(@Nonnull Ref r @Nonnull @Override - public ComponentType componentType() { + public ComponentType componentType() { return ATTACHMENT_TYPE; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 66cd5240..2244cbc3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -24,12 +24,11 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.systems.body.PhysicsStoreKinematicTargetProducerSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.GeneratedProxyLifecycle; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; @@ -50,21 +49,19 @@ * hydrated bodies, and hydrated joints are all settled before this system reads * body transforms.

* - *

Entities attach to PhysicsStore body UUIDs or legacy body keys. Backend body destruction is - * explicit at the world resource boundary; missing generated visual proxies are removed from the - * legacy path, while gameplay entities merely lose the attachment.

+ *

Entities attach to authoritative PhysicsStore body UUIDs. Backend body destruction is explicit + * through PhysicsStore requests; removing an EntityStore attachment only removes the projection.

*/ public class PhysicsSyncSystem extends EntityTickingSystem { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final ComponentType TRANSFORM_TYPE = TransformComponent.getComponentType(); private static final Query QUERY = Query.and(ATTACHMENT_TYPE, TRANSFORM_TYPE); private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), - new SystemDependency<>(Order.AFTER, PhysicsStoreKinematicTargetProducerSystem.class), new SystemDependency<>(Order.AFTER, PhysicsDetachedVisualMaterializationSystem.class), new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class), new SystemDependency<>(Order.BEFORE, UpdateLocationSystems.TickingSystem.class) @@ -131,7 +128,7 @@ public void tick(float dt, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { Ref entityRef = chunk.getReferenceTo(index); - PhysicsBodyAttachmentComponent attachment = chunk.getComponent(index, ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = chunk.getComponent(index, ATTACHMENT_TYPE); TransformComponent transform = chunk.getComponent(index, TRANSFORM_TYPE); if (attachment == null || transform == null) { return; @@ -142,11 +139,9 @@ public void tick(float dt, if (collector != null) { collector.incrementBodiesInspected(); } - UUID physicsBodyUuid = attachment.getPhysicsBodyUuid(); + UUID bodyUuid = attachment.getBodyUuid(); PhysicsSnapshotResource snapshotResource = physicsStoreSnapshots.get(); - PhysicsStoreBodySnapshot physicsStoreSnapshot = physicsBodyUuid != null - ? snapshotResource.getBody(physicsBodyUuid) - : null; + PhysicsStoreBodySnapshot physicsStoreSnapshot = snapshotResource.getBody(bodyUuid); if (physicsStoreSnapshot != null) { if (!PhysicsTransformAuthority.shouldApplyBodyTransform(attachment)) { return; @@ -157,134 +152,7 @@ public void tick(float dt, } return; } - if (attachment.getPhysicsBodyUuid() != null) { - clearMissingPhysicsStoreAttachment(entityRef, attachment, commandBuffer); - return; - } - PhysicsWorldRuntimeResource resource = local.getResource(store); - PhysicsBodyRegistrationView registration = - resource.getBodyRegistrationView(attachment.getBodyKey()); - if (registration == null) { - if (resource.isBodyCreationPending(attachment.getBodyKey())) { - return; - } - GeneratedProxyLifecycle.clearMissingAttachment(entityRef, attachment, resource, commandBuffer); - return; - } - - SpaceId spaceId = registration.spaceId(); - if (resource.getSpaceBinding(spaceId) == null) { - if (collector != null) { - collector.incrementSkippedMissingSpace(); - } - GeneratedProxyLifecycle.clearMissingAttachment(entityRef, attachment, resource, commandBuffer); - return; - } - - if (!PhysicsTransformAuthority.shouldApplyBodyTransform(attachment)) { - return; - } - - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(registration.bodyKey()); - if (snapshot == null) { - GeneratedProxyLifecycle.clearMissingAttachment(entityRef, attachment, resource, commandBuffer); - return; - } - if (snapshot.isStatic()) { - if (collector != null) { - collector.incrementSkippedStatic(); - } - return; - } - - PhysicsSpaceSettings settings = resolveSpaceSettings(resource, spaceId); - snapshot.copyPositionTo(local.position); - snapshot.copyRotationTo(local.rotation); - applyVisualPose(snapshot, attachment, local); - PhysicsBodyRuntimeState.BodySyncState syncState = resource.getOrCreateBodySyncState(entityRef); - if (collector != null) { - collector.recordBodySnapshotMotion(syncState.recordSnapshotObservation(local.visualPosition)); - } else { - syncState.recordSnapshotObservation(local.visualPosition); - } - - boolean sleeping = snapshot.sleeping(); - boolean controlled = resource.isBodyControlled(registration.bodyKey()); - boolean lowSpeed = false; - if (!sleeping && snapshot.isDynamic() && !controlled) { - snapshot.copyLinearVelocityTo(local.linearVelocity); - snapshot.copyAngularVelocityTo(local.angularVelocity); - lowSpeed = local.linearVelocity.lengthSquared() <= LOW_SPEED_LINEAR_THRESHOLD_SQUARED - && local.angularVelocity.lengthSquared() <= LOW_SPEED_ANGULAR_THRESHOLD_SQUARED; - } - - boolean rangeLimitedVisual = shouldCullVisualSync(settings, attachment, controlled); - PhysicsSyncPolicy.SyncRangeTier rangeTier = PhysicsSyncPolicy.resolveRangeTier( - settings, - resource.getBodyVisualInterestState(registration.bodyKey()), - rangeLimitedVisual, - controlled, - playerInterests.get(), - local.visualPosition); - if (rangeTier == PhysicsSyncPolicy.SyncRangeTier.NEAR) { - snapshot.copyPositionTo(local.position); - snapshot.copyRotationTo(local.rotation); - applySnapshotPrediction(snapshot, - PhysicsSyncPolicy.visualPredictionSeconds(settings, - syncNanos.get(), - resource.getLatestSnapshotAppliedNanos()), - local); - applyVisualPose(snapshot, attachment, local); - } - - PhysicsSyncPolicy.SyncDecision decision = PhysicsSyncPolicy.resolveSyncDecision(syncState, - settings, - local.visualPosition, - local.visualRotation, - sleeping, - lowSpeed, - controlled, - rangeTier); - if (decision == PhysicsSyncPolicy.SyncDecision.SKIP_SLEEPING - || decision == PhysicsSyncPolicy.SyncDecision.SKIP_THRESHOLD - || decision == PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_DEADZONE - || decision == PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_RANGE) { - syncState.recordSkip(dt); - if (collector != null) { - if (decision == PhysicsSyncPolicy.SyncDecision.SKIP_SLEEPING) { - collector.incrementSkippedSleeping(); - } else if (decision == PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_DEADZONE) { - collector.incrementSkippedVisualDeadzone(); - } else if (decision == PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_RANGE) { - collector.incrementSkippedVisualRange(); - } else { - collector.incrementSkippedThreshold(); - } - } - return; - } - - if (shouldSmoothVisual(settings, snapshot, controlled, rangeTier, syncState, decision)) { - applyVisualSmoothing(settings, dt, syncState, local); - } - - if (collector != null) { - collector.recordVisualCorrection(distance(transform.getPosition(), local.visualPosition)); - } - transform.getPosition().set(local.visualPosition.x, - local.visualPosition.y, - local.visualPosition.z); - local.visualRotation.getEulerAnglesYXZ(local.euler); - transform.getRotation().set(local.euler.x, local.euler.y, local.euler.z); - syncState.recordSync(local.visualPosition, local.visualRotation, sleeping); - if (collector != null) { - collector.incrementBodiesSynced(); - if (decision == PhysicsSyncPolicy.SyncDecision.TRANSITION) { - collector.incrementTransitionSyncs(); - } else if (decision == PhysicsSyncPolicy.SyncDecision.KEEPALIVE) { - collector.incrementKeepaliveSyncs(); - } - } + clearMissingPhysicsStoreAttachment(entityRef, attachment, commandBuffer); } @Nonnull @@ -296,7 +164,7 @@ private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( } private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transform, - @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull BodyAttachmentComponent attachment, @Nonnull PhysicsStoreBodySnapshot snapshot, @Nonnull Scratch scratch) { scratch.position.set(snapshot.position()); @@ -317,7 +185,7 @@ private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transf } private static void clearMissingPhysicsStoreAttachment(@Nonnull Ref entityRef, - @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull BodyAttachmentComponent attachment, @Nonnull CommandBuffer commandBuffer) { // PhysicsStore snapshot publication is intentionally one completed frame behind request // ingestion. Absence from the latest frame is not enough evidence that the body row is gone. @@ -335,7 +203,7 @@ private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { } private void applyVisualPose(@Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull BodyAttachmentComponent attachment, @Nonnull Scratch scratch) { PhysicsVisualPoseMath.visualPositionFromBodyPose(scratch.position, scratch.rotation, @@ -436,7 +304,7 @@ private static PhysicsSpaceSettings resolveSpaceSettings(@Nonnull PhysicsWorldRu } private static boolean shouldCullVisualSync(@Nullable PhysicsSpaceSettings settings, - @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull BodyAttachmentComponent attachment, boolean controlled) { if (controlled) { return false; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java index 470bfe71..d58752c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.internal.systems.sync; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; import javax.annotation.Nonnull; final class PhysicsTransformAuthority { @@ -9,7 +9,7 @@ final class PhysicsTransformAuthority { private PhysicsTransformAuthority() { } - static boolean shouldApplyBodyTransform(@Nonnull PhysicsBodyAttachmentComponent attachment) { + static boolean shouldApplyBodyTransform(@Nonnull BodyAttachmentComponent attachment) { return attachment.getTransformAuthority() == TransformAuthority.BODY; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java index 2cebf9ad..d1ca67ae 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java @@ -4,8 +4,8 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Queue; import java.util.Set; @@ -57,17 +57,16 @@ private Set bodyKeys() { @Nonnull private static Set collectGameplayAttachmentBodyKeys( @Nonnull Store store) { - ComponentType attachmentType = - PhysicsBodyAttachmentComponent.getComponentType(); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); Queue bodyKeys = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, _) -> { - PhysicsBodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); if (attachment != null - && attachment.usesLegacyBodyKey() && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { - bodyKeys.add(attachment.getBodyKey()); + bodyKeys.add(RigidBodyKey.of(attachment.getBodyUuid())); } }); Set uniqueBodyKeys = new ObjectOpenHashSet<>(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index 18094dfd..aeb69901 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -8,8 +8,8 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import javax.annotation.Nonnull; @@ -20,8 +20,8 @@ */ public final class GeneratedProxyLifecycle { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private GeneratedProxyLifecycle() { } @@ -46,13 +46,14 @@ static void removeProxy(@Nonnull ComponentAccessor accessor, } public static void clearMissingAttachment(@Nonnull Ref entityRef, - @Nonnull PhysicsBodyAttachmentComponent attachment, + @Nonnull BodyAttachmentComponent attachment, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull CommandBuffer commandBuffer) { - resource.unregisterBodyAttachment(attachment.getBodyKey(), entityRef); + RigidBodyKey bodyKey = RigidBodyKey.of(attachment.getBodyUuid()); + resource.unregisterBodyAttachment(bodyKey, entityRef); resource.clearBodySyncState(entityRef); if (attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { - removeProxy(commandBuffer, resource, attachment.getBodyKey(), entityRef); + removeProxy(commandBuffer, resource, bodyKey, entityRef); } else if (attachment.shouldRemoveEntityWhenBodyMissing()) { removeEntity(commandBuffer, entityRef); } else { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 6e7d49cd..2a93b463 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -33,9 +33,9 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; @@ -62,7 +62,7 @@ * *

Detached bodies stay physics-authoritative and are not persisted through these visual * proxies. Proxies are ordinary Hytale block entities with a generated - * {@link PhysicsBodyAttachmentComponent}, so removing a proxy never removes the backend body.

+ * {@link BodyAttachmentComponent}, so removing a proxy never removes the backend body.

*/ public class PhysicsDetachedVisualMaterializationSystem extends TickingSystem { @@ -174,15 +174,14 @@ private static void clearCachedMaterializationState(@Nonnull MaterializationStat } private static void removeLegacyGeneratedVisualProxies(@Nonnull Store store) { - ComponentType attachmentType = - PhysicsBodyAttachmentComponent.getComponentType(); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, commandBuffer) -> { - PhysicsBodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); if (attachment == null - || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY - || !attachment.usesLegacyBodyKey()) { + || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { return; } commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); @@ -613,11 +612,11 @@ private static void collectMaterializationCandidates(@Nonnull Store private static void removeOrphanVisualFollowers(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource) { Queue orphanProxies = new ConcurrentLinkedQueue<>(); - ComponentType attachmentType = - PhysicsBodyAttachmentComponent.getComponentType(); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, commandBuffer) -> { - PhysicsBodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); if (attachment == null || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { @@ -625,7 +624,7 @@ private static void removeOrphanVisualFollowers(@Nonnull Store stor } var ref = archetypeChunk.getReferenceTo(index); - orphanProxies.add(new OrphanVisualProxy(attachment.getBodyKey(), + orphanProxies.add(new OrphanVisualProxy(RigidBodyKey.of(attachment.getBodyUuid()), attachment.getSpaceId(), ref)); }); @@ -665,13 +664,13 @@ private static boolean hasGameplayAttachment(@Nonnull Store store, @Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxy, @Nonnull GameplayAttachmentSnapshot gameplayAttachments) { - ComponentType attachmentType = - PhysicsBodyAttachmentComponent.getComponentType(); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); for (Ref attachmentRef : resource.getBodyAttachments(bodyKey)) { if (attachmentRef == proxy || attachmentRef.equals(proxy)) { continue; } - PhysicsBodyAttachmentComponent attachment = store.getComponent(attachmentRef, + BodyAttachmentComponent attachment = store.getComponent(attachmentRef, attachmentType); if (attachment != null && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { return true; @@ -777,11 +776,11 @@ private static boolean isExpectedProxy(@Nonnull Store store, if (!proxy.isValid()) { return false; } - PhysicsBodyAttachmentComponent attachment = - store.getComponent(proxy, PhysicsBodyAttachmentComponent.getComponentType()); + BodyAttachmentComponent attachment = + store.getComponent(proxy, BodyAttachmentComponent.getComponentType()); return attachment != null && attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY - && attachment.getBodyKey().equals(bodyKey) + && attachment.getBodyUuid().equals(bodyKey.value()) && sameSpaceId(attachment.getSpaceId(), spaceId); } @@ -820,8 +819,8 @@ private static Ref spawnProxy(@Nonnull Store store, holder.removeComponent(Velocity.getComponentType()); holder.addComponent(store.getRegistry().getNonSerializedComponentType(), NonSerialized.get()); holder.addComponent(GeneratedVisualProxyComponent.getComponentType(), new GeneratedVisualProxyComponent()); - holder.addComponent(PhysicsBodyAttachmentComponent.getComponentType(), - new PhysicsBodyAttachmentComponent(bodyKey, + holder.addComponent(BodyAttachmentComponent.getComponentType(), + new BodyAttachmentComponent(bodyKey.value(), registration.spaceId(), TransformAuthority.BODY, AttachmentLifecycle.GENERATED_PROXY)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyCollisionComponent.java deleted file mode 100644 index fec3b99f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyCollisionComponent.java +++ /dev/null @@ -1,70 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; - -/** - * Durable ECS collision filtering data for an entity-authored physics body. - */ -public class PhysicsBodyCollisionComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyCollisionComponent.class, - PhysicsBodyCollisionComponent::new) - .append(new KeyedCodec<>("Sensor", Codec.BOOLEAN, false), - (component, value) -> component.sensor = value != null && value, - PhysicsBodyCollisionComponent::isSensor) - .add() - .append(new KeyedCodec<>("CollisionGroup", Codec.INTEGER, false), - (component, value) -> component.collisionGroup = value != null - ? value - : PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsBodyCollisionComponent::getCollisionGroup) - .add() - .append(new KeyedCodec<>("CollisionMask", Codec.INTEGER, false), - (component, value) -> component.collisionMask = value != null - ? value - : PhysicsCollisionFilters.ALL, - PhysicsBodyCollisionComponent::getCollisionMask) - .add() - .build(); - - @Setter - @Getter - private boolean sensor; - @Setter - @Getter - private int collisionGroup = PhysicsCollisionFilters.DYNAMIC_BODY; - @Setter - @Getter - private int collisionMask = PhysicsCollisionFilters.ALL; - - public PhysicsBodyCollisionComponent() { - } - - public PhysicsBodyCollisionComponent(boolean sensor, int collisionGroup, int collisionMask) { - this.sensor = sensor; - this.collisionGroup = collisionGroup; - this.collisionMask = collisionMask; - } - - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyCollisionComponentType(); - } - - @Nonnull - @Override - public PhysicsBodyCollisionComponent clone() { - return new PhysicsBodyCollisionComponent(sensor, collisionGroup, collisionMask); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentValues.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentValues.java deleted file mode 100644 index 85aacacb..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentValues.java +++ /dev/null @@ -1,65 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Stateless conversions from ECS body authoring components to simulation values. - */ -public final class PhysicsBodyComponentValues { - - private PhysicsBodyComponentValues() { - } - - public static boolean hasExplicitSpace(@Nullable PhysicsBodyIdentityComponent component) { - return component != null - && component.getSpaceId() != null - && component.getSpaceId().value() > 0; - } - - @Nonnull - public static PhysicsShapeSpec toShapeSpec(@Nonnull PhysicsBodyShapeComponent component) { - Objects.requireNonNull(component, "component"); - return switch (component.getShapeType()) { - case BOX -> PhysicsShapeSpec.box(component.getHalfExtentX(), - component.getHalfExtentY(), - component.getHalfExtentZ()); - case SPHERE -> PhysicsShapeSpec.sphere(component.getRadius()); - case CAPSULE -> PhysicsShapeSpec.capsule(component.getRadius(), - component.getHalfHeight(), - component.getAxis()); - case CYLINDER -> PhysicsShapeSpec.cylinder(component.getRadius(), - component.getHalfHeight(), - component.getAxis()); - case CONE -> PhysicsShapeSpec.cone(component.getRadius(), - component.getHalfHeight(), - component.getAxis()); - case PLANE -> PhysicsShapeSpec.plane(component.getGroundY()); - case VOXELS, UNKNOWN -> - throw new IllegalArgumentException("Unsupported physics body shape " - + component.getShapeType()); - }; - } - - @Nonnull - public static RigidBodySpawnSettings toSpawnSettings( - @Nonnull PhysicsBodyDynamicsComponent dynamics, - @Nonnull PhysicsBodyMaterialComponent material, - @Nonnull PhysicsBodyCollisionComponent collision) { - Objects.requireNonNull(dynamics, "dynamics"); - Objects.requireNonNull(material, "material"); - Objects.requireNonNull(collision, "collision"); - return RigidBodySpawnSettings.fromOptionalValues( - material.getFriction(), - material.getRestitution(), - dynamics.getLinearDamping(), - dynamics.getAngularDamping(), - collision.getCollisionGroup(), - collision.getCollisionMask(), - collision.isSensor()); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyDynamicsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyDynamicsComponent.java deleted file mode 100644 index 27e1d619..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyDynamicsComponent.java +++ /dev/null @@ -1,89 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import java.util.Objects; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; - -/** - * Durable ECS mass and damping data for an entity-authored physics body. - */ -public class PhysicsBodyDynamicsComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyDynamicsComponent.class, - PhysicsBodyDynamicsComponent::new) - .append(new KeyedCodec<>("BodyType", new EnumCodec<>(PhysicsBodyType.class), false), - (component, value) -> component.bodyType = value != null - ? value - : PhysicsBodyType.DYNAMIC, - PhysicsBodyDynamicsComponent::getBodyType) - .add() - .append(new KeyedCodec<>("Mass", Codec.FLOAT, false), - (component, value) -> component.mass = value != null ? value : 1.0f, - PhysicsBodyDynamicsComponent::getMass) - .add() - .append(new KeyedCodec<>("LinearDamping", Codec.FLOAT, false), - (component, value) -> component.linearDamping = value != null ? value : 0.0f, - PhysicsBodyDynamicsComponent::getLinearDamping) - .add() - .append(new KeyedCodec<>("AngularDamping", Codec.FLOAT, false), - (component, value) -> component.angularDamping = value != null ? value : 0.0f, - PhysicsBodyDynamicsComponent::getAngularDamping) - .add() - .build(); - - @Nonnull - private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; - @Setter - @Getter - private float mass = 1.0f; - @Setter - @Getter - private float linearDamping; - @Setter - @Getter - private float angularDamping; - - public PhysicsBodyDynamicsComponent() { - } - - public PhysicsBodyDynamicsComponent(@Nonnull PhysicsBodyType bodyType, - float mass, - float linearDamping, - float angularDamping) { - this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); - this.mass = mass; - this.linearDamping = linearDamping; - this.angularDamping = angularDamping; - } - - @Nonnull - public PhysicsBodyType getBodyType() { - return bodyType; - } - - public void setBodyType(@Nonnull PhysicsBodyType bodyType) { - this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); - } - - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyDynamicsComponentType(); - } - - @Nonnull - @Override - public PhysicsBodyDynamicsComponent clone() { - return new PhysicsBodyDynamicsComponent(bodyType, mass, linearDamping, angularDamping); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyIdentityComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyIdentityComponent.java deleted file mode 100644 index 7bb395f5..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyIdentityComponent.java +++ /dev/null @@ -1,112 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Durable ECS identity for an entity-authored physics body. - */ -public class PhysicsBodyIdentityComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyIdentityComponent.class, - PhysicsBodyIdentityComponent::new) - .append(new KeyedCodec<>("BodyId", Codec.UUID_BINARY, false), - (component, value) -> component.bodyKey = value != null - ? RigidBodyKey.of(value) - : RigidBodyKey.random(), - PhysicsBodyIdentityComponent::getBodyKeyValue) - .add() - .append(new KeyedCodec<>("SpaceId", Codec.INTEGER, false), - (component, value) -> component.spaceId = value != null && value > 0 - ? new SpaceId(value) - : null, - PhysicsBodyIdentityComponent::getSpaceIdValue) - .add() - .append(new KeyedCodec<>("PersistenceMode", new EnumCodec<>(PhysicsBodyPersistenceMode.class), false), - (component, value) -> component.persistenceMode = value != null - ? value - : PhysicsBodyPersistenceMode.RUNTIME_ONLY, - PhysicsBodyIdentityComponent::getPersistenceMode) - .add() - .build(); - - @Nonnull - private RigidBodyKey bodyKey = RigidBodyKey.random(); - @Nullable - private SpaceId spaceId; - @Nonnull - private PhysicsBodyPersistenceMode persistenceMode = PhysicsBodyPersistenceMode.RUNTIME_ONLY; - - public PhysicsBodyIdentityComponent() { - } - - public PhysicsBodyIdentityComponent(@Nonnull RigidBodyKey bodyKey, - @Nullable SpaceId spaceId, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - this.bodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - this.spaceId = spaceId; - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); - } - - @Nonnull - public RigidBodyKey getBodyKey() { - return bodyKey; - } - - public void setBodyKey(@Nonnull RigidBodyKey bodyKey) { - this.bodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - } - - @Nullable - public SpaceId getSpaceId() { - return spaceId; - } - - public void setSpaceId(@Nullable SpaceId spaceId) { - this.spaceId = spaceId; - } - - @Nonnull - public PhysicsBodyPersistenceMode getPersistenceMode() { - return persistenceMode; - } - - public void setPersistenceMode(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); - } - - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyIdentityComponentType(); - } - - @Nullable - private Integer getSpaceIdValue() { - return spaceId != null ? spaceId.value() : null; - } - - @Nonnull - private UUID getBodyKeyValue() { - return bodyKey.value(); - } - - @Nonnull - @Override - public PhysicsBodyIdentityComponent clone() { - return new PhysicsBodyIdentityComponent(bodyKey, spaceId, persistenceMode); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyKinematicTargetComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyKinematicTargetComponent.java deleted file mode 100644 index ef34bdc1..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyKinematicTargetComponent.java +++ /dev/null @@ -1,130 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; -import java.util.Objects; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Data-only kinematic target for an entity-authored physics body. - */ -public class PhysicsBodyKinematicTargetComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyKinematicTargetComponent.class, - PhysicsBodyKinematicTargetComponent::new) - .append(new KeyedCodec<>("Position", Vector3fUtil.CODEC, false), - (component, value) -> component.position.set(value != null ? value : new Vector3f()), - PhysicsBodyKinematicTargetComponent::getPosition) - .add() - .append(new KeyedCodec<>("Rotation", ImpulseCodecs.QUATERNIONF, false), - (component, value) -> component.rotation.set(value != null ? value : new Quaternionf()), - component -> new Quaternionf(component.rotation)) - .add() - .append(new KeyedCodec<>("LinearVelocity", Vector3fUtil.CODEC, false), - (component, value) -> component.linearVelocity.set(value != null ? value : new Vector3f()), - PhysicsBodyKinematicTargetComponent::getLinearVelocity) - .add() - .append(new KeyedCodec<>("AngularVelocity", Vector3fUtil.CODEC, false), - (component, value) -> component.angularVelocity.set(value != null ? value : new Vector3f()), - PhysicsBodyKinematicTargetComponent::getAngularVelocity) - .add() - .append(new KeyedCodec<>("TransformEnabled", Codec.BOOLEAN, false), - (component, value) -> component.transformEnabled = value == null || value, - PhysicsBodyKinematicTargetComponent::isTransformEnabled) - .add() - .append(new KeyedCodec<>("VelocityEnabled", Codec.BOOLEAN, false), - (component, value) -> component.velocityEnabled = value != null && value, - PhysicsBodyKinematicTargetComponent::isVelocityEnabled) - .add() - .append(new KeyedCodec<>("Activate", Codec.BOOLEAN, false), - (component, value) -> component.activate = value == null || value, - PhysicsBodyKinematicTargetComponent::isActivate) - .add() - .build(); - - @Nonnull - private final Vector3f position = new Vector3f(); - @Nonnull - private final Quaternionf rotation = new Quaternionf(); - @Nonnull - private final Vector3f linearVelocity = new Vector3f(); - @Nonnull - private final Vector3f angularVelocity = new Vector3f(); - @Setter - @Getter - private boolean transformEnabled = true; - @Setter - @Getter - private boolean velocityEnabled; - @Setter - @Getter - private boolean activate = true; - - public PhysicsBodyKinematicTargetComponent() { - } - - public PhysicsBodyKinematicTargetComponent(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean transformEnabled, - boolean velocityEnabled, - boolean activate) { - this.position.set(Objects.requireNonNull(position, "position")); - this.rotation.set(Objects.requireNonNull(rotation, "rotation")); - this.linearVelocity.set(Objects.requireNonNull(linearVelocity, "linearVelocity")); - this.angularVelocity.set(Objects.requireNonNull(angularVelocity, "angularVelocity")); - this.transformEnabled = transformEnabled; - this.velocityEnabled = velocityEnabled; - this.activate = activate; - } - - @Nonnull - public Vector3f getPosition() { - return position; - } - - @Nonnull - public Quaternionf getRotation() { - return rotation; - } - - @Nonnull - public Vector3f getLinearVelocity() { - return linearVelocity; - } - - @Nonnull - public Vector3f getAngularVelocity() { - return angularVelocity; - } - - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyKinematicTargetComponentType(); - } - - @Nonnull - @Override - public PhysicsBodyKinematicTargetComponent clone() { - return new PhysicsBodyKinematicTargetComponent(position, - rotation, - linearVelocity, - angularVelocity, - transformEnabled, - velocityEnabled, - activate); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyLifecycleComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyLifecycleComponent.java deleted file mode 100644 index a40d7dc9..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyLifecycleComponent.java +++ /dev/null @@ -1,127 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Runtime readback state for entity-authored physics body reconciliation. - */ -public class PhysicsBodyLifecycleComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyLifecycleComponent.class, - PhysicsBodyLifecycleComponent::new) - .append(new KeyedCodec<>("State", new EnumCodec<>(State.class), false), - (component, value) -> component.state = value != null ? value : State.PENDING, - PhysicsBodyLifecycleComponent::getState) - .add() - .append(new KeyedCodec<>("BodyId", Codec.UUID_BINARY, false), - (component, value) -> component.bodyKey = value != null ? RigidBodyKey.of(value) : null, - PhysicsBodyLifecycleComponent::getBodyKeyValue) - .add() - .append(new KeyedCodec<>("Message", Codec.STRING, false), - (component, value) -> component.message = value, - PhysicsBodyLifecycleComponent::getMessage) - .add() - .build(); - - @Nonnull - private State state = State.PENDING; - @Nullable - private RigidBodyKey bodyKey; - @Nullable - private String message; - - public PhysicsBodyLifecycleComponent() { - } - - public PhysicsBodyLifecycleComponent(@Nonnull State state, - @Nullable RigidBodyKey bodyKey, - @Nullable String message) { - this.state = Objects.requireNonNull(state, "state"); - this.bodyKey = bodyKey; - this.message = message; - } - - @Nonnull - public static PhysicsBodyLifecycleComponent pending(@Nonnull RigidBodyKey bodyKey) { - return new PhysicsBodyLifecycleComponent(State.PENDING, bodyKey, null); - } - - @Nonnull - public static PhysicsBodyLifecycleComponent created(@Nonnull RigidBodyKey bodyKey) { - return new PhysicsBodyLifecycleComponent(State.CREATED, bodyKey, null); - } - - @Nonnull - public static PhysicsBodyLifecycleComponent destroyed(@Nonnull RigidBodyKey bodyKey) { - return new PhysicsBodyLifecycleComponent(State.DESTROYED, bodyKey, null); - } - - @Nonnull - public static PhysicsBodyLifecycleComponent failed(@Nullable RigidBodyKey bodyKey, - @Nonnull String message) { - return new PhysicsBodyLifecycleComponent(State.FAILED, bodyKey, message); - } - - @Nonnull - public State getState() { - return state; - } - - public void setState(@Nonnull State state) { - this.state = Objects.requireNonNull(state, "state"); - } - - @Nullable - public RigidBodyKey getBodyKey() { - return bodyKey; - } - - public void setBodyKey(@Nullable RigidBodyKey bodyKey) { - this.bodyKey = bodyKey; - } - - @Nullable - public String getMessage() { - return message; - } - - public void setMessage(@Nullable String message) { - this.message = message; - } - - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyLifecycleComponentType(); - } - - @Nullable - private UUID getBodyKeyValue() { - return bodyKey != null ? bodyKey.value() : null; - } - - @Nonnull - @Override - public PhysicsBodyLifecycleComponent clone() { - return new PhysicsBodyLifecycleComponent(state, bodyKey, message); - } - - public enum State { - PENDING, - CREATED, - DESTROYED, - FAILED - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyMaterialComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyMaterialComponent.java deleted file mode 100644 index 2fca5533..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyMaterialComponent.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; - -/** - * Durable ECS material data for an entity-authored physics body. - */ -public class PhysicsBodyMaterialComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyMaterialComponent.class, - PhysicsBodyMaterialComponent::new) - .append(new KeyedCodec<>("Friction", Codec.FLOAT, false), - (component, value) -> component.friction = value != null ? value : 0.5f, - PhysicsBodyMaterialComponent::getFriction) - .add() - .append(new KeyedCodec<>("Restitution", Codec.FLOAT, false), - (component, value) -> component.restitution = value != null ? value : 0.0f, - PhysicsBodyMaterialComponent::getRestitution) - .add() - .build(); - - @Setter - @Getter - private float friction = 0.5f; - @Setter - @Getter - private float restitution; - - public PhysicsBodyMaterialComponent() { - } - - public PhysicsBodyMaterialComponent(float friction, float restitution) { - this.friction = friction; - this.restitution = restitution; - } - - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyMaterialComponentType(); - } - - @Nonnull - @Override - public PhysicsBodyMaterialComponent clone() { - return new PhysicsBodyMaterialComponent(friction, restitution); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyShapeComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyShapeComponent.java deleted file mode 100644 index 65861b4a..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyShapeComponent.java +++ /dev/null @@ -1,177 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import java.util.Objects; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; - -/** - * Durable ECS shape data for an entity-authored physics body. - */ -public class PhysicsBodyShapeComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyShapeComponent.class, - PhysicsBodyShapeComponent::new) - .append(new KeyedCodec<>("ShapeType", new EnumCodec<>(ShapeType.class), false), - (component, value) -> component.shapeType = value != null ? value : ShapeType.BOX, - PhysicsBodyShapeComponent::getShapeType) - .add() - .append(new KeyedCodec<>("HalfExtentX", Codec.FLOAT, false), - (component, value) -> component.halfExtentX = value != null ? value : 0.5f, - PhysicsBodyShapeComponent::getHalfExtentX) - .add() - .append(new KeyedCodec<>("HalfExtentY", Codec.FLOAT, false), - (component, value) -> component.halfExtentY = value != null ? value : 0.5f, - PhysicsBodyShapeComponent::getHalfExtentY) - .add() - .append(new KeyedCodec<>("HalfExtentZ", Codec.FLOAT, false), - (component, value) -> component.halfExtentZ = value != null ? value : 0.5f, - PhysicsBodyShapeComponent::getHalfExtentZ) - .add() - .append(new KeyedCodec<>("Radius", Codec.FLOAT, false), - (component, value) -> component.radius = value != null ? value : 0.5f, - PhysicsBodyShapeComponent::getRadius) - .add() - .append(new KeyedCodec<>("HalfHeight", Codec.FLOAT, false), - (component, value) -> component.halfHeight = value != null ? value : 0.5f, - PhysicsBodyShapeComponent::getHalfHeight) - .add() - .append(new KeyedCodec<>("Axis", new EnumCodec<>(PhysicsAxis.class), false), - (component, value) -> component.axis = value != null ? value : PhysicsAxis.Y, - PhysicsBodyShapeComponent::getAxis) - .add() - .append(new KeyedCodec<>("GroundY", Codec.FLOAT, false), - (component, value) -> component.groundY = value != null ? value : 0.0f, - PhysicsBodyShapeComponent::getGroundY) - .add() - .build(); - - @Nonnull - private ShapeType shapeType = ShapeType.BOX; - @Setter - @Getter - private float halfExtentX = 0.5f; - @Setter - @Getter - private float halfExtentY = 0.5f; - @Setter - @Getter - private float halfExtentZ = 0.5f; - @Setter - @Getter - private float radius = 0.5f; - @Setter - @Getter - private float halfHeight = 0.5f; - @Nonnull - private PhysicsAxis axis = PhysicsAxis.Y; - @Setter - @Getter - private float groundY; - - public PhysicsBodyShapeComponent() { - } - - public PhysicsBodyShapeComponent(@Nonnull ShapeType shapeType, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float groundY) { - this.shapeType = Objects.requireNonNull(shapeType, "shapeType"); - this.halfExtentX = halfExtentX; - this.halfExtentY = halfExtentY; - this.halfExtentZ = halfExtentZ; - this.radius = radius; - this.halfHeight = halfHeight; - this.axis = Objects.requireNonNull(axis, "axis"); - this.groundY = groundY; - } - - @Nonnull - public static PhysicsBodyShapeComponent box(float halfX, float halfY, float halfZ) { - return new PhysicsBodyShapeComponent(ShapeType.BOX, - halfX, - halfY, - halfZ, - 0.5f, - 0.5f, - PhysicsAxis.Y, - 0.0f); - } - - @Nonnull - public static PhysicsBodyShapeComponent sphere(float radius) { - return new PhysicsBodyShapeComponent(ShapeType.SPHERE, - 0.5f, - 0.5f, - 0.5f, - radius, - 0.5f, - PhysicsAxis.Y, - 0.0f); - } - - @Nonnull - public static PhysicsBodyShapeComponent capsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis) { - return new PhysicsBodyShapeComponent(ShapeType.CAPSULE, - 0.5f, - 0.5f, - 0.5f, - radius, - halfHeight, - axis, - 0.0f); - } - - @Nonnull - public ShapeType getShapeType() { - return shapeType; - } - - public void setShapeType(@Nonnull ShapeType shapeType) { - this.shapeType = Objects.requireNonNull(shapeType, "shapeType"); - } - - @Nonnull - public PhysicsAxis getAxis() { - return axis; - } - - public void setAxis(@Nonnull PhysicsAxis axis) { - this.axis = Objects.requireNonNull(axis, "axis"); - } - - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyShapeComponentType(); - } - - @Nonnull - @Override - public PhysicsBodyShapeComponent clone() { - return new PhysicsBodyShapeComponent(shapeType, - halfExtentX, - halfExtentY, - halfExtentZ, - radius, - halfHeight, - axis, - groundY); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java new file mode 100644 index 00000000..df4d1ce4 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java @@ -0,0 +1,137 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Factories for copied PhysicsStore body graph requests. + */ +public final class PhysicsBodySpawnRequests { + + private PhysicsBodySpawnRequests() { + } + + @Nonnull + public static BodyUpsertRequest dynamicBody(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + return body(spaceUuid, + bodyUuid, + bodyCenter, + shape, + PhysicsBodyType.DYNAMIC, + mass, + settings, + linearVelocity, + PhysicsBodyKind.BODY, + persistenceMode); + } + + @Nonnull + public static BodyUpsertRequest body(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + @Nonnull PhysicsBodyType bodyType, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(bodyCenter, "bodyCenter"); + Objects.requireNonNull(shape, "shape"); + Objects.requireNonNull(bodyType, "bodyType"); + Objects.requireNonNull(settings, "settings"); + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(persistenceMode, "persistenceMode"); + + UUID colliderUuid = BodyGraphUuids.collider(bodyUuid); + UUID shapeUuid = BodyGraphUuids.shape(bodyUuid); + UUID materialUuid = BodyGraphUuids.material(bodyUuid); + UUID filterUuid = BodyGraphUuids.filter(bodyUuid); + return BodyUpsertRequest.of(bodyUuid, + new BodyComponent(spaceUuid, + kind, + persistenceMode), + new DynamicsComponent(bodyType, + mass, + settings.hasLinearDamping() ? settings.linearDamping() : 0.0f, + settings.hasAngularDamping() ? settings.angularDamping() : 0.0f, + false), + initialTarget(bodyCenter, linearVelocity), + colliderUuid, + new ColliderComponent(bodyUuid, + shapeUuid, + materialUuid, + filterUuid, + new Vector3f(), + new Quaternionf(), + settings.hasSensor() && settings.sensor()), + shapeUuid, + new ShapeComponent(shape.type(), + shape.halfExtentX(), + shape.halfExtentY(), + shape.halfExtentZ(), + shape.radius(), + shape.halfHeight(), + shape.axis(), + shape.groundY(), + ""), + materialUuid, + new MaterialComponent(settings.hasFriction() ? settings.friction() : 0.5f, + settings.hasRestitution() ? settings.restitution() : 0.0f), + filterUuid, + collisionFilter(settings)); + } + + @Nonnull + private static TargetComponent initialTarget(@Nonnull Vector3f bodyCenter, + @Nullable Vector3f linearVelocity) { + TargetComponent target = new TargetComponent(); + target.setActive(false); + target.setPosition(bodyCenter); + target.setRotation(new Quaternionf()); + target.setLinearVelocity(linearVelocity != null ? linearVelocity : new Vector3f()); + target.setAngularVelocity(new Vector3f()); + target.setTransformEnabled(true); + target.setVelocityEnabled(linearVelocity != null); + target.setActivate(true); + return target; + } + + @Nonnull + private static CollisionFilterComponent collisionFilter(@Nonnull RigidBodySpawnSettings settings) { + return new CollisionFilterComponent( + settings.hasCollisionFilter() + ? settings.collisionGroup() + : PhysicsCollisionFilters.DYNAMIC_BODY, + settings.hasCollisionFilter() + ? settings.collisionMask() + : PhysicsCollisionFilters.ALL); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java similarity index 57% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java index 7c9a63ee..fd32d406 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.components; +package dev.hytalemodding.impulse.core.plugin.physicsstore.projection; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -10,8 +10,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -21,55 +21,43 @@ import org.joml.Vector3f; /** - * Runtime attachment from a Hytale entity to an Impulse body. - * - *

Authoritative PhysicsStore rows use {@code PhysicsBodyUuid}; {@code BodyId} - * and {@code SpaceId} remain compatibility metadata for legacy rows. The entity - * is a gameplay or visual representation. It does not own backend body - * destruction; removing the entity only removes this attachment unless the - * lifecycle marks it as a disposable Impulse visual.

+ * EntityStore projection relationship to an authoritative PhysicsStore body. */ -public class PhysicsBodyAttachmentComponent implements Component { +public class BodyAttachmentComponent implements Component { private static final float USE_BODY_VISUAL_ORIGIN_OFFSET_Y = -1.0f; @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsBodyAttachmentComponent.class, - PhysicsBodyAttachmentComponent::new) - .append(new KeyedCodec<>("BodyId", Codec.UUID_BINARY, false), - (component, value) -> component.bodyKey = value != null - ? RigidBodyKey.of(value) - : RigidBodyKey.random(), - PhysicsBodyAttachmentComponent::getBodyKeyValue) - .add() - .append(new KeyedCodec<>("PhysicsBodyUuid", Codec.UUID_BINARY, false), - (component, value) -> component.physicsBodyUuid = value, - PhysicsBodyAttachmentComponent::getPhysicsBodyUuid) + public static final BuilderCodec CODEC = BuilderCodec.builder( + BodyAttachmentComponent.class, + BodyAttachmentComponent::new) + .append(new KeyedCodec<>("BodyUuid", Codec.UUID_BINARY, false), + (component, value) -> component.bodyUuid = value != null ? value : UUID.randomUUID(), + BodyAttachmentComponent::getBodyUuid) .add() .append(new KeyedCodec<>("SpaceId", Codec.INTEGER, false), (component, value) -> component.spaceId = value != null && value > 0 ? new SpaceId(value) : null, - PhysicsBodyAttachmentComponent::getSpaceIdValue) + BodyAttachmentComponent::getSpaceIdValue) .add() .append(new KeyedCodec<>("TransformAuthority", new EnumCodec<>(TransformAuthority.class), false), (component, value) -> component.transformAuthority = value != null ? value : TransformAuthority.BODY, - PhysicsBodyAttachmentComponent::getTransformAuthority) + BodyAttachmentComponent::getTransformAuthority) .add() .append(new KeyedCodec<>("Lifecycle", new EnumCodec<>(AttachmentLifecycle.class), false), (component, value) -> component.lifecycle = value != null ? value : AttachmentLifecycle.EXTERNAL_ENTITY, - PhysicsBodyAttachmentComponent::getLifecycle) + BodyAttachmentComponent::getLifecycle) .add() .append(new KeyedCodec<>("LocalPositionOffset", Vector3fUtil.CODEC, false), (component, value) -> component.localPositionOffset.set(value != null ? value : new Vector3f()), - PhysicsBodyAttachmentComponent::getLocalPositionOffset) + BodyAttachmentComponent::getLocalPositionOffset) .add() .append(new KeyedCodec<>("LocalRotationOffset", ImpulseCodecs.QUATERNIONF, false), (component, value) -> component.localRotationOffset.set(value != null @@ -79,14 +67,12 @@ public class PhysicsBodyAttachmentComponent implements Component { .add() .append(new KeyedCodec<>("VisualOriginOffsetY", Codec.FLOAT, false), (component, value) -> component.visualOriginOffsetY = normalizeVisualOriginOffsetY(value), - PhysicsBodyAttachmentComponent::getVisualOriginOffsetY) + BodyAttachmentComponent::getVisualOriginOffsetY) .add() .build(); - private RigidBodyKey bodyKey = RigidBodyKey.random(); - - @Nullable - private UUID physicsBodyUuid; + @Nonnull + private UUID bodyUuid = UUID.randomUUID(); @Setter @Getter @@ -109,12 +95,12 @@ public class PhysicsBodyAttachmentComponent implements Component { private float visualOriginOffsetY = USE_BODY_VISUAL_ORIGIN_OFFSET_Y; - public PhysicsBodyAttachmentComponent() { + public BodyAttachmentComponent() { } - public PhysicsBodyAttachmentComponent(@Nonnull RigidBodyKey bodyKey, + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId) { - this(bodyKey, + this(bodyUuid, spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY, @@ -122,20 +108,20 @@ public PhysicsBodyAttachmentComponent(@Nonnull RigidBodyKey bodyKey, new Quaternionf()); } - public PhysicsBodyAttachmentComponent(@Nonnull RigidBodyKey bodyKey, + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @Nonnull AttachmentLifecycle lifecycle) { - this(bodyKey, spaceId, transformAuthority, lifecycle, new Vector3f(), new Quaternionf()); + this(bodyUuid, spaceId, transformAuthority, lifecycle, new Vector3f(), new Quaternionf()); } - public PhysicsBodyAttachmentComponent(@Nonnull RigidBodyKey bodyKey, + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @Nonnull AttachmentLifecycle lifecycle, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset) { - this(bodyKey, + this(bodyUuid, spaceId, transformAuthority, lifecycle, @@ -144,52 +130,39 @@ public PhysicsBodyAttachmentComponent(@Nonnull RigidBodyKey bodyKey, USE_BODY_VISUAL_ORIGIN_OFFSET_Y); } - public PhysicsBodyAttachmentComponent(@Nonnull RigidBodyKey bodyKey, + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @Nonnull AttachmentLifecycle lifecycle, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY) { - this.bodyKey = bodyKey; + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); this.spaceId = spaceId; - this.transformAuthority = transformAuthority; - this.lifecycle = lifecycle; - this.localPositionOffset.set(localPositionOffset); - this.localRotationOffset.set(localRotationOffset); + this.transformAuthority = Objects.requireNonNull(transformAuthority, "transformAuthority"); + this.lifecycle = Objects.requireNonNull(lifecycle, "lifecycle"); + this.localPositionOffset.set(Objects.requireNonNull(localPositionOffset, + "localPositionOffset")); + this.localRotationOffset.set(Objects.requireNonNull(localRotationOffset, + "localRotationOffset")); this.visualOriginOffsetY = normalizeVisualOriginOffsetY(visualOriginOffsetY); } @Nonnull - public static PhysicsBodyAttachmentComponent physicsStoreEntity(@Nonnull UUID physicsBodyUuid) { - PhysicsBodyAttachmentComponent component = externalEntity(RigidBodyKey.of(physicsBodyUuid), null); - component.setPhysicsBodyUuid(physicsBodyUuid); - return component; - } - - /** - * Creates the normal attachment for a plugin-owned gameplay or visual entity. - * - *

The entity follows the body but does not own backend body destruction.

- */ - @Nonnull - public static PhysicsBodyAttachmentComponent externalEntity(@Nonnull RigidBodyKey bodyKey, + public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId) { - return new PhysicsBodyAttachmentComponent(bodyKey, + return new BodyAttachmentComponent(bodyUuid, spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY); } - /** - * Creates the normal external-entity attachment with a local transform offset. - */ @Nonnull - public static PhysicsBodyAttachmentComponent externalEntity(@Nonnull RigidBodyKey bodyKey, + public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset) { - return new PhysicsBodyAttachmentComponent(bodyKey, + return new BodyAttachmentComponent(bodyUuid, spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY, @@ -197,17 +170,13 @@ public static PhysicsBodyAttachmentComponent externalEntity(@Nonnull RigidBodyKe localRotationOffset); } - /** - * Creates an external-entity attachment whose visual transform origin differs from the owning - * body's support/base offset. - */ @Nonnull - public static PhysicsBodyAttachmentComponent externalEntity(@Nonnull RigidBodyKey bodyKey, + public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY) { - return new PhysicsBodyAttachmentComponent(bodyKey, + return new BodyAttachmentComponent(bodyUuid, spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY, @@ -216,19 +185,13 @@ public static PhysicsBodyAttachmentComponent externalEntity(@Nonnull RigidBodyKe visualOriginOffsetY); } - /** - * Creates a disposable Impulse-owned visual attachment. - * - *

Unlike {@link #externalEntity(RigidBodyKey, SpaceId)}, this entity should be removed when - * the attached body is no longer available.

- */ @Nonnull - public static PhysicsBodyAttachmentComponent impulseOwnedVisual(@Nonnull RigidBodyKey bodyKey, + public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY) { - return new PhysicsBodyAttachmentComponent(bodyKey, + return new BodyAttachmentComponent(bodyUuid, spaceId, TransformAuthority.BODY, AttachmentLifecycle.IMPULSE_OWNED_VISUAL, @@ -238,30 +201,27 @@ public static PhysicsBodyAttachmentComponent impulseOwnedVisual(@Nonnull RigidBo } @Nonnull - public RigidBodyKey getBodyKey() { - return bodyKey; - } - - public void setBodyKey(@Nonnull RigidBodyKey bodyKey) { - this.bodyKey = bodyKey; - } - - @Nullable - public UUID getPhysicsBodyUuid() { - return physicsBodyUuid; - } - - public void setPhysicsBodyUuid(@Nullable UUID physicsBodyUuid) { - this.physicsBodyUuid = physicsBodyUuid; + public static BodyAttachmentComponent generatedProxy(@Nonnull UUID bodyUuid, + @Nullable SpaceId spaceId, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY) { + return new BodyAttachmentComponent(bodyUuid, + spaceId, + TransformAuthority.BODY, + AttachmentLifecycle.GENERATED_PROXY, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); } - public boolean usesLegacyBodyKey() { - return physicsBodyUuid == null; + @Nonnull + public UUID getBodyUuid() { + return bodyUuid; } - @Nonnull - public UUID getPhysicsBodyUuidOrLegacy() { - return physicsBodyUuid != null ? physicsBodyUuid : bodyKey.value(); + public void setBodyUuid(@Nonnull UUID bodyUuid) { + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); } @Nonnull @@ -291,13 +251,8 @@ public boolean shouldRemoveEntityWhenBodyMissing() { || lifecycle == AttachmentLifecycle.GENERATED_PROXY; } - public static ComponentType getComponentType() { - return ImpulsePlugin.get().getPhysicsBodyAttachmentComponentType(); - } - - @Nonnull - private UUID getBodyKeyValue() { - return bodyKey.value(); + public static ComponentType getComponentType() { + return ImpulsePlugin.get().getBodyAttachmentComponentType(); } @Nullable @@ -307,16 +262,14 @@ private Integer getSpaceIdValue() { @Nonnull @Override - public PhysicsBodyAttachmentComponent clone() { - PhysicsBodyAttachmentComponent copy = new PhysicsBodyAttachmentComponent(bodyKey, + public BodyAttachmentComponent clone() { + return new BodyAttachmentComponent(bodyUuid, spaceId, transformAuthority, lifecycle, localPositionOffset, localRotationOffset, visualOriginOffsetY); - copy.physicsBodyUuid = physicsBodyUuid; - return copy; } private static float normalizeVisualOriginOffsetY(@Nullable Float value) { @@ -331,11 +284,6 @@ private static float normalizeVisualOriginOffsetY(float value) { } public enum TransformAuthority { - /* - * TODO: Revisit transform ownership when multi-body actor wrappers land. - * A root actor may need to own gameplay transforms while individual - * bodies still publish resolved physics poses. - */ BODY, CONTROLLER, ENTITY_KINEMATIC diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index 2aace07d..f9afeffa 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -441,7 +441,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, strength, verticalLift); Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, - bodyKey, bodyUuid, spaceId, blockType, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 76a501fa..73969082 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -18,29 +18,15 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyGraphUuids; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodySpawnRequests; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -69,20 +55,8 @@ public final class ExamplePhysicsUtils { PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; private static final ComponentType TRANSFORM_TYPE = TransformComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); - private static final ComponentType BODY_IDENTITY_TYPE = - PhysicsBodyIdentityComponent.getComponentType(); - private static final ComponentType BODY_SHAPE_TYPE = - PhysicsBodyShapeComponent.getComponentType(); - private static final ComponentType BODY_DYNAMICS_TYPE = - PhysicsBodyDynamicsComponent.getComponentType(); - private static final ComponentType BODY_MATERIAL_TYPE = - PhysicsBodyMaterialComponent.getComponentType(); - private static final ComponentType BODY_COLLISION_TYPE = - PhysicsBodyCollisionComponent.getComponentType(); - private static final ComponentType - BODY_KINEMATIC_TARGET_TYPE = PhysicsBodyKinematicTargetComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final ComponentType MODEL_TYPE = ModelComponent.getComponentType(); private static final ComponentType HEAD_ROTATION_TYPE = HeadRotation.getComponentType(); @@ -280,14 +254,13 @@ public static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - return bodyUpsertRequest(spaceUuid, + return PhysicsBodySpawnRequests.dynamicBody(spaceUuid, bodyUuid, bodyCenter, shape, mass, settings, linearVelocity, - PhysicsBodyKind.BODY, PhysicsBodyPersistenceMode.PERSISTENT); } @@ -301,43 +274,16 @@ private static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - UUID colliderUuid = BodyGraphUuids.collider(bodyUuid); - UUID shapeUuid = BodyGraphUuids.shape(bodyUuid); - UUID materialUuid = BodyGraphUuids.material(bodyUuid); - UUID filterUuid = BodyGraphUuids.filter(bodyUuid); - return BodyUpsertRequest.of(bodyUuid, - new BodyComponent(spaceUuid, - kind, - persistenceMode), - new DynamicsComponent(PhysicsBodyType.DYNAMIC, - mass, - settings.hasLinearDamping() ? settings.linearDamping() : 0.0f, - settings.hasAngularDamping() ? settings.angularDamping() : 0.0f, - false), - initialTarget(bodyCenter, linearVelocity), - colliderUuid, - new ColliderComponent(bodyUuid, - shapeUuid, - materialUuid, - filterUuid, - new Vector3f(), - new Quaternionf(), - settings.hasSensor() && settings.sensor()), - shapeUuid, - new ShapeComponent(shape.type(), - shape.halfExtentX(), - shape.halfExtentY(), - shape.halfExtentZ(), - shape.radius(), - shape.halfHeight(), - shape.axis(), - shape.groundY(), - ""), - materialUuid, - new MaterialComponent(settings.hasFriction() ? settings.friction() : 0.5f, - settings.hasRestitution() ? settings.restitution() : 0.0f), - filterUuid, - collisionFilter(settings)); + return PhysicsBodySpawnRequests.body(spaceUuid, + bodyUuid, + bodyCenter, + shape, + PhysicsBodyType.DYNAMIC, + mass, + settings, + linearVelocity, + kind, + persistenceMode); } @Nonnull @@ -451,33 +397,6 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, return new DynamicBodyBatchPlan(requests, System.nanoTime() - setupStartNanos); } - @Nonnull - private static TargetComponent initialTarget(@Nonnull Vector3f bodyCenter, - @Nullable Vector3f linearVelocity) { - TargetComponent target = new TargetComponent(); - // BodyBindingSystem reads this once; keeping it inactive avoids pinning dynamic bodies. - target.setActive(false); - target.setPosition(bodyCenter); - target.setRotation(new Quaternionf()); - target.setLinearVelocity(linearVelocity != null ? linearVelocity : new Vector3f()); - target.setAngularVelocity(new Vector3f()); - target.setTransformEnabled(true); - target.setVelocityEnabled(linearVelocity != null); - target.setActivate(true); - return target; - } - - @Nonnull - private static CollisionFilterComponent collisionFilter(@Nonnull RigidBodySpawnSettings settings) { - return new CollisionFilterComponent( - settings.hasCollisionFilter() - ? settings.collisionGroup() - : PhysicsCollisionFilters.DYNAMIC_BODY, - settings.hasCollisionFilter() - ? settings.collisionMask() - : PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - } - @Nonnull public static PendingBlockBody recordBlockBodySpawn(@Nonnull PhysicsCommandRecorder commands, @Nonnull SpaceId spaceId, @@ -645,7 +564,6 @@ public static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store entity = spawnAttachedPhysicsStoreBlockEntity(store, time, - pending.bodyKey(), pending.bodyKey().value(), pending.spaceId(), pending.blockType(), @@ -759,7 +677,6 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store entity = spawnAttachedPhysicsStoreBlockEntity(store, time, - bodyKey, bodyKey.value(), spaceId, blockType, @@ -788,62 +705,6 @@ public static void requireApplied(@Nonnull PhysicsCommandHandle handle, }); } - @Nullable - public static Ref spawnPhysicsBodyBlockEntity(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsBodyType bodyType, - float mass, - @Nullable PhysicsBodyKinematicTargetComponent kinematicTarget) { - Holder holder = physicsBodyBlockEntityHolder(time, - bodyKey, - spaceId, - visualPosition, - blockType, - bodyType, - mass, - 0.5f, - 0.2f, - kinematicTarget); - return store.addEntity(holder, AddReason.SPAWN); - } - - @Nonnull - public static Holder physicsBodyBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsBodyType bodyType, - float mass, - float friction, - float restitution, - @Nullable PhysicsBodyKinematicTargetComponent kinematicTarget) { - Holder holder = blockEntityHolder(time, blockType, visualPosition); - holder.addComponent(BODY_IDENTITY_TYPE, - new PhysicsBodyIdentityComponent(bodyKey, - spaceId, - PhysicsBodyPersistenceMode.PERSISTENT)); - holder.addComponent(BODY_SHAPE_TYPE, - PhysicsBodyShapeComponent.box(0.5f, 0.5f, 0.5f)); - holder.addComponent(BODY_DYNAMICS_TYPE, - new PhysicsBodyDynamicsComponent(bodyType, mass, 0.0f, 0.0f)); - holder.addComponent(BODY_MATERIAL_TYPE, - new PhysicsBodyMaterialComponent(friction, restitution)); - holder.addComponent(BODY_COLLISION_TYPE, - new PhysicsBodyCollisionComponent(false, - PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY)); - addControllableMarkerIfAvailable(holder, bodyType); - if (kinematicTarget != null) { - holder.addComponent(BODY_KINEMATIC_TARGET_TYPE, kinematicTarget); - } - return holder; - } - static void addControllableMarkerIfAvailable(@Nonnull Holder holder, @Nonnull PhysicsBodyType bodyType) { Objects.requireNonNull(holder, "holder"); @@ -854,17 +715,6 @@ static void addControllableMarkerIfAvailable(@Nonnull Holder holder } } - @Nonnull - public static PhysicsBodyKinematicTargetComponent kinematicTargetAt(@Nonnull Vector3d position) { - return new PhysicsBodyKinematicTargetComponent(toVector3f(position), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - true, - false, - true); - } - @Nullable public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, @@ -874,7 +724,7 @@ public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(ATTACHMENT_TYPE, - PhysicsBodyAttachmentComponent.externalEntity(bodyKey, spaceId)); + BodyAttachmentComponent.externalEntity(bodyKey.value(), spaceId)); return store.addEntity(holder, AddReason.SPAWN); } @@ -987,7 +837,7 @@ public static Holder attachedBlockEntityHolder(@Nonnull TimeResourc boolean controllable) { Holder holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(ATTACHMENT_TYPE, - PhysicsBodyAttachmentComponent.impulseOwnedVisual(bodyKey, + BodyAttachmentComponent.impulseOwnedVisual(bodyKey.value(), spaceId, localPositionOffset, localRotationOffset, @@ -1002,14 +852,12 @@ public static Holder attachedBlockEntityHolder(@Nonnull TimeResourc @Nullable private static Ref spawnAttachedPhysicsStoreBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, @Nonnull UUID physicsBodyUuid, @Nonnull SpaceId spaceId, @Nullable String blockType, @Nonnull Vector3d visualPosition, boolean controllable) { Holder holder = attachedPhysicsStoreBlockEntityHolder(time, - bodyKey, physicsBodyUuid, spaceId, blockType, @@ -1023,7 +871,6 @@ private static Ref spawnAttachedPhysicsStoreBlockEntity(@Nonnull St @Nonnull public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, @Nonnull UUID physicsBodyUuid, @Nonnull SpaceId spaceId, @Nullable String blockType, @@ -1033,14 +880,12 @@ public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull float visualOriginOffsetY, boolean controllable) { Holder holder = blockEntityHolder(time, blockType, visualPosition); - PhysicsBodyAttachmentComponent attachment = PhysicsBodyAttachmentComponent.impulseOwnedVisual( - bodyKey, + holder.addComponent(ATTACHMENT_TYPE, + BodyAttachmentComponent.impulseOwnedVisual(physicsBodyUuid, spaceId, localPositionOffset, localRotationOffset, - visualOriginOffsetY); - attachment.setPhysicsBodyUuid(physicsBodyUuid); - holder.addComponent(ATTACHMENT_TYPE, attachment); + visualOriginOffsetY)); if (controllable && PhysicsControlSessions.isAvailable()) { holder.addComponent(ImpulseControllableComponent.getComponentType(), new ImpulseControllableComponent()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 08feae10..d26ed479 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; @@ -62,8 +62,8 @@ public class GrabCommand extends AbstractAsyncPlayerCommand { private static final Vector3f VIEW_OFFSET = new Vector3f(0.85f, -0.35f, 0.0f); private static final ComponentType TRANSFORM_TYPE = TransformComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private final OptionalArg spaceArg = this.withOptionalArg( "space", "Physics space id to target", @@ -321,9 +321,9 @@ private static AttachmentSelection inspectGameplayAttachments(@Nonnull PhysicsWo @Nonnull RigidBodyKey bodyKey) { boolean hasGameplayAttachment = false; for (Ref attachmentRef : resource.getBodyAttachments(bodyKey)) { - PhysicsBodyAttachmentComponent attachment = store.getComponent(attachmentRef, ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = store.getComponent(attachmentRef, ATTACHMENT_TYPE); if (attachment == null - || attachment.getLifecycle() == PhysicsBodyAttachmentComponent.AttachmentLifecycle.GENERATED_PROXY) { + || attachment.getLifecycle() == BodyAttachmentComponent.AttachmentLifecycle.GENERATED_PROXY) { continue; } hasGameplayAttachment = true; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 023f5361..db75a25f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -190,7 +190,6 @@ private static void spawnGroupVisuals(@Nonnull TimeResource time, for (FragmentVisual visual : group.visualBlocks()) { boolean controllable = body.controllable() && !controllableAssigned; Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, - body.bodyKey(), body.bodyKey().value(), body.spaceId(), visual.blockType(), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java index 88023e10..99cf1990 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,8 +20,8 @@ public final class BenchmarkEntityRemovalDiagnosticsSystem extends RefSystem { - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final Query QUERY = ATTACHMENT_TYPE; private static final AtomicInteger PHYSICS_ENTITY_REMOVALS = new AtomicInteger(); @@ -55,7 +55,7 @@ public void onEntityRemove(@Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { - PhysicsBodyAttachmentComponent component = store.getComponent(ref, ATTACHMENT_TYPE); + BodyAttachmentComponent component = store.getComponent(ref, ATTACHMENT_TYPE); if (component == null) { return; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index 9b7fdaa3..dc97bcb4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; @@ -29,8 +29,8 @@ public final class ExplosiveFuseContactSystem ExplosiveBlockComponent.getComponentType(); private static final ComponentType FUSE_TYPE = ExplosiveFuseComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); public ExplosiveFuseContactSystem() { super(PhysicsEventFramePublishedEvent.class); @@ -71,13 +71,13 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer ref : resource.getBodyAttachments(explosiveBodyKey)) { - PhysicsBodyAttachmentComponent attachment = commandBuffer.getComponent(ref, ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = commandBuffer.getComponent(ref, ATTACHMENT_TYPE); ExplosiveBlockComponent explosive = commandBuffer.getComponent(ref, EXPLOSIVE_TYPE); ExplosiveFuseComponent fuse = commandBuffer.getComponent(ref, FUSE_TYPE); if (attachment == null || explosive == null || fuse == null - || !explosiveBodyKey.equals(attachment.getBodyKey())) { + || !explosiveBodyKey.value().equals(attachment.getBodyUuid())) { continue; } ExplosiveFuseComponent updated = fuse.clone(); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 01e340c5..6c22369a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -12,13 +12,10 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -35,8 +32,8 @@ public final class ExplosiveFuseTickSystem extends EntityTickingSystem FUSE_TYPE = ExplosiveFuseComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - PhysicsBodyAttachmentComponent.getComponentType(); + private static final ComponentType ATTACHMENT_TYPE = + BodyAttachmentComponent.getComponentType(); private static final ComponentType TRANSFORM_TYPE = TransformComponent.getComponentType(); private static final Query QUERY = @@ -59,7 +56,7 @@ public void tick(float dt, return; } ExplosiveBlockComponent explosive = chunk.getComponent(index, EXPLOSIVE_TYPE); - PhysicsBodyAttachmentComponent attachment = chunk.getComponent(index, ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = chunk.getComponent(index, ATTACHMENT_TYPE); TransformComponent transform = chunk.getComponent(index, TRANSFORM_TYPE); SpaceId spaceId = attachment != null ? attachment.getSpaceId() : null; if (explosive == null || attachment == null || transform == null || spaceId == null) { @@ -67,8 +64,7 @@ public void tick(float dt, } Ref ref = chunk.getReferenceTo(index); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - BodyMotionSnapshot snapshot = bodySnapshot(store, resource, attachment); + BodyMotionSnapshot snapshot = bodySnapshot(store, attachment); Vector3d currentCenter = explosionCenter(snapshot, transform); if (!fuse.isArmed()) { ExplosiveFuseComponent updated = fuse.clone(); @@ -116,23 +112,11 @@ private static Vector3d explosionCenter(@Nullable BodyMotionSnapshot snapshot, @Nullable private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store, - @Nonnull PhysicsWorldResource resource, - @Nonnull PhysicsBodyAttachmentComponent attachment) { - UUID physicsBodyUuid = attachment.getPhysicsBodyUuid(); - if (physicsBodyUuid != null) { - PhysicsStoreBodySnapshot snapshot = - PhysicsStoreAccess.getBodySnapshot(store.getExternalData().getWorld(), physicsBodyUuid); - return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; - } - RigidBodyKey bodyKey = attachment.getBodyKey(); - if (resource.getBodyRegistrationView(bodyKey) != null) { - try { - return BodyMotionSnapshot.from(resource.getBodySnapshot(bodyKey)); - } catch (IllegalArgumentException ignored) { - // Fall back to the last synced entity transform if the source body was destroyed. - } - } - return null; + @Nonnull BodyAttachmentComponent attachment) { + UUID bodyUuid = attachment.getBodyUuid(); + PhysicsStoreBodySnapshot snapshot = + PhysicsStoreAccess.getBodySnapshot(store.getExternalData().getWorld(), bodyUuid); + return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; } private record BodyMotionSnapshot(float positionX, @@ -147,12 +131,5 @@ private static BodyMotionSnapshot from(@Nonnull PhysicsStoreBodySnapshot snapsho return new BodyMotionSnapshot(position.x, position.y, position.z, velocity.y); } - @Nonnull - private static BodyMotionSnapshot from(@Nonnull PhysicsBodySnapshot snapshot) { - return new BodyMotionSnapshot(snapshot.positionX(), - snapshot.positionY(), - snapshot.positionZ(), - snapshot.linearVelocityY()); - } } } From b2764e9b9b1c9579eb15a156a09e0b9b808c2c15 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 13 Jun 2026 23:38:47 +0200 Subject: [PATCH 047/534] fix(physics): resolve visual proxy component lazily Signed-off-by: Blovien --- .../visual/PhysicsDetachedVisualMaterializationSystem.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 2a93b463..2692acff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -66,9 +66,6 @@ */ public class PhysicsDetachedVisualMaterializationSystem extends TickingSystem { - private static final ComponentType GENERATED_PROXY_TYPE = - GeneratedVisualProxyComponent.getComponentType(); - private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()) ); @@ -186,7 +183,9 @@ private static void removeLegacyGeneratedVisualProxies(@Nonnull Store generatedProxyType = + GeneratedVisualProxyComponent.getComponentType(); + store.forEachEntityParallel(generatedProxyType, (index, archetypeChunk, commandBuffer) -> { if (archetypeChunk.getComponent(index, attachmentType) != null) { return; From ca577d00f43c8a4a7441cadeeaf9c2f725d3dbd8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:16:11 +0200 Subject: [PATCH 048/534] refactor(physicsstore): expose typed world store contract Signed-off-by: Blovien --- .../PhysicsKinematicControlSystem.java | 5 ++-- ...sicsStoreWorldCollisionProducerSystem.java | 4 +-- .../PhysicsStoreEarlyPluginProbe.java | 5 ++++ .../systems/sync/PhysicsSyncSystem.java | 5 ++-- ...csDetachedVisualMaterializationSystem.java | 6 ++--- .../persistence/PhysicsPersistence.java | 10 ++++--- .../early/PhysicsStoreEarlyTransformer.java | 27 +++++++++++++++++-- .../impulse/early/PhysicsStoreWorld.java | 13 +++++++++ impulse-examples/build.gradle.kts | 1 + .../systems/ExplosiveFuseTickSystem.java | 10 ++++--- 10 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreWorld.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 7ffa0f96..a4495143 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -18,6 +18,7 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; @@ -25,7 +26,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -168,7 +168,8 @@ private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( @Nonnull Store store, @Nonnull RigidBodyKey bodyKey, @Nonnull RigidBodyKey anchorBodyKey) { - PhysicsStore physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()); + PhysicsStore physicsStore = + ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); Store physics = physicsStore.getStore(); PhysicsIdentityIndexResource identity = physics.getResource( PhysicsIdentityIndexResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index 803e214e..5f70cbd2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -16,6 +16,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainRequestCache; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainRequestCache.TargetRefreshDecision; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; @@ -28,7 +29,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -81,7 +81,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { long tickStart = snapshot != null ? System.nanoTime() : 0L; try { World world = store.getExternalData().getWorld(); - PhysicsStore physicsStore = PhysicsStoreAccess.require(world); + PhysicsStore physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore(); Store physics = physicsStore.getStore(); PhysicsRequestQueueResource queue = physics.getResource( PhysicsRequestQueueResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java index 782b71c8..a0579ec5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.universe.system.WorldConfigSaveSystem; import com.hypixel.hytale.server.core.universe.world.World; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.lang.reflect.Method; import javax.annotation.Nonnull; @@ -30,6 +31,10 @@ public static void requireAvailable() { + "unexpected World.getPhysicsStore() return type: " + worldAccessor.getReturnType().getName()); } + if (!PhysicsStoreWorld.class.isAssignableFrom(World.class)) { + throw new IllegalStateException("Impulse PhysicsStore early plugin did not make World " + + "implement " + PhysicsStoreWorld.class.getName()); + } requireField(World.class, WORLD_LIFECYCLE_MARKER); requireField(WorldConfigSaveSystem.class, WORLD_RESOURCE_SAVE_MARKER); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 2244cbc3..d5bddf09 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -18,6 +18,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; @@ -30,7 +31,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.List; @@ -158,7 +158,8 @@ public void tick(float dt, @Nonnull private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( @Nonnull Store store) { - PhysicsStore physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()); + PhysicsStore physicsStore = + ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); return physicsStore.getStore().getResource( PhysicsSnapshotResource.getResourceType()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 2692acff..77f6bf9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -22,6 +22,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; @@ -36,7 +37,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -155,9 +155,9 @@ private void tickMaterialization(@Nonnull Store store, private static boolean hasAuthoritativePhysicsStore(@Nonnull Store store) { try { - PhysicsStoreAccess.require(store.getExternalData().getWorld()); + ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); return true; - } catch (IllegalStateException exception) { + } catch (ClassCastException | IllegalStateException exception) { return false; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index ecc9d113..2be83378 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -3,10 +3,11 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; @@ -44,7 +45,8 @@ public static RestoreRequestResult requestRuntimeRestore(@Nonnull Store store) { PhysicsWorldResource runtime = runtime(store); - Store physicsStore = PhysicsStoreAccess.require(store.getExternalData().getWorld()) + Store physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()) + .getPhysicsStore() .getStore(); PersistentPhysicsStoreResource persistent = physicsStore.getResource( PersistentPhysicsStoreResource.getResourceType()); @@ -55,7 +57,9 @@ public static Status status(@Nonnull Store store) { List summaries = spaceSummaries(runtime); int runtimeBodies = summaries.stream().mapToInt(SpaceSummary::bodyCount).sum(); int runtimeJoints = summaries.stream().mapToInt(SpaceSummary::jointCount).sum(); - return new Status(Math.max(PhysicsStoreAccess.spaceCount(store.getExternalData().getWorld()), + int physicsStoreSpaces = physicsStore.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()).size(); + return new Status(Math.max(physicsStoreSpaces, summaries.size()), runtimeBodies, 0, diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java index df76de64..dab36802 100644 --- a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java @@ -62,6 +62,8 @@ public final class PhysicsStoreEarlyTransformer implements ClassTransformer { ClassDesc.of("com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore"); private static final ClassDesc CD_PHYSICS_STORE_HOOKS = ClassDesc.of("dev.hytalemodding.impulse.early.PhysicsStoreHooks"); + private static final ClassDesc CD_PHYSICS_STORE_WORLD = + ClassDesc.of("dev.hytalemodding.impulse.early.PhysicsStoreWorld"); private static final ClassDesc CD_PLUGIN_BASE = ClassDesc.of("com.hypixel.hytale.server.core.plugin.PluginBase"); private static final ClassDesc CD_STORE = ClassDesc.of("com.hypixel.hytale.component.Store"); @@ -149,8 +151,17 @@ private static byte[] transformPluginBase(@Nonnull byte[] bytes) { @Nonnull private static byte[] transformWorld(@Nonnull byte[] bytes) { ClassModel model = CLASS_FILE.parse(bytes); - if (hasField(model.fields(), WORLD_PATCH_MARKER_FIELD, ConstantDescs.CD_boolean)) { - return bytes; + boolean patchMarkerPresent = hasField(model.fields(), + WORLD_PATCH_MARKER_FIELD, + ConstantDescs.CD_boolean); + boolean interfacePresent = hasInterface(model, CD_PHYSICS_STORE_WORLD); + if (patchMarkerPresent) { + if (interfacePresent) { + return bytes; + } + return CLASS_FILE.transformClass(model, + ClassTransform.ACCEPT_ALL.andThen(ClassTransform.endHandler(builder -> + builder.withInterfaceSymbols(CD_PHYSICS_STORE_WORLD)))); } boolean fieldPresent = hasField(model.fields(), WORLD_STORE_FIELD, CD_PHYSICS_STORE); boolean methodPresent = hasMethod(model.methods(), WORLD_STORE_METHOD, MTD_PHYSICS_STORE); @@ -178,6 +189,9 @@ private static byte[] transformWorld(@Nonnull byte[] bytes) { if (!methodPresent) { addWorldStoreAccessor(builder); } + if (!interfacePresent) { + builder.withInterfaceSymbols(CD_PHYSICS_STORE_WORLD); + } builder.withField(WORLD_PATCH_MARKER_FIELD, ConstantDescs.CD_boolean, ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC | ClassFile.ACC_FINAL @@ -560,6 +574,15 @@ private static boolean hasMethod(@Nonnull Iterable methods, return false; } + private static boolean hasInterface(@Nonnull ClassModel model, @Nonnull ClassDesc descriptor) { + for (var classEntry : model.interfaces()) { + if (classEntry.matches(descriptor)) { + return true; + } + } + return false; + } + private static boolean matches(@Nonnull String name, @Nonnull String target) { return target.equals(name) || target.replace('.', '/').equals(name); } diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreWorld.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreWorld.java new file mode 100644 index 00000000..367831dd --- /dev/null +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreWorld.java @@ -0,0 +1,13 @@ +package dev.hytalemodding.impulse.early; + +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import javax.annotation.Nonnull; + +/** + * Compile-time contract implemented by World after the Impulse early plugin transform. + */ +public interface PhysicsStoreWorld { + + @Nonnull + PhysicsStore getPhysicsStore(); +} diff --git a/impulse-examples/build.gradle.kts b/impulse-examples/build.gradle.kts index d5be9f04..0337f2d5 100644 --- a/impulse-examples/build.gradle.kts +++ b/impulse-examples/build.gradle.kts @@ -9,6 +9,7 @@ version = rootProject.version dependencies { implementation(project(":impulse-api")) compileOnly(project(":impulse-core")) + compileOnly(project(":impulse-early-plugin")) testImplementation(project(":impulse-core")) testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 6c22369a..6e2030d1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -12,9 +12,10 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; @@ -114,8 +115,11 @@ private static Vector3d explosionCenter(@Nullable BodyMotionSnapshot snapshot, private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { UUID bodyUuid = attachment.getBodyUuid(); - PhysicsStoreBodySnapshot snapshot = - PhysicsStoreAccess.getBodySnapshot(store.getExternalData().getWorld(), bodyUuid); + PhysicsStoreBodySnapshot snapshot = ((PhysicsStoreWorld) store.getExternalData().getWorld()) + .getPhysicsStore() + .getStore() + .getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(bodyUuid); return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; } From ce028a7075f7c1c13570ddeb741827bba31ecc36 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:24:29 +0200 Subject: [PATCH 049/534] refactor(physicsstore): aggregate body collider components Signed-off-by: Blovien --- .../PhysicsStoreControlSessionRequests.java | 5 +- .../systems/BodyBindingSystem.java | 88 +++-------- .../systems/PersistenceCaptureSystem.java | 119 +++++---------- .../systems/PersistenceHydrationSystem.java | 144 ++++++++++-------- .../systems/RequestDrainSystem.java | 116 +++----------- .../plugin/physicsstore/BodyGraphUuids.java | 49 ------ .../PhysicsBodySpawnRequests.java | 18 +-- .../physicsstore/PhysicsStoreEntities.java | 106 +++++++++++++ .../components/ColliderComponent.java | 79 +--------- .../examples/commands/GrabCommand.java | 19 +-- 10 files changed, 285 insertions(+), 458 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java index 161f4332..19a9ac52 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java @@ -6,7 +6,6 @@ import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyGraphUuids; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; @@ -65,9 +64,7 @@ static List releaseRequests( RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); if (anchorBodyKey != null) { - UUID anchorBodyUuid = anchorBodyKey.value(); - requests.add(BodyRemoveRequest.owned(anchorBodyUuid, - BodyGraphUuids.privateOwnedRows(anchorBodyUuid))); + requests.add(BodyRemoveRequest.of(anchorBodyKey.value())); } return requests; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index f58fc8eb..d907b06a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -2,7 +2,6 @@ import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; @@ -28,8 +27,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -57,42 +54,16 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - Map collidersByBodyUuid = collectColliders(store, systemIndex); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindBodies(store, runtime, identity, restore, collidersByBodyUuid, chunk); + (chunk, _) -> bindBodies(runtime, identity, restore, chunk); store.forEachChunk(systemIndex, collector); } - @Nonnull - private static Map collectColliders(@Nonnull Store store, - int systemIndex) { - Map collidersByBodyUuid = new Object2ObjectOpenHashMap<>(); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> { - for (int index = 0; index < chunk.size(); index++) { - ColliderComponent collider = chunk.getComponent(index, - ColliderComponent.getComponentType()); - if (collider == null) { - continue; - } - UUID colliderUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (!PhysicsStoreSystemSupport.isNil(colliderUuid)) { - collidersByBodyUuid.putIfAbsent(collider.getBodyUuid(), - new ColliderRow(colliderUuid, collider)); - } - } - }; - store.forEachChunk(systemIndex, collector); - return collidersByBodyUuid; - } - - private static void bindBodies(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime, + private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull Map collidersByBodyUuid, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); @@ -104,13 +75,7 @@ private static void bindBodies(@Nonnull Store store, || runtime.getBodyHandle(bodyUuid) != null) { continue; } - ColliderRow collider = collidersByBodyUuid.get(bodyUuid); - if (collider == null) { - restore.recordSoftSkip("Body has no collider: " + bodyUuid); - continue; - } - bindBody(store, - runtime, + bindBody(runtime, identity, restore, chunk.getReferenceTo(index), @@ -118,12 +83,14 @@ private static void bindBodies(@Nonnull Store store, body, chunk.getComponent(index, DynamicsComponent.getComponentType()), chunk.getComponent(index, TargetComponent.getComponentType()), - collider); + chunk.getComponent(index, ColliderComponent.getComponentType()), + chunk.getComponent(index, ShapeComponent.getComponentType()), + chunk.getComponent(index, MaterialComponent.getComponentType()), + chunk.getComponent(index, CollisionFilterComponent.getComponentType())); } } - private static void bindBody(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime, + private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Ref bodyRef, @@ -131,7 +98,10 @@ private static void bindBody(@Nonnull Store store, @Nonnull BodyComponent body, @Nullable DynamicsComponent dynamics, @Nullable TargetComponent target, - @Nonnull ColliderRow colliderRow) { + @Nullable ColliderComponent collider, + @Nullable ShapeComponent shape, + @Nullable MaterialComponent material, + @Nullable CollisionFilterComponent filter) { BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(body.getSpaceUuid()); if (spaceHandle == null) { restore.recordSoftSkip("Body references unbound space: " + bodyUuid); @@ -142,20 +112,8 @@ private static void bindBody(@Nonnull Store store, restore.recordSoftSkip("Body references missing backend runtime: " + bodyUuid); return; } - ShapeComponent shape = componentByUuid(store, - identity, - colliderRow.collider().getShapeUuid(), - ShapeComponent.getComponentType()); - MaterialComponent material = componentByUuid(store, - identity, - colliderRow.collider().getMaterialUuid(), - MaterialComponent.getComponentType()); - CollisionFilterComponent filter = componentByUuid(store, - identity, - colliderRow.collider().getFilterUuid(), - CollisionFilterComponent.getComponentType()); - if (shape == null || material == null || filter == null) { - restore.recordSoftSkip("Body references incomplete collider rows: " + bodyUuid); + if (collider == null || shape == null || material == null || filter == null) { + restore.recordSoftSkip("Body aggregate is missing collider data: " + bodyUuid); return; } DynamicsComponent bodyDynamics = dynamics != null ? dynamics : new DynamicsComponent(); @@ -195,7 +153,7 @@ private static void bindBody(@Nonnull Store store, bodyId, filter.getCollisionGroup(), filter.getCollisionMask()); - backendRuntime.setBodySensor(spaceHandle.value(), bodyId, colliderRow.collider().isSensor()); + backendRuntime.setBodySensor(spaceHandle.value(), bodyId, collider.isSensor()); if (bodyDynamics.isContinuousCollisionEnabled() && backendRuntime.supportsContinuousCollision(spaceHandle.value())) { backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); @@ -254,16 +212,6 @@ private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeReso return backendId != null ? runtime.getRuntime(backendId) : null; } - @Nullable - private static > C componentByUuid(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID uuid, - @Nonnull com.hypixel.hytale.component.ComponentType type) { - return PhysicsStoreSystemSupport.component(store, - PhysicsStoreSystemSupport.refForUuid(identity, uuid), - type); - } - @Nonnull @Override public Query getQuery() { @@ -276,6 +224,4 @@ public Set> getDependencies() { return DEPENDENCIES; } - private record ColliderRow(@Nonnull UUID uuid, @Nonnull ColliderComponent collider) { - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index e7cf8ce6..49508d6d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -109,18 +109,9 @@ private static final class Capture { @Nonnull private final List bodyRows = new ArrayList<>(); @Nonnull - private final List colliderRows = new ArrayList<>(); - @Nonnull - private final List shapeRows = new ArrayList<>(); - @Nonnull - private final List materialRows = new ArrayList<>(); - @Nonnull private final List jointRows = new ArrayList<>(); @Nonnull private final List terrainRows = new ArrayList<>(); - @Nonnull - private final Map filtersByUuid = - new Object2ObjectOpenHashMap<>(); private Capture(@Nonnull Map snapshotsByBodyUuid) { this.snapshotsByBodyUuid = snapshotsByBodyUuid; @@ -156,24 +147,11 @@ private void collectRow(@Nonnull UUID uuid, bodyRows.add(new BodyRow(uuid, body, chunk.getComponent(index, DynamicsComponent.getComponentType()), - chunk.getComponent(index, TargetComponent.getComponentType()))); - } - ColliderComponent collider = chunk.getComponent(index, ColliderComponent.getComponentType()); - if (collider != null) { - colliderRows.add(new ColliderRow(uuid, collider)); - } - ShapeComponent shape = chunk.getComponent(index, ShapeComponent.getComponentType()); - if (shape != null) { - shapeRows.add(new ShapeRow(uuid, shape)); - } - MaterialComponent material = chunk.getComponent(index, MaterialComponent.getComponentType()); - if (material != null) { - materialRows.add(new MaterialRow(uuid, material)); - } - CollisionFilterComponent filter = chunk.getComponent(index, - CollisionFilterComponent.getComponentType()); - if (filter != null) { - filtersByUuid.put(uuid, filter); + chunk.getComponent(index, TargetComponent.getComponentType()), + chunk.getComponent(index, ColliderComponent.getComponentType()), + chunk.getComponent(index, ShapeComponent.getComponentType()), + chunk.getComponent(index, MaterialComponent.getComponentType()), + chunk.getComponent(index, CollisionFilterComponent.getComponentType()))); } JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); if (joint != null) { @@ -188,27 +166,12 @@ private void collectRow(@Nonnull UUID uuid, private void writeTo(@Nonnull PersistentPhysicsStoreResource persistent) { ObjectOpenHashSet bodyUuids = persistentBodyUuids(); - ObjectOpenHashSet shapeUuids = new ObjectOpenHashSet<>(); - ObjectOpenHashSet materialUuids = new ObjectOpenHashSet<>(); - Map> colliderUuidsByBodyUuid = - new Object2ObjectOpenHashMap<>(); - - for (ColliderRow row : colliderRows) { - if (!bodyUuids.contains(row.collider().getBodyUuid())) { - continue; - } - colliderUuidsByBodyUuid - .computeIfAbsent(row.collider().getBodyUuid(), _ -> new ArrayList<>()) - .add(row.uuid()); - shapeUuids.add(row.collider().getShapeUuid()); - materialUuids.add(row.collider().getMaterialUuid()); - } persistent.setSpaces(spaceDtos()); - persistent.setBodies(bodyDtos(colliderUuidsByBodyUuid)); + persistent.setBodies(bodyDtos()); persistent.setColliders(colliderDtos(bodyUuids)); - persistent.setShapes(shapeDtos(shapeUuids)); - persistent.setMaterials(materialDtos(materialUuids)); + persistent.setShapes(shapeDtos(bodyUuids)); + persistent.setMaterials(materialDtos(bodyUuids)); persistent.setJoints(jointDtos(bodyUuids)); persistent.setTerrainColliders(terrainDtos()); } @@ -217,7 +180,8 @@ private void writeTo(@Nonnull PersistentPhysicsStoreResource persistent) { private ObjectOpenHashSet persistentBodyUuids() { ObjectOpenHashSet bodyUuids = new ObjectOpenHashSet<>(); for (BodyRow row : bodyRows) { - if (row.body().getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) { + if (row.body().getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT + && row.hasAggregateCollider()) { bodyUuids.add(row.uuid()); } } @@ -266,19 +230,17 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { } @Nonnull - private PersistentBodyDto[] bodyDtos( - @Nonnull Map> colliderUuidsByBodyUuid) { + private PersistentBodyDto[] bodyDtos() { return bodyRows.stream() .filter(row -> row.body().getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) - .map(row -> bodyDto(row, - colliderUuidsByBodyUuid.getOrDefault(row.uuid(), List.of()))) + .filter(BodyRow::hasAggregateCollider) + .map(this::bodyDto) .sorted(Comparator.comparing(PersistentBodyDto::getBodyUuid)) .toArray(PersistentBodyDto[]::new); } @Nonnull - private PersistentBodyDto bodyDto(@Nonnull BodyRow row, - @Nonnull List colliderUuids) { + private PersistentBodyDto bodyDto(@Nonnull BodyRow row) { DynamicsComponent dynamics = row.dynamics() != null ? row.dynamics() : new DynamicsComponent(); @@ -291,7 +253,7 @@ private PersistentBodyDto bodyDto(@Nonnull BodyRow row, dynamics.getLinearDamping(), dynamics.getAngularDamping(), dynamics.isContinuousCollisionEnabled(), - colliderUuids.stream().sorted().toArray(UUID[]::new), + new UUID[] {row.uuid()}, runtimeState(row.uuid(), row.target())); } @@ -322,23 +284,23 @@ private PersistentBodyRuntimeStateDto runtimeState(@Nonnull UUID bodyUuid, @Nonnull private PersistentColliderDto[] colliderDtos(@Nonnull Set bodyUuids) { - return colliderRows.stream() - .filter(row -> bodyUuids.contains(row.collider().getBodyUuid())) + return bodyRows.stream() + .filter(row -> bodyUuids.contains(row.uuid())) + .filter(BodyRow::hasAggregateCollider) .map(this::colliderDto) .sorted(Comparator.comparing(PersistentColliderDto::getColliderUuid)) .toArray(PersistentColliderDto[]::new); } @Nonnull - private PersistentColliderDto colliderDto(@Nonnull ColliderRow row) { - CollisionFilterComponent filter = filtersByUuid.get(row.collider().getFilterUuid()); - CollisionFilterComponent resolvedFilter = filter != null - ? filter + private PersistentColliderDto colliderDto(@Nonnull BodyRow row) { + CollisionFilterComponent resolvedFilter = row.filter() != null + ? row.filter() : new CollisionFilterComponent(); return new PersistentColliderDto(row.uuid(), - row.collider().getBodyUuid(), - row.collider().getShapeUuid(), - row.collider().getMaterialUuid(), + row.uuid(), + row.uuid(), + row.uuid(), row.collider().getLocalPosition(), row.collider().getLocalRotation(), row.collider().isSensor(), @@ -347,9 +309,10 @@ private PersistentColliderDto colliderDto(@Nonnull ColliderRow row) { } @Nonnull - private PersistentShapeDto[] shapeDtos(@Nonnull Set shapeUuids) { - return shapeRows.stream() - .filter(row -> shapeUuids.contains(row.uuid())) + private PersistentShapeDto[] shapeDtos(@Nonnull Set bodyUuids) { + return bodyRows.stream() + .filter(row -> bodyUuids.contains(row.uuid())) + .filter(BodyRow::hasAggregateCollider) .map(row -> new PersistentShapeDto(row.uuid(), row.shape().getShapeType(), row.shape().getHalfExtentX(), @@ -365,9 +328,10 @@ private PersistentShapeDto[] shapeDtos(@Nonnull Set shapeUuids) { } @Nonnull - private PersistentMaterialDto[] materialDtos(@Nonnull Set materialUuids) { - return materialRows.stream() - .filter(row -> materialUuids.contains(row.uuid())) + private PersistentMaterialDto[] materialDtos(@Nonnull Set bodyUuids) { + return bodyRows.stream() + .filter(row -> bodyUuids.contains(row.uuid())) + .filter(BodyRow::hasAggregateCollider) .map(row -> new PersistentMaterialDto(row.uuid(), row.material().getFriction(), row.material().getRestitution())) @@ -431,16 +395,15 @@ private record SpaceRow(@Nonnull UUID uuid, private record BodyRow(@Nonnull UUID uuid, @Nonnull BodyComponent body, @Nullable DynamicsComponent dynamics, - @Nullable TargetComponent target) { - } - - private record ColliderRow(@Nonnull UUID uuid, @Nonnull ColliderComponent collider) { - } - - private record ShapeRow(@Nonnull UUID uuid, @Nonnull ShapeComponent shape) { - } - - private record MaterialRow(@Nonnull UUID uuid, @Nonnull MaterialComponent material) { + @Nullable TargetComponent target, + @Nullable ColliderComponent collider, + @Nullable ShapeComponent shape, + @Nullable MaterialComponent material, + @Nullable CollisionFilterComponent filter) { + + private boolean hasAggregateCollider() { + return collider != null && shape != null && material != null && filter != null; + } } private record JointRow(@Nonnull UUID uuid, @Nonnull JointComponent joint) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index eecb4994..5db5cce1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentTerrainColliderDto; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; @@ -35,6 +36,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; @@ -70,18 +73,7 @@ private static void hydrateRows(@Nonnull Store store, for (PersistentSpaceDto dto : persistent.getSpaces()) { addSpace(store, dto); } - for (PersistentShapeDto dto : persistent.getShapes()) { - addShape(store, dto); - } - for (PersistentMaterialDto dto : persistent.getMaterials()) { - addMaterial(store, dto); - } - for (PersistentBodyDto dto : persistent.getBodies()) { - addBody(store, dto); - } - for (PersistentColliderDto dto : persistent.getColliders()) { - addCollider(store, dto); - } + addBodies(store, persistent); for (PersistentJointDto dto : persistent.getJoints()) { addJoint(store, dto); } @@ -110,62 +102,92 @@ private static void addSpace(@Nonnull Store store, add(store, holder); } - private static void addShape(@Nonnull Store store, - @Nonnull PersistentShapeDto dto) { - Holder holder = row(store, dto.getShapeUuid()); - holder.addComponent(ShapeComponent.getComponentType(), - new ShapeComponent(dto.getShapeType(), - dto.getHalfExtentX(), - dto.getHalfExtentY(), - dto.getHalfExtentZ(), - dto.getRadius(), - dto.getHalfHeight(), - dto.getAxis(), - dto.getGroundY(), - dto.getResourceKey())); - add(store, holder); - } - - private static void addMaterial(@Nonnull Store store, - @Nonnull PersistentMaterialDto dto) { - Holder holder = row(store, dto.getMaterialUuid()); - holder.addComponent(MaterialComponent.getComponentType(), - new MaterialComponent(dto.getFriction(), dto.getRestitution())); - add(store, holder); + private static void addBodies(@Nonnull Store store, + @Nonnull PersistentPhysicsStoreResource persistent) { + Map collidersByUuid = new Object2ObjectOpenHashMap<>(); + Map collidersByBodyUuid = new Object2ObjectOpenHashMap<>(); + for (PersistentColliderDto collider : persistent.getColliders()) { + collidersByUuid.put(collider.getColliderUuid(), collider); + collidersByBodyUuid.putIfAbsent(collider.getBodyUuid(), collider); + } + Map shapesByUuid = new Object2ObjectOpenHashMap<>(); + for (PersistentShapeDto shape : persistent.getShapes()) { + shapesByUuid.put(shape.getShapeUuid(), shape); + } + Map materialsByUuid = new Object2ObjectOpenHashMap<>(); + for (PersistentMaterialDto material : persistent.getMaterials()) { + materialsByUuid.put(material.getMaterialUuid(), material); + } + for (PersistentBodyDto dto : persistent.getBodies()) { + addBody(store, + dto, + colliderFor(dto, collidersByUuid, collidersByBodyUuid), + shapesByUuid, + materialsByUuid); + } } private static void addBody(@Nonnull Store store, - @Nonnull PersistentBodyDto dto) { + @Nonnull PersistentBodyDto dto, + PersistentColliderDto collider, + @Nonnull Map shapesByUuid, + @Nonnull Map materialsByUuid) { Holder holder = row(store, dto.getBodyUuid()); - holder.addComponent(BodyComponent.getComponentType(), - new BodyComponent(dto.getSpaceUuid(), - dto.getKind(), - dto.getPersistenceMode())); - holder.addComponent(DynamicsComponent.getComponentType(), - new DynamicsComponent(dto.getBodyType(), - dto.getMass(), - dto.getLinearDamping(), - dto.getAngularDamping(), - dto.isContinuousCollisionEnabled())); - holder.addComponent(TargetComponent.getComponentType(), - inactiveTarget(dto.getRuntimeState())); + BodyComponent body = new BodyComponent(dto.getSpaceUuid(), + dto.getKind(), + dto.getPersistenceMode()); + DynamicsComponent dynamics = new DynamicsComponent(dto.getBodyType(), + dto.getMass(), + dto.getLinearDamping(), + dto.getAngularDamping(), + dto.isContinuousCollisionEnabled()); + TargetComponent target = inactiveTarget(dto.getRuntimeState()); + if (collider != null) { + PersistentShapeDto shape = shapesByUuid.get(collider.getShapeUuid()); + PersistentMaterialDto material = materialsByUuid.get(collider.getMaterialUuid()); + if (shape != null && material != null) { + PhysicsStoreEntities.addBodyComponents(holder, + body, + dynamics, + target, + new ColliderComponent(collider.getLocalPosition(), + collider.getLocalRotation(), + collider.isSensor()), + new ShapeComponent(shape.getShapeType(), + shape.getHalfExtentX(), + shape.getHalfExtentY(), + shape.getHalfExtentZ(), + shape.getRadius(), + shape.getHalfHeight(), + shape.getAxis(), + shape.getGroundY(), + shape.getResourceKey()), + new MaterialComponent(material.getFriction(), material.getRestitution()), + new CollisionFilterComponent(collider.getCollisionGroup(), + collider.getCollisionMask())); + } else { + holder.addComponent(BodyComponent.getComponentType(), body); + holder.addComponent(DynamicsComponent.getComponentType(), dynamics); + holder.addComponent(TargetComponent.getComponentType(), target); + } + } else { + holder.addComponent(BodyComponent.getComponentType(), body); + holder.addComponent(DynamicsComponent.getComponentType(), dynamics); + holder.addComponent(TargetComponent.getComponentType(), target); + } add(store, holder); } - private static void addCollider(@Nonnull Store store, - @Nonnull PersistentColliderDto dto) { - Holder holder = row(store, dto.getColliderUuid()); - holder.addComponent(ColliderComponent.getComponentType(), - new ColliderComponent(dto.getBodyUuid(), - dto.getShapeUuid(), - dto.getMaterialUuid(), - dto.getColliderUuid(), - dto.getLocalPosition(), - dto.getLocalRotation(), - dto.isSensor())); - holder.addComponent(CollisionFilterComponent.getComponentType(), - new CollisionFilterComponent(dto.getCollisionGroup(), dto.getCollisionMask())); - add(store, holder); + private static PersistentColliderDto colliderFor(@Nonnull PersistentBodyDto body, + @Nonnull Map collidersByUuid, + @Nonnull Map collidersByBodyUuid) { + for (UUID colliderUuid : body.getColliderUuids()) { + PersistentColliderDto collider = collidersByUuid.get(colliderUuid); + if (collider != null) { + return collider; + } + } + return collidersByBodyUuid.get(body.getBodyUuid()); } private static void addJoint(@Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index 4c3eb99f..023d0a78 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -30,6 +30,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; @@ -515,7 +516,6 @@ private static RequestApplicationStatus applyBodyRemove(@Nonnull Store attachedJoints = collectAttachedJoints(store, systemIndex, bodyUuid); - List colliders = collectColliders(store, systemIndex, bodyUuid); Set removedRows = new ObjectOpenHashSet<>(); for (JointRow joint : attachedJoints) { @@ -529,9 +529,6 @@ private static RequestApplicationStatus applyBodyRemove(@Nonnull Store bodyRef = ensureRow(store, identity, refsThisDrain, request.bodyUuid()); - store.putComponent(bodyRef, BodyComponent.getComponentType(), request.body().clone()); - store.putComponent(bodyRef, DynamicsComponent.getComponentType(), request.dynamics().clone()); - if (request.target() != null) { - store.putComponent(bodyRef, TargetComponent.getComponentType(), request.target().clone()); - } else { - store.removeComponent(bodyRef, TargetComponent.getComponentType()); - } + PhysicsStoreEntities.putBodyComponents(store, + bodyRef, + request.body(), + request.dynamics(), + request.target(), + request.collider(), + request.shape(), + request.material(), + request.filter()); return RequestApplicationStatus.APPLIED; } @@ -878,19 +852,6 @@ private static RequestApplicationStatus applyTerrainRequest(@Nonnull Store> Ref upsertComponentRow( - @Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull UUID uuid, - @Nonnull ComponentType type, - @Nonnull C component) { - Ref ref = ensureRow(store, identity, refsThisDrain, uuid); - store.putComponent(ref, type, component); - return ref; - } - @Nonnull private static Ref ensureRow(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @@ -1026,31 +987,6 @@ private static List collectAttachedJoints(@Nonnull Store return rows; } - @Nonnull - private static List collectColliders(@Nonnull Store store, - int systemIndex, - @Nonnull UUID bodyUuid) { - List rows = new ArrayList<>(); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> { - for (int index = 0; index < chunk.size(); index++) { - ColliderComponent collider = chunk.getComponent(index, - ColliderComponent.getComponentType()); - if (collider == null || !bodyUuid.equals(collider.getBodyUuid())) { - continue; - } - UUID colliderUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (!PhysicsStoreSystemSupport.isNil(colliderUuid)) { - rows.add(new ColliderRow(colliderUuid, - chunk.getReferenceTo(index), - collider.clone())); - } - } - }; - store.forEachChunk(systemIndex, collector); - return rows; - } - @Nonnull private static Set structuralConflicts( @Nonnull List requests, @@ -1178,22 +1114,10 @@ private static PendingBodyOperation.Kind pendingKind(@Nonnull BodyForceRequest r private static boolean isValidBodyUpsert(@Nonnull BodyUpsertRequest request, @Nonnull PhysicsRestoreStatusResource restore) { if (isNil(request.bodyUuid()) - || isNil(request.body().getSpaceUuid()) - || isNil(request.colliderUuid()) - || isNil(request.shapeUuid()) - || isNil(request.materialUuid()) - || isNil(request.filterUuid())) { + || isNil(request.body().getSpaceUuid())) { restore.recordSoftSkip("Body upsert contains nil UUIDs: " + request.bodyUuid()); return false; } - if (!request.bodyUuid().equals(request.collider().getBodyUuid()) - || !request.shapeUuid().equals(request.collider().getShapeUuid()) - || !request.materialUuid().equals(request.collider().getMaterialUuid()) - || !request.filterUuid().equals(request.collider().getFilterUuid())) { - restore.recordSoftSkip("Body upsert collider refs do not match request UUIDs: " - + request.bodyUuid()); - return false; - } return true; } @@ -1294,6 +1218,19 @@ private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrain } } + @Nonnull + private static > Ref upsertComponentRow( + @Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull UUID uuid, + @Nonnull ComponentType type, + @Nonnull C component) { + Ref ref = ensureRow(store, identity, refsThisDrain, uuid); + store.putComponent(ref, type, component); + return ref; + } + @Nonnull @Override public Set> getDependencies() { @@ -1305,11 +1242,6 @@ private record JointRow(@Nonnull UUID uuid, @Nonnull JointComponent joint) { } - private record ColliderRow(@Nonnull UUID uuid, - @Nonnull Ref ref, - @Nonnull ColliderComponent collider) { - } - private record RuntimeBodyBinding(@Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java deleted file mode 100644 index 08c1584d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyGraphUuids.java +++ /dev/null @@ -1,49 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; - -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Deterministic private row UUIDs for one-body PhysicsStore graphs. - */ -public final class BodyGraphUuids { - - private BodyGraphUuids() { - } - - @Nonnull - public static UUID collider(@Nonnull UUID bodyUuid) { - return row(bodyUuid, "collider"); - } - - @Nonnull - public static UUID shape(@Nonnull UUID bodyUuid) { - return row(bodyUuid, "shape"); - } - - @Nonnull - public static UUID material(@Nonnull UUID bodyUuid) { - return row(bodyUuid, "material"); - } - - @Nonnull - public static UUID filter(@Nonnull UUID bodyUuid) { - return row(bodyUuid, "filter"); - } - - @Nonnull - public static List privateOwnedRows(@Nonnull UUID bodyUuid) { - return List.of(shape(bodyUuid), material(bodyUuid), filter(bodyUuid)); - } - - @Nonnull - private static UUID row(@Nonnull UUID bodyUuid, @Nonnull String rowKind) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(rowKind, "rowKind"); - return UUID.nameUUIDFromBytes(("impulse:physics-body:" + bodyUuid + ':' + rowKind) - .getBytes(StandardCharsets.UTF_8)); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java index df4d1ce4..4c1ce96b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java @@ -70,10 +70,6 @@ public static BodyUpsertRequest body(@Nonnull UUID spaceUuid, Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(persistenceMode, "persistenceMode"); - UUID colliderUuid = BodyGraphUuids.collider(bodyUuid); - UUID shapeUuid = BodyGraphUuids.shape(bodyUuid); - UUID materialUuid = BodyGraphUuids.material(bodyUuid); - UUID filterUuid = BodyGraphUuids.filter(bodyUuid); return BodyUpsertRequest.of(bodyUuid, new BodyComponent(spaceUuid, kind, @@ -84,15 +80,11 @@ public static BodyUpsertRequest body(@Nonnull UUID spaceUuid, settings.hasAngularDamping() ? settings.angularDamping() : 0.0f, false), initialTarget(bodyCenter, linearVelocity), - colliderUuid, - new ColliderComponent(bodyUuid, - shapeUuid, - materialUuid, - filterUuid, - new Vector3f(), + bodyUuid, + new ColliderComponent(new Vector3f(), new Quaternionf(), settings.hasSensor() && settings.sensor()), - shapeUuid, + bodyUuid, new ShapeComponent(shape.type(), shape.halfExtentX(), shape.halfExtentY(), @@ -102,10 +94,10 @@ public static BodyUpsertRequest body(@Nonnull UUID spaceUuid, shape.axis(), shape.groundY(), ""), - materialUuid, + bodyUuid, new MaterialComponent(settings.hasFriction() ? settings.friction() : 0.5f, settings.hasRestitution() ? settings.restitution() : 0.0f), - filterUuid, + bodyUuid, collisionFilter(settings)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java new file mode 100644 index 00000000..59007145 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java @@ -0,0 +1,106 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Holder/component helpers for authoritative PhysicsStore aggregate entities. + */ +public final class PhysicsStoreEntities { + + private PhysicsStoreEntities() { + } + + @Nonnull + public static Holder bodyHolder(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull BodyComponent body, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target, + @Nonnull ColliderComponent collider, + @Nonnull ShapeComponent shape, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter) { + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(UuidComponent.getComponentType(), + new UuidComponent(Objects.requireNonNull(bodyUuid, "bodyUuid"))); + addBodyComponents(holder, body, dynamics, target, collider, shape, material, filter); + return holder; + } + + public static void addBodyComponents(@Nonnull Holder holder, + @Nonnull BodyComponent body, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target, + @Nonnull ColliderComponent collider, + @Nonnull ShapeComponent shape, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter) { + Objects.requireNonNull(holder, "holder") + .addComponent(BodyComponent.getComponentType(), + Objects.requireNonNull(body, "body").clone()); + holder.addComponent(DynamicsComponent.getComponentType(), + Objects.requireNonNull(dynamics, "dynamics").clone()); + holder.addComponent(ColliderComponent.getComponentType(), + Objects.requireNonNull(collider, "collider").clone()); + holder.addComponent(ShapeComponent.getComponentType(), + Objects.requireNonNull(shape, "shape").clone()); + holder.addComponent(MaterialComponent.getComponentType(), + Objects.requireNonNull(material, "material").clone()); + holder.addComponent(CollisionFilterComponent.getComponentType(), + Objects.requireNonNull(filter, "filter").clone()); + if (target != null) { + holder.addComponent(TargetComponent.getComponentType(), target.clone()); + } + } + + public static void putBodyComponents(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull BodyComponent body, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target, + @Nonnull ColliderComponent collider, + @Nonnull ShapeComponent shape, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(ref, "ref"); + store.putComponent(ref, + BodyComponent.getComponentType(), + Objects.requireNonNull(body, "body").clone()); + store.putComponent(ref, + DynamicsComponent.getComponentType(), + Objects.requireNonNull(dynamics, "dynamics").clone()); + store.putComponent(ref, + ColliderComponent.getComponentType(), + Objects.requireNonNull(collider, "collider").clone()); + store.putComponent(ref, + ShapeComponent.getComponentType(), + Objects.requireNonNull(shape, "shape").clone()); + store.putComponent(ref, + MaterialComponent.getComponentType(), + Objects.requireNonNull(material, "material").clone()); + store.putComponent(ref, + CollisionFilterComponent.getComponentType(), + Objects.requireNonNull(filter, "filter").clone()); + if (target != null) { + store.putComponent(ref, TargetComponent.getComponentType(), target.clone()); + } else { + store.removeComponent(ref, TargetComponent.getComponentType()); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java index b07ea199..ca54818f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java @@ -10,13 +10,12 @@ import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; -import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Quaternionf; import org.joml.Vector3f; /** - * Collider row that binds a body to shape, material, and filter rows. + * Local collider settings for one body aggregate row. */ public final class ColliderComponent implements Component { @@ -27,22 +26,6 @@ public final class ColliderComponent implements Component { public static final BuilderCodec CODEC = BuilderCodec.builder( ColliderComponent.class, ColliderComponent::new) - .append(new KeyedCodec<>("BodyUuid", Codec.UUID_BINARY, false), - (component, value) -> component.bodyUuid = value, - ColliderComponent::getBodyUuid) - .add() - .append(new KeyedCodec<>("ShapeUuid", Codec.UUID_BINARY, false), - (component, value) -> component.shapeUuid = value, - ColliderComponent::getShapeUuid) - .add() - .append(new KeyedCodec<>("MaterialUuid", Codec.UUID_BINARY, false), - (component, value) -> component.materialUuid = value, - ColliderComponent::getMaterialUuid) - .add() - .append(new KeyedCodec<>("FilterUuid", Codec.UUID_BINARY, false), - (component, value) -> component.filterUuid = value, - ColliderComponent::getFilterUuid) - .add() .append(new KeyedCodec<>("LocalPosition", Vector3fUtil.CODEC, false), (component, value) -> component.localPosition.set(value != null ? value : ZERO), ColliderComponent::getLocalPosition) @@ -57,14 +40,6 @@ public final class ColliderComponent implements Component { .add() .build(); - @Nonnull - private UUID bodyUuid = new UUID(0L, 0L); - @Nonnull - private UUID shapeUuid = new UUID(0L, 0L); - @Nonnull - private UUID materialUuid = new UUID(0L, 0L); - @Nonnull - private UUID filterUuid = new UUID(0L, 0L); @Nonnull private final Vector3f localPosition = new Vector3f(); @Nonnull @@ -74,58 +49,14 @@ public final class ColliderComponent implements Component { public ColliderComponent() { } - public ColliderComponent(@Nonnull UUID bodyUuid, - @Nonnull UUID shapeUuid, - @Nonnull UUID materialUuid, - @Nonnull UUID filterUuid, - @Nonnull Vector3f localPosition, + public ColliderComponent(@Nonnull Vector3f localPosition, @Nonnull Quaternionf localRotation, boolean sensor) { - this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); - this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); - this.filterUuid = Objects.requireNonNull(filterUuid, "filterUuid"); this.localPosition.set(Objects.requireNonNull(localPosition, "localPosition")); this.localRotation.set(Objects.requireNonNull(localRotation, "localRotation")); this.sensor = sensor; } - @Nonnull - public UUID getBodyUuid() { - return bodyUuid; - } - - public void setBodyUuid(@Nonnull UUID bodyUuid) { - this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - } - - @Nonnull - public UUID getShapeUuid() { - return shapeUuid; - } - - public void setShapeUuid(@Nonnull UUID shapeUuid) { - this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); - } - - @Nonnull - public UUID getMaterialUuid() { - return materialUuid; - } - - public void setMaterialUuid(@Nonnull UUID materialUuid) { - this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); - } - - @Nonnull - public UUID getFilterUuid() { - return filterUuid; - } - - public void setFilterUuid(@Nonnull UUID filterUuid) { - this.filterUuid = Objects.requireNonNull(filterUuid, "filterUuid"); - } - @Nonnull public Vector3f getLocalPosition() { return new Vector3f(localPosition); @@ -160,11 +91,7 @@ public static ComponentType getComponentType() @Nonnull @Override public ColliderComponent clone() { - return new ColliderComponent(bodyUuid, - shapeUuid, - materialUuid, - filterUuid, - localPosition, + return new ColliderComponent(localPosition, localRotation, sensor); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index d26ed479..c586e920 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -25,7 +25,6 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyGraphUuids; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -196,10 +195,6 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, private static BodyUpsertRequest anchorBodyUpsertRequest(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f hitPoint) { - UUID colliderUuid = BodyGraphUuids.collider(bodyUuid); - UUID shapeUuid = BodyGraphUuids.shape(bodyUuid); - UUID materialUuid = BodyGraphUuids.material(bodyUuid); - UUID filterUuid = BodyGraphUuids.filter(bodyUuid); return BodyUpsertRequest.of(bodyUuid, new BodyComponent(spaceUuid, PhysicsBodyKind.TEMPORARY, @@ -210,15 +205,11 @@ private static BodyUpsertRequest anchorBodyUpsertRequest(@Nonnull UUID spaceUuid 0.0f, false), initialAnchorTarget(hitPoint), - colliderUuid, - new ColliderComponent(bodyUuid, - shapeUuid, - materialUuid, - filterUuid, - new Vector3f(), + bodyUuid, + new ColliderComponent(new Vector3f(), new Quaternionf(), true), - shapeUuid, + bodyUuid, new ShapeComponent(ShapeType.SPHERE, 0.0f, 0.0f, @@ -228,9 +219,9 @@ private static BodyUpsertRequest anchorBodyUpsertRequest(@Nonnull UUID spaceUuid PhysicsAxis.Y, 0.0f, ""), - materialUuid, + bodyUuid, new MaterialComponent(0.5f, 0.0f), - filterUuid, + bodyUuid, new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, 0)); } From 39a68ef7b1804f53ebc95c85b0fe624d38f23564 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:37:17 +0200 Subject: [PATCH 050/534] feat(physicsstore): queue owner-lane read helpers Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 25 +- .../commands/perf/PerfStatsCommand.java | 7 +- .../settings/SolverSettingsCommand.java | 5 +- .../settings/StepModeSettingCommand.java | 9 +- .../WorldCollisionPerfReportCommand.java | 9 +- .../PhysicsStoreRegistration.java | 8 + .../PhysicsStoreReadQueueResource.java | 101 ++++++ .../CompletedStepPublicationSystem.java | 3 +- .../PhysicsStoreReadRequestSystem.java | 53 ++++ .../PhysicsWorldRuntimeResource.java | 5 + .../visual/DetachedVisualOcclusion.java | 8 +- .../persistence/PhysicsPersistence.java | 19 +- .../PhysicsStoreBackendAccess.java | 115 +++++++ .../physicsstore/PhysicsStoreDiagnostics.java | 224 +++++++++++++ .../physicsstore/PhysicsStoreRaycasts.java | 299 ++++++++++++++++++ .../physicsstore/PhysicsStoreTypes.java | 13 + .../impulse/examples/commands/EcsCommand.java | 10 +- .../examples/commands/GrabCommand.java | 41 ++- .../examples/commands/RaycastCommand.java | 8 +- .../stress/StressBenchmarkCommand.java | 5 +- .../commands/stress/StressRaycastCommand.java | 5 +- 21 files changed, 896 insertions(+), 76 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index cba86155..0ce94f94 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -15,11 +15,11 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -100,7 +100,7 @@ protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - List spaces = spaceSummaries(resource, null).stream() + List spaces = spaceSummaries(world, null).stream() .map(summary -> { PhysicsSpaceSettings settings = resource.getSpaceSettings(summary.spaceId()); return new SpaceListEntry(summary.spaceId(), @@ -171,7 +171,7 @@ protected void execute(@Nonnull CommandContext context, * so they still require an explicit clean/destroy before deleting the space. */ int registeredBodies = countRegisteredBodies(resource, spaceId); - SpaceCounts counts = countSpaceContents(resource, spaceId); + SpaceCounts counts = countSpaceContents(world, spaceId); int backendBodies = counts.bodies(); int joints = counts.joints(); if (registeredBodies > 0 || joints > 0) { @@ -189,9 +189,9 @@ protected void execute(@Nonnull CommandContext context, } @Nonnull - private static SpaceCounts countSpaceContents(@Nonnull PhysicsWorldResource resource, + private static SpaceCounts countSpaceContents(@Nonnull World world, @Nonnull SpaceId spaceId) { - List summaries = spaceSummaries(resource, spaceId); + List summaries = spaceSummaries(world, spaceId); if (summaries.isEmpty()) { return new SpaceCounts(0, 0); } @@ -201,12 +201,19 @@ private static SpaceCounts countSpaceContents(@Nonnull PhysicsWorldResource reso } @Nonnull - private static List spaceSummaries(@Nonnull PhysicsWorldResource resource, + private static List spaceSummaries(@Nonnull World world, @Nullable SpaceId spaceId) { - return resource.query(new SpaceSummaryQuery(spaceId)) - .completion() + if (spaceId == null) { + return PhysicsStoreDiagnostics.spaceSummariesAsync(world) + .toCompletableFuture() + .join(); + } + return PhysicsStoreDiagnostics.spaceSummariesAsync(world) .toCompletableFuture() - .join(); + .join() + .stream() + .filter(summary -> summary.spaceId().equals(spaceId)) + .toList(); } private static int countRegisteredBodies(@Nonnull PhysicsWorldResource resource, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java index b9b96d11..67dbd3c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java @@ -6,9 +6,8 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; import java.util.List; import javax.annotation.Nonnull; @@ -22,9 +21,7 @@ public PerfStatsCommand() { protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - List spaces = resource.query(new SpaceSummaryQuery(null)) - .completion() + List spaces = PhysicsStoreDiagnostics.spaceSummariesAsync(world) .toCompletableFuture() .join(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 3a265c3c..8ec2deee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -10,9 +10,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SolverCapabilityQuery; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; import javax.annotation.Nonnull; @@ -56,8 +56,7 @@ protected void execute(@Nonnull CommandContext ctx, if (spaceId == null) { return; } - SolverCapabilitySummary summary = resource.query(new SolverCapabilityQuery(spaceId)) - .completion() + SolverCapabilitySummary summary = PhysicsStoreDiagnostics.solverCapabilityAsync(world, spaceId) .toCompletableFuture() .join(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java index 0bca6d46..eccf67cb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java @@ -10,11 +10,11 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.UnsupportedCcdSpacesQuery; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -54,7 +54,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } if (stepMode == PhysicsStepMode.CCD) { - List unsupportedSpaces = unsupportedCcdSpaces(resource); + List unsupportedSpaces = unsupportedCcdSpaces(world); if (!unsupportedSpaces.isEmpty()) { ctx.sender().sendMessage(Message.raw("CCD mode is not available for: " + String.join(", ", unsupportedSpaces))); @@ -71,9 +71,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } @Nonnull - private static List unsupportedCcdSpaces(@Nonnull PhysicsWorldResource resource) { - return resource.query(new UnsupportedCcdSpacesQuery()) - .completion() + private static List unsupportedCcdSpaces(@Nonnull World world) { + return PhysicsStoreDiagnostics.unsupportedCcdSpacesAsync(world) .toCompletableFuture() .join() .stream() diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index 35417ded..1445a492 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -13,9 +13,9 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.VisualSnapshot; import dev.hytalemodding.impulse.core.plugin.events.PhysicsCommandBatchEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import java.util.List; @@ -51,7 +51,7 @@ protected void execute(@Nonnull CommandContext ctx, Snapshot worst = profiling.getWorstTickSnapshot(); PhysicsEntityDiagnostics.Snapshot entityDiagnostics = PhysicsEntityDiagnostics.collect(store); PhysicsWorldResource physicsWorld = store.getResource(PhysicsWorldResource.getResourceType()); - RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(physicsWorld); + RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(world); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling: " + ((runtimeProfiling.isEnabled() || profiling.isEnabled()) ? "enabled" : "disabled"))); @@ -563,7 +563,7 @@ private record RuntimeFootprint(int spaces, int runtimeJoints) { @Nonnull - private static RuntimeFootprint collect(@Nonnull PhysicsWorldResource resource) { + private static RuntimeFootprint collect(@Nonnull World world) { int spaces = 0; int backendBodies = 0; int backendJoints = 0; @@ -578,8 +578,7 @@ private static RuntimeFootprint collect(@Nonnull PhysicsWorldResource resource) int runtimeTerrainContactPairs = 0; int runtimeActiveIslands = 0; int runtimeJoints = 0; - List summaries = resource.query(new SpaceSummaryQuery(null)) - .completion() + List summaries = PhysicsStoreDiagnostics.spaceSummariesAsync(world) .toCompletableFuture() .join(); for (SpaceSummary summary : summaries) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 3a338dff..03a3cea9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -14,6 +14,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyBindingSystem; @@ -23,6 +24,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.JointBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceCaptureSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceHydrationSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PhysicsStoreReadRequestSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.RequestDrainSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; @@ -146,6 +148,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setSnapshotResourceType(registry.registerResource( PhysicsSnapshotResource.class, PhysicsSnapshotResource::new)); + PhysicsStoreTypes.setReadQueueResourceType(registry.registerResource( + PhysicsStoreReadQueueResource.class, + PhysicsStoreReadQueueResource::new)); PhysicsStoreTypes.setTerrainPayloadResourceType(registry.registerResource( PhysicsTerrainPayloadResource.class, PhysicsTerrainPayloadResource::new)); @@ -177,6 +182,7 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new TargetBindingSystem()); + registry.registerSystem(new PhysicsStoreReadRequestSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); registry.registerSystem(new PersistenceCaptureSystem()); registry.registerSystem(new StepSubmissionSystem()); @@ -198,6 +204,8 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic () -> store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsSnapshotResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java new file mode 100644 index 00000000..d56b97ef --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -0,0 +1,101 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import javax.annotation.Nonnull; + +/** + * Owner-lane live backend read queue drained by PhysicsStore systems. + */ +public final class PhysicsStoreReadQueueResource implements Resource { + + @Nonnull + private final Queue> reads = new ArrayDeque<>(); + + public PhysicsStoreReadQueueResource() { + } + + @Nonnull + public synchronized CompletionStage enqueue( + @Nonnull Function, R> read) { + CompletableFuture completion = new CompletableFuture<>(); + reads.add(new QueuedRead<>(read, completion)); + return completion.minimalCompletionStage(); + } + + @Nonnull + public synchronized List> drain() { + List> drained = new ArrayList<>(reads.size()); + QueuedRead read; + while ((read = reads.poll()) != null) { + drained.add(read); + } + return drained; + } + + public void clear() { + List> drained; + synchronized (this) { + drained = new ArrayList<>(reads); + reads.clear(); + } + CancellationException cancelled = + new CancellationException("PhysicsStore read queue cleared"); + for (QueuedRead read : drained) { + read.fail(cancelled); + } + } + + public synchronized int size() { + return reads.size(); + } + + @Nonnull + @Override + public PhysicsStoreReadQueueResource clone() { + return new PhysicsStoreReadQueueResource(); + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.readQueueResourceType(); + } + + public static final class QueuedRead { + + @Nonnull + private final Function, R> read; + @Nonnull + private final CompletableFuture completion; + + private QueuedRead(@Nonnull Function, R> read, + @Nonnull CompletableFuture completion) { + this.read = Objects.requireNonNull(read, "read"); + this.completion = Objects.requireNonNull(completion, "completion"); + } + + public void complete(@Nonnull Store store) { + try { + completion.complete(read.apply(store)); + } catch (RuntimeException | Error exception) { + fail(exception); + } + } + + public void fail(@Nonnull Throwable failure) { + completion.completeExceptionally(Objects.requireNonNull(failure, "failure")); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 6302850b..275e8cce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -25,7 +25,8 @@ public final class CompletedStepPublicationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TargetBindingSystem.class) + new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), + new SystemDependency<>(Order.AFTER, PhysicsStoreReadRequestSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java new file mode 100644 index 00000000..7c9942f5 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java @@ -0,0 +1,53 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Resolves queued live backend reads on the PhysicsStore owner lane. + */ +public final class PhysicsStoreReadRequestSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsStoreReadQueueResource queue = store.getResource( + PhysicsStoreReadQueueResource.getResourceType()); + List> reads = queue.drain(); + if (reads.isEmpty()) { + return; + } + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + IllegalStateException failure = new IllegalStateException( + "PhysicsStore restore failed: " + restore.getFailureMessage()); + for (PhysicsStoreReadQueueResource.QueuedRead read : reads) { + read.fail(failure); + } + return; + } + for (PhysicsStoreReadQueueResource.QueuedRead read : reads) { + read.complete(store); + } + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 5422ded0..61e747ae 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -164,6 +164,11 @@ public void detachEntityStore(@Nonnull Store store) { } } + @Nonnull + public World requireAuthoritativeWorldForPhysicsStore(@Nonnull String operation) { + return requireAuthoritativeWorld(operation); + } + public boolean canAccessLiveBackendDirectly() { return ownerGateway.canAccessLiveBackendDirectly(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java index ffb7116a..1173f730 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java @@ -7,9 +7,9 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.List; import java.util.Objects; @@ -114,9 +114,11 @@ private static void submitRaycast(@Nonnull PhysicsWorldRuntimeResource resource, VisualInterest interest = Objects.requireNonNull(probe.interest(), "interest"); Vector3f target = RAYCAST_TARGET.get() .set(snapshot.positionX(), snapshot.positionY(), snapshot.positionZ()); - state.startPendingRaycast(resource.query(new RaycastClosestQuery(space.spaceId(), + state.startPendingRaycast(PhysicsStoreRaycasts.closestAsync( + resource.requireAuthoritativeWorldForPhysicsStore("submit visual occlusion raycast"), + space.spaceId(), interest.position(), - target)).completion()); + target)); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 2be83378..8265cd83 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -8,9 +8,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; import java.util.List; import javax.annotation.Nonnull; @@ -44,7 +43,6 @@ public static RestoreRequestResult requestRuntimeRestore(@Nonnull Store store) { - PhysicsWorldResource runtime = runtime(store); Store physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()) .getPhysicsStore() .getStore(); @@ -54,7 +52,7 @@ public static Status status(@Nonnull Store store) { PersistentPhysicsWorldResource.getResourceType()); PhysicsRestoreStatusResource restore = physicsStore.getResource( PhysicsRestoreStatusResource.getResourceType()); - List summaries = spaceSummaries(runtime); + List summaries = PhysicsStoreDiagnostics.spaceSummaries(physicsStore); int runtimeBodies = summaries.stream().mapToInt(SpaceSummary::bodyCount).sum(); int runtimeJoints = summaries.stream().mapToInt(SpaceSummary::jointCount).sum(); int physicsStoreSpaces = physicsStore.getResource( @@ -72,14 +70,6 @@ public static Status status(@Nonnull Store store) { restoreMessage(restore, persistent, legacy)); } - @Nonnull - private static List spaceSummaries(@Nonnull PhysicsWorldResource runtime) { - return runtime.query(new SpaceSummaryQuery(null)) - .completion() - .toCompletableFuture() - .join(); - } - @Nonnull private static RestoreState restoreState(@Nonnull PhysicsRestoreStatusResource restore) { if (restore.isFailed()) { @@ -125,11 +115,6 @@ private static boolean hasLegacyData(@Nonnull PersistentPhysicsWorldResource leg return legacy.getSpaceCount() > 0 || legacy.getBodyCount() > 0 || legacy.getJointCount() > 0; } - @Nonnull - private static PhysicsWorldResource runtime(@Nonnull Store store) { - return store.getResource(PhysicsWorldResource.getResourceType()); - } - public enum RestoreState { IDLE("idle"), PENDING_SPACES("pending-spaces"), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java new file mode 100644 index 00000000..830bed1a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java @@ -0,0 +1,115 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class PhysicsStoreBackendAccess { + + private PhysicsStoreBackendAccess() { + } + + @Nullable + static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId spaceId) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + UUID spaceUuid = compatibility.getSpaceUuid(spaceId); + return spaceUuid != null ? space(runtime, spaceUuid) : null; + } + + @Nullable + static SpaceContext space(@Nonnull Store store, @Nonnull UUID spaceUuid) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + return space(runtime, spaceUuid); + } + + @Nullable + static SpaceContext space(@Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID spaceUuid) { + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); + BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + PhysicsBackendRuntime backendRuntime = + backendId != null ? runtime.getRuntime(backendId) : null; + if (spaceHandle == null || backendId == null || backendRuntime == null) { + return null; + } + return new SpaceContext(spaceUuid, backendId, spaceHandle, backendRuntime); + } + + @Nonnull + static SpaceContext requireSpace(@Nonnull Store store, @Nonnull SpaceId spaceId) { + SpaceContext space = space(store, spaceId); + if (space == null) { + throw new IllegalArgumentException("Physics space id=" + spaceId + " is not registered"); + } + return space; + } + + @Nonnull + static SpaceContext requireSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { + SpaceContext space = space(store, spaceUuid); + if (space == null) { + throw new IllegalArgumentException("Physics space uuid=" + spaceUuid + + " is not registered"); + } + return space; + } + + @Nonnull + static SpaceSummary summary(@Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull SpaceContext space) { + SpaceId spaceId = compatibility.getSpaceId(space.spaceUuid()); + if (spaceId == null) { + throw new IllegalStateException("PhysicsStore space has no compatibility SpaceId: " + + space.spaceUuid()); + } + return new SpaceSummary(spaceId, + space.backendId(), + space.backendRuntime().bodyCount(space.spaceHandle().value()), + space.backendRuntime().jointCount(space.spaceHandle().value())); + } + + @Nonnull + static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, + long bodyId, + float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float fraction, + float distance) { + BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyId); + return new RaycastHitView(metadata != null ? metadata.bodyKey() : null, + metadata != null ? metadata.bodyType() : PhysicsBodyType.STATIC, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + metadata != null ? metadata.shapeType() : ShapeType.UNKNOWN, + fraction, + distance); + } + + record SpaceContext(@Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java new file mode 100644 index 00000000..6b47827d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -0,0 +1,224 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; +import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; + +/** + * Synchronous owner-lane diagnostics for live PhysicsStore backend state. + */ +public final class PhysicsStoreDiagnostics { + + private PhysicsStoreDiagnostics() { + } + + public static int bodyCount(@Nonnull Store store, @Nonnull SpaceId spaceId) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; + } + + public static int bodyCount(@Nonnull Store store, @Nonnull UUID spaceUuid) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; + } + + @Nonnull + public static CompletionStage bodyCountAsync(@Nonnull World world, + @Nonnull SpaceId spaceId) { + return bodyCountAsync(store(world), spaceId); + } + + @Nonnull + public static CompletionStage bodyCountAsync(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); + return queue(store).enqueue(physics -> bodyCount(physics, spaceId)); + } + + public static int runtimeJointCount(@Nonnull Store store) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + int[] count = {0}; + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + count[0] += backendRuntime.jointCount(spaceHandle.value())); + return count[0]; + } + + @Nonnull + public static CompletionStage runtimeJointCountAsync(@Nonnull World world) { + return runtimeJointCountAsync(store(world)); + } + + @Nonnull + public static CompletionStage runtimeJointCountAsync(@Nonnull Store store) { + return queue(store).enqueue(PhysicsStoreDiagnostics::runtimeJointCount); + } + + public static boolean ccdSupported(@Nonnull Store store) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + boolean[] supported = {false}; + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + if (backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + supported[0] = true; + } + }); + return supported[0]; + } + + @Nonnull + public static CompletionStage ccdSupportedAsync(@Nonnull World world) { + return ccdSupportedAsync(store(world)); + } + + @Nonnull + public static CompletionStage ccdSupportedAsync(@Nonnull Store store) { + return queue(store).enqueue(PhysicsStoreDiagnostics::ccdSupported); + } + + @Nonnull + public static SolverCapabilitySummary solverCapability(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.requireSpace(store, Objects.requireNonNull(spaceId, "spaceId")); + return solverCapability(spaceId, space); + } + + @Nonnull + public static CompletionStage solverCapabilityAsync( + @Nonnull World world, + @Nonnull SpaceId spaceId) { + return solverCapabilityAsync(store(world), spaceId); + } + + @Nonnull + public static CompletionStage solverCapabilityAsync( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); + return queue(store).enqueue(physics -> solverCapability(physics, spaceId)); + } + + @Nonnull + public static SolverCapabilitySummary solverCapability(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + SpaceId spaceId = compatibility.getSpaceId(Objects.requireNonNull(spaceUuid, "spaceUuid")); + if (spaceId == null) { + throw new IllegalArgumentException("Physics space uuid=" + spaceUuid + + " has no compatibility SpaceId"); + } + return solverCapability(spaceId, PhysicsStoreBackendAccess.requireSpace(store, spaceUuid)); + } + + @Nonnull + public static List spaceSummaries(@Nonnull Store store) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + List summaries = new ArrayList<>(); + runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { + SpaceId spaceId = compatibility.getSpaceId(spaceUuid); + if (spaceId != null) { + summaries.add(new SpaceSummary(spaceId, + backendId, + backendRuntime.bodyCount(spaceHandle.value()), + backendRuntime.jointCount(spaceHandle.value()))); + } + }); + return summaries.isEmpty() ? List.of() : List.copyOf(summaries); + } + + @Nonnull + public static CompletionStage> spaceSummariesAsync(@Nonnull World world) { + return spaceSummariesAsync(store(world)); + } + + @Nonnull + public static CompletionStage> spaceSummariesAsync( + @Nonnull Store store) { + return queue(store).enqueue(PhysicsStoreDiagnostics::spaceSummaries); + } + + @Nonnull + public static List spaceSummaries(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + UUID spaceUuid = compatibility.getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + if (spaceUuid == null) { + return List.of(); + } + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(runtime, spaceUuid); + return space != null + ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) + : List.of(); + } + + @Nonnull + public static List unsupportedCcdSpaces(@Nonnull Store store) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + List spaces = new ArrayList<>(); + runtime.forEachSpaceBinding((spaceUuid, _, spaceHandle, backendRuntime) -> { + if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + PhysicsStoreBackendAccess.SpaceContext context = + PhysicsStoreBackendAccess.space(runtime, spaceUuid); + if (context != null) { + spaces.add(PhysicsStoreBackendAccess.summary(compatibility, context)); + } + } + }); + return spaces.isEmpty() ? List.of() : List.copyOf(spaces); + } + + @Nonnull + public static CompletionStage> unsupportedCcdSpacesAsync( + @Nonnull World world) { + return unsupportedCcdSpacesAsync(store(world)); + } + + @Nonnull + public static CompletionStage> unsupportedCcdSpacesAsync( + @Nonnull Store store) { + return queue(store).enqueue(PhysicsStoreDiagnostics::unsupportedCcdSpaces); + } + + @Nonnull + private static SolverCapabilitySummary solverCapability(@Nonnull SpaceId spaceId, + @Nonnull PhysicsStoreBackendAccess.SpaceContext space) { + return new SolverCapabilitySummary(spaceId, + space.backendId().value(), + space.backendRuntime().supportsSolverTuning(space.spaceHandle().value()), + space.backendRuntime().supportsActivationTuning(space.spaceHandle().value())); + } + + @Nonnull + private static Store store(@Nonnull World world) { + return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() + .getStore(); + } + + @Nonnull + private static PhysicsStoreReadQueueResource queue(@Nonnull Store store) { + return Objects.requireNonNull(store, "store") + .getResource(PhysicsStoreReadQueueResource.getResourceType()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java new file mode 100644 index 00000000..38747c2a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java @@ -0,0 +1,299 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; +import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; +import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * PhysicsStore live backend raycasts. + */ +public final class PhysicsStoreRaycasts { + + private PhysicsStoreRaycasts() { + } + + @Nonnull + public static Optional closest(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + return space != null ? closest(store, space, from, to) : Optional.empty(); + } + + @Nonnull + public static Optional closest(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + return space != null ? closest(store, space, from, to) : Optional.empty(); + } + + @Nonnull + public static List all(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + return space != null ? all(store, space, from, to) : List.of(); + } + + @Nonnull + public static List all(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + return space != null ? all(store, space, from, to) : List.of(); + } + + @Nonnull + public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull List rays) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + return closestBatch(store, space, rays); + } + + @Nonnull + public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull List rays) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + return closestBatch(store, space, rays); + } + + @Nonnull + public static CompletionStage> closestAsync(@Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + return closestAsync(store(world), spaceId, from, to); + } + + @Nonnull + public static CompletionStage> closestAsync( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + return queue(store).enqueue(physics -> + closest(physics, spaceId, copiedFrom, copiedTo)); + } + + @Nonnull + public static CompletionStage> allAsync(@Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + return allAsync(store(world), spaceId, from, to); + } + + @Nonnull + public static CompletionStage> allAsync(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + return queue(store).enqueue(physics -> all(physics, spaceId, copiedFrom, copiedTo)); + } + + @Nonnull + public static CompletionStage closestBatchAsync( + @Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull List rays) { + return closestBatchAsync(store(world), spaceId, rays); + } + + @Nonnull + public static CompletionStage closestBatchAsync( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull List rays) { + List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); + return queue(store).enqueue(physics -> closestBatch(physics, spaceId, copied)); + } + + @Nonnull + private static Optional closest(@Nonnull Store store, + @Nonnull PhysicsStoreBackendAccess.SpaceContext space, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + RayHitCapture hit = new RayHitCapture(runtime); + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + boolean hitFound = space.backendRuntime().raycastClosest(space.spaceHandle().value(), + copiedFrom.x, + copiedFrom.y, + copiedFrom.z, + copiedTo.x, + copiedTo.y, + copiedTo.z, + hit); + return hitFound && hit.captured ? Optional.of(hit.view()) : Optional.empty(); + } + + @Nonnull + private static List all(@Nonnull Store store, + @Nonnull PhysicsStoreBackendAccess.SpaceContext space, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + List hits = new ArrayList<>(); + space.backendRuntime().raycastAll(space.spaceHandle().value(), + copiedFrom.x, + copiedFrom.y, + copiedFrom.z, + copiedTo.x, + copiedTo.y, + copiedTo.z, + (bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance) -> hits.add(PhysicsStoreBackendAccess.toView(runtime, + bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance))); + return hits.isEmpty() ? List.of() : List.copyOf(hits); + } + + @Nonnull + private static RaycastClosestBatchResult closestBatch(@Nonnull Store store, + PhysicsStoreBackendAccess.SpaceContext space, + @Nonnull List rays) { + List copiedRays = List.copyOf(Objects.requireNonNull(rays, "rays")); + int rayCount = copiedRays.size(); + RaycastHitView[] hits = new RaycastHitView[rayCount]; + if (space == null) { + return new RaycastClosestBatchResult(hits); + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + Vector3f from = new Vector3f(); + Vector3f to = new Vector3f(); + for (int index = 0; index < rayCount; index++) { + RaycastSegment ray = copiedRays.get(index); + ray.copyFrom(from); + ray.copyTo(to); + int rayIndex = index; + space.backendRuntime().raycastClosest(space.spaceHandle().value(), + from.x, + from.y, + from.z, + to.x, + to.y, + to.z, + (bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance) -> hits[rayIndex] = PhysicsStoreBackendAccess.toView(runtime, + bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance)); + } + return new RaycastClosestBatchResult(hits); + } + + @Nonnull + private static Store store(@Nonnull World world) { + return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() + .getStore(); + } + + @Nonnull + private static PhysicsStoreReadQueueResource queue(@Nonnull Store store) { + return Objects.requireNonNull(store, "store") + .getResource(PhysicsStoreReadQueueResource.getResourceType()); + } + + private static final class RayHitCapture implements BackendRayHitSink { + + @Nonnull + private final PhysicsRuntimeResource runtime; + private boolean captured; + private RaycastHitView view; + + private RayHitCapture(@Nonnull PhysicsRuntimeResource runtime) { + this.runtime = runtime; + } + + @Override + public void accept(long bodyId, + float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float fraction, + float distance) { + view = PhysicsStoreBackendAccess.toView(runtime, + bodyId, + pointX, + pointY, + pointZ, + normalX, + normalY, + normalZ, + fraction, + distance); + captured = true; + } + + @Nonnull + private RaycastHitView view() { + return Objects.requireNonNull(view, "view"); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index 242249db..b94b9c7a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -11,6 +11,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; @@ -88,6 +89,8 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType snapshotResourceType; @Nullable + private static ResourceType readQueueResourceType; + @Nullable private static ResourceType terrainPayloadResourceType; @Nullable private static ResourceType worldCollisionIndexResourceType; @@ -213,6 +216,11 @@ public static void setSnapshotResourceType( snapshotResourceType = Objects.requireNonNull(type, "type"); } + public static void setReadQueueResourceType( + @Nonnull ResourceType type) { + readQueueResourceType = Objects.requireNonNull(type, "type"); + } + public static void setTerrainPayloadResourceType( @Nonnull ResourceType type) { terrainPayloadResourceType = Objects.requireNonNull(type, "type"); @@ -359,6 +367,11 @@ public static ResourceType snapshotResour return require(snapshotResourceType, "PhysicsSnapshotResource"); } + @Nonnull + public static ResourceType readQueueResourceType() { + return require(readQueueResourceType, "PhysicsStoreReadQueueResource"); + } + @Nonnull public static ResourceType terrainPayloadResourceType() { return require(terrainPayloadResourceType, "PhysicsTerrainPayloadResource"); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index f9afeffa..f06fd90b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -22,6 +22,7 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; @@ -31,7 +32,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockPolicy; @@ -530,9 +530,11 @@ private static RaycastHitView raycast(@Nonnull CommandContext ctx, Vector3d start = ExamplePhysicsUtils.eyePosition(store, ref, transform); Vector3d end = new Vector3d(start) .add(ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(RAY_LENGTH)); - Optional hit = ExamplePhysicsUtils.resource(store) - .query(new RaycastClosestQuery(spaceId, vector(start), vector(end))) - .completion() + Optional hit = PhysicsStoreRaycasts.closestAsync( + store.getExternalData().getWorld(), + spaceId, + vector(start), + vector(end)) .toCompletableFuture() .join(); return hit.orElse(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index c586e920..b5c5a53a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -12,11 +12,13 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -26,6 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; @@ -38,11 +41,11 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastAllQuery; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; import java.util.ArrayList; import java.util.List; @@ -103,7 +106,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d direction = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(RAY_LENGTH); Vector3d end = new Vector3d(start).add(direction); - HitSelection selection = findControllableHit(resource, + HitSelection selection = findControllableHit(world, + resource, store, targetSpaceId, controllableType, @@ -123,7 +127,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } GrabPhysicsState physicsState = createGrabControl(world, - resource, selectedSpaceId, selection); if (physicsState == null) { @@ -150,14 +153,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nullable private static GrabPhysicsState createGrabControl(@Nonnull World world, - @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId selectedSpaceId, @Nonnull HitSelection selection) { - RigidBodyStateView selectedState = resource.query(new RigidBodyStateQuery(selection.bodyKey())) - .completion() - .toCompletableFuture() - .join() - .orElse(null); + RigidBodyStateView selectedState = bodyState(world, selection.bodyKey()); if (selectedState == null) { return null; } @@ -257,16 +255,17 @@ private static JointComponent controlJoint(@Nonnull UUID spaceUuid, } @Nullable - private static HitSelection findControllableHit(@Nonnull PhysicsWorldResource resource, + private static HitSelection findControllableHit(@Nonnull World world, + @Nonnull PhysicsWorldResource resource, @Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull ComponentType controllableType, @Nonnull Vector3d start, @Nonnull Vector3d end) { - List hits = resource.query(new RaycastAllQuery(spaceId, + List hits = PhysicsStoreRaycasts.allAsync(world, + spaceId, ExamplePhysicsUtils.toVector3f(start), - ExamplePhysicsUtils.toVector3f(end))) - .completion() + ExamplePhysicsUtils.toVector3f(end)) .toCompletableFuture() .join(); List candidates = new ArrayList<>(hits.size()); @@ -305,6 +304,20 @@ private static HitSelection findControllableHit(@Nonnull PhysicsWorldResource re return best; } + @Nullable + private static RigidBodyStateView bodyState(@Nonnull World world, + @Nonnull RigidBodyKey bodyKey) { + PhysicsStoreBodySnapshot body = ((PhysicsStoreWorld) world).getPhysicsStore() + .getStore() + .getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(bodyKey.value()); + return body != null + ? new RigidBodyStateView(bodyKey, + body.bodyType(), + RigidBodyPose.of(body.position(), body.rotation())) + : null; + } + @Nonnull private static AttachmentSelection inspectGameplayAttachments(@Nonnull PhysicsWorldResource resource, @Nonnull Store store, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index ca2af605..c98c49e3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -14,8 +14,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -60,10 +60,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, DebugUtils.addArrow(world, start, direction, DebugUtils.COLOR_WHITE, 0.8f, 4.0f, DebugUtils.FLAG_FADE); - RaycastResult hit = resource.query(new RaycastClosestQuery(spaceId, + RaycastResult hit = PhysicsStoreRaycasts.closestAsync(world, + spaceId, ExamplePhysicsUtils.toVector3f(start), - ExamplePhysicsUtils.toVector3f(end))) - .completion() + ExamplePhysicsUtils.toVector3f(end)) .toCompletableFuture() .join() .map(RaycastCommand::toResult) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 8f5f8d0c..c4ac36b6 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -14,10 +14,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -79,8 +79,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } BenchmarkLayout layout = BenchmarkLayout.around(playerPos, request.count()); - int beforeBodies = resource.query(new SpaceBodyCountQuery(spaceId)) - .completion() + int beforeBodies = PhysicsStoreDiagnostics.bodyCountAsync(world, spaceId) .toCompletableFuture() .join(); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index f836c3c3..989fdfe7 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -11,8 +11,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; import java.util.ArrayList; @@ -65,8 +65,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, List segments = getRaycastSegments(side, rays, playerPos); long startNanos = System.nanoTime(); - long hits = resource.query(new RaycastClosestBatchQuery(spaceId, segments)) - .completion() + long hits = PhysicsStoreRaycasts.closestBatchAsync(world, spaceId, segments) .toCompletableFuture() .join() .hitCount(); From be17d8445a0a922647a789519dd3a30d6a13a402 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:47:02 +0200 Subject: [PATCH 051/534] refactor(physicsstore): drain body commands from ecs rows Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 7 + .../systems/BodyCommandApplicationSystem.java | 209 +++++++++++ .../systems/RequestDrainSystem.java | 145 ++------ .../systems/TargetBindingSystem.java | 3 +- .../physicsstore/PhysicsStoreTypes.java | 13 + .../components/BodyCommandComponent.java | 344 ++++++++++++++++++ 6 files changed, 612 insertions(+), 109 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 03a3cea9..1e2d7a73 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyCommandApplicationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.ColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.CompletedStepPublicationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.IdentityIndexSystem; @@ -33,6 +34,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.WorldCollisionIndexSystem; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; @@ -82,6 +84,10 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setBodyComponentType(registry.registerComponent(BodyComponent.class, "Body", BodyComponent.CODEC)); + PhysicsStoreTypes.setBodyCommandComponentType(registry.registerComponent( + BodyCommandComponent.class, + "BodyCommand", + BodyCommandComponent.CODEC)); PhysicsStoreTypes.setDynamicsComponentType(registry.registerComponent(DynamicsComponent.class, "Dynamics", DynamicsComponent.CODEC)); @@ -181,6 +187,7 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new TerrainColliderBindingSystem()); + registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); registry.registerSystem(new PhysicsStoreReadRequestSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java new file mode 100644 index 00000000..8b31317c --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java @@ -0,0 +1,209 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Drains one-tick body command components into runtime/backend operations. + */ +public final class BodyCommandApplicationSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, BodyBindingSystem.class), + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) + ); + private static final Query QUERY = BodyCommandComponent.getComponentType(); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + BiConsumer, CommandBuffer> collector = + (chunk, commandBuffer) -> applyCommands(store, runtime, restore, chunk, commandBuffer); + store.forEachChunk(systemIndex, collector); + } + + private static void applyCommands(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull ArchetypeChunk chunk, + @Nonnull CommandBuffer commandBuffer) { + for (int index = 0; index < chunk.size(); index++) { + BodyCommandComponent commands = chunk.getComponent(index, + BodyCommandComponent.getComponentType()); + if (commands == null) { + continue; + } + UUID bodyUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + Ref ref = chunk.getReferenceTo(index); + if (PhysicsStoreSystemSupport.isNil(bodyUuid)) { + restore.recordSoftSkip("Body command row has nil UUID"); + commandBuffer.removeComponent(ref, BodyCommandComponent.getComponentType()); + continue; + } + for (BodyCommandComponent.Entry command : commands.entries()) { + applyCommand(store, runtime, restore, ref, bodyUuid, command); + } + commandBuffer.removeComponent(ref, BodyCommandComponent.getComponentType()); + } + } + + private static void applyCommand(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Ref ref, + @Nonnull UUID bodyUuid, + @Nonnull BodyCommandComponent.Entry command) { + switch (command.getKind()) { + case WAKE -> runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + null, + null)); + case SLEEP -> runtime.enqueuePendingBodyOperation(PendingBodyOperation.sleep(bodyUuid, + null, + null)); + case IMPULSE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.IMPULSE); + case TORQUE_IMPULSE -> enqueueVector(runtime, + bodyUuid, + command, + PendingBodyOperation.Kind.TORQUE_IMPULSE); + case FORCE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.FORCE); + case TORQUE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.TORQUE); + case SET_TYPE -> applyBodyType(store, runtime, restore, ref, bodyUuid, command); + } + } + + private static void applyBodyType(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Ref ref, + @Nonnull UUID bodyUuid, + @Nonnull BodyCommandComponent.Entry command) { + DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, + ref, + DynamicsComponent.getComponentType()); + DynamicsComponent updated = dynamics != null ? dynamics.clone() : new DynamicsComponent(); + updated.setBodyType(command.getBodyType()); + store.putComponent(ref, DynamicsComponent.getComponentType(), updated); + + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, bodyUuid, restore, false); + if (binding == null) { + if (command.isActivate()) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, null, null)); + } + return; + } + binding.backendRuntime().setBodyType(binding.spaceHandle().value(), + binding.bodyHandle().value(), + BackendRuntimeCodes.bodyTypeCode(command.getBodyType())); + updateBodyHitMetadata(runtime, binding.bodyHandle(), command.getBodyType()); + if (command.isActivate()) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + binding.spaceHandle(), + binding.bodyHandle())); + } + } + + private static void enqueueVector(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID bodyUuid, + @Nonnull BodyCommandComponent.Entry command, + @Nonnull PendingBodyOperation.Kind kind) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.vector(kind, + bodyUuid, + null, + null, + command.getX(), + command.getY(), + command.getZ(), + command.hasOffset(), + command.getOffsetX(), + command.getOffsetY(), + command.getOffsetZ())); + } + + @Nullable + private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsRestoreStatusResource restore, + boolean requireBound) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + if (bodyHandle == null || spaceHandle == null) { + if (requireBound) { + restore.recordSoftSkip("Body command target is unbound: " + bodyUuid); + } + return null; + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (backendRuntime == null) { + restore.recordSoftSkip("Body command backend runtime is missing: " + bodyUuid); + return null; + } + return new RuntimeBodyBinding(spaceHandle, bodyHandle, backendRuntime); + } + + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull BackendSpaceHandle spaceHandle) { + final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; + runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { + if (handle.value() == spaceHandle.value()) { + resolved[0] = backendRuntime; + } + }); + return resolved[0]; + } + + private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull PhysicsBodyType bodyType) { + PhysicsRuntimeResource.BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyHandle); + if (metadata != null) { + runtime.putBodyHitMetadata(bodyHandle, + metadata.bodyKey(), + bodyType, + metadata.shapeType()); + } + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + private record RuntimeBodyBinding(@Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index 023d0a78..29ce9d29 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -17,14 +17,12 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource.QueuedRequest; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; @@ -32,6 +30,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; @@ -156,9 +155,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) fences, structuralConflicts, requests); - applyBodyTypeRequests(store, identity, runtime, refsThisDrain, restore, fences, requests); + applyBodyTypeRequests(store, identity, refsThisDrain, restore, fences, requests); applyTargetRequests(store, identity, refsThisDrain, restore, fences, requests); - enqueueRuntimeBodyRequests(store, identity, runtime, refsThisDrain, restore, fences, requests); + enqueueRuntimeBodyRequests(store, identity, refsThisDrain, restore, fences, requests); recordUnsupported(restore, fences, requests); } catch (RuntimeException | Error exception) { fences.failUnfinished(); @@ -640,7 +639,6 @@ private static RequestApplicationStatus applyTargetRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull RequestFenceTracker fences, @@ -651,7 +649,6 @@ private static void applyBodyTypeRequests(@Nonnull Store store, request, applyBodyTypeRequest(store, identity, - runtime, refsThisDrain, restore, typeRequest)); @@ -661,7 +658,6 @@ private static void applyBodyTypeRequests(@Nonnull Store store, private static void enqueueRuntimeBodyRequests(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull RequestFenceTracker fences, @@ -670,8 +666,8 @@ private static void enqueueRuntimeBodyRequests(@Nonnull Store stor if (request instanceof BodyActivationRequest activationRequest) { trackRequest(fences, request, - enqueueBodyActivationRequest(identity, - runtime, + enqueueBodyActivationRequest(store, + identity, refsThisDrain, restore, activationRequest)); @@ -682,7 +678,6 @@ private static void enqueueRuntimeBodyRequests(@Nonnull Store stor request, enqueueBodyForceRequest(store, identity, - runtime, refsThisDrain, restore, forceRequest)); @@ -693,7 +688,6 @@ private static void enqueueRuntimeBodyRequests(@Nonnull Store stor @Nonnull private static RequestApplicationStatus applyBodyTypeRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull BodyTypeRequest request) { @@ -708,56 +702,28 @@ private static RequestApplicationStatus applyBodyTypeRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull BodyActivationRequest request) { - if (refForUuid(identity, refsThisDrain, request.bodyUuid()) == null) { + Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); + if (bodyRef == null) { restore.recordSoftSkip("Activation request body is missing: " + request.bodyUuid()); return RequestApplicationStatus.SOFT_SKIPPED; } - RuntimeBodyBinding binding = runtimeBodyBinding(runtime, - request.bodyUuid(), - restore, - "Activation request", - false); if (request.action() == BodyActivationRequest.Action.WAKE) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(request.bodyUuid(), - binding != null ? binding.spaceHandle() : null, - binding != null ? binding.bodyHandle() : null)); + appendBodyCommand(store, bodyRef, BodyCommandComponent.wake()); } else { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.sleep(request.bodyUuid(), - binding != null ? binding.spaceHandle() : null, - binding != null ? binding.bodyHandle() : null)); + appendBodyCommand(store, bodyRef, BodyCommandComponent.sleep()); } return RequestApplicationStatus.APPLIED; } @@ -765,7 +731,6 @@ private static RequestApplicationStatus enqueueBodyActivationRequest( @Nonnull private static RequestApplicationStatus enqueueBodyForceRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull BodyForceRequest request) { @@ -785,22 +750,16 @@ private static RequestApplicationStatus enqueueBodyForceRequest(@Nonnull Store

PendingBodyOperation.Kind.IMPULSE; - case TORQUE_IMPULSE -> PendingBodyOperation.Kind.TORQUE_IMPULSE; - case FORCE -> PendingBodyOperation.Kind.FORCE; - case TORQUE -> PendingBodyOperation.Kind.TORQUE; + case IMPULSE -> BodyCommandComponent.Kind.IMPULSE; + case TORQUE_IMPULSE -> BodyCommandComponent.Kind.TORQUE_IMPULSE; + case FORCE -> BodyCommandComponent.Kind.FORCE; + case TORQUE -> BodyCommandComponent.Kind.TORQUE; }; } + private static void appendBodyCommand(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull BodyCommandComponent command) { + BodyCommandComponent existing = store.getComponent(bodyRef, + BodyCommandComponent.getComponentType()); + BodyCommandComponent merged = existing != null ? existing.append(command) : command; + store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); + } + private static boolean isValidBodyUpsert(@Nonnull BodyUpsertRequest request, @Nonnull PhysicsRestoreStatusResource restore) { if (isNil(request.bodyUuid()) @@ -1242,11 +1176,6 @@ private record JointRow(@Nonnull UUID uuid, @Nonnull JointComponent joint) { } - private record RuntimeBodyBinding(@Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle, - @Nonnull PhysicsBackendRuntime backendRuntime) { - } - private enum RequestApplicationStatus { APPLIED, SOFT_SKIPPED diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index 6b48ea49..98f402e3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -31,7 +31,8 @@ public final class TargetBindingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class), + new SystemDependency<>(Order.AFTER, BodyCommandApplicationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index b94b9c7a..05ab3ff3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -15,6 +15,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; @@ -48,6 +49,8 @@ public final class PhysicsStoreTypes { @Nullable private static ComponentType bodyComponentType; @Nullable + private static ComponentType bodyCommandComponentType; + @Nullable private static ComponentType dynamicsComponentType; @Nullable private static ComponentType colliderComponentType; @@ -121,6 +124,11 @@ public static void setBodyComponentType( bodyComponentType = Objects.requireNonNull(type, "type"); } + public static void setBodyCommandComponentType( + @Nonnull ComponentType type) { + bodyCommandComponentType = Objects.requireNonNull(type, "type"); + } + public static void setDynamicsComponentType( @Nonnull ComponentType type) { dynamicsComponentType = Objects.requireNonNull(type, "type"); @@ -266,6 +274,11 @@ public static ComponentType bodyComponentType() { return require(bodyComponentType, "BodyComponent"); } + @Nonnull + public static ComponentType bodyCommandComponentType() { + return require(bodyCommandComponentType, "BodyCommandComponent"); + } + @Nonnull public static ComponentType dynamicsComponentType() { return require(dynamicsComponentType, "DynamicsComponent"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java new file mode 100644 index 00000000..692d194a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java @@ -0,0 +1,344 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * One-tick ordered body commands drained by PhysicsStore systems. + */ +public final class BodyCommandComponent implements Component { + + private static final Entry[] EMPTY_ENTRIES = new Entry[0]; + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + BodyCommandComponent.class, + BodyCommandComponent::new) + .append(new KeyedCodec<>("Commands", + new ArrayCodec<>(Entry.CODEC, Entry[]::new), + false), + (component, value) -> component.entries = copyEntries(value), + BodyCommandComponent::entries) + .add() + .build(); + + @Nonnull + private Entry[] entries = EMPTY_ENTRIES; + + public BodyCommandComponent() { + } + + private BodyCommandComponent(@Nonnull Entry[] entries) { + this.entries = copyEntries(entries); + } + + @Nonnull + public static BodyCommandComponent wake() { + return new BodyCommandComponent(new Entry[] {Entry.wake()}); + } + + @Nonnull + public static BodyCommandComponent sleep() { + return new BodyCommandComponent(new Entry[] {Entry.sleep()}); + } + + @Nonnull + public static BodyCommandComponent setType(@Nonnull PhysicsBodyType bodyType, + boolean activate) { + return new BodyCommandComponent(new Entry[] {Entry.setType(bodyType, activate)}); + } + + @Nonnull + public static BodyCommandComponent vector(@Nonnull Kind kind, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ) { + return new BodyCommandComponent(new Entry[] { + Entry.vector(kind, x, y, z, hasOffset, offsetX, offsetY, offsetZ) + }); + } + + @Nonnull + public BodyCommandComponent append(@Nonnull BodyCommandComponent other) { + Entry[] otherEntries = other.entries(); + if (otherEntries.length == 0) { + return clone(); + } + Entry[] merged = Arrays.copyOf(entries, entries.length + otherEntries.length); + for (int index = 0; index < otherEntries.length; index++) { + merged[entries.length + index] = otherEntries[index].clone(); + } + return new BodyCommandComponent(merged); + } + + @Nonnull + public Entry[] entries() { + return copyEntries(entries); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsStoreTypes.bodyCommandComponentType(); + } + + @Nonnull + @Override + public BodyCommandComponent clone() { + return new BodyCommandComponent(entries); + } + + @Nonnull + private static Entry[] copyEntries(Entry[] entries) { + if (entries == null || entries.length == 0) { + return EMPTY_ENTRIES; + } + return Arrays.stream(entries) + .filter(Objects::nonNull) + .map(Entry::clone) + .toArray(Entry[]::new); + } + + /** + * One ordered body command entry. + */ + public static final class Entry { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Entry.class, + Entry::new) + .append(new KeyedCodec<>("Kind", new EnumCodec<>(Kind.class), false), + (entry, value) -> entry.kind = value != null ? value : Kind.WAKE, + Entry::getKind) + .add() + .append(new KeyedCodec<>("BodyType", new EnumCodec<>(PhysicsBodyType.class), false), + (entry, value) -> entry.bodyType = value != null + ? value + : PhysicsBodyType.DYNAMIC, + Entry::getBodyType) + .add() + .append(new KeyedCodec<>("Activate", Codec.BOOLEAN, false), + (entry, value) -> entry.activate = value != null && value, + Entry::isActivate) + .add() + .append(new KeyedCodec<>("X", Codec.FLOAT, false), + (entry, value) -> entry.x = value != null ? value : 0.0f, + Entry::getX) + .add() + .append(new KeyedCodec<>("Y", Codec.FLOAT, false), + (entry, value) -> entry.y = value != null ? value : 0.0f, + Entry::getY) + .add() + .append(new KeyedCodec<>("Z", Codec.FLOAT, false), + (entry, value) -> entry.z = value != null ? value : 0.0f, + Entry::getZ) + .add() + .append(new KeyedCodec<>("HasOffset", Codec.BOOLEAN, false), + (entry, value) -> entry.hasOffset = value != null && value, + Entry::hasOffset) + .add() + .append(new KeyedCodec<>("OffsetX", Codec.FLOAT, false), + (entry, value) -> entry.offsetX = value != null ? value : 0.0f, + Entry::getOffsetX) + .add() + .append(new KeyedCodec<>("OffsetY", Codec.FLOAT, false), + (entry, value) -> entry.offsetY = value != null ? value : 0.0f, + Entry::getOffsetY) + .add() + .append(new KeyedCodec<>("OffsetZ", Codec.FLOAT, false), + (entry, value) -> entry.offsetZ = value != null ? value : 0.0f, + Entry::getOffsetZ) + .add() + .build(); + + @Nonnull + private Kind kind = Kind.WAKE; + @Nonnull + private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; + private boolean activate; + private float x; + private float y; + private float z; + private boolean hasOffset; + private float offsetX; + private float offsetY; + private float offsetZ; + + public Entry() { + } + + private Entry(@Nonnull Kind kind, + @Nonnull PhysicsBodyType bodyType, + boolean activate, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ) { + this.kind = Objects.requireNonNull(kind, "kind"); + this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); + this.activate = activate; + this.x = x; + this.y = y; + this.z = z; + this.hasOffset = hasOffset; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + } + + @Nonnull + private static Entry wake() { + return new Entry(Kind.WAKE, + PhysicsBodyType.DYNAMIC, + true, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f); + } + + @Nonnull + private static Entry sleep() { + return new Entry(Kind.SLEEP, + PhysicsBodyType.DYNAMIC, + false, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f); + } + + @Nonnull + private static Entry setType(@Nonnull PhysicsBodyType bodyType, + boolean activate) { + return new Entry(Kind.SET_TYPE, + bodyType, + activate, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f); + } + + @Nonnull + private static Entry vector(@Nonnull Kind kind, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ) { + if (!kind.isVector()) { + throw new IllegalArgumentException("Body command kind is not vector-valued: " + kind); + } + return new Entry(kind, + PhysicsBodyType.DYNAMIC, + true, + x, + y, + z, + hasOffset, + offsetX, + offsetY, + offsetZ); + } + + @Nonnull + public Kind getKind() { + return kind; + } + + @Nonnull + public PhysicsBodyType getBodyType() { + return bodyType; + } + + public boolean isActivate() { + return activate; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public float getZ() { + return z; + } + + public boolean hasOffset() { + return hasOffset; + } + + public float getOffsetX() { + return offsetX; + } + + public float getOffsetY() { + return offsetY; + } + + public float getOffsetZ() { + return offsetZ; + } + + @Nonnull + @Override + public Entry clone() { + return new Entry(kind, + bodyType, + activate, + x, + y, + z, + hasOffset, + offsetX, + offsetY, + offsetZ); + } + } + + public enum Kind { + WAKE, + SLEEP, + IMPULSE, + TORQUE_IMPULSE, + FORCE, + TORQUE, + SET_TYPE; + + public boolean isVector() { + return this == IMPULSE || this == TORQUE_IMPULSE || this == FORCE || this == TORQUE; + } + } +} From 9423eaf5a5b84a5ba0e42a556a27427a5b430e98 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:51:19 +0200 Subject: [PATCH 052/534] refactor(physicsstore): centralize ecs row authoring Signed-off-by: Blovien --- .../systems/RequestDrainSystem.java | 110 +++-------- .../physicsstore/PhysicsStoreEntities.java | 182 +++++++++++++++++- 2 files changed, 206 insertions(+), 86 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index 29ce9d29..08b5e5d7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -3,8 +3,6 @@ import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; @@ -32,20 +30,13 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; @@ -434,11 +425,15 @@ private static RequestApplicationStatus applySpaceUpsert(@Nonnull Store ref = ensureRow(store, identity, refsThisDrain, request.spaceUuid()); - store.putComponent(ref, SpaceComponent.getComponentType(), request.space().clone()); - store.putComponent(ref, - WorldCollisionComponent.getComponentType(), - request.worldCollision().clone()); - putSpaceSettingsComponents(store, ref, request); + PhysicsStoreEntities.putSpaceComponents(store, + ref, + request.space(), + request.worldCollision(), + request.solverSettings(), + request.visualSyncSettings(), + request.visualMaterializationSettings(), + request.collisionLodSettings(), + request.extensionSettings()); compatibility.putSpace(request.compatibilitySpaceId(), request.spaceUuid()); SpaceId.reserveAtLeast(request.compatibilitySpaceId().value()); runtime.markSpaceSettingsPending(request.spaceUuid()); @@ -461,51 +456,17 @@ private static RequestApplicationStatus applySpaceSettings(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull SpaceUpsertRequest request) { - store.putComponent(ref, - SolverSettingsComponent.getComponentType(), - request.solverSettings().clone()); - store.putComponent(ref, - VisualSyncSettingsComponent.getComponentType(), - request.visualSyncSettings().clone()); - store.putComponent(ref, - VisualMaterializationSettingsComponent.getComponentType(), - request.visualMaterializationSettings().clone()); - store.putComponent(ref, - CollisionLodSettingsComponent.getComponentType(), - request.collisionLodSettings().clone()); - store.putComponent(ref, - ExtensionSettingsComponent.getComponentType(), - request.extensionSettings().clone()); - } - - private static void putSpaceSettingsComponents(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull SpaceSettingsRequest request) { - store.putComponent(ref, - SolverSettingsComponent.getComponentType(), - request.solverSettings().clone()); - store.putComponent(ref, - VisualSyncSettingsComponent.getComponentType(), - request.visualSyncSettings().clone()); - store.putComponent(ref, - VisualMaterializationSettingsComponent.getComponentType(), - request.visualMaterializationSettings().clone()); - store.putComponent(ref, - CollisionLodSettingsComponent.getComponentType(), - request.collisionLodSettings().clone()); - store.putComponent(ref, - ExtensionSettingsComponent.getComponentType(), - request.extensionSettings().clone()); - } - @Nonnull private static RequestApplicationStatus applyBodyRemove(@Nonnull Store store, int systemIndex, @@ -604,12 +565,8 @@ private static RequestApplicationStatus applyJointUpsert(@Nonnull Store jointRef = ensureRow(store, identity, refsThisDrain, request.jointUuid()); + PhysicsStoreEntities.putJointComponent(store, jointRef, request.joint()); return RequestApplicationStatus.APPLIED; } @@ -779,8 +736,8 @@ private static RequestApplicationStatus applyTerrainRequest(@Nonnull Store holder = store.getRegistry().newHolder(); - holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(terrainUuid)); - holder.addComponent(TerrainColliderComponent.getComponentType(), component); - refsThisDrain.put(terrainUuid, store.addEntity(holder, AddReason.SPAWN)); + refsThisDrain.put(terrainUuid, + store.addEntity(PhysicsStoreEntities.terrainColliderHolder(store, + terrainUuid, + component), + AddReason.SPAWN)); return RequestApplicationStatus.APPLIED; } @@ -820,8 +778,7 @@ private static Ref ensureRow(@Nonnull Store store, if (ref != null) { return ref; } - Holder holder = store.getRegistry().newHolder(); - holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(uuid)); + Holder holder = PhysicsStoreEntities.rowHolder(store, uuid); ref = store.addEntity(holder, AddReason.SPAWN); refsThisDrain.put(uuid, ref); return ref; @@ -1152,19 +1109,6 @@ private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrain } } - @Nonnull - private static > Ref upsertComponentRow( - @Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull UUID uuid, - @Nonnull ComponentType type, - @Nonnull C component) { - Ref ref = ensureRow(store, identity, refsThisDrain, uuid); - store.putComponent(ref, type, component); - return ref; - } - @Nonnull @Override public Set> getDependencies() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java index 59007145..c22c9e61 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java @@ -7,11 +7,20 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -25,6 +34,43 @@ public final class PhysicsStoreEntities { private PhysicsStoreEntities() { } + @Nonnull + public static Holder rowHolder(@Nonnull Store store, + @Nonnull UUID rowUuid) { + Holder holder = store.getRegistry().newHolder(); + addUuid(holder, rowUuid); + return holder; + } + + public static void addUuid(@Nonnull Holder holder, + @Nonnull UUID rowUuid) { + Objects.requireNonNull(holder, "holder") + .addComponent(UuidComponent.getComponentType(), + new UuidComponent(Objects.requireNonNull(rowUuid, "rowUuid"))); + } + + @Nonnull + public static Holder spaceHolder(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull SpaceComponent space, + @Nonnull WorldCollisionComponent worldCollision, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { + Holder holder = rowHolder(store, spaceUuid); + addSpaceComponents(holder, + space, + worldCollision, + solverSettings, + visualSyncSettings, + visualMaterializationSettings, + collisionLodSettings, + extensionSettings); + return holder; + } + @Nonnull public static Holder bodyHolder(@Nonnull Store store, @Nonnull UUID bodyUuid, @@ -35,13 +81,72 @@ public static Holder bodyHolder(@Nonnull Store store @Nonnull ShapeComponent shape, @Nonnull MaterialComponent material, @Nonnull CollisionFilterComponent filter) { - Holder holder = store.getRegistry().newHolder(); - holder.addComponent(UuidComponent.getComponentType(), - new UuidComponent(Objects.requireNonNull(bodyUuid, "bodyUuid"))); + Holder holder = rowHolder(store, bodyUuid); addBodyComponents(holder, body, dynamics, target, collider, shape, material, filter); return holder; } + @Nonnull + public static Holder jointHolder(@Nonnull Store store, + @Nonnull UUID jointUuid, + @Nonnull JointComponent joint) { + Holder holder = rowHolder(store, jointUuid); + holder.addComponent(JointComponent.getComponentType(), + Objects.requireNonNull(joint, "joint").clone()); + return holder; + } + + @Nonnull + public static Holder terrainColliderHolder(@Nonnull Store store, + @Nonnull UUID terrainColliderUuid, + @Nonnull TerrainColliderComponent terrainCollider) { + Holder holder = rowHolder(store, terrainColliderUuid); + holder.addComponent(TerrainColliderComponent.getComponentType(), + Objects.requireNonNull(terrainCollider, "terrainCollider").clone()); + return holder; + } + + public static void addSpaceComponents(@Nonnull Holder holder, + @Nonnull SpaceComponent space, + @Nonnull WorldCollisionComponent worldCollision, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { + Objects.requireNonNull(holder, "holder") + .addComponent(SpaceComponent.getComponentType(), + Objects.requireNonNull(space, "space").clone()); + holder.addComponent(WorldCollisionComponent.getComponentType(), + Objects.requireNonNull(worldCollision, "worldCollision").clone()); + addSpaceSettingsComponents(holder, + solverSettings, + visualSyncSettings, + visualMaterializationSettings, + collisionLodSettings, + extensionSettings); + } + + public static void addSpaceSettingsComponents(@Nonnull Holder holder, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { + Objects.requireNonNull(holder, "holder") + .addComponent(SolverSettingsComponent.getComponentType(), + Objects.requireNonNull(solverSettings, "solverSettings").clone()); + holder.addComponent(VisualSyncSettingsComponent.getComponentType(), + Objects.requireNonNull(visualSyncSettings, "visualSyncSettings").clone()); + holder.addComponent(VisualMaterializationSettingsComponent.getComponentType(), + Objects.requireNonNull(visualMaterializationSettings, + "visualMaterializationSettings").clone()); + holder.addComponent(CollisionLodSettingsComponent.getComponentType(), + Objects.requireNonNull(collisionLodSettings, "collisionLodSettings").clone()); + holder.addComponent(ExtensionSettingsComponent.getComponentType(), + Objects.requireNonNull(extensionSettings, "extensionSettings").clone()); + } + public static void addBodyComponents(@Nonnull Holder holder, @Nonnull BodyComponent body, @Nonnull DynamicsComponent dynamics, @@ -68,6 +173,59 @@ public static void addBodyComponents(@Nonnull Holder holder, } } + public static void putSpaceComponents(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull SpaceComponent space, + @Nonnull WorldCollisionComponent worldCollision, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(ref, "ref"); + store.putComponent(ref, + SpaceComponent.getComponentType(), + Objects.requireNonNull(space, "space").clone()); + store.putComponent(ref, + WorldCollisionComponent.getComponentType(), + Objects.requireNonNull(worldCollision, "worldCollision").clone()); + putSpaceSettingsComponents(store, + ref, + solverSettings, + visualSyncSettings, + visualMaterializationSettings, + collisionLodSettings, + extensionSettings); + } + + public static void putSpaceSettingsComponents(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(ref, "ref"); + store.putComponent(ref, + SolverSettingsComponent.getComponentType(), + Objects.requireNonNull(solverSettings, "solverSettings").clone()); + store.putComponent(ref, + VisualSyncSettingsComponent.getComponentType(), + Objects.requireNonNull(visualSyncSettings, "visualSyncSettings").clone()); + store.putComponent(ref, + VisualMaterializationSettingsComponent.getComponentType(), + Objects.requireNonNull(visualMaterializationSettings, + "visualMaterializationSettings").clone()); + store.putComponent(ref, + CollisionLodSettingsComponent.getComponentType(), + Objects.requireNonNull(collisionLodSettings, "collisionLodSettings").clone()); + store.putComponent(ref, + ExtensionSettingsComponent.getComponentType(), + Objects.requireNonNull(extensionSettings, "extensionSettings").clone()); + } + public static void putBodyComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull BodyComponent body, @@ -103,4 +261,22 @@ public static void putBodyComponents(@Nonnull Store store, store.removeComponent(ref, TargetComponent.getComponentType()); } } + + public static void putJointComponent(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull JointComponent joint) { + Objects.requireNonNull(store, "store") + .putComponent(Objects.requireNonNull(ref, "ref"), + JointComponent.getComponentType(), + Objects.requireNonNull(joint, "joint").clone()); + } + + public static void putTerrainColliderComponent(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull TerrainColliderComponent terrainCollider) { + Objects.requireNonNull(store, "store") + .putComponent(Objects.requireNonNull(ref, "ref"), + TerrainColliderComponent.getComponentType(), + Objects.requireNonNull(terrainCollider, "terrainCollider").clone()); + } } From b420e4206131cebf51c9de2acab3bea6fbc51c98 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:54:22 +0200 Subject: [PATCH 053/534] refactor(physicsstore): use typed store access in core Signed-off-by: Blovien --- .../PhysicsStoreControlSessionRequests.java | 10 +- .../PhysicsWorldRuntimeResource.java | 145 +++++++++++++++--- 2 files changed, 133 insertions(+), 22 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java index 19a9ac52..aef25e5d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java @@ -2,11 +2,13 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; @@ -34,7 +36,11 @@ public static void enqueueRelease(@Nonnull Store store, @Nonnull PhysicsControlSessionComponent session) { List requests = releaseRequests(session); if (!requests.isEmpty()) { - PhysicsStoreAccess.enqueueAll(store.getExternalData().getWorld(), requests); + Store physicsStore = + ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() + .getStore(); + physicsStore.getResource(PhysicsRequestQueueResource.getResourceType()) + .enqueueAll(requests); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 61e747ae..8571b290 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -7,13 +7,18 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; @@ -53,7 +58,17 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -73,6 +88,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.RejectedExecutionException; @@ -262,10 +278,14 @@ private Optional> tryPhysicsStoreQuery(@Nonnull Physic if (entityStore == null) { return Optional.empty(); } + if (!isAuthoritativePhysicsStoreActive()) { + return Optional.empty(); + } try { - PhysicsStore physicsStore = PhysicsStoreAccess.require(entityStore.getExternalData().getWorld()); - return PhysicsStoreQueryBridge.tryQuery(physicsStore.getStore(), query); - } catch (IllegalStateException exception) { + return PhysicsStoreQueryBridge.tryQuery( + physicsStore(entityStore.getExternalData().getWorld()), + query); + } catch (ClassCastException | IllegalStateException exception) { return Optional.empty(); } } @@ -314,6 +334,87 @@ private World requireAuthoritativeWorld(@Nonnull String operation) { return entityStore.getExternalData().getWorld(); } + @Nonnull + private Store authoritativePhysicsStore(@Nonnull String operation) { + return physicsStore(requireAuthoritativeWorld(operation)); + } + + @Nonnull + private static Store physicsStore(@Nonnull World world) { + return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() + .getStore(); + } + + private static void enqueuePhysicsStoreRequest(@Nonnull Store store, + @Nonnull PhysicsStoreRequest request) { + store.getResource(PhysicsRequestQueueResource.getResourceType()) + .enqueue(Objects.requireNonNull(request, "request")); + } + + @Nonnull + private static UUID requireSpaceUuid(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + if (spaceUuid == null) { + throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() + + " is not registered"); + } + return spaceUuid; + } + + @Nullable + private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + if (spaceUuid == null) { + return null; + } + Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + if (ref == null || !ref.isValid()) { + return null; + } + SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); + if (space == null) { + return null; + } + WorldCollisionComponent worldCollision = store.getComponent(ref, + WorldCollisionComponent.getComponentType()); + SolverSettingsComponent solverSettings = store.getComponent(ref, + SolverSettingsComponent.getComponentType()); + VisualSyncSettingsComponent visualSyncSettings = store.getComponent(ref, + VisualSyncSettingsComponent.getComponentType()); + VisualMaterializationSettingsComponent visualMaterializationSettings = + store.getComponent(ref, VisualMaterializationSettingsComponent.getComponentType()); + CollisionLodSettingsComponent collisionLodSettings = store.getComponent(ref, + CollisionLodSettingsComponent.getComponentType()); + ExtensionSettingsComponent extensionSettings = store.getComponent(ref, + ExtensionSettingsComponent.getComponentType()); + PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); + if (worldCollision != null) { + worldCollision.copyTo(settings); + } + if (solverSettings != null) { + solverSettings.copyTo(settings); + } + if (visualSyncSettings != null) { + visualSyncSettings.copyTo(settings); + } + if (visualMaterializationSettings != null) { + visualMaterializationSettings.copyTo(settings); + } + if (collisionLodSettings != null) { + collisionLodSettings.copyTo(settings); + } + if (extensionSettings != null) { + extensionSettings.copyTo(settings); + } + return settings; + } + @Nonnull private IllegalStateException authoritativeFenceUnavailable(@Nonnull String operation) { return new IllegalStateException("Cannot " + operation @@ -403,10 +504,9 @@ public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreAccess.enqueueSpaceUpsert(requireAuthoritativeWorld("create physics space"), - spaceId, - backendId, - settings); + Impulse.getRuntimeProvider(backendId); + enqueuePhysicsStoreRequest(authoritativePhysicsStore("create physics space"), + SpaceUpsertRequest.of(UUID.randomUUID(), spaceId, backendId, settings)); return spaceId; } requireLegacyMutationAllowed("create physics space"); @@ -464,8 +564,9 @@ public PhysicsSpaceBinding getSpaceBinding(@Nonnull SpaceId spaceId) { @Override public boolean hasSpace(@Nonnull SpaceId spaceId) { if (isAuthoritativePhysicsStoreActive()) { - return PhysicsStoreAccess.hasSpace(requireAuthoritativeWorld("check physics space"), - spaceId); + return authoritativePhysicsStore("check physics space") + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .hasSpace(spaceId); } return spaceRuntime.getBinding(spaceId) != null; } @@ -484,7 +585,9 @@ public Collection getSpaceBindings() { @Override public Collection getSpaceIds() { if (isAuthoritativePhysicsStoreActive()) { - return PhysicsStoreAccess.spaceIds(requireAuthoritativeWorld("list physics spaces")); + return List.copyOf(authoritativePhysicsStore("list physics spaces") + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .spaceIds()); } return spaceRuntime.getSpaceIds(); } @@ -492,7 +595,9 @@ public Collection getSpaceIds() { @Override public int getSpaceCount() { if (isAuthoritativePhysicsStoreActive()) { - return PhysicsStoreAccess.spaceCount(requireAuthoritativeWorld("count physics spaces")); + return authoritativePhysicsStore("count physics spaces") + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .size(); } return spaceRuntime.getSpaceCount(); } @@ -890,8 +995,9 @@ public void removeSpace(@Nonnull SpaceId spaceId) { @Override public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreAccess.enqueueSpaceRemove(requireAuthoritativeWorld("remove physics space"), - spaceId); + Store physicsStore = authoritativePhysicsStore("remove physics space"); + enqueuePhysicsStoreRequest(physicsStore, + SpaceRemoveRequest.of(requireSpaceUuid(physicsStore, spaceId))); return; } requireLegacyMutationAllowed("remove physics space"); @@ -1002,8 +1108,8 @@ private PhysicsRuntimeResetResult resetRuntimeStateKeepingSpacesDirect(@Nonnull @Override public PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { if (isAuthoritativePhysicsStoreActive()) { - PhysicsSpaceSettings settings = PhysicsStoreAccess.getSpaceSettings( - requireAuthoritativeWorld("read physics space settings"), + PhysicsSpaceSettings settings = getPhysicsStoreSpaceSettings( + authoritativePhysicsStore("read physics space settings"), spaceId); if (settings == null) { throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() @@ -1022,10 +1128,9 @@ public PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { @Override public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreAccess.enqueueSpaceSettings( - requireAuthoritativeWorld("set physics space settings"), - spaceId, - settings); + Store physicsStore = authoritativePhysicsStore("set physics space settings"); + enqueuePhysicsStoreRequest(physicsStore, + SpaceSettingsRequest.of(requireSpaceUuid(physicsStore, spaceId), settings)); return; } requireLegacyMutationAllowed("set physics space settings"); From 913a00389bc4831c2c50dca5618139d843bfe358 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:57:12 +0200 Subject: [PATCH 054/534] refactor(physicsstore): remove generic access facade Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreAccess.java | 268 ------------------ .../impulse/examples/commands/EcsCommand.java | 11 +- .../commands/ExamplePhysicsUtils.java | 61 +++- .../examples/commands/ForcesCommand.java | 5 +- .../examples/commands/GrabCommand.java | 5 +- .../examples/commands/JointsCommand.java | 5 +- .../commands/stress/StressJointsCommand.java | 5 +- .../explosive/ExplosiveBlockRuntime.java | 5 +- 8 files changed, 67 insertions(+), 298 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java deleted file mode 100644 index f9ba7074..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAccess.java +++ /dev/null @@ -1,268 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Access to the early-plugin-injected PhysicsStore on a Hytale world. - */ -public final class PhysicsStoreAccess { - - @Nullable - private static volatile MethodHandle worldGetPhysicsStore; - - private PhysicsStoreAccess() { - } - - @Nonnull - public static PhysicsStore require(@Nonnull World world) { - Objects.requireNonNull(world, "world"); - try { - return (PhysicsStore) worldAccessor().invoke(world); - } catch (RuntimeException | Error exception) { - throw exception; - } catch (Throwable throwable) { - throw new IllegalStateException("Unable to access World.getPhysicsStore()", throwable); - } - } - - @Nullable - public static UUID resolveSpaceUuid(@Nonnull World world, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - Store store = require(world).getStore(); - PhysicsSpaceCompatibilityIndexResource index = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - return index.getSpaceUuid(spaceId); - } - - public static boolean hasSpace(@Nonnull World world, @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - Store store = require(world).getStore(); - return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .hasSpace(spaceId); - } - - @Nonnull - public static Collection spaceIds(@Nonnull World world) { - Store store = require(world).getStore(); - return List.copyOf(store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .spaceIds()); - } - - public static int spaceCount(@Nonnull World world) { - Store store = require(world).getStore(); - return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).size(); - } - - @Nullable - public static PhysicsStoreBodySnapshot getBodySnapshot(@Nonnull World world, - @Nonnull UUID bodyUuid) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Store store = require(world).getStore(); - return store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyUuid); - } - - @Nullable - public static PhysicsSpaceSettings getSpaceSettings(@Nonnull World world, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - Store store = require(world).getStore(); - UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(spaceId); - if (spaceUuid == null) { - return null; - } - Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - if (ref == null || !ref.isValid()) { - return null; - } - SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); - if (space == null) { - return null; - } - WorldCollisionComponent worldCollision = store.getComponent(ref, - WorldCollisionComponent.getComponentType()); - SolverSettingsComponent solverSettings = store.getComponent(ref, - SolverSettingsComponent.getComponentType()); - VisualSyncSettingsComponent visualSyncSettings = store.getComponent(ref, - VisualSyncSettingsComponent.getComponentType()); - VisualMaterializationSettingsComponent visualMaterializationSettings = - store.getComponent(ref, VisualMaterializationSettingsComponent.getComponentType()); - CollisionLodSettingsComponent collisionLodSettings = store.getComponent(ref, - CollisionLodSettingsComponent.getComponentType()); - ExtensionSettingsComponent extensionSettings = store.getComponent(ref, - ExtensionSettingsComponent.getComponentType()); - return toSpaceSettings(worldCollision, - solverSettings, - visualSyncSettings, - visualMaterializationSettings, - collisionLodSettings, - extensionSettings); - } - - @Nonnull - public static UUID enqueueSpaceUpsert(@Nonnull World world, - @Nonnull SpaceId compatibilitySpaceId, - @Nonnull BackendId backendId, - @Nonnull PhysicsSpaceSettings settings) { - Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); - Objects.requireNonNull(backendId, "backendId"); - Objects.requireNonNull(settings, "settings"); - Impulse.getRuntimeProvider(backendId); - UUID spaceUuid = UUID.randomUUID(); - enqueue(world, SpaceUpsertRequest.of(spaceUuid, compatibilitySpaceId, backendId, settings)); - return spaceUuid; - } - - public static void enqueueSpaceRemove(@Nonnull World world, @Nonnull SpaceId spaceId) { - UUID spaceUuid = requireSpaceUuid(world, spaceId); - enqueue(world, SpaceRemoveRequest.of(spaceUuid)); - } - - public static void enqueueSpaceSettings(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { - Objects.requireNonNull(settings, "settings"); - UUID spaceUuid = requireSpaceUuid(world, spaceId); - enqueue(world, SpaceSettingsRequest.of(spaceUuid, settings)); - } - - public static void enqueue(@Nonnull World world, - @Nonnull PhysicsStoreRequest request) { - Objects.requireNonNull(request, "request"); - Store store = require(world).getStore(); - store.getResource(PhysicsRequestQueueResource.getResourceType()) - .enqueue(request); - } - - public static void enqueueAll(@Nonnull World world, - @Nonnull Iterable requests) { - Objects.requireNonNull(requests, "requests"); - List copied = new ArrayList<>(); - for (PhysicsStoreRequest request : requests) { - copied.add(Objects.requireNonNull(request, "request")); - } - Store store = require(world).getStore(); - PhysicsRequestQueueResource queue = store.getResource( - PhysicsRequestQueueResource.getResourceType()); - queue.enqueueAll(copied); - } - - @Nonnull - public static PhysicsStoreRequestFenceHandle enqueueAllFenced(@Nonnull World world, - @Nonnull Iterable requests, - long submittedServerTick) { - Objects.requireNonNull(requests, "requests"); - List copied = new ArrayList<>(); - for (PhysicsStoreRequest request : requests) { - copied.add(Objects.requireNonNull(request, "request")); - } - Store store = require(world).getStore(); - PhysicsRequestQueueResource queue = store.getResource( - PhysicsRequestQueueResource.getResourceType()); - return queue.enqueueAllFenced(copied, submittedServerTick); - } - - @Nonnull - private static MethodHandle worldAccessor() { - MethodHandle accessor = worldGetPhysicsStore; - if (accessor != null) { - return accessor; - } - synchronized (PhysicsStoreAccess.class) { - accessor = worldGetPhysicsStore; - if (accessor == null) { - accessor = findWorldAccessor(); - worldGetPhysicsStore = accessor; - } - return accessor; - } - } - - @Nonnull - private static UUID requireSpaceUuid(@Nonnull World world, @Nonnull SpaceId spaceId) { - UUID spaceUuid = resolveSpaceUuid(world, spaceId); - if (spaceUuid == null) { - throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() - + " is not registered"); - } - return spaceUuid; - } - - @Nonnull - private static PhysicsSpaceSettings toSpaceSettings( - @Nullable WorldCollisionComponent worldCollision, - @Nullable SolverSettingsComponent solverSettings, - @Nullable VisualSyncSettingsComponent visualSyncSettings, - @Nullable VisualMaterializationSettingsComponent visualMaterializationSettings, - @Nullable CollisionLodSettingsComponent collisionLodSettings, - @Nullable ExtensionSettingsComponent extensionSettings) { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - if (worldCollision != null) { - worldCollision.copyTo(settings); - } - if (solverSettings != null) { - solverSettings.copyTo(settings); - } - if (visualSyncSettings != null) { - visualSyncSettings.copyTo(settings); - } - if (visualMaterializationSettings != null) { - visualMaterializationSettings.copyTo(settings); - } - if (collisionLodSettings != null) { - collisionLodSettings.copyTo(settings); - } - if (extensionSettings != null) { - extensionSettings.copyTo(settings); - } - return settings; - } - - @Nonnull - private static MethodHandle findWorldAccessor() { - try { - return MethodHandles.publicLookup() - .findVirtual(World.class, "getPhysicsStore", MethodType.methodType(PhysicsStore.class)); - } catch (NoSuchMethodException | IllegalAccessException exception) { - throw new IllegalStateException("Impulse requires the PhysicsStore early plugin. " - + "Install impulse-early-plugin as a Hytale early plugin before using PhysicsStore APIs.", - exception); - } - } -} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index f06fd90b..64c17db9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -21,7 +21,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; @@ -171,7 +170,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); Vector3d impulse = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(strength); - PhysicsStoreAccess.enqueue(world, + ExamplePhysicsUtils.enqueuePhysicsStoreRequest(world, BodyForceRequest.impulse(hit.bodyKey().value(), (float) impulse.x, (float) impulse.y, @@ -208,7 +207,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, RigidBodyKey bodyKey = RigidBodyKey.random(); UUID bodyUuid = bodyKey.value(); Vector3d spawn = new Vector3d(playerPos).add(0.0, 2.0, 0.0); - UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); if (spaceUuid == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound yet.")); @@ -232,7 +231,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, true, false, true)); - PhysicsStoreAccess.enqueueAll(world, requests); + ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, @@ -410,7 +409,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); boolean contactEventsEnabled = contactEventsEnabled(resource); - UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); if (spaceUuid == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound yet.")); @@ -424,7 +423,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, RigidBodyKey bodyKey = RigidBodyKey.random(); UUID bodyUuid = bodyKey.value(); - PhysicsStoreAccess.enqueue(world, + ExamplePhysicsUtils.enqueuePhysicsStoreRequest(world, ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, bodyUuid, vector(spawn), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 73969082..ba9d2060 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -17,8 +17,12 @@ import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; @@ -26,8 +30,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodySpawnRequests; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; @@ -80,6 +84,45 @@ public static PhysicsWorldResource resource(@Nonnull Store store) { return store.getResource(PhysicsWorldResource.getResourceType()); } + @Nonnull + public static Store physicsStore(@Nonnull World world) { + return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() + .getStore(); + } + + @Nullable + public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, + @Nonnull SpaceId spaceId) { + return physicsStore(world) + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + } + + public static void enqueuePhysicsStoreRequest(@Nonnull World world, + @Nonnull PhysicsStoreRequest request) { + physicsStore(world) + .getResource(PhysicsRequestQueueResource.getResourceType()) + .enqueue(Objects.requireNonNull(request, "request")); + } + + public static void enqueuePhysicsStoreRequests(@Nonnull World world, + @Nonnull Iterable requests) { + physicsStore(world) + .getResource(PhysicsRequestQueueResource.getResourceType()) + .enqueueAll(Objects.requireNonNull(requests, "requests")); + } + + @Nonnull + public static PhysicsStoreRequestFenceHandle enqueuePhysicsStoreRequestsFenced( + @Nonnull World world, + @Nonnull Iterable requests, + long submittedServerTick) { + return physicsStore(world) + .getResource(PhysicsRequestQueueResource.getResourceType()) + .enqueueAllFenced(Objects.requireNonNull(requests, "requests"), + Math.max(0L, submittedServerTick)); + } + @Nullable public static SpaceId spaceId(@Nonnull CommandContext ctx, @Nonnull PhysicsWorldResource resource, @@ -213,7 +256,7 @@ private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store tryCreatePhysicsStoreDemo(@Nonnull World w @Nonnull Vector3d origin) { UUID spaceUuid; try { - spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); } catch (IllegalStateException exception) { return null; } @@ -105,7 +104,7 @@ private static List tryCreatePhysicsStoreDemo(@Nonnull World w createSlider(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); createSpring(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); try { - PhysicsStoreAccess.enqueueAll(world, requests); + ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); } catch (IllegalStateException exception) { return null; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 8ac0059e..d243e24d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -14,7 +14,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; @@ -88,7 +87,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); UUID spaceUuid; try { - spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); } catch (IllegalStateException exception) { spaceUuid = null; } @@ -123,7 +122,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, createdJoints += rowJoints; } try { - PhysicsStoreAccess.enqueueAll(world, requests); + ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); } catch (IllegalStateException exception) { ctx.sender().sendMessage(Message.raw("Cannot queue stress joint demo: " + exception.getMessage())); return CompletableFuture.completedFuture(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index db75a25f..0432909b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -20,7 +20,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAccess; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -113,7 +112,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @Nonnull ExplosiveBlockComponent settings) { - UUID spaceUuid = PhysicsStoreAccess.resolveSpaceUuid(world, spaceId); + UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); if (spaceUuid == null) { throw new IllegalStateException("Cannot spawn explosive fragments because PhysicsStore " + "space id=" + spaceId.value() + " is not bound"); @@ -174,7 +173,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e (float) groupCenter.z, group.mass() > 0.0f)); } - PhysicsStoreAccess.enqueueAll(world, requests); + ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); for (int i = 0; i < groups.size(); i++) { spawnGroupVisuals(time, fragmentSpawner, groups.get(i), pending.get(i)); From ef3d500cb64c3791cb90923b2552f80557543470 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 11:58:51 +0200 Subject: [PATCH 055/534] refactor(physicsstore): drop generic query bridge Signed-off-by: Blovien --- .../queries/PhysicsStoreQueryBridge.java | 408 ------------------ .../PhysicsWorldRuntimeResource.java | 29 +- 2 files changed, 6 insertions(+), 431 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java deleted file mode 100644 index 964844fb..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/queries/PhysicsStoreQueryBridge.java +++ /dev/null @@ -1,408 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.queries; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; -import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.CcdSupportQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQueryHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastAllQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RuntimeJointCountQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SolverCapabilityQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.UnsupportedCcdSpacesQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Internal compatibility adapter from legacy query DTOs to authoritative PhysicsStore state. - */ -public final class PhysicsStoreQueryBridge { - - private PhysicsStoreQueryBridge() { - } - - @Nonnull - public static Optional> tryQuery(@Nonnull Store store, - @Nonnull PhysicsQuery query) { - Objects.requireNonNull(store, "store"); - Objects.requireNonNull(query, "query"); - Object result; - if (query instanceof RigidBodyStateQuery state) { - result = rigidBodyState(store, state); - } else if (query instanceof RaycastClosestQuery raycast) { - result = raycastClosest(store, raycast); - } else if (query instanceof RaycastClosestBatchQuery raycasts) { - result = raycastClosestBatch(store, raycasts); - } else if (query instanceof RaycastAllQuery raycast) { - result = raycastAll(store, raycast); - } else if (query instanceof SpaceBodyCountQuery count) { - result = spaceBodyCount(store, count); - } else if (query instanceof SpaceSummaryQuery summary) { - result = spaceSummary(store, summary); - } else if (query instanceof CcdSupportQuery ccd) { - result = ccdSupported(store, ccd); - } else if (query instanceof UnsupportedCcdSpacesQuery unsupportedCcd) { - result = unsupportedCcdSpaces(store, unsupportedCcd); - } else if (query instanceof SolverCapabilityQuery solver) { - result = solverCapability(store, solver); - } else if (query instanceof RuntimeJointCountQuery jointCount) { - result = runtimeJointCount(store, jointCount); - } else { - return Optional.empty(); - } - @SuppressWarnings("unchecked") - R typed = (R) result; - return Optional.of(PhysicsQueryHandle.completed(query, typed)); - } - - @Nonnull - private static Optional rigidBodyState(@Nonnull Store store, - @Nonnull RigidBodyStateQuery query) { - PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsStoreBodySnapshot body = snapshots.getBody(query.bodyKey().value()); - if (body == null) { - return Optional.empty(); - } - return Optional.of(new RigidBodyStateView(query.bodyKey(), - body.bodyType(), - RigidBodyPose.of(body.position(), body.rotation()))); - } - - @Nonnull - private static Optional raycastClosest(@Nonnull Store store, - @Nonnull RaycastClosestQuery query) { - SpaceQueryContext space = space(store, query.spaceId()); - if (space == null) { - return Optional.empty(); - } - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - RayHitCapture hit = new RayHitCapture(runtime); - Vector3f from = query.from(); - Vector3f to = query.to(); - boolean hitFound = space.backendRuntime().raycastClosest(space.spaceHandle().value(), - from.x, - from.y, - from.z, - to.x, - to.y, - to.z, - hit); - return hitFound && hit.captured ? Optional.of(hit.view()) : Optional.empty(); - } - - @Nonnull - private static RaycastClosestBatchResult raycastClosestBatch(@Nonnull Store store, - @Nonnull RaycastClosestBatchQuery query) { - SpaceQueryContext space = space(store, query.spaceId()); - if (space == null) { - return new RaycastClosestBatchResult(new RaycastHitView[query.rayCount()]); - } - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - int rayCount = query.rayCount(); - RaycastHitView[] hits = new RaycastHitView[rayCount]; - Vector3f from = new Vector3f(); - Vector3f to = new Vector3f(); - for (int index = 0; index < rayCount; index++) { - RaycastSegment ray = query.ray(index); - ray.copyFrom(from); - ray.copyTo(to); - int rayIndex = index; - space.backendRuntime().raycastClosest(space.spaceHandle().value(), - from.x, - from.y, - from.z, - to.x, - to.y, - to.z, - (bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance) -> hits[rayIndex] = toView(runtime, - bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance)); - } - return new RaycastClosestBatchResult(hits); - } - - @Nonnull - private static List raycastAll(@Nonnull Store store, - @Nonnull RaycastAllQuery query) { - SpaceQueryContext space = space(store, query.spaceId()); - if (space == null) { - return List.of(); - } - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - Vector3f from = query.from(); - Vector3f to = query.to(); - List hits = new ArrayList<>(); - space.backendRuntime().raycastAll(space.spaceHandle().value(), - from.x, - from.y, - from.z, - to.x, - to.y, - to.z, - (bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance) -> hits.add(toView(runtime, - bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance))); - return hits.isEmpty() ? List.of() : List.copyOf(hits); - } - - private static int spaceBodyCount(@Nonnull Store store, - @Nonnull SpaceBodyCountQuery query) { - SpaceQueryContext space = space(store, query.spaceId()); - return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; - } - - @Nonnull - private static List spaceSummary(@Nonnull Store store, - @Nonnull SpaceSummaryQuery query) { - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - List summaries = new ArrayList<>(); - if (query.spaceId() != null) { - SpaceQueryContext space = space(runtime, compatibility, query.spaceId()); - if (space != null) { - summaries.add(summary(compatibility, space)); - } - return summaries.isEmpty() ? List.of() : List.copyOf(summaries); - } - runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { - SpaceId spaceId = compatibility.getSpaceId(spaceUuid); - if (spaceId != null) { - summaries.add(new SpaceSummary(spaceId, - backendId, - backendRuntime.bodyCount(spaceHandle.value()), - backendRuntime.jointCount(spaceHandle.value()))); - } - }); - return summaries.isEmpty() ? List.of() : List.copyOf(summaries); - } - - private static boolean ccdSupported(@Nonnull Store store, - @Nonnull CcdSupportQuery query) { - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - boolean[] supported = {false}; - runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { - if (backendRuntime.supportsContinuousCollision(spaceHandle.value())) { - supported[0] = true; - } - }); - return supported[0]; - } - - @Nonnull - private static List unsupportedCcdSpaces(@Nonnull Store store, - @Nonnull UnsupportedCcdSpacesQuery query) { - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - List spaces = new ArrayList<>(); - runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { - if (backendRuntime.supportsContinuousCollision(spaceHandle.value())) { - return; - } - spaces.add(summary(compatibility, - new SpaceQueryContext(spaceUuid, backendId, spaceHandle, backendRuntime))); - }); - return spaces.isEmpty() ? List.of() : List.copyOf(spaces); - } - - @Nonnull - private static SolverCapabilitySummary solverCapability(@Nonnull Store store, - @Nonnull SolverCapabilityQuery query) { - SpaceQueryContext space = requireSpace(store, query.spaceId()); - return new SolverCapabilitySummary(query.spaceId(), - space.backendId().value(), - space.backendRuntime().supportsSolverTuning(space.spaceHandle().value()), - space.backendRuntime().supportsActivationTuning(space.spaceHandle().value())); - } - - private static int runtimeJointCount(@Nonnull Store store, - @Nonnull RuntimeJointCountQuery query) { - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - int[] count = {0}; - runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> - count[0] += backendRuntime.jointCount(spaceHandle.value())); - return count[0]; - } - - @Nonnull - private static SpaceQueryContext requireSpace(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - SpaceQueryContext space = space(store, spaceId); - if (space == null) { - throw new IllegalArgumentException("Physics space id=" + spaceId + " is not registered"); - } - return space; - } - - @Nonnull - private static SpaceSummary summary(@Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull SpaceQueryContext space) { - SpaceId spaceId = compatibility.getSpaceId(space.spaceUuid()); - if (spaceId == null) { - throw new IllegalStateException("PhysicsStore space has no compatibility SpaceId: " - + space.spaceUuid()); - } - return new SpaceSummary(spaceId, - space.backendId(), - space.backendRuntime().bodyCount(space.spaceHandle().value()), - space.backendRuntime().jointCount(space.spaceHandle().value())); - } - - @Nullable - private static SpaceQueryContext space(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - return space(runtime, compatibility, spaceId); - } - - @Nullable - private static SpaceQueryContext space(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull SpaceId spaceId) { - UUID spaceUuid = compatibility.getSpaceUuid(spaceId); - if (spaceUuid == null) { - return null; - } - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); - BackendId backendId = runtime.getSpaceBackendId(spaceUuid); - PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; - if (spaceHandle == null || backendId == null || backendRuntime == null) { - return null; - } - return new SpaceQueryContext(spaceUuid, backendId, spaceHandle, backendRuntime); - } - - @Nonnull - private static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, - long bodyId, - float pointX, - float pointY, - float pointZ, - float normalX, - float normalY, - float normalZ, - float fraction, - float distance) { - BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyId); - return new RaycastHitView(metadata != null ? metadata.bodyKey() : null, - metadata != null ? metadata.bodyType() : PhysicsBodyType.STATIC, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - metadata != null ? metadata.shapeType() : ShapeType.UNKNOWN, - fraction, - distance); - } - - private record SpaceQueryContext(@Nonnull UUID spaceUuid, - @Nonnull BackendId backendId, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull PhysicsBackendRuntime backendRuntime) { - } - - private static final class RayHitCapture implements BackendRayHitSink { - - @Nonnull - private final PhysicsRuntimeResource runtime; - private boolean captured; - @Nullable - private RaycastHitView view; - - private RayHitCapture(@Nonnull PhysicsRuntimeResource runtime) { - this.runtime = runtime; - } - - @Override - public void accept(long bodyId, - float pointX, - float pointY, - float pointZ, - float normalX, - float normalY, - float normalZ, - float fraction, - float distance) { - view = toView(runtime, - bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance); - captured = true; - } - - @Nonnull - private RaycastHitView view() { - return Objects.requireNonNull(view, "view"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 8571b290..2a4462bd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -44,7 +44,6 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsWorldCollisionRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; -import dev.hytalemodding.impulse.core.internal.physicsstore.queries.PhysicsStoreQueryBridge; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -87,7 +86,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -263,33 +261,18 @@ public PhysicsCommandHandle submitRecordedCommands(@Nonnull MutablePhysicsComman @Override public PhysicsQueryHandle query(@Nonnull PhysicsQuery query) { Objects.requireNonNull(query, "query"); - Optional> physicsStoreQuery = tryPhysicsStoreQuery(query); - if (physicsStoreQuery.isPresent()) { - return physicsStoreQuery.get(); + if (isAuthoritativePhysicsStoreActive()) { + return PhysicsQueryHandle.failed(query, + new IllegalStateException("PhysicsWorldResource.query is a legacy runtime API while " + + "authoritative PhysicsStore is active. Use PhysicsSnapshotResource for copied " + + "body state, PhysicsStoreDiagnostics for owner-lane diagnostics, or " + + "PhysicsStoreRaycasts for raycasts.")); } CompletableFuture completion = ownerGateway.enqueueCall("execute physics query", () -> simulationExecutor.query(query)); return PhysicsQueryHandle.fromCompletion(query, completion); } - @Nonnull - private Optional> tryPhysicsStoreQuery(@Nonnull PhysicsQuery query) { - Store entityStore = owningStore; - if (entityStore == null) { - return Optional.empty(); - } - if (!isAuthoritativePhysicsStoreActive()) { - return Optional.empty(); - } - try { - return PhysicsStoreQueryBridge.tryQuery( - physicsStore(entityStore.getExternalData().getWorld()), - query); - } catch (ClassCastException | IllegalStateException exception) { - return Optional.empty(); - } - } - @Nonnull public CompletionStage queryInternal(@Nonnull PhysicsInternalQuery query) { Objects.requireNonNull(query, "query"); From a67e9857df5c80ebd8c21cb73d7abdc197dbc4f1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:00:23 +0200 Subject: [PATCH 056/534] refactor(examples): add simple bodies as physics rows Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index ba9d2060..0c380283 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -29,6 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodySpawnRequests; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; @@ -123,6 +124,30 @@ public static PhysicsStoreRequestFenceHandle enqueuePhysicsStoreRequestsFenced( Math.max(0L, submittedServerTick)); } + @Nonnull + public static Ref addPhysicsStoreBody(@Nonnull World world, + @Nonnull BodyUpsertRequest request) { + Objects.requireNonNull(request, "request"); + Store store = physicsStore(world); + return store.addEntity(PhysicsStoreEntities.bodyHolder(store, + request.bodyUuid(), + request.body(), + request.dynamics(), + request.target(), + request.collider(), + request.shape(), + request.material(), + request.filter()), AddReason.SPAWN); + } + + public static void addPhysicsStoreBodies(@Nonnull World world, + @Nonnull Iterable requests) { + Objects.requireNonNull(requests, "requests"); + for (BodyUpsertRequest request : requests) { + addPhysicsStoreBody(world, request); + } + } + @Nullable public static SpaceId spaceId(@Nonnull CommandContext ctx, @Nonnull PhysicsWorldResource resource, @@ -268,7 +293,7 @@ private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store Date: Sun, 14 Jun 2026 12:09:10 +0200 Subject: [PATCH 057/534] refactor(examples): drive force demo with body commands Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 42 +++++++-- .../examples/commands/ForcesCommand.java | 92 +++++++++++++------ 2 files changed, 97 insertions(+), 37 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 0c380283..463bb3e3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -30,6 +30,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodySpawnRequests; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; @@ -127,8 +128,31 @@ public static PhysicsStoreRequestFenceHandle enqueuePhysicsStoreRequestsFenced( @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyUpsertRequest request) { - Objects.requireNonNull(request, "request"); + return addPhysicsStoreBody(physicsStore(world), request); + } + + @Nonnull + public static Ref addPhysicsStoreBody(@Nonnull World world, + @Nonnull BodyUpsertRequest request, + @Nonnull BodyCommandComponent command) { Store store = physicsStore(world); + Ref bodyRef = addPhysicsStoreBody(store, request); + appendPhysicsStoreBodyCommand(store, bodyRef, command); + return bodyRef; + } + + public static void addPhysicsStoreBodies(@Nonnull World world, + @Nonnull Iterable requests) { + Objects.requireNonNull(requests, "requests"); + for (BodyUpsertRequest request : requests) { + addPhysicsStoreBody(world, request); + } + } + + @Nonnull + private static Ref addPhysicsStoreBody(@Nonnull Store store, + @Nonnull BodyUpsertRequest request) { + Objects.requireNonNull(request, "request"); return store.addEntity(PhysicsStoreEntities.bodyHolder(store, request.bodyUuid(), request.body(), @@ -140,12 +164,16 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, request.filter()), AddReason.SPAWN); } - public static void addPhysicsStoreBodies(@Nonnull World world, - @Nonnull Iterable requests) { - Objects.requireNonNull(requests, "requests"); - for (BodyUpsertRequest request : requests) { - addPhysicsStoreBody(world, request); - } + public static void appendPhysicsStoreBodyCommand(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull BodyCommandComponent command) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(bodyRef, "bodyRef"); + Objects.requireNonNull(command, "command"); + BodyCommandComponent existing = store.getComponent(bodyRef, + BodyCommandComponent.getComponentType()); + BodyCommandComponent merged = existing != null ? existing.append(command) : command; + store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); } @Nullable diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index 1ef2a4b4..8dd414b2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -14,14 +14,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -110,41 +107,76 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, return null; } - List requests = new ArrayList<>(8); - PendingBlockBody central = spawnBox(requests, spaceUuid, spaceId, centralPosition); - requests.add(BodyForceRequest.impulse(central.bodyKey().value(), 4.0f, 2.0f, 0.0f)); - PendingBlockBody offCenter = spawnBox(requests, spaceUuid, spaceId, offCenterPosition); - requests.add(BodyForceRequest.impulseAt(offCenter.bodyKey().value(), - 3.5f, - 0.0f, - 0.0f, - 0.0f, - 0.5f, - 0.5f)); - PendingBlockBody torque = spawnBox(requests, spaceUuid, spaceId, torquePosition); - requests.add(BodyForceRequest.torqueImpulse(torque.bodyKey().value(), 0.0f, 0.0f, 8.0f)); - PendingBlockBody force = spawnBox(requests, spaceUuid, spaceId, forcePosition); - requests.add(BodyForceRequest.force(force.bodyKey().value(), 30.0f, 0.0f, 0.0f)); try { - ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); + PendingBlockBody central = spawnBox(world, + spaceUuid, + spaceId, + centralPosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, + 4.0f, + 2.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f)); + PendingBlockBody offCenter = spawnBox(world, + spaceUuid, + spaceId, + offCenterPosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, + 3.5f, + 0.0f, + 0.0f, + true, + 0.0f, + 0.5f, + 0.5f)); + PendingBlockBody torque = spawnBox(world, + spaceUuid, + spaceId, + torquePosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.TORQUE_IMPULSE, + 0.0f, + 0.0f, + 8.0f, + false, + 0.0f, + 0.0f, + 0.0f)); + PendingBlockBody force = spawnBox(world, + spaceUuid, + spaceId, + forcePosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.FORCE, + 30.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f)); + return new ForceDemoBodies(central, offCenter, torque, force); } catch (IllegalStateException exception) { return null; } - return new ForceDemoBodies(central, offCenter, torque, force); } - private static PendingBlockBody spawnBox(@Nonnull List requests, + private static PendingBlockBody spawnBox(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, - @Nonnull Vector3d position) { + @Nonnull Vector3d position, + @Nonnull BodyCommandComponent command) { RigidBodyKey bodyKey = RigidBodyKey.random(); - requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, - bodyKey.value(), - ExamplePhysicsUtils.toVector3f(position), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - RigidBodySpawnSettings.material(0.5f, 0.25f), - null)); + ExamplePhysicsUtils.addPhysicsStoreBody(world, + ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyKey.value(), + ExamplePhysicsUtils.toVector3f(position), + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 1.0f, + RigidBodySpawnSettings.material(0.5f, 0.25f), + null), + command); return new PendingBlockBody(bodyKey, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, From fba63d175781d3082bfaf47c2763ffb43f320235 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:10:19 +0200 Subject: [PATCH 058/534] refactor(examples): spawn explosion fragments as body rows Signed-off-by: Blovien --- .../explosive/ExplosiveBlockRuntime.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 0432909b..b104ab68 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -20,8 +20,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -147,24 +146,31 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e Vector3f centerF = toVector3f(center); List pending = new ArrayList<>(groups.size()); - List requests = new ArrayList<>(groups.size() * 2); for (FragmentGroup group : groups) { RigidBodyKey bodyKey = RigidBodyKey.random(); UUID bodyUuid = bodyKey.value(); Vector3d groupCenter = group.center(); - requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, - bodyUuid, - toVector3f(groupCenter), - group.shape(), - group.mass(), - FRAGMENT_SETTINGS, - null)); Vector3f impulse = ExplosiveBlockPolicy.outwardImpulse(centerF, toVector3f(groupCenter), settings.getImpulseStrength(), settings.getVerticalLift()) .mul(group.mass()); - requests.add(BodyForceRequest.impulse(bodyUuid, impulse.x, impulse.y, impulse.z)); + ExamplePhysicsUtils.addPhysicsStoreBody(world, + ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyUuid, + toVector3f(groupCenter), + group.shape(), + group.mass(), + FRAGMENT_SETTINGS, + null), + BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, + impulse.x, + impulse.y, + impulse.z, + false, + 0.0f, + 0.0f, + 0.0f)); pending.add(new PendingBlockBody(bodyKey, spaceId, group.blockType(), @@ -173,7 +179,6 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e (float) groupCenter.z, group.mass() > 0.0f)); } - ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); for (int i = 0; i < groups.size(); i++) { spawnGroupVisuals(time, fragmentSpawner, groups.get(i), pending.get(i)); From 27ced515f56ed74ec93349938ea6cbebd302f296 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:13:13 +0200 Subject: [PATCH 059/534] refactor(examples): author ecs commands through physics rows Signed-off-by: Blovien --- .../impulse/examples/commands/EcsCommand.java | 72 ++++++++++++------- .../commands/ExamplePhysicsUtils.java | 39 +++++++++- 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index 64c17db9..80b6585b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -22,11 +22,9 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -170,13 +168,23 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); Vector3d impulse = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(strength); - ExamplePhysicsUtils.enqueuePhysicsStoreRequest(world, - BodyForceRequest.impulse(hit.bodyKey().value(), + boolean applied = ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(world, + hit.bodyKey().value(), + BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, (float) impulse.x, (float) impulse.y, - (float) impulse.z)); + (float) impulse.z, + false, + 0.0f, + 0.0f, + 0.0f)); + if (!applied) { + ctx.sender().sendMessage(Message.raw("Rigid body " + hit.bodyKey() + + " is not bound in PhysicsStore.")); + return CompletableFuture.completedFuture(null); + } - ctx.sender().sendMessage(Message.raw("Queued ECS impulse request for " + ctx.sender().sendMessage(Message.raw("Queued ECS impulse command for " + hit.bodyKey() + ".")); return CompletableFuture.completedFuture(null); } @@ -214,24 +222,20 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Vector3f targetPosition = vector(spawn); - BodyUpsertRequest bodyRequest = ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, - bodyUuid, - targetPosition, - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 0.0f, - RigidBodySpawnSettings.material(0.5f, 0.2f), - null); - List requests = List.of(bodyRequest, - BodyTypeRequest.of(bodyUuid, PhysicsBodyType.KINEMATIC, true), - BodyTargetRequest.of(bodyUuid, + ExamplePhysicsUtils.addPhysicsStoreBody(world, + ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyUuid, targetPosition, - new Quaternionf(), - new Vector3f(), - new Vector3f(), - true, - false, - true)); - ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 0.0f, + RigidBodySpawnSettings.material(0.5f, 0.2f), + null), + new DynamicsComponent(PhysicsBodyType.KINEMATIC, + 0.0f, + 0.0f, + 0.0f, + false), + target(targetPosition)); TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, @@ -423,7 +427,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, RigidBodyKey bodyKey = RigidBodyKey.random(); UUID bodyUuid = bodyKey.value(); - ExamplePhysicsUtils.enqueuePhysicsStoreRequest(world, + ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, bodyUuid, vector(spawn), @@ -539,6 +543,20 @@ private static RaycastHitView raycast(@Nonnull CommandContext ctx, return hit.orElse(null); } + @Nonnull + private static TargetComponent target(@Nonnull Vector3f targetPosition) { + TargetComponent target = new TargetComponent(); + target.setActive(true); + target.setPosition(targetPosition); + target.setRotation(new Quaternionf()); + target.setLinearVelocity(new Vector3f()); + target.setAngularVelocity(new Vector3f()); + target.setTransformEnabled(true); + target.setVelocityEnabled(false); + target.setActivate(true); + return target; + } + @Nonnull private static Vector3f vector(@Nonnull Vector3d value) { return new Vector3f((float) value.x, (float) value.y, (float) value.z); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 463bb3e3..05b29589 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -21,6 +21,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -31,6 +32,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodySpawnRequests; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; @@ -141,6 +144,14 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, return bodyRef; } + @Nonnull + public static Ref addPhysicsStoreBody(@Nonnull World world, + @Nonnull BodyUpsertRequest request, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target) { + return addPhysicsStoreBody(physicsStore(world), request, dynamics, target); + } + public static void addPhysicsStoreBodies(@Nonnull World world, @Nonnull Iterable requests) { Objects.requireNonNull(requests, "requests"); @@ -153,17 +164,41 @@ public static void addPhysicsStoreBodies(@Nonnull World world, private static Ref addPhysicsStoreBody(@Nonnull Store store, @Nonnull BodyUpsertRequest request) { Objects.requireNonNull(request, "request"); + return addPhysicsStoreBody(store, request, request.dynamics(), request.target()); + } + + @Nonnull + private static Ref addPhysicsStoreBody(@Nonnull Store store, + @Nonnull BodyUpsertRequest request, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target) { + Objects.requireNonNull(request, "request"); return store.addEntity(PhysicsStoreEntities.bodyHolder(store, request.bodyUuid(), request.body(), - request.dynamics(), - request.target(), + Objects.requireNonNull(dynamics, "dynamics"), + target, request.collider(), request.shape(), request.material(), request.filter()), AddReason.SPAWN); } + public static boolean appendPhysicsStoreBodyCommand(@Nonnull World world, + @Nonnull UUID bodyUuid, + @Nonnull BodyCommandComponent command) { + Objects.requireNonNull(command, "command"); + Store store = physicsStore(world); + Ref bodyRef = store + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(Objects.requireNonNull(bodyUuid, "bodyUuid")); + if (bodyRef == null || !bodyRef.isValid()) { + return false; + } + appendPhysicsStoreBodyCommand(store, bodyRef, command); + return true; + } + public static void appendPhysicsStoreBodyCommand(@Nonnull Store store, @Nonnull Ref bodyRef, @Nonnull BodyCommandComponent command) { From 87ceca12244b4d5ad507f0191f2f2d039c2abb87 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:15:13 +0200 Subject: [PATCH 060/534] refactor(examples): add joint demo rows directly Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 11 +++ .../examples/commands/JointsCommand.java | 81 +++++++++---------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 05b29589..9dfbac20 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -33,6 +33,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; @@ -199,6 +200,16 @@ public static boolean appendPhysicsStoreBodyCommand(@Nonnull World world, return true; } + @Nonnull + public static Ref addPhysicsStoreJoint(@Nonnull World world, + @Nonnull UUID jointUuid, + @Nonnull JointComponent joint) { + Store store = physicsStore(world); + return store.addEntity(PhysicsStoreEntities.jointHolder(store, + Objects.requireNonNull(jointUuid, "jointUuid"), + joint), AddReason.SPAWN); + } + public static void appendPhysicsStoreBodyCommand(@Nonnull Store store, @Nonnull Ref bodyRef, @Nonnull BodyCommandComponent command) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index d9b54beb..eaf0fa5b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -15,8 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -97,14 +95,12 @@ private static List tryCreatePhysicsStoreDemo(@Nonnull World w } List pendingBodies = new ArrayList<>(10); - List requests = new ArrayList<>(15); - createFixed(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin)); - createPoint(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); - createHinge(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); - createSlider(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); - createSpring(pendingBodies, requests, spaceUuid, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); try { - ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); + createFixed(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin)); + createPoint(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); + createHinge(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); + createSlider(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); + createSpring(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); } catch (IllegalStateException exception) { return null; } @@ -112,53 +108,55 @@ private static List tryCreatePhysicsStoreDemo(@Nonnull World w } private static void createFixed(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey childKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, + RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey childKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); - requests.add(JointUpsertRequest.of(JointKey.random().value(), + ExamplePhysicsUtils.addPhysicsStoreJoint(world, + JointKey.random().value(), joint(spaceUuid, anchorKey, childKey, JointType.FIXED, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), - new Vector3f()))); + new Vector3f())); } private static void createPoint(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); RigidBodyKey bobKey = spawnBox(pendingBodies, - requests, + world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f, new Vector3f(1.5f, 0.0f, 0.0f)); - requests.add(JointUpsertRequest.of(JointKey.random().value(), + ExamplePhysicsUtils.addPhysicsStoreJoint(world, + JointKey.random().value(), joint(spaceUuid, anchorKey, bobKey, JointType.POINT, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), - new Vector3f()))); + new Vector3f())); } private static void createHinge(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey armKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, + RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey armKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); JointComponent joint = joint(spaceUuid, anchorKey, @@ -172,16 +170,16 @@ private static void createHinge(@Nonnull List pendingBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.5f); joint.setMotorMaxForce(3.0f); - requests.add(JointUpsertRequest.of(JointKey.random().value(), joint)); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, JointKey.random().value(), joint); } private static void createSlider(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey blockKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, + RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey blockKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(TOUCHING_SPACING, 0.0, 0.0), 1.0f); JointComponent joint = joint(spaceUuid, anchorKey, @@ -195,17 +193,17 @@ private static void createSlider(@Nonnull List pendingBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.0f); joint.setMotorMaxForce(4.0f); - requests.add(JointUpsertRequest.of(JointKey.random().value(), joint)); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, JointKey.random().value(), joint); } private static void createSpring(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, requests, spaceUuid, spaceId, origin, 0.0f); + RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); RigidBodyKey bobKey = spawnBox(pendingBodies, - requests, + world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -(TOUCHING_SPACING + SPRING_REST_LENGTH), 0.0), @@ -221,33 +219,34 @@ private static void createSpring(@Nonnull List pendingBodies, joint.setSpringRestLength(SPRING_REST_LENGTH); joint.setSpringStiffness(20.0f); joint.setSpringDamping(2.0f); - requests.add(JointUpsertRequest.of(JointKey.random().value(), joint)); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, JointKey.random().value(), joint); } private static RigidBodyKey spawnBox(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass) { - return spawnBox(pendingBodies, requests, spaceUuid, spaceId, position, mass, null); + return spawnBox(pendingBodies, world, spaceUuid, spaceId, position, mass, null); } private static RigidBodyKey spawnBox(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass, @Nullable Vector3f linearVelocity) { RigidBodyKey bodyKey = RigidBodyKey.random(); - requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, - bodyKey.value(), - ExamplePhysicsUtils.toVector3f(position), - PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), - mass, - RigidBodySpawnSettings.material(0.6f, 0.15f), - linearVelocity)); + ExamplePhysicsUtils.addPhysicsStoreBody(world, + ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyKey.value(), + ExamplePhysicsUtils.toVector3f(position), + PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), + mass, + RigidBodySpawnSettings.material(0.6f, 0.15f), + linearVelocity)); pendingBodies.add(new PendingBlockBody(bodyKey, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, From 62d0fa74c51bbcf7e0c47ea2efa012072f52ae5e Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:16:12 +0200 Subject: [PATCH 061/534] refactor(examples): add stress joints as physics rows Signed-off-by: Blovien --- .../commands/stress/StressJointsCommand.java | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index d243e24d..237d87fe 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -15,8 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -101,30 +99,28 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int createdJoints = 0; int createdBodies = 0; List pendingBodies = new ArrayList<>(totalJoints + ROWS); - List requests = new ArrayList<>(totalJoints * 2 + ROWS); int baseJointsPerRow = totalJoints / ROWS; int remainder = totalJoints % ROWS; - for (int row = 0; row < ROWS; row++) { - int rowJoints = baseJointsPerRow + (row < remainder ? 1 : 0); - if (rowJoints <= 0) { - continue; - } - - Vector3d rowOrigin = new Vector3d(origin).add(0.0, 0.0, row * ROW_SPACING); - createdBodies += appendRow(pendingBodies, - requests, - spaceUuid, - spaceId, - rowOrigin, - rowJoints, - row, - blockType); - createdJoints += rowJoints; - } try { - ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); + for (int row = 0; row < ROWS; row++) { + int rowJoints = baseJointsPerRow + (row < remainder ? 1 : 0); + if (rowJoints <= 0) { + continue; + } + + Vector3d rowOrigin = new Vector3d(origin).add(0.0, 0.0, row * ROW_SPACING); + createdBodies += appendRow(pendingBodies, + world, + spaceUuid, + spaceId, + rowOrigin, + rowJoints, + row, + blockType); + createdJoints += rowJoints; + } } catch (IllegalStateException exception) { - ctx.sender().sendMessage(Message.raw("Cannot queue stress joint demo: " + exception.getMessage())); + ctx.sender().sendMessage(Message.raw("Cannot create stress joint demo: " + exception.getMessage())); return CompletableFuture.completedFuture(null); } for (PendingBlockBody pendingBody : pendingBodies) { @@ -138,7 +134,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static int appendRow(@Nonnull List pendingBodies, - @Nonnull List requests, + @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin, @@ -163,15 +159,16 @@ private static int appendRow(@Nonnull List pendingBodies, positions[positionOffset + 1] = (float) origin.y; positions[positionOffset + 2] = (float) origin.z; float mass = i == 0 ? 0.0f : 1.0f; - requests.add(ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, - bodyKey.value(), - new Vector3f(positions[positionOffset], - positions[positionOffset + 1], - positions[positionOffset + 2]), - box, - mass, - spawnSettings, - initialVelocity(jointType, i))); + ExamplePhysicsUtils.addPhysicsStoreBody(world, + ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + bodyKey.value(), + new Vector3f(positions[positionOffset], + positions[positionOffset + 1], + positions[positionOffset + 2]), + box, + mass, + spawnSettings, + initialVelocity(jointType, i))); pendingBodies.add(new PendingBlockBody( bodyKeys[i], spaceId, @@ -182,8 +179,9 @@ private static int appendRow(@Nonnull List pendingBodies, i > 0)); } for (int i = 0; i < jointCount; i++) { - requests.add(JointUpsertRequest.of(JointKey.of(jointKeyRunId, i + 1L).value(), - joint(spaceUuid, bodyKeys[i], bodyKeys[i + 1], jointType))); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, + JointKey.of(jointKeyRunId, i + 1L).value(), + joint(spaceUuid, bodyKeys[i], bodyKeys[i + 1], jointType)); } return bodyCount; } From b5b12fe2fdd841e7a001f35be4fc20591a04e75d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:17:33 +0200 Subject: [PATCH 062/534] refactor(examples): create grab control rows directly Signed-off-by: Blovien --- .../examples/commands/GrabCommand.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 973c345f..101e840c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -28,6 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; @@ -36,10 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; @@ -175,13 +173,18 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, RigidBodyKey anchorBodyKey = RigidBodyKey.random(); JointKey controlJointKey = JointKey.random(); - List requests = new ArrayList<>(3); - requests.add(anchorBodyUpsertRequest(spaceUuid, anchorBodyKey.value(), hitPoint)); - requests.add(JointUpsertRequest.of(controlJointKey.value(), - controlJoint(spaceUuid, anchorBodyKey, selection.bodyKey(), bodyLocalHit))); - requests.add(BodyActivationRequest.wake(selection.bodyKey().value())); + boolean selectedBound = ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(world, + selection.bodyKey().value(), + BodyCommandComponent.wake()); + if (!selectedBound) { + return null; + } try { - ExamplePhysicsUtils.enqueuePhysicsStoreRequests(world, requests); + ExamplePhysicsUtils.addPhysicsStoreBody(world, + anchorBodyUpsertRequest(spaceUuid, anchorBodyKey.value(), hitPoint)); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, + controlJointKey.value(), + controlJoint(spaceUuid, anchorBodyKey, selection.bodyKey(), bodyLocalHit)); } catch (IllegalStateException exception) { return null; } From ba25235f1ea7ab615609e463ea2ca4bef8f48290 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:22:36 +0200 Subject: [PATCH 063/534] refactor(examples): measure direct body row batches Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 111 +++--------------- .../stress/StressBenchmarkCommand.java | 18 +-- .../commands/stress/StressBodiesCommand.java | 16 +-- .../stress/StressRawBodiesCommand.java | 57 ++------- 4 files changed, 45 insertions(+), 157 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 9dfbac20..f08316d0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -22,7 +22,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -37,8 +36,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; @@ -104,31 +101,6 @@ public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); } - public static void enqueuePhysicsStoreRequest(@Nonnull World world, - @Nonnull PhysicsStoreRequest request) { - physicsStore(world) - .getResource(PhysicsRequestQueueResource.getResourceType()) - .enqueue(Objects.requireNonNull(request, "request")); - } - - public static void enqueuePhysicsStoreRequests(@Nonnull World world, - @Nonnull Iterable requests) { - physicsStore(world) - .getResource(PhysicsRequestQueueResource.getResourceType()) - .enqueueAll(Objects.requireNonNull(requests, "requests")); - } - - @Nonnull - public static PhysicsStoreRequestFenceHandle enqueuePhysicsStoreRequestsFenced( - @Nonnull World world, - @Nonnull Iterable requests, - long submittedServerTick) { - return physicsStore(world) - .getResource(PhysicsRequestQueueResource.getResourceType()) - .enqueueAllFenced(Objects.requireNonNull(requests, "requests"), - Math.max(0L, submittedServerTick)); - } - @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyUpsertRequest request) { @@ -429,7 +401,7 @@ private static BodyUpsertRequest bodyUpsertRequest(@Nonnull UUID spaceUuid, } @Nonnull - public static BodyRequestBatchTiming enqueueDynamicBodyBatchMeasured(@Nonnull World world, + public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, @Nonnull SpaceId spaceId, int expectedBodies, @Nonnull PhysicsShapeSpec shape, @@ -448,47 +420,15 @@ public static BodyRequestBatchTiming enqueueDynamicBodyBatchMeasured(@Nonnull Wo persistenceMode, recipe); if (plan.isEmpty()) { - return new BodyRequestBatchTiming(0, plan.setupWallNanos(), 0L); + return new BodyRowBatchTiming(0, plan.setupWallNanos(), 0L); } - long requestStartNanos = System.nanoTime(); - enqueuePhysicsStoreRequests(world, plan.requests()); - long requestEnqueueNanos = System.nanoTime() - requestStartNanos; - return new BodyRequestBatchTiming(plan.count(), - plan.setupWallNanos(), - requestEnqueueNanos); - } - - @Nonnull - public static FencedBodyRequestBatchTiming enqueueDynamicBodyBatchFencedMeasured(@Nonnull World world, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - long submittedServerTick, - @Nonnull Consumer recipe) { - DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(world, - spaceId, - expectedBodies, - shape, - mass, - settings, - kind, - persistenceMode, - recipe); - - long requestStartNanos = System.nanoTime(); - PhysicsStoreRequestFenceHandle fence = enqueuePhysicsStoreRequestsFenced(world, - plan.requests(), - submittedServerTick); - long requestEnqueueNanos = System.nanoTime() - requestStartNanos; - return new FencedBodyRequestBatchTiming(plan.count(), + long applyStartNanos = System.nanoTime(); + addPhysicsStoreBodies(world, plan.bodies()); + long rowApplyNanos = System.nanoTime() - applyStartNanos; + return new BodyRowBatchTiming(plan.count(), plan.setupWallNanos(), - requestEnqueueNanos, - fence); + rowApplyNanos); } @Nonnull @@ -522,10 +462,10 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, + "bound in PhysicsStore: " + spaceId.value()); } - List requests = new ArrayList<>(batch.size()); + List bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { RigidBodyKey bodyKey = batch.bodyKey(i); - requests.add(bodyUpsertRequest(spaceUuid, + bodies.add(bodyUpsertRequest(spaceUuid, bodyKey.value(), new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -536,7 +476,7 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, persistenceMode)); } - return new DynamicBodyBatchPlan(requests, System.nanoTime() - setupStartNanos); + return new DynamicBodyBatchPlan(bodies, System.nanoTime() - setupStartNanos); } @Nonnull @@ -1114,44 +1054,31 @@ public record BlockBodyBatchTiming(int count, } } - public record BodyRequestBatchTiming(int count, - long setupWallNanos, - long requestEnqueueNanos) { - - public BodyRequestBatchTiming { - count = Math.max(0, count); - setupWallNanos = Math.max(0L, setupWallNanos); - requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); - } - } - - public record FencedBodyRequestBatchTiming(int count, - long setupWallNanos, - long requestEnqueueNanos, - @Nonnull PhysicsStoreRequestFenceHandle fence) { + public record BodyRowBatchTiming(int count, + long setupWallNanos, + long rowApplyNanos) { - public FencedBodyRequestBatchTiming { + public BodyRowBatchTiming { count = Math.max(0, count); setupWallNanos = Math.max(0L, setupWallNanos); - requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); - Objects.requireNonNull(fence, "fence"); + rowApplyNanos = Math.max(0L, rowApplyNanos); } } - private record DynamicBodyBatchPlan(@Nonnull List requests, + private record DynamicBodyBatchPlan(@Nonnull List bodies, long setupWallNanos) { DynamicBodyBatchPlan { - requests = List.copyOf(Objects.requireNonNull(requests, "requests")); + bodies = List.copyOf(Objects.requireNonNull(bodies, "bodies")); setupWallNanos = Math.max(0L, setupWallNanos); } private int count() { - return requests.size(); + return bodies.size(); } private boolean isEmpty() { - return requests.isEmpty(); + return bodies.isEmpty(); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index c4ac36b6..835fbcc4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -96,17 +96,17 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, }; if (timing.spawned() > 0) { - ctx.sender().sendMessage(Message.raw("Queued " + timing.spawned() + " " + ctx.sender().sendMessage(Message.raw("Added " + timing.spawned() + " " + request.mode().label() + " benchmark bodies: setupWallMs=" + millis(timing.setupWallNanos()) - + " requestEnqueueMs=" + millis(timing.requestEnqueueNanos()) + + " rowApplyMs=" + millis(timing.rowApplyNanos()) + (timing.entityAttachNanos() > 0L ? " entityAttachMs=" + millis(timing.entityAttachNanos()) : "") + " (" + microsPerBody(timing.setupWallNanos(), timing.spawned()) - + " us/body). Space bodies before enqueue: " + beforeBodies + + " us/body). Space bodies before add: " + beforeBodies + (request.mode() == BenchmarkMode.ENTITY ? ". blockType=" + request.blockType() : "") - + ". Body-count updates are visible after PhysicsStore drains the queued requests" + + ". Body-count updates are visible after PhysicsStore binds the new rows" + ". This command measures raw setup/entity attachment; use /impulse-examples stress bodies" + " for detached/detached-view scalability scenarios" + ". For clean comparisons run /impulse clean, /impulse-world-collision perf reset," @@ -145,8 +145,8 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, int count) { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - ExamplePhysicsUtils.BodyRequestBatchTiming timing = - ExamplePhysicsUtils.enqueueDynamicBodyBatchMeasured(world, + ExamplePhysicsUtils.BodyRowBatchTiming timing = + ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, spaceId, count, box, @@ -163,7 +163,7 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, }); return new BenchmarkSpawnTiming(timing.count(), timing.setupWallNanos(), - timing.requestEnqueueNanos(), + timing.rowApplyNanos(), 0L); } @@ -264,12 +264,12 @@ private record BenchmarkRequest(BenchmarkMode mode, int count, @Nonnull String b private record BenchmarkSpawnTiming(int spawned, long setupWallNanos, - long requestEnqueueNanos, + long rowApplyNanos, long entityAttachNanos) { private BenchmarkSpawnTiming { setupWallNanos = Math.max(0L, setupWallNanos); - requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); + rowApplyNanos = Math.max(0L, rowApplyNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index f1afa7f1..a26a924b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -196,8 +196,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } else { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = detachedSpawnSettings(collisionPolicy); - ExamplePhysicsUtils.BodyRequestBatchTiming batchTiming = - ExamplePhysicsUtils.enqueueDynamicBodyBatchMeasured(world, + ExamplePhysicsUtils.BodyRowBatchTiming batchTiming = + ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, spaceId, count, box, @@ -213,7 +213,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } }); timing = new StressSpawnTiming(batchTiming.setupWallNanos(), - batchTiming.requestEnqueueNanos(), + batchTiming.rowApplyNanos(), 0L); } PhysicsWorldCollisionSettings worldCollisionSettings = @@ -224,11 +224,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsCollisionLodSettings collisionLodSettings = settings.getCollisionLodSettings(); PhysicsWorldSettings worldSettings = resource.getWorldSettings(); - ctx.sender().sendMessage(Message.raw("Queued " + count + ctx.sender().sendMessage(Message.raw("Added " + count + " stress bodies: setupWallMs=" + millis(prewarmNanos + timing.setupWallNanos()) + " prewarmMs=" + millis(prewarmNanos) - + " requestEnqueueMs=" + millis(timing.requestEnqueueNanos()) + + " rowApplyMs=" + millis(timing.rowApplyNanos()) + (timing.entityAttachNanos() > 0L ? " entityAttachMs=" + millis(timing.entityAttachNanos()) : "") @@ -243,7 +243,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " visuals=" + mode.visualDescription() + (mode == StressMode.ENTITY ? " blockType=" + visualSettings.blockType() : "") + (mode.usesDetachedBodies() - ? " body-count and detached-view snapshots update after PhysicsStore drains queued requests" + ? " body-count and detached-view snapshots update after PhysicsStore binds the new rows" : "") + (mode == StressMode.DETACHED_VIEW ? " visualProxyCap=" @@ -592,12 +592,12 @@ private record StressVisualSettings(int materializationRadius, } private record StressSpawnTiming(long setupWallNanos, - long requestEnqueueNanos, + long rowApplyNanos, long entityAttachNanos) { private StressSpawnTiming { setupWallNanos = Math.max(0L, setupWallNanos); - requestEnqueueNanos = Math.max(0L, requestEnqueueNanos); + rowApplyNanos = Math.max(0L, rowApplyNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index b61e116e..a963c49e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -13,15 +13,13 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.FencedBodyRequestBatchTiming; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.BodyRowBatchTiming; import java.util.Locale; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -76,9 +74,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - long submittedServerTick = Math.max(0L, world.getTick()); long commandStartNanos = System.nanoTime(); - FencedBodyRequestBatchTiming timing = ExamplePhysicsUtils.enqueueDynamicBodyBatchFencedMeasured(world, + BodyRowBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, spaceId, count, box, @@ -86,7 +83,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, spawnSettings, PhysicsBodyKind.TEMPORARY, PhysicsBodyPersistenceMode.RUNTIME_ONLY, - submittedServerTick, spawns -> { for (int i = 0; i < count; i++) { int x = i % side; @@ -98,20 +94,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, (float) (originZ + z * SPACING)); } }); - long fenceStartNanos = System.nanoTime(); - - return timing.fence() - .completion() - .thenAccept(result -> ctx.sender().sendMessage(Message.raw(successMessage(timing, - result, - System.nanoTime() - fenceStartNanos, - System.nanoTime() - commandStartNanos)))) - .exceptionally(failure -> { - ctx.sender().sendMessage(Message.raw("Failed to drain raw physics body requests: " - + failureMessage(failure))); - return null; - }) - .toCompletableFuture(); + ctx.sender().sendMessage(Message.raw(successMessage(timing, + System.nanoTime() - commandStartNanos))); + return CompletableFuture.completedFuture(null); } private static String millis(long nanos) { @@ -119,37 +104,13 @@ private static String millis(long nanos) { } @Nonnull - private static String successMessage(@Nonnull FencedBodyRequestBatchTiming timing, - @Nonnull PhysicsStoreRequestFenceResult result, - long fenceWaitNanos, + private static String successMessage(@Nonnull BodyRowBatchTiming timing, long totalWallNanos) { - return "PhysicsStore drained raw body requests for " + timing.count() + return "PhysicsStore added raw body rows for " + timing.count() + " physics-only bodies: setupWallMs=" + millis(timing.setupWallNanos()) - + " requestEnqueueMs=" + millis(timing.requestEnqueueNanos()) - + " fenceWaitMs=" + millis(fenceWaitNanos) + + " rowApplyMs=" + millis(timing.rowApplyNanos()) + " totalWallMs=" + millis(totalWallNanos) - + " accepted=" + result.acceptedCount() - + " applied=" + result.appliedCount() - + " softSkipped=" + result.softSkippedCount() - + " rejected=" + result.rejectedCount() - + " failed=" + result.failedCount() - + " submittedTick=" + result.submittedServerTick() - + " consumedTick=" + result.consumedServerTick() - + " consumedTickLatency=" + result.consumedServerTickLatency() - + " allApplied=" + result.allApplied() - + " hasProblems=" + result.hasProblems() - + " fence=" + result.fenceUuid() - + ". This reports request drain/application only."; - } - - @Nonnull - private static String failureMessage(@Nonnull Throwable failure) { - Throwable unwrapped = failure instanceof CompletionException && failure.getCause() != null - ? failure.getCause() - : failure; - return unwrapped.getMessage() != null - ? unwrapped.getMessage() - : unwrapped.getClass().getSimpleName(); + + ". Body-count updates are visible after PhysicsStore binds the new rows."; } } From 66fa39886bd2762e3f685408c706ba925aa42946 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:27:06 +0200 Subject: [PATCH 064/534] refactor(control): mutate physics rows for control sessions Signed-off-by: Blovien --- .../systems/PhysicsControlSessionCleanup.java | 2 +- .../PhysicsKinematicControlSystem.java | 87 +++++--- .../PhysicsStoreControlSessionMutations.java | 202 ++++++++++++++++++ .../PhysicsStoreControlSessionRequests.java | 85 -------- .../control/PhysicsControlSessions.java | 4 +- 5 files changed, 257 insertions(+), 123 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java index e74b917b..1426527f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java @@ -36,7 +36,7 @@ private static void cleanupInternal(@Nonnull Store store, resource.clearControlledBody(bodyKey); } - PhysicsStoreControlSessionRequests.enqueueRelease(store, session); + PhysicsStoreControlSessionMutations.applyRelease(store, session); session.deactivate(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index a4495143..9a24f229 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -22,12 +22,11 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Collections; @@ -56,7 +55,7 @@ public class PhysicsKinematicControlSystem extends EntityTickingSystem scratch = ThreadLocal.withInitial(Scratch::new); private static final Vector3f ZERO_VELOCITY = new Vector3f(); private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); - // Anchor updates are copied PhysicsStore requests; avoid resubmitting unchanged targets. + // Anchor updates are copied into PhysicsStore rows; avoid rewriting unchanged targets. @Nonnull private static final Map, ControlMutationState> STATES_BY_STORE = Collections.synchronizedMap(new WeakHashMap<>()); @@ -153,8 +152,8 @@ public void tick(float dt, PhysicsStoreControlTargets physicsStoreTargets = resolvePhysicsStoreTargets(store, bodyKey, anchorBodyKey); if (physicsStoreTargets != null) { - physicsStoreTargets.enqueue(readyUpdate); - state.trackSubmittedRequest(anchorBodyKey, readyUpdate); + physicsStoreTargets.apply(readyUpdate); + state.trackSubmittedMutation(anchorBodyKey, readyUpdate); return; } @@ -175,23 +174,28 @@ private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( PhysicsIdentityIndexResource.getResourceType()); UUID bodyUuid = bodyKey.value(); UUID anchorBodyUuid = anchorBodyKey.value(); - if (!hasPhysicsStoreBody(physics, identity, bodyUuid) - || !hasPhysicsStoreBody(physics, identity, anchorBodyUuid)) { + Ref bodyRef = bodyRef(physics, identity, bodyUuid); + Ref anchorBodyRef = bodyRef(physics, identity, anchorBodyUuid); + if (bodyRef == null || anchorBodyRef == null) { return null; } return new PhysicsStoreControlTargets( - physics.getResource(PhysicsRequestQueueResource.getResourceType()), - bodyUuid, - anchorBodyUuid); + physics, + bodyRef, + anchorBodyRef); } - private static boolean hasPhysicsStoreBody(@Nonnull Store physics, + @Nullable + private static Ref bodyRef(@Nonnull Store physics, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull UUID bodyUuid) { Ref ref = identity.getByUuid(bodyUuid); - return ref != null + if (ref != null && ref.isValid() - && physics.getComponent(ref, BodyComponent.getComponentType()) != null; + && physics.getComponent(ref, BodyComponent.getComponentType()) != null) { + return ref; + } + return null; } private float eyeHeight(@Nonnull ArchetypeChunk chunk, @@ -261,27 +265,40 @@ record ControlAnchorUpdate(@Nonnull RigidBodyKey bodyKey, } } - private record PhysicsStoreControlTargets(@Nonnull PhysicsRequestQueueResource queue, - @Nonnull UUID bodyUuid, - @Nonnull UUID anchorBodyUuid) { - - private void enqueue(@Nonnull ControlAnchorUpdate update) { - queue.enqueue(BodyTargetRequest.of(anchorBodyUuid, - update.target(), - IDENTITY_ROTATION, - update.releaseVelocity(), - ZERO_VELOCITY, - true, - true, - true)); - queue.enqueue(BodyTargetRequest.of(bodyUuid, - update.target(), - IDENTITY_ROTATION, - ZERO_VELOCITY, - ZERO_VELOCITY, - false, - false, - true)); + private record PhysicsStoreControlTargets(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull Ref anchorBodyRef) { + + private void apply(@Nonnull ControlAnchorUpdate update) { + store.putComponent(anchorBodyRef, + TargetComponent.getComponentType(), + target(update.target(), + update.releaseVelocity(), + true, + true)); + store.putComponent(bodyRef, + TargetComponent.getComponentType(), + target(update.target(), + ZERO_VELOCITY, + false, + false)); + } + + @Nonnull + private static TargetComponent target(@Nonnull Vector3f position, + @Nonnull Vector3f linearVelocity, + boolean transformEnabled, + boolean velocityEnabled) { + TargetComponent target = new TargetComponent(); + target.setActive(true); + target.setPosition(position); + target.setRotation(IDENTITY_ROTATION); + target.setLinearVelocity(linearVelocity); + target.setAngularVelocity(ZERO_VELOCITY); + target.setTransformEnabled(transformEnabled); + target.setVelocityEnabled(velocityEnabled); + target.setActivate(true); + return target; } } @@ -301,7 +318,7 @@ synchronized ControlAnchorUpdate selectReadyUpdate(@Nonnull RigidBodyKey bodyKey return currentUpdate; } - synchronized void trackSubmittedRequest(@Nonnull RigidBodyKey bodyKey, + synchronized void trackSubmittedMutation(@Nonnull RigidBodyKey bodyKey, @Nonnull ControlAnchorUpdate submittedUpdate) { submittedUpdates.put(bodyKey, submittedUpdate); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java new file mode 100644 index 00000000..b0533492 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -0,0 +1,202 @@ +package dev.hytalemodding.impulse.core.internal.modules.control.systems; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * Direct PhysicsStore row mutations for kinematic control lifecycle cleanup. + */ +public final class PhysicsStoreControlSessionMutations { + + private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); + private static final Vector3f ZERO = new Vector3f(); + + private PhysicsStoreControlSessionMutations() { + } + + public static void applyRelease(@Nonnull Store store, + @Nonnull PhysicsControlSessionComponent session) { + Store physicsStore = + ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() + .getStore(); + PhysicsIdentityIndexResource identity = physicsStore.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsRuntimeResource runtime = physicsStore.getResource(PhysicsRuntimeResource.getResourceType()); + + JointKey controlJointKey = session.getControlJointKey(); + if (controlJointKey != null) { + removeJoint(physicsStore, identity, runtime, controlJointKey.value()); + } + + RigidBodyKey bodyKey = session.getBodyKey(); + if (bodyKey != null) { + restoreControlledBody(physicsStore, + identity, + bodyKey.value(), + session.getOriginalBodyType(), + releaseVelocity(session)); + } + + RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); + if (anchorBodyKey != null) { + removeBody(physicsStore, identity, runtime, anchorBodyKey.value()); + } + } + + private static void restoreControlledBody(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyType originalBodyType, + @Nonnull Vector3f releaseVelocity) { + Ref bodyRef = refForUuid(identity, bodyUuid); + if (bodyRef == null || store.getComponent(bodyRef, BodyComponent.getComponentType()) == null) { + return; + } + appendBodyCommand(store, bodyRef, BodyCommandComponent.setType(originalBodyType, true)); + store.putComponent(bodyRef, TargetComponent.getComponentType(), releaseTarget(releaseVelocity)); + } + + @Nonnull + private static TargetComponent releaseTarget(@Nonnull Vector3f releaseVelocity) { + TargetComponent target = new TargetComponent(); + target.setActive(true); + target.setPosition(ZERO); + target.setRotation(IDENTITY_ROTATION); + target.setLinearVelocity(releaseVelocity); + target.setAngularVelocity(ZERO); + target.setTransformEnabled(false); + target.setVelocityEnabled(true); + target.setActivate(true); + return target; + } + + private static void appendBodyCommand(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull BodyCommandComponent command) { + BodyCommandComponent existing = store.getComponent(bodyRef, + BodyCommandComponent.getComponentType()); + BodyCommandComponent merged = existing != null ? existing.append(command) : command; + store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); + } + + @Nullable + private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID uuid) { + Ref ref = identity.getByUuid(uuid); + return ref != null && ref.isValid() ? ref : null; + } + + private static void removeBody(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID bodyUuid) { + Ref ref = refForUuid(identity, bodyUuid); + removeBodyBackend(identity, runtime, bodyUuid); + removeRow(store, identity, bodyUuid, ref); + } + + private static void removeJoint(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID jointUuid) { + Ref ref = refForUuid(identity, jointUuid); + JointComponent joint = ref != null + ? store.getComponent(ref, JointComponent.getComponentType()) + : null; + removeJointBackend(identity, runtime, jointUuid, joint); + removeRow(store, identity, jointUuid, ref); + } + + private static void removeRow(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID uuid, + @Nullable Ref ref) { + if (ref == null || !ref.isValid()) { + return; + } + identity.removeUuid(uuid, ref); + store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); + } + + private static void removeBodyBackend(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID bodyUuid) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); + if (bodyHandle == null) { + return; + } + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); + } + identity.removeBodyHandle(bodyHandle); + runtime.removeBodyHandle(bodyUuid); + } + + private static void removeJointBackend(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID jointUuid, + @Nullable JointComponent joint) { + BackendJointHandle jointHandle = runtime.getJointHandle(jointUuid); + if (jointHandle == null) { + return; + } + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); + if (spaceHandle == null && joint != null) { + spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); + } + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); + } + identity.removeJointHandle(jointHandle); + runtime.removeJointHandle(jointUuid); + } + + @Nullable + private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nullable BackendSpaceHandle spaceHandle) { + if (spaceHandle == null) { + return null; + } + final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; + runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { + if (handle.value() == spaceHandle.value()) { + resolved[0] = backendRuntime; + } + }); + return resolved[0]; + } + + @Nonnull + private static Vector3f releaseVelocity(@Nonnull PhysicsControlSessionComponent session) { + if (session.getOriginalBodyType() == PhysicsBodyType.DYNAMIC) { + return session.getReleaseVelocity(); + } + return new Vector3f(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java deleted file mode 100644 index aef25e5d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionRequests.java +++ /dev/null @@ -1,85 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Copied PhysicsStore request batches for kinematic control lifecycle cleanup. - */ -public final class PhysicsStoreControlSessionRequests { - - private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); - private static final Vector3f ZERO = new Vector3f(); - - private PhysicsStoreControlSessionRequests() { - } - - public static void enqueueRelease(@Nonnull Store store, - @Nonnull PhysicsControlSessionComponent session) { - List requests = releaseRequests(session); - if (!requests.isEmpty()) { - Store physicsStore = - ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() - .getStore(); - physicsStore.getResource(PhysicsRequestQueueResource.getResourceType()) - .enqueueAll(requests); - } - } - - @Nonnull - static List releaseRequests( - @Nonnull PhysicsControlSessionComponent session) { - ArrayList requests = new ArrayList<>(4); - JointKey controlJointKey = session.getControlJointKey(); - if (controlJointKey != null) { - requests.add(JointRemoveRequest.of(controlJointKey.value())); - } - - RigidBodyKey bodyKey = session.getBodyKey(); - if (bodyKey != null) { - UUID bodyUuid = bodyKey.value(); - PhysicsBodyType originalBodyType = session.getOriginalBodyType(); - requests.add(BodyTypeRequest.of(bodyUuid, originalBodyType, true)); - requests.add(BodyTargetRequest.of(bodyUuid, - ZERO, - IDENTITY_ROTATION, - releaseVelocity(session), - ZERO, - false, - true, - true)); - } - - RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); - if (anchorBodyKey != null) { - requests.add(BodyRemoveRequest.of(anchorBodyKey.value())); - } - return requests; - } - - @Nonnull - private static Vector3f releaseVelocity(@Nonnull PhysicsControlSessionComponent session) { - if (session.getOriginalBodyType() == PhysicsBodyType.DYNAMIC) { - return session.getReleaseVelocity(); - } - return new Vector3f(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 9c7e478a..63cdab64 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionRequests; +import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; @@ -150,7 +150,7 @@ private static void releaseSession(@Nonnull PhysicsWorldRuntimeResource resource if (bodyKey != null) { resource.clearControlledBody(bodyKey); } - PhysicsStoreControlSessionRequests.enqueueRelease(store, session); + PhysicsStoreControlSessionMutations.applyRelease(store, session); session.deactivate(); store.removeComponent(controllerRef, sessionType); From 4369519d3de2505c95cb11627f31daa74c3bbf71 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:39:40 +0200 Subject: [PATCH 065/534] refactor(physicsstore): mutate space rows directly Signed-off-by: Blovien --- .../PhysicsStoreSpaceMutations.java | 172 ++++++++++++++++++ .../PhysicsWorldRuntimeResource.java | 45 ++--- 2 files changed, 189 insertions(+), 28 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java new file mode 100644 index 00000000..bb8f43d8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -0,0 +1,172 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * Direct PhysicsStore space row mutations for store-lane callers. + */ +public final class PhysicsStoreSpaceMutations { + + private PhysicsStoreSpaceMutations() { + } + + @Nonnull + public static Ref addSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId compatibilitySpaceId, + @Nonnull BackendId backendId, + @Nonnull PhysicsSpaceSettings settings) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); + Objects.requireNonNull(backendId, "backendId"); + Objects.requireNonNull(settings, "settings"); + if (backendId.value().isBlank()) { + throw new IllegalArgumentException("PhysicsStore space backend id is blank: " + + spaceUuid); + } + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + if (compatibility.getSpaceUuid(compatibilitySpaceId) != null) { + throw new IllegalArgumentException("PhysicsStore space id=" + + compatibilitySpaceId.value() + " is already registered"); + } + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + Ref existing = identity.getByUuid(spaceUuid); + if (existing != null && existing.isValid()) { + throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid + + " is already registered"); + } + Ref ref = store.addEntity(PhysicsStoreEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), + new WorldCollisionComponent(settings.getWorldCollisionSettings()), + new SolverSettingsComponent(settings.getSolverSettings()), + new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), + new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), + new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), + new ExtensionSettingsComponent(settings.getExtensionSettings())), AddReason.SPAWN); + identity.putUuid(spaceUuid, ref); + compatibility.putSpace(compatibilitySpaceId, spaceUuid); + SpaceId.reserveAtLeast(compatibilitySpaceId.value()); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(spaceUuid); + return ref; + } + + public static void putSpaceSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsSpaceSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putSpaceSettings(store, ref, spaceUuid, settings); + } + + public static void putSpaceSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsSpaceSettings settings) { + Objects.requireNonNull(settings, "settings"); + store.putComponent(ref, + WorldCollisionComponent.getComponentType(), + new WorldCollisionComponent(settings.getWorldCollisionSettings())); + PhysicsStoreEntities.putSpaceSettingsComponents(store, + ref, + new SolverSettingsComponent(settings.getSolverSettings()), + new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), + new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), + new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), + new ExtensionSettingsComponent(settings.getExtensionSettings())); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(spaceUuid); + } + + public static void removeEmptySpace(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + removeEmptySpace(store, spaceUuid); + } + + public static void removeEmptySpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + BackendSpaceHandle handle = runtime.getSpaceHandle(spaceUuid); + if (handle != null) { + BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + PhysicsBackendRuntime backendRuntime = + backendId != null ? runtime.getRuntime(backendId) : null; + if (backendRuntime == null) { + throw new IllegalStateException("PhysicsStore space backend runtime is missing: " + + spaceUuid); + } + if (backendRuntime.bodyCount(handle.value()) > 0 + || backendRuntime.jointCount(handle.value()) > 0) { + throw new IllegalStateException("PhysicsStore space is not empty: " + spaceUuid); + } + backendRuntime.destroySpace(handle.value()); + identity.removeSpaceHandle(handle); + runtime.removeSpaceHandle(spaceUuid); + } + compatibility.removeBySpaceUuid(spaceUuid); + Ref ref = identity.getByUuid(spaceUuid); + if (ref != null && ref.isValid()) { + identity.removeUuid(spaceUuid, ref); + store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); + } + } + + @Nonnull + public static UUID requireSpaceUuid(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + if (spaceUuid == null) { + throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() + + " is not registered"); + } + return spaceUuid; + } + + @Nonnull + private static Ref requireSpaceRef(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); + if (ref == null || !ref.isValid()) { + throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid + + " row is not registered"); + } + return ref; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 2a4462bd..840f9a7e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; @@ -64,10 +64,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -328,22 +324,10 @@ private static Store physicsStore(@Nonnull World world) { .getStore(); } - private static void enqueuePhysicsStoreRequest(@Nonnull Store store, - @Nonnull PhysicsStoreRequest request) { - store.getResource(PhysicsRequestQueueResource.getResourceType()) - .enqueue(Objects.requireNonNull(request, "request")); - } - @Nonnull private static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull SpaceId spaceId) { - UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); - if (spaceUuid == null) { - throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() - + " is not registered"); - } - return spaceUuid; + return PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId); } @Nullable @@ -401,8 +385,9 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( @Nonnull private IllegalStateException authoritativeFenceUnavailable(@Nonnull String operation) { return new IllegalStateException("Cannot " + operation - + " through authoritative PhysicsStore yet because request completion fences are not " - + "implemented. Use the synchronous enqueueing facade or add Worker F request fences."); + + " through authoritative PhysicsStore yet because async store-lane mutation " + + "completion is not implemented. Use the synchronous store-lane facade or add an " + + "explicit PhysicsStore scheduling completion contract."); } public void runOwnerMutation(@Nonnull String operation, @@ -488,8 +473,11 @@ public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { Impulse.getRuntimeProvider(backendId); - enqueuePhysicsStoreRequest(authoritativePhysicsStore("create physics space"), - SpaceUpsertRequest.of(UUID.randomUUID(), spaceId, backendId, settings)); + PhysicsStoreSpaceMutations.addSpace(authoritativePhysicsStore("create physics space"), + UUID.randomUUID(), + spaceId, + backendId, + settings); return spaceId; } requireLegacyMutationAllowed("create physics space"); @@ -978,9 +966,9 @@ public void removeSpace(@Nonnull SpaceId spaceId) { @Override public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { - Store physicsStore = authoritativePhysicsStore("remove physics space"); - enqueuePhysicsStoreRequest(physicsStore, - SpaceRemoveRequest.of(requireSpaceUuid(physicsStore, spaceId))); + PhysicsStoreSpaceMutations.removeEmptySpace( + authoritativePhysicsStore("remove physics space"), + spaceId); return; } requireLegacyMutationAllowed("remove physics space"); @@ -1111,9 +1099,10 @@ public PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { @Override public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { - Store physicsStore = authoritativePhysicsStore("set physics space settings"); - enqueuePhysicsStoreRequest(physicsStore, - SpaceSettingsRequest.of(requireSpaceUuid(physicsStore, spaceId), settings)); + PhysicsStoreSpaceMutations.putSpaceSettings( + authoritativePhysicsStore("set physics space settings"), + spaceId, + settings); return; } requireLegacyMutationAllowed("set physics space settings"); From 446d408d2d84bbaaae6bf523d14217046983e4a9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:43:47 +0200 Subject: [PATCH 066/534] refactor(physicsstore): remove space request dtos Signed-off-by: Blovien --- .../systems/RequestDrainSystem.java | 249 ------------------ .../requests/SpaceRemoveRequest.java | 22 -- .../requests/SpaceSettingsRequest.java | 54 ---- .../requests/SpaceUpsertRequest.java | 66 ----- 4 files changed, 391 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index 08b5e5d7..0aa8afe7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -12,16 +12,13 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource.QueuedRequest; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; @@ -37,7 +34,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; @@ -48,9 +44,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceSettingsRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.SpaceUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -92,8 +85,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsTerrainPayloadResource terrainPayloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); Set structuralConflicts = structuralConflicts(requests, restore); @@ -110,32 +101,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) fences, structuralConflicts, requests); - applySpaceRemovals(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - fences, - structuralConflicts, - requests); - applySpaceUpserts(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - fences, - structuralConflicts, - requests); - applySpaceSettings(store, - runtime, - identity, - refsThisDrain, - restore, - fences, - structuralConflicts, - requests); applyUpserts(store, systemIndex, identity, @@ -158,88 +123,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) queue.completeFences(fences.results(currentServerTick(store))); } - private static void applySpaceRemovals(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull Set structuralConflicts, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof SpaceRemoveRequest spaceRequest) { - if (structuralConflicts.contains(spaceRequest.spaceUuid())) { - fences.rejected(request); - continue; - } - trackRequest(fences, - request, - applySpaceRemove(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - spaceRequest)); - } - } - } - - private static void applySpaceUpserts(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull Set structuralConflicts, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof SpaceUpsertRequest spaceRequest) { - if (structuralConflicts.contains(spaceRequest.spaceUuid())) { - fences.rejected(request); - continue; - } - trackRequest(fences, - request, - applySpaceUpsert(store, - identity, - runtime, - compatibility, - refsThisDrain, - restore, - spaceRequest)); - } - } - } - - private static void applySpaceSettings(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull Set structuralConflicts, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof SpaceSettingsRequest settingsRequest) { - if (structuralConflicts.contains(settingsRequest.spaceUuid())) { - fences.rejected(request); - continue; - } - trackRequest(fences, - request, - applySpaceSettings(store, - runtime, - identity, - refsThisDrain, - restore, - settingsRequest)); - } - } - } - private static void applyRemovals(@Nonnull Store store, int systemIndex, @Nonnull PhysicsIdentityIndexResource identity, @@ -381,92 +264,6 @@ private static void recordUnsupported(@Nonnull PhysicsRestoreStatusResource rest } } - @Nonnull - private static RequestApplicationStatus applySpaceRemove(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull SpaceRemoveRequest request) { - UUID spaceUuid = request.spaceUuid(); - BackendSpaceHandle handle = runtime.getSpaceHandle(spaceUuid); - if (handle != null && !removeSpaceBackend(runtime, identity, restore, spaceUuid, handle)) { - return RequestApplicationStatus.SOFT_SKIPPED; - } - compatibility.removeBySpaceUuid(spaceUuid); - removeRow(store, - identity, - refsThisDrain, - new ObjectOpenHashSet<>(), - spaceUuid, - refForUuid(identity, refsThisDrain, spaceUuid)); - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static RequestApplicationStatus applySpaceUpsert(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull SpaceUpsertRequest request) { - if (isNil(request.spaceUuid())) { - restore.recordSoftSkip("Space upsert contains nil UUID: " + request.spaceUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - if (request.space().getBackendIdValue().isBlank()) { - restore.recordSoftSkip("Space upsert backend id is blank: " + request.spaceUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - if (runtime.getSpaceHandle(request.spaceUuid()) != null) { - restore.recordSoftSkip("Space upsert target is already bound: " + request.spaceUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - Ref ref = ensureRow(store, identity, refsThisDrain, request.spaceUuid()); - PhysicsStoreEntities.putSpaceComponents(store, - ref, - request.space(), - request.worldCollision(), - request.solverSettings(), - request.visualSyncSettings(), - request.visualMaterializationSettings(), - request.collisionLodSettings(), - request.extensionSettings()); - compatibility.putSpace(request.compatibilitySpaceId(), request.spaceUuid()); - SpaceId.reserveAtLeast(request.compatibilitySpaceId().value()); - runtime.markSpaceSettingsPending(request.spaceUuid()); - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static RequestApplicationStatus applySpaceSettings(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull SpaceSettingsRequest request) { - Ref ref = refForUuid(identity, refsThisDrain, request.spaceUuid()); - if (ref == null) { - restore.recordSoftSkip("Space settings request target is missing: " - + request.spaceUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - store.putComponent(ref, - WorldCollisionComponent.getComponentType(), - request.worldCollision().clone()); - PhysicsStoreEntities.putSpaceSettingsComponents(store, - ref, - request.solverSettings(), - request.visualSyncSettings(), - request.visualMaterializationSettings(), - request.collisionLodSettings(), - request.extensionSettings()); - runtime.markSpaceSettingsPending(request.spaceUuid()); - return RequestApplicationStatus.APPLIED; - } - @Nonnull private static RequestApplicationStatus applyBodyRemove(@Nonnull Store store, int systemIndex, @@ -837,31 +634,6 @@ private static void removeJointBackend(@Nonnull PhysicsRuntimeResource runtime, runtime.removeJointHandle(jointUuid); } - private static boolean removeSpaceBackend(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull UUID spaceUuid, - @Nonnull BackendSpaceHandle handle) { - BackendId backendId = runtime.getSpaceBackendId(spaceUuid); - PhysicsBackendRuntime backendRuntime = backendId != null - ? runtime.getRuntime(backendId) - : null; - if (backendRuntime == null) { - restore.recordSoftSkip("Space remove backend runtime is missing: " + spaceUuid); - return false; - } - int bodyCount = backendRuntime.bodyCount(handle.value()); - int jointCount = backendRuntime.jointCount(handle.value()); - if (bodyCount > 0 || jointCount > 0) { - restore.recordSoftSkip("Space remove target is not empty: " + spaceUuid); - return false; - } - backendRuntime.destroySpace(handle.value()); - identity.removeSpaceHandle(handle); - runtime.removeSpaceHandle(spaceUuid); - return true; - } - @Nullable private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nullable BackendSpaceHandle spaceHandle) { @@ -941,15 +713,6 @@ private static UUID structuralUuid(@Nonnull PhysicsStoreRequest request) { if (request instanceof JointRemoveRequest jointRequest) { return jointRequest.jointUuid(); } - if (request instanceof SpaceUpsertRequest spaceRequest) { - return spaceRequest.spaceUuid(); - } - if (request instanceof SpaceRemoveRequest spaceRequest) { - return spaceRequest.spaceUuid(); - } - if (request instanceof SpaceSettingsRequest spaceRequest) { - return spaceRequest.spaceUuid(); - } if (request instanceof TerrainColliderRequest terrainRequest) { return terrainRequest.terrainColliderUuid(); } @@ -961,15 +724,6 @@ private static String structuralOperation(@Nonnull PhysicsStoreRequest request) if (request instanceof TerrainColliderRequest terrainRequest) { return terrainRequest.remove() ? "terrain-remove" : "terrain-upsert"; } - if (request instanceof SpaceUpsertRequest) { - return "space-upsert"; - } - if (request instanceof SpaceRemoveRequest) { - return "space-remove"; - } - if (request instanceof SpaceSettingsRequest) { - return "space-settings"; - } return request.getClass().getName(); } @@ -1029,9 +783,6 @@ private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) || request instanceof BodyActivationRequest || request instanceof BodyForceRequest || request instanceof BodyTypeRequest - || request instanceof SpaceUpsertRequest - || request instanceof SpaceRemoveRequest - || request instanceof SpaceSettingsRequest || request instanceof TerrainColliderRequest || request instanceof BodyUpsertRequest || request instanceof BodyRemoveRequest diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java deleted file mode 100644 index 6b62a968..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceRemoveRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request that removes one empty PhysicsStore space row and backend binding. - */ -public record SpaceRemoveRequest(@Nonnull UUID requestUuid, - @Nonnull UUID spaceUuid) implements PhysicsStoreRequest { - - public SpaceRemoveRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - } - - @Nonnull - public static SpaceRemoveRequest of(@Nonnull UUID spaceUuid) { - return new SpaceRemoveRequest(UUID.randomUUID(), spaceUuid); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java deleted file mode 100644 index d3d77350..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceSettingsRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request that updates represented PhysicsStore settings for one space. - */ -public record SpaceSettingsRequest(@Nonnull UUID requestUuid, - @Nonnull UUID spaceUuid, - @Nonnull WorldCollisionComponent worldCollision, - @Nonnull SolverSettingsComponent solverSettings, - @Nonnull VisualSyncSettingsComponent visualSyncSettings, - @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, - @Nonnull CollisionLodSettingsComponent collisionLodSettings, - @Nonnull ExtensionSettingsComponent extensionSettings) - implements PhysicsStoreRequest { - - public SpaceSettingsRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - worldCollision = Objects.requireNonNull(worldCollision, "worldCollision").clone(); - solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); - visualSyncSettings = Objects.requireNonNull(visualSyncSettings, - "visualSyncSettings").clone(); - visualMaterializationSettings = Objects.requireNonNull(visualMaterializationSettings, - "visualMaterializationSettings").clone(); - collisionLodSettings = Objects.requireNonNull(collisionLodSettings, - "collisionLodSettings").clone(); - extensionSettings = Objects.requireNonNull(extensionSettings, "extensionSettings").clone(); - } - - @Nonnull - public static SpaceSettingsRequest of(@Nonnull UUID spaceUuid, - @Nonnull PhysicsSpaceSettings settings) { - Objects.requireNonNull(settings, "settings"); - return new SpaceSettingsRequest(UUID.randomUUID(), - spaceUuid, - new WorldCollisionComponent(settings.getWorldCollisionSettings()), - new SolverSettingsComponent(settings.getSolverSettings()), - new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), - new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), - new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), - new ExtensionSettingsComponent(settings.getExtensionSettings())); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java deleted file mode 100644 index 8fef2e40..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/SpaceUpsertRequest.java +++ /dev/null @@ -1,66 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Copied request that authors one PhysicsStore space row and its compatibility token. - */ -public record SpaceUpsertRequest(@Nonnull UUID requestUuid, - @Nonnull UUID spaceUuid, - @Nonnull SpaceId compatibilitySpaceId, - @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent worldCollision, - @Nonnull SolverSettingsComponent solverSettings, - @Nonnull VisualSyncSettingsComponent visualSyncSettings, - @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, - @Nonnull CollisionLodSettingsComponent collisionLodSettings, - @Nonnull ExtensionSettingsComponent extensionSettings) - implements PhysicsStoreRequest { - - public SpaceUpsertRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); - space = Objects.requireNonNull(space, "space").clone(); - worldCollision = Objects.requireNonNull(worldCollision, "worldCollision").clone(); - solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); - visualSyncSettings = Objects.requireNonNull(visualSyncSettings, - "visualSyncSettings").clone(); - visualMaterializationSettings = Objects.requireNonNull(visualMaterializationSettings, - "visualMaterializationSettings").clone(); - collisionLodSettings = Objects.requireNonNull(collisionLodSettings, - "collisionLodSettings").clone(); - extensionSettings = Objects.requireNonNull(extensionSettings, "extensionSettings").clone(); - } - - @Nonnull - public static SpaceUpsertRequest of(@Nonnull UUID spaceUuid, - @Nonnull SpaceId compatibilitySpaceId, - @Nonnull BackendId backendId, - @Nonnull PhysicsSpaceSettings settings) { - Objects.requireNonNull(settings, "settings"); - return new SpaceUpsertRequest(UUID.randomUUID(), - spaceUuid, - compatibilitySpaceId, - new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), - new WorldCollisionComponent(settings.getWorldCollisionSettings()), - new SolverSettingsComponent(settings.getSolverSettings()), - new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), - new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), - new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), - new ExtensionSettingsComponent(settings.getExtensionSettings())); - } -} From 757ae3cf1f352a3fb1ca2bba65e8795296c2b2fe Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:53:24 +0200 Subject: [PATCH 067/534] refactor(physicsstore): remove body action request dtos Signed-off-by: Blovien --- .../systems/RequestDrainSystem.java | 210 +----------------- .../requests/BodyActivationRequest.java | 34 --- .../requests/BodyForceRequest.java | 129 ----------- .../requests/BodyTargetRequest.java | 91 -------- .../requests/BodyTypeRequest.java | 28 --- 5 files changed, 1 insertion(+), 491 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index 0aa8afe7..c633b08e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -12,7 +12,6 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; @@ -25,20 +24,14 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyActivationRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyForceRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTargetRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyTypeRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; @@ -111,9 +104,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) fences, structuralConflicts, requests); - applyBodyTypeRequests(store, identity, refsThisDrain, restore, fences, requests); - applyTargetRequests(store, identity, refsThisDrain, restore, fences, requests); - enqueueRuntimeBodyRequests(store, identity, refsThisDrain, restore, fences, requests); recordUnsupported(restore, fences, requests); } catch (RuntimeException | Error exception) { fences.failUnfinished(); @@ -237,21 +227,6 @@ private static void applyUpserts(@Nonnull Store store, } } - private static void applyTargetRequests(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof BodyTargetRequest targetRequest) { - trackRequest(fences, - request, - applyTargetRequest(store, identity, refsThisDrain, restore, targetRequest)); - } - } - } - private static void recordUnsupported(@Nonnull PhysicsRestoreStatusResource restore, @Nonnull RequestFenceTracker fences, @Nonnull List requests) { @@ -367,156 +342,6 @@ private static RequestApplicationStatus applyJointUpsert(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull BodyTargetRequest request) { - Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); - if (bodyRef == null) { - restore.recordSoftSkip("Target request body is missing: " + request.bodyUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - TargetComponent target = new TargetComponent(); - target.setActive(true); - target.setPosition(request.position()); - target.setRotation(request.rotation()); - target.setLinearVelocity(request.linearVelocity()); - target.setAngularVelocity(request.angularVelocity()); - target.setTransformEnabled(request.transformEnabled()); - target.setVelocityEnabled(request.velocityEnabled()); - target.setActivate(request.activate()); - store.putComponent(bodyRef, TargetComponent.getComponentType(), target); - return RequestApplicationStatus.APPLIED; - } - - private static void applyBodyTypeRequests(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof BodyTypeRequest typeRequest) { - trackRequest(fences, - request, - applyBodyTypeRequest(store, - identity, - refsThisDrain, - restore, - typeRequest)); - } - } - } - - private static void enqueueRuntimeBodyRequests(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof BodyActivationRequest activationRequest) { - trackRequest(fences, - request, - enqueueBodyActivationRequest(store, - identity, - refsThisDrain, - restore, - activationRequest)); - continue; - } - if (request instanceof BodyForceRequest forceRequest) { - trackRequest(fences, - request, - enqueueBodyForceRequest(store, - identity, - refsThisDrain, - restore, - forceRequest)); - } - } - } - - @Nonnull - private static RequestApplicationStatus applyBodyTypeRequest(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull BodyTypeRequest request) { - Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); - if (bodyRef == null) { - restore.recordSoftSkip("Body type request body is missing: " + request.bodyUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, - bodyRef, - DynamicsComponent.getComponentType()); - DynamicsComponent updated = dynamics != null ? dynamics.clone() : new DynamicsComponent(); - updated.setBodyType(request.bodyType()); - store.putComponent(bodyRef, DynamicsComponent.getComponentType(), updated); - appendBodyCommand(store, - bodyRef, - BodyCommandComponent.setType(request.bodyType(), request.activate())); - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static RequestApplicationStatus enqueueBodyActivationRequest( - @Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull BodyActivationRequest request) { - Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); - if (bodyRef == null) { - restore.recordSoftSkip("Activation request body is missing: " + request.bodyUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - if (request.action() == BodyActivationRequest.Action.WAKE) { - appendBodyCommand(store, bodyRef, BodyCommandComponent.wake()); - } else { - appendBodyCommand(store, bodyRef, BodyCommandComponent.sleep()); - } - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static RequestApplicationStatus enqueueBodyForceRequest(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull BodyForceRequest request) { - Ref bodyRef = refForUuid(identity, refsThisDrain, request.bodyUuid()); - if (bodyRef == null) { - restore.recordSoftSkip("Force request body is missing: " + request.bodyUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, - bodyRef, - DynamicsComponent.getComponentType()); - if (dynamics == null || dynamics.getBodyType() != PhysicsBodyType.DYNAMIC) { - restore.recordSoftSkip("Force request target is not dynamic: " + request.bodyUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - if (!hasFiniteVector(request)) { - restore.recordSoftSkip("Force request contains non-finite values: " + request.bodyUuid()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - appendBodyCommand(store, - bodyRef, - BodyCommandComponent.vector(commandKind(request), - request.x(), - request.y(), - request.z(), - request.hasOffset(), - request.offsetX(), - request.offsetY(), - request.offsetZ())); - return RequestApplicationStatus.APPLIED; - } - @Nonnull private static RequestApplicationStatus applyTerrainRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @@ -727,35 +552,6 @@ private static String structuralOperation(@Nonnull PhysicsStoreRequest request) return request.getClass().getName(); } - private static boolean hasFiniteVector(@Nonnull BodyForceRequest request) { - return Float.isFinite(request.x()) - && Float.isFinite(request.y()) - && Float.isFinite(request.z()) - && (!request.hasOffset() - || (Float.isFinite(request.offsetX()) - && Float.isFinite(request.offsetY()) - && Float.isFinite(request.offsetZ()))); - } - - @Nonnull - private static BodyCommandComponent.Kind commandKind(@Nonnull BodyForceRequest request) { - return switch (request.kind()) { - case IMPULSE -> BodyCommandComponent.Kind.IMPULSE; - case TORQUE_IMPULSE -> BodyCommandComponent.Kind.TORQUE_IMPULSE; - case FORCE -> BodyCommandComponent.Kind.FORCE; - case TORQUE -> BodyCommandComponent.Kind.TORQUE; - }; - } - - private static void appendBodyCommand(@Nonnull Store store, - @Nonnull Ref bodyRef, - @Nonnull BodyCommandComponent command) { - BodyCommandComponent existing = store.getComponent(bodyRef, - BodyCommandComponent.getComponentType()); - BodyCommandComponent merged = existing != null ? existing.append(command) : command; - store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); - } - private static boolean isValidBodyUpsert(@Nonnull BodyUpsertRequest request, @Nonnull PhysicsRestoreStatusResource restore) { if (isNil(request.bodyUuid()) @@ -779,11 +575,7 @@ private static boolean isValidJointUpsert(@Nonnull JointUpsertRequest request, } private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) { - return request instanceof BodyTargetRequest - || request instanceof BodyActivationRequest - || request instanceof BodyForceRequest - || request instanceof BodyTypeRequest - || request instanceof TerrainColliderRequest + return request instanceof TerrainColliderRequest || request instanceof BodyUpsertRequest || request instanceof BodyRemoveRequest || request instanceof JointUpsertRequest diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java deleted file mode 100644 index 71f678a6..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyActivationRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request that explicitly wakes or sleeps a PhysicsStore body after backend binding. - */ -public record BodyActivationRequest(@Nonnull UUID requestUuid, - @Nonnull UUID bodyUuid, - @Nonnull Action action) implements PhysicsStoreRequest { - - public BodyActivationRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(action, "action"); - } - - @Nonnull - public static BodyActivationRequest wake(@Nonnull UUID bodyUuid) { - return new BodyActivationRequest(UUID.randomUUID(), bodyUuid, Action.WAKE); - } - - @Nonnull - public static BodyActivationRequest sleep(@Nonnull UUID bodyUuid) { - return new BodyActivationRequest(UUID.randomUUID(), bodyUuid, Action.SLEEP); - } - - public enum Action { - WAKE, - SLEEP - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java deleted file mode 100644 index 6940a1e1..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyForceRequest.java +++ /dev/null @@ -1,129 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied transient force or impulse request for a PhysicsStore body after backend binding. - */ -public record BodyForceRequest(@Nonnull UUID requestUuid, - @Nonnull UUID bodyUuid, - @Nonnull Kind kind, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ) implements PhysicsStoreRequest { - - public BodyForceRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(kind, "kind"); - } - - @Nonnull - public static BodyForceRequest impulse(@Nonnull UUID bodyUuid, float x, float y, float z) { - return new BodyForceRequest(UUID.randomUUID(), - bodyUuid, - Kind.IMPULSE, - x, - y, - z, - false, - 0.0f, - 0.0f, - 0.0f); - } - - @Nonnull - public static BodyForceRequest impulseAt(@Nonnull UUID bodyUuid, - float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - return new BodyForceRequest(UUID.randomUUID(), - bodyUuid, - Kind.IMPULSE, - x, - y, - z, - true, - offsetX, - offsetY, - offsetZ); - } - - @Nonnull - public static BodyForceRequest torqueImpulse(@Nonnull UUID bodyUuid, float x, float y, float z) { - return new BodyForceRequest(UUID.randomUUID(), - bodyUuid, - Kind.TORQUE_IMPULSE, - x, - y, - z, - false, - 0.0f, - 0.0f, - 0.0f); - } - - @Nonnull - public static BodyForceRequest force(@Nonnull UUID bodyUuid, float x, float y, float z) { - return new BodyForceRequest(UUID.randomUUID(), - bodyUuid, - Kind.FORCE, - x, - y, - z, - false, - 0.0f, - 0.0f, - 0.0f); - } - - @Nonnull - public static BodyForceRequest forceAt(@Nonnull UUID bodyUuid, - float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - return new BodyForceRequest(UUID.randomUUID(), - bodyUuid, - Kind.FORCE, - x, - y, - z, - true, - offsetX, - offsetY, - offsetZ); - } - - @Nonnull - public static BodyForceRequest torque(@Nonnull UUID bodyUuid, float x, float y, float z) { - return new BodyForceRequest(UUID.randomUUID(), - bodyUuid, - Kind.TORQUE, - x, - y, - z, - false, - 0.0f, - 0.0f, - 0.0f); - } - - public enum Kind { - IMPULSE, - TORQUE_IMPULSE, - FORCE, - TORQUE - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java deleted file mode 100644 index 2d4e7402..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTargetRequest.java +++ /dev/null @@ -1,91 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Copied kinematic target request from gameplay/control systems. - */ -public record BodyTargetRequest(@Nonnull UUID requestUuid, - @Nonnull UUID bodyUuid, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean transformEnabled, - boolean velocityEnabled, - boolean activate) implements PhysicsStoreRequest { - - public BodyTargetRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(bodyUuid, "bodyUuid"); - position = new Vector3f(Objects.requireNonNull(position, "position")); - rotation = new Quaternionf(Objects.requireNonNull(rotation, "rotation")); - linearVelocity = new Vector3f(Objects.requireNonNull(linearVelocity, "linearVelocity")); - angularVelocity = new Vector3f(Objects.requireNonNull(angularVelocity, "angularVelocity")); - } - - @Nonnull - public static BodyTargetRequest of(@Nonnull UUID bodyUuid, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - return new BodyTargetRequest(UUID.randomUUID(), - bodyUuid, - position, - rotation, - linearVelocity, - angularVelocity, - true, - true, - true); - } - - @Nonnull - public static BodyTargetRequest of(@Nonnull UUID bodyUuid, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean transformEnabled, - boolean velocityEnabled, - boolean activate) { - return new BodyTargetRequest(UUID.randomUUID(), - bodyUuid, - position, - rotation, - linearVelocity, - angularVelocity, - transformEnabled, - velocityEnabled, - activate); - } - - @Nonnull - @Override - public Vector3f position() { - return new Vector3f(position); - } - - @Nonnull - @Override - public Quaternionf rotation() { - return new Quaternionf(rotation); - } - - @Nonnull - @Override - public Vector3f linearVelocity() { - return new Vector3f(linearVelocity); - } - - @Nonnull - @Override - public Vector3f angularVelocity() { - return new Vector3f(angularVelocity); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java deleted file mode 100644 index c788fe7b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyTypeRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request that changes a PhysicsStore body's canonical motion type. - */ -public record BodyTypeRequest(@Nonnull UUID requestUuid, - @Nonnull UUID bodyUuid, - @Nonnull PhysicsBodyType bodyType, - boolean activate) implements PhysicsStoreRequest { - - public BodyTypeRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(bodyType, "bodyType"); - } - - @Nonnull - public static BodyTypeRequest of(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodyType bodyType, - boolean activate) { - return new BodyTypeRequest(UUID.randomUUID(), bodyUuid, bodyType, activate); - } -} From 7e51b9986eafe7f48d0f58de5a2e0bc0dbf88c90 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:54:46 +0200 Subject: [PATCH 068/534] fix(physicsstore): publish snapshots atomically Signed-off-by: Blovien --- .../resources/PhysicsSnapshotResource.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index 36eb22db..afbe8a5f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -18,43 +18,38 @@ public final class PhysicsSnapshotResource implements Resource { @Nonnull - private PhysicsStoreSnapshotFrame latestFrame = PhysicsStoreSnapshotFrame.EMPTY; - @Nonnull - private final Map bodiesByUuid = - new Object2ObjectOpenHashMap<>(); + private volatile PublishedSnapshot snapshot = PublishedSnapshot.EMPTY; public PhysicsSnapshotResource() { } @Nonnull public PhysicsStoreSnapshotFrame getLatestFrame() { - return latestFrame; + return snapshot.frame(); } @Nullable public PhysicsStoreBodySnapshot getBody(@Nonnull UUID bodyUuid) { - return bodiesByUuid.get(bodyUuid); + return snapshot.bodiesByUuid().get(bodyUuid); } public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { - latestFrame = frame; - bodiesByUuid.clear(); + Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); for (PhysicsStoreBodySnapshot body : frame.bodies()) { bodiesByUuid.put(body.bodyUuid(), body); } + snapshot = new PublishedSnapshot(frame, Map.copyOf(bodiesByUuid)); } public void clear() { - latestFrame = PhysicsStoreSnapshotFrame.EMPTY; - bodiesByUuid.clear(); + snapshot = PublishedSnapshot.EMPTY; } @Nonnull @Override public PhysicsSnapshotResource clone() { PhysicsSnapshotResource copy = new PhysicsSnapshotResource(); - copy.latestFrame = latestFrame; - copy.bodiesByUuid.putAll(bodiesByUuid); + copy.snapshot = snapshot; return copy; } @@ -62,4 +57,12 @@ public PhysicsSnapshotResource clone() { public static ResourceType getResourceType() { return PhysicsStoreTypes.snapshotResourceType(); } + + private record PublishedSnapshot( + @Nonnull PhysicsStoreSnapshotFrame frame, + @Nonnull Map bodiesByUuid) { + + private static final PublishedSnapshot EMPTY = + new PublishedSnapshot(PhysicsStoreSnapshotFrame.EMPTY, Map.of()); + } } From 387900bab202dad4b7860a764c0976a974bfb704 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 12:56:40 +0200 Subject: [PATCH 069/534] fix(physicsstore): complete async results off lane Signed-off-by: Blovien --- .../PhysicsStoreAsyncCompletions.java | 32 +++++++++++++++++++ .../PhysicsRequestQueueResource.java | 6 ++-- .../PhysicsStoreReadQueueResource.java | 5 +-- 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreAsyncCompletions.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreAsyncCompletions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreAsyncCompletions.java new file mode 100644 index 00000000..4ce6401f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreAsyncCompletions.java @@ -0,0 +1,32 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadFactory; +import javax.annotation.Nonnull; + +/** + * Completes public PhysicsStore futures outside the store tick lane. + */ +public final class PhysicsStoreAsyncCompletions { + + private static final ThreadFactory COMPLETION_THREADS = + Thread.ofVirtual().name("Impulse PhysicsStore completion ", 1).factory(); + + private PhysicsStoreAsyncCompletions() { + } + + public static void complete(@Nonnull CompletableFuture completion, T value) { + dispatch(() -> completion.complete(value)); + } + + public static void fail(@Nonnull CompletableFuture completion, + @Nonnull Throwable failure) { + Objects.requireNonNull(failure, "failure"); + dispatch(() -> completion.completeExceptionally(failure)); + } + + private static void dispatch(@Nonnull Runnable completion) { + COMPLETION_THREADS.newThread(Objects.requireNonNull(completion, "completion")).start(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java index 1e132dce..9cb74b6a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; @@ -57,7 +58,8 @@ public synchronized PhysicsStoreRequestFenceHandle enqueueAllFenced( count++; } if (count == 0) { - completion.complete(new PhysicsStoreRequestFenceResult(fenceUuid, + PhysicsStoreAsyncCompletions.complete(completion, + new PhysicsStoreRequestFenceResult(fenceUuid, submittedServerTick, submittedServerTick, 0, @@ -135,7 +137,7 @@ public static ResourceType getResourc private static void complete(@Nonnull Iterable completions) { for (FenceCompletion completion : completions) { - completion.completion().complete(completion.result()); + PhysicsStoreAsyncCompletions.complete(completion.completion(), completion.result()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java index d56b97ef..920eb85a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.ArrayDeque; import java.util.ArrayList; @@ -88,14 +89,14 @@ private QueuedRead(@Nonnull Function, R> read, public void complete(@Nonnull Store store) { try { - completion.complete(read.apply(store)); + PhysicsStoreAsyncCompletions.complete(completion, read.apply(store)); } catch (RuntimeException | Error exception) { fail(exception); } } public void fail(@Nonnull Throwable failure) { - completion.completeExceptionally(Objects.requireNonNull(failure, "failure")); + PhysicsStoreAsyncCompletions.fail(completion, Objects.requireNonNull(failure, "failure")); } } } From 5f7f5059763c05500731732821ae21b2be5d66fb Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 13:00:31 +0200 Subject: [PATCH 070/534] refactor(physicsstore): limit request drain to terrain Signed-off-by: Blovien --- .../systems/RequestDrainSystem.java | 349 +----------------- 1 file changed, 2 insertions(+), 347 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java index c633b08e..9f2bb604 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java @@ -1,40 +1,20 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource.QueuedRequest; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointRemoveRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.JointUpsertRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; @@ -50,12 +30,11 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.function.BiConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * Applies copied boundary requests before backend reconciliation. + * Applies copied terrain boundary requests before backend reconciliation. */ public final class RequestDrainSystem extends TickingSystem { @@ -77,7 +56,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsIdentityIndexResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsTerrainPayloadResource terrainPayloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); Set structuralConflicts = structuralConflicts(requests, restore); @@ -85,9 +63,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) try { applyRemovals(store, - systemIndex, identity, - runtime, terrainPayloads, refsThisDrain, restore, @@ -95,9 +71,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) structuralConflicts, requests); applyUpserts(store, - systemIndex, identity, - runtime, terrainPayloads, refsThisDrain, restore, @@ -114,9 +88,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } private static void applyRemovals(@Nonnull Store store, - int systemIndex, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @@ -124,31 +96,6 @@ private static void applyRemovals(@Nonnull Store store, @Nonnull Set structuralConflicts, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { - if (request instanceof BodyRemoveRequest bodyRequest) { - if (structuralConflicts.contains(bodyRequest.bodyUuid())) { - fences.rejected(request); - } else { - trackRequest(fences, - request, - applyBodyRemove(store, - systemIndex, - identity, - runtime, - refsThisDrain, - bodyRequest)); - } - continue; - } - if (request instanceof JointRemoveRequest jointRequest) { - if (structuralConflicts.contains(jointRequest.jointUuid())) { - fences.rejected(request); - } else { - trackRequest(fences, - request, - applyJointRemove(store, identity, runtime, refsThisDrain, jointRequest)); - } - continue; - } if (request instanceof TerrainColliderRequest terrainRequest && terrainRequest.remove()) { if (structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { @@ -168,9 +115,7 @@ private static void applyRemovals(@Nonnull Store store, } private static void applyUpserts(@Nonnull Store store, - int systemIndex, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @@ -178,37 +123,6 @@ private static void applyUpserts(@Nonnull Store store, @Nonnull Set structuralConflicts, @Nonnull List requests) { for (PhysicsStoreRequest request : requests) { - if (request instanceof BodyUpsertRequest bodyRequest) { - if (structuralConflicts.contains(bodyRequest.bodyUuid())) { - fences.rejected(request); - } else { - trackRequest(fences, - request, - applyBodyUpsert(store, - systemIndex, - identity, - runtime, - refsThisDrain, - restore, - bodyRequest)); - } - continue; - } - if (request instanceof JointUpsertRequest jointRequest) { - if (structuralConflicts.contains(jointRequest.jointUuid())) { - fences.rejected(request); - } else { - trackRequest(fences, - request, - applyJointUpsert(store, - identity, - runtime, - refsThisDrain, - restore, - jointRequest)); - } - continue; - } if (request instanceof TerrainColliderRequest terrainRequest && !terrainRequest.remove()) { if (structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { @@ -239,109 +153,6 @@ private static void recordUnsupported(@Nonnull PhysicsRestoreStatusResource rest } } - @Nonnull - private static RequestApplicationStatus applyBodyRemove(@Nonnull Store store, - int systemIndex, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull Map> refsThisDrain, - @Nonnull BodyRemoveRequest request) { - UUID bodyUuid = request.bodyUuid(); - List attachedJoints = collectAttachedJoints(store, systemIndex, bodyUuid); - Set removedRows = new ObjectOpenHashSet<>(); - - for (JointRow joint : attachedJoints) { - removeJointBackend(runtime, identity, joint.uuid(), joint.joint()); - removeRow(store, identity, refsThisDrain, removedRows, joint.uuid(), joint.ref()); - } - removeBodyBackend(runtime, identity, bodyUuid); - removeRow(store, - identity, - refsThisDrain, - removedRows, - bodyUuid, - refForUuid(identity, refsThisDrain, bodyUuid)); - for (UUID ownedRowUuid : request.ownedRowUuids()) { - removeRow(store, - identity, - refsThisDrain, - removedRows, - ownedRowUuid, - refForUuid(identity, refsThisDrain, ownedRowUuid)); - } - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static RequestApplicationStatus applyBodyUpsert(@Nonnull Store store, - int systemIndex, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull BodyUpsertRequest request) { - if (!isValidBodyUpsert(request, restore)) { - return RequestApplicationStatus.SOFT_SKIPPED; - } - if (runtime.getBodyHandle(request.bodyUuid()) != null) { - for (JointRow joint : collectAttachedJoints(store, systemIndex, request.bodyUuid())) { - removeJointBackend(runtime, identity, joint.uuid(), joint.joint()); - } - removeBodyBackend(runtime, identity, request.bodyUuid()); - } - Ref bodyRef = ensureRow(store, identity, refsThisDrain, request.bodyUuid()); - PhysicsStoreEntities.putBodyComponents(store, - bodyRef, - request.body(), - request.dynamics(), - request.target(), - request.collider(), - request.shape(), - request.material(), - request.filter()); - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static RequestApplicationStatus applyJointRemove(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull Map> refsThisDrain, - @Nonnull JointRemoveRequest request) { - Ref ref = refForUuid(identity, refsThisDrain, request.jointUuid()); - JointComponent joint = PhysicsStoreSystemSupport.component(store, - ref, - JointComponent.getComponentType()); - removeJointBackend(runtime, identity, request.jointUuid(), joint); - removeRow(store, - identity, - refsThisDrain, - new ObjectOpenHashSet<>(), - request.jointUuid(), - ref); - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static RequestApplicationStatus applyJointUpsert(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull JointUpsertRequest request) { - if (!isValidJointUpsert(request, restore)) { - return RequestApplicationStatus.SOFT_SKIPPED; - } - Ref ref = refForUuid(identity, refsThisDrain, request.jointUuid()); - JointComponent existing = PhysicsStoreSystemSupport.component(store, - ref, - JointComponent.getComponentType()); - removeJointBackend(runtime, identity, request.jointUuid(), existing); - Ref jointRef = ensureRow(store, identity, refsThisDrain, request.jointUuid()); - PhysicsStoreEntities.putJointComponent(store, jointRef, request.joint()); - return RequestApplicationStatus.APPLIED; - } - @Nonnull private static RequestApplicationStatus applyTerrainRequest(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @@ -391,115 +202,6 @@ private static RequestApplicationStatus applyTerrainRequest(@Nonnull Store ensureRow(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull UUID uuid) { - Ref ref = refForUuid(identity, refsThisDrain, uuid); - if (ref != null) { - return ref; - } - Holder holder = PhysicsStoreEntities.rowHolder(store, uuid); - ref = store.addEntity(holder, AddReason.SPAWN); - refsThisDrain.put(uuid, ref); - return ref; - } - - private static void removeRow(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull Set removedRows, - @Nonnull UUID uuid, - @Nullable Ref ref) { - if (!removedRows.add(uuid)) { - return; - } - refsThisDrain.remove(uuid); - if (ref == null || !ref.isValid()) { - return; - } - identity.removeUuid(uuid, ref); - store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); - } - - private static void removeBodyBackend(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID bodyUuid) { - BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); - if (bodyHandle == null) { - return; - } - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); - if (spaceHandle != null && backendRuntime != null) { - backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); - } - identity.removeBodyHandle(bodyHandle); - runtime.removeBodyHandle(bodyUuid); - } - - private static void removeJointBackend(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID jointUuid, - @Nullable JointComponent joint) { - BackendJointHandle jointHandle = runtime.getJointHandle(jointUuid); - if (jointHandle == null) { - return; - } - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); - if (spaceHandle == null && joint != null) { - spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); - } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); - if (spaceHandle != null && backendRuntime != null) { - backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); - } - identity.removeJointHandle(jointHandle); - runtime.removeJointHandle(jointUuid); - } - - @Nullable - private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nullable BackendSpaceHandle spaceHandle) { - if (spaceHandle == null) { - return null; - } - final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; - runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { - if (handle.value() == spaceHandle.value()) { - resolved[0] = backendRuntime; - } - }); - return resolved[0]; - } - - @Nonnull - private static List collectAttachedJoints(@Nonnull Store store, - int systemIndex, - @Nonnull UUID bodyUuid) { - List rows = new ArrayList<>(); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> { - for (int index = 0; index < chunk.size(); index++) { - JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); - if (joint == null - || (!bodyUuid.equals(joint.getBodyAUuid()) - && !bodyUuid.equals(joint.getBodyBUuid()))) { - continue; - } - UUID jointUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (!PhysicsStoreSystemSupport.isNil(jointUuid)) { - rows.add(new JointRow(jointUuid, - chunk.getReferenceTo(index), - joint.clone())); - } - } - }; - store.forEachChunk(systemIndex, collector); - return rows; - } - @Nonnull private static Set structuralConflicts( @Nonnull List requests, @@ -526,18 +228,6 @@ private static Set structuralConflicts( @Nullable private static UUID structuralUuid(@Nonnull PhysicsStoreRequest request) { - if (request instanceof BodyUpsertRequest bodyRequest) { - return bodyRequest.bodyUuid(); - } - if (request instanceof BodyRemoveRequest bodyRequest) { - return bodyRequest.bodyUuid(); - } - if (request instanceof JointUpsertRequest jointRequest) { - return jointRequest.jointUuid(); - } - if (request instanceof JointRemoveRequest jointRequest) { - return jointRequest.jointUuid(); - } if (request instanceof TerrainColliderRequest terrainRequest) { return terrainRequest.terrainColliderUuid(); } @@ -552,34 +242,8 @@ private static String structuralOperation(@Nonnull PhysicsStoreRequest request) return request.getClass().getName(); } - private static boolean isValidBodyUpsert(@Nonnull BodyUpsertRequest request, - @Nonnull PhysicsRestoreStatusResource restore) { - if (isNil(request.bodyUuid()) - || isNil(request.body().getSpaceUuid())) { - restore.recordSoftSkip("Body upsert contains nil UUIDs: " + request.bodyUuid()); - return false; - } - return true; - } - - private static boolean isValidJointUpsert(@Nonnull JointUpsertRequest request, - @Nonnull PhysicsRestoreStatusResource restore) { - if (isNil(request.jointUuid()) - || isNil(request.joint().getSpaceUuid()) - || isNil(request.joint().getBodyAUuid()) - || isNil(request.joint().getBodyBUuid())) { - restore.recordSoftSkip("Joint upsert contains nil UUIDs: " + request.jointUuid()); - return false; - } - return true; - } - private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) { - return request instanceof TerrainColliderRequest - || request instanceof BodyUpsertRequest - || request instanceof BodyRemoveRequest - || request instanceof JointUpsertRequest - || request instanceof JointRemoveRequest; + return request instanceof TerrainColliderRequest; } @Nonnull @@ -616,10 +280,6 @@ private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResourc return PhysicsStoreSystemSupport.refForUuid(identity, uuid); } - private static boolean isNil(@Nonnull UUID uuid) { - return PhysicsStoreSystemSupport.isNil(uuid); - } - @Nonnull private static TerrainColliderComponent activeTerrainComponent( @Nonnull TerrainColliderRequest request) { @@ -658,11 +318,6 @@ public Set> getDependencies() { return DEPENDENCIES; } - private record JointRow(@Nonnull UUID uuid, - @Nonnull Ref ref, - @Nonnull JointComponent joint) { - } - private enum RequestApplicationStatus { APPLIED, SOFT_SKIPPED From 7fd9ed2ee4239e5e6b157030084c894acd5e600a Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 13:05:34 +0200 Subject: [PATCH 071/534] refactor(physicsstore): model body rows directly Signed-off-by: Blovien --- ...ertRequest.java => BodyRowDescriptor.java} | 18 ++--- ...pawnRequests.java => PhysicsBodyRows.java} | 13 ++-- .../requests/BodyRemoveRequest.java | 31 --------- .../requests/JointUpsertRequest.java | 26 ------- .../impulse/examples/commands/EcsCommand.java | 4 +- .../commands/ExamplePhysicsUtils.java | 68 +++++++++---------- .../examples/commands/ForcesCommand.java | 2 +- .../examples/commands/GrabCommand.java | 8 +-- .../examples/commands/JointsCommand.java | 2 +- .../commands/stress/StressJointsCommand.java | 2 +- .../explosive/ExplosiveBlockRuntime.java | 2 +- 11 files changed, 57 insertions(+), 119 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{requests/BodyUpsertRequest.java => BodyRowDescriptor.java} (84%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsBodySpawnRequests.java => PhysicsBodyRows.java} (92%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyUpsertRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyRowDescriptor.java similarity index 84% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyUpsertRequest.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyRowDescriptor.java index 114859cd..86285336 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyUpsertRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyRowDescriptor.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; +package dev.hytalemodding.impulse.core.plugin.physicsstore; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -13,10 +13,9 @@ import javax.annotation.Nullable; /** - * Copied request that authors a single-body graph in PhysicsStore. + * Copied component graph for one direct PhysicsStore body row. */ -public record BodyUpsertRequest(@Nonnull UUID requestUuid, - @Nonnull UUID bodyUuid, +public record BodyRowDescriptor(@Nonnull UUID bodyUuid, @Nonnull BodyComponent body, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target, @@ -27,11 +26,9 @@ public record BodyUpsertRequest(@Nonnull UUID requestUuid, @Nonnull UUID materialUuid, @Nonnull MaterialComponent material, @Nonnull UUID filterUuid, - @Nonnull CollisionFilterComponent filter) - implements PhysicsStoreRequest { + @Nonnull CollisionFilterComponent filter) { - public BodyUpsertRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); + public BodyRowDescriptor { Objects.requireNonNull(bodyUuid, "bodyUuid"); body = Objects.requireNonNull(body, "body").clone(); dynamics = Objects.requireNonNull(dynamics, "dynamics").clone(); @@ -47,7 +44,7 @@ public record BodyUpsertRequest(@Nonnull UUID requestUuid, } @Nonnull - public static BodyUpsertRequest of(@Nonnull UUID bodyUuid, + public static BodyRowDescriptor of(@Nonnull UUID bodyUuid, @Nonnull BodyComponent body, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target, @@ -59,8 +56,7 @@ public static BodyUpsertRequest of(@Nonnull UUID bodyUuid, @Nonnull MaterialComponent material, @Nonnull UUID filterUuid, @Nonnull CollisionFilterComponent filter) { - return new BodyUpsertRequest(UUID.randomUUID(), - bodyUuid, + return new BodyRowDescriptor(bodyUuid, body, dynamics, target, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java similarity index 92% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java index 4c1ce96b..11a2e963 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodySpawnRequests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java @@ -11,7 +11,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.Objects; @@ -22,15 +21,15 @@ import org.joml.Vector3f; /** - * Factories for copied PhysicsStore body graph requests. + * Factories for direct PhysicsStore body row descriptors. */ -public final class PhysicsBodySpawnRequests { +public final class PhysicsBodyRows { - private PhysicsBodySpawnRequests() { + private PhysicsBodyRows() { } @Nonnull - public static BodyUpsertRequest dynamicBody(@Nonnull UUID spaceUuid, + public static BodyRowDescriptor dynamicBody(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -51,7 +50,7 @@ public static BodyUpsertRequest dynamicBody(@Nonnull UUID spaceUuid, } @Nonnull - public static BodyUpsertRequest body(@Nonnull UUID spaceUuid, + public static BodyRowDescriptor body(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -70,7 +69,7 @@ public static BodyUpsertRequest body(@Nonnull UUID spaceUuid, Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(persistenceMode, "persistenceMode"); - return BodyUpsertRequest.of(bodyUuid, + return BodyRowDescriptor.of(bodyUuid, new BodyComponent(spaceUuid, kind, persistenceMode), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java deleted file mode 100644 index e8ff81c0..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/BodyRemoveRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request that removes one body graph from PhysicsStore. - */ -public record BodyRemoveRequest(@Nonnull UUID requestUuid, - @Nonnull UUID bodyUuid, - @Nonnull List ownedRowUuids) implements PhysicsStoreRequest { - - public BodyRemoveRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(bodyUuid, "bodyUuid"); - ownedRowUuids = List.copyOf(Objects.requireNonNull(ownedRowUuids, "ownedRowUuids")); - } - - @Nonnull - public static BodyRemoveRequest of(@Nonnull UUID bodyUuid) { - return owned(bodyUuid, List.of()); - } - - @Nonnull - public static BodyRemoveRequest owned(@Nonnull UUID bodyUuid, - @Nonnull List ownedRowUuids) { - return new BodyRemoveRequest(UUID.randomUUID(), bodyUuid, ownedRowUuids); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java deleted file mode 100644 index 0081cf00..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointUpsertRequest.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request that authors one joint row in PhysicsStore. - */ -public record JointUpsertRequest(@Nonnull UUID requestUuid, - @Nonnull UUID jointUuid, - @Nonnull JointComponent joint) implements PhysicsStoreRequest { - - public JointUpsertRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(jointUuid, "jointUuid"); - joint = Objects.requireNonNull(joint, "joint").clone(); - } - - @Nonnull - public static JointUpsertRequest of(@Nonnull UUID jointUuid, - @Nonnull JointComponent joint) { - return new JointUpsertRequest(UUID.randomUUID(), jointUuid, joint); - } -} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index 80b6585b..f8b72019 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -223,7 +223,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } Vector3f targetPosition = vector(spawn); ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, targetPosition, PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), @@ -428,7 +428,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, RigidBodyKey bodyKey = RigidBodyKey.random(); UUID bodyUuid = bodyKey.value(); ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, vector(spawn), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index f08316d0..44651ebf 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -28,14 +28,14 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodySpawnRequests; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; @@ -103,58 +103,58 @@ public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyUpsertRequest request) { - return addPhysicsStoreBody(physicsStore(world), request); + @Nonnull BodyRowDescriptor row) { + return addPhysicsStoreBody(physicsStore(world), row); } @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyUpsertRequest request, + @Nonnull BodyRowDescriptor row, @Nonnull BodyCommandComponent command) { Store store = physicsStore(world); - Ref bodyRef = addPhysicsStoreBody(store, request); + Ref bodyRef = addPhysicsStoreBody(store, row); appendPhysicsStoreBodyCommand(store, bodyRef, command); return bodyRef; } @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyUpsertRequest request, + @Nonnull BodyRowDescriptor row, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { - return addPhysicsStoreBody(physicsStore(world), request, dynamics, target); + return addPhysicsStoreBody(physicsStore(world), row, dynamics, target); } public static void addPhysicsStoreBodies(@Nonnull World world, - @Nonnull Iterable requests) { - Objects.requireNonNull(requests, "requests"); - for (BodyUpsertRequest request : requests) { - addPhysicsStoreBody(world, request); + @Nonnull Iterable rows) { + Objects.requireNonNull(rows, "rows"); + for (BodyRowDescriptor row : rows) { + addPhysicsStoreBody(world, row); } } @Nonnull private static Ref addPhysicsStoreBody(@Nonnull Store store, - @Nonnull BodyUpsertRequest request) { - Objects.requireNonNull(request, "request"); - return addPhysicsStoreBody(store, request, request.dynamics(), request.target()); + @Nonnull BodyRowDescriptor row) { + Objects.requireNonNull(row, "row"); + return addPhysicsStoreBody(store, row, row.dynamics(), row.target()); } @Nonnull private static Ref addPhysicsStoreBody(@Nonnull Store store, - @Nonnull BodyUpsertRequest request, + @Nonnull BodyRowDescriptor row, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { - Objects.requireNonNull(request, "request"); + Objects.requireNonNull(row, "row"); return store.addEntity(PhysicsStoreEntities.bodyHolder(store, - request.bodyUuid(), - request.body(), + row.bodyUuid(), + row.body(), Objects.requireNonNull(dynamics, "dynamics"), target, - request.collider(), - request.shape(), - request.material(), - request.filter()), AddReason.SPAWN); + row.collider(), + row.shape(), + row.material(), + row.filter()), AddReason.SPAWN); } public static boolean appendPhysicsStoreBodyCommand(@Nonnull World world, @@ -340,7 +340,7 @@ private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store bodies = new ArrayList<>(batch.size()); + List bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { RigidBodyKey bodyKey = batch.bodyKey(i); - bodies.add(bodyUpsertRequest(spaceUuid, + bodies.add(bodyRow(spaceUuid, bodyKey.value(), new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -737,10 +737,10 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store requests = new ArrayList<>(batch.size()); + List rows = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { RigidBodyKey bodyKey = batch.bodyKey(i); - requests.add(bodyUpsertRequest(spaceUuid, + rows.add(bodyRow(spaceUuid, bodyKey.value(), new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -750,7 +750,7 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store bodies, + private record DynamicBodyBatchPlan(@Nonnull List bodies, long setupWallNanos) { DynamicBodyBatchPlan { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index 8dd414b2..a097e897 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -169,7 +169,7 @@ private static PendingBlockBody spawnBox(@Nonnull World world, @Nonnull BodyCommandComponent command) { RigidBodyKey bodyKey = RigidBodyKey.random(); ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceUuid, bodyKey.value(), ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 101e840c..267b5b4d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -37,7 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.BodyUpsertRequest; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; @@ -181,7 +181,7 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, } try { ExamplePhysicsUtils.addPhysicsStoreBody(world, - anchorBodyUpsertRequest(spaceUuid, anchorBodyKey.value(), hitPoint)); + anchorBodyRow(spaceUuid, anchorBodyKey.value(), hitPoint)); ExamplePhysicsUtils.addPhysicsStoreJoint(world, controlJointKey.value(), controlJoint(spaceUuid, anchorBodyKey, selection.bodyKey(), bodyLocalHit)); @@ -192,10 +192,10 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, } @Nonnull - private static BodyUpsertRequest anchorBodyUpsertRequest(@Nonnull UUID spaceUuid, + private static BodyRowDescriptor anchorBodyRow(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f hitPoint) { - return BodyUpsertRequest.of(bodyUuid, + return BodyRowDescriptor.of(bodyUuid, new BodyComponent(spaceUuid, PhysicsBodyKind.TEMPORARY, PhysicsBodyPersistenceMode.RUNTIME_ONLY), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index eaf0fa5b..4af8815b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -240,7 +240,7 @@ private static RigidBodyKey spawnBox(@Nonnull List pendingBodi @Nullable Vector3f linearVelocity) { RigidBodyKey bodyKey = RigidBodyKey.random(); ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceUuid, bodyKey.value(), ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 237d87fe..e38aa636 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -160,7 +160,7 @@ private static int appendRow(@Nonnull List pendingBodies, positions[positionOffset + 2] = (float) origin.z; float mass = i == 0 ? 0.0f : 1.0f; ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceUuid, bodyKey.value(), new Vector3f(positions[positionOffset], positions[positionOffset + 1], diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index b104ab68..a9df6a0c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -156,7 +156,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e settings.getVerticalLift()) .mul(group.mass()); ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyUpsertRequest(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, toVector3f(groupCenter), group.shape(), From f55ef4a0872d2a5c8353685e77cfebea8ae9fde2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 13:18:15 +0200 Subject: [PATCH 072/534] refactor(physicsstore): make terrain mutation queue explicit Signed-off-by: Blovien --- ... => PhysicsStoreTerrainMutationCache.java} | 24 +- ...java => PhysicsStoreTerrainMutations.java} | 22 +- ...sicsStoreWorldCollisionProducerSystem.java | 22 +- .../PhysicsStoreRegistration.java | 14 +- .../PhysicsRequestQueueResource.java | 191 -------- .../PhysicsTerrainMutationQueueResource.java | 60 +++ .../PhysicsTerrainPayloadResource.java | 2 +- .../systems/IdentityIndexSystem.java | 2 +- .../systems/RequestDrainSystem.java | 425 ------------------ .../systems/TerrainColliderBindingSystem.java | 10 +- .../systems/TerrainMutationDrainSystem.java | 232 ++++++++++ .../terrain/TerrainColliderMutation.java} | 34 +- .../terrain}/TerrainColliderPayload.java | 2 +- .../physicsstore/PhysicsStoreTypes.java | 14 +- .../requests/JointRemoveRequest.java | 22 - .../requests/PhysicsStoreRequest.java | 13 - .../PhysicsStoreRequestFenceHandle.java | 33 -- .../PhysicsStoreRequestFenceResult.java | 47 -- 18 files changed, 363 insertions(+), 806 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/{PhysicsStoreTerrainRequestCache.java => PhysicsStoreTerrainMutationCache.java} (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/{PhysicsStoreTerrainRequests.java => PhysicsStoreTerrainMutations.java} (83%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{plugin/physicsstore/requests/TerrainColliderRequest.java => internal/physicsstore/terrain/TerrainColliderMutation.java} (54%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{plugin/physicsstore/requests => internal/physicsstore/terrain}/TerrainColliderPayload.java (97%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequestCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequestCache.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java index 795d6fda..ff8f73f0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequestCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.MissingSectionReason; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -26,9 +26,9 @@ import org.joml.Vector3d; /** - * Section cache for PhysicsStore terrain request producers. + * Section cache for PhysicsStore terrain mutation producers. */ -public final class PhysicsStoreTerrainRequestCache { +public final class PhysicsStoreTerrainMutationCache { private static final int ACTIVE_BODY_STREAMING_INTERVAL_TICKS = 4; private static final int SLEEPING_BODY_STREAMING_INTERVAL_TICKS = 20; @@ -47,7 +47,7 @@ public final class PhysicsStoreTerrainRequestCache { @Nonnull public synchronized WorldVoxelCollisionCache.BuildStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsRequestQueueResource queue, + @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull Vector3d center, int radius, long tick, @@ -105,7 +105,7 @@ public synchronized WorldVoxelCollisionCache.BuildStats ensureAround(@Nonnull Wo } public synchronized int pruneUnused(@Nonnull UUID spaceUuid, - @Nonnull PhysicsRequestQueueResource queue, + @Nonnull PhysicsTerrainMutationQueueResource queue, long currentTick, int ttlTicks, @Nullable Snapshot profiling) { @@ -141,7 +141,7 @@ public synchronized int pruneUnused(@Nonnull UUID spaceUuid, public synchronized int pruneUnloaded(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsRequestQueueResource queue, + @Nonnull PhysicsTerrainMutationQueueResource queue, @Nullable Snapshot profiling) { long start = profiling != null ? System.nanoTime() : 0L; SpaceCollisionCache cache = spaces.get(spaceUuid); @@ -173,7 +173,7 @@ public synchronized int pruneUnloaded(@Nonnull World world, } public synchronized void retainSpaces(@Nonnull Set retainedSpaces, - @Nonnull PhysicsRequestQueueResource queue) { + @Nonnull PhysicsTerrainMutationQueueResource queue) { Iterator> iterator = spaces.object2ObjectEntrySet().iterator(); while (iterator.hasNext()) { @@ -302,7 +302,7 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, @Nonnull private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsRequestQueueResource queue, + @Nonnull PhysicsTerrainMutationQueueResource queue, int chunkX, int sectionY, int chunkZ, @@ -398,7 +398,7 @@ private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, buildOptions.nativeVoxelTerrainEnabled() && geometry.hasFullCubeVoxels()); int removedBodies = cached != null ? removeSection(spaceUuid, queue, cached) : 0; if (built.bodyCount > 0) { - queue.enqueue(PhysicsStoreTerrainRequests.upsert(spaceUuid, + queue.enqueue(PhysicsStoreTerrainMutations.upsert(spaceUuid, chunkX, sectionY, chunkZ, @@ -435,12 +435,12 @@ private static int bodyCount(@Nonnull SectionCollisionGeometry geometry, } private static int removeSection(@Nonnull UUID spaceUuid, - @Nonnull PhysicsRequestQueueResource queue, + @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull CachedSection section) { if (section.bodyCount <= 0) { return 0; } - queue.enqueue(PhysicsStoreTerrainRequests.remove(spaceUuid, + queue.enqueue(PhysicsStoreTerrainMutations.remove(spaceUuid, section.chunkX, section.sectionY, section.chunkZ)); @@ -448,7 +448,7 @@ private static int removeSection(@Nonnull UUID spaceUuid, } private static void removeAllSections(@Nonnull UUID spaceUuid, - @Nonnull PhysicsRequestQueueResource queue, + @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull SpaceCollisionCache cache) { for (CachedSection section : cache.sections.values()) { removeSection(spaceUuid, queue, section); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java similarity index 83% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequests.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java index 008aa248..66e7836f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainRequests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java @@ -2,26 +2,26 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.BoxPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.TerrainNeighbor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.TerrainNeighbor; import java.util.List; import java.util.UUID; import javax.annotation.Nonnull; /** - * Converts generated world-collision sections into copied PhysicsStore terrain requests. + * Converts generated world-collision sections into copied PhysicsStore terrain mutations. */ -public final class PhysicsStoreTerrainRequests { +public final class PhysicsStoreTerrainMutations { private static final int ADJACENT_SECTION_VOXEL_SHIFT = 16; - private PhysicsStoreTerrainRequests() { + private PhysicsStoreTerrainMutations() { } @Nonnull - public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, + public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, int chunkX, int sectionY, int chunkZ, @@ -29,7 +29,7 @@ public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, @Nonnull SectionCollisionGeometry geometry, @Nonnull WorldCollisionBuildOptions buildOptions) { String sourceKey = sourceKey(chunkX, sectionY, chunkZ); - return TerrainColliderRequest.upsert(spaceUuid, + return TerrainColliderMutation.upsert(spaceUuid, sourceKey, chunkX, sectionY, @@ -39,11 +39,11 @@ public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, } @Nonnull - public static TerrainColliderRequest remove(@Nonnull UUID spaceUuid, + public static TerrainColliderMutation remove(@Nonnull UUID spaceUuid, int chunkX, int sectionY, int chunkZ) { - return TerrainColliderRequest.remove(spaceUuid, + return TerrainColliderMutation.remove(spaceUuid, sourceKey(chunkX, sectionY, chunkZ), chunkX, sectionY, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index 5f70cbd2..2a48f942 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -17,14 +17,14 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainRequestCache; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainRequestCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionStreamingBounds; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; @@ -83,8 +83,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { World world = store.getExternalData().getWorld(); PhysicsStore physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore(); Store physics = physicsStore.getStore(); - PhysicsRequestQueueResource queue = physics.getResource( - PhysicsRequestQueueResource.getResourceType()); + PhysicsTerrainMutationQueueResource queue = physics.getResource( + PhysicsTerrainMutationQueueResource.getResourceType()); PhysicsWorldCollisionIndexResource worldCollisionIndex = physics.getResource( PhysicsWorldCollisionIndexResource.getResourceType()); PhysicsSnapshotResource snapshotResource = physics.getResource( @@ -102,7 +102,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { snapshot.setPlayerStreamingTargets(playerPositions.size()); } long currentTick = state.nextTick(); - PhysicsStoreTerrainRequestCache cache = state.cache(); + PhysicsStoreTerrainMutationCache cache = state.cache(); Set retainedSpaces = new ObjectOpenHashSet<>(); for (SpaceWorldCollisionSettings settings : spaces) { retainedSpaces.add(settings.spaceUuid()); @@ -132,8 +132,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } private static void processSpace(@Nonnull World world, - @Nonnull PhysicsStoreTerrainRequestCache cache, - @Nonnull PhysicsRequestQueueResource queue, + @Nonnull PhysicsStoreTerrainMutationCache cache, + @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull SpaceWorldCollisionSettings settings, @Nonnull List playerPositions, @Nonnull PhysicsStoreSnapshotFrame physicsFrame, @@ -195,7 +195,7 @@ private static void processSpace(@Nonnull World world, @Nonnull private static List collectDynamicBodyTargets( - @Nonnull PhysicsStoreTerrainRequestCache cache, + @Nonnull PhysicsStoreTerrainMutationCache cache, @Nonnull SpaceWorldCollisionSettings settings, @Nonnull PhysicsStoreSnapshotFrame physicsFrame, long currentTick, @@ -348,11 +348,11 @@ private record BodyStreamingRefresh(@Nonnull UUID bodyUuid, private static final class StreamingState { @Nonnull - private final PhysicsStoreTerrainRequestCache cache = new PhysicsStoreTerrainRequestCache(); + private final PhysicsStoreTerrainMutationCache cache = new PhysicsStoreTerrainMutationCache(); private long tick; @Nonnull - private PhysicsStoreTerrainRequestCache cache() { + private PhysicsStoreTerrainMutationCache cache() { return cache; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 1e2d7a73..31b25545 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -26,7 +26,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceCaptureSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PhysicsStoreReadRequestSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.RequestDrainSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; @@ -145,9 +145,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setSpaceCompatibilityIndexResourceType(registry.registerResource( PhysicsSpaceCompatibilityIndexResource.class, PhysicsSpaceCompatibilityIndexResource::new)); - PhysicsStoreTypes.setRequestQueueResourceType(registry.registerResource( - PhysicsRequestQueueResource.class, - PhysicsRequestQueueResource::new)); + PhysicsStoreTypes.setTerrainMutationQueueResourceType(registry.registerResource( + PhysicsTerrainMutationQueueResource.class, + PhysicsTerrainMutationQueueResource::new)); PhysicsStoreTypes.setIdentityIndexResourceType(registry.registerResource( PhysicsIdentityIndexResource.class, PhysicsIdentityIndexResource::new)); @@ -178,7 +178,7 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsDebugResource::new)); registry.registerSystem(new PersistenceHydrationSystem()); - registry.registerSystem(new RequestDrainSystem()); + registry.registerSystem(new TerrainMutationDrainSystem()); registry.registerSystem(new IdentityIndexSystem()); registry.registerSystem(new WorldCollisionIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); @@ -202,7 +202,7 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic } RuntimeException failure = null; failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsRequestQueueResource.getResourceType()).clear()); + () -> store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings()); failure = runShutdownCleanup(failure, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java deleted file mode 100644 index 9cb74b6a..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRequestQueueResource.java +++ /dev/null @@ -1,191 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; - -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Copied request queue drained by PhysicsStore.tick(). - */ -public final class PhysicsRequestQueueResource implements Resource { - - @Nonnull - private final Queue requests = new ArrayDeque<>(); - @Nonnull - private final Map pendingFences = new Object2ObjectLinkedOpenHashMap<>(); - - public PhysicsRequestQueueResource() { - } - - public synchronized void enqueue(@Nonnull PhysicsStoreRequest request) { - requests.add(QueuedRequest.unfenced(request)); - } - - public synchronized void enqueueAll(@Nonnull Iterable batch) { - Objects.requireNonNull(batch, "batch"); - for (PhysicsStoreRequest request : batch) { - requests.add(QueuedRequest.unfenced(request)); - } - } - - @Nonnull - public synchronized PhysicsStoreRequestFenceHandle enqueueAllFenced( - @Nonnull Iterable batch, - long submittedServerTick) { - Objects.requireNonNull(batch, "batch"); - UUID fenceUuid = UUID.randomUUID(); - CompletableFuture completion = new CompletableFuture<>(); - int count = 0; - for (PhysicsStoreRequest request : batch) { - requests.add(QueuedRequest.fenced(request, fenceUuid, submittedServerTick)); - count++; - } - if (count == 0) { - PhysicsStoreAsyncCompletions.complete(completion, - new PhysicsStoreRequestFenceResult(fenceUuid, - submittedServerTick, - submittedServerTick, - 0, - 0, - 0, - 0, - 0)); - } else { - pendingFences.put(fenceUuid, - new PendingFence(fenceUuid, submittedServerTick, count, completion)); - } - return new PhysicsStoreRequestFenceHandle(fenceUuid, completion.minimalCompletionStage()); - } - - @Nonnull - public synchronized List drain() { - List drained = new ArrayList<>(requests.size()); - QueuedRequest request; - while ((request = requests.poll()) != null) { - drained.add(request); - } - return drained; - } - - public void completeFences(@Nonnull Collection results) { - Objects.requireNonNull(results, "results"); - List completions = new ArrayList<>(); - synchronized (this) { - for (PhysicsStoreRequestFenceResult result : results) { - PendingFence fence = pendingFences.remove(result.fenceUuid()); - if (fence != null) { - completions.add(new FenceCompletion(fence.completion(), result)); - } - } - } - complete(completions); - } - - public synchronized int size() { - return requests.size(); - } - - public void clear() { - List completions = new ArrayList<>(); - synchronized (this) { - requests.clear(); - for (PendingFence fence : pendingFences.values()) { - completions.add(new FenceCompletion(fence.completion(), - new PhysicsStoreRequestFenceResult(fence.fenceUuid(), - fence.submittedServerTick(), - fence.submittedServerTick(), - fence.acceptedCount(), - 0, - 0, - 0, - fence.acceptedCount()))); - } - pendingFences.clear(); - } - complete(completions); - } - - @Nonnull - @Override - public synchronized PhysicsRequestQueueResource clone() { - PhysicsRequestQueueResource copy = new PhysicsRequestQueueResource(); - copy.requests.addAll(requests); - return copy; - } - - @Nonnull - public static ResourceType getResourceType() { - return PhysicsStoreTypes.requestQueueResourceType(); - } - - private static void complete(@Nonnull Iterable completions) { - for (FenceCompletion completion : completions) { - PhysicsStoreAsyncCompletions.complete(completion.completion(), completion.result()); - } - } - - public record QueuedRequest(@Nonnull PhysicsStoreRequest request, - @Nullable UUID fenceUuid, - long submittedServerTick) { - - public QueuedRequest { - Objects.requireNonNull(request, "request"); - submittedServerTick = Math.max(0L, submittedServerTick); - } - - @Nonnull - private static QueuedRequest unfenced(@Nonnull PhysicsStoreRequest request) { - return new QueuedRequest(Objects.requireNonNull(request, "request"), null, 0L); - } - - @Nonnull - private static QueuedRequest fenced(@Nonnull PhysicsStoreRequest request, - @Nonnull UUID fenceUuid, - long submittedServerTick) { - return new QueuedRequest(Objects.requireNonNull(request, "request"), - Objects.requireNonNull(fenceUuid, "fenceUuid"), - submittedServerTick); - } - } - - private record PendingFence(@Nonnull UUID fenceUuid, - long submittedServerTick, - int acceptedCount, - @Nonnull CompletableFuture - completion) { - - private PendingFence { - Objects.requireNonNull(fenceUuid, "fenceUuid"); - submittedServerTick = Math.max(0L, submittedServerTick); - acceptedCount = Math.max(0, acceptedCount); - Objects.requireNonNull(completion, "completion"); - } - } - - private record FenceCompletion( - @Nonnull CompletableFuture completion, - @Nonnull PhysicsStoreRequestFenceResult result) { - - private FenceCompletion { - Objects.requireNonNull(completion, "completion"); - Objects.requireNonNull(result, "result"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java new file mode 100644 index 00000000..88fc0b7c --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java @@ -0,0 +1,60 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import javax.annotation.Nonnull; + +/** + * Copied terrain mutation queue drained by PhysicsStore.tick(). + */ +public final class PhysicsTerrainMutationQueueResource implements Resource { + + @Nonnull + private final Queue mutations = new ArrayDeque<>(); + + public PhysicsTerrainMutationQueueResource() { + } + + public synchronized void enqueue(@Nonnull TerrainColliderMutation mutation) { + mutations.add(Objects.requireNonNull(mutation, "mutation")); + } + + @Nonnull + public synchronized List drain() { + List drained = new ArrayList<>(mutations.size()); + TerrainColliderMutation mutation; + while ((mutation = mutations.poll()) != null) { + drained.add(mutation); + } + return drained; + } + + public synchronized int size() { + return mutations.size(); + } + + public synchronized void clear() { + mutations.clear(); + } + + @Nonnull + @Override + public synchronized PhysicsTerrainMutationQueueResource clone() { + PhysicsTerrainMutationQueueResource copy = new PhysicsTerrainMutationQueueResource(); + copy.mutations.addAll(mutations); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.terrainMutationQueueResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java index 606c9f8d..9cf5931a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java @@ -3,8 +3,8 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java index a65dd8c5..a6bd16d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java @@ -24,7 +24,7 @@ public final class IdentityIndexSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, RequestDrainSystem.class) + new SystemDependency<>(Order.AFTER, TerrainMutationDrainSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java deleted file mode 100644 index 9f2bb604..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/RequestDrainSystem.java +++ /dev/null @@ -1,425 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; - -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource.QueuedRequest; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.PhysicsStoreRequestFenceResult; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Applies copied terrain boundary requests before backend reconciliation. - */ -public final class RequestDrainSystem extends TickingSystem { - - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class) - ); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsRequestQueueResource queue = store.getResource( - PhysicsRequestQueueResource.getResourceType()); - List queuedRequests = queue.drain(); - if (queuedRequests.isEmpty()) { - return; - } - List requests = requests(queuedRequests); - RequestFenceTracker fences = new RequestFenceTracker(queuedRequests); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - PhysicsRestoreStatusResource restore = store.getResource( - PhysicsRestoreStatusResource.getResourceType()); - PhysicsTerrainPayloadResource terrainPayloads = store.getResource( - PhysicsTerrainPayloadResource.getResourceType()); - Set structuralConflicts = structuralConflicts(requests, restore); - Map> refsThisDrain = new Object2ObjectOpenHashMap<>(); - - try { - applyRemovals(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - fences, - structuralConflicts, - requests); - applyUpserts(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - fences, - structuralConflicts, - requests); - recordUnsupported(restore, fences, requests); - } catch (RuntimeException | Error exception) { - fences.failUnfinished(); - queue.completeFences(fences.results(currentServerTick(store))); - throw exception; - } - queue.completeFences(fences.results(currentServerTick(store))); - } - - private static void applyRemovals(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull Set structuralConflicts, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof TerrainColliderRequest terrainRequest - && terrainRequest.remove()) { - if (structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { - fences.rejected(request); - } else { - trackRequest(fences, - request, - applyTerrainRequest(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - terrainRequest)); - } - } - } - } - - private static void applyUpserts(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull Set structuralConflicts, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (request instanceof TerrainColliderRequest terrainRequest - && !terrainRequest.remove()) { - if (structuralConflicts.contains(terrainRequest.terrainColliderUuid())) { - fences.rejected(request); - } else { - trackRequest(fences, - request, - applyTerrainRequest(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - terrainRequest)); - } - } - } - } - - private static void recordUnsupported(@Nonnull PhysicsRestoreStatusResource restore, - @Nonnull RequestFenceTracker fences, - @Nonnull List requests) { - for (PhysicsStoreRequest request : requests) { - if (!isSupportedRequest(request)) { - restore.recordSoftSkip("Unsupported PhysicsStore request " - + request.getClass().getName()); - fences.rejected(request); - } - } - } - - @Nonnull - private static RequestApplicationStatus applyTerrainRequest(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull TerrainColliderRequest request) { - UUID terrainUuid = request.terrainColliderUuid(); - Ref ref = refForUuid(identity, refsThisDrain, terrainUuid); - if (request.remove()) { - if (ref != null) { - TerrainColliderComponent existing = store.getComponent(ref, - TerrainColliderComponent.getComponentType()); - if (existing != null) { - removePayload(terrainPayloads, existing.getPayloadResourceKey()); - } - PhysicsStoreEntities.putTerrainColliderComponent(store, - ref, - removedTerrainComponent(request)); - } - removePayload(terrainPayloads, request.payloadResourceKey()); - return RequestApplicationStatus.APPLIED; - } - TerrainColliderPayload payload = request.payload(); - if (payload == null || payload.isEmpty()) { - restore.recordSoftSkip("Terrain upsert payload is missing: " + request.sourceKey()); - return RequestApplicationStatus.SOFT_SKIPPED; - } - terrainPayloads.put(request.payloadResourceKey(), payload); - TerrainColliderComponent component = activeTerrainComponent(request); - if (ref != null) { - TerrainColliderComponent existing = store.getComponent(ref, - TerrainColliderComponent.getComponentType()); - if (existing != null - && !existing.getPayloadResourceKey().equals(component.getPayloadResourceKey())) { - removePayload(terrainPayloads, existing.getPayloadResourceKey()); - } - PhysicsStoreEntities.putTerrainColliderComponent(store, ref, component); - refsThisDrain.put(terrainUuid, ref); - return RequestApplicationStatus.APPLIED; - } - refsThisDrain.put(terrainUuid, - store.addEntity(PhysicsStoreEntities.terrainColliderHolder(store, - terrainUuid, - component), - AddReason.SPAWN)); - return RequestApplicationStatus.APPLIED; - } - - @Nonnull - private static Set structuralConflicts( - @Nonnull List requests, - @Nonnull PhysicsRestoreStatusResource restore) { - Map operationsByUuid = new Object2ObjectOpenHashMap<>(); - Set conflicts = new ObjectOpenHashSet<>(); - for (PhysicsStoreRequest request : requests) { - UUID uuid = structuralUuid(request); - if (uuid == null) { - continue; - } - String operation = structuralOperation(request); - String previous = operationsByUuid.putIfAbsent(uuid, operation); - if (previous != null) { - conflicts.add(uuid); - } - } - for (UUID conflict : conflicts) { - restore.recordSoftSkip("Conflicting structural PhysicsStore requests for uuid: " - + conflict); - } - return conflicts; - } - - @Nullable - private static UUID structuralUuid(@Nonnull PhysicsStoreRequest request) { - if (request instanceof TerrainColliderRequest terrainRequest) { - return terrainRequest.terrainColliderUuid(); - } - return null; - } - - @Nonnull - private static String structuralOperation(@Nonnull PhysicsStoreRequest request) { - if (request instanceof TerrainColliderRequest terrainRequest) { - return terrainRequest.remove() ? "terrain-remove" : "terrain-upsert"; - } - return request.getClass().getName(); - } - - private static boolean isSupportedRequest(@Nonnull PhysicsStoreRequest request) { - return request instanceof TerrainColliderRequest; - } - - @Nonnull - private static List requests(@Nonnull List queuedRequests) { - List requests = new ArrayList<>(queuedRequests.size()); - for (QueuedRequest queuedRequest : queuedRequests) { - requests.add(queuedRequest.request()); - } - return requests; - } - - private static void trackRequest(@Nonnull RequestFenceTracker fences, - @Nonnull PhysicsStoreRequest request, - @Nonnull RequestApplicationStatus status) { - if (status == RequestApplicationStatus.APPLIED) { - fences.applied(request); - } else { - fences.softSkipped(request); - } - } - - private static long currentServerTick(@Nonnull Store store) { - return Math.max(0L, store.getExternalData().getWorld().getTick()); - } - - @Nullable - private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull UUID uuid) { - Ref ref = refsThisDrain.get(uuid); - if (ref != null && ref.isValid()) { - return ref; - } - return PhysicsStoreSystemSupport.refForUuid(identity, uuid); - } - - @Nonnull - private static TerrainColliderComponent activeTerrainComponent( - @Nonnull TerrainColliderRequest request) { - return terrainComponent(request, request.payloadResourceKey(), true); - } - - @Nonnull - private static TerrainColliderComponent removedTerrainComponent( - @Nonnull TerrainColliderRequest request) { - return terrainComponent(request, request.payloadResourceKey(), false); - } - - @Nonnull - private static TerrainColliderComponent terrainComponent(@Nonnull TerrainColliderRequest request, - @Nullable String payloadResourceKey, - boolean retained) { - return new TerrainColliderComponent(request.spaceUuid(), - request.sourceKey(), - request.chunkX(), - request.sectionY(), - request.chunkZ(), - payloadResourceKey != null ? payloadResourceKey : "", - retained); - } - - private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nullable String payloadResourceKey) { - if (payloadResourceKey != null && !payloadResourceKey.isBlank()) { - terrainPayloads.remove(payloadResourceKey); - } - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - - private enum RequestApplicationStatus { - APPLIED, - SOFT_SKIPPED - } - - private static final class RequestFenceTracker { - - @Nonnull - private final Map countsByFenceUuid = new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map> countsByRequest = - new IdentityHashMap<>(); - - private RequestFenceTracker(@Nonnull List queuedRequests) { - for (QueuedRequest queuedRequest : queuedRequests) { - UUID fenceUuid = queuedRequest.fenceUuid(); - if (fenceUuid == null) { - continue; - } - FenceCounts counts = countsByFenceUuid.computeIfAbsent(fenceUuid, - uuid -> new FenceCounts(uuid, queuedRequest.submittedServerTick())); - counts.accepted++; - countsByRequest.computeIfAbsent(queuedRequest.request(), - _ -> new ArrayDeque<>()).addLast(counts); - } - } - - private void applied(@Nonnull PhysicsStoreRequest request) { - FenceCounts counts = countsFor(request); - if (counts != null) { - counts.applied++; - } - } - - private void softSkipped(@Nonnull PhysicsStoreRequest request) { - FenceCounts counts = countsFor(request); - if (counts != null) { - counts.softSkipped++; - } - } - - private void rejected(@Nonnull PhysicsStoreRequest request) { - FenceCounts counts = countsFor(request); - if (counts != null) { - counts.rejected++; - } - } - - @Nullable - private FenceCounts countsFor(@Nonnull PhysicsStoreRequest request) { - Deque counts = countsByRequest.get(request); - if (counts == null) { - return null; - } - FenceCounts next = counts.pollFirst(); - if (counts.isEmpty()) { - countsByRequest.remove(request); - } - return next; - } - - private void failUnfinished() { - for (FenceCounts counts : countsByFenceUuid.values()) { - int finished = counts.applied + counts.softSkipped + counts.rejected + counts.failed; - if (finished < counts.accepted) { - counts.failed += counts.accepted - finished; - } - } - } - - @Nonnull - private Collection results(long consumedServerTick) { - List results = - new ArrayList<>(countsByFenceUuid.size()); - for (FenceCounts counts : countsByFenceUuid.values()) { - results.add(new PhysicsStoreRequestFenceResult(counts.fenceUuid, - counts.submittedServerTick, - consumedServerTick, - counts.accepted, - counts.applied, - counts.softSkipped, - counts.rejected, - counts.failed)); - } - return results; - } - } - - private static final class FenceCounts { - - @Nonnull - private final UUID fenceUuid; - private final long submittedServerTick; - private int accepted; - private int applied; - private int softSkipped; - private int rejected; - private int failed; - - private FenceCounts(@Nonnull UUID fenceUuid, long submittedServerTick) { - this.fenceUuid = fenceUuid; - this.submittedServerTick = Math.max(0L, submittedServerTick); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index c2b361c7..9a6a1a82 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -18,13 +18,13 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.TerrainNeighbor; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.BoxPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderPayload.TerrainNeighbor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.requests.TerrainColliderRequest; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -208,7 +208,7 @@ private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, return; } for (TerrainNeighbor neighbor : payload.neighbors()) { - UUID neighborUuid = TerrainColliderRequest.terrainColliderUuid(terrain.getSpaceUuid(), + UUID neighborUuid = TerrainColliderMutation.terrainColliderUuid(terrain.getSpaceUuid(), neighbor.sourceKey()); BackendBodyHandle neighborBody = runtime.getTerrainVoxelBodyHandle(neighborUuid); BackendSpaceHandle neighborSpace = runtime.getTerrainSpaceHandle(neighborUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java new file mode 100644 index 00000000..d90ecc1e --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java @@ -0,0 +1,232 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Applies copied terrain mutations before backend reconciliation. + */ +public final class TerrainMutationDrainSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsTerrainMutationQueueResource queue = store.getResource( + PhysicsTerrainMutationQueueResource.getResourceType()); + List mutations = queue.drain(); + if (mutations.isEmpty()) { + return; + } + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + PhysicsTerrainPayloadResource terrainPayloads = store.getResource( + PhysicsTerrainPayloadResource.getResourceType()); + Set structuralConflicts = structuralConflicts(mutations, restore); + Map> refsThisDrain = new Object2ObjectOpenHashMap<>(); + + applyRemovals(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + structuralConflicts, + mutations); + applyUpserts(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + structuralConflicts, + mutations); + } + + private static void applyRemovals(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set structuralConflicts, + @Nonnull List mutations) { + for (TerrainColliderMutation mutation : mutations) { + if (mutation.remove()) { + if (structuralConflicts.contains(mutation.terrainColliderUuid())) { + restore.recordSoftSkip("Conflicting terrain mutation for uuid: " + + mutation.terrainColliderUuid()); + } else { + applyTerrainMutation(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + mutation); + } + } + } + } + + private static void applyUpserts(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set structuralConflicts, + @Nonnull List mutations) { + for (TerrainColliderMutation mutation : mutations) { + if (!mutation.remove()) { + if (structuralConflicts.contains(mutation.terrainColliderUuid())) { + restore.recordSoftSkip("Conflicting terrain mutation for uuid: " + + mutation.terrainColliderUuid()); + } else { + applyTerrainMutation(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + mutation); + } + } + } + } + + private static void applyTerrainMutation(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull Map> refsThisDrain, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull TerrainColliderMutation mutation) { + UUID terrainUuid = mutation.terrainColliderUuid(); + Ref ref = refForUuid(identity, refsThisDrain, terrainUuid); + if (mutation.remove()) { + if (ref != null) { + TerrainColliderComponent existing = store.getComponent(ref, + TerrainColliderComponent.getComponentType()); + if (existing != null) { + removePayload(terrainPayloads, existing.getPayloadResourceKey()); + } + PhysicsStoreEntities.putTerrainColliderComponent(store, + ref, + removedTerrainComponent(mutation)); + } + removePayload(terrainPayloads, mutation.payloadResourceKey()); + return; + } + TerrainColliderPayload payload = mutation.payload(); + if (payload == null || payload.isEmpty()) { + restore.recordSoftSkip("Terrain upsert payload is missing: " + mutation.sourceKey()); + return; + } + terrainPayloads.put(mutation.payloadResourceKey(), payload); + TerrainColliderComponent component = activeTerrainComponent(mutation); + if (ref != null) { + TerrainColliderComponent existing = store.getComponent(ref, + TerrainColliderComponent.getComponentType()); + if (existing != null + && !existing.getPayloadResourceKey().equals(component.getPayloadResourceKey())) { + removePayload(terrainPayloads, existing.getPayloadResourceKey()); + } + PhysicsStoreEntities.putTerrainColliderComponent(store, ref, component); + refsThisDrain.put(terrainUuid, ref); + return; + } + refsThisDrain.put(terrainUuid, + store.addEntity(PhysicsStoreEntities.terrainColliderHolder(store, + terrainUuid, + component), + AddReason.SPAWN)); + } + + @Nonnull + private static Set structuralConflicts( + @Nonnull List mutations, + @Nonnull PhysicsRestoreStatusResource restore) { + Set seen = new ObjectOpenHashSet<>(); + Set conflicts = new ObjectOpenHashSet<>(); + for (TerrainColliderMutation mutation : mutations) { + UUID uuid = mutation.terrainColliderUuid(); + if (!seen.add(uuid)) { + conflicts.add(uuid); + } + } + for (UUID conflict : conflicts) { + restore.recordSoftSkip("Conflicting terrain mutations for uuid: " + conflict); + } + return conflicts; + } + + @Nullable + private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Map> refsThisDrain, + @Nonnull UUID uuid) { + Ref ref = refsThisDrain.get(uuid); + if (ref != null && ref.isValid()) { + return ref; + } + return PhysicsStoreSystemSupport.refForUuid(identity, uuid); + } + + @Nonnull + private static TerrainColliderComponent activeTerrainComponent( + @Nonnull TerrainColliderMutation mutation) { + return terrainComponent(mutation, mutation.payloadResourceKey(), true); + } + + @Nonnull + private static TerrainColliderComponent removedTerrainComponent( + @Nonnull TerrainColliderMutation mutation) { + return terrainComponent(mutation, mutation.payloadResourceKey(), false); + } + + @Nonnull + private static TerrainColliderComponent terrainComponent( + @Nonnull TerrainColliderMutation mutation, + @Nullable String payloadResourceKey, + boolean retained) { + return new TerrainColliderComponent(mutation.spaceUuid(), + mutation.sourceKey(), + mutation.chunkX(), + mutation.sectionY(), + mutation.chunkZ(), + payloadResourceKey != null ? payloadResourceKey : "", + retained); + } + + private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nullable String payloadResourceKey) { + if (payloadResourceKey != null && !payloadResourceKey.isBlank()) { + terrainPayloads.remove(payloadResourceKey); + } + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderMutation.java similarity index 54% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderMutation.java index 760fc121..569e19f1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderRequest.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderMutation.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; +package dev.hytalemodding.impulse.core.internal.physicsstore.terrain; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -7,20 +7,18 @@ import javax.annotation.Nullable; /** - * Copied terrain collider request emitted from chunk/world-collision code. + * Copied terrain collider mutation emitted from chunk/world-collision code. */ -public record TerrainColliderRequest(@Nonnull UUID requestUuid, - @Nonnull UUID spaceUuid, - @Nonnull String sourceKey, - int chunkX, - int sectionY, - int chunkZ, - @Nonnull String payloadResourceKey, - @Nullable TerrainColliderPayload payload, - boolean remove) implements PhysicsStoreRequest { +public record TerrainColliderMutation(@Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + int chunkX, + int sectionY, + int chunkZ, + @Nonnull String payloadResourceKey, + @Nullable TerrainColliderPayload payload, + boolean remove) { - public TerrainColliderRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); + public TerrainColliderMutation { Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(sourceKey, "sourceKey"); Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); @@ -32,15 +30,14 @@ public UUID terrainColliderUuid() { } @Nonnull - public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, + public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, int chunkX, int sectionY, int chunkZ, @Nonnull String payloadResourceKey, @Nonnull TerrainColliderPayload payload) { - return new TerrainColliderRequest(UUID.randomUUID(), - spaceUuid, + return new TerrainColliderMutation(spaceUuid, sourceKey, chunkX, sectionY, @@ -51,13 +48,12 @@ public static TerrainColliderRequest upsert(@Nonnull UUID spaceUuid, } @Nonnull - public static TerrainColliderRequest remove(@Nonnull UUID spaceUuid, + public static TerrainColliderMutation remove(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, int chunkX, int sectionY, int chunkZ) { - return new TerrainColliderRequest(UUID.randomUUID(), - spaceUuid, + return new TerrainColliderMutation(spaceUuid, sourceKey, chunkX, sectionY, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderPayload.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderPayload.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java index de1a1126..c239fd12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/TerrainColliderPayload.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; +package dev.hytalemodding.impulse.core.internal.physicsstore.terrain; import java.util.Arrays; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index 05ab3ff3..a5a0d942 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -6,7 +6,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRequestQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -86,7 +86,7 @@ public final class PhysicsStoreTypes { private static ResourceType spaceCompatibilityIndexResourceType; @Nullable - private static ResourceType requestQueueResourceType; + private static ResourceType terrainMutationQueueResourceType; @Nullable private static ResourceType identityIndexResourceType; @Nullable @@ -209,9 +209,9 @@ public static void setSpaceCompatibilityIndexResourceType( spaceCompatibilityIndexResourceType = Objects.requireNonNull(type, "type"); } - public static void setRequestQueueResourceType( - @Nonnull ResourceType type) { - requestQueueResourceType = Objects.requireNonNull(type, "type"); + public static void setTerrainMutationQueueResourceType( + @Nonnull ResourceType type) { + terrainMutationQueueResourceType = Objects.requireNonNull(type, "type"); } public static void setIdentityIndexResourceType( @@ -366,8 +366,8 @@ public static ResourceType runtimeResource } @Nonnull - public static ResourceType requestQueueResourceType() { - return require(requestQueueResourceType, "PhysicsRequestQueueResource"); + public static ResourceType terrainMutationQueueResourceType() { + return require(terrainMutationQueueResourceType, "PhysicsTerrainMutationQueueResource"); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java deleted file mode 100644 index e097a1ff..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/JointRemoveRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request that removes one joint row from PhysicsStore. - */ -public record JointRemoveRequest(@Nonnull UUID requestUuid, - @Nonnull UUID jointUuid) implements PhysicsStoreRequest { - - public JointRemoveRequest { - Objects.requireNonNull(requestUuid, "requestUuid"); - Objects.requireNonNull(jointUuid, "jointUuid"); - } - - @Nonnull - public static JointRemoveRequest of(@Nonnull UUID jointUuid) { - return new JointRemoveRequest(UUID.randomUUID(), jointUuid); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java deleted file mode 100644 index 290efa74..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Copied request boundary for cross-store PhysicsStore communication. - */ -public interface PhysicsStoreRequest { - - @Nonnull - UUID requestUuid(); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java deleted file mode 100644 index 0c769b0d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceHandle.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.CompletionStage; -import javax.annotation.Nonnull; - -/** - * Runtime-only handle completed when PhysicsStore drains and applies a queued request batch. - */ -public final class PhysicsStoreRequestFenceHandle { - - @Nonnull - private final UUID fenceUuid; - @Nonnull - private final CompletionStage completion; - - public PhysicsStoreRequestFenceHandle(@Nonnull UUID fenceUuid, - @Nonnull CompletionStage completion) { - this.fenceUuid = Objects.requireNonNull(fenceUuid, "fenceUuid"); - this.completion = Objects.requireNonNull(completion, "completion"); - } - - @Nonnull - public UUID fenceUuid() { - return fenceUuid; - } - - @Nonnull - public CompletionStage completion() { - return completion; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java deleted file mode 100644 index c123fc3d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/requests/PhysicsStoreRequestFenceResult.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.requests; - -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Runtime-only completion summary for one queued PhysicsStore request batch. - */ -public record PhysicsStoreRequestFenceResult(@Nonnull UUID fenceUuid, - long submittedServerTick, - long consumedServerTick, - int acceptedCount, - int appliedCount, - int softSkippedCount, - int rejectedCount, - int failedCount) { - - public PhysicsStoreRequestFenceResult { - Objects.requireNonNull(fenceUuid, "fenceUuid"); - submittedServerTick = Math.max(0L, submittedServerTick); - consumedServerTick = Math.max(0L, consumedServerTick); - acceptedCount = Math.max(0, acceptedCount); - appliedCount = Math.max(0, appliedCount); - softSkippedCount = Math.max(0, softSkippedCount); - rejectedCount = Math.max(0, rejectedCount); - failedCount = Math.max(0, failedCount); - } - - public boolean allApplied() { - return acceptedCount == appliedCount - && softSkippedCount == 0 - && rejectedCount == 0 - && failedCount == 0; - } - - public boolean hasProblems() { - return softSkippedCount > 0 - || rejectedCount > 0 - || failedCount > 0 - || acceptedCount != appliedCount + softSkippedCount + rejectedCount + failedCount; - } - - public long consumedServerTickLatency() { - return Math.max(0L, consumedServerTick - submittedServerTick); - } -} From 01cb0ff36451fd369a642db434eec75ee044fa3d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 13:27:47 +0200 Subject: [PATCH 073/534] refactor(examples): remove legacy block body command helpers Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 349 +----------------- .../stress/StressBenchmarkCommand.java | 4 +- .../commands/stress/StressBodiesCommand.java | 4 +- .../stress/StressRawBodiesCommand.java | 4 +- 4 files changed, 15 insertions(+), 346 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 44651ebf..91aa597c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -38,8 +38,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.ArrayList; @@ -285,30 +283,6 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, + "bound in PhysicsStore: " + spaceId.value()); } - @Nonnull - public static SpawnedBlockBody spawnBlockBodyLegacy(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - PendingBlockBodyCapture pending = new PendingBlockBodyCapture(); - requireApplied(resource.submitCommands(0L, linearVelocity != null ? 2 : 1, commands -> - pending.set(recordBlockBodySpawn(commands, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - linearVelocity))), "spawn attached block body"); - return attachRecordedBlockBody(store, time, pending.require()); - } - @Nullable private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store store, @Nonnull SpaceId spaceId, @@ -458,7 +432,7 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, UUID spaceUuid = resolvePhysicsStoreSpaceUuid(world, spaceId); if (spaceUuid == null) { - throw new IllegalStateException("Cannot enqueue dynamic body batch because the target space is not " + throw new IllegalStateException("Cannot add dynamic body rows because the target space is not " + "bound in PhysicsStore: " + spaceId.value()); } @@ -479,167 +453,6 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, return new DynamicBodyBatchPlan(bodies, System.nanoTime() - setupStartNanos); } - @Nonnull - public static PendingBlockBody recordBlockBodySpawn(@Nonnull PhysicsCommandRecorder commands, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings) { - return recordBlockBodySpawn(commands, - spaceId, - visualPosition, - DEFAULT_BLOCK_TYPE, - shape, - mass, - settings, - null); - } - - @Nonnull - public static PendingBlockBody recordBlockBodySpawn(@Nonnull PhysicsCommandRecorder commands, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings) { - return recordBlockBodySpawn(commands, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - null); - } - - @Nonnull - public static PendingBlockBody recordBlockBodySpawn(@Nonnull PhysicsCommandRecorder commands, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - Objects.requireNonNull(commands, "commands"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(visualPosition, "visualPosition"); - Objects.requireNonNull(shape, "shape"); - Objects.requireNonNull(settings, "settings"); - return recordBlockBodySpawnAtResolvedPose(commands, - spaceId, - toVector3f(visualPosition), - visualPosition, - blockType, - shape, - mass, - settings, - linearVelocity); - } - - @Nonnull - public static PendingBlockBody recordBlockBodySpawnAtBodyCenter(@Nonnull PhysicsCommandRecorder commands, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d bodyCenter, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings) { - return recordBlockBodySpawnAtBodyCenter(commands, - spaceId, - bodyCenter, - blockType, - shape, - mass, - settings, - null); - } - - @Nonnull - public static PendingBlockBody recordBlockBodySpawnAtBodyCenter(@Nonnull PhysicsCommandRecorder commands, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d bodyCenter, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - Objects.requireNonNull(bodyCenter, "bodyCenter"); - Objects.requireNonNull(shape, "shape"); - return recordBlockBodySpawnAtResolvedPose(commands, - spaceId, - new Vector3f((float) bodyCenter.x, (float) bodyCenter.y, (float) bodyCenter.z), - visualPositionFromBodyCenter(bodyCenter, shape), - blockType, - shape, - mass, - settings, - linearVelocity); - } - - @Nonnull - private static PendingBlockBody recordBlockBodySpawnAtResolvedPose(@Nonnull PhysicsCommandRecorder commands, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f bodyCenter, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - Objects.requireNonNull(commands, "commands"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(bodyCenter, "bodyCenter"); - Objects.requireNonNull(visualPosition, "visualPosition"); - Objects.requireNonNull(shape, "shape"); - Objects.requireNonNull(settings, "settings"); - RigidBodyKey bodyKey = RigidBodyKey.random(); - commands.spawnBody(bodyKey, spawn -> spawn - .space(spaceId) - .shape(shape) - .mass(mass) - .dynamic() - .position(bodyCenter.x, - bodyCenter.y, - bodyCenter.z) - .settings(settings) - .persistent()); - if (linearVelocity != null) { - commands.setBodyVelocity(bodyKey, - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - 0.0f, - 0.0f, - 0.0f, - true); - } - return new PendingBlockBody(bodyKey, - spaceId, - blockType, - (float) visualPosition.x, - (float) visualPosition.y, - (float) visualPosition.z, - mass > 0.0f); - } - - @Nonnull - public static SpawnedBlockBody attachRecordedBlockBody(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull PendingBlockBody pending) { - Ref entity = spawnAttachedBlockEntity(store, - time, - pending.bodyKey(), - pending.spaceId(), - pending.blockType(), - new Vector3d(pending.positionX(), pending.positionY(), pending.positionZ()), - pending.controllable()); - assert entity != null; - return new SpawnedBlockBody(pending.bodyKey(), pending.spaceId(), entity); - } - @Nonnull public static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store store, @Nonnull TimeResource time, @@ -749,9 +562,9 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store { - throw new IllegalStateException(operation + " command " - + result.commandSequence() + " rejected: " + result.message()); - }); - } - static void addControllableMarkerIfAvailable(@Nonnull Holder holder, @Nonnull PhysicsBodyType bodyType) { Objects.requireNonNull(holder, "holder"); @@ -816,121 +618,6 @@ static Vector3d visualPositionFromBodyCenter(@Nonnull Vector3d bodyCenter, return ExamplePhysicsOriginMath.visualPositionFromBodyCenter(bodyCenter, shape); } - @Nullable - public static Ref spawnAttachedBlockEntity(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nullable String blockType, - @Nonnull Vector3d visualPosition, - boolean controllable) { - Holder holder = attachedBlockEntityHolder(time, - bodyKey, - spaceId, - blockType, - visualPosition, - controllable); - return store.addEntity(holder, AddReason.SPAWN); - } - - @Nonnull - public static Holder attachedBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nullable String blockType, - @Nonnull Vector3d visualPosition, - boolean controllable) { - return attachedBlockEntityHolder(time, - bodyKey, - spaceId, - blockType, - visualPosition, - new Vector3f(), - controllable); - } - - @Nonnull - public static Holder attachedBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nullable String blockType, - @Nonnull Vector3d visualPosition, - @Nonnull Vector3f localPositionOffset, - boolean controllable) { - return attachedBlockEntityHolder(time, - bodyKey, - spaceId, - blockType, - visualPosition, - localPositionOffset, - new Quaternionf(), - controllable); - } - - @Nonnull - public static Holder attachedBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nullable String blockType, - @Nonnull Vector3d visualPosition, - @Nonnull Vector3f localPositionOffset, - float visualOriginOffsetY, - boolean controllable) { - return attachedBlockEntityHolder(time, - bodyKey, - spaceId, - blockType, - visualPosition, - localPositionOffset, - new Quaternionf(), - visualOriginOffsetY, - controllable); - } - - @Nonnull - public static Holder attachedBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nullable String blockType, - @Nonnull Vector3d visualPosition, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - boolean controllable) { - return attachedBlockEntityHolder(time, - bodyKey, - spaceId, - blockType, - visualPosition, - localPositionOffset, - localRotationOffset, - Float.NaN, - controllable); - } - - @Nonnull - public static Holder attachedBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nullable String blockType, - @Nonnull Vector3d visualPosition, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - float visualOriginOffsetY, - boolean controllable) { - Holder holder = blockEntityHolder(time, blockType, visualPosition); - holder.addComponent(ATTACHMENT_TYPE, - BodyAttachmentComponent.impulseOwnedVisual(bodyKey.value(), - spaceId, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY)); - if (controllable && PhysicsControlSessions.isAvailable()) { - holder.addComponent(ImpulseControllableComponent.getComponentType(), - new ImpulseControllableComponent()); - } - return holder; - } - @Nullable private static Ref spawnAttachedPhysicsStoreBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, @@ -1044,12 +731,12 @@ public record SpawnedBlockBody(@Nonnull RigidBodyKey bodyKey, } public record BlockBodyBatchTiming(int count, - long commandApplyNanos, + long rowApplyNanos, long entityAttachNanos) { public BlockBodyBatchTiming { count = Math.max(0, count); - commandApplyNanos = Math.max(0L, commandApplyNanos); + rowApplyNanos = Math.max(0L, rowApplyNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } } @@ -1096,24 +783,6 @@ public record PendingBlockBody(@Nonnull RigidBodyKey bodyKey, } } - private static final class PendingBlockBodyCapture { - - @Nullable - private PendingBlockBody body; - - private void set(@Nonnull PendingBlockBody body) { - this.body = Objects.requireNonNull(body, "body"); - } - - @Nonnull - private PendingBlockBody require() { - if (body == null) { - throw new IllegalStateException("Pending block body was not recorded"); - } - return body; - } - } - public static final class BlockBodyBatchRecorder { private static final int POSITION_STRIDE = 3; @@ -1264,7 +933,7 @@ private void assertMutable() { private record BlockBodyBatchResult(@Nullable SpawnedBlockBody[] bodies, int count, - long commandApplyNanos, + long rowApplyNanos, long entityAttachNanos) { private BlockBodyBatchResult { @@ -1272,7 +941,7 @@ private record BlockBodyBatchResult(@Nullable SpawnedBlockBody[] bodies, throw new IllegalArgumentException("Collected body count does not match batch count"); } count = Math.max(0, count); - commandApplyNanos = Math.max(0L, commandApplyNanos); + rowApplyNanos = Math.max(0L, rowApplyNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } @@ -1287,7 +956,7 @@ private SpawnedBlockBody[] collectedBodies() { @Nonnull private BlockBodyBatchTiming timing() { return new BlockBodyBatchTiming(count, - commandApplyNanos, + rowApplyNanos, entityAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 835fbcc4..81b50954 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -196,8 +196,8 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st } }); return new BenchmarkSpawnTiming(timing.count(), - timing.commandApplyNanos() + timing.entityAttachNanos(), - timing.commandApplyNanos(), + timing.rowApplyNanos() + timing.entityAttachNanos(), + timing.rowApplyNanos(), timing.entityAttachNanos()); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index a26a924b..edd14d58 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -190,8 +190,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, layout.positionZ(i)); } }); - timing = new StressSpawnTiming(batchTiming.commandApplyNanos() + batchTiming.entityAttachNanos(), - batchTiming.commandApplyNanos(), + timing = new StressSpawnTiming(batchTiming.rowApplyNanos() + batchTiming.entityAttachNanos(), + batchTiming.rowApplyNanos(), batchTiming.entityAttachNanos()); } else { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index a963c49e..bb472f7d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -74,7 +74,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - long commandStartNanos = System.nanoTime(); + long totalStartNanos = System.nanoTime(); BodyRowBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, spaceId, count, @@ -95,7 +95,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } }); ctx.sender().sendMessage(Message.raw(successMessage(timing, - System.nanoTime() - commandStartNanos))); + System.nanoTime() - totalStartNanos))); return CompletableFuture.completedFuture(null); } From cb4a3bf30e228d83ae79946555ba9fd324a2f30d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 13:46:55 +0200 Subject: [PATCH 074/534] fix(worldcollision): route terrain refresh through physicsstore Signed-off-by: Blovien --- .../PhysicsStoreTerrainMutationCache.java | 110 ++++++++- ...sStoreWorldCollisionStreamingResource.java | 233 ++++++++++++++++++ ...sicsStoreWorldCollisionProducerSystem.java | 62 ++--- .../PhysicsWorldCollisionIndexResource.java | 14 +- .../systems/TerrainMutationDrainSystem.java | 58 +---- ...PersistentPhysicsJointHydrationSystem.java | 3 +- ...ersistentPhysicsRestoreTerrainPrewarm.java | 42 +++- .../ImpulseWorldCollisionPlugin.java | 5 + .../impulse/examples/commands/EcsCommand.java | 7 +- .../commands/ExamplePhysicsUtils.java | 141 +++++++++++ .../commands/WorldCollisionCommand.java | 8 +- .../commands/stress/StressBodiesCommand.java | 11 +- .../explosive/ExplosiveBlockRuntime.java | 14 +- 13 files changed, 584 insertions(+), 124 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java index ff8f73f0..68e31acc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java @@ -186,6 +186,72 @@ public synchronized void retainSpaces(@Nonnull Set retainedSpaces, } } + public synchronized int clearSpace(@Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue) { + SpaceCollisionCache cache = spaces.remove(spaceUuid); + if (cache == null) { + return 0; + } + return removeAllSections(spaceUuid, queue, cache); + } + + public synchronized int clearSectionsAround(@Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull Vector3d center, + int radius) { + SpaceCollisionCache cache = spaces.get(spaceUuid); + if (cache == null) { + return 0; + } + + int clampedRadius = Math.max(0, radius); + int minX = (int) Math.floor(center.x) - clampedRadius; + int maxX = (int) Math.floor(center.x) + clampedRadius; + int minY = Math.max(0, (int) Math.floor(center.y) - clampedRadius); + int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, (int) Math.floor(center.y) + clampedRadius); + int minZ = (int) Math.floor(center.z) - clampedRadius; + int maxZ = (int) Math.floor(center.z) + clampedRadius; + + int minChunkX = ChunkUtil.chunkCoordinate(minX); + int maxChunkX = ChunkUtil.chunkCoordinate(maxX); + int minSectionY = ChunkUtil.indexSection(minY); + int maxSectionY = ChunkUtil.indexSection(maxY); + int minChunkZ = ChunkUtil.chunkCoordinate(minZ); + int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); + + int removed = 0; + Iterator> iterator = + cache.sections.long2ObjectEntrySet().iterator(); + while (iterator.hasNext()) { + CachedSection section = iterator.next().getValue(); + if (section.chunkX < minChunkX + || section.chunkX > maxChunkX + || section.sectionY < minSectionY + || section.sectionY > maxSectionY + || section.chunkZ < minChunkZ + || section.chunkZ > maxChunkZ) { + continue; + } + removed += removeSection(spaceUuid, queue, section); + iterator.remove(); + } + + for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { + cache.missingBlockChunkBackoffs.remove(ChunkUtil.indexChunk(chunkX, chunkZ)); + for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { + cache.missingBlockSectionBackoffs.remove(packSectionKey(chunkX, + sectionY, + chunkZ)); + } + } + } + if (cache.isEmpty()) { + spaces.remove(spaceUuid); + } + return removed; + } + @Nonnull public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @@ -447,13 +513,53 @@ private static int removeSection(@Nonnull UUID spaceUuid, return section.bodyCount; } - private static void removeAllSections(@Nonnull UUID spaceUuid, + private static int removeAllSections(@Nonnull UUID spaceUuid, @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull SpaceCollisionCache cache) { + int removed = 0; for (CachedSection section : cache.sections.values()) { - removeSection(spaceUuid, queue, section); + removed += removeSection(spaceUuid, queue, section); } cache.sections.clear(); + return removed; + } + + public synchronized int bodyCount() { + int count = 0; + for (SpaceCollisionCache cache : spaces.values()) { + for (CachedSection section : cache.sections.values()) { + count += section.bodyCount; + } + } + return count; + } + + public synchronized int bodyCount(@Nonnull UUID spaceUuid) { + SpaceCollisionCache cache = spaces.get(spaceUuid); + if (cache == null) { + return 0; + } + int count = 0; + for (CachedSection section : cache.sections.values()) { + count += section.bodyCount; + } + return count; + } + + public synchronized int sectionCount() { + int count = 0; + for (SpaceCollisionCache cache : spaces.values()) { + count += cache.sections.size(); + } + return count; + } + + public synchronized int spaceCount() { + return spaces.size(); + } + + public synchronized int shapeTemplateCount() { + return shapeTemplates.size(); } private static void recordMissingBackoff(@Nullable Snapshot profiling, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java new file mode 100644 index 00000000..05af11f2 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java @@ -0,0 +1,233 @@ +package dev.hytalemodding.impulse.core.internal.modules.worldcollision; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3d; + +/** + * Shared EntityStore-side producer state for copied PhysicsStore terrain mutations. + */ +public final class PhysicsStoreWorldCollisionStreamingResource implements Resource { + + @Nonnull + private final PhysicsStoreTerrainMutationCache cache = new PhysicsStoreTerrainMutationCache(); + private long tick; + + @Nullable + private static ResourceType resourceType; + + public PhysicsStoreWorldCollisionStreamingResource() { + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = Objects.requireNonNull(type, "type"); + } + + public static void clearResourceType() { + resourceType = null; + } + + @Nonnull + public static ResourceType getResourceType() { + if (resourceType == null) { + throw new IllegalStateException("PhysicsStore world-collision streaming resource is not registered"); + } + return resourceType; + } + + public synchronized long nextTick() { + return ++tick; + } + + public synchronized void retainSpaces(@Nonnull Set retainedSpaces, + @Nonnull PhysicsTerrainMutationQueueResource queue) { + cache.retainSpaces(retainedSpaces, queue); + } + + @Nonnull + public synchronized WorldCollisionPrewarmStats ensureAround(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull Iterable centers, + int radius, + long tick, + @Nullable Snapshot profiling, + @Nonnull WorldCollisionBuildOptions buildOptions) { + LongSet visitedSections = new LongOpenHashSet(); + BuildStats total = BuildStats.empty(); + for (Vector3d center : centers) { + total = total.plus(ensureAround(world, + spaceUuid, + queue, + center, + radius, + tick, + profiling, + visitedSections, + null, + buildOptions)); + } + return new WorldCollisionPrewarmStats(visitedSections.size(), worldCollisionStats(total)); + } + + @Nonnull + public synchronized WorldCollisionBuildStats refreshAround(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull Vector3d center, + int radius, + long tick, + @Nullable Snapshot profiling, + @Nonnull WorldCollisionBuildOptions buildOptions) { + int removed = cache.clearSectionsAround(spaceUuid, queue, center, radius); + BuildStats stats = ensureAround(world, + spaceUuid, + queue, + center, + radius, + tick, + profiling, + null, + null, + buildOptions); + return worldCollisionStats(withRemovedBodies(stats, stats.removedBodies() + removed)); + } + + @Nonnull + public synchronized BuildStats ensureAround(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull Vector3d center, + int radius, + long tick, + @Nullable Snapshot profiling, + @Nullable LongSet visitedSections, + @Nullable StreamingTargetDiagnostic targetDiagnostic, + @Nonnull WorldCollisionBuildOptions buildOptions) { + return cache.ensureAround(world, + spaceUuid, + queue, + center, + radius, + tick, + profiling, + visitedSections, + targetDiagnostic, + buildOptions); + } + + @Nonnull + public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + return cache.shouldRefreshBodyTarget(spaceUuid, + bodyUuid, + bounds, + sleeping, + currentTick, + ttlTicks, + profiling); + } + + public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick) { + cache.recordBodyTargetRefresh(spaceUuid, bodyUuid, bounds, sleeping, currentTick); + } + + public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + return cache.pruneBodyStreamingTargets(spaceUuid, currentTick, ttlTicks, profiling); + } + + public synchronized int pruneUnloaded(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nullable Snapshot profiling) { + return cache.pruneUnloaded(world, spaceUuid, queue, profiling); + } + + public synchronized int pruneUnused(@Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + return cache.pruneUnused(spaceUuid, queue, currentTick, ttlTicks, profiling); + } + + public synchronized int clearSpace(@Nonnull UUID spaceUuid, + @Nonnull PhysicsTerrainMutationQueueResource queue) { + return cache.clearSpace(spaceUuid, queue); + } + + @Nonnull + public synchronized WorldCollisionStats stats() { + return new WorldCollisionStats(cache.spaceCount(), + cache.sectionCount(), + cache.bodyCount(), + cache.shapeTemplateCount()); + } + + @Nonnull + @Override + public synchronized PhysicsStoreWorldCollisionStreamingResource clone() { + PhysicsStoreWorldCollisionStreamingResource copy = + new PhysicsStoreWorldCollisionStreamingResource(); + copy.tick = tick; + return copy; + } + + @Nonnull + private static WorldCollisionBuildStats worldCollisionStats(@Nonnull BuildStats stats) { + return new WorldCollisionBuildStats(stats.scannedBlocks(), + stats.solidBlocks(), + stats.culledInteriorBlocks(), + stats.fullCubeRuns(), + stats.detailBoxes(), + stats.colliderBodies(), + stats.removedBodies(), + stats.sectionsBuilt(), + stats.sectionsRebuilt(), + stats.voxelBodies()); + } + + @Nonnull + private static BuildStats withRemovedBodies(@Nonnull BuildStats stats, int removedBodies) { + return new BuildStats(stats.scannedBlocks(), + stats.solidBlocks(), + stats.culledInteriorBlocks(), + stats.fullCubeRuns(), + stats.detailBoxes(), + stats.colliderBodies(), + removedBodies, + stats.sectionsBuilt(), + stats.sectionsRebuilt(), + stats.voxelBodies()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index 2a48f942..f77bbb2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -17,8 +17,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionStreamingBounds; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; @@ -36,12 +36,10 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.WeakHashMap; import java.util.function.BiConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -65,10 +63,6 @@ public final class PhysicsStoreWorldCollisionProducerSystem extends TickingSyste new SystemDependency<>(Order.AFTER, PhysicsSyncSystem.class) ); - @Nonnull - private final Map, StreamingState> statesByStore = - Collections.synchronizedMap(new WeakHashMap<>()); - @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { if (!WorldCollisionLifecycle.isEnabled()) { @@ -89,11 +83,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { PhysicsWorldCollisionIndexResource.getResourceType()); PhysicsSnapshotResource snapshotResource = physics.getResource( PhysicsSnapshotResource.getResourceType()); + PhysicsStoreWorldCollisionStreamingResource streaming = store.getResource( + PhysicsStoreWorldCollisionStreamingResource.getResourceType()); List spaces = worldCollisionIndex.streamingSpaces(); - StreamingState state = stateFor(store); if (spaces.isEmpty()) { - state.cache().retainSpaces(Set.of(), queue); + streaming.retainSpaces(Set.of(), queue); return; } @@ -101,13 +96,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { if (snapshot != null) { snapshot.setPlayerStreamingTargets(playerPositions.size()); } - long currentTick = state.nextTick(); - PhysicsStoreTerrainMutationCache cache = state.cache(); + long currentTick = streaming.nextTick(); Set retainedSpaces = new ObjectOpenHashSet<>(); for (SpaceWorldCollisionSettings settings : spaces) { retainedSpaces.add(settings.spaceUuid()); } - cache.retainSpaces(retainedSpaces, queue); + streaming.retainSpaces(retainedSpaces, queue); PhysicsStoreSnapshotFrame physicsFrame = snapshotResource.getLatestFrame(); for (SpaceWorldCollisionSettings settings : spaces) { @@ -115,7 +109,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { snapshot.incrementStreamingSpaces(); } processSpace(world, - cache, + streaming, queue, settings, playerPositions, @@ -132,7 +126,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } private static void processSpace(@Nonnull World world, - @Nonnull PhysicsStoreTerrainMutationCache cache, + @Nonnull PhysicsStoreWorldCollisionStreamingResource streaming, @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull SpaceWorldCollisionSettings settings, @Nonnull List playerPositions, @@ -142,7 +136,7 @@ private static void processSpace(@Nonnull World world, LongSet visitedSections = new LongOpenHashSet(); for (Vector3d position : playerPositions) { int sectionsBefore = visitedSections.size(); - cache.ensureAround(world, + streaming.ensureAround(world, settings.spaceUuid(), queue, position, @@ -157,13 +151,13 @@ private static void processSpace(@Nonnull World world, } } - for (BodyStreamingTarget target : collectDynamicBodyTargets(cache, + for (BodyStreamingTarget target : collectDynamicBodyTargets(streaming, settings, physicsFrame, currentTick, snapshot)) { int sectionsBefore = visitedSections.size(); - cache.ensureAround(world, + streaming.ensureAround(world, settings.spaceUuid(), queue, target.position(), @@ -174,7 +168,7 @@ private static void processSpace(@Nonnull World world, null, settings.buildOptions()); for (BodyStreamingRefresh refresh : target.refreshes()) { - cache.recordBodyTargetRefresh(settings.spaceUuid(), + streaming.recordBodyTargetRefresh(settings.spaceUuid(), refresh.bodyUuid(), target.bounds(), refresh.sleeping(), @@ -185,9 +179,9 @@ private static void processSpace(@Nonnull World world, } } - cache.pruneUnloaded(world, settings.spaceUuid(), queue, snapshot); - cache.pruneUnused(settings.spaceUuid(), queue, currentTick, settings.ttlTicks(), snapshot); - cache.pruneBodyStreamingTargets(settings.spaceUuid(), + streaming.pruneUnloaded(world, settings.spaceUuid(), queue, snapshot); + streaming.pruneUnused(settings.spaceUuid(), queue, currentTick, settings.ttlTicks(), snapshot); + streaming.pruneBodyStreamingTargets(settings.spaceUuid(), currentTick, settings.ttlTicks(), snapshot); @@ -195,7 +189,7 @@ private static void processSpace(@Nonnull World world, @Nonnull private static List collectDynamicBodyTargets( - @Nonnull PhysicsStoreTerrainMutationCache cache, + @Nonnull PhysicsStoreWorldCollisionStreamingResource streaming, @Nonnull SpaceWorldCollisionSettings settings, @Nonnull PhysicsStoreSnapshotFrame physicsFrame, long currentTick, @@ -218,7 +212,7 @@ private static List collectDynamicBodyTargets( position.y, position.z, settings.bodyRadius()); - TargetRefreshDecision decision = cache.shouldRefreshBodyTarget(settings.spaceUuid(), + TargetRefreshDecision decision = streaming.shouldRefreshBodyTarget(settings.spaceUuid(), body.bodyUuid(), bounds, body.sleeping(), @@ -269,13 +263,6 @@ private static void collectPlayerPositions(@Nonnull ArchetypeChunk } } - @Nonnull - private StreamingState stateFor(@Nonnull Store store) { - synchronized (statesByStore) { - return statesByStore.computeIfAbsent(store, _ -> new StreamingState()); - } - } - @Nonnull @Override public Query getQuery() { @@ -345,19 +332,4 @@ private record BodyStreamingRefresh(@Nonnull UUID bodyUuid, boolean sleeping) { } - private static final class StreamingState { - - @Nonnull - private final PhysicsStoreTerrainMutationCache cache = new PhysicsStoreTerrainMutationCache(); - private long tick; - - @Nonnull - private PhysicsStoreTerrainMutationCache cache() { - return cache; - } - - private synchronized long nextTick() { - return ++tick; - } - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java index 2e25e4e1..3d6cc06b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Runtime-only copied world-collision settings indexed by PhysicsStore space UUID. @@ -26,25 +27,30 @@ public final class PhysicsWorldCollisionIndexResource implements Resource settings) { + public synchronized void replaceAll(@Nonnull Map settings) { settingsBySpaceUuid.clear(); settingsBySpaceUuid.putAll(settings); } @Nonnull - public List streamingSpaces() { + public synchronized List streamingSpaces() { return settingsBySpaceUuid.values().stream() .filter(settings -> settings.mode() == WorldCollisionMode.STREAMING) .toList(); } - public void clear() { + @Nullable + public synchronized SpaceWorldCollisionSettings settings(@Nonnull UUID spaceUuid) { + return settingsBySpaceUuid.get(spaceUuid); + } + + public synchronized void clear() { settingsBySpaceUuid.clear(); } @Nonnull @Override - public PhysicsWorldCollisionIndexResource clone() { + public synchronized PhysicsWorldCollisionIndexResource clone() { PhysicsWorldCollisionIndexResource copy = new PhysicsWorldCollisionIndexResource(); copy.settingsBySpaceUuid.putAll(settingsBySpaceUuid); return copy; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java index d90ecc1e..c23345dd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java @@ -17,7 +17,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -48,7 +47,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRestoreStatusResource.getResourceType()); PhysicsTerrainPayloadResource terrainPayloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); - Set structuralConflicts = structuralConflicts(mutations, restore); Map> refsThisDrain = new Object2ObjectOpenHashMap<>(); applyRemovals(store, @@ -56,14 +54,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) terrainPayloads, refsThisDrain, restore, - structuralConflicts, mutations); applyUpserts(store, identity, terrainPayloads, refsThisDrain, restore, - structuralConflicts, mutations); } @@ -72,21 +68,15 @@ private static void applyRemovals(@Nonnull Store store, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull Set structuralConflicts, @Nonnull List mutations) { for (TerrainColliderMutation mutation : mutations) { if (mutation.remove()) { - if (structuralConflicts.contains(mutation.terrainColliderUuid())) { - restore.recordSoftSkip("Conflicting terrain mutation for uuid: " - + mutation.terrainColliderUuid()); - } else { - applyTerrainMutation(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - mutation); - } + applyTerrainMutation(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + mutation); } } } @@ -96,21 +86,15 @@ private static void applyUpserts(@Nonnull Store store, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull Set structuralConflicts, @Nonnull List mutations) { for (TerrainColliderMutation mutation : mutations) { if (!mutation.remove()) { - if (structuralConflicts.contains(mutation.terrainColliderUuid())) { - restore.recordSoftSkip("Conflicting terrain mutation for uuid: " - + mutation.terrainColliderUuid()); - } else { - applyTerrainMutation(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - mutation); - } + applyTerrainMutation(store, + identity, + terrainPayloads, + refsThisDrain, + restore, + mutation); } } } @@ -162,24 +146,6 @@ private static void applyTerrainMutation(@Nonnull Store store, AddReason.SPAWN)); } - @Nonnull - private static Set structuralConflicts( - @Nonnull List mutations, - @Nonnull PhysicsRestoreStatusResource restore) { - Set seen = new ObjectOpenHashSet<>(); - Set conflicts = new ObjectOpenHashSet<>(); - for (TerrainColliderMutation mutation : mutations) { - UUID uuid = mutation.terrainColliderUuid(); - if (!seen.add(uuid)) { - conflicts.add(uuid); - } - } - for (UUID conflict : conflicts) { - restore.recordSoftSkip("Conflicting terrain mutations for uuid: " + conflict); - } - return conflicts; - } - @Nullable private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> refsThisDrain, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java index f538924c..c5a34f37 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java @@ -71,7 +71,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } World world = store.getExternalData().getWorld(); - PersistentPhysicsRestoreTerrainPrewarm.prewarmRestoredDynamicBodyTerrain(world, + PersistentPhysicsRestoreTerrainPrewarm.prewarmRestoredDynamicBodyTerrain(store, + world, PhysicsWorldRuntimeResource.require(store), persistent, Math.max(0L, world.getTick())); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java index 3583d97f..e127ca27 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java @@ -1,11 +1,19 @@ package dev.hytalemodding.impulse.core.internal.systems.persistence; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsBodyState; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; @@ -13,6 +21,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Vector3d; import org.joml.Vector3f; @@ -22,7 +31,8 @@ final class PersistentPhysicsRestoreTerrainPrewarm { private PersistentPhysicsRestoreTerrainPrewarm() { } - static void prewarmRestoredDynamicBodyTerrain(@Nonnull World world, + static void prewarmRestoredDynamicBodyTerrain(@Nonnull Store store, + @Nonnull World world, @Nonnull PhysicsWorldRuntimeResource runtime, @Nonnull PersistentPhysicsWorldResource persistent, long tick) { @@ -45,14 +55,34 @@ static void prewarmRestoredDynamicBodyTerrain(@Nonnull World world, if (targets.isEmpty()) { continue; } - runtime.ensureWorldCollisionAround(world, - spaceId, - targets, - settings.getWorldCollisionBodyRadius(), - tick); + UUID spaceUuid = physicsStore(world) + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(spaceId); + if (spaceUuid == null) { + continue; + } + store.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()) + .ensureAround(world, + spaceUuid, + terrainMutationQueue(world), + targets, + settings.getWorldCollisionBodyRadius(), + tick, + null, + WorldCollisionBuildOptions.fromSettings(settings)); } } + @Nonnull + private static Store physicsStore(@Nonnull World world) { + return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + } + + @Nonnull + private static PhysicsTerrainMutationQueueResource terrainMutationQueue(@Nonnull World world) { + return physicsStore(world).getResource(PhysicsTerrainMutationQueueResource.getResourceType()); + } + @Nonnull static Map> dynamicPrewarmTargetsBySpace( @Nonnull PersistentPhysicsBodyState[] bodies, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java index e49f83dd..d3f38cff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsStoreWorldCollisionProducerSystem; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import java.util.logging.Level; @@ -28,6 +29,9 @@ protected void setup() { WorldCollisionProfilingResource.setResourceType(entityRegistry.registerResource( WorldCollisionProfilingResource.class, WorldCollisionProfilingResource::new)); + PhysicsStoreWorldCollisionStreamingResource.setResourceType(entityRegistry.registerResource( + PhysicsStoreWorldCollisionStreamingResource.class, + PhysicsStoreWorldCollisionStreamingResource::new)); entityRegistry.registerSystem(new PhysicsStoreWorldCollisionProducerSystem()); WorldCollisionLifecycle.enable(); LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore request producer enabled."); @@ -37,5 +41,6 @@ protected void setup() { protected void shutdown() { WorldCollisionLifecycle.disable(); WorldCollisionProfilingResource.clearResourceType(); + PhysicsStoreWorldCollisionStreamingResource.clearResourceType(); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index f8b72019..4457b74f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -320,8 +320,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, 8, 1, 24); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); - WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, + WorldCollisionPrewarmStats stats = ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, + world, spaceId, List.of(playerPos), radius, @@ -419,7 +419,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, + WorldCollisionPrewarmStats stats = ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, + world, spaceId, List.of(spawn), Math.max(8, radius + 6), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 91aa597c..38d6a15f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -21,11 +21,18 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; @@ -34,6 +41,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -99,6 +107,139 @@ public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); } + @Nonnull + public static WorldCollisionBuildStats rebuildPhysicsStoreWorldCollisionAround( + @Nonnull Store store, + @Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d center, + int radius) { + PhysicsStoreWorldCollisionStreamingResource streaming = worldCollisionStreaming(store); + SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); + PhysicsTerrainMutationQueueResource queue = terrainMutationQueue(world); + int removed = streaming.clearSpace(settings.spaceUuid(), queue); + WorldCollisionPrewarmStats stats = streaming.ensureAround(world, + settings.spaceUuid(), + queue, + List.of(center), + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + return withRemovedBodies(stats.buildStats(), stats.buildStats().removedBodies() + removed); + } + + @Nonnull + public static WorldCollisionBuildStats refreshPhysicsStoreWorldCollisionAround( + @Nonnull Store store, + @Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d center, + int radius) { + SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); + return worldCollisionStreaming(store).refreshAround(world, + settings.spaceUuid(), + terrainMutationQueue(world), + center, + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + } + + @Nonnull + public static WorldCollisionPrewarmStats ensurePhysicsStoreWorldCollisionAround( + @Nonnull Store store, + @Nonnull World world, + @Nonnull SpaceId spaceId, + @Nonnull Iterable centers, + int radius, + long tick) { + SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); + return worldCollisionStreaming(store).ensureAround(world, + settings.spaceUuid(), + terrainMutationQueue(world), + centers, + radius, + tick, + null, + settings.buildOptions()); + } + + public static int clearPhysicsStoreWorldCollision(@Nonnull Store store, + @Nonnull World world, + @Nonnull SpaceId spaceId) { + SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); + return worldCollisionStreaming(store).clearSpace(settings.spaceUuid(), + terrainMutationQueue(world)); + } + + @Nonnull + public static WorldCollisionStats physicsStoreWorldCollisionStats( + @Nonnull Store store) { + return worldCollisionStreaming(store).stats(); + } + + @Nonnull + private static PhysicsStoreWorldCollisionStreamingResource worldCollisionStreaming( + @Nonnull Store store) { + return store.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()); + } + + @Nonnull + private static PhysicsTerrainMutationQueueResource terrainMutationQueue(@Nonnull World world) { + return physicsStore(world).getResource(PhysicsTerrainMutationQueueResource.getResourceType()); + } + + @Nonnull + private static SpaceWorldCollisionSettings requireWorldCollisionSettings(@Nonnull World world, + @Nonnull SpaceId spaceId) { + Store physics = physicsStore(world); + UUID spaceUuid = resolvePhysicsStoreSpaceUuid(world, spaceId); + if (spaceUuid == null) { + throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() + + " is not bound yet"); + } + Ref spaceRef = physics + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + if (spaceRef == null || !spaceRef.isValid()) { + throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() + + " is not bound yet"); + } + WorldCollisionComponent component = physics.getComponent(spaceRef, + WorldCollisionComponent.getComponentType()); + WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); + if (settings.getMode() == WorldCollisionMode.NONE) { + throw new IllegalStateException("World collision is disabled for space " + spaceId); + } + return new SpaceWorldCollisionSettings(spaceUuid, + settings.getMode(), + settings.getEntityChunkBoundaryMode(), + settings.isNativeVoxelTerrainEnabled(), + settings.getRadius(), + settings.getBodyRadius(), + settings.getTtlTicks(), + settings.getTerrainFriction(), + settings.getTerrainRestitution()); + } + + @Nonnull + private static WorldCollisionBuildStats withRemovedBodies( + @Nonnull WorldCollisionBuildStats stats, + int removedBodies) { + return new WorldCollisionBuildStats(stats.scannedBlocks(), + stats.solidBlocks(), + stats.culledInteriorBlocks(), + stats.fullCubeRuns(), + stats.detailBoxes(), + stats.colliderBodies(), + removedBodies, + stats.sectionsBuilt(), + stats.sectionsRebuilt(), + stats.voxelBodies()); + } + @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyRowDescriptor row) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java index 42c69223..4c58b09d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java @@ -67,7 +67,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - WorldCollisionBuildStats stats = resource.rebuildWorldCollisionAround(world, + WorldCollisionBuildStats stats = ExamplePhysicsUtils.rebuildPhysicsStoreWorldCollisionAround(store, + world, spaceId, playerPos, radius); @@ -111,7 +112,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - int removed = resource.clearWorldCollision(spaceId); + int removed = ExamplePhysicsUtils.clearPhysicsStoreWorldCollision(store, world, spaceId); ctx.sender().sendMessage(Message.raw("Removed " + removed + " world voxel collision bodies.")); return CompletableFuture.completedFuture(null); @@ -131,8 +132,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); - WorldCollisionStats stats = resource.getWorldCollisionStats(); + WorldCollisionStats stats = ExamplePhysicsUtils.physicsStoreWorldCollisionStats(store); ctx.sender().sendMessage(Message.raw("World voxel collision: " + stats.spaces() + " spaces, " + stats.sections() + " sections, " diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index edd14d58..f34ec516 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -159,8 +159,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, StressLayout layout = StressLayout.forCount(count, playerPos); long prewarmStartNanos = System.nanoTime(); - int prewarmedSections = prewarmStressWorldCollision(world, - resource, + int prewarmedSections = prewarmStressWorldCollision(store, + world, spaceId, settings, mode, @@ -315,8 +315,8 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull PhysicsWorld return settings; } - private static int prewarmStressWorldCollision(@Nonnull World world, - @Nonnull PhysicsWorldResource resource, + private static int prewarmStressWorldCollision(@Nonnull Store store, + @Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings, @Nonnull StressMode mode, @@ -329,7 +329,8 @@ private static int prewarmStressWorldCollision(@Nonnull World world, return 0; } - WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, + WorldCollisionPrewarmStats stats = ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, + world, spaceId, layout.positions(count), worldCollisionSettings.getWorldCollisionBodyRadius(), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index a9df6a0c..c2dd2112 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -21,7 +21,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; @@ -73,11 +72,9 @@ public static void scheduleExplosion(@Nonnull Store store, ExplosiveBlockComponent settingsCopy = settings.clone(); world.execute(() -> { TimeResource time = store.getResource(TimeResource.getResourceType()); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); explode(store, world, time, - resource, spaceId, centerCopy, settingsCopy); @@ -88,15 +85,14 @@ public static void scheduleExplosion(@Nonnull Store store, private static ExplosionResult explode(@Nonnull Store store, @Nonnull World world, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @Nonnull ExplosiveBlockComponent settings) { return explode(store, + store, holder -> store.addEntity(holder, AddReason.SPAWN), world, time, - resource, spaceId, center, settings); @@ -104,10 +100,10 @@ private static ExplosionResult explode(@Nonnull Store store, @Nonnull private static ExplosionResult explode(@Nonnull ComponentAccessor entityAccessor, + @Nonnull Store store, @Nonnull Consumer> fragmentSpawner, @Nonnull World world, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @Nonnull ExplosiveBlockComponent settings) { @@ -134,11 +130,13 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e } List groups = groupFragments(fragments, center, settings.getRadius()); - resource.refreshWorldCollisionAround(world, + ExamplePhysicsUtils.refreshPhysicsStoreWorldCollisionAround(store, + world, spaceId, center, Math.max(8, settings.getRadius() + 4)); - resource.ensureWorldCollisionAround(world, + ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, + world, spaceId, groupCenters(groups), Math.max(8, maxGroupCollisionRadius(groups) + 4), From 6aa5536cc4dde36e92bfaf92c8165d68cf173d4e Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 14:12:23 +0200 Subject: [PATCH 075/534] fix(physicsstore): avoid blocking queued backend reads Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 86 +++++++++------- .../commands/perf/PerfStatsCommand.java | 24 +++-- .../settings/SolverSettingsCommand.java | 26 +++-- .../settings/StepModeSettingCommand.java | 43 ++++---- .../WorldCollisionPerfReportCommand.java | 27 +++-- .../PhysicsStoreReadQueueResource.java | 4 + .../persistence/PhysicsPersistence.java | 98 +++++++++++++++++-- .../physicsstore/PhysicsStoreAsync.java | 60 ++++++++++++ .../physicsstore/PhysicsStoreDiagnostics.java | 7 +- .../physicsstore/PhysicsStoreRaycasts.java | 5 + .../impulse/examples/commands/EcsCommand.java | 47 +++++---- .../examples/commands/GrabCommand.java | 51 ++++++---- .../examples/commands/PersistenceCommand.java | 55 ++++++++--- .../examples/commands/RaycastCommand.java | 19 ++-- .../stress/StressBenchmarkCommand.java | 24 ++++- .../commands/stress/StressRaycastCommand.java | 18 ++-- 16 files changed, 429 insertions(+), 165 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAsync.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 0ce94f94..ae621fd3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; @@ -16,6 +17,7 @@ import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -24,6 +26,7 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -89,18 +92,28 @@ protected void execute(@Nonnull CommandContext context, } } - private static final class ListCommand extends AbstractWorldCommand { + private static final class ListCommand extends AbstractAsyncWorldCommand { private ListCommand() { super("list", "List physics spaces in the target world", false); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext context, - @Nonnull World world, - @Nonnull Store store) { + protected CompletableFuture executeAsync(@Nonnull CommandContext context, + @Nonnull World world) { + Store store = world.getEntityStore().getStore(); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - List spaces = spaceSummaries(world, null).stream() + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreDiagnostics.spaceSummariesAsync(world), + summaries -> sendSpaces(context, world, resource, summaries)); + } + + private static void sendSpaces(@Nonnull CommandContext context, + @Nonnull World world, + @Nonnull PhysicsWorldResource resource, + @Nonnull List summaries) { + List spaces = summaries.stream() .map(summary -> { PhysicsSpaceSettings settings = resource.getSpaceSettings(summary.spaceId()); return new SpaceListEntry(summary.spaceId(), @@ -129,7 +142,7 @@ protected void execute(@Nonnull CommandContext context, } } - private static final class DeleteCommand extends AbstractWorldCommand { + private static final class DeleteCommand extends AbstractAsyncWorldCommand { private final OptionalArg spaceArg = withOptionalArg( "space", @@ -140,28 +153,29 @@ private DeleteCommand() { super("delete", "Delete a physics space and its runtime backend state", true); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext context, - @Nonnull World world, - @Nonnull Store store) { + protected CompletableFuture executeAsync(@Nonnull CommandContext context, + @Nonnull World world) { if (!spaceArg.provided(context)) { context.sendMessage(Message.raw("Missing space id. Example:" + " /impulse space delete --space=1 --confirm")); - return; + return CompletableFuture.completedFuture(null); } int rawSpaceId = spaceArg.get(context); if (rawSpaceId <= 0) { context.sendMessage(Message.raw("Space id must be a positive integer.")); - return; + return CompletableFuture.completedFuture(null); } + Store store = world.getEntityStore().getStore(); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = new SpaceId(rawSpaceId); if (!resource.hasSpace(spaceId)) { context.sendMessage(Message.raw("No physics space id=" + rawSpaceId + " exists in world " + world.getName() + ".")); - return; + return CompletableFuture.completedFuture(null); } /* @@ -171,7 +185,25 @@ protected void execute(@Nonnull CommandContext context, * so they still require an explicit clean/destroy before deleting the space. */ int registeredBodies = countRegisteredBodies(resource, spaceId); - SpaceCounts counts = countSpaceContents(world, spaceId); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreDiagnostics.spaceSummariesAsync(world), + summaries -> deleteIfEmpty(context, + world, + resource, + spaceId, + rawSpaceId, + registeredBodies, + summaries)); + } + + private static void deleteIfEmpty(@Nonnull CommandContext context, + @Nonnull World world, + @Nonnull PhysicsWorldResource resource, + @Nonnull SpaceId spaceId, + int rawSpaceId, + int registeredBodies, + @Nonnull List summaries) { + SpaceCounts counts = countSpaceContents(summaries, spaceId); int backendBodies = counts.bodies(); int joints = counts.joints(); if (registeredBodies > 0 || joints > 0) { @@ -189,31 +221,13 @@ protected void execute(@Nonnull CommandContext context, } @Nonnull - private static SpaceCounts countSpaceContents(@Nonnull World world, + private static SpaceCounts countSpaceContents(@Nonnull List summaries, @Nonnull SpaceId spaceId) { - List summaries = spaceSummaries(world, spaceId); - if (summaries.isEmpty()) { - return new SpaceCounts(0, 0); - } - - SpaceSummary summary = summaries.getFirst(); - return new SpaceCounts(summary.bodyCount(), summary.jointCount()); - } - - @Nonnull - private static List spaceSummaries(@Nonnull World world, - @Nullable SpaceId spaceId) { - if (spaceId == null) { - return PhysicsStoreDiagnostics.spaceSummariesAsync(world) - .toCompletableFuture() - .join(); - } - return PhysicsStoreDiagnostics.spaceSummariesAsync(world) - .toCompletableFuture() - .join() - .stream() + return summaries.stream() .filter(summary -> summary.spaceId().equals(spaceId)) - .toList(); + .findFirst() + .map(summary -> new SpaceCounts(summary.bodyCount(), summary.jointCount())) + .orElseGet(() -> new SpaceCounts(0, 0)); } private static int countRegisteredBodies(@Nonnull PhysicsWorldResource resource, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java index 67dbd3c9..78aea161 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java @@ -1,30 +1,34 @@ package dev.hytalemodding.impulse.core.internal.commands.perf; -import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; -public class PerfStatsCommand extends AbstractWorldCommand { +public class PerfStatsCommand extends AbstractAsyncWorldCommand { public PerfStatsCommand() { super("stats", "Show Impulse per-space runtime body and contact counts"); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext ctx, - @Nonnull World world, - @Nonnull Store store) { - List spaces = PhysicsStoreDiagnostics.spaceSummariesAsync(world) - .toCompletableFuture() - .join(); + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull World world) { + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreDiagnostics.spaceSummariesAsync(world), + spaces -> sendStats(ctx, world, spaces)); + } + private static void sendStats(@Nonnull CommandContext ctx, + @Nonnull World world, + @Nonnull List spaces) { if (spaces.isEmpty()) { ctx.sender().sendMessage(Message.raw("Impulse runtime stats: no physics spaces in world " + world.getName() + ".")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 8ec2deee..ccdd7e86 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -5,18 +5,20 @@ import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; -public class SolverSettingsCommand extends AbstractWorldCommand { +public class SolverSettingsCommand extends AbstractAsyncWorldCommand { private final OptionalArg solverIterationsArg = this.withOptionalArg( "solverIterations", @@ -47,19 +49,25 @@ public SolverSettingsCommand() { super("solver", "Get or set solver tuning for a physics space", true); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext ctx, - @Nonnull World world, - @Nonnull Store store) { + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull World world) { + Store store = world.getEntityStore().getStore(); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = SpaceSelection.resolve(ctx, world, resource, spaceArg); if (spaceId == null) { - return; + return CompletableFuture.completedFuture(null); } - SolverCapabilitySummary summary = PhysicsStoreDiagnostics.solverCapabilityAsync(world, spaceId) - .toCompletableFuture() - .join(); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreDiagnostics.solverCapabilityAsync(world, spaceId), + summary -> applySettings(ctx, resource, spaceId, summary)); + } + private void applySettings(@Nonnull CommandContext ctx, + @Nonnull PhysicsWorldResource resource, + @Nonnull SpaceId spaceId, + @Nonnull SolverCapabilitySummary summary) { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, summary, settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java index eccf67cb..0b15cde7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; @@ -54,30 +55,38 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } if (stepMode == PhysicsStepMode.CCD) { - List unsupportedSpaces = unsupportedCcdSpaces(world); - if (!unsupportedSpaces.isEmpty()) { - ctx.sender().sendMessage(Message.raw("CCD mode is not available for: " - + String.join(", ", unsupportedSpaces))); - return CompletableFuture.completedFuture(null); - } + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreDiagnostics.unsupportedCcdSpacesAsync(world), + summaries -> applyStepModeIfSupported(ctx, resource, stepMode, summaries)); } - PhysicsWorldSettings settings = resource.getWorldSettings(); - settings.setStepMode(stepMode); - resource.setWorldSettings(settings); - ctx.sender().sendMessage(Message.raw("Impulse step mode set to " - + stepMode.getSerializedName())); + applyStepMode(ctx, resource, stepMode); return CompletableFuture.completedFuture(null); } - @Nonnull - private static List unsupportedCcdSpaces(@Nonnull World world) { - return PhysicsStoreDiagnostics.unsupportedCcdSpacesAsync(world) - .toCompletableFuture() - .join() - .stream() + private static void applyStepModeIfSupported(@Nonnull CommandContext ctx, + @Nonnull PhysicsWorldResource resource, + @Nonnull PhysicsStepMode stepMode, + @Nonnull List unsupportedSummaries) { + List unsupportedSpaces = unsupportedSummaries.stream() .map(StepModeSettingCommand::formatSpace) .toList(); + if (!unsupportedSpaces.isEmpty()) { + ctx.sender().sendMessage(Message.raw("CCD mode is not available for: " + + String.join(", ", unsupportedSpaces))); + return; + } + applyStepMode(ctx, resource, stepMode); + } + + private static void applyStepMode(@Nonnull CommandContext ctx, + @Nonnull PhysicsWorldResource resource, + @Nonnull PhysicsStepMode stepMode) { + PhysicsWorldSettings settings = resource.getWorldSettings(); + settings.setStepMode(stepMode); + resource.setWorldSettings(settings); + ctx.sender().sendMessage(Message.raw("Impulse step mode set to " + + stepMode.getSerializedName())); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index 1445a492..7625695c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.diagnostics.PhysicsEntityDiagnostics; @@ -14,24 +14,36 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsCommandBatchEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import java.util.List; import java.util.Locale; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; -public class WorldCollisionPerfReportCommand extends AbstractWorldCommand { +public class WorldCollisionPerfReportCommand extends AbstractAsyncWorldCommand { public WorldCollisionPerfReportCommand() { super("report", "Report Impulse world collision profiling metrics"); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext ctx, + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull World world) { + Store store = world.getEntityStore().getStore(); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreDiagnostics.spaceSummariesAsync(world), + summaries -> sendReport(ctx, world, store, summaries)); + } + + private static void sendReport(@Nonnull CommandContext ctx, @Nonnull World world, - @Nonnull Store store) { + @Nonnull Store store, + @Nonnull List summaries) { PhysicsRuntimeProfilingResource runtimeProfiling = store.getResource( PhysicsRuntimeProfilingResource.getResourceType()); StepSnapshot cumulativeStep = runtimeProfiling.getCumulativeStep(); @@ -51,7 +63,7 @@ protected void execute(@Nonnull CommandContext ctx, Snapshot worst = profiling.getWorstTickSnapshot(); PhysicsEntityDiagnostics.Snapshot entityDiagnostics = PhysicsEntityDiagnostics.collect(store); PhysicsWorldResource physicsWorld = store.getResource(PhysicsWorldResource.getResourceType()); - RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(world); + RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(summaries); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling: " + ((runtimeProfiling.isEnabled() || profiling.isEnabled()) ? "enabled" : "disabled"))); @@ -563,7 +575,7 @@ private record RuntimeFootprint(int spaces, int runtimeJoints) { @Nonnull - private static RuntimeFootprint collect(@Nonnull World world) { + private static RuntimeFootprint collect(@Nonnull List summaries) { int spaces = 0; int backendBodies = 0; int backendJoints = 0; @@ -578,9 +590,6 @@ private static RuntimeFootprint collect(@Nonnull World world) { int runtimeTerrainContactPairs = 0; int runtimeActiveIslands = 0; int runtimeJoints = 0; - List summaries = PhysicsStoreDiagnostics.spaceSummariesAsync(world) - .toCompletableFuture() - .join(); for (SpaceSummary summary : summaries) { spaces++; backendBodies += summary.bodyCount(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java index 920eb85a..4469d324 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -19,6 +19,10 @@ /** * Owner-lane live backend read queue drained by PhysicsStore systems. + * + *

Enqueued reads must capture copied inputs only. They execute during PhysicsStore ticking and + * must return copied values rather than live {@code Ref}, runtime resources, or + * backend handles.

*/ public final class PhysicsStoreReadQueueResource implements Resource { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 8265cd83..764ca0a9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -8,9 +8,12 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; +import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; /** @@ -35,21 +38,55 @@ public static SaveResult saveRuntimeSnapshot(@Nonnull Store store) "authoritative-physics-store-auto-capture"); } + @Nonnull + public static CompletionStage saveRuntimeSnapshotAsync( + @Nonnull Store store) { + return statusAsync(store).thenApply(status -> new SaveResult(false, + status.schemaVersion(), + status.storedSpaces(), + status.storedBodies(), + status.storedJoints(), + "authoritative-physics-store-auto-capture")); + } + @Nonnull public static RestoreRequestResult requestRuntimeRestore(@Nonnull Store store) { Status status = status(store); return new RestoreRequestResult(false, "authoritative-physics-store-auto-restore", status); } + @Nonnull + public static CompletionStage requestRuntimeRestoreAsync( + @Nonnull Store store) { + return statusAsync(store).thenApply(status -> + new RestoreRequestResult(false, "authoritative-physics-store-auto-restore", status)); + } + @Nonnull public static Status status(@Nonnull Store store) { - Store physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()) - .getPhysicsStore() + Store physicsStore = physicsStore(store); + return copiedStatus(physicsStore, legacyStatus(store)); + } + + @Nonnull + public static CompletionStage statusAsync(@Nonnull Store store) { + Store physicsStore = physicsStore(store); + LegacyStatus legacy = legacyStatus(store); + return physicsStore.getResource(PhysicsStoreReadQueueResource.getResourceType()) + .enqueue(physics -> liveStatus(physics, legacy)); + } + + @Nonnull + private static Store physicsStore(@Nonnull Store store) { + return ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() .getStore(); + } + + @Nonnull + private static Status liveStatus(@Nonnull Store physicsStore, + @Nonnull LegacyStatus legacy) { PersistentPhysicsStoreResource persistent = physicsStore.getResource( PersistentPhysicsStoreResource.getResourceType()); - PersistentPhysicsWorldResource legacy = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); PhysicsRestoreStatusResource restore = physicsStore.getResource( PhysicsRestoreStatusResource.getResourceType()); List summaries = PhysicsStoreDiagnostics.spaceSummaries(physicsStore); @@ -70,6 +107,31 @@ public static Status status(@Nonnull Store store) { restoreMessage(restore, persistent, legacy)); } + @Nonnull + private static Status copiedStatus(@Nonnull Store physicsStore, + @Nonnull LegacyStatus legacy) { + PersistentPhysicsStoreResource persistent = physicsStore.getResource( + PersistentPhysicsStoreResource.getResourceType()); + PhysicsRestoreStatusResource restore = physicsStore.getResource( + PhysicsRestoreStatusResource.getResourceType()); + int runtimeBodies = physicsStore.getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame() + .bodies() + .size(); + int physicsStoreSpaces = physicsStore.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()).size(); + return new Status(physicsStoreSpaces, + runtimeBodies, + 0, + persistent.getJoints().length, + persistent.getSchemaVersion(), + persistent.getSpaces().length, + persistent.getBodies().length, + persistent.getJoints().length, + restoreState(restore), + restoreMessage(restore, persistent, legacy)); + } + @Nonnull private static RestoreState restoreState(@Nonnull PhysicsRestoreStatusResource restore) { if (restore.isFailed()) { @@ -84,7 +146,7 @@ private static RestoreState restoreState(@Nonnull PhysicsRestoreStatusResource r @Nonnull private static String restoreMessage(@Nonnull PhysicsRestoreStatusResource restore, @Nonnull PersistentPhysicsStoreResource persistent, - @Nonnull PersistentPhysicsWorldResource legacy) { + @Nonnull LegacyStatus legacy) { if (restore.isFailed()) { return restore.getFailureMessage(); } @@ -92,9 +154,9 @@ private static String restoreMessage(@Nonnull PhysicsRestoreStatusResource resto return "PhysicsStore restore soft skips: " + restore.getSoftSkipsByReason(); } if (hasLegacyData(legacy)) { - String legacyCounts = "legacy PersistentPhysicsWorld spaces=" + legacy.getSpaceCount() - + ", bodies=" + legacy.getBodyCount() - + ", joints=" + legacy.getJointCount(); + String legacyCounts = "legacy PersistentPhysicsWorld spaces=" + legacy.spaceCount() + + ", bodies=" + legacy.bodyCount() + + ", joints=" + legacy.jointCount(); if (hasAuthoritativeData(persistent)) { return legacyCounts + " ignored because PersistentPhysicsStore contains authoritative state."; @@ -111,8 +173,17 @@ private static boolean hasAuthoritativeData(@Nonnull PersistentPhysicsStoreResou || persistent.getJoints().length > 0; } - private static boolean hasLegacyData(@Nonnull PersistentPhysicsWorldResource legacy) { - return legacy.getSpaceCount() > 0 || legacy.getBodyCount() > 0 || legacy.getJointCount() > 0; + @Nonnull + private static LegacyStatus legacyStatus(@Nonnull Store store) { + PersistentPhysicsWorldResource legacy = store.getResource( + PersistentPhysicsWorldResource.getResourceType()); + return new LegacyStatus(legacy.getSpaceCount(), + legacy.getBodyCount(), + legacy.getJointCount()); + } + + private static boolean hasLegacyData(@Nonnull LegacyStatus legacy) { + return legacy.hasData(); } public enum RestoreState { @@ -162,4 +233,11 @@ public boolean hasRestoreMessage() { return !restoreMessage.isEmpty(); } } + + private record LegacyStatus(int spaceCount, int bodyCount, int jointCount) { + + private boolean hasData() { + return spaceCount > 0 || bodyCount > 0 || jointCount > 0; + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAsync.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAsync.java new file mode 100644 index 00000000..8e8480cd --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAsync.java @@ -0,0 +1,60 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +/** + * Helpers for consuming copied PhysicsStore async results from world-thread code. + */ +public final class PhysicsStoreAsync { + + private PhysicsStoreAsync() { + } + + @Nonnull + public static CompletableFuture acceptOnWorldThread(@Nonnull World world, + @Nonnull CompletionStage stage, + @Nonnull Consumer consumer) { + Objects.requireNonNull(world, "world"); + Objects.requireNonNull(stage, "stage"); + Objects.requireNonNull(consumer, "consumer"); + CompletableFuture completion = new CompletableFuture<>(); + stage.whenComplete((value, failure) -> { + if (failure != null) { + completion.completeExceptionally(unwrap(failure)); + return; + } + try { + world.execute(() -> accept(value, consumer, completion)); + } catch (RuntimeException exception) { + completion.completeExceptionally(exception); + } + }); + return completion; + } + + private static void accept(T value, + @Nonnull Consumer consumer, + @Nonnull CompletableFuture completion) { + try { + consumer.accept(value); + completion.complete(null); + } catch (RuntimeException | Error exception) { + completion.completeExceptionally(exception); + } + } + + @Nonnull + private static Throwable unwrap(@Nonnull Throwable failure) { + if (failure instanceof CompletionException completionException + && completionException.getCause() != null) { + return completionException.getCause(); + } + return failure; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index 6b47827d..1da2acfe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -18,7 +18,12 @@ import javax.annotation.Nonnull; /** - * Synchronous owner-lane diagnostics for live PhysicsStore backend state. + * Diagnostics for live PhysicsStore backend state. + * + *

The synchronous methods read mutable runtime/backend state and must only run from the + * PhysicsStore tick lane or explicitly scheduled PhysicsStore owner work. Off-lane callers should + * use the {@code *Async} methods, which enqueue copied reads for + * {@link PhysicsStoreReadQueueResource}.

*/ public final class PhysicsStoreDiagnostics { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java index 38747c2a..89e4a1e0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java @@ -22,6 +22,11 @@ /** * PhysicsStore live backend raycasts. + * + *

The synchronous methods read live backend state through {@link PhysicsRuntimeResource} and + * must only be called from the PhysicsStore tick lane or explicitly scheduled PhysicsStore owner + * work. Off-lane callers should use the {@code *Async} methods, which copy inputs, enqueue the + * read for {@link PhysicsStoreReadQueueResource}, and complete with copied hit views.

*/ public final class PhysicsStoreRaycasts { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index 4457b74f..3ba1ab59 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -21,6 +21,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; @@ -34,9 +35,9 @@ import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockPolicy; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; @@ -155,16 +156,25 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - RaycastHitView hit = raycast(ctx, store, ref, spaceId); + return PhysicsStoreAsync.acceptOnWorldThread(world, + raycastAsync(ctx, store, ref, spaceId), + hit -> applyImpulse(ctx, store, ref, world, hit)); + } + + private void applyImpulse(@Nonnull CommandContext ctx, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull World world, + @Nullable RaycastHitView hit) { if (hit == null || hit.bodyKey() == null) { ctx.sender().sendMessage(Message.raw("No rigid body in view.")); - return CompletableFuture.completedFuture(null); + return; } TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType()); if (transform == null) { ctx.sender().sendMessage(Message.raw("Cannot determine player direction.")); - return CompletableFuture.completedFuture(null); + return; } int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); Vector3d impulse = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(strength); @@ -181,12 +191,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (!applied) { ctx.sender().sendMessage(Message.raw("Rigid body " + hit.bodyKey() + " is not bound in PhysicsStore.")); - return CompletableFuture.completedFuture(null); + return; } ctx.sender().sendMessage(Message.raw("Queued ECS impulse command for " + hit.bodyKey() + ".")); - return CompletableFuture.completedFuture(null); } } @@ -271,10 +280,18 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - RaycastHitView hit = raycast(ctx, store, ref, spaceId); + return PhysicsStoreAsync.acceptOnWorldThread(world, + raycastAsync(ctx, store, ref, spaceId), + hit -> attachView(ctx, store, spaceId, hit)); + } + + private static void attachView(@Nonnull CommandContext ctx, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nullable RaycastHitView hit) { if (hit == null || hit.bodyKey() == null) { ctx.sender().sendMessage(Message.raw("No rigid body in view.")); - return CompletableFuture.completedFuture(null); + return; } Vector3d point = new Vector3d(hit.point().x, hit.point().y, hit.point().z); @@ -288,7 +305,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Attached view-only ECS entity to " + hit.bodyKey() + ".")); - return CompletableFuture.completedFuture(null); } } @@ -520,28 +536,25 @@ private static float optionalFloat(@Nonnull CommandContext ctx, } } - @Nullable - private static RaycastHitView raycast(@Nonnull CommandContext ctx, + @Nonnull + private static CompletionStage raycastAsync(@Nonnull CommandContext ctx, @Nonnull Store store, @Nonnull Ref ref, @Nonnull SpaceId spaceId) { TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType()); if (transform == null) { ctx.sender().sendMessage(Message.raw("Cannot determine player position.")); - return null; + return CompletableFuture.completedFuture(null); } Vector3d start = ExamplePhysicsUtils.eyePosition(store, ref, transform); Vector3d end = new Vector3d(start) .add(ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(RAY_LENGTH)); - Optional hit = PhysicsStoreRaycasts.closestAsync( - store.getExternalData().getWorld(), + return PhysicsStoreRaycasts.closestAsync(store.getExternalData().getWorld(), spaceId, vector(start), vector(end)) - .toCompletableFuture() - .join(); - return hit.orElse(null); + .thenApply(hit -> hit.orElse(null)); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 267b5b4d..9cb88344 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -27,6 +27,7 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; @@ -103,16 +104,36 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d direction = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(RAY_LENGTH); Vector3d end = new Vector3d(start).add(direction); - HitSelection selection = findControllableHit(world, - resource, + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreRaycasts.allAsync(world, + targetSpaceId, + ExamplePhysicsUtils.toVector3f(start), + ExamplePhysicsUtils.toVector3f(end)), + hits -> finishGrab(ctx, + world, + store, + ref, + resource, + targetSpaceId, + controllableType, + hits)); + } + + private static void finishGrab(@Nonnull CommandContext ctx, + @Nonnull World world, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsWorldResource resource, + @Nonnull SpaceId targetSpaceId, + @Nonnull ComponentType controllableType, + @Nonnull List hits) { + HitSelection selection = selectControllableHit(resource, store, - targetSpaceId, controllableType, - start, - end); + hits); if (selection == null) { ctx.sender().sendMessage(Message.raw("No controllable physics body in sight.")); - return CompletableFuture.completedFuture(null); + return; } PhysicsControlSessions.releaseSession(store, ref); @@ -120,7 +141,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, SpaceId selectedSpaceId = selection.spaceId() != null ? selection.spaceId() : targetSpaceId; if (!resource.hasSpace(selectedSpaceId)) { ctx.sender().sendMessage(Message.raw("Selected physics space no longer exists.")); - return CompletableFuture.completedFuture(null); + return; } GrabPhysicsState physicsState = createGrabControl(world, @@ -128,7 +149,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, selection); if (physicsState == null) { ctx.sender().sendMessage(Message.raw("Selected physics body no longer exists.")); - return CompletableFuture.completedFuture(null); + return; } PhysicsControlSessions.startSession(store, @@ -145,7 +166,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Grabbed physics body at distance " + selection.distance())); - return CompletableFuture.completedFuture(null); } @Nullable @@ -257,19 +277,10 @@ private static JointComponent controlJoint(@Nonnull UUID spaceUuid, } @Nullable - private static HitSelection findControllableHit(@Nonnull World world, - @Nonnull PhysicsWorldResource resource, + private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource resource, @Nonnull Store store, - @Nonnull SpaceId spaceId, @Nonnull ComponentType controllableType, - @Nonnull Vector3d start, - @Nonnull Vector3d end) { - List hits = PhysicsStoreRaycasts.allAsync(world, - spaceId, - ExamplePhysicsUtils.toVector3f(start), - ExamplePhysicsUtils.toVector3f(end)) - .toCompletableFuture() - .join(); + @Nonnull List hits) { List candidates = new ArrayList<>(hits.size()); for (RaycastHitView hit : hits) { if (hit.bodyType() != PhysicsBodyType.DYNAMIC || hit.bodyKey() == null) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java index 45c4334b..6cf308f4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java @@ -4,13 +4,15 @@ import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.RestoreRequestResult; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.SaveResult; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.Status; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; /** @@ -25,17 +27,25 @@ public PersistenceCommand() { addSubCommand(new StatusCommand()); } - private static final class SaveCommand extends AbstractWorldCommand { + private static final class SaveCommand extends AbstractAsyncWorldCommand { private SaveCommand() { super("save", "Report authoritative PhysicsStore persistence capture status", false); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext ctx, + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull World world) { + Store store = world.getEntityStore().getStore(); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsPersistence.saveRuntimeSnapshotAsync(store), + result -> sendSaveResult(ctx, world, result)); + } + + private static void sendSaveResult(@Nonnull CommandContext ctx, @Nonnull World world, - @Nonnull Store store) { - SaveResult result = PhysicsPersistence.saveRuntimeSnapshot(store); + @Nonnull SaveResult result) { if (!result.synced()) { ctx.sendMessage(Message.raw("Manual Impulse persistence save is disabled in " + "authoritative PhysicsStore mode; PhysicsStore captures canonical state " @@ -57,24 +67,32 @@ protected void execute(@Nonnull CommandContext ctx, } } - private static final class LoadCommand extends AbstractWorldCommand { + private static final class LoadCommand extends AbstractAsyncWorldCommand { private LoadCommand() { super("load", "Report authoritative PhysicsStore persistence restore status", false); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext ctx, + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull World world) { + Store store = world.getEntityStore().getStore(); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsPersistence.requestRuntimeRestoreAsync(store), + result -> sendLoadResult(ctx, world, result)); + } + + private static void sendLoadResult(@Nonnull CommandContext ctx, @Nonnull World world, - @Nonnull Store store) { - Status status = PhysicsPersistence.status(store); + @Nonnull RestoreRequestResult result) { + Status status = result.status(); if (status.restoreState() == PhysicsPersistence.RestoreState.PENDING_SPACES || status.restoreState() == PhysicsPersistence.RestoreState.PENDING_BODIES_AND_JOINTS) { ctx.sendMessage(Message.raw("Impulse persistence restore is already pending.")); return; } - RestoreRequestResult result = PhysicsPersistence.requestRuntimeRestore(store); if (!result.queued()) { ctx.sendMessage(Message.raw("Manual Impulse persistence restore is disabled in " + "authoritative PhysicsStore mode; PhysicsStore restores automatically from " @@ -98,18 +116,25 @@ protected void execute(@Nonnull CommandContext ctx, } } - private static final class StatusCommand extends AbstractWorldCommand { + private static final class StatusCommand extends AbstractAsyncWorldCommand { private StatusCommand() { super("status", "Show runtime and stored Impulse persistence counts", false); } + @Nonnull @Override - protected void execute(@Nonnull CommandContext ctx, - @Nonnull World world, - @Nonnull Store store) { - Status status = PhysicsPersistence.status(store); + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull World world) { + Store store = world.getEntityStore().getStore(); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsPersistence.statusAsync(store), + status -> sendStatus(ctx, world, status)); + } + private static void sendStatus(@Nonnull CommandContext ctx, + @Nonnull World world, + @Nonnull Status status) { ctx.sendMessage(Message.raw("Impulse persistence status for " + world.getName() + ": runtime spaces=" + status.runtimeSpaces() + ", runtimeBodies=" diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index c98c49e3..9bf241fb 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -14,6 +14,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; @@ -60,17 +61,20 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, DebugUtils.addArrow(world, start, direction, DebugUtils.COLOR_WHITE, 0.8f, 4.0f, DebugUtils.FLAG_FADE); - RaycastResult hit = PhysicsStoreRaycasts.closestAsync(world, + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreRaycasts.closestAsync(world, spaceId, ExamplePhysicsUtils.toVector3f(start), - ExamplePhysicsUtils.toVector3f(end)) - .toCompletableFuture() - .join() - .map(RaycastCommand::toResult) - .orElse(null); + ExamplePhysicsUtils.toVector3f(end)), + hit -> handleHit(ctx, world, hit.map(RaycastCommand::toResult).orElse(null))); + } + + private static void handleHit(@Nonnull CommandContext ctx, + @Nonnull World world, + RaycastResult hit) { if (hit == null) { ctx.sender().sendMessage(Message.raw("Physics ray missed.")); - return CompletableFuture.completedFuture(null); + return; } Vector3d hitPoint = hit.point(); @@ -83,7 +87,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Physics ray hit " + hit.shapeType() + " at distance " + hit.distance())); - return CompletableFuture.completedFuture(null); } private record RaycastResult(@Nonnull Vector3d point, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 81b50954..d0c20869 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -14,6 +14,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -79,10 +80,26 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } BenchmarkLayout layout = BenchmarkLayout.around(playerPos, request.count()); - int beforeBodies = PhysicsStoreDiagnostics.bodyCountAsync(world, spaceId) - .toCompletableFuture() - .join(); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreDiagnostics.bodyCountAsync(world, spaceId), + beforeBodies -> spawnBenchmark(ctx, + store, + world, + resource, + spaceId, + request, + layout, + beforeBodies)); + } + private static void spawnBenchmark(@Nonnull CommandContext ctx, + @Nonnull Store store, + @Nonnull World world, + @Nonnull PhysicsWorldResource resource, + @Nonnull SpaceId spaceId, + @Nonnull BenchmarkRequest request, + @Nonnull BenchmarkLayout layout, + int beforeBodies) { long serverTick = Math.max(0L, world.getTick()); BenchmarkSpawnTiming timing = switch (request.mode()) { case RAW -> spawnRaw(world, spaceId, layout, request.count()); @@ -112,7 +129,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + ". For clean comparisons run /impulse clean, /impulse-world-collision perf reset," + " /impulse-world-collision perf toggle before spawning, then /impulse-world-collision perf report.")); } - return CompletableFuture.completedFuture(null); } @Nullable diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index 989fdfe7..ad5389a8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; @@ -65,15 +66,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, List segments = getRaycastSegments(side, rays, playerPos); long startNanos = System.nanoTime(); - long hits = PhysicsStoreRaycasts.closestBatchAsync(world, spaceId, segments) - .toCompletableFuture() - .join() - .hitCount(); - long elapsedNanos = System.nanoTime() - startNanos; - - ctx.sender().sendMessage(Message.raw("Ran " + rays + " raycasts: " + hits - + " hits in " + millis(elapsedNanos) + " ms.")); - return CompletableFuture.completedFuture(null); + return PhysicsStoreAsync.acceptOnWorldThread(world, + PhysicsStoreRaycasts.closestBatchAsync(world, spaceId, segments), + result -> { + long elapsedNanos = System.nanoTime() - startNanos; + ctx.sender().sendMessage(Message.raw("Ran " + rays + " raycasts: " + + result.hitCount() + + " hits in " + millis(elapsedNanos) + " ms.")); + }); } @Nonnull From 63c3573fc9ed0ef49e1feaee8c90ad4ed98f219d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 14:20:59 +0200 Subject: [PATCH 076/534] fix(physicsstore): enforce direct access world-thread rules Signed-off-by: Blovien --- .../PhysicsStoreControlSessionMutations.java | 3 ++ .../PhysicsStoreSpaceMutations.java | 7 ++++ .../PhysicsStoreBackendAccess.java | 2 ++ .../physicsstore/PhysicsStoreDiagnostics.java | 6 ++++ .../physicsstore/PhysicsStoreThreading.java | 32 +++++++++++++++++++ .../commands/ExamplePhysicsUtils.java | 17 +++++++++- 6 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index b0533492..c78939fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; @@ -42,6 +43,8 @@ public static void applyRelease(@Nonnull Store store, Store physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() .getStore(); + PhysicsStoreThreading.requireWorldThread(physicsStore, + "apply PhysicsStore control-session release mutations"); PhysicsIdentityIndexResource identity = physicsStore.getResource( PhysicsIdentityIndexResource.getResourceType()); PhysicsRuntimeResource runtime = physicsStore.getResource(PhysicsRuntimeResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index bb8f43d8..6038d46e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; @@ -45,6 +46,7 @@ public static Ref addSpace(@Nonnull Store store, Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); Objects.requireNonNull(backendId, "backendId"); Objects.requireNonNull(settings, "settings"); + PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore space row"); if (backendId.value().isBlank()) { throw new IllegalArgumentException("PhysicsStore space backend id is blank: " + spaceUuid); @@ -91,7 +93,11 @@ public static void putSpaceSettings(@Nonnull Store store, @Nonnull Ref ref, @Nonnull UUID spaceUuid, @Nonnull PhysicsSpaceSettings settings) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(ref, "ref"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(settings, "settings"); + PhysicsStoreThreading.requireWorldThread(store, "update a PhysicsStore space row"); store.putComponent(ref, WorldCollisionComponent.getComponentType(), new WorldCollisionComponent(settings.getWorldCollisionSettings())); @@ -116,6 +122,7 @@ public static void removeEmptySpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { Objects.requireNonNull(store, "store"); Objects.requireNonNull(spaceUuid, "spaceUuid"); + PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space row"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java index 830bed1a..9c8e5a75 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java @@ -24,6 +24,7 @@ private PhysicsStoreBackendAccess() { @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId spaceId) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); @@ -33,6 +34,7 @@ static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId s @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull UUID spaceUuid) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); return space(runtime, spaceUuid); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index 1da2acfe..0d0161fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -56,6 +56,7 @@ public static CompletionStage bodyCountAsync(@Nonnull Store store) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); int[] count = {0}; runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> @@ -74,6 +75,7 @@ public static CompletionStage runtimeJointCountAsync(@Nonnull Store store) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); boolean[] supported = {false}; runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { @@ -120,6 +122,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull UUID spaceUuid) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); SpaceId spaceId = compatibility.getSpaceId(Objects.requireNonNull(spaceUuid, "spaceUuid")); @@ -132,6 +135,7 @@ public static SolverCapabilitySummary solverCapability(@Nonnull Store spaceSummaries(@Nonnull Store store) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); @@ -162,6 +166,7 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull SpaceId spaceId) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); @@ -178,6 +183,7 @@ public static List spaceSummaries(@Nonnull Store sto @Nonnull public static List unsupportedCcdSpaces(@Nonnull Store store) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java new file mode 100644 index 00000000..9c11a949 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -0,0 +1,32 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Thread assertions for direct PhysicsStore row and backend access. + */ +public final class PhysicsStoreThreading { + + private PhysicsStoreThreading() { + } + + @Nonnull + public static World requireWorldThread(@Nonnull Store store, + @Nonnull String operation) { + World world = world(store); + if (!world.isInThread()) { + throw new IllegalStateException("Cannot " + operation + + " outside the owning PhysicsStore world thread"); + } + return world; + } + + @Nonnull + public static World world(@Nonnull Store store) { + return Objects.requireNonNull(store, "store").getExternalData().getWorld(); + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 38d6a15f..739b345e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -37,6 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; @@ -267,8 +268,10 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, public static void addPhysicsStoreBodies(@Nonnull World world, @Nonnull Iterable rows) { Objects.requireNonNull(rows, "rows"); + Store store = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(store, "add PhysicsStore body rows"); for (BodyRowDescriptor row : rows) { - addPhysicsStoreBody(world, row); + addPhysicsStoreBodyUnchecked(store, row, row.dynamics(), row.target()); } } @@ -281,6 +284,16 @@ private static Ref addPhysicsStoreBody(@Nonnull Store addPhysicsStoreBody(@Nonnull Store store, + @Nonnull BodyRowDescriptor row, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target) { + Objects.requireNonNull(row, "row"); + PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore body row"); + return addPhysicsStoreBodyUnchecked(store, row, dynamics, target); + } + + @Nonnull + private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store store, @Nonnull BodyRowDescriptor row, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { @@ -316,6 +329,7 @@ public static Ref addPhysicsStoreJoint(@Nonnull World world, @Nonnull UUID jointUuid, @Nonnull JointComponent joint) { Store store = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore joint row"); return store.addEntity(PhysicsStoreEntities.jointHolder(store, Objects.requireNonNull(jointUuid, "jointUuid"), joint), AddReason.SPAWN); @@ -327,6 +341,7 @@ public static void appendPhysicsStoreBodyCommand(@Nonnull Store st Objects.requireNonNull(store, "store"); Objects.requireNonNull(bodyRef, "bodyRef"); Objects.requireNonNull(command, "command"); + PhysicsStoreThreading.requireWorldThread(store, "append a PhysicsStore body command"); BodyCommandComponent existing = store.getComponent(bodyRef, BodyCommandComponent.getComponentType()); BodyCommandComponent merged = existing != null ? existing.append(command) : command; From 9a63274584d53152cfdc2cfca6453357f3f780f9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 14:26:26 +0200 Subject: [PATCH 077/534] fix(physicsstore): schedule authoritative async space mutations Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 37 ++++++++++----- .../persistence/PhysicsPersistence.java | 3 ++ .../physicsstore/PhysicsStoreThreading.java | 46 +++++++++++++++++++ .../commands/ExamplePhysicsUtils.java | 7 ++- 4 files changed, 80 insertions(+), 13 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 840f9a7e..d317a883 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -57,6 +57,7 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; @@ -315,7 +316,9 @@ private World requireAuthoritativeWorld(@Nonnull String operation) { @Nonnull private Store authoritativePhysicsStore(@Nonnull String operation) { - return physicsStore(requireAuthoritativeWorld(operation)); + Store store = physicsStore(requireAuthoritativeWorld(operation)); + PhysicsStoreThreading.requireWorldThread(store, operation); + return store; } @Nonnull @@ -383,11 +386,14 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( } @Nonnull - private IllegalStateException authoritativeFenceUnavailable(@Nonnull String operation) { - return new IllegalStateException("Cannot " + operation - + " through authoritative PhysicsStore yet because async store-lane mutation " - + "completion is not implemented. Use the synchronous store-lane facade or add an " - + "explicit PhysicsStore scheduling completion contract."); + private PhysicsMutationHandle enqueueAuthoritativePhysicsStoreMutation( + @Nonnull String operation, + @Nullable T value, + @Nonnull Consumer> mutation) { + World world = requireAuthoritativeWorld(operation); + return PhysicsMutationHandle.fromCompletion(operation, + value, + PhysicsStoreThreading.executeOnWorldThread(world, operation, mutation)); } public void runOwnerMutation(@Nonnull String operation, @@ -502,9 +508,15 @@ public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backen @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { - return PhysicsMutationHandle.failed("create physics space", + Impulse.getRuntimeProvider(backendId); + PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); + return enqueueAuthoritativePhysicsStoreMutation("create physics space", spaceId, - authoritativeFenceUnavailable("create physics space asynchronously")); + store -> PhysicsStoreSpaceMutations.addSpace(store, + UUID.randomUUID(), + spaceId, + backendId, + requested)); } requireLegacyMutationAllowed("create physics space"); return enqueueOwnerMutation("create physics space", @@ -980,9 +992,9 @@ public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { - return PhysicsMutationHandle.failed("remove physics space", + return enqueueAuthoritativePhysicsStoreMutation("remove physics space", spaceId, - authoritativeFenceUnavailable("remove physics space asynchronously")); + store -> PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId)); } requireLegacyMutationAllowed("remove physics space"); return enqueueOwnerMutation("remove physics space", @@ -1115,9 +1127,10 @@ public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSett public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { - return PhysicsMutationHandle.failed("set physics space settings", + PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); + return enqueueAuthoritativePhysicsStoreMutation("set physics space settings", spaceId, - authoritativeFenceUnavailable("set physics space settings asynchronously")); + store -> PhysicsStoreSpaceMutations.putSpaceSettings(store, spaceId, requested)); } requireLegacyMutationAllowed("set physics space settings"); PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 764ca0a9..3294f200 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -11,6 +11,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.concurrent.CompletionStage; @@ -110,6 +111,8 @@ private static Status liveStatus(@Nonnull Store physicsStore, @Nonnull private static Status copiedStatus(@Nonnull Store physicsStore, @Nonnull LegacyStatus legacy) { + PhysicsStoreThreading.requireWorldThread(physicsStore, + "read copied PhysicsStore persistence status"); PersistentPhysicsStoreResource persistent = physicsStore.getResource( PersistentPhysicsStoreResource.getResourceType()); PhysicsRestoreStatusResource restore = physicsStore.getResource( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java index 9c11a949..9fdba29d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -3,7 +3,12 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; import javax.annotation.Nonnull; /** @@ -29,4 +34,45 @@ public static World requireWorldThread(@Nonnull Store store, public static World world(@Nonnull Store store) { return Objects.requireNonNull(store, "store").getExternalData().getWorld(); } + + @Nonnull + public static CompletionStage executeOnWorldThread(@Nonnull World world, + @Nonnull String operation, + @Nonnull Consumer> mutation) { + Objects.requireNonNull(world, "world"); + Objects.requireNonNull(operation, "operation"); + Objects.requireNonNull(mutation, "mutation"); + CompletableFuture completion = new CompletableFuture<>(); + Runnable task = () -> execute(world, operation, mutation, completion); + try { + if (world.isInThread()) { + task.run(); + } else { + world.execute(task); + } + } catch (RuntimeException exception) { + PhysicsStoreAsyncCompletions.fail(completion, exception); + } + return completion.minimalCompletionStage(); + } + + private static void execute(@Nonnull World world, + @Nonnull String operation, + @Nonnull Consumer> mutation, + @Nonnull CompletableFuture completion) { + try { + Store store = store(world); + requireWorldThread(store, operation); + mutation.accept(store); + PhysicsStoreAsyncCompletions.complete(completion, null); + } catch (RuntimeException | Error throwable) { + PhysicsStoreAsyncCompletions.fail(completion, throwable); + } + } + + @Nonnull + private static Store store(@Nonnull World world) { + return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() + .getStore(); + } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 739b345e..78761196 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -103,7 +103,9 @@ public static Store physicsStore(@Nonnull World world) { @Nullable public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, @Nonnull SpaceId spaceId) { - return physicsStore(world) + Store store = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); + return store .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); } @@ -196,6 +198,8 @@ private static PhysicsTerrainMutationQueueResource terrainMutationQueue(@Nonnull private static SpaceWorldCollisionSettings requireWorldCollisionSettings(@Nonnull World world, @Nonnull SpaceId spaceId) { Store physics = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(physics, + "read PhysicsStore world-collision settings"); UUID spaceUuid = resolvePhysicsStoreSpaceUuid(world, spaceId); if (spaceUuid == null) { throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() @@ -314,6 +318,7 @@ public static boolean appendPhysicsStoreBodyCommand(@Nonnull World world, @Nonnull BodyCommandComponent command) { Objects.requireNonNull(command, "command"); Store store = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(store, "append a PhysicsStore body command"); Ref bodyRef = store .getResource(PhysicsIdentityIndexResource.getResourceType()) .getByUuid(Objects.requireNonNull(bodyUuid, "bodyUuid")); From d9830b6a70d2bf7a7617504af30a9f6a2a1773e9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 14:31:08 +0200 Subject: [PATCH 078/534] fix(physicsstore): assert direct read world-thread access Signed-off-by: Blovien --- .../control/systems/PhysicsKinematicControlSystem.java | 3 +++ .../PhysicsStoreWorldCollisionProducerSystem.java | 3 +++ .../physicsstore/PhysicsStoreSpaceMutations.java | 2 ++ .../PersistentPhysicsRestoreTerrainPrewarm.java | 6 +++++- .../core/internal/systems/sync/PhysicsSyncSystem.java | 6 +++++- .../impulse/examples/commands/ExamplePhysicsUtils.java | 5 ++++- .../impulse/examples/commands/GrabCommand.java | 8 ++++++-- .../examples/systems/ExplosiveFuseTickSystem.java | 10 +++++++--- 8 files changed, 35 insertions(+), 8 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 9a24f229..1e984e68 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -25,6 +25,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -170,6 +171,8 @@ private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( PhysicsStore physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); Store physics = physicsStore.getStore(); + PhysicsStoreThreading.requireWorldThread(physics, + "resolve PhysicsStore kinematic control targets"); PhysicsIdentityIndexResource identity = physics.getResource( PhysicsIdentityIndexResource.getResourceType()); UUID bodyUuid = bodyKey.value(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index f77bbb2e..c084e121 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -29,6 +29,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -77,6 +78,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { World world = store.getExternalData().getWorld(); PhysicsStore physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore(); Store physics = physicsStore.getStore(); + PhysicsStoreThreading.requireWorldThread(physics, + "produce PhysicsStore world-collision terrain mutations"); PhysicsTerrainMutationQueueResource queue = physics.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); PhysicsWorldCollisionIndexResource worldCollisionIndex = physics.getResource( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 6038d46e..7b0f5e6c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -156,6 +156,7 @@ public static void removeEmptySpace(@Nonnull Store store, @Nonnull public static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull SpaceId spaceId) { + PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); if (spaceUuid == null) { @@ -168,6 +169,7 @@ public static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull private static Ref requireSpaceRef(@Nonnull Store store, @Nonnull UUID spaceUuid) { + PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space row"); Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) .getByUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); if (ref == null || !ref.isValid()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java index e127ca27..d4daada4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -75,7 +76,10 @@ static void prewarmRestoredDynamicBodyTerrain(@Nonnull Store store, @Nonnull private static Store physicsStore(@Nonnull World world) { - return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + PhysicsStoreThreading.requireWorldThread(store, + "read PhysicsStore restore terrain prewarm state"); + return store; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index d5bddf09..5ceceaf0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -28,6 +28,7 @@ import dev.hytalemodding.impulse.core.internal.systems.visual.GeneratedProxyLifecycle; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; @@ -160,7 +161,10 @@ private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( @Nonnull Store store) { PhysicsStore physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); - return physicsStore.getStore().getResource( + Store physics = physicsStore.getStore(); + PhysicsStoreThreading.requireWorldThread(physics, + "read copied PhysicsStore sync snapshots"); + return physics.getResource( PhysicsSnapshotResource.getResourceType()); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 78761196..70bf0a16 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -191,7 +191,10 @@ private static PhysicsStoreWorldCollisionStreamingResource worldCollisionStreami @Nonnull private static PhysicsTerrainMutationQueueResource terrainMutationQueue(@Nonnull World world) { - return physicsStore(world).getResource(PhysicsTerrainMutationQueueResource.getResourceType()); + Store store = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(store, + "read PhysicsStore terrain mutation queue"); + return store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 9cb88344..ab3c6405 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -12,6 +12,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; @@ -29,6 +30,7 @@ import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; @@ -320,8 +322,10 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource @Nullable private static RigidBodyStateView bodyState(@Nonnull World world, @Nonnull RigidBodyKey bodyKey) { - PhysicsStoreBodySnapshot body = ((PhysicsStoreWorld) world).getPhysicsStore() - .getStore() + Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + PhysicsStoreThreading.requireWorldThread(store, + "read copied PhysicsStore grab body snapshot"); + PhysicsStoreBodySnapshot body = store .getResource(PhysicsSnapshotResource.getResourceType()) .getBody(bodyKey.value()); return body != null diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 6e2030d1..af0f41db 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -12,9 +12,11 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; @@ -115,9 +117,11 @@ private static Vector3d explosionCenter(@Nullable BodyMotionSnapshot snapshot, private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { UUID bodyUuid = attachment.getBodyUuid(); - PhysicsStoreBodySnapshot snapshot = ((PhysicsStoreWorld) store.getExternalData().getWorld()) - .getPhysicsStore() - .getStore() + Store physics = ((PhysicsStoreWorld) store.getExternalData().getWorld()) + .getPhysicsStore().getStore(); + PhysicsStoreThreading.requireWorldThread(physics, + "read copied PhysicsStore explosive body snapshot"); + PhysicsStoreBodySnapshot snapshot = physics .getResource(PhysicsSnapshotResource.getResourceType()) .getBody(bodyUuid); return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; From 492cbc079713a3d72a0a94baacbd48fec8306fba Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 14:48:41 +0200 Subject: [PATCH 079/534] fix(physicsstore): apply live space gravity rows Signed-off-by: Blovien --- .../crucible/ImpulseLiveCrucibleTests.java | 86 ++++++++++--------- .../PhysicsStoreSpaceMutations.java | 29 +++++++ .../SpaceSettingsApplicationSystem.java | 7 ++ 3 files changed, 82 insertions(+), 40 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 899b187c..280a676b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -11,28 +11,33 @@ import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; import java.util.Comparator; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.joml.Vector3d; +import org.joml.Vector3f; /** * Crucible suites that exercise entity-backed bodies through Hytale's live ECS. @@ -78,20 +83,18 @@ private static CompletionStage entityBodyFallsThroughEcs(CrucibleContex context.wx(0), context.wy(20), context.wz(0)); - resource.submitCommands(0L, - commands -> commands.setSpaceGravity(spaceId, 0f, -9.81f, 0f)) - .completionSummary() - .toCompletableFuture() - .join(); + Store physicsStore = physicsStore(world); + PhysicsStoreSpaceMutations.putSpaceGravity(physicsStore, + spaceId, + new Vector3f(0.0f, -9.81f, 0.0f)); RigidBodyKey bodyKey = RigidBodyKey.random(); - submitLiveBody(resource, spaceId, bodyKey, visualPosition); + submitLiveBody(physicsStore, spaceId, bodyKey, visualPosition); Ref ref = spawnLiveBlockBody(store, spaceId, bodyKey, visualPosition); double startY = visualPosition.y; return context.waitApproxTicksOnWorld(40).thenApply(ignored -> bodyAndEntityMovedDown( store, - resource, ref, bodyKey, startY)); @@ -101,7 +104,6 @@ private static CompletionStage entityBodyFallsThroughEcs(CrucibleContex } private static boolean bodyAndEntityMovedDown(Store store, - PhysicsWorldResource resource, Ref ref, RigidBodyKey bodyKey, double startY) { @@ -114,14 +116,13 @@ private static boolean bodyAndEntityMovedDown(Store store, return false; } double transformY = transform.getPosition().y; - Optional state = resource.query(new RigidBodyStateQuery(bodyKey)) - .completion() - .toCompletableFuture() - .join(); - if (state.isEmpty()) { + PhysicsStoreBodySnapshot snapshot = physicsStore(store.getExternalData().getWorld()) + .getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(bodyKey.value()); + if (snapshot == null) { return false; } - float bodyY = state.get().pose().position().y; + float bodyY = snapshot.position().y; return transformY < startY - 0.05 && bodyY < startY - 0.05f; } @@ -138,30 +139,35 @@ private static SpaceId liveTestSpaceId(PhysicsWorldResource resource, World worl PhysicsSpaceSettings.defaults()); } - private static void submitLiveBody(PhysicsWorldResource resource, + private static void submitLiveBody(Store store, SpaceId spaceId, RigidBodyKey bodyKey, Vector3d visualPosition) { - resource.submitCommands(0L, - 1, - commands -> commands.spawnBody(bodyKey, spawn -> spawn - .space(spaceId) - .shape(PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f)) - .mass(1.0f) - .type(PhysicsBodyType.DYNAMIC) - .position((float) visualPosition.x, - (float) visualPosition.y, - (float) visualPosition.z) - .settings(RigidBodySpawnSettings.defaults()) - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.PERSISTENT))) - .firstRejected() - .toCompletableFuture() - .join() - .ifPresent(result -> { - throw new IllegalStateException("spawn live crucible body command " - + result.commandSequence() + " rejected: " + result.message()); - }); + PhysicsStoreThreading.requireWorldThread(store, "add Crucible live PhysicsStore body row"); + BodyRowDescriptor row = PhysicsBodyRows.dynamicBody( + PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), + bodyKey.value(), + new Vector3f((float) visualPosition.x, + (float) visualPosition.y, + (float) visualPosition.z), + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 1.0f, + RigidBodySpawnSettings.defaults(), + null, + PhysicsBodyPersistenceMode.PERSISTENT); + store.addEntity(PhysicsStoreEntities.bodyHolder(store, + row.bodyUuid(), + row.body(), + row.dynamics(), + row.target(), + row.collider(), + row.shape(), + row.material(), + row.filter()), AddReason.SPAWN); + } + + private static Store physicsStore(World world) { + return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); } private static Ref spawnLiveBlockBody(Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 7b0f5e6c..280083e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -89,6 +89,35 @@ public static void putSpaceSettings(@Nonnull Store store, putSpaceSettings(store, ref, spaceUuid, settings); } + public static void putSpaceGravity(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f gravity) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putSpaceGravity(store, ref, spaceUuid, gravity); + } + + public static void putSpaceGravity(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull UUID spaceUuid, + @Nonnull Vector3f gravity) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(ref, "ref"); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(gravity, "gravity"); + PhysicsStoreThreading.requireWorldThread(store, "update PhysicsStore space gravity"); + SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); + if (space == null) { + throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid + + " row has no SpaceComponent"); + } + SpaceComponent updated = space.clone(); + updated.setGravity(gravity); + store.putComponent(ref, SpaceComponent.getComponentType(), updated); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(spaceUuid); + } + public static void putSpaceSettings(@Nonnull Store store, @Nonnull Ref ref, @Nonnull UUID spaceUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java index 5daddaf7..82ebdd81 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java @@ -17,6 +17,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettingValue; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.api.BackendId; @@ -25,6 +26,7 @@ import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.joml.Vector3f; /** * Applies changed backend-facing space settings after space binding and before body binding. @@ -76,10 +78,15 @@ static boolean applyIfBound(@Nonnull Store store, if (backendRuntime == null) { return false; } + SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); SolverSettingsComponent solverSettings = store.getComponent(ref, SolverSettingsComponent.getComponentType()); ExtensionSettingsComponent extensionSettings = store.getComponent(ref, ExtensionSettingsComponent.getComponentType()); + if (space != null) { + Vector3f gravity = space.getGravity(); + backendRuntime.setGravity(handle.value(), gravity.x, gravity.y, gravity.z); + } applyBackendSettings(backendRuntime, handle, solverSettings != null ? solverSettings : new SolverSettingsComponent(), From 148a6c7412962fe36f22a92f93f51e1bbec6969f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 14:56:40 +0200 Subject: [PATCH 080/534] fix(physicsstore): destroy stale backend body rows Signed-off-by: Blovien --- .../systems/BodyBindingSystem.java | 71 +++++++++++++++++++ .../systems/JointBindingSystem.java | 20 +++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index d907b06a..c3413fbc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -17,6 +17,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; @@ -27,6 +28,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -56,11 +59,73 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); + if (!removeStaleBodies(store, runtime, identity, restore)) { + return; + } BiConsumer, CommandBuffer> collector = (chunk, _) -> bindBodies(runtime, identity, restore, chunk); store.forEachChunk(systemIndex, collector); } + private static boolean removeStaleBodies(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore) { + List staleBodies = new ArrayList<>(); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachBodyHandle(spaceHandle, + bodyId -> collectStaleBody(store, + runtime, + identity, + restore, + staleBodies, + spaceHandle, + backendRuntime, + bodyId))); + if (restore.isFailed()) { + return false; + } + for (BoundBody body : staleBodies) { + try { + body.backendRuntime().removeBody(body.spaceHandle().value(), body.bodyHandle().value()); + } catch (RuntimeException exception) { + restore.markFailed("PhysicsStore body " + body.bodyUuid() + + " failed backend removal: " + exception.getMessage()); + return false; + } + identity.removeBodyHandle(body.bodyHandle()); + runtime.removeBodyHandle(body.bodyUuid()); + } + return true; + } + + private static void collectStaleBody(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull List staleBodies, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull PhysicsBackendRuntime backendRuntime, + long bodyId) { + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + if (metadata == null) { + restore.markFailed("PhysicsStore backend body " + bodyId + + " has no runtime snapshot metadata"); + return; + } + Ref ref = PhysicsStoreSystemSupport.refForUuid(identity, metadata.bodyUuid()); + BodyComponent body = PhysicsStoreSystemSupport.component(store, + ref, + BodyComponent.getComponentType()); + if (body != null) { + return; + } + staleBodies.add(new BoundBody(metadata.bodyUuid(), + spaceHandle, + new BackendBodyHandle(bodyId), + backendRuntime)); + } + private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @@ -224,4 +289,10 @@ public Set> getDependencies() { return DEPENDENCIES; } + private record BoundBody(@Nonnull UUID bodyUuid, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } + } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index 0fb99f9e..d02e674e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -69,7 +69,11 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, removeJoint(runtime, identity, jointUuid, joint); continue; } - if (runtime.getJointHandle(jointUuid) != null) { + BackendJointHandle existing = runtime.getJointHandle(jointUuid); + if (existing != null) { + if (!endpointsBound(runtime, joint)) { + removeJoint(runtime, identity, jointUuid, joint); + } continue; } bindJoint(runtime, identity, restore, chunk.getReferenceTo(index), jointUuid, joint); @@ -142,6 +146,20 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, } } + private static boolean endpointsBound(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull JointComponent joint) { + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); + BackendSpaceHandle bodyASpace = runtime.getBodySpaceHandle(joint.getBodyAUuid()); + BackendSpaceHandle bodyBSpace = runtime.getBodySpaceHandle(joint.getBodyBUuid()); + return spaceHandle != null + && runtime.getBodyHandle(joint.getBodyAUuid()) != null + && runtime.getBodyHandle(joint.getBodyBUuid()) != null + && bodyASpace != null + && bodyBSpace != null + && bodyASpace.value() == spaceHandle.value() + && bodyBSpace.value() == spaceHandle.value(); + } + private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull UUID jointUuid, From cfb1359199cbd3120d643530886a46d1e31a229d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:00:22 +0200 Subject: [PATCH 081/534] refactor(crucible): use physicsstore rows in api stability tests Signed-off-by: Blovien --- .../crucible/ImpulseApiCrucibleTests.java | 211 ++++++++++++------ 1 file changed, 140 insertions(+), 71 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index afb1d4ba..7423b897 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -1,5 +1,13 @@ package dev.hytalemodding.impulse.core.internal.crucible; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; @@ -8,23 +16,28 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import java.util.UUID; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; +import org.joml.Vector3f; /** * Crucible suites that exercise Impulse API behavior inside a live Hytale server. @@ -88,19 +101,19 @@ private static CrucibleSuite runtimeStabilitySuite() { "Verifies explicit space lifecycle, detached cleanup, and settings retention", Set.of("smoke", "stability"), List.of( - CrucibleTestCase.sync("fresh world has no spaces", - ImpulseApiCrucibleTests::freshWorldHasNoSpaces, - "Fresh PhysicsWorldResource unexpectedly has spaces"), - CrucibleTestCase.sync("created explicit space lifecycle works", + CrucibleTestCase.async("space count round trip", + ImpulseApiCrucibleTests::spaceCountRoundTrip, + "PhysicsStore space count did not return to its previous value"), + CrucibleTestCase.async("created explicit space lifecycle works", ImpulseApiCrucibleTests::createdExplicitSpaceLifecycleWorks, "Explicit space was not registered correctly"), - CrucibleTestCase.sync("clear populated spaces", + CrucibleTestCase.async("clear populated spaces", ImpulseApiCrucibleTests::clearPopulatedSpaces, - "clearAllSpaces did not remove populated runtime spaces"), - CrucibleTestCase.sync("detached unregister removes body", + "PhysicsStore row cleanup did not remove populated runtime spaces"), + CrucibleTestCase.async("detached unregister removes body", ImpulseApiCrucibleTests::detachedUnregisterRemovesBackendBody, "Detached unregister did not remove the backend body"), - CrucibleTestCase.sync("settings round trip", + CrucibleTestCase.async("settings round trip", ImpulseApiCrucibleTests::settingsRoundTrip, "PhysicsSpaceSettings did not retain runtime settings"))); } @@ -165,81 +178,126 @@ private static boolean createSpaceAndBody() { } } - private static boolean freshWorldHasNoSpaces() { - PhysicsWorldResource resource = new PhysicsWorldRuntimeResource(); - return resource.getSpaceIds().isEmpty(); + private static CompletionStage spaceCountRoundTrip(@Nonnull CrucibleContext context) { + try { + World world = context.world(); + PhysicsWorldResource resource = physicsResource(world); + Store store = physicsStore(world); + int previousCount = resource.getSpaceCount(); + SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), + "crucible", + PhysicsSpaceSettings.defaults()); + PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); + return CompletableFuture.completedFuture(resource.getSpaceCount() == previousCount + && !resource.hasSpace(spaceId)); + } catch (ReflectiveOperationException e) { + return CompletableFuture.failedFuture(e); + } } - private static boolean createdExplicitSpaceLifecycleWorks() { - PhysicsWorldResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", - PhysicsSpaceSettings.streamingWorldCollision()); + private static CompletionStage createdExplicitSpaceLifecycleWorks( + @Nonnull CrucibleContext context) { try { - return resource.hasSpace(spaceId) + World world = context.world(); + PhysicsWorldResource resource = physicsResource(world); + Store store = physicsStore(world); + SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), + "crucible", + PhysicsSpaceSettings.streamingWorldCollision()); + boolean registered = resource.hasSpace(spaceId) && resource.getSpaceSettings(spaceId).getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING; - } finally { - resource.clearAllSpaces("crucible"); + PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); + return CompletableFuture.completedFuture(registered && !resource.hasSpace(spaceId)); + } catch (ReflectiveOperationException e) { + return CompletableFuture.failedFuture(e); } } - private static boolean clearPopulatedSpaces() { - PhysicsWorldResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", - PhysicsSpaceSettings.defaults()); - submitCrucibleBox(resource, spaceId, RigidBodyKey.random()); + private static CompletionStage clearPopulatedSpaces( + @Nonnull CrucibleContext context) { + return populatedBodyCleanup(context, true); + } - resource.clearAllSpaces("crucible"); - return resource.getSpaceIds().isEmpty(); + private static CompletionStage detachedUnregisterRemovesBackendBody( + @Nonnull CrucibleContext context) { + return populatedBodyCleanup(context, false); } - private static boolean detachedUnregisterRemovesBackendBody() { - PhysicsWorldResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", - PhysicsSpaceSettings.defaults()); + private static CompletionStage populatedBodyCleanup( + @Nonnull CrucibleContext context, + boolean checkSpaceRemoval) { try { - RigidBodyKey bodyKey = RigidBodyKey.random(); - submitCrucibleBox(resource, spaceId, bodyKey); - - resource.destroyBody(bodyKey); - boolean spaceEmpty = resource.query(new SpaceBodyCountQuery(spaceId)) - .completion() - .toCompletableFuture() - .join() == 0; - return spaceEmpty && resource.getBodyRegistrationViews().isEmpty(); - } finally { - resource.clearAllSpaces("crucible"); + World world = context.world(); + PhysicsWorldResource resource = physicsResource(world); + Store store = physicsStore(world); + SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), + "crucible", + PhysicsSpaceSettings.defaults()); + Ref bodyRef = addCrucibleBox(store, spaceId, UUID.randomUUID()); + return context.waitApproxTicksOnWorld(4) + .thenCompose(_ -> removeBodyRowAndWait(context, store, bodyRef)) + .thenApply(_ -> { + boolean spaceEmpty = PhysicsStoreDiagnostics.bodyCount(store, spaceId) == 0; + boolean noRegistrations = resource.getBodyRegistrationViews().isEmpty(); + boolean removedSpace = true; + if (checkSpaceRemoval || spaceEmpty) { + PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); + removedSpace = !resource.hasSpace(spaceId); + } + return spaceEmpty && noRegistrations && removedSpace; + }); + } catch (ReflectiveOperationException e) { + return CompletableFuture.failedFuture(e); } } - private static void submitCrucibleBox(@Nonnull PhysicsWorldResource resource, + private static CompletionStage removeBodyRowAndWait(@Nonnull CrucibleContext context, + @Nonnull Store store, + @Nonnull Ref bodyRef) { + if (bodyRef.isValid()) { + store.removeEntity(bodyRef, store.getRegistry().newHolder(), RemoveReason.REMOVE); + } + try { + return context.waitApproxTicksOnWorld(4); + } catch (ReflectiveOperationException e) { + return CompletableFuture.failedFuture(e); + } + } + + @Nonnull + private static Ref addCrucibleBox(@Nonnull Store store, @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey) { - resource.submitCommands(0L, - 1, - commands -> commands.spawnBody(bodyKey, spawn -> spawn - .space(spaceId) - .shape(PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f)) - .mass(1.0f) - .type(PhysicsBodyType.DYNAMIC) - .position(0f, 5f, 0f) - .settings(RigidBodySpawnSettings.defaults()) - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY))) - .firstRejected() - .toCompletableFuture() - .join() - .ifPresent(result -> { - throw new IllegalStateException("spawn crucible box command " - + result.commandSequence() + " rejected: " + result.message()); - }); + @Nonnull UUID bodyUuid) { + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId); + BodyRowDescriptor row = PhysicsBodyRows.dynamicBody(spaceUuid, + bodyUuid, + new Vector3f(0.0f, 5.0f, 0.0f), + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 1.0f, + RigidBodySpawnSettings.defaults(), + null, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + return store.addEntity(PhysicsStoreEntities.bodyHolder(store, + row.bodyUuid(), + row.body(), + row.dynamics(), + row.target(), + row.collider(), + row.shape(), + row.material(), + row.filter()), AddReason.SPAWN); } - private static boolean settingsRoundTrip() { - PhysicsWorldResource resource = new PhysicsWorldRuntimeResource(); + private static CompletionStage settingsRoundTrip(@Nonnull CrucibleContext context) { + World world; + try { + world = context.world(); + } catch (ReflectiveOperationException e) { + return CompletableFuture.failedFuture(e); + } + PhysicsWorldResource resource = physicsResource(world); + Store store = physicsStore(world); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); settings.getWorldCollisionSettings().setWorldCollisionRadius(9); @@ -275,7 +333,8 @@ private static boolean settingsRoundTrip() { settings); try { PhysicsSpaceSettings copy = resource.getSpaceSettings(spaceId); - return copy.getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING + boolean roundTrip = + copy.getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING && copy.getWorldCollisionSettings().getWorldCollisionRadius() == 9 && copy.getWorldCollisionSettings().getWorldCollisionBodyRadius() == 5 && copy.getWorldCollisionSettings().getWorldCollisionTtlTicks() == 77 @@ -305,11 +364,21 @@ private static boolean settingsRoundTrip() { && copy.getVisualMaterializationSettings().getDetachedVisualMaxSpawnsPerTick() == 33 && copy.getVisualMaterializationSettings().getDetachedVisualMaxMaterialized() == 444 && "Rock_Stone".equals(copy.getVisualMaterializationSettings().getDetachedVisualBlockType()); + return CompletableFuture.completedFuture(roundTrip); } finally { - resource.clearAllSpaces("crucible"); + PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); } } + private static PhysicsWorldResource physicsResource(@Nonnull World world) { + Store store = world.getEntityStore().getStore(); + return store.getResource(PhysicsWorldResource.getResourceType()); + } + + private static Store physicsStore(@Nonnull World world) { + return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + } + private static boolean stepSpaceDoesNotThrow() { PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); int spaceId = runtime.createSpace(SpaceId.next()); From 9e4771ed296a81bbd565cfafc95367e9ab096aed Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:05:01 +0200 Subject: [PATCH 082/534] refactor(crucible): populate benchmarks through physicsstore rows Signed-off-by: Blovien --- ...tachedStreamingBenchmarkCrucibleTests.java | 51 ++++------ ...pulseRapierBodyBenchmarkCrucibleTests.java | 78 +++++++-------- .../crucible/PhysicsStoreCrucibleSupport.java | 98 +++++++++++++++++++ 3 files changed, 153 insertions(+), 74 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 4473443c..3942c2c6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; @@ -23,7 +24,6 @@ import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.internal.simulation.query.WorldCollisionPrewarmEnvelopeQuery; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; @@ -42,6 +42,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; @@ -49,6 +50,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3d; +import org.joml.Vector3f; /** * Benchmark-oriented Crucible scenario for detached bodies using streamed world collision. @@ -136,6 +138,7 @@ private static final class StageRunner { private final StagePlan plan; private final World world; private final PhysicsWorldRuntimeResource physics; + private final Store physicsStore; private final PhysicsRuntimeProfilingResource runtimeProfiling; private final WorldCollisionProfilingResource worldCollisionProfiling; private final PhysicsWorldSettings previousWorldSettings; @@ -148,6 +151,7 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) this.world = context.world(); Store store = world.getEntityStore().getStore(); this.physics = PhysicsWorldRuntimeResource.require(store); + this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.worldCollisionProfiling = store.getResource( WorldCollisionProfilingResource.getResourceType()); @@ -329,8 +333,7 @@ private CompletionStage contextWait(int ticks) { private void clearStageState() { releaseRetainedChunks(); - physics.clearBodies(); - physics.clearAllSpaces(world.getName()); + PhysicsStoreCrucibleSupport.clearAll(physicsStore); runtimeProfiling.reset(); worldCollisionProfiling.reset(); worldCollisionProfiling.clearDiagnosticRetainedSections(); @@ -381,33 +384,21 @@ private void spawnDetachedBodies(@Nonnull SpaceId spaceId, int count) { 0.25f, PhysicsCollisionFilters.DYNAMIC_BODY, PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - long bodyKeyRunId = RigidBodyKey.random().mostSignificantBits(); - physics.submitCommands(Math.max(0L, world.getTick()), - 1, - commands -> commands.spawnBodies(count, - spaceId, - box, - 1.0f, - PhysicsBodyType.DYNAMIC, - settings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { - for (int i = 0; i < count; i++) { - spawns.body(bodyKeyRunId, - i + 1L, - (float) layout.positionX(i), - (float) layout.positionY(), - (float) layout.positionZ(i)); - } - })) - .firstRejected() - .toCompletableFuture() - .join() - .ifPresent(result -> { - throw new IllegalStateException("spawn detached streaming benchmark bodies command " - + result.commandSequence() + " rejected: " + result.message()); - }); + for (int i = 0; i < count; i++) { + PhysicsStoreCrucibleSupport.addBody(physicsStore, + spaceId, + UUID.randomUUID(), + new Vector3f((float) layout.positionX(i), + (float) layout.positionY(), + (float) layout.positionZ(i)), + box, + PhysicsBodyType.DYNAMIC, + 1.0f, + settings, + null, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + } } private BenchmarkChunks benchmarkChunks(int count) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 4eca0608..f52eade6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBodyType; @@ -21,7 +22,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; @@ -37,6 +37,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; @@ -44,6 +45,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3d; +import org.joml.Vector3f; /** * Benchmark-oriented Crucible scenario for Rapier detached body-only fixed substeps. @@ -140,6 +142,7 @@ private static final class MatrixRunner { private final World world; private final Store store; private final PhysicsWorldRuntimeResource physics; + private final Store physicsStore; private final PhysicsRuntimeProfilingResource runtimeProfiling; private final WorldCollisionProfilingResource worldCollisionProfiling; private final PhysicsWorldSettings previousWorldSettings; @@ -153,6 +156,7 @@ private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) this.world = context.world(); this.store = world.getEntityStore().getStore(); this.physics = PhysicsWorldRuntimeResource.require(store); + this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.worldCollisionProfiling = store.getResource( WorldCollisionProfilingResource.getResourceType()); @@ -248,47 +252,34 @@ private CompletionStage populateBenchmarkSpace(@Nonnull SpaceId spa 0.25f, PhysicsCollisionFilters.DYNAMIC_BODY, PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - long bodyKeyRunId = RigidBodyKey.random().mostSignificantBits(); - return physics.submitCommands(Math.max(0L, world.getTick()), - 2, - commands -> { - commands.spawnBody(RigidBodyKey.random(), - body -> body.space(spaceId) - .plane(GROUND_Y) - .type(PhysicsBodyType.STATIC) - .settings(groundSettings) - .temporary() - .runtimeOnly()); - commands.spawnBodies(matrixCase.count(), - spaceId, - PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f), - 1.0f, - PhysicsBodyType.DYNAMIC, - bodySettings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { - for (int i = 0; i < matrixCase.count(); i++) { - spawns.body(bodyKeyRunId, - i + 1L, - (float) layout.positionX(i), - (float) layout.positionY(), - (float) layout.positionZ(i)); - } - }); - }) - .completionSummary() - .handle((completion, failure) -> { - if (failure != null) { - return StartedCase.failed(failure.getMessage()); - } - return completion.firstRejected() - .map(result -> StartedCase.failed("prepare command " - + result.commandSequence() - + " rejected: " - + result.message())) - .orElseGet(() -> StartedCase.started(spaceId)); - }); + PhysicsStoreCrucibleSupport.addBody(physicsStore, + spaceId, + UUID.randomUUID(), + new Vector3f(0.0f, GROUND_Y, 0.0f), + PhysicsShapeSpec.plane(GROUND_Y), + PhysicsBodyType.STATIC, + 0.0f, + groundSettings, + null, + PhysicsBodyKind.TEMPORARY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); + for (int i = 0; i < matrixCase.count(); i++) { + PhysicsStoreCrucibleSupport.addBody(physicsStore, + spaceId, + UUID.randomUUID(), + new Vector3f((float) layout.positionX(i), + (float) layout.positionY(), + (float) layout.positionZ(i)), + box, + PhysicsBodyType.DYNAMIC, + 1.0f, + bodySettings, + null, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + } + return CompletableFuture.completedFuture(StartedCase.started(spaceId)); } private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, @@ -368,8 +359,7 @@ private CompletionStage contextWait(int ticks) { private void clearCaseState() { removeBenchmarkEntities(); physics.clearSyntheticVisualInterests(); - physics.clearBodies(); - physics.clearAllSpaces(world.getName()); + PhysicsStoreCrucibleSupport.clearAll(physicsStore); runtimeProfiling.reset(); worldCollisionProfiling.reset(); worldCollisionProfiling.clearDiagnosticRetainedSections(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java new file mode 100644 index 00000000..315612f0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -0,0 +1,98 @@ +package dev.hytalemodding.impulse.core.internal.crucible; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3f; + +/** + * Internal Crucible helpers for authoring and clearing live PhysicsStore rows. + */ +final class PhysicsStoreCrucibleSupport { + + private PhysicsStoreCrucibleSupport() { + } + + @Nonnull + static Store physicsStore(@Nonnull World world) { + return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + } + + static void clearAll(@Nonnull Store store) { + PhysicsStoreThreading.requireWorldThread(store, "clear Crucible PhysicsStore rows"); + store.forEachEntityParallel(UuidComponent.getComponentType(), + (index, chunk, commandBuffer) -> commandBuffer.removeEntity( + chunk.getReferenceTo(index), + RemoveReason.REMOVE)); + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear(); + store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings(); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); + store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); + store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); + store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); + store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()).clear(); + } + + @Nonnull + static Ref addBody(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + @Nonnull PhysicsBodyType bodyType, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + PhysicsStoreThreading.requireWorldThread(store, "add Crucible PhysicsStore body row"); + BodyRowDescriptor row = PhysicsBodyRows.body( + PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), + bodyUuid, + bodyCenter, + shape, + bodyType, + mass, + settings, + linearVelocity, + kind, + persistenceMode); + return store.addEntity(PhysicsStoreEntities.bodyHolder(store, + row.bodyUuid(), + row.body(), + row.dynamics(), + row.target(), + row.collider(), + row.shape(), + row.material(), + row.filter()), AddReason.SPAWN); + } +} From f96913ccb0c5b72ccd50fb4d132fa362f395da50 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:08:21 +0200 Subject: [PATCH 083/534] fix(physicsstore): route lifecycle cleanup through store rows Signed-off-by: Blovien --- .../crucible/PhysicsStoreCrucibleSupport.java | 25 +---------- .../PhysicsStoreRuntimeCleaner.java | 41 +++++++++++++++++++ .../owner/PhysicsOwnerLifecycleSystem.java | 26 ++++++++++-- ...PersistentPhysicsSpaceBootstrapSystem.java | 31 ++++++++++++-- 4 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index 315612f0..cb1fcf83 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -2,29 +2,20 @@ import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -46,19 +37,7 @@ static Store physicsStore(@Nonnull World world) { } static void clearAll(@Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "clear Crucible PhysicsStore rows"); - store.forEachEntityParallel(UuidComponent.getComponentType(), - (index, chunk, commandBuffer) -> commandBuffer.removeEntity( - chunk.getReferenceTo(index), - RemoveReason.REMOVE)); - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear(); - store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings(); - store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); - store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); - store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); - store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); - store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); - store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()).clear(); + PhysicsStoreRuntimeCleaner.clearAll(store); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java new file mode 100644 index 00000000..66f55b8b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -0,0 +1,41 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import javax.annotation.Nonnull; + +/** + * Internal full reset helper for authoritative PhysicsStore runtime state. + */ +public final class PhysicsStoreRuntimeCleaner { + + private PhysicsStoreRuntimeCleaner() { + } + + public static void clearAll(@Nonnull Store store) { + PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore runtime rows"); + store.forEachEntityParallel(UuidComponent.getComponentType(), + (index, chunk, commandBuffer) -> commandBuffer.removeEntity( + chunk.getReferenceTo(index), + RemoveReason.REMOVE)); + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear(); + store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings(); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); + store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); + store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); + store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); + store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()).clear(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java index 99310606..827f43e4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java @@ -4,7 +4,11 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.system.StoreSystem; import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -58,14 +62,14 @@ public void onSystemRemovedFromStore(@Nonnull Store store) { PhysicsOwnerResource owner = store.getResource(ownerResourceType); PhysicsWorldResource physics = store.getResource(physicsWorldResourceType); PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(physics); - RuntimeException clearFailure = tryClearSpaces(physics, worldName); + RuntimeException clearFailure = tryClearSpaces(store, physics, worldName); boolean closedOwner = closeOwner(owner); runtime.detachOwnerExecutor(owner); runtime.detachEntityStore(store); // Retry if it failed before, this could happen. if (clearFailure != null && closedOwner) { - clearFailure = tryClearSpaces(physics, worldName); + clearFailure = tryClearSpaces(store, physics, worldName); } if (clearFailure != null) { LOGGER.at(Level.WARNING).log("Failed to clear physics spaces for world %s: %s", @@ -118,13 +122,27 @@ private static String worldName(@Nonnull Store store) { } @Nullable - private static RuntimeException tryClearSpaces(@Nonnull PhysicsWorldResource physics, + private static RuntimeException tryClearSpaces(@Nonnull Store store, + @Nonnull PhysicsWorldResource physics, @Nonnull String worldName) { try { - physics.clearAllSpaces(worldName); + Store physicsStore = physicsStoreOrNull(store); + if (physicsStore != null) { + PhysicsStoreRuntimeCleaner.clearAll(physicsStore); + } else { + physics.clearAllSpaces(worldName); + } return null; } catch (RuntimeException exception) { return exception; } } + + @Nullable + private static Store physicsStoreOrNull(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + return world instanceof PhysicsStoreWorld physicsWorld + ? physicsWorld.getPhysicsStore().getStore() + : null; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java index d490f3cd..0fcd7f75 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java @@ -8,6 +8,8 @@ import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; @@ -15,6 +17,7 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRestorePreflight; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsSpaceState; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; @@ -66,6 +69,14 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { World world = store.getExternalData().getWorld(); PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(store); + if (physicsStoreOrNull(store) != null) { + stripRuntimePhysicsStateForRestore(store, runtime, world); + persistent.clearRuntimeRestorePending(); + LOGGER.at(Level.INFO).log("Skipped legacy PersistentPhysicsWorldResource restore in " + + "world %s because authoritative PhysicsStore persistence is active", + world.getName()); + return; + } PersistentPhysicsSpaceState[] spaces = persistent.getSpaces(); String validationFailure = PersistentPhysicsRestorePreflight.validate(persistent); if (validationFailure != null) { @@ -128,10 +139,15 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { private static void stripRuntimePhysicsStateForRestore(@Nonnull Store store, @Nonnull PhysicsWorldResource runtime, @Nonnull World world) { - PhysicsOwnerBridge.run(store, "strip runtime physics state for restore", () -> { - runtime.clearAllSpaces(world.getName()); - runtime.clearBodies(); - }); + Store physicsStore = physicsStoreOrNull(store); + if (physicsStore != null) { + PhysicsStoreRuntimeCleaner.clearAll(physicsStore); + } else { + PhysicsOwnerBridge.run(store, "strip runtime physics state for restore", () -> { + runtime.clearAllSpaces(world.getName()); + runtime.clearBodies(); + }); + } store.forEachEntityParallel(ATTACHMENT_TYPE, (index, archetypeChunk, commandBuffer) -> { @@ -165,6 +181,13 @@ private static void stripRuntimePhysicsStateForRestore(@Nonnull Store physicsStoreOrNull(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + return world instanceof PhysicsStoreWorld physicsWorld + ? physicsWorld.getPhysicsStore().getStore() + : null; + } + @Nonnull @Override public SystemGroup getGroup() { From 0fde8ecbe68fe94621d0df0411b129c65e25b92d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:09:45 +0200 Subject: [PATCH 084/534] fix(physicsstore): allow world settings compatibility writes Signed-off-by: Blovien --- .../resources/PhysicsWorldRuntimeResource.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index d317a883..c6bac7f6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -431,8 +431,12 @@ public PhysicsWorldSettings getWorldSettings() { @Override public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { - requireLegacyMutationAllowed("set physics world settings"); PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); + if (isAuthoritativePhysicsStoreActive()) { + setWorldSettingsDirect(requested); + return; + } + requireLegacyMutationAllowed("set physics world settings"); runOwnerMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); } @@ -440,8 +444,13 @@ public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { @Override public PhysicsMutationHandle setWorldSettingsAsync( @Nonnull PhysicsWorldSettings settings) { - requireLegacyMutationAllowed("set physics world settings"); PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); + if (isAuthoritativePhysicsStoreActive()) { + return enqueueAuthoritativePhysicsStoreMutation("set physics world settings", + null, + _ -> setWorldSettingsDirect(requested)); + } + requireLegacyMutationAllowed("set physics world settings"); return enqueueOwnerMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); } From cda7b75f999be4fd0c720c2e099b23bab5965504 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:20:53 +0200 Subject: [PATCH 085/534] fix(physicsstore): apply store-owned world step settings Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 4 ++ .../PhysicsWorldSettingsResource.java | 42 +++++++++++++++ .../systems/SpaceBindingSystem.java | 15 +++++- .../systems/StepSubmissionSystem.java | 17 ++++++- .../PhysicsWorldRuntimeResource.java | 51 ++++++++++++++++++- .../physicsstore/PhysicsStoreTypes.java | 13 +++++ 6 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 31b25545..6a78cb2b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyCommandApplicationSystem; @@ -142,6 +143,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setRuntimeResourceType(registry.registerResource( PhysicsRuntimeResource.class, PhysicsRuntimeResource::new)); + PhysicsStoreTypes.setWorldSettingsResourceType(registry.registerResource( + PhysicsWorldSettingsResource.class, + PhysicsWorldSettingsResource::new)); PhysicsStoreTypes.setSpaceCompatibilityIndexResourceType(registry.registerResource( PhysicsSpaceCompatibilityIndexResource.class, PhysicsSpaceCompatibilityIndexResource::new)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java new file mode 100644 index 00000000..50af28a9 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java @@ -0,0 +1,42 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import javax.annotation.Nonnull; + +/** + * World-level PhysicsStore step settings. + */ +public final class PhysicsWorldSettingsResource implements Resource { + + @Nonnull + private final PhysicsWorldSettings settings = new PhysicsWorldSettings(); + + public PhysicsWorldSettingsResource() { + } + + @Nonnull + public PhysicsWorldSettings getSettings() { + return new PhysicsWorldSettings(settings); + } + + public void setSettings(@Nonnull PhysicsWorldSettings settings) { + this.settings.copyFrom(settings); + } + + @Nonnull + @Override + public PhysicsWorldSettingsResource clone() { + PhysicsWorldSettingsResource copy = new PhysicsWorldSettingsResource(); + copy.setSettings(settings); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.worldSettingsResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index 20a2b4f7..4ab33216 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -19,10 +19,12 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -53,8 +55,11 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource( PhysicsIdentityIndexResource.getResourceType()); + PhysicsStepMode stepMode = store.getResource(PhysicsWorldSettingsResource.getResourceType()) + .getSettings() + .getStepMode(); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindChunk(runtime, compatibility, identity, restore, chunk); + (chunk, _) -> bindChunk(runtime, compatibility, identity, restore, stepMode, chunk); store.forEachChunk(systemIndex, collector); } @@ -62,6 +67,7 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull PhysicsStepMode stepMode, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); @@ -77,6 +83,7 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, compatibility, identity, restore, + stepMode, chunk.getReferenceTo(index), spaceUuid, space, @@ -89,6 +96,7 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull PhysicsStepMode stepMode, @Nonnull Ref ref, @Nonnull UUID spaceUuid, @Nonnull SpaceComponent space, @@ -117,6 +125,11 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, BackendSpaceHandle handle = null; try { handle = new BackendSpaceHandle(backendRuntime.createSpace(compatibilitySpaceId)); + if (stepMode == PhysicsStepMode.CCD + && !backendRuntime.supportsContinuousCollision(handle.value())) { + throw new IllegalStateException("backend " + backendId.value() + + " does not support CCD step mode"); + } Vector3f gravity = space.getGravity(); backendRuntime.setGravity(handle.value(), gravity.x, gravity.y, gravity.z); SpaceSettingsApplicationSystem.applyBackendSettings(backendRuntime, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index 003eb249..d2b888b7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -8,6 +8,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.util.Set; import javax.annotation.Nonnull; @@ -31,9 +34,19 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (safeDt <= 0.0f) { return; } + PhysicsWorldSettings settings = store.getResource(PhysicsWorldSettingsResource.getResourceType()) + .getSettings(); + int steps = PhysicsStepCountPolicy.resolveStepCount(safeDt, + settings.getSimulationSteps(), + settings.getMaxStepDt(), + settings.getStepMode()); + float stepDt = safeDt / steps; PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> - backendRuntime.step(spaceHandle.value(), safeDt)); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + for (int step = 0; step < steps; step++) { + backendRuntime.step(spaceHandle.value(), stepDt); + } + }); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index c6bac7f6..fbcfd8c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -18,7 +18,9 @@ import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; @@ -303,6 +305,10 @@ private boolean isAuthoritativePhysicsStoreActive() { return PhysicsStoreEarlyPluginProbe.isAvailable(); } + private boolean hasAttachedAuthoritativePhysicsStore() { + return isAuthoritativePhysicsStoreActive() && owningStore != null; + } + @Nonnull private World requireAuthoritativeWorld(@Nonnull String operation) { Store entityStore = owningStore; @@ -385,6 +391,25 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( return settings; } + private static void validateAuthoritativeStepModeSupported( + @Nonnull Store store, + @Nonnull PhysicsStepMode stepMode) { + if (stepMode != PhysicsStepMode.CCD) { + return; + } + List unsupportedSpaces = new ArrayList<>(); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { + if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + unsupportedSpaces.add(spaceUuid + " backend=" + backendId.value()); + } + }); + if (!unsupportedSpaces.isEmpty()) { + throw new IllegalArgumentException("CCD step mode is not supported by PhysicsStore " + + "spaces: " + unsupportedSpaces); + } + } + @Nonnull private PhysicsMutationHandle enqueueAuthoritativePhysicsStoreMutation( @Nonnull String operation, @@ -426,12 +451,23 @@ public T callOwner(@Nonnull String operation, @Nonnull @Override public PhysicsWorldSettings getWorldSettings() { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics world settings") + .getResource(PhysicsWorldSettingsResource.getResourceType()) + .getSettings(); + } return simulationRuntime.getWorldSettings(); } @Override public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); + if (hasAttachedAuthoritativePhysicsStore()) { + setAuthoritativeWorldSettings( + authoritativePhysicsStore("set physics world settings"), + requested); + return; + } if (isAuthoritativePhysicsStoreActive()) { setWorldSettingsDirect(requested); return; @@ -445,10 +481,14 @@ public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { public PhysicsMutationHandle setWorldSettingsAsync( @Nonnull PhysicsWorldSettings settings) { PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); - if (isAuthoritativePhysicsStoreActive()) { + if (hasAttachedAuthoritativePhysicsStore()) { return enqueueAuthoritativePhysicsStoreMutation("set physics world settings", null, - _ -> setWorldSettingsDirect(requested)); + store -> setAuthoritativeWorldSettings(store, requested)); + } + if (isAuthoritativePhysicsStoreActive()) { + setWorldSettingsDirect(requested); + return PhysicsMutationHandle.completed("set physics world settings", null); } requireLegacyMutationAllowed("set physics world settings"); return enqueueOwnerMutation("set physics world settings", @@ -460,6 +500,13 @@ private void setWorldSettingsDirect(@Nonnull PhysicsWorldSettings settings) { simulationRuntime.setWorldSettings(settings); } + private void setAuthoritativeWorldSettings(@Nonnull Store store, + @Nonnull PhysicsWorldSettings settings) { + validateAuthoritativeStepModeSupported(store, settings.getStepMode()); + store.getResource(PhysicsWorldSettingsResource.getResourceType()).setSettings(settings); + simulationRuntime.setWorldSettings(settings); + } + @Nonnull @Override public SpaceId createSpace(@Nonnull BackendId backendId) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index a5a0d942..748c109f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; @@ -83,6 +84,8 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType runtimeResourceType; @Nullable + private static ResourceType worldSettingsResourceType; + @Nullable private static ResourceType spaceCompatibilityIndexResourceType; @Nullable @@ -204,6 +207,11 @@ public static void setRuntimeResourceType( runtimeResourceType = Objects.requireNonNull(type, "type"); } + public static void setWorldSettingsResourceType( + @Nonnull ResourceType type) { + worldSettingsResourceType = Objects.requireNonNull(type, "type"); + } + public static void setSpaceCompatibilityIndexResourceType( @Nonnull ResourceType type) { spaceCompatibilityIndexResourceType = Objects.requireNonNull(type, "type"); @@ -359,6 +367,11 @@ public static ResourceType runtimeResource return require(runtimeResourceType, "PhysicsRuntimeResource"); } + @Nonnull + public static ResourceType worldSettingsResourceType() { + return require(worldSettingsResourceType, "PhysicsWorldSettingsResource"); + } + @Nonnull public static ResourceType spaceCompatibilityIndexResourceType() { From 519eca63f6688300c4fce1f52559d060c212a605 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:29:05 +0200 Subject: [PATCH 086/534] refactor(crucible): read benchmark stats from physicsstore Signed-off-by: Blovien --- ...tachedStreamingBenchmarkCrucibleTests.java | 93 ++++++++--- ...pulseRapierBodyBenchmarkCrucibleTests.java | 13 +- .../PhysicsStoreBenchmarkQueries.java | 157 ++++++++++++++++++ ...sStoreWorldCollisionStreamingResource.java | 4 + 4 files changed, 240 insertions(+), 27 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 3942c2c6..762886b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -18,12 +18,15 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; -import dev.hytalemodding.impulse.core.internal.simulation.query.WorldCollisionPrewarmEnvelopeQuery; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; @@ -141,6 +144,7 @@ private static final class StageRunner { private final Store physicsStore; private final PhysicsRuntimeProfilingResource runtimeProfiling; private final WorldCollisionProfilingResource worldCollisionProfiling; + private final PhysicsStoreWorldCollisionStreamingResource worldCollisionStreaming; private final PhysicsWorldSettings previousWorldSettings; private final List retainedChunks = new ArrayList<>(); @@ -155,6 +159,8 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.worldCollisionProfiling = store.getResource( WorldCollisionProfilingResource.getResourceType()); + this.worldCollisionStreaming = store.getResource( + PhysicsStoreWorldCollisionStreamingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); } @@ -274,7 +280,7 @@ private StageReport finishStage(int count, double elapsedSeconds = Math.max(0.001, (System.nanoTime() - startedNanos) / 1_000_000_000.0); double observedTickRate = step.getTickSamples() / elapsedSeconds; - SpaceStats stats = SpaceStats.collect(physics, spaceId); + SpaceStats stats = SpaceStats.collect(physicsStore, worldCollisionStreaming, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); @@ -345,25 +351,68 @@ private void restoreStepSettings() { private PrewarmStats prewarmWorldCollision(@Nonnull SpaceId spaceId, int count) { BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); - WorldCollisionPrewarmStats stats = physics.queryInternal(new WorldCollisionPrewarmEnvelopeQuery(world, - spaceId, - count, - (float) layout.origin().x, - (float) layout.origin().y, - (float) layout.origin().z, - layout.side(), - (float) layout.spacing(), - BODY_STREAMING_RADIUS, - (float) STREAMING_FALL_ENVELOPE_MIN_Y, - (float) STREAMING_HORIZONTAL_DRIFT_HALO_BLOCKS, - 0L)) - .toCompletableFuture() - .join(); + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(physicsStore, spaceId); + PhysicsTerrainMutationQueueResource queue = physicsStore.getResource( + PhysicsTerrainMutationQueueResource.getResourceType()); + WorldCollisionBuildOptions buildOptions = WorldCollisionBuildOptions.fromSettings( + physics.getSpaceSettings(spaceId).getWorldCollisionSettings()); + WorldCollisionPrewarmStats stats = worldCollisionStreaming.ensureAround(world, + spaceUuid, + queue, + prewarmCenters(layout, count), + BODY_STREAMING_RADIUS, + 0L, + null, + buildOptions); return new PrewarmStats(stats.sectionTargets(), stats.buildStats().sectionsBuilt(), stats.buildStats().colliderBodies()); } + @Nonnull + private static List prewarmCenters(@Nonnull BenchmarkLayout layout, int count) { + List centers = new ArrayList<>(); + for (int index = 0; index < Math.max(0, count); index++) { + double positionX = layout.positionX(index); + double positionY = layout.positionY(); + double positionZ = layout.positionZ(index); + addPrewarmEnvelopeCenters(centers, positionX, positionY, positionZ); + } + return centers; + } + + private static void addPrewarmEnvelopeCenters(@Nonnull List centers, + double positionX, + double positionY, + double positionZ) { + double halo = STREAMING_HORIZONTAL_DRIFT_HALO_BLOCKS; + for (int offsetX = -1; offsetX <= 1; offsetX++) { + for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { + addPrewarmEnvelopeCentersAt(centers, + positionX + offsetX * halo, + positionY, + positionZ + offsetZ * halo); + } + } + } + + private static void addPrewarmEnvelopeCentersAt(@Nonnull List centers, + double positionX, + double positionY, + double positionZ) { + double step = Math.max(1.0, BODY_STREAMING_RADIUS * 2.0); + double minCenterY = Math.min(positionY, + STREAMING_FALL_ENVELOPE_MIN_Y + BODY_STREAMING_RADIUS); + double lastY = Double.NaN; + for (double y = positionY; y >= minCenterY; y -= step) { + centers.add(new Vector3d(positionX, y, positionZ)); + lastY = y; + } + if (Double.isNaN(lastY) || lastY > minCenterY) { + centers.add(new Vector3d(positionX, minCenterY, positionZ)); + } + } + private void configureMissingSectionDiagnostics(@Nonnull BenchmarkChunks chunks) { LongSet sectionKeys = new LongOpenHashSet(); for (ChunkSection section : chunks.sections()) { @@ -880,16 +929,18 @@ private static final class SpaceStats { private int missingTerrainBaselineBodies; private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; - private static SpaceStats collect(@Nonnull PhysicsWorldRuntimeResource physics, + private static SpaceStats collect(@Nonnull Store physicsStore, + @Nonnull PhysicsStoreWorldCollisionStreamingResource worldCollisionStreaming, @Nonnull SpaceId spaceId) { - BenchmarkSpaceStatsView view = physics.queryInternal(new BenchmarkSpaceStatsQuery(spaceId, + BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( + physicsStore, + worldCollisionStreaming, + new BenchmarkSpaceStatsQuery(spaceId, GROUND_Y, BELOW_PLANE_TOLERANCE, BODY_WORLD_MIN_Y, BODY_VOID_Y, - true)) - .toCompletableFuture() - .join(); + true)); SpaceStats stats = new SpaceStats(); stats.bodies = view.bodies(); stats.dynamicBodies = view.dynamicBodies(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index f52eade6..fadbabc5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -300,7 +300,7 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, double elapsedSeconds = Math.max(0.001, (System.nanoTime() - startedNanos) / 1_000_000_000.0); double observedTickRate = step.getTickSamples() / elapsedSeconds; - SpaceStats stats = SpaceStats.collect(physics, spaceId); + SpaceStats stats = SpaceStats.collect(physicsStore, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); @@ -754,16 +754,17 @@ private static final class SpaceStats { private double minDynamicBodyY = Double.POSITIVE_INFINITY; private double maxDynamicBodyY = Double.NEGATIVE_INFINITY; - private static SpaceStats collect(@Nonnull PhysicsWorldRuntimeResource physics, + private static SpaceStats collect(@Nonnull Store physicsStore, @Nonnull SpaceId spaceId) { - BenchmarkSpaceStatsView view = physics.queryInternal(new BenchmarkSpaceStatsQuery(spaceId, + BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( + physicsStore, + null, + new BenchmarkSpaceStatsQuery(spaceId, GROUND_Y, BELOW_PLANE_TOLERANCE, BODY_WORLD_MIN_Y, BODY_VOID_Y, - false)) - .toCompletableFuture() - .join(); + false)); SpaceStats stats = new SpaceStats(); stats.bodies = view.bodies(); stats.dynamicBodies = view.dynamicBodies(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java new file mode 100644 index 00000000..ba6c8420 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -0,0 +1,157 @@ +package dev.hytalemodding.impulse.core.internal.crucible; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; +import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3f; + +/** + * Crucible-only copied diagnostics sourced from authoritative PhysicsStore rows. + */ +final class PhysicsStoreBenchmarkQueries { + + private PhysicsStoreBenchmarkQueries() { + } + + @Nonnull + static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store store, + @Nullable PhysicsStoreWorldCollisionStreamingResource streaming, + @Nonnull BenchmarkSpaceStatsQuery query) { + PhysicsStoreThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, query.spaceId()); + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + BenchmarkSpaceStatsAccumulator stats = new BenchmarkSpaceStatsAccumulator(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectBodyRows(chunk, snapshots, spaceUuid, query, stats); + store.forEachChunk(BodyComponent.getComponentType(), collector); + int worldCollisionBodies = streaming != null ? streaming.bodyCount(spaceUuid) : 0; + stats.bodies += worldCollisionBodies; + stats.worldCollisionBodies += worldCollisionBodies; + return stats.toView(); + } + + private static void collectBodyRows(@Nonnull ArchetypeChunk chunk, + @Nonnull PhysicsSnapshotResource snapshots, + @Nonnull UUID spaceUuid, + @Nonnull BenchmarkSpaceStatsQuery query, + @Nonnull BenchmarkSpaceStatsAccumulator stats) { + for (int index = 0; index < chunk.size(); index++) { + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body == null || !spaceUuid.equals(body.getSpaceUuid())) { + continue; + } + UuidComponent uuid = chunk.getComponent(index, UuidComponent.getComponentType()); + if (uuid == null) { + continue; + } + PhysicsStoreBodySnapshot snapshot = snapshots.getBody(uuid.getUuid()); + if (snapshot == null) { + continue; + } + ShapeComponent shape = chunk.getComponent(index, ShapeComponent.getComponentType()); + classifyBody(stats, body, shape, snapshot, query); + } + } + + private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, + @Nonnull BodyComponent body, + @Nullable ShapeComponent shape, + @Nonnull PhysicsStoreBodySnapshot snapshot, + @Nonnull BenchmarkSpaceStatsQuery query) { + stats.bodies++; + if (snapshot.bodyType() == PhysicsBodyType.DYNAMIC) { + stats.dynamicBodies++; + Vector3f position = snapshot.position(); + stats.minDynamicBodyY = Math.min(stats.minDynamicBodyY, position.y); + stats.maxDynamicBodyY = Math.max(stats.maxDynamicBodyY, position.y); + if (position.y < query.groundY() - query.belowPlaneTolerance()) { + stats.belowPlaneBodies++; + } + if (query.includeTerrainProbe()) { + stats.missingTerrainBaselineBodies++; + } + if (position.y < query.bodyWorldMinY()) { + stats.belowWorldMinBodies++; + } + if (position.y < query.bodyVoidY()) { + stats.belowVoidBodies++; + } + if (snapshot.sleeping()) { + stats.sleepingDynamicBodies++; + } else { + stats.awakeDynamicBodies++; + } + } + + if (body.getKind() == PhysicsBodyKind.BODY) { + stats.detachedBodies++; + return; + } + if (shape != null && shape.getShapeType() == ShapeType.PLANE) { + return; + } + if (body.getKind() == PhysicsBodyKind.WORLD_COLLISION) { + stats.worldCollisionBodies++; + return; + } + stats.rawBodies++; + } + + private static final class BenchmarkSpaceStatsAccumulator { + + private int bodies; + private int dynamicBodies; + private int awakeDynamicBodies; + private int sleepingDynamicBodies; + private int detachedBodies; + private int rawBodies; + private int worldCollisionBodies; + private int belowPlaneBodies; + private int belowTerrainBodies; + private int belowWorldMinBodies; + private int belowVoidBodies; + private int terrainBaselineBodies; + private int missingTerrainBaselineBodies; + private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; + private double minDynamicBodyY = Double.POSITIVE_INFINITY; + private double maxDynamicBodyY = Double.NEGATIVE_INFINITY; + + @Nonnull + private BenchmarkSpaceStatsView toView() { + return new BenchmarkSpaceStatsView(bodies, + dynamicBodies, + awakeDynamicBodies, + sleepingDynamicBodies, + detachedBodies, + rawBodies, + worldCollisionBodies, + belowPlaneBodies, + belowTerrainBodies, + belowWorldMinBodies, + belowVoidBodies, + terrainBaselineBodies, + missingTerrainBaselineBodies, + Double.isFinite(minTerrainBottomClearance) ? (float) minTerrainBottomClearance : Float.NaN, + Double.isFinite(minDynamicBodyY) ? (float) minDynamicBodyY : Float.NaN, + Double.isFinite(maxDynamicBodyY) ? (float) maxDynamicBodyY : Float.NaN); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java index 05af11f2..c45c0df1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java @@ -194,6 +194,10 @@ public synchronized WorldCollisionStats stats() { cache.shapeTemplateCount()); } + public synchronized int bodyCount(@Nonnull UUID spaceUuid) { + return cache.bodyCount(spaceUuid); + } + @Nonnull @Override public synchronized PhysicsStoreWorldCollisionStreamingResource clone() { From 352a8e76ee9df61a45b1f4f1d3ace79626fecdbe Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:33:58 +0200 Subject: [PATCH 087/534] refactor(debug): queue overlays through physicsstore reads Signed-off-by: Blovien --- .../systems/debug/PhysicsDebugSystem.java | 32 +- .../debug/PhysicsStoreDebugQueries.java | 319 ++++++++++++++++++ 2 files changed, 335 insertions(+), 16 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 31853b9c..7ef1a233 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -14,9 +14,11 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; @@ -26,9 +28,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsDebugContactsQuery; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsDebugJointsQuery; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.DebugSection; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -109,6 +109,8 @@ public void tick(float dt, int index, @Nonnull Store store) { return; } + Store physicsStore = + ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); float overlayLifetime = PhysicsDebugRenderer.lifetimeForRefresh( debug.getOverlayRefreshSeconds(), dt); float worldCollisionLifetime = PhysicsDebugRenderer.lifetimeForRefresh( @@ -146,7 +148,7 @@ public void tick(float dt, int index, @Nonnull Store store) { } if (overlayDue && debugContacts) { renderContacts(target, - resource, + physicsStore, space, viewerUuid, queryCache, @@ -157,7 +159,7 @@ public void tick(float dt, int index, @Nonnull Store store) { } if (overlayDue && debugJoints) { renderJoints(target, - resource, + physicsStore, space, viewerUuid, queryCache, @@ -356,7 +358,7 @@ private static void renderSpaceOnlyShapes(@Nonnull Collection viewers } private static void renderContacts(@Nonnull Collection viewers, - @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull Store physicsStore, @Nonnull PhysicsSpaceBinding space, @Nonnull UUID viewerUuid, @Nonnull DebugQueryCache queryCache, @@ -367,12 +369,11 @@ private static void renderContacts(@Nonnull Collection viewers, DebugQueryKey key = DebugQueryKey.contacts(space.spaceId(), viewerUuid); try { queryCache.requestContactsIfIdle(key, - () -> resource.queryInternal(new PhysicsDebugContactsQuery(space.spaceId(), - viewerPosition.x, - viewerPosition.y, - viewerPosition.z, + () -> PhysicsStoreDebugQueries.contactsAsync(physicsStore, + space.spaceId(), + viewerPosition, viewRadius, - maxContacts))); + maxContacts)); } catch (RuntimeException exception) { return; } @@ -382,7 +383,7 @@ private static void renderContacts(@Nonnull Collection viewers, } private static void renderJoints(@Nonnull Collection viewers, - @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull Store physicsStore, @Nonnull PhysicsSpaceBinding space, @Nonnull UUID viewerUuid, @Nonnull DebugQueryCache queryCache, @@ -393,12 +394,11 @@ private static void renderJoints(@Nonnull Collection viewers, DebugQueryKey key = DebugQueryKey.joints(space.spaceId(), viewerUuid); try { queryCache.requestJointsIfIdle(key, - () -> resource.queryInternal(new PhysicsDebugJointsQuery(space.spaceId(), - viewerPosition.x, - viewerPosition.y, - viewerPosition.z, + () -> PhysicsStoreDebugQueries.jointsAsync(physicsStore, + space.spaceId(), + viewerPosition, viewRadius, - maxJoints))); + maxJoints)); } catch (RuntimeException exception) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java new file mode 100644 index 00000000..0a2285ac --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -0,0 +1,319 @@ +package dev.hytalemodding.impulse.core.internal.systems.debug; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; +import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3d; +import org.joml.Vector3f; + +/** + * Queued PhysicsStore reads for debug overlay contact and joint views. + */ +final class PhysicsStoreDebugQueries { + + private static final float CONTACT_NORMAL_SCALE = 0.75f; + private static final float JOINT_AXIS_SCALE = 0.9f; + + private PhysicsStoreDebugQueries() { + } + + @Nonnull + static CompletionStage> contactsAsync( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d viewerPosition, + double viewRadius, + int maxContacts) { + double viewerX = viewerPosition.x; + double viewerY = viewerPosition.y; + double viewerZ = viewerPosition.z; + return queue(store).enqueue(physics -> contacts(physics, + spaceId, + viewerX, + viewerY, + viewerZ, + viewRadius, + maxContacts)); + } + + @Nonnull + static CompletionStage> jointsAsync( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d viewerPosition, + double viewRadius, + int maxJoints) { + double viewerX = viewerPosition.x; + double viewerY = viewerPosition.y; + double viewerZ = viewerPosition.z; + return queue(store).enqueue(physics -> joints(physics, + spaceId, + viewerX, + viewerY, + viewerZ, + viewRadius, + maxJoints)); + } + + @Nonnull + private static List contacts(@Nonnull Store store, + @Nonnull SpaceId spaceId, + double viewerX, + double viewerY, + double viewerZ, + double viewRadius, + int maxContacts) { + PhysicsStoreThreading.requireWorldThread(store, "read PhysicsStore debug contacts"); + int limit = Math.max(0, maxContacts); + if (limit == 0) { + return List.of(); + } + + SpaceContext space = space(store, spaceId); + if (space == null) { + return List.of(); + } + + double maxDistanceSquared = viewRadius * viewRadius; + List visible = new ArrayList<>(Math.min(limit, 64)); + space.backendRuntime().contacts(space.spaceHandle().value(), (bodyAId, + bodyBId, + pointAX, + pointAY, + pointAZ, + pointBX, + pointBY, + pointBZ, + normalBX, + normalBY, + normalBZ, + distance, + impulse) -> { + if (visible.size() >= limit) { + return; + } + if (distanceSquared(pointBX, pointBY, pointBZ, viewerX, viewerY, viewerZ) + > maxDistanceSquared) { + return; + } + visible.add(toDebugContactView(pointBX, + pointBY, + pointBZ, + normalBX, + normalBY, + normalBZ, + impulse)); + }); + return List.copyOf(visible); + } + + @Nonnull + private static List joints(@Nonnull Store store, + @Nonnull SpaceId spaceId, + double viewerX, + double viewerY, + double viewerZ, + double viewRadius, + int maxJoints) { + PhysicsStoreThreading.requireWorldThread(store, "read PhysicsStore debug joints"); + int limit = Math.max(0, maxJoints); + if (limit == 0) { + return List.of(); + } + + UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(spaceId); + if (spaceUuid == null) { + return List.of(); + } + + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + double maxDistanceSquared = viewRadius * viewRadius; + List visible = new ArrayList<>(Math.min(limit, 64)); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectJointChunk(chunk, + snapshots, + spaceUuid, + viewerX, + viewerY, + viewerZ, + maxDistanceSquared, + limit, + visible); + store.forEachChunk(JointComponent.getComponentType(), collector); + return List.copyOf(visible); + } + + private static void collectJointChunk(@Nonnull ArchetypeChunk chunk, + @Nonnull PhysicsSnapshotResource snapshots, + @Nonnull UUID spaceUuid, + double viewerX, + double viewerY, + double viewerZ, + double maxDistanceSquared, + int limit, + @Nonnull List visible) { + for (int index = 0; index < chunk.size(); index++) { + if (visible.size() >= limit) { + return; + } + JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); + if (joint == null || !spaceUuid.equals(joint.getSpaceUuid()) || !joint.isEnabled()) { + continue; + } + PhysicsDebugJointView view = toDebugJointView(joint, snapshots); + if (view == null) { + continue; + } + double midpointX = (view.anchorAX() + view.anchorBX()) * 0.5; + double midpointY = (view.anchorAY() + view.anchorBY()) * 0.5; + double midpointZ = (view.anchorAZ() + view.anchorBZ()) * 0.5; + if (distanceSquared(midpointX, midpointY, midpointZ, viewerX, viewerY, viewerZ) + > maxDistanceSquared) { + continue; + } + visible.add(view); + } + } + + @Nullable + private static PhysicsDebugJointView toDebugJointView(@Nonnull JointComponent joint, + @Nonnull PhysicsSnapshotResource snapshots) { + PhysicsStoreBodySnapshot bodyA = snapshots.getBody(joint.getBodyAUuid()); + PhysicsStoreBodySnapshot bodyB = snapshots.getBody(joint.getBodyBUuid()); + if (bodyA == null || bodyB == null) { + return null; + } + + Vector3f anchorA = worldAnchor(bodyA, joint.getAnchorA()); + Vector3f anchorB = worldAnchor(bodyB, joint.getAnchorB()); + Vector3f axis = joint.getAxis(); + if (axis.lengthSquared() <= 0.0f) { + return new PhysicsDebugJointView(anchorA.x, + anchorA.y, + anchorA.z, + anchorB.x, + anchorB.y, + anchorB.z, + false, + 0.0f, + 0.0f, + 0.0f); + } + + Vector3f worldAxis = new Vector3f(axis).normalize().mul(JOINT_AXIS_SCALE); + bodyA.rotation().transform(worldAxis); + return new PhysicsDebugJointView(anchorA.x, + anchorA.y, + anchorA.z, + anchorB.x, + anchorB.y, + anchorB.z, + true, + worldAxis.x, + worldAxis.y, + worldAxis.z); + } + + @Nonnull + private static Vector3f worldAnchor(@Nonnull PhysicsStoreBodySnapshot body, + @Nonnull Vector3f localAnchor) { + Vector3f anchor = new Vector3f(localAnchor); + Quaternionf rotation = body.rotation(); + rotation.transform(anchor); + return anchor.add(body.position()); + } + + @Nonnull + private static PhysicsDebugContactView toDebugContactView(float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float impulse) { + Vector3f normal = new Vector3f(normalX, normalY, normalZ); + if (normal.lengthSquared() <= 0.0f) { + return new PhysicsDebugContactView(pointX, + pointY, + pointZ, + false, + 0.0f, + 0.0f, + 0.0f); + } + + float magnitude = Math.max(CONTACT_NORMAL_SCALE, Math.abs(impulse) * 0.05f); + normal.normalize().mul(magnitude); + return new PhysicsDebugContactView(pointX, + pointY, + pointZ, + true, + normal.x, + normal.y, + normal.z); + } + + @Nullable + private static SpaceContext space(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + UUID spaceUuid = compatibility.getSpaceUuid(spaceId); + if (spaceUuid == null) { + return null; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); + BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + PhysicsBackendRuntime backendRuntime = + backendId != null ? runtime.getRuntime(backendId) : null; + if (spaceHandle == null || backendRuntime == null) { + return null; + } + return new SpaceContext(spaceHandle, backendRuntime); + } + + @Nonnull + private static PhysicsStoreReadQueueResource queue(@Nonnull Store store) { + return store.getResource(PhysicsStoreReadQueueResource.getResourceType()); + } + + private static double distanceSquared(double x, + double y, + double z, + double viewerX, + double viewerY, + double viewerZ) { + double dx = x - viewerX; + double dy = y - viewerY; + double dz = z - viewerZ; + return dx * dx + dy * dy + dz * dz; + } + + private record SpaceContext(@Nonnull BackendSpaceHandle spaceHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } +} From 8cea212ab74e08d5d8c8a6f78730e6a80bf49db7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:44:15 +0200 Subject: [PATCH 088/534] refactor(physicsstore): remove legacy internal query bridge Signed-off-by: Blovien --- ...tachedStreamingBenchmarkCrucibleTests.java | 3 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 7 +- .../PhysicsStoreBenchmarkQueries.java | 16 +- .../PhysicsWorldRuntimeResource.java | 11 - .../simulation/PhysicsSimulationExecutor.java | 676 ------------------ .../query/BenchmarkSpaceStatsQuery.java | 23 - .../query/PhysicsDebugContactsQuery.java | 24 - .../query/PhysicsDebugJointsQuery.java | 24 - .../query/PhysicsInternalQuery.java | 13 - .../query/PhysicsSpaceRuntimeStatsQuery.java | 18 - .../WorldCollisionPrewarmEnvelopeQuery.java | 42 -- .../view/PhysicsSpaceRuntimeStatsView.java | 30 - 12 files changed, 16 insertions(+), 871 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/BenchmarkSpaceStatsQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugContactsQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugJointsQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsInternalQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsSpaceRuntimeStatsQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/WorldCollisionPrewarmEnvelopeQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsSpaceRuntimeStatsView.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 762886b2..5727eff3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -25,7 +25,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -935,7 +934,7 @@ private static SpaceStats collect(@Nonnull Store physicsStore, BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( physicsStore, worldCollisionStreaming, - new BenchmarkSpaceStatsQuery(spaceId, + new PhysicsStoreBenchmarkQueries.BenchmarkSpaceStatsRequest(spaceId, GROUND_Y, BELOW_PLANE_TOLERANCE, BODY_WORLD_MIN_Y, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index fadbabc5..f08409ce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -29,7 +29,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -759,9 +758,9 @@ private static SpaceStats collect(@Nonnull Store physicsStore, BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( physicsStore, null, - new BenchmarkSpaceStatsQuery(spaceId, - GROUND_Y, - BELOW_PLANE_TOLERANCE, + new PhysicsStoreBenchmarkQueries.BenchmarkSpaceStatsRequest(spaceId, + GROUND_Y, + BELOW_PLANE_TOLERANCE, BODY_WORLD_MIN_Y, BODY_VOID_Y, false)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index ba6c8420..9e8a4c1e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -6,10 +6,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; @@ -34,7 +34,7 @@ private PhysicsStoreBenchmarkQueries() { @Nonnull static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store store, @Nullable PhysicsStoreWorldCollisionStreamingResource streaming, - @Nonnull BenchmarkSpaceStatsQuery query) { + @Nonnull BenchmarkSpaceStatsRequest query) { PhysicsStoreThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, query.spaceId()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); @@ -51,7 +51,7 @@ static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store private static void collectBodyRows(@Nonnull ArchetypeChunk chunk, @Nonnull PhysicsSnapshotResource snapshots, @Nonnull UUID spaceUuid, - @Nonnull BenchmarkSpaceStatsQuery query, + @Nonnull BenchmarkSpaceStatsRequest query, @Nonnull BenchmarkSpaceStatsAccumulator stats) { for (int index = 0; index < chunk.size(); index++) { BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); @@ -75,7 +75,7 @@ private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, @Nonnull BodyComponent body, @Nullable ShapeComponent shape, @Nonnull PhysicsStoreBodySnapshot snapshot, - @Nonnull BenchmarkSpaceStatsQuery query) { + @Nonnull BenchmarkSpaceStatsRequest query) { stats.bodies++; if (snapshot.bodyType() == PhysicsBodyType.DYNAMIC) { stats.dynamicBodies++; @@ -154,4 +154,12 @@ private BenchmarkSpaceStatsView toView() { Double.isFinite(maxDynamicBodyY) ? (float) maxDynamicBodyY : Float.NaN); } } + + record BenchmarkSpaceStatsRequest(@Nonnull SpaceId spaceId, + float groundY, + float belowPlaneTolerance, + float bodyWorldMinY, + float bodyVoidY, + boolean includeTerrainProbe) { + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index fbcfd8c2..8aaf0c21 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -39,7 +39,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; import dev.hytalemodding.impulse.core.internal.simulation.recorder.MutablePhysicsCommandContext; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsInternalQuery; import dev.hytalemodding.impulse.core.internal.simulation.PhysicsSimulationExecutor; import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; @@ -87,7 +86,6 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -272,15 +270,6 @@ public PhysicsQueryHandle query(@Nonnull PhysicsQuery query) { return PhysicsQueryHandle.fromCompletion(query, completion); } - @Nonnull - public CompletionStage queryInternal(@Nonnull PhysicsInternalQuery query) { - Objects.requireNonNull(query, "query"); - CompletableFuture completion = - ownerGateway.enqueueCall("execute internal physics query", - () -> simulationExecutor.queryInternal(query)); - return completion.minimalCompletionStage(); - } - @Nonnull @Override public PhysicsEventFrame getLatestEventFrame() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java index 8debc6a7..e4524469 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.internal.simulation; -import com.hypixel.hytale.math.util.ChunkUtil; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; @@ -17,24 +16,9 @@ import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnBatch; import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnTemplateBatch; -import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsDebugContactsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsDebugJointsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsInternalQuery; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsSpaceRuntimeStatsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.query.WorldCollisionPrewarmEnvelopeQuery; -import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsSpaceRuntimeStatsView; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.simulation.query.CcdSupportQuery; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; @@ -59,17 +43,12 @@ import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; import dev.hytalemodding.impulse.core.plugin.simulation.query.UnsupportedCcdSpacesQuery; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.joml.Vector3d; import org.joml.Vector3f; /** @@ -77,9 +56,6 @@ */ public final class PhysicsSimulationExecutor implements PhysicsCommandDispatcher { - private static final float CONTACT_NORMAL_SCALE = 0.75f; - private static final float JOINT_AXIS_SCALE = 0.9f; - @Nonnull private final PhysicsWorldRuntimeResource runtime; @@ -369,21 +345,6 @@ public R query(@Nonnull PhysicsQuery query) { return typed; } - @Nonnull - public R queryInternal(@Nonnull PhysicsInternalQuery query) { - Objects.requireNonNull(query, "query"); - Object result = switch (query) { - case BenchmarkSpaceStatsQuery stats -> benchmarkSpaceStats(stats); - case PhysicsDebugContactsQuery contacts -> debugContacts(contacts); - case PhysicsDebugJointsQuery joints -> debugJoints(joints); - case PhysicsSpaceRuntimeStatsQuery stats -> physicsSpaceRuntimeStats(stats); - case WorldCollisionPrewarmEnvelopeQuery prewarm -> prewarmWorldCollisionEnvelope(prewarm); - }; - @SuppressWarnings("unchecked") - R typed = (R) result; - return typed; - } - @Override public void spawnRigidBody(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @@ -916,643 +877,6 @@ private int runtimeJointCount() { return count; } - @Nonnull - private PhysicsSpaceRuntimeStatsView physicsSpaceRuntimeStats( - @Nonnull PhysicsSpaceRuntimeStatsQuery query) { - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - WorldVoxelCollisionCache cache = runtime.worldCollisionCache(); - PhysicsSpaceRuntimeStatsAccumulator stats = new PhysicsSpaceRuntimeStatsAccumulator(); - for (PhysicsBodyRegistration registration : runtime.getBodyRegistrations()) { - if (!registration.spaceId().equals(query.spaceId())) { - continue; - } - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, registration.backendBodyHandle().value()); - if (snapshot != null) { - classifyRuntimeBody(stats, cache, space, registration, snapshot); - } - } - stats.worldCollisionBodies += cache.bodyCount(); - stats.joints = space.runtime().jointCount(space.backendSpaceHandle().value()); - stats.contacts = space.runtime().contactCount(space.backendSpaceHandle().value()); - space.runtime().runtimeStats(space.backendSpaceHandle().value(), - (bodyCount, - colliderCount, - activeBodyCount, - contactPairCount, - contactManifoldCount, - contactPointCount, - dynamicDynamicContactPairCount, - terrainContactPairCount, - activeIslandCount, - jointCount, - available) -> { - if (!available) { - return; - } - stats.runtimeStatsAvailable = true; - stats.runtimeBodyCount = bodyCount; - stats.runtimeColliderCount = colliderCount; - stats.runtimeActiveBodyCount = activeBodyCount; - stats.runtimeContactPairCount = contactPairCount; - stats.runtimeContactManifoldCount = contactManifoldCount; - stats.runtimeContactPointCount = contactPointCount; - stats.runtimeDynamicDynamicContactPairCount = dynamicDynamicContactPairCount; - stats.runtimeTerrainContactPairCount = terrainContactPairCount; - stats.runtimeActiveIslandCount = activeIslandCount; - stats.runtimeJointCount = jointCount; - }); - return stats.toView(); - } - - private void classifyRuntimeBody(@Nonnull PhysicsSpaceRuntimeStatsAccumulator stats, - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull PhysicsSpaceBinding space, - @Nonnull PhysicsBodyRegistration registration, - @Nonnull PhysicsBodySnapshot snapshot) { - stats.bodies++; - if (snapshot.isDynamic()) { - stats.dynamicBodies++; - if (snapshot.sleeping()) { - stats.sleepingDynamicBodies++; - } else { - stats.awakeDynamicBodies++; - } - } else if (snapshot.isKinematic()) { - stats.kinematicBodies++; - } else { - stats.staticBodies++; - } - - if (registration.kind() == PhysicsBodyKind.BODY) { - if (runtime.hasBodyAttachments(registration.bodyKey())) { - stats.entityOwnedBodies++; - } else { - stats.detachedBodies++; - } - return; - } - if (registration.kind() == PhysicsBodyKind.WORLD_COLLISION) { - stats.worldCollisionBodies++; - return; - } - if (snapshot.shapeType() == ShapeType.PLANE) { - stats.planeBodies++; - return; - } - if (cache.containsBody(space.spaceId(), registration.backendBodyHandle().value())) { - stats.worldCollisionBodies++; - return; - } - stats.rawBodies++; - } - - @Nonnull - private BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull BenchmarkSpaceStatsQuery query) { - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - WorldVoxelCollisionCache cache = runtime.worldCollisionCache(); - BenchmarkSpaceStatsAccumulator stats = new BenchmarkSpaceStatsAccumulator(); - for (PhysicsBodyRegistration registration : runtime.getBodyRegistrations()) { - if (!registration.spaceId().equals(query.spaceId())) { - continue; - } - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, registration.backendBodyHandle().value()); - if (snapshot != null) { - classifyBenchmarkBody(stats, - cache, - space, - registration, - snapshot, - query); - } - } - int cachedWorldCollisionBodies = cache.bodyCount(space.spaceId()); - stats.bodies += cachedWorldCollisionBodies; - stats.worldCollisionBodies += cachedWorldCollisionBodies; - return stats.toView(); - } - - private void classifyBenchmarkBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull PhysicsSpaceBinding space, - @Nonnull PhysicsBodyRegistration registration, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull BenchmarkSpaceStatsQuery query) { - stats.bodies++; - if (snapshot.isDynamic()) { - stats.dynamicBodies++; - Vector3f position = snapshot.position(); - stats.minDynamicBodyY = Math.min(stats.minDynamicBodyY, position.y); - stats.maxDynamicBodyY = Math.max(stats.maxDynamicBodyY, position.y); - if (position.y < query.groundY() - query.belowPlaneTolerance()) { - stats.belowPlaneBodies++; - } - if (query.includeTerrainProbe()) { - WorldVoxelCollisionCache.GroundProbe ground = cache.probeGround(space.spaceId(), - position.x, - position.z, - horizontalHalfExtent(snapshot)); - if (ground.found()) { - stats.terrainBaselineBodies++; - double bottomClearance = position.y - verticalHalfExtent(snapshot) - ground.topY(); - stats.minTerrainBottomClearance = - Math.min(stats.minTerrainBottomClearance, bottomClearance); - if (bottomClearance < -query.belowPlaneTolerance()) { - stats.belowTerrainBodies++; - } - } else { - stats.missingTerrainBaselineBodies++; - } - } - if (position.y < query.bodyWorldMinY()) { - stats.belowWorldMinBodies++; - } - if (position.y < query.bodyVoidY()) { - stats.belowVoidBodies++; - } - if (snapshot.sleeping()) { - stats.sleepingDynamicBodies++; - } else { - stats.awakeDynamicBodies++; - } - } - - if (registration.kind() == PhysicsBodyKind.BODY) { - if (!runtime.hasBodyAttachments(registration.bodyKey())) { - stats.detachedBodies++; - } - return; - } - if (snapshot.shapeType() == ShapeType.PLANE) { - return; - } - if (cache.containsBody(space.spaceId(), registration.backendBodyHandle().value())) { - stats.worldCollisionBodies++; - return; - } - stats.rawBodies++; - } - - @Nonnull - private WorldCollisionPrewarmStats prewarmWorldCollisionEnvelope( - @Nonnull WorldCollisionPrewarmEnvelopeQuery query) { - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - WorldVoxelCollisionCache cache = runtime.worldCollisionCache(); - WorldCollisionBuildOptions buildOptions = - WorldCollisionBuildOptions.fromSettings(runtime.getLiveSpaceSettings(query.spaceId()) - .getWorldCollisionSettings()); - LongSet visitedSections = new LongOpenHashSet(); - Set visitedTargets = new ObjectOpenHashSet<>(); - BuildStats total = BuildStats.empty(); - for (int index = 0; index < Math.max(0, query.count()); index++) { - double positionX = query.originX() + (index % query.side()) * query.spacing(); - double positionY = query.originY(); - double positionZ = query.originZ() + ((double) index / query.side()) * query.spacing(); - total = total.plus(prewarmStreamingCollisionEnvelope(cache, - space, - query, - positionX, - positionY, - positionZ, - visitedSections, - visitedTargets, - buildOptions)); - } - return new WorldCollisionPrewarmStats(visitedSections.size(), worldCollisionStats(total)); - } - - @Nonnull - private BuildStats prewarmStreamingCollisionEnvelope( - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull PhysicsSpaceBinding space, - @Nonnull WorldCollisionPrewarmEnvelopeQuery query, - double positionX, - double positionY, - double positionZ, - @Nonnull LongSet visitedSections, - @Nonnull Set visitedTargets, - @Nonnull WorldCollisionBuildOptions buildOptions) { - BuildStats total = BuildStats.empty(); - double halo = query.horizontalDriftHaloBlocks(); - for (int offsetX = -1; offsetX <= 1; offsetX++) { - for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { - total = total.plus(prewarmStreamingCollisionEnvelopeAt(cache, - space, - query, - positionX + offsetX * halo, - positionY, - positionZ + offsetZ * halo, - visitedSections, - visitedTargets, - buildOptions)); - } - } - return total; - } - - @Nonnull - private BuildStats prewarmStreamingCollisionEnvelopeAt( - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull PhysicsSpaceBinding space, - @Nonnull WorldCollisionPrewarmEnvelopeQuery query, - double positionX, - double positionY, - double positionZ, - @Nonnull LongSet visitedSections, - @Nonnull Set visitedTargets, - @Nonnull WorldCollisionBuildOptions buildOptions) { - double step = Math.max(1.0, query.radius() * 2.0); - double minCenterY = Math.min(positionY, query.fallEnvelopeMinY() + query.radius()); - BuildStats total = BuildStats.empty(); - double lastY = Double.NaN; - for (double y = positionY; y >= minCenterY; y -= step) { - total = total.plus(prewarmStreamingCollisionTarget(cache, - space, - query, - positionX, - y, - positionZ, - visitedSections, - visitedTargets, - buildOptions)); - lastY = y; - } - if (Double.isNaN(lastY) || lastY > minCenterY) { - total = total.plus(prewarmStreamingCollisionTarget(cache, - space, - query, - positionX, - minCenterY, - positionZ, - visitedSections, - visitedTargets, - buildOptions)); - } - return total; - } - - @Nonnull - private BuildStats prewarmStreamingCollisionTarget( - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull PhysicsSpaceBinding space, - @Nonnull WorldCollisionPrewarmEnvelopeQuery query, - double centerX, - double centerY, - double centerZ, - @Nonnull LongSet visitedSections, - @Nonnull Set visitedTargets, - @Nonnull WorldCollisionBuildOptions buildOptions) { - StreamingPrewarmTarget target = streamingPrewarmTarget(centerX, - centerY, - centerZ, - query.radius()); - if (!visitedTargets.add(target)) { - return BuildStats.empty(); - } - return cache.ensureAround(query.world(), - space, - new Vector3d(centerX, centerY, centerZ), - query.radius(), - query.tick(), - null, - visitedSections, - null, - null, - buildOptions); - } - - @Nonnull - private static StreamingPrewarmTarget streamingPrewarmTarget(double centerX, - double centerY, - double centerZ, - int radius) { - int minX = (int) Math.floor(centerX) - radius; - int maxX = (int) Math.floor(centerX) + radius; - int minY = Math.max(0, (int) Math.floor(centerY) - radius); - int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, (int) Math.floor(centerY) + radius); - int minZ = (int) Math.floor(centerZ) - radius; - int maxZ = (int) Math.floor(centerZ) + radius; - return new StreamingPrewarmTarget(ChunkUtil.chunkCoordinate(minX), - ChunkUtil.chunkCoordinate(maxX), - ChunkUtil.indexSection(minY), - ChunkUtil.indexSection(maxY), - ChunkUtil.chunkCoordinate(minZ), - ChunkUtil.chunkCoordinate(maxZ)); - } - - @Nonnull - private static WorldCollisionBuildStats worldCollisionStats(@Nonnull BuildStats stats) { - return new WorldCollisionBuildStats(stats.scannedBlocks(), - stats.solidBlocks(), - stats.culledInteriorBlocks(), - stats.fullCubeRuns(), - stats.detailBoxes(), - stats.colliderBodies(), - stats.removedBodies(), - stats.sectionsBuilt(), - stats.sectionsRebuilt(), - stats.voxelBodies()); - } - - private static double horizontalHalfExtent(@Nonnull PhysicsBodySnapshot snapshot) { - if (snapshot.shapeType() == ShapeType.BOX) { - Vector3f halfExtents = snapshot.boxHalfExtents(); - if (halfExtents != null) { - return Math.max(finitePositive(halfExtents.x), finitePositive(halfExtents.z)); - } - } - return Math.max(finitePositive(snapshot.sphereRadius()), finitePositive(snapshot.halfHeight())); - } - - private static double verticalHalfExtent(@Nonnull PhysicsBodySnapshot snapshot) { - if (snapshot.shapeType() == ShapeType.BOX) { - Vector3f halfExtents = snapshot.boxHalfExtents(); - if (halfExtents != null) { - return finitePositive(halfExtents.y); - } - } - if (snapshot.shapeType() == ShapeType.SPHERE) { - return finitePositive(snapshot.sphereRadius()); - } - return finitePositive(snapshot.halfHeight()) + finitePositive(snapshot.sphereRadius()); - } - - private static double finitePositive(float value) { - return Float.isFinite(value) && value > 0.0f ? value : 0.0; - } - - @Nonnull - private List debugContacts(@Nonnull PhysicsDebugContactsQuery query) { - int limit = Math.max(0, query.maxContacts()); - if (limit == 0) { - return List.of(); - } - - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - double maxDistanceSquared = query.viewRadius() * query.viewRadius(); - List visible = new ArrayList<>(Math.min(limit, 64)); - space.runtime().contacts(space.backendSpaceHandle().value(), (bodyAId, - bodyBId, - pointAX, - pointAY, - pointAZ, - pointBX, - pointBY, - pointBZ, - normalBX, - normalBY, - normalBZ, - distance, - impulse) -> { - if (visible.size() >= limit) { - return; - } - - if (distanceSquared(pointBX, - pointBY, - pointBZ, - query.viewerX(), - query.viewerY(), - query.viewerZ()) > maxDistanceSquared) { - return; - } - - visible.add(toDebugContactView(pointBX, - pointBY, - pointBZ, - normalBX, - normalBY, - normalBZ, - impulse)); - }); - return List.copyOf(visible); - } - - @Nonnull - private List debugJoints(@Nonnull PhysicsDebugJointsQuery query) { - int limit = Math.max(0, query.maxJoints()); - if (limit == 0) { - return List.of(); - } - - double maxDistanceSquared = query.viewRadius() * query.viewRadius(); - List visible = new ArrayList<>(Math.min(limit, 64)); - for (PhysicsJointRegistration joint : runtime.getJointRegistrations()) { - if (!joint.spaceId().equals(query.spaceId())) { - continue; - } - if (visible.size() >= limit) { - break; - } - - PhysicsDebugJointView view = toDebugJointView(joint); - double midpointX = (view.anchorAX() + view.anchorBX()) * 0.5; - double midpointY = (view.anchorAY() + view.anchorBY()) * 0.5; - double midpointZ = (view.anchorAZ() + view.anchorBZ()) * 0.5; - if (distanceSquared(midpointX, - midpointY, - midpointZ, - query.viewerX(), - query.viewerY(), - query.viewerZ()) > maxDistanceSquared) { - continue; - } - - visible.add(view); - } - return List.copyOf(visible); - } - - @Nonnull - private static PhysicsDebugContactView toDebugContactView(float pointX, - float pointY, - float pointZ, - float normalX, - float normalY, - float normalZ, - float impulse) { - Vector3f normal = new Vector3f(normalX, normalY, normalZ); - if (normal.lengthSquared() <= 0.0f) { - return new PhysicsDebugContactView(pointX, - pointY, - pointZ, - false, - 0.0f, - 0.0f, - 0.0f); - } - - float magnitude = Math.max(CONTACT_NORMAL_SCALE, Math.abs(impulse) * 0.05f); - normal.normalize().mul(magnitude); - return new PhysicsDebugContactView(pointX, - pointY, - pointZ, - true, - normal.x, - normal.y, - normal.z); - } - - @Nonnull - private PhysicsDebugJointView toDebugJointView(@Nonnull PhysicsJointRegistration joint) { - PhysicsBodySnapshot bodyA = runtime.getBodySnapshot(joint.bodyA()); - PhysicsBodySnapshot bodyB = runtime.getBodySnapshot(joint.bodyB()); - Vector3f anchorA = worldAnchor(bodyA, joint.anchorAX(), joint.anchorAY(), joint.anchorAZ()); - Vector3f anchorB = worldAnchor(bodyB, joint.anchorBX(), joint.anchorBY(), joint.anchorBZ()); - Vector3f axis = new Vector3f(joint.axisX(), joint.axisY(), joint.axisZ()); - if (axis.lengthSquared() <= 0.0f) { - return new PhysicsDebugJointView(anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - false, - 0.0f, - 0.0f, - 0.0f); - } - - Vector3f worldAxis = new Vector3f(axis).normalize().mul(JOINT_AXIS_SCALE); - bodyA.rotation().transform(worldAxis); - return new PhysicsDebugJointView(anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - true, - worldAxis.x, - worldAxis.y, - worldAxis.z); - } - - @Nonnull - private static Vector3f worldAnchor(@Nonnull PhysicsBodySnapshot body, - float localX, - float localY, - float localZ) { - Vector3f anchor = new Vector3f(localX, localY, localZ); - body.rotation().transform(anchor); - Vector3f position = body.position(); - return anchor.add(position); - } - - private static double distanceSquared(double x, - double y, - double z, - double targetX, - double targetY, - double targetZ) { - double dx = x - targetX; - double dy = y - targetY; - double dz = z - targetZ; - return dx * dx + dy * dy + dz * dz; - } - - private static final class BenchmarkSpaceStatsAccumulator { - - private int bodies; - private int dynamicBodies; - private int awakeDynamicBodies; - private int sleepingDynamicBodies; - private int detachedBodies; - private int rawBodies; - private int worldCollisionBodies; - private int belowPlaneBodies; - private int belowTerrainBodies; - private int belowWorldMinBodies; - private int belowVoidBodies; - private int terrainBaselineBodies; - private int missingTerrainBaselineBodies; - private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; - private double minDynamicBodyY = Double.POSITIVE_INFINITY; - private double maxDynamicBodyY = Double.NEGATIVE_INFINITY; - - @Nonnull - private BenchmarkSpaceStatsView toView() { - return new BenchmarkSpaceStatsView(bodies, - dynamicBodies, - awakeDynamicBodies, - sleepingDynamicBodies, - detachedBodies, - rawBodies, - worldCollisionBodies, - belowPlaneBodies, - belowTerrainBodies, - belowWorldMinBodies, - belowVoidBodies, - terrainBaselineBodies, - missingTerrainBaselineBodies, - Double.isFinite(minTerrainBottomClearance) ? (float) minTerrainBottomClearance : Float.NaN, - Double.isFinite(minDynamicBodyY) ? (float) minDynamicBodyY : Float.NaN, - Double.isFinite(maxDynamicBodyY) ? (float) maxDynamicBodyY : Float.NaN); - } - } - - private static final class PhysicsSpaceRuntimeStatsAccumulator { - - private int bodies; - private int dynamicBodies; - private int awakeDynamicBodies; - private int sleepingDynamicBodies; - private int staticBodies; - private int kinematicBodies; - private int entityOwnedBodies; - private int detachedBodies; - private int worldCollisionBodies; - private int planeBodies; - private int rawBodies; - private int joints; - private int contacts; - private boolean runtimeStatsAvailable; - private int runtimeBodyCount; - private int runtimeColliderCount; - private int runtimeActiveBodyCount; - private int runtimeContactPairCount; - private int runtimeContactManifoldCount; - private int runtimeContactPointCount; - private int runtimeDynamicDynamicContactPairCount; - private int runtimeTerrainContactPairCount; - private int runtimeActiveIslandCount; - private int runtimeJointCount; - - @Nonnull - private PhysicsSpaceRuntimeStatsView toView() { - return new PhysicsSpaceRuntimeStatsView(bodies, - dynamicBodies, - awakeDynamicBodies, - sleepingDynamicBodies, - staticBodies, - kinematicBodies, - entityOwnedBodies, - detachedBodies, - worldCollisionBodies, - planeBodies, - rawBodies, - joints, - contacts, - runtimeStatsAvailable, - runtimeBodyCount, - runtimeColliderCount, - runtimeActiveBodyCount, - runtimeContactPairCount, - runtimeContactManifoldCount, - runtimeContactPointCount, - runtimeDynamicDynamicContactPairCount, - runtimeTerrainContactPairCount, - runtimeActiveIslandCount, - runtimeJointCount); - } - } - - private record StreamingPrewarmTarget(int minChunkX, - int maxChunkX, - int minSectionY, - int maxSectionY, - int minChunkZ, - int maxChunkZ) { - } - @Nonnull private Optional rigidBodyState(@Nonnull RigidBodyStateQuery query) { PhysicsBodyRegistration registration = runtime.getRegistration(query.bodyKey()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/BenchmarkSpaceStatsQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/BenchmarkSpaceStatsQuery.java deleted file mode 100644 index c4062832..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/BenchmarkSpaceStatsQuery.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Internal owner-lane query for stress benchmark health counters. - */ -public record BenchmarkSpaceStatsQuery(@Nonnull SpaceId spaceId, - float groundY, - float belowPlaneTolerance, - float bodyWorldMinY, - float bodyVoidY, - boolean includeTerrainProbe) - implements PhysicsInternalQuery { - - public BenchmarkSpaceStatsQuery { - Objects.requireNonNull(spaceId, "spaceId"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugContactsQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugContactsQuery.java deleted file mode 100644 index 03cc1839..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugContactsQuery.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; - -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Internal owner-lane query for nearby contact points used by debug rendering. - */ -public record PhysicsDebugContactsQuery(@Nonnull SpaceId spaceId, - double viewerX, - double viewerY, - double viewerZ, - double viewRadius, - int maxContacts) - implements PhysicsInternalQuery> { - - public PhysicsDebugContactsQuery { - Objects.requireNonNull(spaceId, "spaceId"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugJointsQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugJointsQuery.java deleted file mode 100644 index 8a6ae246..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsDebugJointsQuery.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; - -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Internal owner-lane query for nearby joints used by debug rendering. - */ -public record PhysicsDebugJointsQuery(@Nonnull SpaceId spaceId, - double viewerX, - double viewerY, - double viewerZ, - double viewRadius, - int maxJoints) - implements PhysicsInternalQuery> { - - public PhysicsDebugJointsQuery { - Objects.requireNonNull(spaceId, "spaceId"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsInternalQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsInternalQuery.java deleted file mode 100644 index 444eb945..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsInternalQuery.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.query; - -/** - * Internal owner-lane read that returns copied physics data without widening plugin ABI. - * - * @param immutable query result type - */ -public sealed interface PhysicsInternalQuery permits BenchmarkSpaceStatsQuery, - PhysicsDebugContactsQuery, - PhysicsDebugJointsQuery, - PhysicsSpaceRuntimeStatsQuery, - WorldCollisionPrewarmEnvelopeQuery { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsSpaceRuntimeStatsQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsSpaceRuntimeStatsQuery.java deleted file mode 100644 index cd7b6b2a..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/PhysicsSpaceRuntimeStatsQuery.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsSpaceRuntimeStatsView; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Internal owner-lane query for live backend and registry counters in one physics space. - */ -public record PhysicsSpaceRuntimeStatsQuery(@Nonnull SpaceId spaceId) - implements PhysicsInternalQuery { - - public PhysicsSpaceRuntimeStatsQuery { - Objects.requireNonNull(spaceId, "spaceId"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/WorldCollisionPrewarmEnvelopeQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/WorldCollisionPrewarmEnvelopeQuery.java deleted file mode 100644 index 8a50f599..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/query/WorldCollisionPrewarmEnvelopeQuery.java +++ /dev/null @@ -1,42 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.query; - -import com.hypixel.hytale.server.core.universe.world.World; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Internal owner-lane query that computes collision prewarm coverage for a spawn envelope. - */ -public record WorldCollisionPrewarmEnvelopeQuery(@Nonnull World world, - @Nonnull SpaceId spaceId, - int count, - float originX, - float originY, - float originZ, - int side, - float spacing, - int radius, - float fallEnvelopeMinY, - float horizontalDriftHaloBlocks, - long tick) - implements PhysicsInternalQuery { - - public WorldCollisionPrewarmEnvelopeQuery { - Objects.requireNonNull(world, "world"); - Objects.requireNonNull(spaceId, "spaceId"); - if (count < 0) { - throw new IllegalArgumentException("count must be non-negative"); - } - if (side <= 0) { - throw new IllegalArgumentException("side must be positive"); - } - if (!Float.isFinite(spacing) || spacing <= 0.0f) { - throw new IllegalArgumentException("spacing must be finite and positive"); - } - if (radius < 0) { - throw new IllegalArgumentException("radius must be non-negative"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsSpaceRuntimeStatsView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsSpaceRuntimeStatsView.java deleted file mode 100644 index f0db6312..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsSpaceRuntimeStatsView.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.view; - -/** - * Copied owner-lane runtime counters for one physics space. - */ -public record PhysicsSpaceRuntimeStatsView(int bodies, - int dynamicBodies, - int awakeDynamicBodies, - int sleepingDynamicBodies, - int staticBodies, - int kinematicBodies, - int entityOwnedBodies, - int detachedBodies, - int worldCollisionBodies, - int planeBodies, - int rawBodies, - int joints, - int contacts, - boolean runtimeStatsAvailable, - int runtimeBodyCount, - int runtimeColliderCount, - int runtimeActiveBodyCount, - int runtimeContactPairCount, - int runtimeContactManifoldCount, - int runtimeContactPointCount, - int runtimeDynamicDynamicContactPairCount, - int runtimeTerrainContactPairCount, - int runtimeActiveIslandCount, - int runtimeJointCount) { -} From 87a47ab845d4a17d381c2d4487142222db7fb628 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:49:00 +0200 Subject: [PATCH 089/534] fix(physicsstore): enqueue async reads on owner thread Signed-off-by: Blovien --- .../debug/PhysicsStoreDebugQueries.java | 38 +++++------ .../persistence/PhysicsPersistence.java | 8 +-- .../physicsstore/PhysicsStoreDiagnostics.java | 66 +++++++++++-------- .../physicsstore/PhysicsStoreRaycasts.java | 46 ++++++------- .../physicsstore/PhysicsStoreThreading.java | 52 +++++++++++++++ 5 files changed, 136 insertions(+), 74 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 0a2285ac..fd019656 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -10,7 +10,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; @@ -49,13 +48,15 @@ static CompletionStage> contactsAsync( double viewerX = viewerPosition.x; double viewerY = viewerPosition.y; double viewerZ = viewerPosition.z; - return queue(store).enqueue(physics -> contacts(physics, - spaceId, - viewerX, - viewerY, - viewerZ, - viewRadius, - maxContacts)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore debug contact read", + physics -> contacts(physics, + spaceId, + viewerX, + viewerY, + viewerZ, + viewRadius, + maxContacts)); } @Nonnull @@ -68,13 +69,15 @@ static CompletionStage> jointsAsync( double viewerX = viewerPosition.x; double viewerY = viewerPosition.y; double viewerZ = viewerPosition.z; - return queue(store).enqueue(physics -> joints(physics, - spaceId, - viewerX, - viewerY, - viewerZ, - viewRadius, - maxJoints)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore debug joint read", + physics -> joints(physics, + spaceId, + viewerX, + viewerY, + viewerZ, + viewRadius, + maxJoints)); } @Nonnull @@ -296,11 +299,6 @@ private static SpaceContext space(@Nonnull Store store, return new SpaceContext(spaceHandle, backendRuntime); } - @Nonnull - private static PhysicsStoreReadQueueResource queue(@Nonnull Store store) { - return store.getResource(PhysicsStoreReadQueueResource.getResourceType()); - } - private static double distanceSquared(double x, double y, double z, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 3294f200..70426d69 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -9,7 +9,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; @@ -71,10 +70,9 @@ public static Status status(@Nonnull Store store) { @Nonnull public static CompletionStage statusAsync(@Nonnull Store store) { - Store physicsStore = physicsStore(store); - LegacyStatus legacy = legacyStatus(store); - return physicsStore.getResource(PhysicsStoreReadQueueResource.getResourceType()) - .enqueue(physics -> liveStatus(physics, legacy)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store.getExternalData().getWorld(), + "queue PhysicsStore persistence status read", + physics -> liveStatus(physics, legacyStatus(store))); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index 0d0161fd..253b433d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -3,11 +3,9 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.ArrayList; @@ -22,8 +20,7 @@ * *

The synchronous methods read mutable runtime/backend state and must only run from the * PhysicsStore tick lane or explicitly scheduled PhysicsStore owner work. Off-lane callers should - * use the {@code *Async} methods, which enqueue copied reads for - * {@link PhysicsStoreReadQueueResource}.

+ * use the {@code *Async} methods, which enqueue copied reads on the PhysicsStore owner thread.

*/ public final class PhysicsStoreDiagnostics { @@ -45,14 +42,19 @@ public static int bodyCount(@Nonnull Store store, @Nonnull UUID sp @Nonnull public static CompletionStage bodyCountAsync(@Nonnull World world, @Nonnull SpaceId spaceId) { - return bodyCountAsync(store(world), spaceId); + Objects.requireNonNull(spaceId, "spaceId"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore body count read", + physics -> bodyCount(physics, spaceId)); } @Nonnull public static CompletionStage bodyCountAsync(@Nonnull Store store, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return queue(store).enqueue(physics -> bodyCount(physics, spaceId)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore body count read", + physics -> bodyCount(physics, spaceId)); } public static int runtimeJointCount(@Nonnull Store store) { @@ -66,12 +68,16 @@ public static int runtimeJointCount(@Nonnull Store store) { @Nonnull public static CompletionStage runtimeJointCountAsync(@Nonnull World world) { - return runtimeJointCountAsync(store(world)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore joint count read", + PhysicsStoreDiagnostics::runtimeJointCount); } @Nonnull public static CompletionStage runtimeJointCountAsync(@Nonnull Store store) { - return queue(store).enqueue(PhysicsStoreDiagnostics::runtimeJointCount); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore joint count read", + PhysicsStoreDiagnostics::runtimeJointCount); } public static boolean ccdSupported(@Nonnull Store store) { @@ -88,12 +94,16 @@ public static boolean ccdSupported(@Nonnull Store store) { @Nonnull public static CompletionStage ccdSupportedAsync(@Nonnull World world) { - return ccdSupportedAsync(store(world)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore CCD support read", + PhysicsStoreDiagnostics::ccdSupported); } @Nonnull public static CompletionStage ccdSupportedAsync(@Nonnull Store store) { - return queue(store).enqueue(PhysicsStoreDiagnostics::ccdSupported); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore CCD support read", + PhysicsStoreDiagnostics::ccdSupported); } @Nonnull @@ -108,7 +118,10 @@ public static SolverCapabilitySummary solverCapability(@Nonnull Store solverCapabilityAsync( @Nonnull World world, @Nonnull SpaceId spaceId) { - return solverCapabilityAsync(store(world), spaceId); + Objects.requireNonNull(spaceId, "spaceId"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore solver capability read", + physics -> solverCapability(physics, spaceId)); } @Nonnull @@ -116,7 +129,9 @@ public static CompletionStage solverCapabilityAsync( @Nonnull Store store, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return queue(store).enqueue(physics -> solverCapability(physics, spaceId)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore solver capability read", + physics -> solverCapability(physics, spaceId)); } @Nonnull @@ -154,13 +169,17 @@ public static List spaceSummaries(@Nonnull Store sto @Nonnull public static CompletionStage> spaceSummariesAsync(@Nonnull World world) { - return spaceSummariesAsync(store(world)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore space summaries read", + PhysicsStoreDiagnostics::spaceSummaries); } @Nonnull public static CompletionStage> spaceSummariesAsync( @Nonnull Store store) { - return queue(store).enqueue(PhysicsStoreDiagnostics::spaceSummaries); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore space summaries read", + PhysicsStoreDiagnostics::spaceSummaries); } @Nonnull @@ -203,13 +222,17 @@ public static List unsupportedCcdSpaces(@Nonnull Store> unsupportedCcdSpacesAsync( @Nonnull World world) { - return unsupportedCcdSpacesAsync(store(world)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore unsupported CCD spaces read", + PhysicsStoreDiagnostics::unsupportedCcdSpaces); } @Nonnull public static CompletionStage> unsupportedCcdSpacesAsync( @Nonnull Store store) { - return queue(store).enqueue(PhysicsStoreDiagnostics::unsupportedCcdSpaces); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore unsupported CCD spaces read", + PhysicsStoreDiagnostics::unsupportedCcdSpaces); } @Nonnull @@ -221,15 +244,4 @@ private static SolverCapabilitySummary solverCapability(@Nonnull SpaceId spaceId space.backendRuntime().supportsActivationTuning(space.spaceHandle().value())); } - @Nonnull - private static Store store(@Nonnull World world) { - return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() - .getStore(); - } - - @Nonnull - private static PhysicsStoreReadQueueResource queue(@Nonnull Store store) { - return Objects.requireNonNull(store, "store") - .getResource(PhysicsStoreReadQueueResource.getResourceType()); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java index 89e4a1e0..2ef8c7ca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java @@ -3,11 +3,9 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; @@ -26,7 +24,7 @@ *

The synchronous methods read live backend state through {@link PhysicsRuntimeResource} and * must only be called from the PhysicsStore tick lane or explicitly scheduled PhysicsStore owner * work. Off-lane callers should use the {@code *Async} methods, which copy inputs, enqueue the - * read for {@link PhysicsStoreReadQueueResource}, and complete with copied hit views.

+ * read on the PhysicsStore owner thread, and complete with copied hit views.

*/ public final class PhysicsStoreRaycasts { @@ -96,7 +94,11 @@ public static CompletionStage> closestAsync(@Nonnull Wo @Nonnull SpaceId spaceId, @Nonnull Vector3f from, @Nonnull Vector3f to) { - return closestAsync(store(world), spaceId, from, to); + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore closest raycast read", + physics -> closest(physics, spaceId, copiedFrom, copiedTo)); } @Nonnull @@ -107,8 +109,9 @@ public static CompletionStage> closestAsync( @Nonnull Vector3f to) { Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return queue(store).enqueue(physics -> - closest(physics, spaceId, copiedFrom, copiedTo)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore closest raycast read", + physics -> closest(physics, spaceId, copiedFrom, copiedTo)); } @Nonnull @@ -116,7 +119,11 @@ public static CompletionStage> allAsync(@Nonnull World worl @Nonnull SpaceId spaceId, @Nonnull Vector3f from, @Nonnull Vector3f to) { - return allAsync(store(world), spaceId, from, to); + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore all raycast read", + physics -> all(physics, spaceId, copiedFrom, copiedTo)); } @Nonnull @@ -126,7 +133,9 @@ public static CompletionStage> allAsync(@Nonnull Store all(physics, spaceId, copiedFrom, copiedTo)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore all raycast read", + physics -> all(physics, spaceId, copiedFrom, copiedTo)); } @Nonnull @@ -134,7 +143,10 @@ public static CompletionStage closestBatchAsync( @Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull List rays) { - return closestBatchAsync(store(world), spaceId, rays); + List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore batch raycast read", + physics -> closestBatch(physics, spaceId, copied)); } @Nonnull @@ -143,7 +155,9 @@ public static CompletionStage closestBatchAsync( @Nonnull SpaceId spaceId, @Nonnull List rays) { List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); - return queue(store).enqueue(physics -> closestBatch(physics, spaceId, copied)); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore batch raycast read", + physics -> closestBatch(physics, spaceId, copied)); } @Nonnull @@ -250,18 +264,6 @@ private static RaycastClosestBatchResult closestBatch(@Nonnull Store store(@Nonnull World world) { - return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() - .getStore(); - } - - @Nonnull - private static PhysicsStoreReadQueueResource queue(@Nonnull Store store) { - return Objects.requireNonNull(store, "store") - .getResource(PhysicsStoreReadQueueResource.getResourceType()); - } - private static final class RayHitCapture implements BackendRayHitSink { @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java index 9fdba29d..1997540c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -5,10 +5,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; +import java.util.function.Function; import javax.annotation.Nonnull; /** @@ -56,6 +58,35 @@ public static CompletionStage executeOnWorldThread(@Nonnull World world, return completion.minimalCompletionStage(); } + @Nonnull + public static CompletionStage enqueueReadOnWorldThread( + @Nonnull Store store, + @Nonnull String operation, + @Nonnull Function, R> read) { + return enqueueReadOnWorldThread(world(store), operation, read); + } + + @Nonnull + public static CompletionStage enqueueReadOnWorldThread(@Nonnull World world, + @Nonnull String operation, + @Nonnull Function, R> read) { + Objects.requireNonNull(world, "world"); + Objects.requireNonNull(operation, "operation"); + Objects.requireNonNull(read, "read"); + CompletableFuture completion = new CompletableFuture<>(); + Runnable task = () -> enqueueRead(world, operation, read, completion); + try { + if (world.isInThread()) { + task.run(); + } else { + world.execute(task); + } + } catch (RuntimeException exception) { + PhysicsStoreAsyncCompletions.fail(completion, exception); + } + return completion.minimalCompletionStage(); + } + private static void execute(@Nonnull World world, @Nonnull String operation, @Nonnull Consumer> mutation, @@ -70,6 +101,27 @@ private static void execute(@Nonnull World world, } } + private static void enqueueRead(@Nonnull World world, + @Nonnull String operation, + @Nonnull Function, R> read, + @Nonnull CompletableFuture completion) { + try { + Store store = store(world); + requireWorldThread(store, operation); + store.getResource(PhysicsStoreReadQueueResource.getResourceType()) + .enqueue(read) + .whenComplete((value, failure) -> { + if (failure != null) { + PhysicsStoreAsyncCompletions.fail(completion, failure); + } else { + PhysicsStoreAsyncCompletions.complete(completion, value); + } + }); + } catch (RuntimeException | Error throwable) { + PhysicsStoreAsyncCompletions.fail(completion, throwable); + } + } + @Nonnull private static Store store(@Nonnull World world) { return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() From b7dba491e60958760926dfbd01c9dfee824d3c6e Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 15:53:16 +0200 Subject: [PATCH 090/534] fix(physicsstore): defer control cleanup backend removals Signed-off-by: Blovien --- .../PhysicsStoreControlSessionMutations.java | 81 ++---------- .../PhysicsStoreRegistration.java | 2 + .../systems/BodyBindingSystem.java | 71 ----------- .../systems/StaleBodyRemovalSystem.java | 115 ++++++++++++++++++ 4 files changed, 126 insertions(+), 143 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index c78939fd..e9cba350 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -6,14 +6,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; @@ -47,11 +42,10 @@ public static void applyRelease(@Nonnull Store store, "apply PhysicsStore control-session release mutations"); PhysicsIdentityIndexResource identity = physicsStore.getResource( PhysicsIdentityIndexResource.getResourceType()); - PhysicsRuntimeResource runtime = physicsStore.getResource(PhysicsRuntimeResource.getResourceType()); JointKey controlJointKey = session.getControlJointKey(); if (controlJointKey != null) { - removeJoint(physicsStore, identity, runtime, controlJointKey.value()); + disableJoint(physicsStore, identity, controlJointKey.value()); } RigidBodyKey bodyKey = session.getBodyKey(); @@ -65,7 +59,7 @@ public static void applyRelease(@Nonnull Store store, RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); if (anchorBodyKey != null) { - removeBody(physicsStore, identity, runtime, anchorBodyKey.value()); + removeRow(physicsStore, identity, anchorBodyKey.value(), refForUuid(identity, anchorBodyKey.value())); } } @@ -112,25 +106,19 @@ private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResourc return ref != null && ref.isValid() ? ref : null; } - private static void removeBody(@Nonnull Store store, + private static void disableJoint(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID bodyUuid) { - Ref ref = refForUuid(identity, bodyUuid); - removeBodyBackend(identity, runtime, bodyUuid); - removeRow(store, identity, bodyUuid, ref); - } - - private static void removeJoint(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID jointUuid) { Ref ref = refForUuid(identity, jointUuid); JointComponent joint = ref != null ? store.getComponent(ref, JointComponent.getComponentType()) : null; - removeJointBackend(identity, runtime, jointUuid, joint); - removeRow(store, identity, jointUuid, ref); + if (ref == null || joint == null) { + return; + } + JointComponent disabled = joint.clone(); + disabled.setEnabled(false); + store.putComponent(ref, JointComponent.getComponentType(), disabled); } private static void removeRow(@Nonnull Store store, @@ -144,57 +132,6 @@ private static void removeRow(@Nonnull Store store, store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); } - private static void removeBodyBackend(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID bodyUuid) { - BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); - if (bodyHandle == null) { - return; - } - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); - if (spaceHandle != null && backendRuntime != null) { - backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); - } - identity.removeBodyHandle(bodyHandle); - runtime.removeBodyHandle(bodyUuid); - } - - private static void removeJointBackend(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID jointUuid, - @Nullable JointComponent joint) { - BackendJointHandle jointHandle = runtime.getJointHandle(jointUuid); - if (jointHandle == null) { - return; - } - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); - if (spaceHandle == null && joint != null) { - spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); - } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); - if (spaceHandle != null && backendRuntime != null) { - backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); - } - identity.removeJointHandle(jointHandle); - runtime.removeJointHandle(jointUuid); - } - - @Nullable - private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nullable BackendSpaceHandle spaceHandle) { - if (spaceHandle == null) { - return null; - } - final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; - runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { - if (handle.value() == spaceHandle.value()) { - resolved[0] = backendRuntime; - } - }); - return resolved[0]; - } - @Nonnull private static Vector3f releaseVelocity(@Nonnull PhysicsControlSessionComponent session) { if (session.getOriginalBodyType() == PhysicsBodyType.DYNAMIC) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 6a78cb2b..8e31ac78 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -31,6 +31,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.WorldCollisionIndexSystem; @@ -190,6 +191,7 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new BodyBindingSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); + registry.registerSystem(new StaleBodyRemovalSystem()); registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index c3413fbc..d907b06a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -17,7 +17,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; @@ -28,8 +27,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -59,73 +56,11 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); - if (!removeStaleBodies(store, runtime, identity, restore)) { - return; - } BiConsumer, CommandBuffer> collector = (chunk, _) -> bindBodies(runtime, identity, restore, chunk); store.forEachChunk(systemIndex, collector); } - private static boolean removeStaleBodies(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRestoreStatusResource restore) { - List staleBodies = new ArrayList<>(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> - runtime.forEachBodyHandle(spaceHandle, - bodyId -> collectStaleBody(store, - runtime, - identity, - restore, - staleBodies, - spaceHandle, - backendRuntime, - bodyId))); - if (restore.isFailed()) { - return false; - } - for (BoundBody body : staleBodies) { - try { - body.backendRuntime().removeBody(body.spaceHandle().value(), body.bodyHandle().value()); - } catch (RuntimeException exception) { - restore.markFailed("PhysicsStore body " + body.bodyUuid() - + " failed backend removal: " + exception.getMessage()); - return false; - } - identity.removeBodyHandle(body.bodyHandle()); - runtime.removeBodyHandle(body.bodyUuid()); - } - return true; - } - - private static void collectStaleBody(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull List staleBodies, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull PhysicsBackendRuntime backendRuntime, - long bodyId) { - BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); - if (metadata == null) { - restore.markFailed("PhysicsStore backend body " + bodyId - + " has no runtime snapshot metadata"); - return; - } - Ref ref = PhysicsStoreSystemSupport.refForUuid(identity, metadata.bodyUuid()); - BodyComponent body = PhysicsStoreSystemSupport.component(store, - ref, - BodyComponent.getComponentType()); - if (body != null) { - return; - } - staleBodies.add(new BoundBody(metadata.bodyUuid(), - spaceHandle, - new BackendBodyHandle(bodyId), - backendRuntime)); - } - private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @@ -289,10 +224,4 @@ public Set> getDependencies() { return DEPENDENCIES; } - private record BoundBody(@Nonnull UUID bodyUuid, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle, - @Nonnull PhysicsBackendRuntime backendRuntime) { - } - } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java new file mode 100644 index 00000000..5a5e8efa --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -0,0 +1,115 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Removes backend bodies after their authoritative PhysicsStore body row is gone. + */ +public final class StaleBodyRemovalSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, JointBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + removeStaleBodies(store, runtime, identity, restore); + } + + private static void removeStaleBodies(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore) { + List staleBodies = new ArrayList<>(); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachBodyHandle(spaceHandle, + bodyId -> collectStaleBody(store, + runtime, + identity, + restore, + staleBodies, + spaceHandle, + backendRuntime, + bodyId))); + if (restore.isFailed()) { + return; + } + for (BoundBody body : staleBodies) { + try { + body.backendRuntime().removeBody(body.spaceHandle().value(), body.bodyHandle().value()); + } catch (RuntimeException exception) { + restore.markFailed("PhysicsStore body " + body.bodyUuid() + + " failed backend removal: " + exception.getMessage()); + return; + } + identity.removeBodyHandle(body.bodyHandle()); + runtime.removeBodyHandle(body.bodyUuid()); + } + } + + private static void collectStaleBody(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull List staleBodies, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull PhysicsBackendRuntime backendRuntime, + long bodyId) { + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + if (metadata == null) { + restore.markFailed("PhysicsStore backend body " + bodyId + + " has no runtime snapshot metadata"); + return; + } + Ref ref = PhysicsStoreSystemSupport.refForUuid(identity, metadata.bodyUuid()); + BodyComponent body = PhysicsStoreSystemSupport.component(store, + ref, + BodyComponent.getComponentType()); + if (body != null) { + return; + } + staleBodies.add(new BoundBody(metadata.bodyUuid(), + spaceHandle, + new BackendBodyHandle(bodyId), + backendRuntime)); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + private record BoundBody(@Nonnull UUID bodyUuid, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } +} From f985063f713d6766e7582ab29d7d7c4dc5513d65 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:01:36 +0200 Subject: [PATCH 091/534] fix(physicsstore): apply adaptive ccd step parity Signed-off-by: Blovien --- .../PhysicsWorldSettingsResource.java | 10 + .../systems/StepSubmissionSystem.java | 321 +++++++++++++++++- 2 files changed, 324 insertions(+), 7 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java index 50af28a9..581eca5c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java @@ -14,6 +14,7 @@ public final class PhysicsWorldSettingsResource implements Resource { + private static final float DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP = 0.45f; + private static final float MIN_LINEAR_TRAVEL_PER_SUBSTEP = 0.125f; + private static final float SHAPE_TRAVEL_FRACTION = 0.75f; + private static final float MAX_ANGULAR_RADIANS_PER_SUBSTEP = (float) Math.toRadians(30.0); + private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, PersistenceCaptureSystem.class) ); @@ -34,14 +50,29 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (safeDt <= 0.0f) { return; } - PhysicsWorldSettings settings = store.getResource(PhysicsWorldSettingsResource.getResourceType()) - .getSettings(); - int steps = PhysicsStepCountPolicy.resolveStepCount(safeDt, - settings.getSimulationSteps(), - settings.getMaxStepDt(), - settings.getStepMode()); - float stepDt = safeDt / steps; + PhysicsWorldSettingsResource settingsResource = store.getResource( + PhysicsWorldSettingsResource.getResourceType()); + PhysicsWorldSettings settings = settingsResource.getSettings(); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsStepMode stepMode = settings.getStepMode(); + float maxStepDt = settings.getMaxStepDt() > 0.0f + ? settings.getMaxStepDt() + : PhysicsWorldSettings.DEFAULT_MAX_STEP_DT; + int steps = stepMode == PhysicsStepMode.ADAPTIVE + ? resolveAdaptiveStepCount(runtime, safeDt, settings.getSimulationSteps(), maxStepDt) + : PhysicsStepCountPolicy.resolveStepCount(safeDt, + settings.getSimulationSteps(), + maxStepDt, + stepMode); + float stepDt = safeDt / steps; + boolean ccdMode = stepMode == PhysicsStepMode.CCD; + if (ccdMode || settingsResource.isCcdStepModeActive()) { + syncContinuousCollisionMode(store, + runtime, + store.getResource(PhysicsIdentityIndexResource.getResourceType()), + ccdMode); + } + settingsResource.setCcdStepModeActive(ccdMode); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { for (int step = 0; step < steps; step++) { backendRuntime.step(spaceHandle.value(), stepDt); @@ -49,9 +80,285 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) }); } + private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runtime, + float dt, + int simulationSteps, + float maxStepDt) { + int minimumSteps = PhysicsStepCountPolicy.resolveMaxStepCount(dt, + simulationSteps, + maxStepDt); + StepRisk risk = new StepRisk(dt, minimumSteps); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + backendRuntime.snapshotBodies(spaceHandle.value(), + bodyIds -> runtime.forEachBodyHandle(spaceHandle, bodyIds::accept), + risk)); + return risk.steps(); + } + + private static void syncContinuousCollisionMode(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + boolean forceDynamicBodies) { + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + return; + } + runtime.forEachBodyHandle(spaceHandle, bodyId -> { + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + boolean authoredCcd = metadata != null + && authoredContinuousCollision(store, identity, metadata); + backendRuntime.bodySnapshot(spaceHandle.value(), + bodyId, + new ContinuousCollisionSync(backendRuntime, + spaceHandle, + bodyId, + forceDynamicBodies || authoredCcd)); + }); + }); + } + + private static boolean authoredContinuousCollision(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull BodySnapshotMetadata metadata) { + Ref ref = PhysicsStoreSystemSupport.refForUuid(identity, metadata.bodyUuid()); + DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, + ref, + DynamicsComponent.getComponentType()); + return dynamics != null && dynamics.isContinuousCollisionEnabled(); + } + @Nonnull @Override public Set> getDependencies() { return DEPENDENCIES; } + + private static final class ContinuousCollisionSync implements BackendBodySnapshotSink { + + @Nonnull + private final PhysicsBackendRuntime backendRuntime; + @Nonnull + private final BackendSpaceHandle spaceHandle; + private final long bodyId; + private final boolean targetEnabled; + + private ContinuousCollisionSync(@Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, + long bodyId, + boolean targetEnabled) { + this.backendRuntime = backendRuntime; + this.spaceHandle = spaceHandle; + this.bodyId = bodyId; + this.targetEnabled = targetEnabled; + } + + @Override + public void accept(long bodyId, + int shapeTypeCode, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping, + boolean sensor, + float mass, + float friction, + float restitution, + float linearDamping, + float angularDamping, + int collisionGroup, + int collisionMask, + boolean continuousCollisionEnabled, + float centerOfMassOffsetY, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode) { + if (BackendRuntimeCodes.bodyType(bodyTypeCode) != PhysicsBodyType.DYNAMIC + || continuousCollisionEnabled == targetEnabled) { + return; + } + backendRuntime.setBodyContinuousCollision(spaceHandle.value(), + this.bodyId, + targetEnabled); + } + } + + private static final class StepRisk implements BackendBodySnapshotSink { + + private final float dt; + private int steps; + + private StepRisk(float dt, int minimumSteps) { + this.dt = dt; + steps = minimumSteps; + } + + @Override + public void accept(long bodyId, + int shapeTypeCode, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping, + boolean sensor, + float mass, + float friction, + float restitution, + float linearDamping, + float angularDamping, + int collisionGroup, + int collisionMask, + boolean continuousCollisionEnabled, + float centerOfMassOffsetY, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode) { + if (steps >= PhysicsWorldSettings.MAX_SIMULATION_STEPS + || sleeping + || sensor) { + return; + } + PhysicsBodyType bodyType = BackendRuntimeCodes.bodyType(bodyTypeCode); + if (bodyType != PhysicsBodyType.DYNAMIC && bodyType != PhysicsBodyType.KINEMATIC) { + return; + } + + ShapeType shapeType = BackendRuntimeCodes.shapeType(shapeTypeCode); + float linearTravel = vectorLength(linearVelocityX, + linearVelocityY, + linearVelocityZ) * dt; + float shapeRadius = approximateShapeRadius(shapeType, + hasBoxHalfExtents, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight); + float angularSurfaceTravel = vectorLength(angularVelocityX, + angularVelocityY, + angularVelocityZ) * shapeRadius * dt; + int requiredSteps = Math.max( + requiredSteps(linearTravel, + safeLinearTravel(shapeType, + hasBoxHalfExtents, + halfExtentX, + halfExtentY, + halfExtentZ, + radius)), + requiredSteps(angularSurfaceTravel, safeAngularTravel(shapeRadius))); + steps = Math.clamp(steps, + Math.min(requiredSteps, PhysicsWorldSettings.MAX_SIMULATION_STEPS), + PhysicsWorldSettings.MAX_SIMULATION_STEPS); + } + + private int steps() { + return steps; + } + } + + private static int requiredSteps(float travel, float safeTravel) { + if (travel <= safeTravel) { + return 1; + } + return (int) Math.ceil(travel / safeTravel); + } + + private static float safeLinearTravel(@Nonnull ShapeType shapeType, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius) { + return Math.clamp( + approximateMinimumExtent(shapeType, + hasBoxHalfExtents, + halfExtentX, + halfExtentY, + halfExtentZ, + radius) * SHAPE_TRAVEL_FRACTION, + MIN_LINEAR_TRAVEL_PER_SUBSTEP, + DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP); + } + + private static float safeAngularTravel(float shapeRadius) { + return Math.clamp(shapeRadius * MAX_ANGULAR_RADIANS_PER_SUBSTEP, + MIN_LINEAR_TRAVEL_PER_SUBSTEP, + DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP); + } + + private static float approximateMinimumExtent(@Nonnull ShapeType shapeType, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius) { + if (shapeType == ShapeType.BOX && hasBoxHalfExtents) { + return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, + Math.min(halfExtentX, Math.min(halfExtentY, halfExtentZ))); + } + if (shapeType == ShapeType.SPHERE + || shapeType == ShapeType.CAPSULE + || shapeType == ShapeType.CYLINDER + || shapeType == ShapeType.CONE) { + return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, radius); + } + return DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP; + } + + private static float approximateShapeRadius(@Nonnull ShapeType shapeType, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight) { + if (shapeType == ShapeType.BOX && hasBoxHalfExtents) { + return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, + (float) Math.sqrt(halfExtentX * halfExtentX + + halfExtentY * halfExtentY + + halfExtentZ * halfExtentZ)); + } + if (shapeType == ShapeType.SPHERE) { + return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, radius); + } + if (shapeType == ShapeType.CAPSULE + || shapeType == ShapeType.CYLINDER + || shapeType == ShapeType.CONE) { + return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, radius + halfHeight); + } + return DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP; + } + + private static float vectorLength(float x, float y, float z) { + return (float) Math.sqrt(x * x + y * y + z * z); + } } From 61f66d4864cc959395303f9d6092424614632cfe Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:13:21 +0200 Subject: [PATCH 092/534] feat(physicsstore): publish store event frames Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 2 + .../WorldCollisionPerfResetCommand.java | 15 ++ .../WorldCollisionPerfToggleCommand.java | 16 +++ .../PhysicsStoreRuntimeCleaner.java | 4 + .../PhysicsStoreRegistration.java | 8 ++ .../resources/PhysicsEventResource.java | 82 +++++++++++ .../resources/PhysicsProfilingResource.java | 103 +++++++++++--- .../CompletedStepPublicationSystem.java | 132 +++++++++++++++++- .../systems/StepSubmissionSystem.java | 76 ++++++++++ .../PhysicsWorldRuntimeResource.java | 6 + .../PhysicsStoreEventPublicationSystem.java | 98 +++++++++++++ .../physicsstore/PhysicsStoreTypes.java | 13 ++ 12 files changed, 533 insertions(+), 22 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 4f9f4e09..db508cbe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -32,6 +32,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; +import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; @@ -309,6 +310,7 @@ private void registerSystems() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); persistenceRestoreGroup = entityRegistry.registerSystemGroup(); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); + entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); entityRegistry.registerSystem(new PhysicsDetachedVisualMaterializationSystem()); entityRegistry.registerSystem(new PhysicsSyncSystem()); entityRegistry.registerSystem(new PhysicsDebugSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java index 7a8e2291..86429957 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java @@ -6,6 +6,9 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import javax.annotation.Nonnull; @@ -26,6 +29,18 @@ protected void execute(@Nonnull CommandContext ctx, WorldCollisionProfilingResource.getResourceType()); runtimeProfiling.reset(); profiling.reset(); + Store physicsStore = physicsStoreOrNull(world); + if (physicsStore != null) { + physicsStore.getResource(PhysicsProfilingResource.getResourceType()).reset(); + } ctx.sender().sendMessage(Message.raw("Impulse runtime profiling counters reset")); } + + private static Store physicsStoreOrNull(@Nonnull World world) { + if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { + return null; + } + Store store = physicsStoreWorld.getPhysicsStore().getStore(); + return store.isShutdown() ? null : store; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java index 3e29f256..33cc89de 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java @@ -6,6 +6,9 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import javax.annotation.Nonnull; @@ -27,7 +30,20 @@ protected void execute(@Nonnull CommandContext ctx, boolean enabled = !runtimeProfiling.isEnabled() || !profiling.isEnabled(); runtimeProfiling.setEnabled(enabled); profiling.setEnabled(enabled); + Store physicsStore = physicsStoreOrNull(world); + if (physicsStore != null) { + physicsStore.getResource(PhysicsProfilingResource.getResourceType()) + .setEnabled(enabled); + } ctx.sender().sendMessage(Message.raw("Impulse runtime profiling " + (enabled ? "enabled" : "disabled"))); } + + private static Store physicsStoreOrNull(@Nonnull World world) { + if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { + return null; + } + Store store = physicsStoreWorld.getPhysicsStore().getStore(); + return store.isShutdown() ? null : store; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index 66f55b8b..c3db1b45 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -4,6 +4,8 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -34,6 +36,8 @@ public static void clearAll(@Nonnull Store store) { store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); + store.getResource(PhysicsEventResource.getResourceType()).clear(); + store.getResource(PhysicsProfilingResource.getResourceType()).reset(); store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()).clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 8e31ac78..5d9306d4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -7,6 +7,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; @@ -159,6 +160,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setSnapshotResourceType(registry.registerResource( PhysicsSnapshotResource.class, PhysicsSnapshotResource::new)); + PhysicsStoreTypes.setEventResourceType(registry.registerResource( + PhysicsEventResource.class, + PhysicsEventResource::new)); PhysicsStoreTypes.setReadQueueResourceType(registry.registerResource( PhysicsStoreReadQueueResource.class, PhysicsStoreReadQueueResource::new)); @@ -217,6 +221,10 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic () -> store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsSnapshotResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsEventResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsProfilingResource.getResourceType()).reset()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java new file mode 100644 index 00000000..1968551d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java @@ -0,0 +1,82 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsStepEvent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Latest copied PhysicsStore event frame for EntityStore publication and diagnostics. + */ +public final class PhysicsEventResource implements Resource { + + @Nonnull + private volatile PhysicsEventFrame latestFrame = PhysicsEventFrame.empty(0L); + private long nextFrameSequence; + + public PhysicsEventResource() { + } + + @Nonnull + public PhysicsEventFrame getLatestFrame() { + return latestFrame; + } + + @Nonnull + public PhysicsEventFrame publishStepFrame(long snapshotSequence, + long serverTick, + int bodyCount, + long stepNanos, + long snapshotNanos, + @Nonnull List physicsEvents, + int droppedBackendEventCount) { + long safeSnapshotSequence = Math.max(0L, snapshotSequence); + PhysicsStepEvent stepEvent = new PhysicsStepEvent(safeSnapshotSequence, + serverTick, + safeSnapshotSequence, + PublishedPhysicsSnapshotFrame.Status.COMPLETE, + 0L, + bodyCount, + stepNanos, + snapshotNanos); + PhysicsEventFrame frame = new PhysicsEventFrame(++nextFrameSequence, + safeSnapshotSequence, + safeSnapshotSequence, + safeSnapshotSequence, + serverTick, + 0L, + List.of(), + List.of(stepEvent), + List.of(), + Objects.requireNonNull(physicsEvents, "physicsEvents"), + droppedBackendEventCount); + latestFrame = frame; + return frame; + } + + public void clear() { + latestFrame = PhysicsEventFrame.empty(0L); + nextFrameSequence = 0L; + } + + @Nonnull + @Override + public PhysicsEventResource clone() { + PhysicsEventResource copy = new PhysicsEventResource(); + copy.latestFrame = latestFrame; + copy.nextFrameSequence = nextFrameSequence; + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.eventResourceType(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java index 3d27cacc..8d742aec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java @@ -3,7 +3,9 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import java.util.Objects; import javax.annotation.Nonnull; /** @@ -17,8 +19,12 @@ public final class PhysicsProfilingResource implements Resource { private long snapshotNanos; private long persistenceCaptureNanos; private long stepSubmitNanos; + private int spaces; + private int substeps; private int queuedRequests; private int publishedBodies; + @Nonnull + private PhysicsStepPhaseStats nativePhaseStats = PhysicsStepPhaseStats.unavailable(); public PhysicsProfilingResource() { } @@ -31,20 +37,42 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public void recordLatest(long requestDrainNanos, - long bindingNanos, - long snapshotNanos, - long persistenceCaptureNanos, - long stepSubmitNanos, - int queuedRequests, - int publishedBodies) { - this.requestDrainNanos = requestDrainNanos; - this.bindingNanos = bindingNanos; - this.snapshotNanos = snapshotNanos; - this.persistenceCaptureNanos = persistenceCaptureNanos; - this.stepSubmitNanos = stepSubmitNanos; - this.queuedRequests = queuedRequests; - this.publishedBodies = publishedBodies; + public void recordStep(long stepSubmitNanos, + int spaces, + int substeps, + @Nonnull PhysicsStepPhaseStats nativePhaseStats) { + this.stepSubmitNanos = Math.max(0L, stepSubmitNanos); + this.spaces = Math.max(0, spaces); + this.substeps = Math.max(0, substeps); + this.nativePhaseStats = Objects.requireNonNull(nativePhaseStats, "nativePhaseStats"); + } + + public void recordSnapshot(long snapshotNanos, int publishedBodies) { + this.snapshotNanos = Math.max(0L, snapshotNanos); + this.publishedBodies = Math.max(0, publishedBodies); + } + + public void reset() { + requestDrainNanos = 0L; + bindingNanos = 0L; + snapshotNanos = 0L; + persistenceCaptureNanos = 0L; + stepSubmitNanos = 0L; + spaces = 0; + substeps = 0; + queuedRequests = 0; + publishedBodies = 0; + nativePhaseStats = PhysicsStepPhaseStats.unavailable(); + } + + @Nonnull + public StepSample latestStepSample() { + return new StepSample(spaces, + substeps, + stepSubmitNanos, + snapshotNanos, + publishedBodies, + nativePhaseStats); } public long getRequestDrainNanos() { @@ -67,6 +95,14 @@ public long getStepSubmitNanos() { return stepSubmitNanos; } + public int getSpaces() { + return spaces; + } + + public int getSubsteps() { + return substeps; + } + public int getQueuedRequests() { return queuedRequests; } @@ -75,18 +111,26 @@ public int getPublishedBodies() { return publishedBodies; } + @Nonnull + public PhysicsStepPhaseStats getNativePhaseStats() { + return nativePhaseStats; + } + @Nonnull @Override public PhysicsProfilingResource clone() { PhysicsProfilingResource copy = new PhysicsProfilingResource(); copy.enabled = enabled; - copy.recordLatest(requestDrainNanos, - bindingNanos, - snapshotNanos, - persistenceCaptureNanos, - stepSubmitNanos, - queuedRequests, - publishedBodies); + copy.requestDrainNanos = requestDrainNanos; + copy.bindingNanos = bindingNanos; + copy.snapshotNanos = snapshotNanos; + copy.persistenceCaptureNanos = persistenceCaptureNanos; + copy.stepSubmitNanos = stepSubmitNanos; + copy.spaces = spaces; + copy.substeps = substeps; + copy.queuedRequests = queuedRequests; + copy.publishedBodies = publishedBodies; + copy.nativePhaseStats = nativePhaseStats; return copy; } @@ -94,4 +138,21 @@ public PhysicsProfilingResource clone() { public static ResourceType getResourceType() { return PhysicsStoreTypes.profilingResourceType(); } + + public record StepSample(int spaces, + int substeps, + long stepSubmitNanos, + long snapshotNanos, + int publishedBodies, + @Nonnull PhysicsStepPhaseStats nativePhaseStats) { + + public StepSample { + spaces = Math.max(0, spaces); + substeps = Math.max(0, substeps); + stepSubmitNanos = Math.max(0L, stepSubmitNanos); + snapshotNanos = Math.max(0L, snapshotNanos); + publishedBodies = Math.max(0, publishedBodies); + Objects.requireNonNull(nativePhaseStats, "nativePhaseStats"); + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 275e8cce..45f5fa11 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -6,10 +6,19 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsContactPhase; +import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import java.util.ArrayList; @@ -33,6 +42,9 @@ public final class CompletedStepPublicationSystem extends TickingSystem store) { PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); + boolean profilingEnabled = profiling.isEnabled(); + long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; List bodies = new ArrayList<>(); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> backendRuntime.snapshotBodies(spaceHandle.value(), @@ -90,7 +102,19 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) centerOfMassOffsetY, sleeping))); long nextSequence = snapshot.getLatestFrame().sequence() + 1L; - snapshot.publish(new PhysicsStoreSnapshotFrame(nextSequence, dt, bodies)); + PhysicsStoreSnapshotFrame frame = new PhysicsStoreSnapshotFrame(nextSequence, dt, bodies); + long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; + snapshot.publish(frame); + profiling.recordSnapshot(snapshotNanos, bodies.size()); + StepBackendEvents backendEvents = collectBackendEvents(store, runtime); + store.getResource(PhysicsEventResource.getResourceType()) + .publishStepFrame(frame.sequence(), + Math.max(0L, store.getExternalData().getWorld().getTick()), + bodies.size(), + profiling.getStepSubmitNanos(), + snapshotNanos, + backendEvents.physicsEvents, + backendEvents.droppedBackendEventCount); } private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, @@ -127,9 +151,115 @@ private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, sleeping)); } + @Nonnull + private static StepBackendEvents collectBackendEvents(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime) { + if (!store.getResource(PhysicsWorldSettingsResource.getResourceType()) + .getSettings() + .getEventCollectionMode() + .collectsBackendEvents()) { + return StepBackendEvents.EMPTY; + } + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + StepBackendEvents backendEvents = new StepBackendEvents(); + runtime.forEachSpaceBinding((spaceUuid, _, spaceHandle, backendRuntime) -> { + SpaceId spaceId = compatibility.getSpaceId(spaceUuid); + if (spaceId == null) { + backendEvents.droppedBackendEventCount += backendRuntime.contactCount(spaceHandle.value()); + return; + } + backendRuntime.contacts(spaceHandle.value(), (bodyAId, + bodyBId, + pointAX, + pointAY, + pointAZ, + pointBX, + pointBY, + pointBZ, + normalBX, + normalBY, + normalBZ, + distance, + impulse) -> collectContactEvent(runtime, + backendEvents, + spaceId, + bodyAId, + bodyBId, + pointAX, + pointAY, + pointAZ, + pointBX, + pointBY, + pointBZ, + normalBX, + normalBY, + normalBZ, + distance, + impulse)); + }); + return backendEvents; + } + + private static void collectContactEvent(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull StepBackendEvents backendEvents, + @Nonnull SpaceId spaceId, + long bodyAId, + long bodyBId, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + BodyHitMetadata bodyA = runtime.getBodyHitMetadata(bodyAId); + BodyHitMetadata bodyB = runtime.getBodyHitMetadata(bodyBId); + if (bodyA == null + || bodyA.bodyKey() == null + || bodyB == null + || bodyB.bodyKey() == null) { + backendEvents.droppedBackendEventCount++; + return; + } + backendEvents.physicsEvents.add(new PhysicsContactEvent(spaceId, + PhysicsContactPhase.OBSERVED, + bodyA.bodyKey(), + bodyB.bodyKey(), + new Vector3f(pointAX, pointAY, pointAZ), + new Vector3f(pointBX, pointBY, pointBZ), + new Vector3f(normalBX, normalBY, normalBZ), + distance, + impulse)); + } + @Nonnull @Override public Set> getDependencies() { return DEPENDENCIES; } + + private static final class StepBackendEvents { + + @Nonnull + private static final StepBackendEvents EMPTY = new StepBackendEvents(List.of(), 0); + + @Nonnull + private final List physicsEvents; + private int droppedBackendEventCount; + + private StepBackendEvents() { + this(new ArrayList<>(), 0); + } + + private StepBackendEvents(@Nonnull List physicsEvents, + int droppedBackendEventCount) { + this.physicsEvents = physicsEvents; + this.droppedBackendEventCount = droppedBackendEventCount; + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index b9c712f4..9d66d33b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -8,11 +8,14 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; @@ -73,11 +76,28 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) ccdMode); } settingsResource.setCcdStepModeActive(ccdMode); + PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); + boolean profilingEnabled = profiling.isEnabled(); + if (profilingEnabled) { + resetStepPhaseStats(runtime); + } + long stepStartNanos = profilingEnabled ? System.nanoTime() : 0L; + StepCounters counters = new StepCounters(); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + counters.spaceCount++; for (int step = 0; step < steps; step++) { backendRuntime.step(spaceHandle.value(), stepDt); + counters.substeps++; } }); + long stepNanos = profilingEnabled ? System.nanoTime() - stepStartNanos : 0L; + PhysicsStepPhaseStats nativePhaseStats = profilingEnabled + ? collectStepPhaseStats(runtime) + : PhysicsStepPhaseStats.unavailable(); + profiling.recordStep(stepNanos, + counters.spaceCount, + counters.substeps, + nativePhaseStats); } private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runtime, @@ -127,6 +147,23 @@ private static boolean authoredContinuousCollision(@Nonnull Store return dynamics != null && dynamics.isContinuousCollisionEnabled(); } + private static void resetStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) { + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + backendRuntime.resetStepPhaseStats(spaceHandle.value())); + } + + @Nonnull + private static PhysicsStepPhaseStats collectStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) { + PhysicsStepPhaseStats[] stats = {PhysicsStepPhaseStats.unavailable()}; + StepPhaseStatsCapture capture = new StepPhaseStatsCapture(); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + capture.reset(); + backendRuntime.stepPhaseStats(spaceHandle.value(), capture); + stats[0] = stats[0].add(capture.value()); + }); + return stats[0]; + } + @Nonnull @Override public Set> getDependencies() { @@ -285,6 +322,45 @@ private int steps() { } } + private static final class StepPhaseStatsCapture implements BackendStepPhaseStatsSink { + + @Nonnull + private PhysicsStepPhaseStats value = PhysicsStepPhaseStats.unavailable(); + + @Override + public void accept(long stepNanos, + long broadPhaseNanos, + long narrowPhaseNanos, + long solverNanos, + long continuousCollisionNanos, + long snapshotNanos, + boolean available) { + value = available + ? PhysicsStepPhaseStats.available(stepNanos, + broadPhaseNanos, + narrowPhaseNanos, + solverNanos, + continuousCollisionNanos, + snapshotNanos) + : PhysicsStepPhaseStats.unavailable(); + } + + private void reset() { + value = PhysicsStepPhaseStats.unavailable(); + } + + @Nonnull + private PhysicsStepPhaseStats value() { + return value; + } + } + + private static final class StepCounters { + + private int spaceCount; + private int substeps; + } + private static int requiredSteps(float travel, float safeTravel) { if (travel <= safeTravel) { return 1; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 8aaf0c21..b69ce457 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -17,6 +17,7 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -273,6 +274,11 @@ public PhysicsQueryHandle query(@Nonnull PhysicsQuery query) { @Nonnull @Override public PhysicsEventFrame getLatestEventFrame() { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read latest physics event frame") + .getResource(PhysicsEventResource.getResourceType()) + .getLatestFrame(); + } return lifecycleState.latestEventFrame(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java new file mode 100644 index 00000000..69048912 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -0,0 +1,98 @@ +package dev.hytalemodding.impulse.core.internal.systems.publication; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource.StepSample; +import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import javax.annotation.Nonnull; + +/** + * Forwards copied PhysicsStore event frames into the existing EntityStore world-event boundary. + */ +public final class PhysicsStoreEventPublicationSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.BEFORE, PhysicsDetachedVisualMaterializationSystem.class), + new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) + ); + + @Nonnull + private final Map, Long> lastPublishedSequences = + Collections.synchronizedMap(new WeakHashMap<>()); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { + return; + } + Store physics = physicsStoreWorld.getPhysicsStore().getStore(); + if (physics.isShutdown()) { + return; + } + PhysicsStoreThreading.requireWorldThread(physics, "publish PhysicsStore event frame"); + PhysicsEventFrame frame = physics.getResource(PhysicsEventResource.getResourceType()) + .getLatestFrame(); + if (frame.frameSequence() <= 0L || !markPublished(store, frame.frameSequence())) { + return; + } + recordProfiling(store, physics); + store.invoke(new PhysicsEventFramePublishedEvent(frame)); + } + + private boolean markPublished(@Nonnull Store store, long frameSequence) { + synchronized (lastPublishedSequences) { + long previous = lastPublishedSequences.getOrDefault(store, 0L); + if (frameSequence <= previous) { + return false; + } + lastPublishedSequences.put(store, frameSequence); + return true; + } + } + + private static void recordProfiling(@Nonnull Store store, + @Nonnull Store physics) { + PhysicsRuntimeProfilingResource runtimeProfiling = store.getResource( + PhysicsRuntimeProfilingResource.getResourceType()); + if (!runtimeProfiling.isEnabled()) { + return; + } + StepSample sample = physics.getResource(PhysicsProfilingResource.getResourceType()) + .latestStepSample(); + runtimeProfiling.recordStep(sample.spaces(), + sample.substeps(), + sample.stepSubmitNanos(), + sample.publishedBodies(), + 0, + sample.snapshotNanos(), + 0L, + 0L, + System.nanoTime(), + sample.nativePhaseStats()); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index 748c109f..951138ca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; @@ -95,6 +96,8 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType snapshotResourceType; @Nullable + private static ResourceType eventResourceType; + @Nullable private static ResourceType readQueueResourceType; @Nullable private static ResourceType terrainPayloadResourceType; @@ -232,6 +235,11 @@ public static void setSnapshotResourceType( snapshotResourceType = Objects.requireNonNull(type, "type"); } + public static void setEventResourceType( + @Nonnull ResourceType type) { + eventResourceType = Objects.requireNonNull(type, "type"); + } + public static void setReadQueueResourceType( @Nonnull ResourceType type) { readQueueResourceType = Objects.requireNonNull(type, "type"); @@ -393,6 +401,11 @@ public static ResourceType snapshotResour return require(snapshotResourceType, "PhysicsSnapshotResource"); } + @Nonnull + public static ResourceType eventResourceType() { + return require(eventResourceType, "PhysicsEventResource"); + } + @Nonnull public static ResourceType readQueueResourceType() { return require(readQueueResourceType, "PhysicsStoreReadQueueResource"); From 728d13383077fdda986d0e97b287254e8311f030 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:23:35 +0200 Subject: [PATCH 093/534] feat(physicsstore): publish body registration views Signed-off-by: Blovien --- .../PhysicsStoreRuntimeCleaner.java | 2 + .../PhysicsStoreRegistration.java | 6 + .../PhysicsBodyRegistrationResource.java | 105 ++++++++++++++++++ .../CompletedStepPublicationSystem.java | 87 ++++++++++++++- .../systems/TerrainColliderBindingSystem.java | 11 +- .../PhysicsWorldRuntimeResource.java | 26 +++++ .../physicsstore/PhysicsStoreTypes.java | 15 +++ 7 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index c3db1b45..443ce673 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; @@ -36,6 +37,7 @@ public static void clearAll(@Nonnull Store store) { store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()).clear(); store.getResource(PhysicsEventResource.getResourceType()).clear(); store.getResource(PhysicsProfilingResource.getResourceType()).reset(); store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 5d9306d4..6e1c1b10 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; @@ -160,6 +161,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setSnapshotResourceType(registry.registerResource( PhysicsSnapshotResource.class, PhysicsSnapshotResource::new)); + PhysicsStoreTypes.setBodyRegistrationResourceType(registry.registerResource( + PhysicsBodyRegistrationResource.class, + PhysicsBodyRegistrationResource::new)); PhysicsStoreTypes.setEventResourceType(registry.registerResource( PhysicsEventResource.class, PhysicsEventResource::new)); @@ -221,6 +225,8 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic () -> store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsSnapshotResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsBodyRegistrationResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsEventResource.getResourceType()).clear()); failure = runShutdownCleanup(failure, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java new file mode 100644 index 00000000..5b087e7b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -0,0 +1,105 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Latest copied body registration views published by the authoritative PhysicsStore. + */ +public final class PhysicsBodyRegistrationResource implements Resource { + + @Nonnull + private volatile PublishedRegistrations registrations = PublishedRegistrations.EMPTY; + + public PhysicsBodyRegistrationResource() { + } + + @Nullable + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey bodyKey) { + return registrations.viewsByKey().get(Objects.requireNonNull(bodyKey, "bodyKey")); + } + + @Nonnull + public Collection getBodyRegistrationViews() { + return registrations.views(); + } + + public int getBodyRegistrationCount() { + return registrations.views().size(); + } + + public int getBodyRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { + Objects.requireNonNull(persistenceMode, "persistenceMode"); + int count = 0; + for (PhysicsBodyRegistrationView view : registrations.views()) { + if (view.persistenceMode() == persistenceMode) { + count++; + } + } + return count; + } + + @Nonnull + public Collection getBodyRegistrationViews( + @Nonnull PhysicsBodyKind kind) { + Objects.requireNonNull(kind, "kind"); + List views = new ArrayList<>(); + for (PhysicsBodyRegistrationView view : registrations.views()) { + if (view.kind() == kind) { + views.add(view); + } + } + return views; + } + + public void publish(@Nonnull Collection views) { + Object2ObjectLinkedOpenHashMap viewsByKey = + new Object2ObjectLinkedOpenHashMap<>(); + for (PhysicsBodyRegistrationView view : views) { + PhysicsBodyRegistrationView registration = + Objects.requireNonNull(view, "view"); + viewsByKey.put(registration.bodyKey(), registration); + } + registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), + Map.copyOf(viewsByKey)); + } + + public void clear() { + registrations = PublishedRegistrations.EMPTY; + } + + @Nonnull + @Override + public PhysicsBodyRegistrationResource clone() { + PhysicsBodyRegistrationResource copy = new PhysicsBodyRegistrationResource(); + copy.registrations = registrations; + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.bodyRegistrationResourceType(); + } + + private record PublishedRegistrations( + @Nonnull List views, + @Nonnull Map viewsByKey) { + + private static final PublishedRegistrations EMPTY = + new PublishedRegistrations(List.of(), Map.of()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 45f5fa11..79378655 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -1,14 +1,19 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; @@ -17,13 +22,22 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; import javax.annotation.Nonnull; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -31,7 +45,8 @@ /** * Publishes the last completed backend state as a copied PhysicsStore snapshot frame. */ -public final class CompletedStepPublicationSystem extends TickingSystem { +public final class CompletedStepPublicationSystem extends TickingSystem + implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), @@ -43,9 +58,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()); PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); boolean profilingEnabled = profiling.isEnabled(); long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; List bodies = new ArrayList<>(); + Set snapshotBodyUuids = new ObjectOpenHashSet<>(); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> backendRuntime.snapshotBodies(spaceHandle.value(), bodyConsumer -> runtime.forEachBodyHandle(spaceHandle, bodyConsumer::accept), @@ -84,6 +102,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) _, _) -> collectBodySnapshot(runtime, bodies, + snapshotBodyUuids, bodyId, bodyTypeCode, positionX, @@ -105,6 +124,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsStoreSnapshotFrame frame = new PhysicsStoreSnapshotFrame(nextSequence, dt, bodies); long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; snapshot.publish(frame); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .publish(collectRegistrationViews(store, + systemIndex, + runtime, + compatibility, + snapshotBodyUuids)); profiling.recordSnapshot(snapshotNanos, bodies.size()); StepBackendEvents backendEvents = collectBackendEvents(store, runtime); store.getResource(PhysicsEventResource.getResourceType()) @@ -119,6 +144,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, @Nonnull List bodies, + @Nonnull Set snapshotBodyUuids, long bodyId, int bodyTypeCode, float positionX, @@ -140,6 +166,7 @@ private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, if (metadata == null) { return; } + snapshotBodyUuids.add(metadata.bodyUuid()); bodies.add(new PhysicsStoreBodySnapshot(metadata.bodyUuid(), metadata.spaceUuid(), BackendRuntimeCodes.bodyType(bodyTypeCode), @@ -151,6 +178,58 @@ private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, sleeping)); } + @Nonnull + private static List collectRegistrationViews( + @Nonnull Store store, + int systemIndex, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull Set snapshotBodyUuids) { + List registrations = new ArrayList<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectRegistrationViews(runtime, + compatibility, + snapshotBodyUuids, + registrations, + chunk); + store.forEachChunk(systemIndex, collector); + return registrations; + } + + private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull Set snapshotBodyUuids, + @Nonnull List registrations, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + UUID rowUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(rowUuid)) { + continue; + } + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body != null && snapshotBodyUuids.contains(rowUuid)) { + SpaceId spaceId = compatibility.getSpaceId(body.getSpaceUuid()); + if (spaceId != null) { + registrations.add(new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), + spaceId, + body.getKind(), + body.getPersistenceMode())); + } + } + TerrainColliderComponent terrain = + chunk.getComponent(index, TerrainColliderComponent.getComponentType()); + if (terrain != null && runtime.hasTerrainBodyHandles(rowUuid)) { + SpaceId spaceId = compatibility.getSpaceId(terrain.getSpaceUuid()); + if (spaceId != null) { + registrations.add(new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), + spaceId, + PhysicsBodyKind.WORLD_COLLISION, + PhysicsBodyPersistenceMode.RUNTIME_ONLY)); + } + } + } + } + @Nonnull private static StepBackendEvents collectBackendEvents(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime) { @@ -243,6 +322,12 @@ public Set> getDependencies() { return DEPENDENCIES; } + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.UUID_QUERY; + } + private static final class StepBackendEvents { @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 9a6a1a82..ab061fc8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.TerrainNeighbor; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import java.util.Set; import java.util.UUID; @@ -153,7 +154,10 @@ private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, payload.collisionMask()); BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); runtime.putTerrainBodyHandle(terrainUuid, spaceHandle, bodyHandle, true); - runtime.putBodyHitMetadata(bodyHandle, null, PhysicsBodyType.STATIC, ShapeType.VOXELS); + runtime.putBodyHitMetadata(bodyHandle, + RigidBodyKey.of(terrainUuid), + PhysicsBodyType.STATIC, + ShapeType.VOXELS); } private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, @@ -194,7 +198,10 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, spaceHandle, bodyHandle, false); - runtime.putBodyHitMetadata(bodyHandle, null, PhysicsBodyType.STATIC, ShapeType.BOX); + runtime.putBodyHitMetadata(bodyHandle, + RigidBodyKey.of(terrainUuid), + PhysicsBodyType.STATIC, + ShapeType.BOX); } private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index b69ce457..c5f356c4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -17,6 +17,7 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; @@ -1302,6 +1303,11 @@ public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { @Nullable @Override public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey bodyKey) { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics body registration view") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(bodyKey); + } return bodyRegistry.getPublishedRegistrationView(bodyKey); } @@ -1484,16 +1490,31 @@ public Collection getBodyRegistrations() { @Nonnull @Override public Collection getBodyRegistrationViews() { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics body registration views") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationViews(); + } return bodyRegistry.getPublishedRegistrationViews(); } @Override public int getBodyRegistrationCount() { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics body registration count") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationCount(); + } return bodyRegistry.getPublishedRegistrationCount(); } @Override public int getBodyRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics body registration count") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationCount(persistenceMode); + } return bodyRegistry.getPublishedRegistrationCount(persistenceMode); } @@ -1506,6 +1527,11 @@ public Collection getBodyRegistrations(@Nonnull Physics @Nonnull @Override public Collection getBodyRegistrationViews(@Nonnull PhysicsBodyKind kind) { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics body registration views") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationViews(kind); + } return bodyRegistry.getPublishedRegistrationViews(kind); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index 951138ca..ab7fe95a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; @@ -96,6 +97,9 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType snapshotResourceType; @Nullable + private static ResourceType + bodyRegistrationResourceType; + @Nullable private static ResourceType eventResourceType; @Nullable private static ResourceType readQueueResourceType; @@ -235,6 +239,11 @@ public static void setSnapshotResourceType( snapshotResourceType = Objects.requireNonNull(type, "type"); } + public static void setBodyRegistrationResourceType( + @Nonnull ResourceType type) { + bodyRegistrationResourceType = Objects.requireNonNull(type, "type"); + } + public static void setEventResourceType( @Nonnull ResourceType type) { eventResourceType = Objects.requireNonNull(type, "type"); @@ -401,6 +410,12 @@ public static ResourceType snapshotResour return require(snapshotResourceType, "PhysicsSnapshotResource"); } + @Nonnull + public static ResourceType + bodyRegistrationResourceType() { + return require(bodyRegistrationResourceType, "PhysicsBodyRegistrationResource"); + } + @Nonnull public static ResourceType eventResourceType() { return require(eventResourceType, "PhysicsEventResource"); From 93f501c47e4e82b68e73d2d4ae93d6264b53c27d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:26:44 +0200 Subject: [PATCH 094/534] refactor(physicsstore): group visual projection components Signed-off-by: Blovien --- .../main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java | 2 +- .../impulse/core/internal/commands/CleanCommand.java | 2 +- .../persistence/PersistentPhysicsSpaceBootstrapSystem.java | 2 +- .../visual/PhysicsDetachedVisualMaterializationSystem.java | 2 +- .../physicsstore/projection}/GeneratedVisualProxyComponent.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{internal/components => plugin/physicsstore/projection}/GeneratedVisualProxyComponent.java (93%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index db508cbe..e92dea8c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -20,7 +20,6 @@ import dev.hytalemodding.impulse.api.PhysicsBackend; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; @@ -39,6 +38,7 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.nio.file.Path; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index e4ef26bc..d1e9ec1f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -15,13 +15,13 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerArray; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java index 0fcd7f75..fe7e6547 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java @@ -12,7 +12,6 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRestorePreflight; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsSpaceState; @@ -23,6 +22,7 @@ import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.util.logging.Level; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 77f6bf9d..41cc9621 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -24,7 +24,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; @@ -37,6 +36,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/GeneratedVisualProxyComponent.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/GeneratedVisualProxyComponent.java index 0fbad7c6..5d4a7f74 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/GeneratedVisualProxyComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.components; +package dev.hytalemodding.impulse.core.plugin.physicsstore.projection; import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.component.Component; From 624594639545c44312deaae64d0fc068417ac214 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:33:04 +0200 Subject: [PATCH 095/534] refactor(physicsstore): resolve attachment spaces from registrations Signed-off-by: Blovien --- .../crucible/ImpulseLiveCrucibleTests.java | 1 - ...csDetachedVisualMaterializationSystem.java | 24 ++-- .../projection/BodyAttachmentComponent.java | 117 +++++++++++++++--- .../impulse/examples/commands/EcsCommand.java | 5 +- .../commands/ExamplePhysicsUtils.java | 9 +- .../explosive/ExplosiveBlockRuntime.java | 1 - .../systems/ExplosiveFuseTickSystem.java | 14 ++- 7 files changed, 124 insertions(+), 47 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 280a676b..e1321aa4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -183,7 +183,6 @@ private static Ref spawnLiveBlockBody(Store store, holder.removeComponent(DESPAWN_TYPE); holder.addComponent(ATTACHMENT_TYPE, new BodyAttachmentComponent(bodyKey.value(), - spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY)); holder.addComponent(ImpulseControllableComponent.getComponentType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 41cc9621..7cd63bc5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -498,7 +498,7 @@ private static int processMaterializedProxies(@Nonnull Store store, continue; } Ref proxy = resource.getGeneratedVisualProxy(bodyKey); - if (proxy == null || !isExpectedProxy(store, proxy, bodyKey, registration.spaceId())) { + if (proxy == null || !isExpectedProxy(store, proxy, bodyKey)) { GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey); if (collector != null) { collector.incrementDematerialized(); @@ -624,12 +624,11 @@ private static void removeOrphanVisualFollowers(@Nonnull Store stor var ref = archetypeChunk.getReferenceTo(index); orphanProxies.add(new OrphanVisualProxy(RigidBodyKey.of(attachment.getBodyUuid()), - attachment.getSpaceId(), ref)); }); for (OrphanVisualProxy proxy : orphanProxies) { - if (!hasLiveVisualTarget(resource, proxy.bodyKey(), proxy.spaceId(), proxy.ref())) { + if (!hasLiveVisualTarget(resource, proxy.bodyKey(), proxy.ref())) { GeneratedProxyLifecycle.removeProxy(store, resource, proxy.bodyKey(), proxy.ref()); } } @@ -637,23 +636,18 @@ private static void removeOrphanVisualFollowers(@Nonnull Store stor private static boolean hasLiveVisualTarget(@Nonnull PhysicsWorldRuntimeResource resource, @Nonnull RigidBodyKey bodyKey, - @Nullable SpaceId spaceId, @Nonnull Ref proxyRef) { PhysicsBodyRegistrationView registration = resource.getBodyRegistrationView(bodyKey); if (registration == null) { return resource.isBodyCreationPending(bodyKey) && resource.isGeneratedVisualProxy(bodyKey, proxyRef); } - if (!sameSpaceId(registration.spaceId(), spaceId)) { - return false; - } return resource.getSpaceBinding(registration.spaceId()) != null && resource.isGeneratedVisualProxy(bodyKey, proxyRef); } private record OrphanVisualProxy( @Nonnull RigidBodyKey bodyKey, - @Nullable SpaceId spaceId, @Nonnull Ref ref ) { } @@ -770,8 +764,7 @@ private static boolean isBodyChunkLoaded(@Nonnull Store store, private static boolean isExpectedProxy(@Nonnull Store store, @Nonnull Ref proxy, - @Nonnull RigidBodyKey bodyKey, - @Nullable SpaceId spaceId) { + @Nonnull RigidBodyKey bodyKey) { if (!proxy.isValid()) { return false; } @@ -779,8 +772,7 @@ private static boolean isExpectedProxy(@Nonnull Store store, store.getComponent(proxy, BodyAttachmentComponent.getComponentType()); return attachment != null && attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY - && attachment.getBodyUuid().equals(bodyKey.value()) - && sameSpaceId(attachment.getSpaceId(), spaceId); + && attachment.getBodyUuid().equals(bodyKey.value()); } private static boolean sameSpaceId(@Nullable SpaceId first, @Nullable SpaceId second) { @@ -819,10 +811,10 @@ private static Ref spawnProxy(@Nonnull Store store, holder.addComponent(store.getRegistry().getNonSerializedComponentType(), NonSerialized.get()); holder.addComponent(GeneratedVisualProxyComponent.getComponentType(), new GeneratedVisualProxyComponent()); holder.addComponent(BodyAttachmentComponent.getComponentType(), - new BodyAttachmentComponent(bodyKey.value(), - registration.spaceId(), - TransformAuthority.BODY, - AttachmentLifecycle.GENERATED_PROXY)); + BodyAttachmentComponent.generatedProxy(bodyKey.value(), + new Vector3f(), + new Quaternionf(), + Float.NaN)); return store.addEntity(holder, AddReason.SPAWN); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java index fd32d406..d04acd6e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java @@ -36,10 +36,9 @@ public class BodyAttachmentComponent implements Component { BodyAttachmentComponent::getBodyUuid) .add() .append(new KeyedCodec<>("SpaceId", Codec.INTEGER, false), - (component, value) -> component.spaceId = value != null && value > 0 - ? new SpaceId(value) - : null, - BodyAttachmentComponent::getSpaceIdValue) + (component, _) -> { + }, + _ -> null) .add() .append(new KeyedCodec<>("TransformAuthority", new EnumCodec<>(TransformAuthority.class), false), (component, value) -> component.transformAuthority = value != null @@ -74,11 +73,6 @@ public class BodyAttachmentComponent implements Component { @Nonnull private UUID bodyUuid = UUID.randomUUID(); - @Setter - @Getter - @Nullable - private SpaceId spaceId; - @Setter private TransformAuthority transformAuthority = TransformAuthority.BODY; @@ -98,6 +92,10 @@ public class BodyAttachmentComponent implements Component { public BodyAttachmentComponent() { } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid) { + this(bodyUuid, null); + } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId) { this(bodyUuid, @@ -108,6 +106,12 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, new Quaternionf()); } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, + @Nonnull TransformAuthority transformAuthority, + @Nonnull AttachmentLifecycle lifecycle) { + this(bodyUuid, null, transformAuthority, lifecycle); + } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @@ -115,6 +119,14 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, this(bodyUuid, spaceId, transformAuthority, lifecycle, new Vector3f(), new Quaternionf()); } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, + @Nonnull TransformAuthority transformAuthority, + @Nonnull AttachmentLifecycle lifecycle, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset) { + this(bodyUuid, null, transformAuthority, lifecycle, localPositionOffset, localRotationOffset); + } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @@ -130,6 +142,21 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, USE_BODY_VISUAL_ORIGIN_OFFSET_Y); } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, + @Nonnull TransformAuthority transformAuthority, + @Nonnull AttachmentLifecycle lifecycle, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY) { + this(bodyUuid, + null, + transformAuthority, + lifecycle, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); + } + public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @@ -138,7 +165,6 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY) { this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - this.spaceId = spaceId; this.transformAuthority = Objects.requireNonNull(transformAuthority, "transformAuthority"); this.lifecycle = Objects.requireNonNull(lifecycle, "lifecycle"); this.localPositionOffset.set(Objects.requireNonNull(localPositionOffset, @@ -148,6 +174,11 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, this.visualOriginOffsetY = normalizeVisualOriginOffsetY(visualOriginOffsetY); } + @Nonnull + public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid) { + return externalEntity(bodyUuid, null); + } + @Nonnull public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId) { @@ -157,6 +188,13 @@ public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, AttachmentLifecycle.EXTERNAL_ENTITY); } + @Nonnull + public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset) { + return externalEntity(bodyUuid, null, localPositionOffset, localRotationOffset); + } + @Nonnull public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @@ -170,6 +208,18 @@ public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, localRotationOffset); } + @Nonnull + public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY) { + return externalEntity(bodyUuid, + null, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); + } + @Nonnull public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @@ -185,6 +235,18 @@ public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, visualOriginOffsetY); } + @Nonnull + public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY) { + return impulseOwnedVisual(bodyUuid, + null, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); + } + @Nonnull public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @@ -200,6 +262,18 @@ public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, visualOriginOffsetY); } + @Nonnull + public static BodyAttachmentComponent generatedProxy(@Nonnull UUID bodyUuid, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY) { + return generatedProxy(bodyUuid, + null, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); + } + @Nonnull public static BodyAttachmentComponent generatedProxy(@Nonnull UUID bodyUuid, @Nullable SpaceId spaceId, @@ -224,6 +298,22 @@ public void setBodyUuid(@Nonnull UUID bodyUuid) { this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); } + /** + * Legacy compatibility hook. Space ownership lives in the PhysicsStore body row. + */ + @Deprecated(forRemoval = true) + @Nullable + public SpaceId getSpaceId() { + return null; + } + + /** + * Legacy compatibility hook. Space ownership lives in the PhysicsStore body row. + */ + @Deprecated(forRemoval = true) + public void setSpaceId(@Nullable SpaceId spaceId) { + } + @Nonnull public TransformAuthority getTransformAuthority() { return transformAuthority; @@ -255,16 +345,11 @@ public static ComponentType getComponentTy return ImpulsePlugin.get().getBodyAttachmentComponentType(); } - @Nullable - private Integer getSpaceIdValue() { - return spaceId != null ? spaceId.value() : null; - } - @Nonnull @Override public BodyAttachmentComponent clone() { return new BodyAttachmentComponent(bodyUuid, - spaceId, + null, transformAuthority, lifecycle, localPositionOffset, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index 3ba1ab59..4317dfe8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -282,12 +282,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } return PhysicsStoreAsync.acceptOnWorldThread(world, raycastAsync(ctx, store, ref, spaceId), - hit -> attachView(ctx, store, spaceId, hit)); + hit -> attachView(ctx, store, hit)); } private static void attachView(@Nonnull CommandContext ctx, @Nonnull Store store, - @Nonnull SpaceId spaceId, @Nullable RaycastHitView hit) { if (hit == null || hit.bodyKey() == null) { ctx.sender().sendMessage(Message.raw("No rigid body in view.")); @@ -299,7 +298,6 @@ private static void attachView(@Nonnull CommandContext ctx, ExamplePhysicsUtils.spawnExternalBodyViewBlockEntity(store, time, hit.bodyKey(), - spaceId, point, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE); @@ -462,7 +460,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, verticalLift); Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, bodyUuid, - spaceId, blockType, spawn, new Vector3f(), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 70bf0a16..9017de17 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -624,7 +624,6 @@ public static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store entity = spawnAttachedPhysicsStoreBlockEntity(store, time, pending.bodyKey().value(), - pending.spaceId(), pending.blockType(), new Vector3d(pending.positionX(), pending.positionY(), pending.positionZ()), pending.controllable()); @@ -737,7 +736,6 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store entity = spawnAttachedPhysicsStoreBlockEntity(store, time, bodyKey.value(), - spaceId, blockType, new Vector3d(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), mass > 0.0f); @@ -767,12 +765,11 @@ static void addControllableMarkerIfAvailable(@Nonnull Holder holder public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, @Nonnull Vector3d visualPosition, @Nullable String blockType) { Holder holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(ATTACHMENT_TYPE, - BodyAttachmentComponent.externalEntity(bodyKey.value(), spaceId)); + BodyAttachmentComponent.externalEntity(bodyKey.value())); return store.addEntity(holder, AddReason.SPAWN); } @@ -786,13 +783,11 @@ static Vector3d visualPositionFromBodyCenter(@Nonnull Vector3d bodyCenter, private static Ref spawnAttachedPhysicsStoreBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull UUID physicsBodyUuid, - @Nonnull SpaceId spaceId, @Nullable String blockType, @Nonnull Vector3d visualPosition, boolean controllable) { Holder holder = attachedPhysicsStoreBlockEntityHolder(time, physicsBodyUuid, - spaceId, blockType, visualPosition, new Vector3f(), @@ -805,7 +800,6 @@ private static Ref spawnAttachedPhysicsStoreBlockEntity(@Nonnull St @Nonnull public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, @Nonnull UUID physicsBodyUuid, - @Nonnull SpaceId spaceId, @Nullable String blockType, @Nonnull Vector3d visualPosition, @Nonnull Vector3f localPositionOffset, @@ -815,7 +809,6 @@ public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull Holder holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(ATTACHMENT_TYPE, BodyAttachmentComponent.impulseOwnedVisual(physicsBodyUuid, - spaceId, localPositionOffset, localRotationOffset, visualOriginOffsetY)); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index c2dd2112..e46b892f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -193,7 +193,6 @@ private static void spawnGroupVisuals(@Nonnull TimeResource time, boolean controllable = body.controllable() && !controllableAssigned; Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, body.bodyKey().value(), - body.spaceId(), visual.blockType(), visual.position(), visual.localPositionOffset(), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index af0f41db..f40140d9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -16,9 +16,12 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -61,7 +64,7 @@ public void tick(float dt, ExplosiveBlockComponent explosive = chunk.getComponent(index, EXPLOSIVE_TYPE); BodyAttachmentComponent attachment = chunk.getComponent(index, ATTACHMENT_TYPE); TransformComponent transform = chunk.getComponent(index, TRANSFORM_TYPE); - SpaceId spaceId = attachment != null ? attachment.getSpaceId() : null; + SpaceId spaceId = attachment != null ? attachmentSpaceId(store, attachment) : null; if (explosive == null || attachment == null || transform == null || spaceId == null) { return; } @@ -113,6 +116,15 @@ private static Vector3d explosionCenter(@Nullable BodyMotionSnapshot snapshot, return ExplosiveBlockRuntime.sourceExplosionCenter(new Vector3d(transform.getPosition())); } + @Nullable + private static SpaceId attachmentSpaceId(@Nonnull Store store, + @Nonnull BodyAttachmentComponent attachment) { + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + PhysicsBodyRegistrationView registration = + resource.getBodyRegistrationView(RigidBodyKey.of(attachment.getBodyUuid())); + return registration != null ? registration.spaceId() : null; + } + @Nullable private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { From 147e6c5719f21e1d6f0726c180fbe3e49d0cabf4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:46:49 +0200 Subject: [PATCH 096/534] refactor(physicsstore): read public snapshots from store frames Signed-off-by: Blovien --- .../CompletedStepPublicationSystem.java | 2 +- .../systems/PersistenceCaptureSystem.java | 2 +- .../PhysicsStoreReadRequestSystem.java | 3 +- .../PhysicsWorldRuntimeResource.java | 326 ++++++++++++++++++ 4 files changed, 329 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 79378655..314f53b3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -50,7 +50,7 @@ public final class CompletedStepPublicationSystem extends TickingSystem> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), - new SystemDependency<>(Order.AFTER, PhysicsStoreReadRequestSystem.class) + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index 49508d6d..eedaf447 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -60,7 +60,7 @@ public final class PersistenceCaptureSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, CompletedStepPublicationSystem.class) + new SystemDependency<>(Order.AFTER, PhysicsStoreReadRequestSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java index 7c9942f5..73a1e8cb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java @@ -18,8 +18,7 @@ public final class PhysicsStoreReadRequestSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) + new SystemDependency<>(Order.AFTER, CompletedStepPublicationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index c5f356c4..498db6ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -8,9 +8,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; @@ -21,6 +23,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; @@ -61,13 +64,20 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -648,6 +658,13 @@ public Iterable iterateSpaceBindings() { @Override public int refreshBodySnapshots() { + if (isAuthoritativePhysicsStoreActive()) { + return authoritativePhysicsStore("refresh copied physics body snapshots") + .getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame() + .bodies() + .size(); + } return callOwner("refresh physics body snapshots", () -> { PublishedPhysicsSnapshotFrame frame = capturePublishedSnapshotFrameDirect(0L, 0L, @@ -661,6 +678,16 @@ public int refreshBodySnapshots() { @Nonnull @Override public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { + if (isAuthoritativePhysicsStoreActive()) { + Store store = + authoritativePhysicsStore("read copied physics body snapshot"); + PhysicsBodySnapshot snapshot = getAuthoritativeBodySnapshot(store, bodyKey); + if (snapshot == null) { + throw new IllegalStateException("No copied PhysicsStore body snapshot is available for " + + bodyKey); + } + return snapshot; + } PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); if (snapshot != null) { return snapshot; @@ -671,6 +698,11 @@ public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { @Nullable public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull RigidBodyKey bodyKey) { + if (isAuthoritativePhysicsStoreActive()) { + return getAuthoritativeBodySnapshot( + authoritativePhysicsStore("read optional copied physics body snapshot"), + bodyKey); + } PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); if (snapshot != null) { return snapshot; @@ -699,6 +731,255 @@ private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull RigidBody return registration != null ? captureLiveBodySnapshot(registration) : null; } + @Nullable + private static PhysicsBodySnapshot getAuthoritativeBodySnapshot( + @Nonnull Store store, + @Nonnull RigidBodyKey bodyKey) { + PhysicsStoreBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(Objects.requireNonNull(bodyKey, "bodyKey").value()); + return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; + } + + private static int countAuthoritativeBodySnapshots(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + if (spaceUuid == null) { + return 0; + } + int count = 0; + for (PhysicsStoreBodySnapshot body : store.getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame() + .bodies()) { + if (spaceUuid.equals(body.spaceUuid())) { + count++; + } + } + return count; + } + + private static void forEachAuthoritativeBodySnapshot(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Consumer consumer) { + UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); + if (spaceUuid == null) { + return; + } + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); + for (PhysicsStoreBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { + if (!spaceUuid.equals(body.spaceUuid())) { + continue; + } + PhysicsBodySnapshotEntry entry = + authoritativeSnapshotEntry(store, registrations, body); + if (entry != null) { + consumer.accept(entry); + } + } + } + + private static void forEachIndexedAuthoritativeBodySnapshot( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodySnapshotVisitor visitor) { + UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); + if (spaceUuid == null) { + return; + } + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); + for (PhysicsStoreBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { + if (!spaceUuid.equals(body.spaceUuid())) { + continue; + } + PhysicsBodySnapshotEntry entry = + authoritativeSnapshotEntry(store, registrations, body); + if (entry != null) { + visitor.accept(entry.bodyKey(), + entry.snapshot(), + entry.spaceId(), + entry.kind(), + entry.persistenceMode()); + } + } + } + + private static int forEachAuthoritativeBodySnapshotNear( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f center, + float radius, + @Nonnull Consumer consumer) { + UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); + if (spaceUuid == null || radius < 0.0f || Float.isNaN(radius)) { + return 0; + } + float radiusSquared = radius * radius; + int candidates = 0; + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); + for (PhysicsStoreBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { + if (!spaceUuid.equals(body.spaceUuid())) { + continue; + } + PhysicsBodySnapshotEntry entry = + authoritativeSnapshotEntry(store, registrations, body); + if (entry == null) { + continue; + } + candidates++; + if (withinRadius(entry.snapshot(), center, radiusSquared)) { + consumer.accept(entry); + } + } + return candidates; + } + + private static int forEachIndexedAuthoritativeBodySnapshotNear( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f center, + float radius, + @Nonnull PhysicsBodySnapshotVisitor visitor) { + UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); + if (spaceUuid == null || radius < 0.0f || Float.isNaN(radius)) { + return 0; + } + float radiusSquared = radius * radius; + int candidates = 0; + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); + for (PhysicsStoreBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { + if (!spaceUuid.equals(body.spaceUuid())) { + continue; + } + PhysicsBodySnapshotEntry entry = + authoritativeSnapshotEntry(store, registrations, body); + if (entry == null) { + continue; + } + candidates++; + if (withinRadius(entry.snapshot(), center, radiusSquared)) { + visitor.accept(entry.bodyKey(), + entry.snapshot(), + entry.spaceId(), + entry.kind(), + entry.persistenceMode()); + } + } + return candidates; + } + + @Nullable + private static UUID authoritativeSpaceUuid(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + } + + @Nonnull + private static PhysicsStoreSnapshotFrame authoritativeSnapshotFrame( + @Nonnull Store store) { + return store.getResource(PhysicsSnapshotResource.getResourceType()).getLatestFrame(); + } + + @Nullable + private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( + @Nonnull Store store, + @Nonnull PhysicsBodyRegistrationResource registrations, + @Nonnull PhysicsStoreBodySnapshot body) { + RigidBodyKey bodyKey = RigidBodyKey.of(body.bodyUuid()); + PhysicsBodyRegistrationView registration = registrations.getBodyRegistrationView(bodyKey); + if (registration == null) { + return null; + } + return new PhysicsBodySnapshotEntry(bodyKey, + toPublicBodySnapshot(store, body), + registration.spaceId(), + registration.kind(), + registration.persistenceMode()); + } + + private static boolean withinRadius(@Nonnull PhysicsBodySnapshot snapshot, + @Nonnull Vector3f center, + float radiusSquared) { + Objects.requireNonNull(center, "center"); + float dx = snapshot.positionX() - center.x; + float dy = snapshot.positionY() - center.y; + float dz = snapshot.positionZ() - center.z; + return dx * dx + dy * dy + dz * dz <= radiusSquared; + } + + @Nonnull + private static PhysicsBodySnapshot toPublicBodySnapshot(@Nonnull Store store, + @Nonnull PhysicsStoreBodySnapshot body) { + Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(body.bodyUuid()); + boolean validRef = ref != null && ref.isValid(); + DynamicsComponent dynamics = validRef + ? store.getComponent(ref, DynamicsComponent.getComponentType()) + : null; + ColliderComponent collider = validRef + ? store.getComponent(ref, ColliderComponent.getComponentType()) + : null; + MaterialComponent material = validRef + ? store.getComponent(ref, MaterialComponent.getComponentType()) + : null; + CollisionFilterComponent filter = validRef + ? store.getComponent(ref, CollisionFilterComponent.getComponentType()) + : null; + ShapeComponent shape = validRef + ? store.getComponent(ref, ShapeComponent.getComponentType()) + : null; + + Vector3f position = body.position(); + Quaternionf rotation = body.rotation(); + Vector3f linearVelocity = body.linearVelocity(); + Vector3f angularVelocity = body.angularVelocity(); + PhysicsBodyType bodyType = body.bodyType(); + ShapeType shapeType = shape != null ? shape.getShapeType() : ShapeType.UNKNOWN; + boolean hasBoxHalfExtents = shapeType == ShapeType.BOX && shape != null; + + return PhysicsBodySnapshot.of(position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w, + linearVelocity.x, + linearVelocity.y, + linearVelocity.z, + angularVelocity.x, + angularVelocity.y, + angularVelocity.z, + bodyType, + body.sleeping(), + collider != null && collider.isSensor(), + bodyType == PhysicsBodyType.DYNAMIC ? authoredMass(dynamics) : 0.0f, + material != null ? material.getFriction() : 0.5f, + material != null ? material.getRestitution() : 0.0f, + dynamics != null ? dynamics.getLinearDamping() : 0.0f, + dynamics != null ? dynamics.getAngularDamping() : 0.0f, + filter != null ? filter.getCollisionGroup() : PhysicsCollisionFilters.DYNAMIC_BODY, + filter != null ? filter.getCollisionMask() : PhysicsCollisionFilters.ALL, + dynamics != null && dynamics.isContinuousCollisionEnabled(), + body.centerOfMassOffsetY(), + shapeType, + hasBoxHalfExtents, + hasBoxHalfExtents ? shape.getHalfExtentX() : 0.0f, + hasBoxHalfExtents ? shape.getHalfExtentY() : 0.0f, + hasBoxHalfExtents ? shape.getHalfExtentZ() : 0.0f, + shape != null ? shape.getRadius() : 0.0f, + shape != null ? shape.getHalfHeight() : 0.0f, + shape != null ? shape.getAxis() : PhysicsAxis.Y); + } + + private static float authoredMass(@Nullable DynamicsComponent dynamics) { + return dynamics != null ? dynamics.getMass() : 1.0f; + } + @Nonnull public PhysicsBodySnapshot captureLiveBodySnapshot(@Nonnull PhysicsBodyRegistration registration) { Objects.requireNonNull(registration, "registration"); @@ -809,16 +1090,31 @@ public long getLatestSnapshotAppliedNanos() { @Override public int getBodySnapshotCount() { + if (isAuthoritativePhysicsStoreActive()) { + return authoritativePhysicsStore("count copied physics body snapshots") + .getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame() + .bodies() + .size(); + } return lifecycleState.bodySnapshotCount(); } @Override public int getBodySnapshotCount(@Nonnull SpaceId spaceId) { + if (isAuthoritativePhysicsStoreActive()) { + return countAuthoritativeBodySnapshots( + authoritativePhysicsStore("count copied physics body snapshots"), + spaceId); + } return lifecycleState.bodySnapshotCount(spaceId); } @Override public int getBodySnapshotCellCount() { + if (isAuthoritativePhysicsStoreActive()) { + return 0; + } return lifecycleState.bodySnapshotCellCount(); } @@ -999,11 +1295,25 @@ private void requireWorldCollisionSpaceEnabled(@Nonnull SpaceId spaceId) { @Override public void forEachBodySnapshot(@Nonnull SpaceId spaceId, @Nonnull Consumer consumer) { + if (isAuthoritativePhysicsStoreActive()) { + forEachAuthoritativeBodySnapshot( + authoritativePhysicsStore("iterate copied physics body snapshots"), + spaceId, + consumer); + return; + } lifecycleState.forEachBodySnapshot(spaceId, consumer); } public void forEachIndexedBodySnapshot(@Nonnull SpaceId spaceId, @Nonnull PhysicsBodySnapshotVisitor visitor) { + if (isAuthoritativePhysicsStoreActive()) { + forEachIndexedAuthoritativeBodySnapshot( + authoritativePhysicsStore("iterate copied physics body snapshots"), + spaceId, + visitor); + return; + } lifecycleState.forEachIndexedBodySnapshot(spaceId, visitor); } @@ -1012,6 +1322,14 @@ public int forEachBodySnapshotNear(@Nonnull SpaceId spaceId, @Nonnull Vector3f center, float radius, @Nonnull Consumer consumer) { + if (isAuthoritativePhysicsStoreActive()) { + return forEachAuthoritativeBodySnapshotNear( + authoritativePhysicsStore("iterate nearby copied physics body snapshots"), + spaceId, + center, + radius, + consumer); + } return lifecycleState.forEachBodySnapshotNear(spaceId, center, radius, consumer); } @@ -1019,6 +1337,14 @@ public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, @Nonnull Vector3f center, float radius, @Nonnull PhysicsBodySnapshotVisitor visitor) { + if (isAuthoritativePhysicsStoreActive()) { + return forEachIndexedAuthoritativeBodySnapshotNear( + authoritativePhysicsStore("iterate nearby copied physics body snapshots"), + spaceId, + center, + radius, + visitor); + } return lifecycleState.forEachIndexedBodySnapshotNear(spaceId, center, radius, visitor); } From 91b766b7a8891b1e0d25822b816a2cd11b68a4bd Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:48:26 +0200 Subject: [PATCH 097/534] docs(physicsstore): clarify copied snapshot reads Signed-off-by: Blovien --- .../plugin/resources/PhysicsWorldResource.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 4f014491..b97ffa4f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -199,16 +199,21 @@ public abstract PhysicsMutationHandle createSpaceAsync( public abstract int getSpaceCount(); /** - * Captures and publishes body snapshots from the live backend state. + * Captures and publishes body snapshots from the live backend state in the legacy runtime, or + * returns the latest copied PhysicsStore snapshot count when authoritative PhysicsStore is + * active. * - * @return number of published body snapshots + * @return number of copied body snapshots */ public abstract int refreshBodySnapshots(); /** - * Returns the latest published snapshot for a body, capturing a copied live snapshot on the - * physics owner if the body is registered but missing from the published frame. The fallback - * snapshot is not published into the reader-side frame. + * Returns the latest published snapshot for a body. + * + *

The legacy runtime may capture a copied live snapshot on the physics owner when the body + * is registered but missing from the published frame. Authoritative PhysicsStore mode reads + * only the copied {@code PhysicsSnapshotResource} frame and does not synchronously touch the + * live backend.

*/ @Nonnull public abstract PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey); @@ -224,7 +229,8 @@ public abstract PhysicsMutationHandle createSpaceAsync( public abstract int getBodySnapshotCount(@Nonnull SpaceId spaceId); /** - * Returns the number of occupied snapshot broad-phase cells. + * Returns the number of occupied legacy snapshot broad-phase cells, or {@code 0} for the flat + * authoritative PhysicsStore snapshot frame. */ public abstract int getBodySnapshotCellCount(); From 35c86104b1791212a0dadd2ed918f1f725195f5c Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:49:56 +0200 Subject: [PATCH 098/534] fix(physicsstore): assert lane for row helper mutations Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreEntities.java | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java index c22c9e61..eea9c63b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java @@ -182,15 +182,16 @@ public static void putSpaceComponents(@Nonnull Store store, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { - Objects.requireNonNull(store, "store"); + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsStoreThreading.requireWorldThread(checkedStore, "put PhysicsStore space components"); Objects.requireNonNull(ref, "ref"); - store.putComponent(ref, + checkedStore.putComponent(ref, SpaceComponent.getComponentType(), Objects.requireNonNull(space, "space").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, WorldCollisionComponent.getComponentType(), Objects.requireNonNull(worldCollision, "worldCollision").clone()); - putSpaceSettingsComponents(store, + putSpaceSettingsComponents(checkedStore, ref, solverSettings, visualSyncSettings, @@ -206,22 +207,24 @@ public static void putSpaceSettingsComponents(@Nonnull Store store @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { - Objects.requireNonNull(store, "store"); + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsStoreThreading.requireWorldThread(checkedStore, + "put PhysicsStore space settings components"); Objects.requireNonNull(ref, "ref"); - store.putComponent(ref, + checkedStore.putComponent(ref, SolverSettingsComponent.getComponentType(), Objects.requireNonNull(solverSettings, "solverSettings").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, VisualSyncSettingsComponent.getComponentType(), Objects.requireNonNull(visualSyncSettings, "visualSyncSettings").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, VisualMaterializationSettingsComponent.getComponentType(), Objects.requireNonNull(visualMaterializationSettings, "visualMaterializationSettings").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, CollisionLodSettingsComponent.getComponentType(), Objects.requireNonNull(collisionLodSettings, "collisionLodSettings").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, ExtensionSettingsComponent.getComponentType(), Objects.requireNonNull(extensionSettings, "extensionSettings").clone()); } @@ -235,37 +238,40 @@ public static void putBodyComponents(@Nonnull Store store, @Nonnull ShapeComponent shape, @Nonnull MaterialComponent material, @Nonnull CollisionFilterComponent filter) { - Objects.requireNonNull(store, "store"); + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsStoreThreading.requireWorldThread(checkedStore, "put PhysicsStore body components"); Objects.requireNonNull(ref, "ref"); - store.putComponent(ref, + checkedStore.putComponent(ref, BodyComponent.getComponentType(), Objects.requireNonNull(body, "body").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, DynamicsComponent.getComponentType(), Objects.requireNonNull(dynamics, "dynamics").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, ColliderComponent.getComponentType(), Objects.requireNonNull(collider, "collider").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, ShapeComponent.getComponentType(), Objects.requireNonNull(shape, "shape").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, MaterialComponent.getComponentType(), Objects.requireNonNull(material, "material").clone()); - store.putComponent(ref, + checkedStore.putComponent(ref, CollisionFilterComponent.getComponentType(), Objects.requireNonNull(filter, "filter").clone()); if (target != null) { - store.putComponent(ref, TargetComponent.getComponentType(), target.clone()); + checkedStore.putComponent(ref, TargetComponent.getComponentType(), target.clone()); } else { - store.removeComponent(ref, TargetComponent.getComponentType()); + checkedStore.removeComponent(ref, TargetComponent.getComponentType()); } } public static void putJointComponent(@Nonnull Store store, @Nonnull Ref ref, @Nonnull JointComponent joint) { - Objects.requireNonNull(store, "store") + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsStoreThreading.requireWorldThread(checkedStore, "put PhysicsStore joint component"); + checkedStore .putComponent(Objects.requireNonNull(ref, "ref"), JointComponent.getComponentType(), Objects.requireNonNull(joint, "joint").clone()); @@ -274,7 +280,10 @@ public static void putJointComponent(@Nonnull Store store, public static void putTerrainColliderComponent(@Nonnull Store store, @Nonnull Ref ref, @Nonnull TerrainColliderComponent terrainCollider) { - Objects.requireNonNull(store, "store") + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsStoreThreading.requireWorldThread(checkedStore, + "put PhysicsStore terrain collider component"); + checkedStore .putComponent(Objects.requireNonNull(ref, "ref"), TerrainColliderComponent.getComponentType(), Objects.requireNonNull(terrainCollider, "terrainCollider").clone()); From 7dba01c222c8d0ac1c8b6e4a6971bf1966c3df1a Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 16:58:34 +0200 Subject: [PATCH 099/534] refactor(physicsstore): route collision lod through body commands Signed-off-by: Blovien --- .../systems/PhysicsCollisionLodSystem.java | 119 ++++++++++++++++-- .../systems/BodyCommandApplicationSystem.java | 29 +++++ .../components/BodyCommandComponent.java | 79 ++++++++++-- 3 files changed, 213 insertions(+), 14 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java index 2b25644f..79b73a3b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java @@ -1,12 +1,15 @@ package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; @@ -17,11 +20,17 @@ import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; @@ -37,6 +46,7 @@ import java.util.Queue; import java.util.Set; import java.util.WeakHashMap; +import java.util.UUID; import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -74,9 +84,11 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { return; } - PhysicsMutationHandle handle = PhysicsOwnerBridge.runAsync(store, - "apply collision LOD filters", - () -> applyUpdates(resource, updates)); + PhysicsMutationHandle handle = isAuthoritativePhysicsStoreActive() + ? applyAuthoritativeUpdatesAsync(store, updates) + : PhysicsOwnerBridge.runAsync(store, + "apply collision LOD filters", + () -> applyUpdates(resource, updates)); state.trackPendingMutation(handle, updates); } @@ -89,6 +101,24 @@ private List collectUpdates(@Nonnull Store stor IntOpenHashSet activeSpaces = new IntOpenHashSet(); List interests = VisualInterestCollector.collectMaterializationInterests(store, resource); + if (isAuthoritativePhysicsStoreActive()) { + for (SpaceId spaceId : resource.getSpaceIds()) { + activeSpaces.add(spaceId.value()); + PhysicsSpaceSettings settings = resource.getSpaceSettings(spaceId); + if (!settings.getCollisionLodSettings().isCollisionLodEnabled()) { + state.collectRestoreUpdates(spaceId, updates); + continue; + } + if (!state.shouldRefresh(spaceId, + settings.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks(), + tick)) { + continue; + } + collectSpaceUpdates(resource, spaceId, settings, interests, state, updates); + } + state.pruneRemovedSpaces(activeSpaces); + return updates; + } for (PhysicsSpaceBinding space : resource.getSpaceBindings()) { SpaceId spaceId = space.spaceId(); activeSpaces.add(spaceId.value()); @@ -134,7 +164,11 @@ private static void collectSpaceUpdates(@Nonnull PhysicsWorldRuntimeResource res snapshot.positionY(), snapshot.positionZ(), interests); - state.recordTier(spaceId, bodyKey, tier, updates); + state.recordTier(spaceId, + bodyKey, + tier, + settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled(), + updates); }); state.pruneMissingBodies(spaceId, seenBodies); } @@ -217,6 +251,70 @@ static boolean isCollisionLodCandidate(@Nonnull PhysicsBodySnapshot snapshot, && !snapshot.sensor(); } + @Nonnull + private static PhysicsMutationHandle applyAuthoritativeUpdatesAsync( + @Nonnull Store store, + @Nonnull List updates) { + World world = store.getExternalData().getWorld(); + return PhysicsMutationHandle.fromCompletion("apply collision LOD filters", + null, + PhysicsStoreThreading.executeOnWorldThread(world, + "apply collision LOD filters", + physics -> applyAuthoritativeUpdates(physics, updates))); + } + + private static void applyAuthoritativeUpdates(@Nonnull Store store, + @Nonnull List updates) { + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + for (CollisionLodUpdate update : updates) { + UUID spaceUuid = compatibility.getSpaceUuid(update.spaceId()); + if (spaceUuid == null) { + continue; + } + Ref bodyRef = identity.getByUuid(update.bodyKey().value()); + if (bodyRef == null || !bodyRef.isValid()) { + continue; + } + BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); + if (body == null + || !spaceUuid.equals(body.getSpaceUuid()) + || body.getKind() != PhysicsBodyKind.BODY + || (update.trackTier() + && body.getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT)) { + continue; + } + appendBodyCommand(store, bodyRef, collisionFilterCommand(update)); + if (update.tier() == CollisionLodTier.FAR_SLEEPING && update.farSleepEnabled()) { + appendBodyCommand(store, bodyRef, BodyCommandComponent.sleep()); + } + } + } + + @Nonnull + private static BodyCommandComponent collisionFilterCommand(@Nonnull CollisionLodUpdate update) { + int terrainOnlyMask = PhysicsCollisionFilters.TERRAIN; + int fullDynamicMask = PhysicsCollisionFilters.TERRAIN + | PhysicsCollisionFilters.DYNAMIC_BODY; + int mask = update.tier() == CollisionLodTier.NEAR_FULL + ? fullDynamicMask + : terrainOnlyMask; + return BodyCommandComponent.setCollisionFilter(PhysicsCollisionFilters.DYNAMIC_BODY, + mask, + update.tier() != CollisionLodTier.FAR_SLEEPING); + } + + private static void appendBodyCommand(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull BodyCommandComponent command) { + BodyCommandComponent existing = store.getComponent(bodyRef, + BodyCommandComponent.getComponentType()); + BodyCommandComponent merged = existing != null ? existing.append(command) : command; + store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); + } + private static void applyUpdates(@Nonnull PhysicsWorldRuntimeResource resource, @Nonnull List updates) { for (CollisionLodUpdate update : updates) { @@ -237,14 +335,17 @@ private static void applyUpdates(@Nonnull PhysicsWorldRuntimeResource resource, if (snapshot == null || !snapshot.isDynamic() || snapshot.sensor()) { continue; } - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(update.spaceId()); applyTier(space, registration.backendBodyHandle().value(), update.tier(), - settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled()); + update.farSleepEnabled()); } } + private static boolean isAuthoritativePhysicsStoreActive() { + return PhysicsStoreEarlyPluginProbe.isAvailable(); + } + private static void applyTier(@Nonnull PhysicsSpaceBinding space, long backendBodyId, @Nonnull CollisionLodTier tier, @@ -295,6 +396,7 @@ enum CollisionLodTier { record CollisionLodUpdate(@Nonnull SpaceId spaceId, @Nonnull RigidBodyKey bodyKey, @Nonnull CollisionLodTier tier, + boolean farSleepEnabled, boolean trackTier) { } @@ -366,12 +468,13 @@ CollisionLodTier tier(@Nonnull RigidBodyKey bodyKey) { void recordTier(@Nonnull SpaceId spaceId, @Nonnull RigidBodyKey bodyKey, @Nonnull CollisionLodTier tier, + boolean farSleepEnabled, @Nonnull List updates) { BodyTier previous = tiers.get(bodyKey); if (previous != null && previous.spaceId().equals(spaceId) && previous.tier() == tier) { return; } - updates.add(new CollisionLodUpdate(spaceId, bodyKey, tier, true)); + updates.add(new CollisionLodUpdate(spaceId, bodyKey, tier, farSleepEnabled, true)); } void recordRestore(@Nonnull SpaceId spaceId, @@ -384,6 +487,7 @@ void recordRestore(@Nonnull SpaceId spaceId, updates.add(new CollisionLodUpdate(spaceId, bodyKey, CollisionLodTier.NEAR_FULL, + false, false)); } @@ -397,6 +501,7 @@ private void collectRestoreUpdates(@Nonnull SpaceId spaceId, updates.add(new CollisionLodUpdate(spaceId, entry.getKey(), CollisionLodTier.NEAR_FULL, + false, false)); } nextRefreshTicks.remove(spaceId.value()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java index 8b31317c..9dfa235f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java @@ -20,6 +20,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import java.util.Set; import java.util.UUID; @@ -95,6 +96,7 @@ private static void applyCommand(@Nonnull Store store, case FORCE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.FORCE); case TORQUE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.TORQUE); case SET_TYPE -> applyBodyType(store, runtime, restore, ref, bodyUuid, command); + case SET_COLLISION_FILTER -> applyCollisionFilter(runtime, restore, store, ref, bodyUuid, command); } } @@ -129,6 +131,33 @@ private static void applyBodyType(@Nonnull Store store, } } + private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull UUID bodyUuid, + @Nonnull BodyCommandComponent.Entry command) { + store.putComponent(ref, + CollisionFilterComponent.getComponentType(), + new CollisionFilterComponent(command.getCollisionGroup(), command.getCollisionMask())); + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, bodyUuid, restore, false); + if (binding == null) { + if (command.isActivate()) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, null, null)); + } + return; + } + binding.backendRuntime().setBodyCollisionFilter(binding.spaceHandle().value(), + binding.bodyHandle().value(), + command.getCollisionGroup(), + command.getCollisionMask()); + if (command.isActivate()) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + binding.spaceHandle(), + binding.bodyHandle())); + } + } + private static void enqueueVector(@Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID bodyUuid, @Nonnull BodyCommandComponent.Entry command, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java index 692d194a..763ef453 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java @@ -9,6 +9,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Arrays; import java.util.Objects; @@ -59,6 +60,15 @@ public static BodyCommandComponent setType(@Nonnull PhysicsBodyType bodyType, return new BodyCommandComponent(new Entry[] {Entry.setType(bodyType, activate)}); } + @Nonnull + public static BodyCommandComponent setCollisionFilter(int collisionGroup, + int collisionMask, + boolean activate) { + return new BodyCommandComponent(new Entry[] { + Entry.setCollisionFilter(collisionGroup, collisionMask, activate) + }); + } + @Nonnull public static BodyCommandComponent vector(@Nonnull Kind kind, float x, @@ -163,6 +173,18 @@ public static final class Entry { (entry, value) -> entry.offsetZ = value != null ? value : 0.0f, Entry::getOffsetZ) .add() + .append(new KeyedCodec<>("CollisionGroup", Codec.INTEGER, false), + (entry, value) -> entry.collisionGroup = value != null + ? value + : PhysicsCollisionFilters.DYNAMIC_BODY, + Entry::getCollisionGroup) + .add() + .append(new KeyedCodec<>("CollisionMask", Codec.INTEGER, false), + (entry, value) -> entry.collisionMask = value != null + ? value + : PhysicsCollisionFilters.ALL, + Entry::getCollisionMask) + .add() .build(); @Nonnull @@ -177,6 +199,8 @@ public static final class Entry { private float offsetX; private float offsetY; private float offsetZ; + private int collisionGroup = PhysicsCollisionFilters.DYNAMIC_BODY; + private int collisionMask = PhysicsCollisionFilters.ALL; public Entry() { } @@ -190,7 +214,9 @@ private Entry(@Nonnull Kind kind, boolean hasOffset, float offsetX, float offsetY, - float offsetZ) { + float offsetZ, + int collisionGroup, + int collisionMask) { this.kind = Objects.requireNonNull(kind, "kind"); this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); this.activate = activate; @@ -201,6 +227,8 @@ private Entry(@Nonnull Kind kind, this.offsetX = offsetX; this.offsetY = offsetY; this.offsetZ = offsetZ; + this.collisionGroup = collisionGroup; + this.collisionMask = collisionMask; } @Nonnull @@ -214,7 +242,9 @@ private static Entry wake() { false, 0.0f, 0.0f, - 0.0f); + 0.0f, + PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.ALL); } @Nonnull @@ -228,7 +258,9 @@ private static Entry sleep() { false, 0.0f, 0.0f, - 0.0f); + 0.0f, + PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.ALL); } @Nonnull @@ -243,7 +275,27 @@ private static Entry setType(@Nonnull PhysicsBodyType bodyType, false, 0.0f, 0.0f, - 0.0f); + 0.0f, + PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.ALL); + } + + @Nonnull + private static Entry setCollisionFilter(int collisionGroup, + int collisionMask, + boolean activate) { + return new Entry(Kind.SET_COLLISION_FILTER, + PhysicsBodyType.DYNAMIC, + activate, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f, + collisionGroup, + collisionMask); } @Nonnull @@ -267,7 +319,9 @@ private static Entry vector(@Nonnull Kind kind, hasOffset, offsetX, offsetY, - offsetZ); + offsetZ, + PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.ALL); } @Nonnull @@ -312,6 +366,14 @@ public float getOffsetZ() { return offsetZ; } + public int getCollisionGroup() { + return collisionGroup; + } + + public int getCollisionMask() { + return collisionMask; + } + @Nonnull @Override public Entry clone() { @@ -324,7 +386,9 @@ public Entry clone() { hasOffset, offsetX, offsetY, - offsetZ); + offsetZ, + collisionGroup, + collisionMask); } } @@ -335,7 +399,8 @@ public enum Kind { TORQUE_IMPULSE, FORCE, TORQUE, - SET_TYPE; + SET_TYPE, + SET_COLLISION_FILTER; public boolean isVector() { return this == IMPULSE || this == TORQUE_IMPULSE || this == FORCE || this == TORQUE; From 5491089de5b0730798484065f7ae483d0d4b81bc Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:09:11 +0200 Subject: [PATCH 100/534] refactor(physicsstore): route chunk-boundary mutations through store Signed-off-by: Blovien --- .../PhysicsStoreControlSessionMutations.java | 21 +- .../systems/PhysicsChunkBoundarySystem.java | 185 ++++++++++++++++-- .../systems/BodyCommandApplicationSystem.java | 24 +++ .../components/BodyCommandComponent.java | 85 ++++++++ 4 files changed, 286 insertions(+), 29 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index e9cba350..09b6f1ab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -15,11 +15,9 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.joml.Quaternionf; import org.joml.Vector3f; /** @@ -27,7 +25,6 @@ */ public final class PhysicsStoreControlSessionMutations { - private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); private static final Vector3f ZERO = new Vector3f(); private PhysicsStoreControlSessionMutations() { @@ -73,21 +70,9 @@ private static void restoreControlledBody(@Nonnull Store store, return; } appendBodyCommand(store, bodyRef, BodyCommandComponent.setType(originalBodyType, true)); - store.putComponent(bodyRef, TargetComponent.getComponentType(), releaseTarget(releaseVelocity)); - } - - @Nonnull - private static TargetComponent releaseTarget(@Nonnull Vector3f releaseVelocity) { - TargetComponent target = new TargetComponent(); - target.setActive(true); - target.setPosition(ZERO); - target.setRotation(IDENTITY_ROTATION); - target.setLinearVelocity(releaseVelocity); - target.setAngularVelocity(ZERO); - target.setTransformEnabled(false); - target.setVelocityEnabled(true); - target.setActivate(true); - return target; + appendBodyCommand(store, + bodyRef, + BodyCommandComponent.setVelocity(releaseVelocity, ZERO, true)); } private static void appendBodyCommand(@Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java index 16add875..ce17f0e9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java @@ -1,11 +1,13 @@ package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.dependency.SystemGroupDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.math.util.MathUtil; import com.hypixel.hytale.server.core.modules.entity.system.UpdateLocationSystems; @@ -14,28 +16,41 @@ import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundarySafeState; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; +import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.logging.Level; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Quaternionf; import org.joml.Vector2d; +import org.joml.Vector3f; /** * Keeps registered dynamic physics bodies from drifting into unloaded chunks. @@ -46,6 +61,7 @@ */ public class PhysicsChunkBoundarySystem extends TickingSystem { + private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), new SystemDependency<>(Order.AFTER, PhysicsSnapshotPublicationSystem.class), @@ -79,7 +95,8 @@ private void processBody(@Nonnull PhysicsBodyRegistrationView registration, @Nonnull Store store, @Nonnull ChunkStore chunkStore, @Nonnull Store chunkComponentStore) { - if (resource.getSpaceBinding(registration.spaceId()) == null) { + boolean authoritative = isAuthoritativePhysicsStoreActive(); + if (!authoritative && resource.getSpaceBinding(registration.spaceId()) == null) { return; } @@ -92,7 +109,9 @@ private void processBody(@Nonnull PhysicsBodyRegistrationView registration, return; } - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(registration.spaceId()); + PhysicsSpaceSettings settings = authoritative + ? resource.getSpaceSettings(registration.spaceId()) + : resource.getLiveSpaceSettings(registration.spaceId()); EntityChunkBoundaryMode mode = settings.getWorldCollisionSettings().getEntityChunkBoundaryMode(); PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState = resource.getChunkBoundaryPauseState(bodyKey); @@ -104,7 +123,8 @@ private void processBody(@Nonnull PhysicsBodyRegistrationView registration, resource, store, chunkStore, - chunkComponentStore); + chunkComponentStore, + authoritative); return; } @@ -119,8 +139,12 @@ private void processBody(@Nonnull PhysicsBodyRegistrationView registration, return; } - PhysicsOwnerBridge.run(store, "pause chunk-boundary physics body", - () -> pauseBody(bodyKey, snapshot, targetChunkIndices, resource)); + if (authoritative) { + pauseBodyAuthoritative(store, bodyKey, snapshot, targetChunkIndices, resource); + } else { + PhysicsOwnerBridge.run(store, "pause chunk-boundary physics body", + () -> pauseBody(bodyKey, snapshot, targetChunkIndices, resource)); + } } private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, @@ -130,7 +154,8 @@ private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Store entityStore, @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { + @Nonnull Store chunkComponentStore, + boolean authoritative) { long[] targetChunkIndices = pauseState.getTargetChunkIndices(); if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { requestTickingChunks(chunkStore, targetChunkIndices); @@ -140,6 +165,11 @@ private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, return; } + if (authoritative) { + resumeBodyAuthoritative(entityStore, bodyKey, snapshot, pauseState, resource); + return; + } + PhysicsOwnerBridge.run(entityStore, "resume chunk-boundary physics body", () -> { var registration = resource.getRegistration(bodyKey); if (registration == null) { @@ -168,6 +198,139 @@ private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, }); } + private static void pauseBodyAuthoritative(@Nonnull Store entityStore, + @Nonnull RigidBodyKey bodyKey, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull long[] targetChunkIndices, + @Nonnull PhysicsWorldRuntimeResource resource) { + ChunkBoundarySafeState safeState = resource.getChunkBoundarySafeState(bodyKey); + Vector3f safePosition = safeState != null ? new Vector3f(safeState.getPosition()) : null; + Quaternionf safeRotation = safeState != null + ? new Quaternionf(safeState.getRotation()) + : null; + resource.pauseChunkBoundaryBody(bodyKey, + primaryChunkIndex(targetChunkIndices, snapshot), + targetChunkIndices, + snapshot); + scheduleAuthoritativeMutation(entityStore, + "pause chunk-boundary PhysicsStore body", + physics -> applyPauseBody(physics, + bodyKey.value(), + snapshot.bodyType(), + safePosition, + safeRotation)); + } + + private static void resumeBodyAuthoritative(@Nonnull Store entityStore, + @Nonnull RigidBodyKey bodyKey, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState, + @Nonnull PhysicsWorldRuntimeResource resource) { + Vector3f linearVelocity = new Vector3f(pauseState.getLinearVelocity()); + Vector3f angularVelocity = new Vector3f(pauseState.getAngularVelocity()); + PhysicsBodyType originalBodyType = pauseState.getOriginalBodyType(); + scheduleAuthoritativeMutation(entityStore, + "resume chunk-boundary PhysicsStore body", + physics -> applyResumeBody(physics, + bodyKey.value(), + originalBodyType, + linearVelocity, + angularVelocity)); + resource.clearChunkBoundaryPauseState(bodyKey); + recordSafePose(bodyKey, snapshot, resource); + } + + private static void applyPauseBody(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyType originalBodyType, + @Nullable Vector3f safePosition, + @Nullable Quaternionf safeRotation) { + Ref bodyRef = bodyRef(store, bodyUuid); + if (bodyRef == null) { + return; + } + if (originalBodyType != PhysicsBodyType.KINEMATIC) { + appendBodyCommand(store, + bodyRef, + BodyCommandComponent.setType(PhysicsBodyType.KINEMATIC, false)); + } + store.putComponent(bodyRef, + TargetComponent.getComponentType(), + parkedTarget(safePosition, safeRotation)); + } + + private static void applyResumeBody(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyType originalBodyType, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity) { + Ref bodyRef = bodyRef(store, bodyUuid); + if (bodyRef == null) { + return; + } + appendBodyCommand(store, bodyRef, BodyCommandComponent.setType(originalBodyType, true)); + appendBodyCommand(store, + bodyRef, + BodyCommandComponent.setVelocity(linearVelocity, angularVelocity, true)); + store.removeComponent(bodyRef, TargetComponent.getComponentType()); + } + + @Nonnull + private static TargetComponent parkedTarget(@Nullable Vector3f safePosition, + @Nullable Quaternionf safeRotation) { + TargetComponent target = new TargetComponent(); + target.setActive(true); + target.setTransformEnabled(safePosition != null && safeRotation != null); + if (safePosition != null && safeRotation != null) { + target.setPosition(safePosition); + target.setRotation(safeRotation); + } + target.setLinearVelocity(new Vector3f()); + target.setAngularVelocity(new Vector3f()); + target.setVelocityEnabled(true); + target.setActivate(false); + return target; + } + + private static void appendBodyCommand(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull BodyCommandComponent command) { + BodyCommandComponent existing = store.getComponent(bodyRef, + BodyCommandComponent.getComponentType()); + BodyCommandComponent merged = existing != null ? existing.append(command) : command; + store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); + } + + @Nullable + private static Ref bodyRef(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + Ref bodyRef = identity.getByUuid(bodyUuid); + if (bodyRef == null || !bodyRef.isValid()) { + return null; + } + BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); + return body != null && body.getKind() == PhysicsBodyKind.BODY ? bodyRef : null; + } + + private static void scheduleAuthoritativeMutation(@Nonnull Store entityStore, + @Nonnull String operation, + @Nonnull Consumer> mutation) { + World world = entityStore.getExternalData().getWorld(); + PhysicsStoreThreading.executeOnWorldThread(world, operation, mutation) + .whenComplete((ignored, failure) -> { + if (failure != null) { + LOGGER.at(Level.WARNING).log("PhysicsStore chunk-boundary mutation failed " + + "(%s): %s", operation, failure.getMessage()); + } + }); + } + + private static boolean isAuthoritativePhysicsStoreActive() { + return PhysicsStoreEarlyPluginProbe.isAvailable(); + } + static void pauseBody(@Nonnull RigidBodyKey bodyKey, @Nonnull PhysicsBodySnapshot snapshot, long targetChunkIndex, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java index 9dfa235f..ec8c4c34 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java @@ -96,6 +96,7 @@ private static void applyCommand(@Nonnull Store store, case FORCE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.FORCE); case TORQUE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.TORQUE); case SET_TYPE -> applyBodyType(store, runtime, restore, ref, bodyUuid, command); + case SET_VELOCITY -> applyVelocity(runtime, restore, bodyUuid, command); case SET_COLLISION_FILTER -> applyCollisionFilter(runtime, restore, store, ref, bodyUuid, command); } } @@ -158,6 +159,29 @@ private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime } } + private static void applyVelocity(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull UUID bodyUuid, + @Nonnull BodyCommandComponent.Entry command) { + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, bodyUuid, restore, true); + if (binding == null) { + return; + } + binding.backendRuntime().setBodyVelocity(binding.spaceHandle().value(), + binding.bodyHandle().value(), + command.getX(), + command.getY(), + command.getZ(), + command.getAngularX(), + command.getAngularY(), + command.getAngularZ()); + if (command.isActivate()) { + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + binding.spaceHandle(), + binding.bodyHandle())); + } + } + private static void enqueueVector(@Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID bodyUuid, @Nonnull BodyCommandComponent.Entry command, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java index 763ef453..25f29dee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Objects; import javax.annotation.Nonnull; +import org.joml.Vector3f; /** * One-tick ordered body commands drained by PhysicsStore systems. @@ -69,6 +70,15 @@ public static BodyCommandComponent setCollisionFilter(int collisionGroup, }); } + @Nonnull + public static BodyCommandComponent setVelocity(@Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + boolean activate) { + return new BodyCommandComponent(new Entry[] { + Entry.setVelocity(linearVelocity, angularVelocity, activate) + }); + } + @Nonnull public static BodyCommandComponent vector(@Nonnull Kind kind, float x, @@ -173,6 +183,18 @@ public static final class Entry { (entry, value) -> entry.offsetZ = value != null ? value : 0.0f, Entry::getOffsetZ) .add() + .append(new KeyedCodec<>("AngularX", Codec.FLOAT, false), + (entry, value) -> entry.angularX = value != null ? value : 0.0f, + Entry::getAngularX) + .add() + .append(new KeyedCodec<>("AngularY", Codec.FLOAT, false), + (entry, value) -> entry.angularY = value != null ? value : 0.0f, + Entry::getAngularY) + .add() + .append(new KeyedCodec<>("AngularZ", Codec.FLOAT, false), + (entry, value) -> entry.angularZ = value != null ? value : 0.0f, + Entry::getAngularZ) + .add() .append(new KeyedCodec<>("CollisionGroup", Codec.INTEGER, false), (entry, value) -> entry.collisionGroup = value != null ? value @@ -199,6 +221,9 @@ public static final class Entry { private float offsetX; private float offsetY; private float offsetZ; + private float angularX; + private float angularY; + private float angularZ; private int collisionGroup = PhysicsCollisionFilters.DYNAMIC_BODY; private int collisionMask = PhysicsCollisionFilters.ALL; @@ -215,6 +240,9 @@ private Entry(@Nonnull Kind kind, float offsetX, float offsetY, float offsetZ, + float angularX, + float angularY, + float angularZ, int collisionGroup, int collisionMask) { this.kind = Objects.requireNonNull(kind, "kind"); @@ -227,6 +255,9 @@ private Entry(@Nonnull Kind kind, this.offsetX = offsetX; this.offsetY = offsetY; this.offsetZ = offsetZ; + this.angularX = angularX; + this.angularY = angularY; + this.angularZ = angularZ; this.collisionGroup = collisionGroup; this.collisionMask = collisionMask; } @@ -243,6 +274,9 @@ private static Entry wake() { 0.0f, 0.0f, 0.0f, + 0.0f, + 0.0f, + 0.0f, PhysicsCollisionFilters.DYNAMIC_BODY, PhysicsCollisionFilters.ALL); } @@ -259,6 +293,9 @@ private static Entry sleep() { 0.0f, 0.0f, 0.0f, + 0.0f, + 0.0f, + 0.0f, PhysicsCollisionFilters.DYNAMIC_BODY, PhysicsCollisionFilters.ALL); } @@ -276,6 +313,9 @@ private static Entry setType(@Nonnull PhysicsBodyType bodyType, 0.0f, 0.0f, 0.0f, + 0.0f, + 0.0f, + 0.0f, PhysicsCollisionFilters.DYNAMIC_BODY, PhysicsCollisionFilters.ALL); } @@ -294,10 +334,36 @@ private static Entry setCollisionFilter(int collisionGroup, 0.0f, 0.0f, 0.0f, + 0.0f, + 0.0f, + 0.0f, collisionGroup, collisionMask); } + @Nonnull + private static Entry setVelocity(@Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + boolean activate) { + Objects.requireNonNull(linearVelocity, "linearVelocity"); + Objects.requireNonNull(angularVelocity, "angularVelocity"); + return new Entry(Kind.SET_VELOCITY, + PhysicsBodyType.DYNAMIC, + activate, + linearVelocity.x, + linearVelocity.y, + linearVelocity.z, + false, + 0.0f, + 0.0f, + 0.0f, + angularVelocity.x, + angularVelocity.y, + angularVelocity.z, + PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.ALL); + } + @Nonnull private static Entry vector(@Nonnull Kind kind, float x, @@ -320,6 +386,9 @@ private static Entry vector(@Nonnull Kind kind, offsetX, offsetY, offsetZ, + 0.0f, + 0.0f, + 0.0f, PhysicsCollisionFilters.DYNAMIC_BODY, PhysicsCollisionFilters.ALL); } @@ -366,6 +435,18 @@ public float getOffsetZ() { return offsetZ; } + public float getAngularX() { + return angularX; + } + + public float getAngularY() { + return angularY; + } + + public float getAngularZ() { + return angularZ; + } + public int getCollisionGroup() { return collisionGroup; } @@ -387,6 +468,9 @@ public Entry clone() { offsetX, offsetY, offsetZ, + angularX, + angularY, + angularZ, collisionGroup, collisionMask); } @@ -400,6 +484,7 @@ public enum Kind { FORCE, TORQUE, SET_TYPE, + SET_VELOCITY, SET_COLLISION_FILTER; public boolean isVector() { From 7bd72bf122184d16ea151f7758df4cfe13da3b91 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:13:51 +0200 Subject: [PATCH 101/534] feat(physicsstore): add async uuid query helpers Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreDiagnostics.java | 90 +++++++++++++++++++ .../physicsstore/PhysicsStoreRaycasts.java | 78 ++++++++++++++++ 2 files changed, 168 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index 253b433d..3b46f9ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -57,6 +57,24 @@ public static CompletionStage bodyCountAsync(@Nonnull Store bodyCount(physics, spaceId)); } + @Nonnull + public static CompletionStage bodyCountAsync(@Nonnull World world, + @Nonnull UUID spaceUuid) { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore body count read", + physics -> bodyCount(physics, spaceUuid)); + } + + @Nonnull + public static CompletionStage bodyCountAsync(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore body count read", + physics -> bodyCount(physics, spaceUuid)); + } + public static int runtimeJointCount(@Nonnull Store store) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); @@ -134,6 +152,26 @@ public static CompletionStage solverCapabilityAsync( physics -> solverCapability(physics, spaceId)); } + @Nonnull + public static CompletionStage solverCapabilityAsync( + @Nonnull World world, + @Nonnull UUID spaceUuid) { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore solver capability read", + physics -> solverCapability(physics, spaceUuid)); + } + + @Nonnull + public static CompletionStage solverCapabilityAsync( + @Nonnull Store store, + @Nonnull UUID spaceUuid) { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore solver capability read", + physics -> solverCapability(physics, spaceUuid)); + } + @Nonnull public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull UUID spaceUuid) { @@ -200,6 +238,58 @@ public static List spaceSummaries(@Nonnull Store sto : List.of(); } + @Nonnull + public static CompletionStage> spaceSummariesAsync(@Nonnull World world, + @Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore space summary read", + physics -> spaceSummaries(physics, spaceId)); + } + + @Nonnull + public static CompletionStage> spaceSummariesAsync( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore space summary read", + physics -> spaceSummaries(physics, spaceId)); + } + + @Nonnull + public static List spaceSummaries(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(runtime, Objects.requireNonNull(spaceUuid, "spaceUuid")); + return space != null && compatibility.getSpaceId(space.spaceUuid()) != null + ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) + : List.of(); + } + + @Nonnull + public static CompletionStage> spaceSummariesAsync(@Nonnull World world, + @Nonnull UUID spaceUuid) { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore space summary read", + physics -> spaceSummaries(physics, spaceUuid)); + } + + @Nonnull + public static CompletionStage> spaceSummariesAsync( + @Nonnull Store store, + @Nonnull UUID spaceUuid) { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore space summary read", + physics -> spaceSummaries(physics, spaceUuid)); + } + @Nonnull public static List unsupportedCcdSpaces(@Nonnull Store store) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java index 2ef8c7ca..d56d9aff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java @@ -114,6 +114,33 @@ public static CompletionStage> closestAsync( physics -> closest(physics, spaceId, copiedFrom, copiedTo)); } + @Nonnull + public static CompletionStage> closestAsync(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore closest raycast read", + physics -> closest(physics, spaceUuid, copiedFrom, copiedTo)); + } + + @Nonnull + public static CompletionStage> closestAsync( + @Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore closest raycast read", + physics -> closest(physics, spaceUuid, copiedFrom, copiedTo)); + } + @Nonnull public static CompletionStage> allAsync(@Nonnull World world, @Nonnull SpaceId spaceId, @@ -138,6 +165,33 @@ public static CompletionStage> allAsync(@Nonnull Store all(physics, spaceId, copiedFrom, copiedTo)); } + @Nonnull + public static CompletionStage> allAsync(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore all raycast read", + physics -> all(physics, spaceUuid, copiedFrom, copiedTo)); + } + + @Nonnull + public static CompletionStage> allAsync( + @Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore all raycast read", + physics -> all(physics, spaceUuid, copiedFrom, copiedTo)); + } + @Nonnull public static CompletionStage closestBatchAsync( @Nonnull World world, @@ -160,6 +214,30 @@ public static CompletionStage closestBatchAsync( physics -> closestBatch(physics, spaceId, copied)); } + @Nonnull + public static CompletionStage closestBatchAsync( + @Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull List rays) { + List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore batch raycast read", + physics -> closestBatch(physics, spaceUuid, copied)); + } + + @Nonnull + public static CompletionStage closestBatchAsync( + @Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull List rays) { + List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); + Objects.requireNonNull(spaceUuid, "spaceUuid"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore batch raycast read", + physics -> closestBatch(physics, spaceUuid, copied)); + } + @Nonnull private static Optional closest(@Nonnull Store store, @Nonnull PhysicsStoreBackendAccess.SpaceContext space, From dc0d67ea78398269b7cd0980d1e094a8d9282db6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:19:35 +0200 Subject: [PATCH 102/534] refactor(physicsstore): remove legacy command query facade Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 105 ------------------ .../resources/PhysicsWorldResource.java | 42 +------ 2 files changed, 3 insertions(+), 144 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 498db6ff..cba7298e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -43,9 +43,6 @@ import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerMutation; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.internal.simulation.recorder.MutablePhysicsCommandContext; -import dev.hytalemodding.impulse.core.internal.simulation.PhysicsSimulationExecutor; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsWorldCollisionRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; @@ -85,11 +82,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandCompletion; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandRecipe; -import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQueryHandle; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.util.ArrayList; @@ -97,7 +89,6 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -143,7 +134,6 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { private final AtomicLong visualInterestTick = new AtomicLong(); private final PhysicsOwnerGateway ownerGateway = new PhysicsOwnerGateway(); - private final PhysicsSimulationExecutor simulationExecutor = new PhysicsSimulationExecutor(this); @Nullable private Store owningStore; @@ -208,80 +198,6 @@ public long commandWorldEpoch() { return lifecycleState.commandWorldEpoch(); } - @Nonnull - public MutablePhysicsCommandContext createMutableCommandContext(long submittedServerTick) { - return new MutablePhysicsCommandContext(submittedServerTick, - lifecycleState.commandWorldEpoch()); - } - - @Nonnull - public MutablePhysicsCommandContext createMutableCommandContext(long submittedServerTick, - int expectedOperations) { - return new MutablePhysicsCommandContext(submittedServerTick, - lifecycleState.commandWorldEpoch(), - expectedOperations); - } - - @Nonnull - @Override - public PhysicsCommandHandle submitCommands(long submittedServerTick, - @Nonnull PhysicsCommandRecipe recipe) { - MutablePhysicsCommandContext context = createMutableCommandContext(submittedServerTick); - context.compose(recipe); - return submitRecordedCommands(context); - } - - @Nonnull - @Override - public PhysicsCommandHandle submitCommands(long submittedServerTick, - int expectedOperations, - @Nonnull PhysicsCommandRecipe recipe) { - MutablePhysicsCommandContext context = - createMutableCommandContext(submittedServerTick, expectedOperations); - context.compose(recipe); - return submitRecordedCommands(context); - } - - @Nonnull - public PhysicsCommandHandle submitRecordedCommands(@Nonnull MutablePhysicsCommandContext context) { - requireLegacyMutationAllowed("submit recorded physics commands"); - Objects.requireNonNull(context, "context"); - RecordedPhysicsCommandBatch batch = - context.freezeInternal(lifecycleState.nextCommandBatchSequence()); - /* - * Body creation through commands publishes registration views with the next snapshot frame. - * Until that frame is applied, sync/materialization must treat creation as pending even - * though the owner may already have completed the command batch. - */ - boolean trackBodyCreationPublication = trackCommandBodyCreationPublication(batch); - CompletableFuture completion = - ownerGateway.enqueueCall("execute physics command batch", () -> executeCommandBatch(batch)); - if (trackBodyCreationPublication) { - completion.whenComplete((ignored, failure) -> { - if (failure != null) { - clearCommandBodyCreationPublication(batch); - } - }); - } - return PhysicsCommandHandle.fromCompletionSummary(batch.publicBatch(), completion); - } - - @Nonnull - @Override - public PhysicsQueryHandle query(@Nonnull PhysicsQuery query) { - Objects.requireNonNull(query, "query"); - if (isAuthoritativePhysicsStoreActive()) { - return PhysicsQueryHandle.failed(query, - new IllegalStateException("PhysicsWorldResource.query is a legacy runtime API while " - + "authoritative PhysicsStore is active. Use PhysicsSnapshotResource for copied " - + "body state, PhysicsStoreDiagnostics for owner-lane diagnostics, or " - + "PhysicsStoreRaycasts for raycasts.")); - } - CompletableFuture completion = - ownerGateway.enqueueCall("execute physics query", () -> simulationExecutor.query(query)); - return PhysicsQueryHandle.fromCompletion(query, completion); - } - @Nonnull @Override public PhysicsEventFrame getLatestEventFrame() { @@ -2113,31 +2029,10 @@ private void clearRuntimeTopologyDirect(boolean clearCollision) { } } - private void markBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - bodyRuntime.markBodyCreationPending(bodyKey); - } - - private void clearBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - bodyRuntime.clearBodyCreationPending(bodyKey); - } - - private boolean trackCommandBodyCreationPublication(@Nonnull RecordedPhysicsCommandBatch batch) { - return lifecycleState.trackBodyCreationPublication(batch, ownerGateway.hasOwnerExecutor()); - } - - private void clearCommandBodyCreationPublication(@Nonnull RecordedPhysicsCommandBatch batch) { - lifecycleState.clearBodyCreationPublication(batch); - } - private void markWorldChanged() { lifecycleState.markWorldChanged(bodyRegistry, ownerGateway.hasOwnerExecutor()); } - @Nonnull - private PhysicsCommandCompletion executeCommandBatch(@Nonnull RecordedPhysicsCommandBatch batch) { - return lifecycleState.executeCommandBatch(batch, simulationExecutor::execute); - } - @Nonnull @Override public PhysicsWorldResource clone() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index b97ffa4f..9d837819 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -19,10 +19,6 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandRecipe; -import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQueryHandle; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import java.util.Collection; import java.util.function.Consumer; @@ -35,7 +31,7 @@ * Public alpha facade for a world's physics runtime resource. * *

The concrete Impulse runtime lives in the internal package. Plugin-facing code should depend on - * this facade for explicit space lifecycle, world settings, command submission, body lifetime by key, + * this facade for explicit space lifecycle, world settings, body lifetime by key, * immutable snapshots, read-only registration views, public attachment/control hooks, and world * collision operations.

* @@ -43,46 +39,14 @@ * target for each operation.

* *

This facade does not directly return live backend spaces or bodies. Gameplay code should use - * physics simulation commands, copied queries, and published snapshots. Advanced diagnostics should - * be modeled as backend-neutral commands or copied queries instead of retaining live backend - * handles.

+ * PhysicsStore rows for authoring, copied snapshots for body state, and explicit PhysicsStore + * diagnostics/raycast helpers for owner-lane backend reads.

*/ public abstract class PhysicsWorldResource implements Resource { protected PhysicsWorldResource() { } - /** - * Records copied simulation intent through the fluent command DSL and submits it. - * - *

The returned handle completes when the physics owner executes the batch. Snapshot - * publication and ECS visual synchronization are separate phases and can lag behind command - * completion.

- */ - @Nonnull - public abstract PhysicsCommandHandle submitCommands(long submittedServerTick, - @Nonnull PhysicsCommandRecipe recipe); - - /** - * Records copied simulation intent through the fluent command DSL with a capacity hint. - * - *

{@code expectedOperations} sizes the internal recorder arrays. It is a performance hint, - * not a correctness requirement.

- */ - @Nonnull - public abstract PhysicsCommandHandle submitCommands(long submittedServerTick, - int expectedOperations, - @Nonnull PhysicsCommandRecipe recipe); - - /** - * Runs a copied owner-lane physics query without exposing live backend handles. - * - *

Queries should return immutable or defensively copied values. Use commands for mutations - * so all writes stay ordered through the physics owner.

- */ - @Nonnull - public abstract PhysicsQueryHandle query(@Nonnull PhysicsQuery query); - /** * Returns the latest value-only physics owner event frame. * From 36d2f767c8c28907d183eb3c2660f8ff59723f74 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:24:43 +0200 Subject: [PATCH 103/534] refactor(physicsstore): remove legacy query dtos Signed-off-by: Blovien --- .../simulation/PhysicsSimulationExecutor.java | 303 ------------------ .../simulation/RaycastClosestBatchResult.java | 5 +- .../simulation/query/CcdSupportQuery.java | 7 - .../plugin/simulation/query/PhysicsQuery.java | 18 -- .../simulation/query/PhysicsQueryHandle.java | 58 ---- .../simulation/query/RaycastAllQuery.java | 38 --- .../query/RaycastClosestBatchQuery.java | 39 --- .../simulation/query/RaycastClosestQuery.java | 38 --- .../simulation/query/RigidBodyStateQuery.java | 19 -- .../query/RuntimeJointCountQuery.java | 7 - .../query/SolverCapabilityQuery.java | 18 -- .../simulation/query/SpaceBodyCountQuery.java | 16 - .../simulation/query/SpaceSummaryQuery.java | 15 - .../query/UnsupportedCcdSpacesQuery.java | 11 - 14 files changed, 2 insertions(+), 590 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/CcdSupportQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQueryHandle.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastAllQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestBatchQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RigidBodyStateQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RuntimeJointCountQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SolverCapabilityQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceBodyCountQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceSummaryQuery.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/UnsupportedCcdSpacesQuery.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java index e4524469..b94b4e1b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java @@ -1,17 +1,13 @@ package dev.hytalemodding.impulse.core.internal.simulation; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnBatch; @@ -20,36 +16,16 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.query.CcdSupportQuery; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandCompletion; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; -import dev.hytalemodding.impulse.core.plugin.simulation.query.PhysicsQuery; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastAllQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RuntimeJointCountQuery; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SolverCapabilityQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceSummaryQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.query.UnsupportedCcdSpacesQuery; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.joml.Vector3f; /** * Owner-lane translator from public simulation commands to live backend calls. @@ -325,26 +301,6 @@ private void dispatchJoint(int index, operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_MOTOR_MAX_FORCE_FLOAT_SLOT)); } - @Nonnull - public R query(@Nonnull PhysicsQuery query) { - Objects.requireNonNull(query, "query"); - Object result = switch (query) { - case RaycastClosestQuery raycast -> raycastClosest(raycast); - case RaycastClosestBatchQuery raycasts -> raycastClosestBatch(raycasts); - case RaycastAllQuery raycast -> raycastAll(raycast); - case SpaceBodyCountQuery count -> spaceBodyCount(count); - case SpaceSummaryQuery summary -> spaceSummary(summary); - case CcdSupportQuery ignored -> ccdSupported(); - case UnsupportedCcdSpacesQuery ignored -> unsupportedCcdSpaces(); - case SolverCapabilityQuery solver -> solverCapability(solver); - case RigidBodyStateQuery state -> rigidBodyState(state); - case RuntimeJointCountQuery ignored -> runtimeJointCount(); - }; - @SuppressWarnings("unchecked") - R typed = (R) result; - return typed; - } - @Override public void spawnRigidBody(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @@ -746,255 +702,6 @@ public void destroyJointBetween(@Nullable JointKey preferredJointKey, } } - @Nonnull - private Optional raycastClosest(@Nonnull RaycastClosestQuery query) { - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - RayHitCapture hit = new RayHitCapture(); - Vector3f from = query.from(); - Vector3f to = query.to(); - boolean hitFound = space.runtime().raycastClosest(space.backendSpaceHandle().value(), - from.x, - from.y, - from.z, - to.x, - to.y, - to.z, - hit); - return hitFound && hit.captured ? Optional.of(toView(hit)) : Optional.empty(); - } - - @Nonnull - private RaycastClosestBatchResult raycastClosestBatch(@Nonnull RaycastClosestBatchQuery query) { - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - int rayCount = query.rayCount(); - RaycastHitView[] hits = new RaycastHitView[rayCount]; - Vector3f from = new Vector3f(); - Vector3f to = new Vector3f(); - for (int index = 0; index < rayCount; index++) { - RaycastSegment ray = query.ray(index); - ray.copyFrom(from); - ray.copyTo(to); - int rayIndex = index; - space.runtime().raycastClosest(space.backendSpaceHandle().value(), - from.x, - from.y, - from.z, - to.x, - to.y, - to.z, - (bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance) -> hits[rayIndex] = toView(bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance)); - } - return new RaycastClosestBatchResult(hits); - } - - @Nonnull - private List raycastAll(@Nonnull RaycastAllQuery query) { - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - Vector3f from = query.from(); - Vector3f to = query.to(); - List views = new ArrayList<>(); - space.runtime().raycastAll(space.backendSpaceHandle().value(), - from.x, - from.y, - from.z, - to.x, - to.y, - to.z, - (bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance) -> views.add(toView(bodyId, - pointX, - pointY, - pointZ, - normalX, - normalY, - normalZ, - fraction, - distance))); - if (views.isEmpty()) { - return List.of(); - } - return List.copyOf(views); - } - - private int spaceBodyCount(@Nonnull SpaceBodyCountQuery query) { - PhysicsSpaceBinding space = runtime.getSpaceBinding(query.spaceId()); - return space != null ? space.runtime().bodyCount(space.backendSpaceHandle().value()) : 0; - } - - @Nonnull - private List spaceSummary(@Nonnull SpaceSummaryQuery query) { - List summaries = new ArrayList<>(); - if (query.spaceId() != null) { - PhysicsSpaceBinding space = runtime.getSpaceBinding(query.spaceId()); - if (space != null) { - summaries.add(summary(space)); - } - return List.copyOf(summaries); - } - for (PhysicsSpaceBinding space : runtime.getSpaceBindings()) { - summaries.add(summary(space)); - } - return List.copyOf(summaries); - } - - private boolean ccdSupported() { - for (PhysicsSpaceBinding space : runtime.getSpaceBindings()) { - if (space.runtime().supportsContinuousCollision(space.backendSpaceHandle().value())) { - return true; - } - } - return false; - } - - private int runtimeJointCount() { - int count = 0; - for (PhysicsSpaceBinding space : runtime.getSpaceBindings()) { - count += space.runtime().jointCount(space.backendSpaceHandle().value()); - } - return count; - } - - @Nonnull - private Optional rigidBodyState(@Nonnull RigidBodyStateQuery query) { - PhysicsBodyRegistration registration = runtime.getRegistration(query.bodyKey()); - if (registration == null) { - return Optional.empty(); - } - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, registration.backendBodyHandle().value()); - if (snapshot == null) { - return Optional.empty(); - } - return Optional.of(new RigidBodyStateView(query.bodyKey(), - snapshot.bodyType(), - RigidBodyPose.of(snapshot.position(), snapshot.rotation()))); - } - - @Nonnull - private SolverCapabilitySummary solverCapability(@Nonnull SolverCapabilityQuery query) { - PhysicsSpaceBinding space = requireSpace(query.spaceId()); - return new SolverCapabilitySummary(space.spaceId(), - space.backendId().value(), - space.runtime().supportsSolverTuning(space.backendSpaceHandle().value()), - space.runtime().supportsActivationTuning(space.backendSpaceHandle().value())); - } - - @Nonnull - private List unsupportedCcdSpaces() { - List spaces = new ArrayList<>(); - for (PhysicsSpaceBinding space : runtime.getSpaceBindings()) { - if (space.runtime().supportsContinuousCollision(space.backendSpaceHandle().value())) { - continue; - } - spaces.add(summary(space)); - } - return List.copyOf(spaces); - } - - @Nonnull - private SpaceSummary summary(@Nonnull PhysicsSpaceBinding space) { - return new SpaceSummary(space.spaceId(), - space.backendId(), - space.runtime().bodyCount(space.backendSpaceHandle().value()), - space.runtime().jointCount(space.backendSpaceHandle().value())); - } - - @Nonnull - private RaycastHitView toView(@Nonnull RayHitCapture hit) { - return toView(hit.bodyId, - hit.pointX, - hit.pointY, - hit.pointZ, - hit.normalX, - hit.normalY, - hit.normalZ, - hit.fraction, - hit.distance); - } - - @Nonnull - private RaycastHitView toView(long bodyId, - float pointX, - float pointY, - float pointZ, - float normalX, - float normalY, - float normalZ, - float fraction, - float distance) { - PhysicsBodyRegistration registration = findBodyRegistration(bodyId); - RigidBodyKey bodyKey = registration != null ? registration.bodyKey() : null; - PhysicsBodySnapshot snapshot = registration != null - ? runtime.getBodySnapshot(registration.bodyKey()) - : null; - return new RaycastHitView(bodyKey, - snapshot != null ? snapshot.bodyType() : PhysicsBodyType.STATIC, - new Vector3f(pointX, pointY, pointZ), - new Vector3f(normalX, normalY, normalZ), - snapshot != null ? snapshot.shapeType() : ShapeType.BOX, - fraction, - distance); - } - - private static final class RayHitCapture implements BackendRayHitSink { - - private long bodyId; - private float pointX; - private float pointY; - private float pointZ; - private float normalX; - private float normalY; - private float normalZ; - private float fraction; - private float distance; - private boolean captured; - - @Override - public void accept(long bodyId, - float pointX, - float pointY, - float pointZ, - float normalX, - float normalY, - float normalZ, - float fraction, - float distance) { - this.bodyId = bodyId; - this.pointX = pointX; - this.pointY = pointY; - this.pointZ = pointZ; - this.normalX = normalX; - this.normalY = normalY; - this.normalZ = normalZ; - this.fraction = fraction; - this.distance = distance; - captured = true; - } - } - @Nonnull private PhysicsSpaceBinding requireSpace(@Nonnull SpaceId spaceId) { PhysicsSpaceBinding space = runtime.getSpaceBinding(spaceId); @@ -1013,16 +720,6 @@ private PhysicsBodyRegistration requireBodyRegistration(@Nonnull RigidBodyKey bo return registration; } - @Nullable - private PhysicsBodyRegistration findBodyRegistration(long backendBodyId) { - for (PhysicsBodyRegistration registration : runtime.getBodyRegistrations()) { - if (registration.backendBodyHandle().value() == backendBodyId) { - return registration; - } - } - return null; - } - private static int toRuntimeJointTypeCode(@Nonnull JointType type) { return switch (type) { case FIXED -> BackendRuntimeCodes.JOINT_FIXED; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchResult.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchResult.java index 44f38f8f..fd0a7145 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchResult.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchResult.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.plugin.simulation; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.Arrays; @@ -11,8 +10,8 @@ /** * Compact copied result for a batch of closest-raycast queries. * - *

Each result slot corresponds to the same index in the submitted - * {@link RaycastClosestBatchQuery#rays()} list. A {@code null} slot means that ray had no hit.

+ *

Each result slot corresponds to the same submitted ray index. A {@code null} slot means that + * ray had no hit.

*/ public final class RaycastClosestBatchResult { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/CcdSupportQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/CcdSupportQuery.java deleted file mode 100644 index aec945f6..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/CcdSupportQuery.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -/** - * Owner-lane query for whether every registered space supports continuous collision detection. - */ -public record CcdSupportQuery() implements PhysicsQuery { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQuery.java deleted file mode 100644 index 0940d36b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQuery.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -/** - * Data-only owner-lane read that returns copied physics data. - * - * @param immutable query result type - */ -public sealed interface PhysicsQuery permits RaycastClosestQuery, - RaycastClosestBatchQuery, - RaycastAllQuery, - SpaceBodyCountQuery, - SpaceSummaryQuery, - CcdSupportQuery, - UnsupportedCcdSpacesQuery, - SolverCapabilityQuery, - RigidBodyStateQuery, - RuntimeJointCountQuery { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQueryHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQueryHandle.java deleted file mode 100644 index 8257d817..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/PhysicsQueryHandle.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import javax.annotation.Nonnull; - -/** - * Completion handle for a typed physics owner query. - * - *

Queries run on the physics owner and return copied values. They are intended for - * read-only inspection that needs live backend state without exposing backend handles to plugin - * code.

- */ -public final class PhysicsQueryHandle { - - @Nonnull - private final PhysicsQuery query; - @Nonnull - private final CompletableFuture completion; - - private PhysicsQueryHandle(@Nonnull PhysicsQuery query, - @Nonnull CompletableFuture completion) { - this.query = Objects.requireNonNull(query, "query"); - this.completion = Objects.requireNonNull(completion, "completion"); - } - - @Nonnull - public static PhysicsQueryHandle completed(@Nonnull PhysicsQuery query, - @Nonnull R value) { - return new PhysicsQueryHandle<>(query, CompletableFuture.completedFuture(value)); - } - - @Nonnull - public static PhysicsQueryHandle failed(@Nonnull PhysicsQuery query, - @Nonnull Throwable failure) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(Objects.requireNonNull(failure, "failure")); - return new PhysicsQueryHandle<>(query, completion); - } - - @Nonnull - public static PhysicsQueryHandle fromCompletion(@Nonnull PhysicsQuery query, - @Nonnull CompletionStage completion) { - return new PhysicsQueryHandle<>(query, - Objects.requireNonNull(completion, "completion").toCompletableFuture()); - } - - @Nonnull - public PhysicsQuery query() { - return query; - } - - @Nonnull - public CompletionStage completion() { - return completion.minimalCompletionStage(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastAllQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastAllQuery.java deleted file mode 100644 index c104cb9a..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastAllQuery.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; - -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import org.joml.Vector3f; - -/** - * Owner-lane query for every ray hit in one physics space. - * - *

The endpoints are defensively copied so callers can reuse mutable vector instances after - * submission.

- */ -public record RaycastAllQuery(@Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) implements PhysicsQuery> { - - public RaycastAllQuery { - Objects.requireNonNull(spaceId, "spaceId"); - from = new Vector3f(Objects.requireNonNull(from, "from")); - to = new Vector3f(Objects.requireNonNull(to, "to")); - } - - @Nonnull - @Override - public Vector3f from() { - return new Vector3f(from); - } - - @Nonnull - @Override - public Vector3f to() { - return new Vector3f(to); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestBatchQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestBatchQuery.java deleted file mode 100644 index 4e2a0135..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestBatchQuery.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; - -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Owner-lane query for closest hits across many ray segments in one physics space. - * - *

The ray list is copied on construction. Result indexes match the input ray order.

- */ -public record RaycastClosestBatchQuery(@Nonnull SpaceId spaceId, - @Nonnull List rays) - implements PhysicsQuery { - - public RaycastClosestBatchQuery(@Nonnull SpaceId spaceId, - @Nonnull List rays) { - this.spaceId = Objects.requireNonNull(spaceId, "spaceId"); - this.rays = List.copyOf(Objects.requireNonNull(rays, "rays")); - } - - public int rayCount() { - return rays.size(); - } - - @Nonnull - public RaycastSegment ray(int index) { - return rays.get(index); - } - - @Nonnull - public List rays() { - return List.copyOf(rays); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestQuery.java deleted file mode 100644 index f5415c4b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RaycastClosestQuery.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import java.util.Objects; -import java.util.Optional; -import javax.annotation.Nonnull; - -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import org.joml.Vector3f; - -/** - * Owner-lane query for the nearest ray hit in one physics space. - * - *

The endpoints are defensively copied so callers can reuse mutable vector instances after - * submission.

- */ -public record RaycastClosestQuery(@Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) implements PhysicsQuery> { - - public RaycastClosestQuery { - Objects.requireNonNull(spaceId, "spaceId"); - from = new Vector3f(Objects.requireNonNull(from, "from")); - to = new Vector3f(Objects.requireNonNull(to, "to")); - } - - @Nonnull - @Override - public Vector3f from() { - return new Vector3f(from); - } - - @Nonnull - @Override - public Vector3f to() { - return new Vector3f(to); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RigidBodyStateQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RigidBodyStateQuery.java deleted file mode 100644 index 401b8975..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RigidBodyStateQuery.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; - -import java.util.Objects; -import java.util.Optional; -import javax.annotation.Nonnull; - -/** - * Owner-lane query for one rigid body's current live state copied by stable body key. - */ -public record RigidBodyStateQuery(@Nonnull RigidBodyKey bodyKey) - implements PhysicsQuery> { - - public RigidBodyStateQuery { - Objects.requireNonNull(bodyKey, "bodyKey"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RuntimeJointCountQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RuntimeJointCountQuery.java deleted file mode 100644 index b14a48d6..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/RuntimeJointCountQuery.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -/** - * Owner-lane query for the current live joint count across all physics spaces. - */ -public record RuntimeJointCountQuery() implements PhysicsQuery { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SolverCapabilityQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SolverCapabilityQuery.java deleted file mode 100644 index 11b9a50a..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SolverCapabilityQuery.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Owner-lane query for backend solver-tuning support in one physics space. - */ -public record SolverCapabilityQuery(@Nonnull SpaceId spaceId) - implements PhysicsQuery { - - public SolverCapabilityQuery { - Objects.requireNonNull(spaceId, "spaceId"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceBodyCountQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceBodyCountQuery.java deleted file mode 100644 index 04e78890..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceBodyCountQuery.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Owner-lane query for the current live rigid body count in one physics space. - */ -public record SpaceBodyCountQuery(@Nonnull SpaceId spaceId) implements PhysicsQuery { - - public SpaceBodyCountQuery { - Objects.requireNonNull(spaceId, "spaceId"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceSummaryQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceSummaryQuery.java deleted file mode 100644 index 4f28217f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/SpaceSummaryQuery.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; - -import java.util.List; -import javax.annotation.Nullable; - -/** - * Owner-lane query for copied space diagnostics. - * - *

A {@code null} space id requests summaries for every registered physics space.

- */ -public record SpaceSummaryQuery(@Nullable SpaceId spaceId) implements PhysicsQuery> { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/UnsupportedCcdSpacesQuery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/UnsupportedCcdSpacesQuery.java deleted file mode 100644 index 98239de2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/query/UnsupportedCcdSpacesQuery.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.query; - -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; - -import java.util.List; - -/** - * Owner-lane query for spaces whose backend does not support continuous collision detection. - */ -public record UnsupportedCcdSpacesQuery() implements PhysicsQuery> { -} From 36637c39dcf0de1f0c52ae0d08bdc9af2afa964a Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:31:52 +0200 Subject: [PATCH 104/534] fix(physicsstore): guard queued read result boundaries Signed-off-by: Blovien --- .../PhysicsStoreReadQueueResource.java | 100 +++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java index 4469d324..c2d6826f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -1,15 +1,23 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; @@ -93,7 +101,9 @@ private QueuedRead(@Nonnull Function, R> read, public void complete(@Nonnull Store store) { try { - PhysicsStoreAsyncCompletions.complete(completion, read.apply(store)); + R value = read.apply(store); + CopiedReadBoundary.requireCopied(value); + PhysicsStoreAsyncCompletions.complete(completion, value); } catch (RuntimeException | Error exception) { fail(exception); } @@ -103,4 +113,92 @@ public void fail(@Nonnull Throwable failure) { PhysicsStoreAsyncCompletions.fail(completion, Objects.requireNonNull(failure, "failure")); } } + + private static final class CopiedReadBoundary { + + private static final String LIVE_BACKEND = "dev.hytalemodding.impulse.api.PhysicsBackend"; + private static final String LIVE_SPACE = "dev.hytalemodding.impulse.api.PhysicsSpace"; + private static final String LIVE_BODY = "dev.hytalemodding.impulse.api.PhysicsBody"; + private static final String LIVE_JOINT = "dev.hytalemodding.impulse.api.PhysicsJoint"; + + private CopiedReadBoundary() { + } + + private static void requireCopied(Object value) { + requireCopied(value, new IdentityHashMap<>()); + } + + private static void requireCopied(Object value, IdentityHashMap seen) { + if (value == null + || isClearlyCopiedScalar(value) + || seen.put(value, Boolean.TRUE) != null) { + return; + } + rejectLiveValue(value); + if (value instanceof Optional optional) { + optional.ifPresent(contained -> requireCopied(contained, seen)); + return; + } + if (value instanceof Iterable iterable) { + for (Object contained : iterable) { + requireCopied(contained, seen); + } + return; + } + if (value instanceof Map map) { + for (Map.Entry entry : map.entrySet()) { + requireCopied(entry.getKey(), seen); + requireCopied(entry.getValue(), seen); + } + return; + } + if (value.getClass().isArray() && !value.getClass().componentType().isPrimitive()) { + Object[] values = (Object[]) value; + for (Object contained : values) { + requireCopied(contained, seen); + } + } + } + + private static boolean isClearlyCopiedScalar(Object value) { + return value instanceof String + || value instanceof Number + || value instanceof Boolean + || value instanceof Character + || value instanceof Enum + || value instanceof java.util.UUID; + } + + private static void rejectLiveValue(Object value) { + if (value instanceof Store + || value instanceof Ref + || value instanceof Resource + || value instanceof PhysicsBackendRuntime + || value instanceof CompletionStage + || implementsNamedType(value.getClass(), LIVE_BACKEND) + || implementsNamedType(value.getClass(), LIVE_SPACE) + || implementsNamedType(value.getClass(), LIVE_BODY) + || implementsNamedType(value.getClass(), LIVE_JOINT) + || value instanceof BackendSpaceHandle + || value instanceof BackendBodyHandle + || value instanceof BackendJointHandle) { + throw new IllegalStateException("PhysicsStore queued reads must complete with " + + "copied values, not live " + value.getClass().getName()); + } + } + + private static boolean implementsNamedType(@Nonnull Class type, + @Nonnull String typeName) { + if (typeName.equals(type.getName())) { + return true; + } + for (Class interfaceType : type.getInterfaces()) { + if (implementsNamedType(interfaceType, typeName)) { + return true; + } + } + Class superType = type.getSuperclass(); + return superType != null && implementsNamedType(superType, typeName); + } + } } From 728caadeb6f22767b3f117875a2cd1c9625a47a8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:40:23 +0200 Subject: [PATCH 105/534] refactor(physicsstore): remove legacy command recorder Signed-off-by: Blovien --- .../resources/PhysicsWorldEventState.java | 69 -- .../resources/PhysicsWorldLifecycleState.java | 62 +- .../PhysicsWorldRuntimeResource.java | 9 +- .../owner/PhysicsCommandVisibilityState.java | 139 ---- .../simulation/PhysicsCommandDispatcher.java | 174 ---- .../simulation/PhysicsCommandOperations.java | 778 ------------------ .../simulation/PhysicsSimulationExecutor.java | 732 ---------------- .../simulation/RecordedBodyCreationKeys.java | 120 --- .../batch/RecordedPhysicsCommandBatch.java | 114 --- .../simulation/batch/RigidBodySpawnBatch.java | 222 ----- .../batch/RigidBodySpawnTemplateBatch.java | 224 ----- .../recorder/MutableJointCommandRecorder.java | 352 -------- .../MutablePhysicsCommandContext.java | 588 ------------- .../MutableRigidBodyCommandRecorder.java | 296 ------- .../MutableRigidBodySpawnBatchRecorder.java | 122 --- .../MutableRigidBodySpawnRecorder.java | 263 ------ ...MutableRigidBodySpawnTemplateRecorder.java | 96 --- .../recorder/RigidBodySpawnSink.java | 26 - .../simulation/PhysicsCommandBatch.java | 16 - .../simulation/PhysicsCommandCompletion.java | 146 ---- .../simulation/PhysicsCommandContext.java | 25 - .../simulation/PhysicsCommandHandle.java | 147 ---- .../simulation/PhysicsCommandMetadata.java | 13 - .../simulation/PhysicsCommandRecipe.java | 17 - .../simulation/PhysicsCommandResult.java | 75 -- .../plugin/simulation/PhysicsFalloff.java | 39 - .../plugin/simulation/PhysicsRecipes.java | 193 ----- .../recorder/JointCommandRecorder.java | 101 --- .../recorder/PhysicsCommandRecorder.java | 333 -------- .../recorder/RigidBodyCommandRecorder.java | 131 --- .../recorder/RigidBodySpawnBatchRecorder.java | 49 -- .../recorder/RigidBodySpawnRecorder.java | 95 --- .../RigidBodySpawnTemplateRecorder.java | 28 - 33 files changed, 3 insertions(+), 5791 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandDispatcher.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperations.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/RecordedBodyCreationKeys.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RecordedPhysicsCommandBatch.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnBatch.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnTemplateBatch.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableJointCommandRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutablePhysicsCommandContext.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodyCommandRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnBatchRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnTemplateRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/RigidBodySpawnSink.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandBatch.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandCompletion.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContext.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandHandle.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandMetadata.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandRecipe.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandResult.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsFalloff.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsRecipes.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/JointCommandRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/PhysicsCommandRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodyCommandRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnBatchRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnRecorder.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnTemplateRecorder.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java index 4cc6b326..e574b42c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java @@ -5,19 +5,12 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsStepEvent; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandCompletion; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandMetadata; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** * Latest value-only event frame for physics-owner outcomes. @@ -38,68 +31,6 @@ public PhysicsEventFrame getLatestFrame() { return latestFrame.get(); } - @Nonnull - public PhysicsEventFrame publishCommandCompletion(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame, - @Nonnull PhysicsCommandMetadata metadata, - int commandCount, - @Nonnull PhysicsCommandCompletion completion) { - return publishCommandCompletion(worldEpoch, - latestCapturedSnapshotFrame, - metadata, - commandCount, - 0, - null, - 0, - null, - completion); - } - - @Nonnull - public PhysicsEventFrame publishCommandCompletion(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame, - @Nonnull RecordedPhysicsCommandBatch batch, - @Nonnull PhysicsCommandCompletion completion) { - Objects.requireNonNull(batch, "batch"); - return publishCommandCompletion(worldEpoch, - latestCapturedSnapshotFrame, - batch.metadata(), - batch.publicBatch().commandCount(), - batch.bodyKeyReferenceCount(), - batch.firstBodyKey(), - batch.jointKeyReferenceCount(), - batch.firstJointKey(), - completion); - } - - @Nonnull - private PhysicsEventFrame publishCommandCompletion(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame, - @Nonnull PhysicsCommandMetadata metadata, - int commandCount, - int bodyKeyReferenceCount, - @Nullable RigidBodyKey firstBodyKey, - int jointKeyReferenceCount, - @Nullable JointKey firstJointKey, - @Nonnull PhysicsCommandCompletion completion) { - Objects.requireNonNull(latestCapturedSnapshotFrame, "latestCapturedSnapshotFrame"); - Objects.requireNonNull(metadata, "metadata"); - Objects.requireNonNull(completion, "completion"); - PhysicsCommandResult firstRejected = completion.firstRejected().orElse(null); - PhysicsCommandBatchEvent commandEvent = new PhysicsCommandBatchEvent(metadata.commandBatchSequence(), - metadata.submittedServerTick(), - System.nanoTime(), - commandCount, - bodyKeyReferenceCount, - firstBodyKey, - jointKeyReferenceCount, - firstJointKey, - completion.allApplied(), - firstRejected != null ? firstRejected.commandSequence() : 0L, - firstRejected != null ? firstRejected.message() : null); - return publishFrame(worldEpoch, latestCapturedSnapshotFrame, List.of(commandEvent), List.of(), List.of()); - } - @Nonnull public PhysicsEventFrame publishStepCaptured(long worldEpoch, @Nonnull PublishedPhysicsSnapshotFrame snapshotFrame) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index 3eeab8a5..0ad6da52 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -5,22 +5,17 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsCommandVisibilityState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSnapshotState.ApplyResult; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandCompletion; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.function.Consumer; -import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -32,20 +27,11 @@ public final class PhysicsWorldLifecycleState { private final PhysicsWorldSnapshotState snapshotState = new PhysicsWorldSnapshotState(); private final PhysicsWorldEventState eventState = new PhysicsWorldEventState(); - private final PhysicsCommandVisibilityState commandVisibility = new PhysicsCommandVisibilityState(); public long worldEpoch() { return snapshotState.worldEpoch(); } - public long commandWorldEpoch() { - return commandVisibility.commandWorldEpoch(); - } - - public long nextCommandBatchSequence() { - return commandVisibility.nextCommandBatchSequence(); - } - @Nonnull public PhysicsEventFrame latestEventFrame() { return eventState.getLatestFrame(); @@ -104,7 +90,7 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( bodyRegistry, stepSequence, serverTick, - commandVisibility.completedCommandBatchSequence(), + 0L, status, stepNanos, profilingEnabled); @@ -121,8 +107,6 @@ public int applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame fr ApplyResult result = snapshotState.applyPublishedSnapshotFrame(frame); if (result.currentWorldEpoch()) { bodyRegistry.applyPublishedRegistrationFrame(frame); - commandVisibility.applyLastIncludedCommandBatchSequence( - frame.lastIncludedCommandBatchSequence()); eventState.publishSnapshotPublication(snapshotState.worldEpoch(), frame, result.appliedCount(), @@ -184,29 +168,8 @@ public void clearBodySnapshots() { snapshotState.clearBodySnapshots(); } - public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey, - boolean directBodyCreationPending, - boolean ownerExecutorAttached) { - return commandVisibility.isBodyCreationPending(bodyKey, directBodyCreationPending); - } - - public boolean trackBodyCreationPublication(@Nonnull RecordedPhysicsCommandBatch batch, - boolean ownerExecutorAttached) { - return commandVisibility.trackBodyCreationPublication(batch, ownerExecutorAttached); - } - - public void clearBodyCreationPublication(@Nonnull RecordedPhysicsCommandBatch batch) { - commandVisibility.clearBodyCreationPublication(batch); - } - - public void clearBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - commandVisibility.clearBodyCreationPending(bodyKey); - } - public void publishDetachedOwnerRegistrationViews(@Nonnull PhysicsBodyRegistry bodyRegistry) { bodyRegistry.publishLiveRegistrationViews(); - commandVisibility.applyLastIncludedCommandBatchSequence( - commandVisibility.completedCommandBatchSequence()); } public void markWorldChanged(@Nonnull PhysicsBodyRegistry bodyRegistry, @@ -215,27 +178,6 @@ public void markWorldChanged(@Nonnull PhysicsBodyRegistry bodyRegistry, if (!ownerExecutorAttached) { bodyRegistry.publishLiveRegistrationViews(); } - if (commandVisibility.markWorldChanged()) { - eventState.publishEmpty(snapshotState.worldEpoch(), snapshotState.getLatestPublishedFrame()); - } - } - - @Nonnull - public PhysicsCommandCompletion executeCommandBatch(@Nonnull RecordedPhysicsCommandBatch batch, - @Nonnull Function executor) { - Objects.requireNonNull(batch, "batch"); - Objects.requireNonNull(executor, "executor"); - int depth = commandVisibility.enterCommandBatchExecution(); - try { - PhysicsCommandCompletion completion = executor.apply(batch); - commandVisibility.markCommandBatchCompleted(batch.metadata().commandBatchSequence()); - eventState.publishCommandCompletion(snapshotState.worldEpoch(), - snapshotState.getLatestPublishedFrame(), - batch, - completion); - return completion; - } finally { - commandVisibility.exitCommandBatchExecution(depth); - } + eventState.publishEmpty(snapshotState.worldEpoch(), snapshotState.getLatestPublishedFrame()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index cba7298e..fe68426d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -194,10 +194,6 @@ public long worldEpoch() { return lifecycleState.worldEpoch(); } - public long commandWorldEpoch() { - return lifecycleState.commandWorldEpoch(); - } - @Nonnull @Override public PhysicsEventFrame getLatestEventFrame() { @@ -1520,7 +1516,6 @@ public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKe } private void destroyBodyDirect(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { - lifecycleState.clearBodyCreationPending(bodyKey); bodyRuntime.destroyBody(bodyKey, removeFromSpace); } @@ -1704,9 +1699,7 @@ public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, } public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - return lifecycleState.isBodyCreationPending(bodyKey, - bodyRuntime.isBodyCreationPending(bodyKey), - ownerGateway.hasOwnerExecutor()); + return bodyRuntime.isBodyCreationPending(bodyKey); } public boolean hasPublishedOrPendingBodyRegistration(@Nonnull RigidBodyKey bodyKey) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityState.java deleted file mode 100644 index 9ee5ff87..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityState.java +++ /dev/null @@ -1,139 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; -import java.util.concurrent.atomic.AtomicLong; -import javax.annotation.Nonnull; - -/** - * Tracks command execution state that controls when command-created bodies are visible to readers. - */ -public final class PhysicsCommandVisibilityState { - - /* - * Monotonic owner-command sequence assigned at submission. It is separate from owner step - * sequence and Hytale world ticks because commands can complete between published snapshots. - */ - private final AtomicLong commandBatchSequence = new AtomicLong(); - - /* - * Highest command batch that has finished owner-lane execution. Snapshot capture copies this - * value as the frame's last-included command-batch sequence. - */ - private final AtomicLong completedCommandBatchSequence = new AtomicLong(); - - /* - * Reader-side body materialization can observe command completion before the next published - * registration frame has applied. Track exact command-created body keys so missing unrelated - * registrations can still be cleaned up during that publication gap. - */ - private final Object2LongOpenHashMap pendingCommandBodyCreationSequences = - new Object2LongOpenHashMap<>(); - - /* - * Command contexts capture this epoch. Runtime resets and topology replacement increment it so - * stale pre-reset command batches reject before dispatching against the new world state. - */ - private final AtomicLong commandWorldEpoch = new AtomicLong(); - - /* - * Topology changes inside a command batch should update snapshot world epoch, but should not - * make the next same-epoch FIFO command batch stale. This depth suppresses command epoch bumps - * during command execution. - */ - private final ThreadLocal commandBatchExecutionDepth = ThreadLocal.withInitial(() -> 0); - - public long nextCommandBatchSequence() { - return commandBatchSequence.incrementAndGet(); - } - - public long commandWorldEpoch() { - return commandWorldEpoch.get(); - } - - public boolean markWorldChanged() { - if (commandBatchExecutionDepth.get() != 0) { - return false; - } - commandWorldEpoch.incrementAndGet(); - return true; - } - - public int enterCommandBatchExecution() { - int depth = commandBatchExecutionDepth.get(); - commandBatchExecutionDepth.set(depth + 1); - return depth; - } - - public void exitCommandBatchExecution(int previousDepth) { - if (previousDepth == 0) { - commandBatchExecutionDepth.remove(); - } else { - commandBatchExecutionDepth.set(previousDepth); - } - } - - public void markCommandBatchCompleted(long commandBatchSequence) { - completedCommandBatchSequence.accumulateAndGet(commandBatchSequence, Math::max); - } - - public long completedCommandBatchSequence() { - return completedCommandBatchSequence.get(); - } - - public boolean trackBodyCreationPublication(@Nonnull RecordedPhysicsCommandBatch batch, - boolean ownerExecutorAttached) { - if (!batch.hasBodyCreationCommands() || !ownerExecutorAttached) { - return false; - } - long commandBatchSequence = batch.metadata().commandBatchSequence(); - synchronized (pendingCommandBodyCreationSequences) { - for (int index = 0; index < batch.bodyCreationKeyCount(); index++) { - RigidBodyKey bodyKey = batch.bodyCreationKey(index); - pendingCommandBodyCreationSequences.put(bodyKey, - Math.max(pendingCommandBodyCreationSequences.getLong(bodyKey), - commandBatchSequence)); - } - } - return true; - } - - public void clearBodyCreationPublication(@Nonnull RecordedPhysicsCommandBatch batch) { - long commandBatchSequence = batch.metadata().commandBatchSequence(); - synchronized (pendingCommandBodyCreationSequences) { - for (int index = 0; index < batch.bodyCreationKeyCount(); index++) { - RigidBodyKey bodyKey = batch.bodyCreationKey(index); - long current = pendingCommandBodyCreationSequences.getLong(bodyKey); - if (current <= commandBatchSequence) { - pendingCommandBodyCreationSequences.removeLong(bodyKey); - } - } - } - } - - public void clearBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - synchronized (pendingCommandBodyCreationSequences) { - pendingCommandBodyCreationSequences.removeLong(bodyKey); - } - } - - public void applyLastIncludedCommandBatchSequence(long lastIncludedCommandBatchSequence) { - synchronized (pendingCommandBodyCreationSequences) { - pendingCommandBodyCreationSequences.object2LongEntrySet() - .removeIf(entry -> entry.getLongValue() <= lastIncludedCommandBatchSequence); - } - } - - public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey, - boolean directBodyCreationPending) { - return directBodyCreationPending - || isCommandBodyCreationPending(bodyKey); - } - - private boolean isCommandBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - synchronized (pendingCommandBodyCreationSequences) { - return pendingCommandBodyCreationSequences.containsKey(bodyKey); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandDispatcher.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandDispatcher.java deleted file mode 100644 index 36a85be9..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandDispatcher.java +++ /dev/null @@ -1,174 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Internal execution target for frozen physics command batches. - */ -public interface PhysicsCommandDispatcher { - - void spawnRigidBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode); - - default void spawnRigidBodies(int bodyCount, - @Nonnull RigidBodyKey[] bodyKeys, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull float[] positions, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - for (int index = 0; index < bodyCount; index++) { - int positionOffset = index * 3; - spawnRigidBody(bodyKeys[index], - spaceId, - shape, - mass, - bodyType, - positions[positionOffset], - positions[positionOffset + 1], - positions[positionOffset + 2], - settings, - kind, - persistenceMode); - } - } - - default void spawnRigidBodies(int bodyCount, - @Nonnull long[] bodyKeyMostSignificantBits, - @Nonnull long[] bodyKeyLeastSignificantBits, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull float[] positions, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - for (int index = 0; index < bodyCount; index++) { - int positionOffset = index * 3; - RigidBodyKey bodyKey = RigidBodyKey.of(bodyKeyMostSignificantBits[index], - bodyKeyLeastSignificantBits[index]); - spawnRigidBody(bodyKey, - spaceId, - shape, - mass, - bodyType, - positions[positionOffset], - positions[positionOffset + 1], - positions[positionOffset + 2], - settings, - kind, - persistenceMode); - } - } - - void destroyRigidBody(@Nonnull RigidBodyKey bodyKey); - - void setSpaceGravity(@Nonnull SpaceId spaceId, - float x, - float y, - float z); - - void setRigidBodyTransform(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate); - - void setRigidBodyPosition(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - boolean activate); - - void setRigidBodyVelocity(@Nonnull RigidBodyKey bodyKey, - float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate); - - void setRigidBodyType(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType, - boolean activate); - - void activateRigidBody(@Nonnull RigidBodyKey bodyKey); - - void applyRigidBodyImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque); - - void applyRigidBodyForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque); - - void createJoint(@Nonnull JointKey jointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce); - - void destroyJoint(@Nonnull JointKey jointKey); - - void destroyJointBetween(@Nullable JointKey preferredJointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperations.java deleted file mode 100644 index 9e68e670..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperations.java +++ /dev/null @@ -1,778 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnBatch; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnTemplateBatch; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandContext; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.Arrays; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Packed execution encoding for a frozen physics command context. - * - *

This is not the plugin authoring API. Plugins should compose commands through - * {@link PhysicsCommandContext} recipes and recorders.

- * - *

The representation keeps opcode, object, and float storage flat so bulk command recording - * does not allocate a wrapper object for every operation. Preserve that property on hot paths such - * as template spawns and batch ray setup.

- * - *

Each operation occupies one row in {@code opcodes}, {@code flags}, {@code objectOffsets}, - * and {@code floatOffsets}. The row stores only the operation kind, flag bits, and the starting - * offsets into two dense payload pools: {@code objects} for reference values and {@code floats} - * for scalar values. When a recorder appends an operation, {@link #add(byte, int, int, int)} - * reserves the opcode row plus the exact object/float slot counts for that opcode, then advances - * the payload sizes. A field read later resolves as {@code objects[objectOffsets[index] + slot]} - * or {@code floats[floatOffsets[index] + slot]}.

- * - *

The {@code *_SLOT} and {@code *_SLOTS} constants below define that per-opcode layout. - * Required object fields are decoded through {@link #requiredObjectAt(int, int, Class)}, while - * intentionally optional fields, such as the preferred joint key in - * {@link #DESTROY_JOINT_BETWEEN_BODIES}, stay nullable through {@link #objectAt(int, int)}.

- */ -public final class PhysicsCommandOperations { - - public static final byte SPAWN_RIGID_BODY = 1; - public static final byte SPAWN_RIGID_BODY_BATCH = 2; - public static final byte DESTROY_RIGID_BODY = 3; - public static final byte SET_RIGID_BODY_TRANSFORM = 4; - public static final byte SET_RIGID_BODY_VELOCITY = 5; - public static final byte SET_RIGID_BODY_TYPE = 6; - public static final byte ACTIVATE_RIGID_BODY = 7; - public static final byte APPLY_RIGID_BODY_IMPULSE = 8; - public static final byte APPLY_RIGID_BODY_FORCE = 9; - public static final byte CREATE_JOINT = 10; - public static final byte DESTROY_JOINT = 11; - public static final byte DESTROY_JOINT_BETWEEN_BODIES = 12; - public static final byte SPAWN_RIGID_BODY_TEMPLATE_BATCH = 14; - public static final byte SET_RIGID_BODY_POSITION = 15; - public static final byte SET_SPACE_GRAVITY = 16; - - public static final int FLAG_ACTIVATE = 1; - public static final int FLAG_OFFSET = 1 << 1; - public static final int FLAG_TORQUE = 1 << 2; - public static final int FLAG_MOTOR_ENABLED = 1 << 3; - - static final int SPAWN_OBJECT_SLOTS = 7; - static final int SPAWN_BODY_KEY_OBJECT_SLOT = 0; - static final int SPAWN_SPACE_ID_OBJECT_SLOT = 1; - static final int SPAWN_SHAPE_OBJECT_SLOT = 2; - static final int SPAWN_BODY_TYPE_OBJECT_SLOT = 3; - static final int SPAWN_SETTINGS_OBJECT_SLOT = 4; - static final int SPAWN_KIND_OBJECT_SLOT = 5; - static final int SPAWN_PERSISTENCE_MODE_OBJECT_SLOT = 6; - static final int SPAWN_FLOAT_SLOTS = 4; - static final int SPAWN_MASS_FLOAT_SLOT = 0; - static final int SPAWN_POSITION_X_FLOAT_SLOT = 1; - static final int SPAWN_POSITION_Y_FLOAT_SLOT = 2; - static final int SPAWN_POSITION_Z_FLOAT_SLOT = 3; - - static final int SPAWN_BATCH_OBJECT_SLOTS = 1; - static final int SPAWN_BATCH_OBJECT_SLOT = 0; - static final int SPAWN_TEMPLATE_BATCH_OBJECT_SLOTS = 1; - static final int SPAWN_TEMPLATE_BATCH_OBJECT_SLOT = 0; - - static final int BODY_COMMAND_OBJECT_SLOTS = 1; - static final int BODY_COMMAND_BODY_KEY_OBJECT_SLOT = 0; - - static final int SET_SPACE_GRAVITY_OBJECT_SLOTS = 1; - static final int SET_SPACE_GRAVITY_SPACE_ID_OBJECT_SLOT = 0; - static final int SET_SPACE_GRAVITY_FLOAT_SLOTS = 3; - static final int SET_SPACE_GRAVITY_X_FLOAT_SLOT = 0; - static final int SET_SPACE_GRAVITY_Y_FLOAT_SLOT = 1; - static final int SET_SPACE_GRAVITY_Z_FLOAT_SLOT = 2; - - static final int SET_TRANSFORM_FLOAT_SLOTS = 7; - static final int SET_TRANSFORM_POSITION_X_FLOAT_SLOT = 0; - static final int SET_TRANSFORM_POSITION_Y_FLOAT_SLOT = 1; - static final int SET_TRANSFORM_POSITION_Z_FLOAT_SLOT = 2; - static final int SET_TRANSFORM_ROTATION_X_FLOAT_SLOT = 3; - static final int SET_TRANSFORM_ROTATION_Y_FLOAT_SLOT = 4; - static final int SET_TRANSFORM_ROTATION_Z_FLOAT_SLOT = 5; - static final int SET_TRANSFORM_ROTATION_W_FLOAT_SLOT = 6; - - static final int SET_POSITION_FLOAT_SLOTS = 3; - static final int SET_POSITION_X_FLOAT_SLOT = 0; - static final int SET_POSITION_Y_FLOAT_SLOT = 1; - static final int SET_POSITION_Z_FLOAT_SLOT = 2; - - static final int SET_VELOCITY_FLOAT_SLOTS = 6; - static final int SET_VELOCITY_LINEAR_X_FLOAT_SLOT = 0; - static final int SET_VELOCITY_LINEAR_Y_FLOAT_SLOT = 1; - static final int SET_VELOCITY_LINEAR_Z_FLOAT_SLOT = 2; - static final int SET_VELOCITY_ANGULAR_X_FLOAT_SLOT = 3; - static final int SET_VELOCITY_ANGULAR_Y_FLOAT_SLOT = 4; - static final int SET_VELOCITY_ANGULAR_Z_FLOAT_SLOT = 5; - - static final int SET_TYPE_OBJECT_SLOTS = 2; - static final int SET_TYPE_BODY_KEY_OBJECT_SLOT = 0; - static final int SET_TYPE_BODY_TYPE_OBJECT_SLOT = 1; - - static final int VECTOR_COMMAND_FLOAT_SLOTS = 3; - static final int OFFSET_VECTOR_COMMAND_FLOAT_SLOTS = 6; - static final int VECTOR_COMMAND_X_FLOAT_SLOT = 0; - static final int VECTOR_COMMAND_Y_FLOAT_SLOT = 1; - static final int VECTOR_COMMAND_Z_FLOAT_SLOT = 2; - static final int VECTOR_COMMAND_OFFSET_X_FLOAT_SLOT = 3; - static final int VECTOR_COMMAND_OFFSET_Y_FLOAT_SLOT = 4; - static final int VECTOR_COMMAND_OFFSET_Z_FLOAT_SLOT = 5; - - static final int CREATE_JOINT_OBJECT_SLOTS = 5; - static final int CREATE_JOINT_JOINT_KEY_OBJECT_SLOT = 0; - static final int CREATE_JOINT_SPACE_ID_OBJECT_SLOT = 1; - static final int CREATE_JOINT_BODY_A_OBJECT_SLOT = 2; - static final int CREATE_JOINT_BODY_B_OBJECT_SLOT = 3; - static final int CREATE_JOINT_TYPE_OBJECT_SLOT = 4; - static final int CREATE_JOINT_FLOAT_SLOTS = 16; - static final int CREATE_JOINT_ANCHOR_A_X_FLOAT_SLOT = 0; - static final int CREATE_JOINT_ANCHOR_A_Y_FLOAT_SLOT = 1; - static final int CREATE_JOINT_ANCHOR_A_Z_FLOAT_SLOT = 2; - static final int CREATE_JOINT_ANCHOR_B_X_FLOAT_SLOT = 3; - static final int CREATE_JOINT_ANCHOR_B_Y_FLOAT_SLOT = 4; - static final int CREATE_JOINT_ANCHOR_B_Z_FLOAT_SLOT = 5; - static final int CREATE_JOINT_AXIS_X_FLOAT_SLOT = 6; - static final int CREATE_JOINT_AXIS_Y_FLOAT_SLOT = 7; - static final int CREATE_JOINT_AXIS_Z_FLOAT_SLOT = 8; - static final int CREATE_JOINT_REST_LENGTH_FLOAT_SLOT = 9; - static final int CREATE_JOINT_STIFFNESS_FLOAT_SLOT = 10; - static final int CREATE_JOINT_DAMPING_FLOAT_SLOT = 11; - static final int CREATE_JOINT_LOWER_LIMIT_FLOAT_SLOT = 12; - static final int CREATE_JOINT_UPPER_LIMIT_FLOAT_SLOT = 13; - static final int CREATE_JOINT_MOTOR_TARGET_VELOCITY_FLOAT_SLOT = 14; - static final int CREATE_JOINT_MOTOR_MAX_FORCE_FLOAT_SLOT = 15; - - static final int DESTROY_JOINT_OBJECT_SLOTS = 1; - static final int DESTROY_JOINT_KEY_OBJECT_SLOT = 0; - static final int DESTROY_JOINT_BETWEEN_OBJECT_SLOTS = 4; - static final int DESTROY_JOINT_BETWEEN_PREFERRED_KEY_OBJECT_SLOT = 0; - static final int DESTROY_JOINT_BETWEEN_SPACE_ID_OBJECT_SLOT = 1; - static final int DESTROY_JOINT_BETWEEN_BODY_A_OBJECT_SLOT = 2; - static final int DESTROY_JOINT_BETWEEN_BODY_B_OBJECT_SLOT = 3; - - private byte[] opcodes; - private int[] objectOffsets; - private int[] floatOffsets; - private Object[] objects; - private float[] floats; - private int[] flags; - private int size; - private int objectSize; - private int floatSize; - - public PhysicsCommandOperations(int expectedOps) { - int capacity = Math.max(1, expectedOps); - opcodes = new byte[capacity]; - objectOffsets = new int[capacity]; - floatOffsets = new int[capacity]; - objects = new Object[capacity * 2]; - floats = new float[capacity * 4]; - flags = new int[capacity]; - } - - private PhysicsCommandOperations(@Nonnull byte[] opcodes, - @Nonnull int[] objectOffsets, - @Nonnull int[] floatOffsets, - @Nonnull Object[] objects, - @Nonnull float[] floats, - @Nonnull int[] flags, - int size, - int objectSize, - int floatSize) { - this.opcodes = opcodes; - this.objectOffsets = objectOffsets; - this.floatOffsets = floatOffsets; - this.objects = objects; - this.floats = floats; - this.flags = flags; - this.size = size; - this.objectSize = objectSize; - this.floatSize = floatSize; - } - - @Nonnull - public PhysicsCommandOperations freeze() { - return new PhysicsCommandOperations( - freeze(opcodes, size), - freeze(objectOffsets, size), - freeze(floatOffsets, size), - freeze(objects, objectSize), - freeze(floats, floatSize), - freeze(flags, size), - size, - objectSize, - floatSize); - } - - public void addSpawn(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - int index = add(SPAWN_RIGID_BODY, 0, SPAWN_OBJECT_SLOTS, SPAWN_FLOAT_SLOTS); - object(index, SPAWN_BODY_KEY_OBJECT_SLOT, bodyKey); - object(index, SPAWN_SPACE_ID_OBJECT_SLOT, spaceId); - object(index, SPAWN_SHAPE_OBJECT_SLOT, shape); - object(index, SPAWN_BODY_TYPE_OBJECT_SLOT, bodyType); - object(index, SPAWN_SETTINGS_OBJECT_SLOT, settings); - object(index, SPAWN_KIND_OBJECT_SLOT, kind); - object(index, SPAWN_PERSISTENCE_MODE_OBJECT_SLOT, persistenceMode); - floatAt(index, SPAWN_MASS_FLOAT_SLOT, mass); - floatAt(index, SPAWN_POSITION_X_FLOAT_SLOT, positionX); - floatAt(index, SPAWN_POSITION_Y_FLOAT_SLOT, positionY); - floatAt(index, SPAWN_POSITION_Z_FLOAT_SLOT, positionZ); - } - - public void addSpawnBatch(@Nonnull RigidBodySpawnBatch batch) { - if (batch.size() <= 0) { - return; - } - int index = add(SPAWN_RIGID_BODY_BATCH, 0, SPAWN_BATCH_OBJECT_SLOTS, 0); - object(index, SPAWN_BATCH_OBJECT_SLOT, batch.freeze()); - } - - public void addSpawnTemplateBatch(@Nonnull RigidBodySpawnTemplateBatch batch) { - if (batch.size() <= 0) { - return; - } - int index = add(SPAWN_RIGID_BODY_TEMPLATE_BATCH, 0, SPAWN_TEMPLATE_BATCH_OBJECT_SLOTS, 0); - object(index, SPAWN_TEMPLATE_BATCH_OBJECT_SLOT, batch.freeze()); - } - - public void addDestroyBody(@Nonnull RigidBodyKey bodyKey) { - int index = add(DESTROY_RIGID_BODY, 0, BODY_COMMAND_OBJECT_SLOTS, 0); - object(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, bodyKey); - } - - public void addSetSpaceGravity(@Nonnull SpaceId spaceId, - float x, - float y, - float z) { - int index = add(SET_SPACE_GRAVITY, 0, SET_SPACE_GRAVITY_OBJECT_SLOTS, SET_SPACE_GRAVITY_FLOAT_SLOTS); - object(index, SET_SPACE_GRAVITY_SPACE_ID_OBJECT_SLOT, spaceId); - floatAt(index, SET_SPACE_GRAVITY_X_FLOAT_SLOT, x); - floatAt(index, SET_SPACE_GRAVITY_Y_FLOAT_SLOT, y); - floatAt(index, SET_SPACE_GRAVITY_Z_FLOAT_SLOT, z); - } - - public void addSetTransform(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate) { - int index = add(SET_RIGID_BODY_TRANSFORM, - activate ? FLAG_ACTIVATE : 0, - BODY_COMMAND_OBJECT_SLOTS, - SET_TRANSFORM_FLOAT_SLOTS); - object(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, bodyKey); - floatAt(index, SET_TRANSFORM_POSITION_X_FLOAT_SLOT, positionX); - floatAt(index, SET_TRANSFORM_POSITION_Y_FLOAT_SLOT, positionY); - floatAt(index, SET_TRANSFORM_POSITION_Z_FLOAT_SLOT, positionZ); - floatAt(index, SET_TRANSFORM_ROTATION_X_FLOAT_SLOT, rotationX); - floatAt(index, SET_TRANSFORM_ROTATION_Y_FLOAT_SLOT, rotationY); - floatAt(index, SET_TRANSFORM_ROTATION_Z_FLOAT_SLOT, rotationZ); - floatAt(index, SET_TRANSFORM_ROTATION_W_FLOAT_SLOT, rotationW); - } - - public void addSetPosition(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - boolean activate) { - int index = add(SET_RIGID_BODY_POSITION, - activate ? FLAG_ACTIVATE : 0, - BODY_COMMAND_OBJECT_SLOTS, - SET_POSITION_FLOAT_SLOTS); - object(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, bodyKey); - floatAt(index, SET_POSITION_X_FLOAT_SLOT, positionX); - floatAt(index, SET_POSITION_Y_FLOAT_SLOT, positionY); - floatAt(index, SET_POSITION_Z_FLOAT_SLOT, positionZ); - } - - public void addSetVelocity(@Nonnull RigidBodyKey bodyKey, - float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate) { - int index = add(SET_RIGID_BODY_VELOCITY, - activate ? FLAG_ACTIVATE : 0, - BODY_COMMAND_OBJECT_SLOTS, - SET_VELOCITY_FLOAT_SLOTS); - object(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, bodyKey); - floatAt(index, SET_VELOCITY_LINEAR_X_FLOAT_SLOT, linearX); - floatAt(index, SET_VELOCITY_LINEAR_Y_FLOAT_SLOT, linearY); - floatAt(index, SET_VELOCITY_LINEAR_Z_FLOAT_SLOT, linearZ); - floatAt(index, SET_VELOCITY_ANGULAR_X_FLOAT_SLOT, angularX); - floatAt(index, SET_VELOCITY_ANGULAR_Y_FLOAT_SLOT, angularY); - floatAt(index, SET_VELOCITY_ANGULAR_Z_FLOAT_SLOT, angularZ); - } - - public void addSetType(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType, - boolean activate) { - int index = add(SET_RIGID_BODY_TYPE, activate ? FLAG_ACTIVATE : 0, SET_TYPE_OBJECT_SLOTS, 0); - object(index, SET_TYPE_BODY_KEY_OBJECT_SLOT, bodyKey); - object(index, SET_TYPE_BODY_TYPE_OBJECT_SLOT, bodyType); - } - - public void addActivate(@Nonnull RigidBodyKey bodyKey) { - int index = add(ACTIVATE_RIGID_BODY, 0, BODY_COMMAND_OBJECT_SLOTS, 0); - object(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, bodyKey); - } - - public void addImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - int operationFlags = torque ? FLAG_TORQUE : 0; - if (hasOffset) { - operationFlags |= FLAG_OFFSET; - } - int index = add(APPLY_RIGID_BODY_IMPULSE, - operationFlags, - BODY_COMMAND_OBJECT_SLOTS, - (operationFlags & FLAG_OFFSET) != 0 ? OFFSET_VECTOR_COMMAND_FLOAT_SLOTS : VECTOR_COMMAND_FLOAT_SLOTS); - object(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, bodyKey); - floatAt(index, VECTOR_COMMAND_X_FLOAT_SLOT, x); - floatAt(index, VECTOR_COMMAND_Y_FLOAT_SLOT, y); - floatAt(index, VECTOR_COMMAND_Z_FLOAT_SLOT, z); - if ((operationFlags & FLAG_OFFSET) != 0) { - floatAt(index, VECTOR_COMMAND_OFFSET_X_FLOAT_SLOT, offsetX); - floatAt(index, VECTOR_COMMAND_OFFSET_Y_FLOAT_SLOT, offsetY); - floatAt(index, VECTOR_COMMAND_OFFSET_Z_FLOAT_SLOT, offsetZ); - } - } - - public void addForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - int operationFlags = torque ? FLAG_TORQUE : 0; - if (hasOffset) { - operationFlags |= FLAG_OFFSET; - } - int index = add(APPLY_RIGID_BODY_FORCE, - operationFlags, - BODY_COMMAND_OBJECT_SLOTS, - (operationFlags & FLAG_OFFSET) != 0 ? OFFSET_VECTOR_COMMAND_FLOAT_SLOTS : VECTOR_COMMAND_FLOAT_SLOTS); - object(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, bodyKey); - floatAt(index, VECTOR_COMMAND_X_FLOAT_SLOT, x); - floatAt(index, VECTOR_COMMAND_Y_FLOAT_SLOT, y); - floatAt(index, VECTOR_COMMAND_Z_FLOAT_SLOT, z); - if ((operationFlags & FLAG_OFFSET) != 0) { - floatAt(index, VECTOR_COMMAND_OFFSET_X_FLOAT_SLOT, offsetX); - floatAt(index, VECTOR_COMMAND_OFFSET_Y_FLOAT_SLOT, offsetY); - floatAt(index, VECTOR_COMMAND_OFFSET_Z_FLOAT_SLOT, offsetZ); - } - } - - public void addJoint(@Nonnull JointKey jointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - int index = add(CREATE_JOINT, - motorEnabled ? FLAG_MOTOR_ENABLED : 0, - CREATE_JOINT_OBJECT_SLOTS, - CREATE_JOINT_FLOAT_SLOTS); - object(index, CREATE_JOINT_JOINT_KEY_OBJECT_SLOT, jointKey); - object(index, CREATE_JOINT_SPACE_ID_OBJECT_SLOT, spaceId); - object(index, CREATE_JOINT_BODY_A_OBJECT_SLOT, bodyA); - object(index, CREATE_JOINT_BODY_B_OBJECT_SLOT, bodyB); - object(index, CREATE_JOINT_TYPE_OBJECT_SLOT, type); - floatAt(index, CREATE_JOINT_ANCHOR_A_X_FLOAT_SLOT, anchorAX); - floatAt(index, CREATE_JOINT_ANCHOR_A_Y_FLOAT_SLOT, anchorAY); - floatAt(index, CREATE_JOINT_ANCHOR_A_Z_FLOAT_SLOT, anchorAZ); - floatAt(index, CREATE_JOINT_ANCHOR_B_X_FLOAT_SLOT, anchorBX); - floatAt(index, CREATE_JOINT_ANCHOR_B_Y_FLOAT_SLOT, anchorBY); - floatAt(index, CREATE_JOINT_ANCHOR_B_Z_FLOAT_SLOT, anchorBZ); - floatAt(index, CREATE_JOINT_AXIS_X_FLOAT_SLOT, axisX); - floatAt(index, CREATE_JOINT_AXIS_Y_FLOAT_SLOT, axisY); - floatAt(index, CREATE_JOINT_AXIS_Z_FLOAT_SLOT, axisZ); - floatAt(index, CREATE_JOINT_REST_LENGTH_FLOAT_SLOT, restLength); - floatAt(index, CREATE_JOINT_STIFFNESS_FLOAT_SLOT, stiffness); - floatAt(index, CREATE_JOINT_DAMPING_FLOAT_SLOT, damping); - floatAt(index, CREATE_JOINT_LOWER_LIMIT_FLOAT_SLOT, lowerLimit); - floatAt(index, CREATE_JOINT_UPPER_LIMIT_FLOAT_SLOT, upperLimit); - floatAt(index, CREATE_JOINT_MOTOR_TARGET_VELOCITY_FLOAT_SLOT, motorTargetVelocity); - floatAt(index, CREATE_JOINT_MOTOR_MAX_FORCE_FLOAT_SLOT, motorMaxForce); - } - - public void addDestroyJoint(@Nonnull JointKey jointKey) { - int index = add(DESTROY_JOINT, 0, DESTROY_JOINT_OBJECT_SLOTS, 0); - object(index, DESTROY_JOINT_KEY_OBJECT_SLOT, jointKey); - } - - public void addDestroyJointBetween(@Nullable JointKey preferredJointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB) { - int index = add(DESTROY_JOINT_BETWEEN_BODIES, 0, DESTROY_JOINT_BETWEEN_OBJECT_SLOTS, 0); - object(index, DESTROY_JOINT_BETWEEN_PREFERRED_KEY_OBJECT_SLOT, preferredJointKey); - object(index, DESTROY_JOINT_BETWEEN_SPACE_ID_OBJECT_SLOT, spaceId); - object(index, DESTROY_JOINT_BETWEEN_BODY_A_OBJECT_SLOT, bodyA); - object(index, DESTROY_JOINT_BETWEEN_BODY_B_OBJECT_SLOT, bodyB); - } - - public int size() { - return size; - } - - @Nonnull - public RecordedBodyCreationKeys bodyCreationKeys() { - RecordedBodyCreationKeys.Builder keys = RecordedBodyCreationKeys.builder(); - for (int index = 0; index < size; index++) { - switch (opcode(index)) { - case SPAWN_RIGID_BODY -> keys.add( - requiredObjectAt(index, SPAWN_BODY_KEY_OBJECT_SLOT, RigidBodyKey.class)); - case SPAWN_RIGID_BODY_BATCH -> { - RigidBodySpawnBatch batch = - requiredObjectAt(index, SPAWN_BATCH_OBJECT_SLOT, RigidBodySpawnBatch.class); - keys.addAll(batch.bodyKeyMostSignificantBits(), - batch.bodyKeyLeastSignificantBits(), - batch.size()); - } - case SPAWN_RIGID_BODY_TEMPLATE_BATCH -> { - RigidBodySpawnTemplateBatch batch = - requiredObjectAt(index, - SPAWN_TEMPLATE_BATCH_OBJECT_SLOT, - RigidBodySpawnTemplateBatch.class); - keys.addAll(batch.bodyKeyMostSignificantBits(), - batch.bodyKeyLeastSignificantBits(), - batch.size()); - } - default -> { - } - } - } - return keys.build(); - } - - @Nonnull - public EntityReferences entityReferences() { - EntityReferenceAccumulator accumulator = new EntityReferenceAccumulator(); - for (int index = 0; index < size; index++) { - switch (opcode(index)) { - case SPAWN_RIGID_BODY, - DESTROY_RIGID_BODY, - SET_RIGID_BODY_TRANSFORM, - SET_RIGID_BODY_POSITION, - SET_RIGID_BODY_VELOCITY, - SET_RIGID_BODY_TYPE, - ACTIVATE_RIGID_BODY, - APPLY_RIGID_BODY_IMPULSE, - APPLY_RIGID_BODY_FORCE -> accumulator.addBody( - requiredObjectAt(index, BODY_COMMAND_BODY_KEY_OBJECT_SLOT, RigidBodyKey.class)); - case SPAWN_RIGID_BODY_BATCH -> - accumulator.addSpawnBatch( - requiredObjectAt(index, SPAWN_BATCH_OBJECT_SLOT, RigidBodySpawnBatch.class)); - case SPAWN_RIGID_BODY_TEMPLATE_BATCH -> - accumulator.addSpawnTemplateBatch( - requiredObjectAt(index, - SPAWN_TEMPLATE_BATCH_OBJECT_SLOT, - RigidBodySpawnTemplateBatch.class)); - case CREATE_JOINT -> { - accumulator.addJoint(requiredObjectAt(index, - CREATE_JOINT_JOINT_KEY_OBJECT_SLOT, - JointKey.class)); - accumulator.addBody(requiredObjectAt(index, - CREATE_JOINT_BODY_A_OBJECT_SLOT, - RigidBodyKey.class)); - accumulator.addBody(requiredObjectAt(index, - CREATE_JOINT_BODY_B_OBJECT_SLOT, - RigidBodyKey.class)); - } - case DESTROY_JOINT -> accumulator.addJoint( - requiredObjectAt(index, DESTROY_JOINT_KEY_OBJECT_SLOT, JointKey.class)); - case DESTROY_JOINT_BETWEEN_BODIES -> { - JointKey jointKey = (JointKey) objectAt(index, - DESTROY_JOINT_BETWEEN_PREFERRED_KEY_OBJECT_SLOT); - if (jointKey != null) { - accumulator.addJoint(jointKey); - } - accumulator.addBody(requiredObjectAt(index, - DESTROY_JOINT_BETWEEN_BODY_A_OBJECT_SLOT, - RigidBodyKey.class)); - accumulator.addBody(requiredObjectAt(index, - DESTROY_JOINT_BETWEEN_BODY_B_OBJECT_SLOT, - RigidBodyKey.class)); - } - default -> { - } - } - } - return accumulator.references(); - } - - public byte opcode(int index) { - checkIndex(index); - return opcodes[index]; - } - - public int flags(int index) { - checkIndex(index); - return flags[index]; - } - - public float floatAt(int index, - int slot) { - checkIndex(index); - return floats[floatOffsets[index] + slot]; - } - - @Nullable - public Object objectAt(int index, - int slot) { - checkIndex(index); - return objects[objectOffsets[index] + slot]; - } - - @Nullable - public T objectAt(int index, - int slot, - @Nonnull Class type) { - return type.cast(objectAt(index, slot)); - } - - @Nonnull - public T requiredObjectAt(int index, - int slot, - @Nonnull Class type) { - T value = objectAt(index, slot, type); - if (value == null) { - throw new IllegalArgumentException("Missing physics command object at index=" - + index - + " slot=" - + slot - + " type=" - + type.getSimpleName()); - } - return value; - } - - private int add(byte opcode, - int operationFlags, - int objectSlots, - int floatSlots) { - ensureOperationCapacity(size + 1); - ensureObjectCapacity(objectSize + objectSlots); - ensureFloatCapacity(floatSize + floatSlots); - int index = size++; - opcodes[index] = opcode; - flags[index] = operationFlags; - objectOffsets[index] = objectSize; - floatOffsets[index] = floatSize; - objectSize += objectSlots; - floatSize += floatSlots; - return index; - } - - private void object(int index, - int slot, - @Nullable Object value) { - objects[objectOffsets[index] + slot] = value; - } - - private void floatAt(int index, - int slot, - float value) { - floats[floatOffsets[index] + slot] = value; - } - - private void checkIndex(int index) { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(index); - } - } - - private void ensureOperationCapacity(int required) { - if (required <= opcodes.length) { - return; - } - int nextCapacity = Math.max(required, opcodes.length + (opcodes.length >> 1)); - opcodes = Arrays.copyOf(opcodes, nextCapacity); - objectOffsets = Arrays.copyOf(objectOffsets, nextCapacity); - floatOffsets = Arrays.copyOf(floatOffsets, nextCapacity); - flags = Arrays.copyOf(flags, nextCapacity); - } - - private void ensureObjectCapacity(int required) { - if (required <= objects.length) { - return; - } - int nextCapacity = Math.max(required, objects.length + (objects.length >> 1)); - objects = Arrays.copyOf(objects, nextCapacity); - } - - private void ensureFloatCapacity(int required) { - if (required <= floats.length) { - return; - } - int nextCapacity = Math.max(required, floats.length + (floats.length >> 1)); - floats = Arrays.copyOf(floats, nextCapacity); - } - - @Nonnull - private static byte[] freeze(@Nonnull byte[] values, - int size) { - return values.length == size ? values : Arrays.copyOf(values, size); - } - - @Nonnull - private static int[] freeze(@Nonnull int[] values, - int size) { - return values.length == size ? values : Arrays.copyOf(values, size); - } - - @Nonnull - private static Object[] freeze(@Nonnull Object[] values, - int size) { - return values.length == size ? values : Arrays.copyOf(values, size); - } - - @Nonnull - private static float[] freeze(@Nonnull float[] values, - int size) { - return values.length == size ? values : Arrays.copyOf(values, size); - } - - public static final class EntityReferences { - - private final int bodyKeyReferenceCount; - @Nullable - private final RigidBodyKey firstBodyKey; - private final int jointKeyReferenceCount; - @Nullable - private final JointKey firstJointKey; - - private EntityReferences(int bodyKeyReferenceCount, - @Nullable RigidBodyKey firstBodyKey, - int jointKeyReferenceCount, - @Nullable JointKey firstJointKey) { - this.bodyKeyReferenceCount = Math.max(0, bodyKeyReferenceCount); - this.firstBodyKey = this.bodyKeyReferenceCount > 0 ? firstBodyKey : null; - this.jointKeyReferenceCount = Math.max(0, jointKeyReferenceCount); - this.firstJointKey = this.jointKeyReferenceCount > 0 ? firstJointKey : null; - } - - public int bodyKeyReferenceCount() { - return bodyKeyReferenceCount; - } - - @Nullable - public RigidBodyKey firstBodyKey() { - return firstBodyKey; - } - - public int jointKeyReferenceCount() { - return jointKeyReferenceCount; - } - - @Nullable - public JointKey firstJointKey() { - return firstJointKey; - } - } - - private static final class EntityReferenceAccumulator { - - private int bodyKeyReferenceCount; - @Nullable - private RigidBodyKey firstBodyKey; - private int jointKeyReferenceCount; - @Nullable - private JointKey firstJointKey; - - void addBody(@Nonnull RigidBodyKey bodyKey) { - if (firstBodyKey == null) { - firstBodyKey = bodyKey; - } - bodyKeyReferenceCount++; - } - - void addSpawnBatch(@Nonnull RigidBodySpawnBatch batch) { - int batchSize = batch.size(); - if (batchSize <= 0) { - return; - } - if (firstBodyKey == null) { - firstBodyKey = batch.bodyKey(0); - } - bodyKeyReferenceCount += batchSize; - } - - void addSpawnTemplateBatch(@Nonnull RigidBodySpawnTemplateBatch batch) { - int batchSize = batch.size(); - if (batchSize <= 0) { - return; - } - if (firstBodyKey == null) { - firstBodyKey = batch.bodyKey(0); - } - bodyKeyReferenceCount += batchSize; - } - - void addJoint(@Nonnull JointKey jointKey) { - if (firstJointKey == null) { - firstJointKey = jointKey; - } - jointKeyReferenceCount++; - } - - @Nonnull - EntityReferences references() { - return new EntityReferences(bodyKeyReferenceCount, - firstBodyKey, - jointKeyReferenceCount, - firstJointKey); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java deleted file mode 100644 index b94b4e1b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutor.java +++ /dev/null @@ -1,732 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnBatch; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnTemplateBatch; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandCompletion; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Owner-lane translator from public simulation commands to live backend calls. - */ -public final class PhysicsSimulationExecutor implements PhysicsCommandDispatcher { - - @Nonnull - private final PhysicsWorldRuntimeResource runtime; - - public PhysicsSimulationExecutor(@Nonnull PhysicsWorldRuntimeResource runtime) { - this.runtime = Objects.requireNonNull(runtime, "runtime"); - } - - @Nonnull - public PhysicsCommandCompletion execute(@Nonnull RecordedPhysicsCommandBatch batch) { - Objects.requireNonNull(batch, "batch"); - long currentWorldEpoch = runtime.commandWorldEpoch(); - if (batch.commandWorldEpoch() != currentWorldEpoch) { - return rejectStaleBatch(batch, currentWorldEpoch); - } - PhysicsCommandOperations operations = batch.operations(); - List results = null; - for (int index = 0; index < operations.size(); index++) { - long commandSequence = index + 1L; - try { - dispatch(index, operations); - if (results != null) { - results.add(PhysicsCommandResult.applied(batch.metadata(), commandSequence)); - } - } catch (RuntimeException exception) { - if (results == null) { - results = new ArrayList<>(operations.size()); - for (int appliedIndex = 0; appliedIndex < index; appliedIndex++) { - results.add(PhysicsCommandResult.applied(batch.metadata(), appliedIndex + 1L)); - } - } - results.add(PhysicsCommandResult.rejected(batch.metadata(), - commandSequence, - exception.getMessage() != null ? exception.getMessage() : exception.getClass().getSimpleName())); - } - } - return results != null - ? PhysicsCommandCompletion.of(results) - : PhysicsCommandCompletion.allApplied(batch.metadata(), operations.size()); - } - - @Nonnull - private static PhysicsCommandCompletion rejectStaleBatch(@Nonnull RecordedPhysicsCommandBatch batch, - long currentWorldEpoch) { - String message = "stale physics command batch worldEpoch=" - + batch.commandWorldEpoch() - + " currentWorldEpoch=" - + currentWorldEpoch; - return PhysicsCommandCompletion.allRejected(batch.metadata(), batch.commandCount(), message); - } - - private void dispatch(int index, - @Nonnull PhysicsCommandOperations operations) { - switch (operations.opcode(index)) { - case PhysicsCommandOperations.SPAWN_RIGID_BODY -> dispatchSpawn(index, operations); - case PhysicsCommandOperations.SPAWN_RIGID_BODY_BATCH -> - dispatchSpawnBatch(operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_BATCH_OBJECT_SLOT, - RigidBodySpawnBatch.class)); - case PhysicsCommandOperations.SPAWN_RIGID_BODY_TEMPLATE_BATCH -> - dispatchSpawnTemplateBatch(operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_TEMPLATE_BATCH_OBJECT_SLOT, - RigidBodySpawnTemplateBatch.class)); - case PhysicsCommandOperations.DESTROY_RIGID_BODY -> destroyRigidBody( - operations.requiredObjectAt(index, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class)); - case PhysicsCommandOperations.SET_SPACE_GRAVITY -> setSpaceGravity( - operations.requiredObjectAt(index, - PhysicsCommandOperations.SET_SPACE_GRAVITY_SPACE_ID_OBJECT_SLOT, - SpaceId.class), - operations.floatAt(index, PhysicsCommandOperations.SET_SPACE_GRAVITY_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_SPACE_GRAVITY_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_SPACE_GRAVITY_Z_FLOAT_SLOT)); - case PhysicsCommandOperations.SET_RIGID_BODY_TRANSFORM -> setRigidBodyTransform( - operations.requiredObjectAt(index, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class), - operations.floatAt(index, PhysicsCommandOperations.SET_TRANSFORM_POSITION_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_TRANSFORM_POSITION_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_TRANSFORM_POSITION_Z_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_TRANSFORM_ROTATION_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_TRANSFORM_ROTATION_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_TRANSFORM_ROTATION_Z_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_TRANSFORM_ROTATION_W_FLOAT_SLOT), - (operations.flags(index) & PhysicsCommandOperations.FLAG_ACTIVATE) != 0); - case PhysicsCommandOperations.SET_RIGID_BODY_POSITION -> setRigidBodyPosition( - operations.requiredObjectAt(index, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class), - operations.floatAt(index, PhysicsCommandOperations.SET_POSITION_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_POSITION_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_POSITION_Z_FLOAT_SLOT), - (operations.flags(index) & PhysicsCommandOperations.FLAG_ACTIVATE) != 0); - case PhysicsCommandOperations.SET_RIGID_BODY_VELOCITY -> setRigidBodyVelocity( - operations.requiredObjectAt(index, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class), - operations.floatAt(index, PhysicsCommandOperations.SET_VELOCITY_LINEAR_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_VELOCITY_LINEAR_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_VELOCITY_LINEAR_Z_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_VELOCITY_ANGULAR_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_VELOCITY_ANGULAR_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SET_VELOCITY_ANGULAR_Z_FLOAT_SLOT), - (operations.flags(index) & PhysicsCommandOperations.FLAG_ACTIVATE) != 0); - case PhysicsCommandOperations.SET_RIGID_BODY_TYPE -> setRigidBodyType( - operations.requiredObjectAt(index, - PhysicsCommandOperations.SET_TYPE_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.SET_TYPE_BODY_TYPE_OBJECT_SLOT, - PhysicsBodyType.class), - (operations.flags(index) & PhysicsCommandOperations.FLAG_ACTIVATE) != 0); - case PhysicsCommandOperations.ACTIVATE_RIGID_BODY -> activateRigidBody( - operations.requiredObjectAt(index, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class)); - case PhysicsCommandOperations.APPLY_RIGID_BODY_IMPULSE -> applyRigidBodyImpulse( - operations.requiredObjectAt(index, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class), - operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_Z_FLOAT_SLOT), - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0, - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0 - ? operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_OFFSET_X_FLOAT_SLOT) - : 0.0f, - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0 - ? operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_OFFSET_Y_FLOAT_SLOT) - : 0.0f, - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0 - ? operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_OFFSET_Z_FLOAT_SLOT) - : 0.0f, - (operations.flags(index) & PhysicsCommandOperations.FLAG_TORQUE) != 0); - case PhysicsCommandOperations.APPLY_RIGID_BODY_FORCE -> applyRigidBodyForce( - operations.requiredObjectAt(index, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class), - operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_Z_FLOAT_SLOT), - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0, - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0 - ? operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_OFFSET_X_FLOAT_SLOT) - : 0.0f, - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0 - ? operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_OFFSET_Y_FLOAT_SLOT) - : 0.0f, - (operations.flags(index) & PhysicsCommandOperations.FLAG_OFFSET) != 0 - ? operations.floatAt(index, PhysicsCommandOperations.VECTOR_COMMAND_OFFSET_Z_FLOAT_SLOT) - : 0.0f, - (operations.flags(index) & PhysicsCommandOperations.FLAG_TORQUE) != 0); - case PhysicsCommandOperations.CREATE_JOINT -> dispatchJoint(index, operations); - case PhysicsCommandOperations.DESTROY_JOINT -> destroyJoint( - operations.requiredObjectAt(index, - PhysicsCommandOperations.DESTROY_JOINT_KEY_OBJECT_SLOT, - JointKey.class)); - case PhysicsCommandOperations.DESTROY_JOINT_BETWEEN_BODIES -> destroyJointBetween( - (JointKey) operations.objectAt(index, - PhysicsCommandOperations.DESTROY_JOINT_BETWEEN_PREFERRED_KEY_OBJECT_SLOT), - operations.requiredObjectAt(index, - PhysicsCommandOperations.DESTROY_JOINT_BETWEEN_SPACE_ID_OBJECT_SLOT, - SpaceId.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.DESTROY_JOINT_BETWEEN_BODY_A_OBJECT_SLOT, - RigidBodyKey.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.DESTROY_JOINT_BETWEEN_BODY_B_OBJECT_SLOT, - RigidBodyKey.class)); - default -> throw new IllegalArgumentException("Unsupported physics command opcode " - + operations.opcode(index)); - } - } - - private void dispatchSpawn(int index, - @Nonnull PhysicsCommandOperations operations) { - spawnRigidBody(operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_SPACE_ID_OBJECT_SLOT, - SpaceId.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_SHAPE_OBJECT_SLOT, - PhysicsShapeSpec.class), - operations.floatAt(index, PhysicsCommandOperations.SPAWN_MASS_FLOAT_SLOT), - operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_BODY_TYPE_OBJECT_SLOT, - PhysicsBodyType.class), - operations.floatAt(index, PhysicsCommandOperations.SPAWN_POSITION_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SPAWN_POSITION_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.SPAWN_POSITION_Z_FLOAT_SLOT), - operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_SETTINGS_OBJECT_SLOT, - RigidBodySpawnSettings.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_KIND_OBJECT_SLOT, - PhysicsBodyKind.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.SPAWN_PERSISTENCE_MODE_OBJECT_SLOT, - PhysicsBodyPersistenceMode.class)); - } - - private void dispatchSpawnBatch(@Nonnull RigidBodySpawnBatch batch) { - for (int index = 0; index < batch.size(); index++) { - spawnRigidBody(batch.bodyKey(index), - batch.spaceId(index), - batch.shape(index), - batch.mass(index), - batch.bodyType(index), - batch.positionX(index), - batch.positionY(index), - batch.positionZ(index), - batch.settings(index), - batch.kind(index), - batch.persistenceMode(index)); - } - } - - private void dispatchSpawnTemplateBatch(@Nonnull RigidBodySpawnTemplateBatch batch) { - spawnRigidBodies(batch.size(), - batch.bodyKeyMostSignificantBits(), - batch.bodyKeyLeastSignificantBits(), - batch.spaceId(), - batch.shape(), - batch.mass(), - batch.bodyType(), - batch.positions(), - batch.settings(), - batch.kind(), - batch.persistenceMode()); - } - - private void dispatchJoint(int index, - @Nonnull PhysicsCommandOperations operations) { - createJoint(operations.requiredObjectAt(index, - PhysicsCommandOperations.CREATE_JOINT_JOINT_KEY_OBJECT_SLOT, - JointKey.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.CREATE_JOINT_SPACE_ID_OBJECT_SLOT, - SpaceId.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.CREATE_JOINT_BODY_A_OBJECT_SLOT, - RigidBodyKey.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.CREATE_JOINT_BODY_B_OBJECT_SLOT, - RigidBodyKey.class), - operations.requiredObjectAt(index, - PhysicsCommandOperations.CREATE_JOINT_TYPE_OBJECT_SLOT, - JointType.class), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_ANCHOR_A_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_ANCHOR_A_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_ANCHOR_A_Z_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_ANCHOR_B_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_ANCHOR_B_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_ANCHOR_B_Z_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_AXIS_X_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_AXIS_Y_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_AXIS_Z_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_REST_LENGTH_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_STIFFNESS_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_DAMPING_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_LOWER_LIMIT_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_UPPER_LIMIT_FLOAT_SLOT), - (operations.flags(index) & PhysicsCommandOperations.FLAG_MOTOR_ENABLED) != 0, - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_MOTOR_TARGET_VELOCITY_FLOAT_SLOT), - operations.floatAt(index, PhysicsCommandOperations.CREATE_JOINT_MOTOR_MAX_FORCE_FLOAT_SLOT)); - } - - @Override - public void spawnRigidBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - if (runtime.getRegistration(bodyKey) != null) { - throw new IllegalArgumentException("Rigid body key=" + bodyKey - + " is already registered"); - } - PhysicsSpaceBinding space = requireSpace(spaceId); - spawnRigidBody(space, - bodyKey, - spaceId, - shape, - mass, - bodyType, - positionX, - positionY, - positionZ, - settings, - kind, - persistenceMode); - } - - @Override - public void spawnRigidBodies(int bodyCount, - @Nonnull long[] bodyKeyMostSignificantBits, - @Nonnull long[] bodyKeyLeastSignificantBits, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull float[] positions, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - PhysicsSpaceBinding space = requireSpace(spaceId); - for (int index = 0; index < bodyCount; index++) { - RigidBodyKey bodyKey = RigidBodyKey.of(bodyKeyMostSignificantBits[index], - bodyKeyLeastSignificantBits[index]); - if (runtime.getRegistration(bodyKey) != null) { - throw new IllegalArgumentException("Rigid body key=" + bodyKey - + " is already registered"); - } - int positionOffset = index * 3; - spawnRigidBody(space, - bodyKey, - spaceId, - shape, - mass, - bodyType, - positions[positionOffset], - positions[positionOffset + 1], - positions[positionOffset + 2], - settings, - kind, - persistenceMode); - } - } - - private void spawnRigidBody(@Nonnull PhysicsSpaceBinding space, - @Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - BackendBodyHandle backendBodyHandle = createRuntimeBody(space, - shape, - mass, - bodyType, - positionX, - positionY, - positionZ); - applySpawnSettings(space, backendBodyHandle, settings); - runtime.addBodyOnOwner(bodyKey, - spaceId, - backendBodyHandle, - kind, - persistenceMode); - } - - private void applySpawnSettings(@Nonnull PhysicsSpaceBinding space, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull RigidBodySpawnSettings settings) { - long backendBodyId = backendBodyHandle.value(); - if (settings.hasFriction()) { - space.runtime().setBodyFriction(space.backendSpaceHandle().value(), backendBodyId, settings.friction()); - } - if (settings.hasRestitution()) { - space.runtime().setBodyRestitution(space.backendSpaceHandle().value(), backendBodyId, settings.restitution()); - } - if (settings.hasLinearDamping()) { - space.runtime().setBodyDamping(space.backendSpaceHandle().value(), - backendBodyId, - settings.linearDamping(), - settings.hasAngularDamping() ? settings.angularDamping() : 0.0f); - } else if (settings.hasAngularDamping()) { - space.runtime().setBodyDamping(space.backendSpaceHandle().value(), backendBodyId, 0.0f, settings.angularDamping()); - } - if (settings.hasCollisionFilter()) { - space.runtime() - .setBodyCollisionFilter(space.backendSpaceHandle().value(), - backendBodyId, - settings.collisionGroup(), - settings.collisionMask()); - } - if (settings.hasSensor()) { - space.runtime().setBodySensor(space.backendSpaceHandle().value(), backendBodyId, settings.sensor()); - } - } - - @Nonnull - private BackendBodyHandle createRuntimeBody(@Nonnull PhysicsSpaceBinding space, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ) { - long backendBodyId = space.runtime().createBody(space.backendSpaceHandle().value(), - BackendRuntimeCodes.shapeTypeCode(shape.type()), - shape.halfExtentX(), - shape.halfExtentY(), - shape.halfExtentZ(), - shape.radius(), - shape.halfHeight(), - BackendRuntimeCodes.axisCode(shape.axis()), - shape.groundY(), - mass, - BackendRuntimeCodes.bodyTypeCode(bodyType), - positionX, - positionY, - positionZ, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - return new BackendBodyHandle(backendBodyId); - } - - @Override - public void destroyRigidBody(@Nonnull RigidBodyKey bodyKey) { - runtime.destroyBody(bodyKey); - } - - @Override - public void setSpaceGravity(@Nonnull SpaceId spaceId, - float x, - float y, - float z) { - PhysicsSpaceBinding space = requireSpace(spaceId); - space.runtime().setGravity(space.backendSpaceHandle().value(), x, y, z); - } - - @Override - public void setRigidBodyTransform(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate) { - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - space.runtime().setBodyTransform(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW); - if (activate) { - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - } - - @Override - public void setRigidBodyPosition(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - boolean activate) { - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - space.runtime().setBodyPosition(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - positionX, - positionY, - positionZ); - if (activate) { - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - } - - @Override - public void setRigidBodyVelocity(@Nonnull RigidBodyKey bodyKey, - float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate) { - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - space.runtime().setBodyVelocity(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - linearX, - linearY, - linearZ, - angularX, - angularY, - angularZ); - if (activate) { - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - } - - @Override - public void setRigidBodyType(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType, - boolean activate) { - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - space.runtime().setBodyType(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - BackendRuntimeCodes.bodyTypeCode(bodyType)); - if (activate) { - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - } - - @Override - public void activateRigidBody(@Nonnull RigidBodyKey bodyKey) { - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - - @Override - public void applyRigidBodyImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - space.runtime().applyBodyImpulse(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - x, - y, - z, - hasOffset, - offsetX, - offsetY, - offsetZ, - torque); - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - - @Override - public void applyRigidBodyForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); - PhysicsSpaceBinding space = requireSpace(registration.spaceId()); - space.runtime().applyBodyForce(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - x, - y, - z, - hasOffset, - offsetX, - offsetY, - offsetZ, - torque); - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - - @Override - public void createJoint(@Nonnull JointKey jointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyAKey, - @Nonnull RigidBodyKey bodyBKey, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - PhysicsSpaceBinding space = requireSpace(spaceId); - PhysicsBodyRegistration bodyA = requireBodyRegistration(bodyAKey); - PhysicsBodyRegistration bodyB = requireBodyRegistration(bodyBKey); - if (!bodyA.spaceId().equals(spaceId) || !bodyB.spaceId().equals(spaceId)) { - throw new IllegalArgumentException("Joint bodies must both be registered in space " + spaceId); - } - BackendJointHandle backendJointHandle = new BackendJointHandle(space.runtime().createJoint( - space.backendSpaceHandle().value(), - toRuntimeJointTypeCode(type), - bodyA.backendBodyHandle().value(), - bodyB.backendBodyHandle().value(), - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce)); - runtime.addJointOnOwner(jointKey, - spaceId, - backendJointHandle, - bodyAKey, - bodyBKey, - type, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce); - } - - @Override - public void destroyJoint(@Nonnull JointKey jointKey) { - runtime.removeJoint(jointKey); - } - - @Override - public void destroyJointBetween(@Nullable JointKey preferredJointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB) { - if (preferredJointKey != null && runtime.removeJoint(preferredJointKey)) { - return; - } - PhysicsJointRegistration registration = runtime.findJointBetween(spaceId, bodyA, bodyB); - if (registration != null) { - runtime.removeJoint(registration.jointKey()); - } - } - - @Nonnull - private PhysicsSpaceBinding requireSpace(@Nonnull SpaceId spaceId) { - PhysicsSpaceBinding space = runtime.getSpaceBinding(spaceId); - if (space == null) { - throw new IllegalArgumentException("Physics space id=" + spaceId + " is not registered"); - } - return space; - } - - @Nonnull - private PhysicsBodyRegistration requireBodyRegistration(@Nonnull RigidBodyKey bodyKey) { - PhysicsBodyRegistration registration = runtime.getRegistration(bodyKey); - if (registration == null) { - throw new IllegalArgumentException("Rigid body key=" + bodyKey + " is not registered"); - } - return registration; - } - - private static int toRuntimeJointTypeCode(@Nonnull JointType type) { - return switch (type) { - case FIXED -> BackendRuntimeCodes.JOINT_FIXED; - case POINT -> BackendRuntimeCodes.JOINT_POINT; - case HINGE -> BackendRuntimeCodes.JOINT_HINGE; - case SLIDER -> BackendRuntimeCodes.JOINT_SLIDER; - case SPRING -> BackendRuntimeCodes.JOINT_SPRING; - }; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/RecordedBodyCreationKeys.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/RecordedBodyCreationKeys.java deleted file mode 100644 index 25d353cf..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/RecordedBodyCreationKeys.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import java.util.Arrays; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Frozen created-body key metadata for one recorded command batch. - */ -public final class RecordedBodyCreationKeys { - - private static final RecordedBodyCreationKeys EMPTY = - new RecordedBodyCreationKeys(new long[0], new long[0], 0); - - @Nonnull - private final long[] mostSignificantBits; - @Nonnull - private final long[] leastSignificantBits; - private final int size; - - private RecordedBodyCreationKeys(@Nonnull long[] mostSignificantBits, - @Nonnull long[] leastSignificantBits, - int size) { - this.mostSignificantBits = mostSignificantBits; - this.leastSignificantBits = leastSignificantBits; - this.size = size; - } - - @Nonnull - public static RecordedBodyCreationKeys empty() { - return EMPTY; - } - - @Nonnull - public static Builder builder() { - return new Builder(); - } - - public boolean isEmpty() { - return size == 0; - } - - public int size() { - return size; - } - - @Nonnull - public RigidBodyKey bodyKey(int index) { - checkIndex(index); - return RigidBodyKey.of(mostSignificantBits[index], leastSignificantBits[index]); - } - - @Nullable - public RigidBodyKey singleBodyKey() { - return size == 1 ? bodyKey(0) : null; - } - - private void checkIndex(int index) { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(index); - } - } - - public static final class Builder { - - private long[] mostSignificantBits = new long[4]; - private long[] leastSignificantBits = new long[4]; - private int size; - - public void add(@Nonnull RigidBodyKey bodyKey) { - add(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); - } - - void add(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits) { - ensureCapacity(size + 1); - mostSignificantBits[size] = bodyKeyMostSignificantBits; - leastSignificantBits[size] = bodyKeyLeastSignificantBits; - size++; - } - - public void addAll(@Nonnull long[] bodyKeyMostSignificantBits, - @Nonnull long[] bodyKeyLeastSignificantBits, - int count) { - if (count <= 0) { - return; - } - ensureCapacity(size + count); - System.arraycopy(bodyKeyMostSignificantBits, 0, mostSignificantBits, size, count); - System.arraycopy(bodyKeyLeastSignificantBits, 0, leastSignificantBits, size, count); - size += count; - } - - @Nonnull - public RecordedBodyCreationKeys build() { - if (size == 0) { - return empty(); - } - return new RecordedBodyCreationKeys( - mostSignificantBits.length == size - ? mostSignificantBits - : Arrays.copyOf(mostSignificantBits, size), - leastSignificantBits.length == size - ? leastSignificantBits - : Arrays.copyOf(leastSignificantBits, size), - size); - } - - private void ensureCapacity(int required) { - if (required <= mostSignificantBits.length) { - return; - } - int nextCapacity = Math.max(required, - mostSignificantBits.length + (mostSignificantBits.length >> 1)); - mostSignificantBits = Arrays.copyOf(mostSignificantBits, nextCapacity); - leastSignificantBits = Arrays.copyOf(leastSignificantBits, nextCapacity); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RecordedPhysicsCommandBatch.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RecordedPhysicsCommandBatch.java deleted file mode 100644 index dc6e318e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RecordedPhysicsCommandBatch.java +++ /dev/null @@ -1,114 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.batch; - -import dev.hytalemodding.impulse.core.internal.simulation.PhysicsCommandOperations; -import dev.hytalemodding.impulse.core.internal.simulation.RecordedBodyCreationKeys; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandBatch; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandMetadata; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Internal frozen command payload plus the public metadata view returned to plugins. - */ -public final class RecordedPhysicsCommandBatch { - - @Nonnull - private final PhysicsCommandMetadata metadata; - private final long commandWorldEpoch; - @Nonnull - private final PhysicsCommandOperations operations; - @Nonnull - private final PhysicsCommandBatch publicBatch; - @Nonnull - private final RecordedBodyCreationKeys bodyCreationKeys; - private final int bodyKeyReferenceCount; - @Nullable - private final RigidBodyKey firstBodyKey; - private final int jointKeyReferenceCount; - @Nullable - private final JointKey firstJointKey; - - public RecordedPhysicsCommandBatch(@Nonnull PhysicsCommandMetadata metadata, - long commandWorldEpoch, - @Nonnull PhysicsCommandOperations operations) { - this.metadata = Objects.requireNonNull(metadata, "metadata"); - this.commandWorldEpoch = Math.max(0L, commandWorldEpoch); - this.operations = Objects.requireNonNull(operations, "operations"); - this.publicBatch = new PhysicsCommandBatch(metadata, operations.size()); - this.bodyCreationKeys = operations.bodyCreationKeys(); - PhysicsCommandOperations.EntityReferences references = operations.entityReferences(); - this.bodyKeyReferenceCount = references.bodyKeyReferenceCount(); - this.firstBodyKey = references.firstBodyKey(); - this.jointKeyReferenceCount = references.jointKeyReferenceCount(); - this.firstJointKey = references.firstJointKey(); - } - - @Nonnull - public PhysicsCommandMetadata metadata() { - return metadata; - } - - public long commandWorldEpoch() { - return commandWorldEpoch; - } - - public int commandCount() { - return operations.size(); - } - - @Nonnull - public PhysicsCommandOperations operations() { - return operations; - } - - @Nonnull - public PhysicsCommandBatch publicBatch() { - return publicBatch; - } - - /** - * Returns whether the frozen batch can create rigid bodies. - */ - public boolean hasBodyCreationCommands() { - return !bodyCreationKeys.isEmpty(); - } - - /** - * Returns the exact created body key when this batch creates exactly one rigid body. - */ - @Nullable - public RigidBodyKey singleSpawnBodyKey() { - return bodyCreationKeys.singleBodyKey(); - } - - public int bodyCreationKeyCount() { - return bodyCreationKeys.size(); - } - - @Nonnull - public RigidBodyKey bodyCreationKey(int index) { - return bodyCreationKeys.bodyKey(index); - } - - public int bodyKeyReferenceCount() { - return bodyKeyReferenceCount; - } - - @Nullable - public RigidBodyKey firstBodyKey() { - return firstBodyKey; - } - - public int jointKeyReferenceCount() { - return jointKeyReferenceCount; - } - - @Nullable - public JointKey firstJointKey() { - return firstJointKey; - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnBatch.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnBatch.java deleted file mode 100644 index fb1f5b63..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnBatch.java +++ /dev/null @@ -1,222 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.batch; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandContext; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.Arrays; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Packed execution encoding for repeated rigid body spawns. - * - *

This is an internal recorded-command representation. Plugin code should record - * through {@link PhysicsCommandContext#spawnBodies} instead of depending on this type.

- */ -public final class RigidBodySpawnBatch { - - private static final int OBJECT_STRIDE = 6; - private static final int FLOAT_STRIDE = 4; - - private long[] bodyKeyMostSignificantBits; - private long[] bodyKeyLeastSignificantBits; - private Object[] objects; - private float[] floats; - private int size; - - public RigidBodySpawnBatch(int expectedBodies) { - int capacity = Math.max(1, expectedBodies); - bodyKeyMostSignificantBits = new long[capacity]; - bodyKeyLeastSignificantBits = new long[capacity]; - objects = new Object[capacity * OBJECT_STRIDE]; - floats = new float[capacity * FLOAT_STRIDE]; - } - - private RigidBodySpawnBatch(@Nonnull long[] bodyKeyMostSignificantBits, - @Nonnull long[] bodyKeyLeastSignificantBits, - @Nonnull Object[] objects, - @Nonnull float[] floats, - int size) { - this.bodyKeyMostSignificantBits = bodyKeyMostSignificantBits; - this.bodyKeyLeastSignificantBits = bodyKeyLeastSignificantBits; - this.objects = objects; - this.floats = floats; - this.size = size; - } - - public void add(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - Objects.requireNonNull(bodyKey, "bodyKey"); - add(bodyKey.mostSignificantBits(), - bodyKey.leastSignificantBits(), - spaceId, - shape, - mass, - bodyType, - positionX, - positionY, - positionZ, - settings, - kind, - persistenceMode); - } - - public void add(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - ensureCapacity(size + 1); - this.bodyKeyMostSignificantBits[size] = bodyKeyMostSignificantBits; - this.bodyKeyLeastSignificantBits[size] = bodyKeyLeastSignificantBits; - int objectOffset = size * OBJECT_STRIDE; - objects[objectOffset] = Objects.requireNonNull(spaceId, "spaceId"); - objects[objectOffset + 1] = Objects.requireNonNull(shape, "shape"); - objects[objectOffset + 2] = Objects.requireNonNull(bodyType, "bodyType"); - objects[objectOffset + 3] = Objects.requireNonNull(settings, "settings"); - objects[objectOffset + 4] = Objects.requireNonNull(kind, "kind"); - objects[objectOffset + 5] = Objects.requireNonNull(persistenceMode, "persistenceMode"); - - int floatOffset = size * FLOAT_STRIDE; - floats[floatOffset] = mass; - floats[floatOffset + 1] = positionX; - floats[floatOffset + 2] = positionY; - floats[floatOffset + 3] = positionZ; - size++; - } - - @Nonnull - public RigidBodySpawnBatch freeze() { - int objectSize = size * OBJECT_STRIDE; - int floatSize = size * FLOAT_STRIDE; - return new RigidBodySpawnBatch( - bodyKeyMostSignificantBits.length == size - ? bodyKeyMostSignificantBits - : Arrays.copyOf(bodyKeyMostSignificantBits, size), - bodyKeyLeastSignificantBits.length == size - ? bodyKeyLeastSignificantBits - : Arrays.copyOf(bodyKeyLeastSignificantBits, size), - objects.length == objectSize ? objects : Arrays.copyOf(objects, objectSize), - floats.length == floatSize ? floats : Arrays.copyOf(floats, floatSize), - size); - } - - public int size() { - return size; - } - - @Nonnull - public RigidBodyKey bodyKey(int index) { - checkIndex(index); - return RigidBodyKey.of(bodyKeyMostSignificantBits[index], bodyKeyLeastSignificantBits[index]); - } - - @Nonnull - public long[] bodyKeyMostSignificantBits() { - return bodyKeyMostSignificantBits; - } - - @Nonnull - public long[] bodyKeyLeastSignificantBits() { - return bodyKeyLeastSignificantBits; - } - - @Nonnull - public SpaceId spaceId(int index) { - return object(index, 0, SpaceId.class); - } - - @Nonnull - public PhysicsShapeSpec shape(int index) { - return object(index, 1, PhysicsShapeSpec.class); - } - - @Nonnull - public PhysicsBodyType bodyType(int index) { - return object(index, 2, PhysicsBodyType.class); - } - - @Nonnull - public RigidBodySpawnSettings settings(int index) { - return object(index, 3, RigidBodySpawnSettings.class); - } - - @Nonnull - public PhysicsBodyKind kind(int index) { - return object(index, 4, PhysicsBodyKind.class); - } - - @Nonnull - public PhysicsBodyPersistenceMode persistenceMode(int index) { - return object(index, 5, PhysicsBodyPersistenceMode.class); - } - - public float mass(int index) { - return floatAt(index, 0); - } - - public float positionX(int index) { - return floatAt(index, 1); - } - - public float positionY(int index) { - return floatAt(index, 2); - } - - public float positionZ(int index) { - return floatAt(index, 3); - } - - private float floatAt(int index, - int slot) { - checkIndex(index); - return floats[index * FLOAT_STRIDE + slot]; - } - - @Nonnull - private T object(int index, - int slot, - @Nonnull Class type) { - checkIndex(index); - return type.cast(objects[index * OBJECT_STRIDE + slot]); - } - - private void checkIndex(int index) { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(index); - } - } - - private void ensureCapacity(int required) { - int capacity = objects.length / OBJECT_STRIDE; - if (required <= capacity) { - return; - } - int nextCapacity = Math.max(required, capacity + (capacity >> 1)); - bodyKeyMostSignificantBits = Arrays.copyOf(bodyKeyMostSignificantBits, nextCapacity); - bodyKeyLeastSignificantBits = Arrays.copyOf(bodyKeyLeastSignificantBits, nextCapacity); - objects = Arrays.copyOf(objects, nextCapacity * OBJECT_STRIDE); - floats = Arrays.copyOf(floats, nextCapacity * FLOAT_STRIDE); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnTemplateBatch.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnTemplateBatch.java deleted file mode 100644 index d3856f9e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/batch/RigidBodySpawnTemplateBatch.java +++ /dev/null @@ -1,224 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.batch; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.Arrays; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Packed execution encoding for repeated rigid body spawns that share spawn properties. - */ -public final class RigidBodySpawnTemplateBatch { - - private static final int POSITION_STRIDE = 3; - - @Nonnull - private final SpaceId spaceId; - @Nonnull - private final PhysicsShapeSpec shape; - private final float mass; - @Nonnull - private final PhysicsBodyType bodyType; - @Nonnull - private final RigidBodySpawnSettings settings; - @Nonnull - private final PhysicsBodyKind kind; - @Nonnull - private final PhysicsBodyPersistenceMode persistenceMode; - private long[] bodyKeyMostSignificantBits; - private long[] bodyKeyLeastSignificantBits; - private float[] positions; - private int size; - - public RigidBodySpawnTemplateBatch(int expectedBodies, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - int capacity = Math.max(1, expectedBodies); - this.spaceId = Objects.requireNonNull(spaceId, "spaceId"); - this.shape = Objects.requireNonNull(shape, "shape"); - this.mass = mass; - this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); - this.settings = Objects.requireNonNull(settings, "settings"); - this.kind = Objects.requireNonNull(kind, "kind"); - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); - bodyKeyMostSignificantBits = new long[capacity]; - bodyKeyLeastSignificantBits = new long[capacity]; - positions = new float[capacity * POSITION_STRIDE]; - } - - private RigidBodySpawnTemplateBatch(@Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull long[] bodyKeyMostSignificantBits, - @Nonnull long[] bodyKeyLeastSignificantBits, - @Nonnull float[] positions, - int size) { - this.spaceId = spaceId; - this.shape = shape; - this.mass = mass; - this.bodyType = bodyType; - this.settings = settings; - this.kind = kind; - this.persistenceMode = persistenceMode; - this.bodyKeyMostSignificantBits = bodyKeyMostSignificantBits; - this.bodyKeyLeastSignificantBits = bodyKeyLeastSignificantBits; - this.positions = positions; - this.size = size; - } - - public void add(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ) { - Objects.requireNonNull(bodyKey, "bodyKey"); - add(bodyKey.mostSignificantBits(), - bodyKey.leastSignificantBits(), - positionX, - positionY, - positionZ); - } - - public void add(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits, - float positionX, - float positionY, - float positionZ) { - ensureCapacity(size + 1); - this.bodyKeyMostSignificantBits[size] = bodyKeyMostSignificantBits; - this.bodyKeyLeastSignificantBits[size] = bodyKeyLeastSignificantBits; - int positionOffset = size * POSITION_STRIDE; - positions[positionOffset] = positionX; - positions[positionOffset + 1] = positionY; - positions[positionOffset + 2] = positionZ; - size++; - } - - @Nonnull - public RigidBodySpawnTemplateBatch freeze() { - int positionSize = size * POSITION_STRIDE; - return new RigidBodySpawnTemplateBatch(spaceId, - shape, - mass, - bodyType, - settings, - kind, - persistenceMode, - this.bodyKeyMostSignificantBits.length == size - ? this.bodyKeyMostSignificantBits - : Arrays.copyOf(this.bodyKeyMostSignificantBits, size), - this.bodyKeyLeastSignificantBits.length == size - ? this.bodyKeyLeastSignificantBits - : Arrays.copyOf(this.bodyKeyLeastSignificantBits, size), - positions.length == positionSize ? positions : Arrays.copyOf(positions, positionSize), - size); - } - - public int size() { - return size; - } - - @Nonnull - public SpaceId spaceId() { - return spaceId; - } - - @Nonnull - public PhysicsShapeSpec shape() { - return shape; - } - - public float mass() { - return mass; - } - - @Nonnull - public PhysicsBodyType bodyType() { - return bodyType; - } - - @Nonnull - public RigidBodySpawnSettings settings() { - return settings; - } - - @Nonnull - public PhysicsBodyKind kind() { - return kind; - } - - @Nonnull - public PhysicsBodyPersistenceMode persistenceMode() { - return persistenceMode; - } - - @Nonnull - public RigidBodyKey bodyKey(int index) { - checkIndex(index); - return RigidBodyKey.of(bodyKeyMostSignificantBits[index], bodyKeyLeastSignificantBits[index]); - } - - @Nonnull - public long[] bodyKeyMostSignificantBits() { - return bodyKeyMostSignificantBits; - } - - @Nonnull - public long[] bodyKeyLeastSignificantBits() { - return bodyKeyLeastSignificantBits; - } - - @Nonnull - public float[] positions() { - return positions; - } - - public float positionX(int index) { - return positionAt(index, 0); - } - - public float positionY(int index) { - return positionAt(index, 1); - } - - public float positionZ(int index) { - return positionAt(index, 2); - } - - private float positionAt(int index, - int slot) { - checkIndex(index); - return positions[index * POSITION_STRIDE + slot]; - } - - private void ensureCapacity(int required) { - if (required <= bodyKeyMostSignificantBits.length) { - return; - } - int nextCapacity = Math.max(required, - bodyKeyMostSignificantBits.length + (bodyKeyMostSignificantBits.length >> 1)); - bodyKeyMostSignificantBits = Arrays.copyOf(bodyKeyMostSignificantBits, nextCapacity); - bodyKeyLeastSignificantBits = Arrays.copyOf(bodyKeyLeastSignificantBits, nextCapacity); - positions = Arrays.copyOf(positions, nextCapacity * POSITION_STRIDE); - } - - private void checkIndex(int index) { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(index); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableJointCommandRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableJointCommandRecorder.java deleted file mode 100644 index a56f3d7f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableJointCommandRecorder.java +++ /dev/null @@ -1,352 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.recorder; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.JointCommandRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Fluent recorder for one joint creation command. - */ -public final class MutableJointCommandRecorder implements JointCommandRecorder { - - @Nonnull - private final MutablePhysicsCommandContext recorder; - @Nonnull - private final JointKey jointKey; - private SpaceId spaceId; - private RigidBodyKey bodyA; - private RigidBodyKey bodyB; - private JointType type; - private float anchorAX; - private float anchorAY; - private float anchorAZ; - private float anchorBX; - private float anchorBY; - private float anchorBZ; - private float axisX; - private float axisY; - private float axisZ; - private float restLength; - private float stiffness; - private float damping; - private float lowerLimit; - private float upperLimit; - private boolean motorEnabled; - private float motorTargetVelocity; - private float motorMaxForce; - private boolean sealed; - - MutableJointCommandRecorder(@Nonnull MutablePhysicsCommandContext recorder, - @Nonnull JointKey jointKey) { - this.recorder = Objects.requireNonNull(recorder, "recorder"); - this.jointKey = Objects.requireNonNull(jointKey, "jointKey"); - } - - @Nonnull - @Override - public JointCommandRecorder space(@Nonnull SpaceId spaceId) { - assertOpen(); - this.spaceId = Objects.requireNonNull(spaceId, "spaceId"); - return this; - } - - @Nonnull - @Override - public JointCommandRecorder bodies(@Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB) { - assertOpen(); - this.bodyA = Objects.requireNonNull(bodyA, "bodyA"); - this.bodyB = Objects.requireNonNull(bodyB, "bodyB"); - return this; - } - - @Nonnull - @Override - public JointCommandRecorder fixed(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - Objects.requireNonNull(anchorA, "anchorA"); - Objects.requireNonNull(anchorB, "anchorB"); - return fixed(anchorA.x, anchorA.y, anchorA.z, anchorB.x, anchorB.y, anchorB.z); - } - - @Nonnull - @Override - public JointCommandRecorder fixed(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ) { - return anchors(JointType.FIXED, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - 0.0f, - 0.0f, - 0.0f); - } - - @Nonnull - @Override - public JointCommandRecorder point(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - Objects.requireNonNull(anchorA, "anchorA"); - Objects.requireNonNull(anchorB, "anchorB"); - return point(anchorA.x, anchorA.y, anchorA.z, anchorB.x, anchorB.y, anchorB.z); - } - - @Nonnull - @Override - public JointCommandRecorder point(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ) { - return anchors(JointType.POINT, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - 0.0f, - 0.0f, - 0.0f); - } - - @Nonnull - @Override - public JointCommandRecorder hinge(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - Objects.requireNonNull(anchorA, "anchorA"); - Objects.requireNonNull(anchorB, "anchorB"); - Objects.requireNonNull(axis, "axis"); - return hinge(anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z); - } - - @Nonnull - @Override - public JointCommandRecorder hinge(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ) { - return anchors(JointType.HINGE, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ); - } - - @Nonnull - @Override - public JointCommandRecorder slider(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - Objects.requireNonNull(anchorA, "anchorA"); - Objects.requireNonNull(anchorB, "anchorB"); - Objects.requireNonNull(axis, "axis"); - return slider(anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z); - } - - @Nonnull - @Override - public JointCommandRecorder slider(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ) { - return anchors(JointType.SLIDER, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ); - } - - @Nonnull - @Override - public JointCommandRecorder spring(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping) { - Objects.requireNonNull(anchorA, "anchorA"); - Objects.requireNonNull(anchorB, "anchorB"); - spring(anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - restLength, - stiffness, - damping); - return this; - } - - @Nonnull - @Override - public JointCommandRecorder spring(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float restLength, - float stiffness, - float damping) { - anchors(JointType.SPRING, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - 0.0f, - 0.0f, - 0.0f); - this.restLength = restLength; - this.stiffness = stiffness; - this.damping = damping; - return this; - } - - @Nonnull - @Override - public JointCommandRecorder limits(float lowerLimit, - float upperLimit) { - assertOpen(); - this.lowerLimit = lowerLimit; - this.upperLimit = upperLimit; - return this; - } - - @Nonnull - @Override - public JointCommandRecorder motor(float targetVelocity, - float maxForce) { - assertOpen(); - this.motorEnabled = true; - this.motorTargetVelocity = targetVelocity; - this.motorMaxForce = maxForce; - return this; - } - - void record() { - assertOpen(); - validate(); - recorder.recordJoint(jointKey, - spaceId, - bodyA, - bodyB, - type, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce); - } - - void validate() { - if (spaceId == null) { - throw new IllegalStateException("Joint command requires a physics space"); - } - if (bodyA == null || bodyB == null) { - throw new IllegalStateException("Joint command requires both rigid bodies"); - } - if (type == null) { - throw new IllegalStateException("Joint command requires a joint type"); - } - } - - @Nonnull - private JointCommandRecorder anchors(@Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ) { - assertOpen(); - this.type = type; - this.anchorAX = anchorAX; - this.anchorAY = anchorAY; - this.anchorAZ = anchorAZ; - this.anchorBX = anchorBX; - this.anchorBY = anchorBY; - this.anchorBZ = anchorBZ; - this.axisX = axisX; - this.axisY = axisY; - this.axisZ = axisZ; - return this; - } - - void seal() { - sealed = true; - } - - private void assertOpen() { - if (sealed) { - throw new IllegalStateException("Joint command recorder is no longer active"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutablePhysicsCommandContext.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutablePhysicsCommandContext.java deleted file mode 100644 index 21492c60..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutablePhysicsCommandContext.java +++ /dev/null @@ -1,588 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.PhysicsCommandOperations; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnBatch; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnTemplateBatch; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.JointCommandRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandBatch; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandContext; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandMetadata; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandRecipe; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodyCommandRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnBatchRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnTemplateRecorder; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Mutable recorder for physics simulation commands. - * - *

Mutable contexts are short-lived. Record commands during an ECS/server tick, then - * submit the context to freeze it into an immutable {@link PhysicsCommandBatch}.

- */ -public final class MutablePhysicsCommandContext implements PhysicsCommandContext { - - private static final int DEFAULT_EXPECTED_OPERATIONS = 8; - - private final long submittedServerTick; - private final long worldEpoch; - private final PhysicsCommandOperations operations; - private final List pendingSpawns = new ArrayList<>(); - private final List pendingJoints = new ArrayList<>(); - private boolean frozen; - - public MutablePhysicsCommandContext(long submittedServerTick, long worldEpoch) { - this(submittedServerTick, worldEpoch, DEFAULT_EXPECTED_OPERATIONS); - } - - public MutablePhysicsCommandContext(long submittedServerTick, - long worldEpoch, - int expectedOperations) { - this.submittedServerTick = Math.max(0L, submittedServerTick); - this.worldEpoch = Math.max(0L, worldEpoch); - this.operations = new PhysicsCommandOperations(expectedOperations); - } - - @Nonnull - @Override - public PhysicsCommandContext compose(@Nonnull PhysicsCommandRecipe recipe) { - Objects.requireNonNull(recipe, "recipe").record(this); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder body(@Nonnull RigidBodyKey bodyKey) { - return new MutableRigidBodyCommandRecorder(this, bodyKey); - } - - @Nonnull - @Override - public PhysicsCommandContext body(@Nonnull RigidBodyKey bodyKey, - @Nonnull Consumer recipe) { - MutableRigidBodyCommandRecorder body = new MutableRigidBodyCommandRecorder(this, bodyKey); - try { - Objects.requireNonNull(recipe, "recipe").accept(body); - } finally { - body.seal(); - } - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext setSpaceGravity(@Nonnull SpaceId spaceId, - float x, - float y, - float z) { - recordSetSpaceGravity(spaceId, x, y, z); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext setBodyTransform(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate) { - recordSetTransform(bodyKey, - positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - activate); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext setBodyVelocity(@Nonnull RigidBodyKey bodyKey, - float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate) { - recordSetVelocity(bodyKey, linearX, linearY, linearZ, angularX, angularY, angularZ, activate); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext setBodyPosition(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - boolean activate) { - recordSetPosition(bodyKey, positionX, positionY, positionZ, activate); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext setBodyType(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType, - boolean activate) { - recordSetType(bodyKey, bodyType, activate); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext activateBody(@Nonnull RigidBodyKey bodyKey) { - recordActivate(bodyKey); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext applyBodyImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - recordImpulse(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, false); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext applyBodyImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - recordImpulse(bodyKey, x, y, z, true, offsetX, offsetY, offsetZ, false); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext applyBodyTorqueImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - recordImpulse(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, true); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext applyBodyForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - recordForce(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, false); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext applyBodyForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - recordForce(bodyKey, x, y, z, true, offsetX, offsetY, offsetZ, false); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext applyBodyTorque(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - recordForce(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, true); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext destroyBody(@Nonnull RigidBodyKey bodyKey) { - recordDestroyBody(bodyKey); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder spawnBody(@Nonnull RigidBodyKey bodyKey) { - assertMutable(); - MutableRigidBodySpawnRecorder spawn = new MutableRigidBodySpawnRecorder(this::recordSpawn, bodyKey); - pendingSpawns.add(spawn); - return spawn; - } - - @Nonnull - @Override - public PhysicsCommandContext spawnBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull Consumer recipe) { - MutableRigidBodySpawnRecorder spawn = - new MutableRigidBodySpawnRecorder(this::recordSpawn, bodyKey); - try { - Objects.requireNonNull(recipe, "recipe").accept(spawn); - spawn.record(); - } finally { - spawn.seal(); - } - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext spawnBodies( - @Nonnull Consumer recipe) { - return spawnBodies(DEFAULT_EXPECTED_OPERATIONS, recipe); - } - - @Nonnull - @Override - public PhysicsCommandContext spawnBodies(int expectedBodies, - @Nonnull Consumer recipe) { - MutableRigidBodySpawnBatchRecorder spawns = - new MutableRigidBodySpawnBatchRecorder(expectedBodies); - try { - Objects.requireNonNull(recipe, "recipe").accept(spawns); - } finally { - spawns.seal(); - } - if (!spawns.isEmpty()) { - recordSpawnBatch(spawns.spawns()); - } - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext spawnBodies(int expectedBodies, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull Consumer recipe) { - MutableRigidBodySpawnTemplateRecorder spawns = new MutableRigidBodySpawnTemplateRecorder(expectedBodies, - spaceId, - shape, - mass, - bodyType, - settings, - kind, - persistenceMode); - try { - Objects.requireNonNull(recipe, "recipe").accept(spawns); - } finally { - spawns.seal(); - } - if (!spawns.isEmpty()) { - recordSpawnTemplateBatch(spawns.spawns()); - } - return this; - } - - @Nonnull - @Override - public JointCommandRecorder joint(@Nonnull JointKey jointKey) { - assertMutable(); - MutableJointCommandRecorder joint = new MutableJointCommandRecorder(this, jointKey); - pendingJoints.add(joint); - return joint; - } - - @Nonnull - @Override - public PhysicsCommandContext joint(@Nonnull JointKey jointKey, - @Nonnull Consumer recipe) { - MutableJointCommandRecorder joint = new MutableJointCommandRecorder(this, jointKey); - try { - Objects.requireNonNull(recipe, "recipe").accept(joint); - joint.record(); - } finally { - joint.seal(); - } - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext destroyJoint(@Nonnull JointKey jointKey) { - recordDestroyJoint(jointKey); - return this; - } - - @Nonnull - @Override - public PhysicsCommandContext destroyJointBetween(@Nullable JointKey preferredJointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB) { - recordDestroyJointBetween(preferredJointKey, spaceId, bodyA, bodyB); - return this; - } - - @Nonnull - public RecordedPhysicsCommandBatch freezeInternal(long commandBatchSequence) { - if (frozen) { - throw new IllegalStateException("Physics command context is already frozen"); - } - recordPendingRecorders(); - frozen = true; - return new RecordedPhysicsCommandBatch( - new PhysicsCommandMetadata(submittedServerTick, commandBatchSequence), - worldEpoch, - operations.freeze()); - } - - public boolean isEmpty() { - return operations.size() == 0 && pendingSpawns.isEmpty() && pendingJoints.isEmpty(); - } - - private void recordPendingRecorders() { - try { - for (MutableRigidBodySpawnRecorder spawn : pendingSpawns) { - spawn.validate(); - } - for (MutableJointCommandRecorder joint : pendingJoints) { - joint.validate(); - } - for (MutableRigidBodySpawnRecorder spawn : pendingSpawns) { - spawn.record(); - } - for (MutableJointCommandRecorder joint : pendingJoints) { - joint.record(); - } - } finally { - for (MutableRigidBodySpawnRecorder spawn : pendingSpawns) { - spawn.seal(); - } - for (MutableJointCommandRecorder joint : pendingJoints) { - joint.seal(); - } - pendingSpawns.clear(); - pendingJoints.clear(); - } - } - - void recordSpawn(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - assertMutable(); - operations.addSpawn(bodyKey, - spaceId, - shape, - mass, - bodyType, - positionX, - positionY, - positionZ, - settings, - kind, - persistenceMode); - } - - void recordSpawnBatch(@Nonnull RigidBodySpawnBatch spawns) { - assertMutable(); - operations.addSpawnBatch(spawns); - } - - void recordSpawnTemplateBatch(@Nonnull RigidBodySpawnTemplateBatch spawns) { - assertMutable(); - operations.addSpawnTemplateBatch(spawns); - } - - void recordDestroyBody(@Nonnull RigidBodyKey bodyKey) { - assertMutable(); - operations.addDestroyBody(bodyKey); - } - - void recordSetSpaceGravity(@Nonnull SpaceId spaceId, - float x, - float y, - float z) { - assertMutable(); - operations.addSetSpaceGravity(spaceId, x, y, z); - } - - void recordSetTransform(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate) { - assertMutable(); - operations.addSetTransform(bodyKey, - positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - activate); - } - - void recordSetVelocity(@Nonnull RigidBodyKey bodyKey, - float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate) { - assertMutable(); - operations.addSetVelocity(bodyKey, linearX, linearY, linearZ, angularX, angularY, angularZ, activate); - } - - void recordSetPosition(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - boolean activate) { - assertMutable(); - operations.addSetPosition(bodyKey, positionX, positionY, positionZ, activate); - } - - void recordSetType(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType, - boolean activate) { - assertMutable(); - operations.addSetType(bodyKey, bodyType, activate); - } - - void recordActivate(@Nonnull RigidBodyKey bodyKey) { - assertMutable(); - operations.addActivate(bodyKey); - } - - void recordImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - assertMutable(); - operations.addImpulse(bodyKey, x, y, z, hasOffset, offsetX, offsetY, offsetZ, torque); - } - - void recordForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - assertMutable(); - operations.addForce(bodyKey, x, y, z, hasOffset, offsetX, offsetY, offsetZ, torque); - } - - void recordJoint(@Nonnull JointKey jointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - assertMutable(); - operations.addJoint(jointKey, - spaceId, - bodyA, - bodyB, - type, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce); - } - - void recordDestroyJoint(@Nonnull JointKey jointKey) { - assertMutable(); - operations.addDestroyJoint(jointKey); - } - - void recordDestroyJointBetween(@Nullable JointKey preferredJointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB) { - assertMutable(); - operations.addDestroyJointBetween(preferredJointKey, spaceId, bodyA, bodyB); - } - - private void assertMutable() { - if (frozen) { - throw new IllegalStateException("Physics command context is already frozen"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodyCommandRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodyCommandRecorder.java deleted file mode 100644 index 94692876..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodyCommandRecorder.java +++ /dev/null @@ -1,296 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodyCommandRecorder; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Fluent recorder for operations targeting one rigid body key. - */ -public final class MutableRigidBodyCommandRecorder implements RigidBodyCommandRecorder { - - @Nonnull - private final MutablePhysicsCommandContext recorder; - @Nonnull - private final RigidBodyKey bodyKey; - private boolean sealed; - - MutableRigidBodyCommandRecorder(@Nonnull MutablePhysicsCommandContext recorder, - @Nonnull RigidBodyKey bodyKey) { - this.recorder = Objects.requireNonNull(recorder, "recorder"); - this.bodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setTransform(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - return setTransform(position, rotation, false); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setTransform(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - boolean activate) { - Objects.requireNonNull(position, "position"); - Objects.requireNonNull(rotation, "rotation"); - return setTransform(position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - activate); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setTransform(float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate) { - assertOpen(); - recorder.recordSetTransform(bodyKey, - positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - activate); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setPosition(@Nonnull Vector3f position) { - return setPosition(position, false); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setPosition(@Nonnull Vector3f position, - boolean activate) { - Objects.requireNonNull(position, "position"); - return setPosition(position.x, position.y, position.z, activate); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setPosition(float positionX, - float positionY, - float positionZ, - boolean activate) { - assertOpen(); - recorder.recordSetPosition(bodyKey, positionX, positionY, positionZ, activate); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setVelocity(@Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - return setVelocity(linearVelocity, angularVelocity, false); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setVelocity(@Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean activate) { - Objects.requireNonNull(linearVelocity, "linearVelocity"); - Objects.requireNonNull(angularVelocity, "angularVelocity"); - return setVelocity(linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z, - activate); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setVelocity(float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate) { - assertOpen(); - recorder.recordSetVelocity(bodyKey, - linearX, - linearY, - linearZ, - angularX, - angularY, - angularZ, - activate); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setType(@Nonnull PhysicsBodyType bodyType) { - return setType(bodyType, false); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder setType(@Nonnull PhysicsBodyType bodyType, - boolean activate) { - assertOpen(); - recorder.recordSetType(bodyKey, bodyType, activate); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder activate() { - assertOpen(); - recorder.recordActivate(bodyKey); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyImpulse(@Nonnull Vector3f impulse) { - Objects.requireNonNull(impulse, "impulse"); - return applyImpulse(impulse.x, impulse.y, impulse.z); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyImpulse(float x, - float y, - float z) { - assertOpen(); - recorder.recordImpulse(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, false); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyImpulse(@Nonnull Vector3f impulse, - @Nonnull Vector3f offset) { - Objects.requireNonNull(impulse, "impulse"); - Objects.requireNonNull(offset, "offset"); - return applyImpulse(impulse.x, impulse.y, impulse.z, offset.x, offset.y, offset.z); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyImpulse(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - assertOpen(); - recorder.recordImpulse(bodyKey, x, y, z, true, offsetX, offsetY, offsetZ, false); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyTorqueImpulse(@Nonnull Vector3f impulse) { - Objects.requireNonNull(impulse, "impulse"); - return applyTorqueImpulse(impulse.x, impulse.y, impulse.z); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyTorqueImpulse(float x, - float y, - float z) { - assertOpen(); - recorder.recordImpulse(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, true); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyForce(@Nonnull Vector3f force) { - Objects.requireNonNull(force, "force"); - return applyForce(force.x, force.y, force.z); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyForce(float x, - float y, - float z) { - assertOpen(); - recorder.recordForce(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, false); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyForce(@Nonnull Vector3f force, - @Nonnull Vector3f offset) { - Objects.requireNonNull(force, "force"); - Objects.requireNonNull(offset, "offset"); - return applyForce(force.x, force.y, force.z, offset.x, offset.y, offset.z); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyForce(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - assertOpen(); - recorder.recordForce(bodyKey, x, y, z, true, offsetX, offsetY, offsetZ, false); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyTorque(@Nonnull Vector3f force) { - Objects.requireNonNull(force, "force"); - return applyTorque(force.x, force.y, force.z); - } - - @Nonnull - @Override - public RigidBodyCommandRecorder applyTorque(float x, - float y, - float z) { - assertOpen(); - recorder.recordForce(bodyKey, x, y, z, false, 0.0f, 0.0f, 0.0f, true); - return this; - } - - @Nonnull - @Override - public RigidBodyCommandRecorder destroy() { - assertOpen(); - recorder.recordDestroyBody(bodyKey); - return this; - } - - void seal() { - sealed = true; - } - - private void assertOpen() { - if (sealed) { - throw new IllegalStateException("Rigid body command recorder is no longer active"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnBatchRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnBatchRecorder.java deleted file mode 100644 index 4f7b5911..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnBatchRecorder.java +++ /dev/null @@ -1,122 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnBatch; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnBatchRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.Objects; -import java.util.function.Consumer; -import javax.annotation.Nonnull; - -/** - * Fluent recorder for a bulk rigid body spawn command. - */ -public final class MutableRigidBodySpawnBatchRecorder implements RigidBodySpawnBatchRecorder { - - @Nonnull - private final RigidBodySpawnBatch spawns; - private boolean sealed; - - MutableRigidBodySpawnBatchRecorder(int expectedBodies) { - spawns = new RigidBodySpawnBatch(expectedBodies); - } - - @Nonnull - @Override - public RigidBodySpawnBatchRecorder body(@Nonnull RigidBodyKey bodyKey, - @Nonnull Consumer recipe) { - assertMutable(); - MutableRigidBodySpawnRecorder spawn = new MutableRigidBodySpawnRecorder(spawns::add, bodyKey); - try { - Objects.requireNonNull(recipe, "recipe").accept(spawn); - spawn.record(); - } finally { - spawn.seal(); - } - return this; - } - - @Nonnull - @Override - public RigidBodySpawnBatchRecorder body(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - assertMutable(); - Objects.requireNonNull(bodyKey, "bodyKey"); - return body(bodyKey.mostSignificantBits(), - bodyKey.leastSignificantBits(), - spaceId, - shape, - mass, - bodyType, - positionX, - positionY, - positionZ, - settings, - kind, - persistenceMode); - } - - @Nonnull - @Override - public RigidBodySpawnBatchRecorder body(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - assertMutable(); - spawns.add(bodyKeyMostSignificantBits, - bodyKeyLeastSignificantBits, - spaceId, - shape, - mass, - bodyType, - positionX, - positionY, - positionZ, - settings, - kind, - persistenceMode); - return this; - } - - boolean isEmpty() { - return spawns.size() == 0; - } - - @Nonnull - RigidBodySpawnBatch spawns() { - return spawns; - } - - void seal() { - sealed = true; - } - - private void assertMutable() { - if (sealed) { - throw new IllegalStateException("Rigid body spawn batch is already recorded"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnRecorder.java deleted file mode 100644 index 96d352f6..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnRecorder.java +++ /dev/null @@ -1,263 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Fluent recorder for one copied rigid body spawn request. - */ -public final class MutableRigidBodySpawnRecorder implements RigidBodySpawnRecorder { - - @Nonnull - private final RigidBodySpawnSink sink; - @Nonnull - private final RigidBodyKey bodyKey; - private SpaceId spaceId; - private PhysicsShapeSpec shape; - private float mass = 1.0f; - @Nonnull - private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; - private float positionX; - private float positionY; - private float positionZ; - private RigidBodySpawnSettings settings; - @Nonnull - private PhysicsBodyKind kind = PhysicsBodyKind.BODY; - @Nonnull - private PhysicsBodyPersistenceMode persistenceMode = PhysicsBodyPersistenceMode.RUNTIME_ONLY; - private boolean sealed; - - MutableRigidBodySpawnRecorder(@Nonnull RigidBodySpawnSink sink, - @Nonnull RigidBodyKey bodyKey) { - this.sink = Objects.requireNonNull(sink, "sink"); - this.bodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - } - - @Nonnull - @Override - public RigidBodySpawnRecorder space(@Nonnull SpaceId spaceId) { - assertMutable(); - this.spaceId = Objects.requireNonNull(spaceId, "spaceId"); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder box(float halfX, - float halfY, - float halfZ) { - assertMutable(); - this.shape = PhysicsShapeSpec.box(halfX, halfY, halfZ); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder shape(@Nonnull PhysicsShapeSpec shape) { - assertMutable(); - this.shape = Objects.requireNonNull(shape, "shape"); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder sphere(float radius) { - assertMutable(); - this.shape = PhysicsShapeSpec.sphere(radius); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder capsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis) { - assertMutable(); - this.shape = PhysicsShapeSpec.capsule(radius, halfHeight, axis); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder cylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis) { - assertMutable(); - this.shape = PhysicsShapeSpec.cylinder(radius, halfHeight, axis); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder cone(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis) { - assertMutable(); - this.shape = PhysicsShapeSpec.cone(radius, halfHeight, axis); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder plane(float groundY) { - assertMutable(); - this.shape = PhysicsShapeSpec.plane(groundY); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder mass(float mass) { - assertMutable(); - this.mass = mass; - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder type(@Nonnull PhysicsBodyType bodyType) { - assertMutable(); - this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder dynamic() { - return type(PhysicsBodyType.DYNAMIC); - } - - @Nonnull - @Override - public RigidBodySpawnRecorder kinematic() { - return type(PhysicsBodyType.KINEMATIC); - } - - @Nonnull - @Override - public RigidBodySpawnRecorder position(@Nonnull Vector3f position) { - Objects.requireNonNull(position, "position"); - return position(position.x, position.y, position.z); - } - - @Nonnull - @Override - public RigidBodySpawnRecorder position(float x, - float y, - float z) { - assertMutable(); - this.positionX = x; - this.positionY = y; - this.positionZ = z; - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder settings(@Nonnull RigidBodySpawnSettings settings) { - assertMutable(); - this.settings = Objects.requireNonNull(settings, "settings"); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder sensor(boolean sensor) { - assertMutable(); - this.settings = settingsOrDefaults().withSensor(sensor); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder collisionFilter(int group, - int mask) { - assertMutable(); - this.settings = settingsOrDefaults().withCollisionFilter(group, mask); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder kind(@Nonnull PhysicsBodyKind kind) { - assertMutable(); - this.kind = Objects.requireNonNull(kind, "kind"); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder temporary() { - return kind(PhysicsBodyKind.TEMPORARY); - } - - @Nonnull - @Override - public RigidBodySpawnRecorder persistence(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { - assertMutable(); - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnRecorder runtimeOnly() { - return persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY); - } - - @Nonnull - @Override - public RigidBodySpawnRecorder persistent() { - return persistence(PhysicsBodyPersistenceMode.PERSISTENT); - } - - public void record() { - assertMutable(); - validate(); - sink.accept(bodyKey, - spaceId, - shape, - mass, - bodyType, - positionX, - positionY, - positionZ, - settingsOrDefaults(), - kind, - persistenceMode); - } - - void validate() { - if (spaceId == null) { - throw new IllegalStateException("Spawn command requires a physics space"); - } - if (shape == null) { - throw new IllegalStateException("Spawn command requires a shape"); - } - } - - public void seal() { - sealed = true; - } - - @Nonnull - private RigidBodySpawnSettings settingsOrDefaults() { - return settings != null ? settings : RigidBodySpawnSettings.defaults(); - } - - private void assertMutable() { - if (sealed) { - throw new IllegalStateException("Rigid body spawn recorder is no longer active"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnTemplateRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnTemplateRecorder.java deleted file mode 100644 index 4f77f637..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/MutableRigidBodySpawnTemplateRecorder.java +++ /dev/null @@ -1,96 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RigidBodySpawnTemplateBatch; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnTemplateRecorder; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Fluent recorder for high-volume rigid body spawns sharing one copied spawn template. - */ -public final class MutableRigidBodySpawnTemplateRecorder implements RigidBodySpawnTemplateRecorder { - - @Nonnull - private final RigidBodySpawnTemplateBatch spawns; - private boolean sealed; - - MutableRigidBodySpawnTemplateRecorder(int expectedBodies, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - spawns = new RigidBodySpawnTemplateBatch(expectedBodies, - spaceId, - shape, - mass, - bodyType, - settings, - kind, - persistenceMode); - } - - @Nonnull - @Override - public RigidBodySpawnTemplateRecorder body(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position) { - Objects.requireNonNull(position, "position"); - return body(bodyKey, position.x, position.y, position.z); - } - - @Nonnull - @Override - public RigidBodySpawnTemplateRecorder body(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ) { - assertMutable(); - spawns.add(bodyKey, positionX, positionY, positionZ); - return this; - } - - @Nonnull - @Override - public RigidBodySpawnTemplateRecorder body(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits, - float positionX, - float positionY, - float positionZ) { - assertMutable(); - spawns.add(bodyKeyMostSignificantBits, - bodyKeyLeastSignificantBits, - positionX, - positionY, - positionZ); - return this; - } - - boolean isEmpty() { - return spawns.size() == 0; - } - - @Nonnull - RigidBodySpawnTemplateBatch spawns() { - return spawns; - } - - void seal() { - sealed = true; - } - - private void assertMutable() { - if (sealed) { - throw new IllegalStateException("Rigid body spawn template is already recorded"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/RigidBodySpawnSink.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/RigidBodySpawnSink.java deleted file mode 100644 index 51b80207..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/recorder/RigidBodySpawnSink.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import javax.annotation.Nonnull; - -@FunctionalInterface -interface RigidBodySpawnSink { - - void accept(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandBatch.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandBatch.java deleted file mode 100644 index fe7123cb..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandBatch.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Public metadata view for a submitted physics simulation command batch. - */ -public record PhysicsCommandBatch(@Nonnull PhysicsCommandMetadata metadata, int commandCount) { - - public PhysicsCommandBatch(@Nonnull PhysicsCommandMetadata metadata, - int commandCount) { - this.metadata = Objects.requireNonNull(metadata, "metadata"); - this.commandCount = Math.max(0, commandCount); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandCompletion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandCompletion.java deleted file mode 100644 index 253d0924..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandCompletion.java +++ /dev/null @@ -1,146 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import java.util.AbstractList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.RandomAccess; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Completion summary for a submitted physics command batch. - * - *

All-applied and all-rejected summaries may expose lightweight generated result lists instead - * of storing one result object per command. Code that only needs status should prefer - * {@link #allApplied()} and {@link #firstRejected()}.

- */ -public final class PhysicsCommandCompletion { - - @Nonnull - private final List results; - private final boolean allApplied; - @Nullable - private final PhysicsCommandResult firstRejected; - - private PhysicsCommandCompletion(@Nonnull List results, - boolean allApplied, - @Nullable PhysicsCommandResult firstRejected) { - this.results = Objects.requireNonNull(results, "results"); - this.allApplied = allApplied; - this.firstRejected = firstRejected; - } - - @Nonnull - public static PhysicsCommandCompletion of(@Nonnull List results) { - List copied = List.copyOf(results); - PhysicsCommandResult firstRejected = firstRejected(copied); - return new PhysicsCommandCompletion(copied, firstRejected == null, firstRejected); - } - - @Nonnull - public static PhysicsCommandCompletion allApplied(@Nonnull PhysicsCommandMetadata metadata, - int commandCount) { - int size = Math.max(0, commandCount); - List results = size == 0 - ? List.of() - : new AppliedCommandResultList(metadata, size); - return new PhysicsCommandCompletion(results, true, null); - } - - @Nonnull - public static PhysicsCommandCompletion allRejected(@Nonnull PhysicsCommandMetadata metadata, - int commandCount, - @Nonnull String message) { - int size = Math.max(0, commandCount); - if (size == 0) { - return new PhysicsCommandCompletion(List.of(), false, null); - } - String copiedMessage = Objects.requireNonNull(message, "message"); - List results = new RejectedCommandResultList(metadata, - size, - copiedMessage); - return new PhysicsCommandCompletion(results, - false, - PhysicsCommandResult.rejected(metadata, 1L, copiedMessage)); - } - - @Nonnull - public List results() { - return results; - } - - public boolean allApplied() { - return allApplied; - } - - @Nonnull - public Optional firstRejected() { - return Optional.ofNullable(firstRejected); - } - - @Nullable - private static PhysicsCommandResult firstRejected(@Nonnull List results) { - for (PhysicsCommandResult result : results) { - if (result.status() == PhysicsCommandResult.Status.REJECTED) { - return result; - } - } - return null; - } - - private static final class AppliedCommandResultList extends AbstractList - implements RandomAccess { - - @Nonnull - private final PhysicsCommandMetadata metadata; - private final int size; - - private AppliedCommandResultList(@Nonnull PhysicsCommandMetadata metadata, int size) { - this.metadata = Objects.requireNonNull(metadata, "metadata"); - this.size = size; - } - - @Nonnull - @Override - public PhysicsCommandResult get(int index) { - Objects.checkIndex(index, size); - return PhysicsCommandResult.applied(metadata, index + 1L); - } - - @Override - public int size() { - return size; - } - } - - private static final class RejectedCommandResultList extends AbstractList - implements RandomAccess { - - @Nonnull - private final PhysicsCommandMetadata metadata; - private final int size; - @Nonnull - private final String message; - - private RejectedCommandResultList(@Nonnull PhysicsCommandMetadata metadata, - int size, - @Nonnull String message) { - this.metadata = Objects.requireNonNull(metadata, "metadata"); - this.size = size; - this.message = Objects.requireNonNull(message, "message"); - } - - @Nonnull - @Override - public PhysicsCommandResult get(int index) { - Objects.checkIndex(index, size); - return PhysicsCommandResult.rejected(metadata, index + 1L, message); - } - - @Override - public int size() { - return size; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContext.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContext.java deleted file mode 100644 index 35a64708..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContext.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.PhysicsCommandRecorder; - -import javax.annotation.Nonnull; - -/** - * Plugin-facing authoring context for deferred physics simulation intent. - * - *

This is an authoring surface only. Mutable storage, freezing, and owner-lane - * execution live in the internal simulation package.

- * - *

Commands recorded here are copied into an internal batch before they cross - * the physics-owner boundary. Completion of that batch means the owner lane - * executed the operations; published snapshots may still lag by one or more - * physics frames.

- */ -public interface PhysicsCommandContext extends PhysicsCommandRecorder { - - /** - * Records another recipe into the same pending command batch. - */ - @Nonnull - PhysicsCommandContext compose(@Nonnull PhysicsCommandRecipe recipe); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandHandle.java deleted file mode 100644 index 6d9cc821..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandHandle.java +++ /dev/null @@ -1,147 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import javax.annotation.Nonnull; - -/** - * Completion handle for a submitted physics command batch. - * - *

The completion stage reports owner-lane execution of the batch. It does not mean the - * latest published snapshot, ECS attachments, or debug readers have observed the resulting body - * state yet. Use the snapshot-frame inclusion helpers when callers need to correlate a completion - * to a captured physics snapshot frame. Completion callbacks run outside the physics owner lane; - * they may queue follow-up async physics mutations, but synchronous physics owner waits from - * completion callbacks are rejected to avoid callback/owner-lane deadlocks.

- */ -public final class PhysicsCommandHandle { - - @Nonnull - private final PhysicsCommandBatch batch; - @Nonnull - private final CompletableFuture completion; - - private PhysicsCommandHandle(@Nonnull PhysicsCommandBatch batch, - @Nonnull CompletableFuture completion) { - this.batch = Objects.requireNonNull(batch, "batch"); - this.completion = Objects.requireNonNull(completion, "completion"); - } - - @Nonnull - public static PhysicsCommandHandle completed(@Nonnull PhysicsCommandBatch batch, - @Nonnull List results) { - return new PhysicsCommandHandle(batch, - CompletableFuture.completedFuture(PhysicsCommandCompletion.of(results))); - } - - @Nonnull - public static PhysicsCommandHandle failed(@Nonnull PhysicsCommandBatch batch, - @Nonnull Throwable failure) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(Objects.requireNonNull(failure, "failure")); - return new PhysicsCommandHandle(batch, completion); - } - - @Nonnull - public static PhysicsCommandHandle fromCompletion(@Nonnull PhysicsCommandBatch batch, - @Nonnull CompletionStage> completion) { - return new PhysicsCommandHandle(batch, - Objects.requireNonNull(completion, "completion") - .thenApply(PhysicsCommandCompletion::of) - .toCompletableFuture()); - } - - @Nonnull - public static PhysicsCommandHandle fromCompletionSummary(@Nonnull PhysicsCommandBatch batch, - @Nonnull CompletionStage completion) { - return new PhysicsCommandHandle(batch, - Objects.requireNonNull(completion, "completion").toCompletableFuture()); - } - - @Nonnull - public PhysicsCommandBatch batch() { - return batch; - } - - /** - * Returns whether the latest captured snapshot in {@code frame} is known to include this - * submitted command batch. - */ - public boolean isIncludedInLatestCapturedSnapshot(@Nonnull PhysicsEventFrame frame) { - return Objects.requireNonNull(frame, "frame") - .latestCapturedSnapshotIncludesCommandBatch(batch.metadata().commandBatchSequence()); - } - - /** - * Returns the submitted-server-tick distance from this command batch to the latest captured - * snapshot in {@code frame}, or {@code 0} when that snapshot is not known to include this batch. - */ - public long capturedSnapshotServerTickLatency(@Nonnull PhysicsEventFrame frame) { - PhysicsEventFrame eventFrame = Objects.requireNonNull(frame, "frame"); - if (!eventFrame.latestCapturedSnapshotIncludesCommandBatch(batch.metadata().commandBatchSequence())) { - return 0L; - } - return eventFrame.latestCapturedSnapshotServerTickLatencyFromSubmittedTick( - batch.metadata().submittedServerTick()); - } - - /** - * Returns whether {@code frame} is known to include this submitted command batch. - */ - public boolean isIncludedInSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { - return Objects.requireNonNull(frame, "frame") - .includesCommandBatch(batch.metadata().commandBatchSequence()); - } - - /** - * Returns the submitted-server-tick distance from this command batch to {@code frame}, or - * {@code 0} when the snapshot is not known to include this batch. - */ - public long capturedSnapshotServerTickLatency(@Nonnull PublishedPhysicsSnapshotFrame frame) { - PublishedPhysicsSnapshotFrame snapshotFrame = Objects.requireNonNull(frame, "frame"); - if (!snapshotFrame.includesCommandBatch(batch.metadata().commandBatchSequence())) { - return 0L; - } - return snapshotFrame.serverTickLatencyFromSubmittedTick( - batch.metadata().submittedServerTick()); - } - - @Nonnull - public CompletionStage> completion() { - return completion.thenApply(PhysicsCommandCompletion::results); - } - - /** - * Returns the owner-lane execution summary for this batch. - * - *

This stage completes before snapshot capture, reader-side snapshot application, and ECS - * consumption. Use {@link #isIncludedInSnapshotFrame(PublishedPhysicsSnapshotFrame)} or - * {@link #isIncludedInLatestCapturedSnapshot(PhysicsEventFrame)} when snapshot-frame inclusion - * matters.

- */ - @Nonnull - public CompletionStage completionSummary() { - return completion.minimalCompletionStage(); - } - - /** - * Returns whether every recorded operation applied on the physics owner lane. - */ - @Nonnull - public CompletionStage allApplied() { - return completion.thenApply(PhysicsCommandCompletion::allApplied); - } - - /** - * Returns the first owner-lane rejection, if any. - */ - @Nonnull - public CompletionStage> firstRejected() { - return completion.thenApply(PhysicsCommandCompletion::firstRejected); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandMetadata.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandMetadata.java deleted file mode 100644 index 016693f7..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandMetadata.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -/** - * Correlation data assigned when a recorded physics command batch is frozen. - */ -public record PhysicsCommandMetadata(long submittedServerTick, - long commandBatchSequence) { - - public PhysicsCommandMetadata { - submittedServerTick = Math.max(0L, submittedServerTick); - commandBatchSequence = Math.max(0L, commandBatchSequence); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandRecipe.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandRecipe.java deleted file mode 100644 index 36468ef3..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandRecipe.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import javax.annotation.Nonnull; - -/** - * Plugin-defined command authoring recipe. - * - *

Recipes run immediately on the caller thread and record copied value - * commands into the supplied command context. They are the preferred way for plugins - * to expose higher-level reusable physics operations without requiring Impulse - * to execute unknown command classes.

- */ -@FunctionalInterface -public interface PhysicsCommandRecipe { - - void record(@Nonnull PhysicsCommandContext commands); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandResult.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandResult.java deleted file mode 100644 index 18440cae..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandResult.java +++ /dev/null @@ -1,75 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Copied result for one physics command. - * - * @param commandBatchSequence owner FIFO batch sequence assigned when the command batch was submitted - * @param submittedServerTick server tick supplied by the caller when the command context was recorded - * @param includedSnapshotFrameEpoch published snapshot frame that is known to include this command, - * or {@code 0} when completion has not been correlated to a - * snapshot frame - */ -public record PhysicsCommandResult(@Nonnull Status status, - long commandSequence, - long commandBatchSequence, - long submittedServerTick, - long includedSnapshotFrameEpoch, - @Nullable String message) { - - public PhysicsCommandResult { - Objects.requireNonNull(status, "status"); - commandSequence = Math.max(0L, commandSequence); - commandBatchSequence = Math.max(0L, commandBatchSequence); - submittedServerTick = Math.max(0L, submittedServerTick); - includedSnapshotFrameEpoch = Math.max(0L, includedSnapshotFrameEpoch); - } - - @Nonnull - public static PhysicsCommandResult applied(long commandSequence) { - return new PhysicsCommandResult(Status.APPLIED, commandSequence, 0L, 0L, 0L, null); - } - - @Nonnull - public static PhysicsCommandResult applied(@Nonnull PhysicsCommandMetadata metadata, - long commandSequence) { - Objects.requireNonNull(metadata, "metadata"); - return new PhysicsCommandResult(Status.APPLIED, - commandSequence, - metadata.commandBatchSequence(), - metadata.submittedServerTick(), - 0L, - null); - } - - @Nonnull - public static PhysicsCommandResult rejected(long commandSequence, @Nonnull String message) { - return new PhysicsCommandResult(Status.REJECTED, - commandSequence, - 0L, - 0L, - 0L, - Objects.requireNonNull(message, "message")); - } - - @Nonnull - public static PhysicsCommandResult rejected(@Nonnull PhysicsCommandMetadata metadata, - long commandSequence, - @Nonnull String message) { - Objects.requireNonNull(metadata, "metadata"); - return new PhysicsCommandResult(Status.REJECTED, - commandSequence, - metadata.commandBatchSequence(), - metadata.submittedServerTick(), - 0L, - Objects.requireNonNull(message, "message")); - } - - public enum Status { - APPLIED, - REJECTED - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsFalloff.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsFalloff.java deleted file mode 100644 index 99b4cf83..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsFalloff.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import javax.annotation.Nonnull; - -/** - * Scalar attenuation for area physics command recipes. - */ -@FunctionalInterface -public interface PhysicsFalloff { - - PhysicsFalloff CONSTANT = (distance, radius) -> distance <= radius ? 1.0f : 0.0f; - PhysicsFalloff LINEAR = (distance, radius) -> radius > 0.0f - ? Math.max(0.0f, 1.0f - distance / radius) - : 0.0f; - PhysicsFalloff SMOOTH = (distance, radius) -> { - if (radius <= 0.0f) { - return 0.0f; - } - float t = Math.max(0.0f, Math.min(1.0f, distance / radius)); - return 1.0f - (t * t * (3.0f - 2.0f * t)); - }; - - float scale(float distance, float radius); - - @Nonnull - static PhysicsFalloff constant() { - return CONSTANT; - } - - @Nonnull - static PhysicsFalloff linear() { - return LINEAR; - } - - @Nonnull - static PhysicsFalloff smooth() { - return SMOOTH; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsRecipes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsRecipes.java deleted file mode 100644 index 55cb6b18..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsRecipes.java +++ /dev/null @@ -1,193 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Small reusable physics command recipes for common gameplay impulses and forces. - */ -public final class PhysicsRecipes { - - private static final float EPSILON = 0.000001f; - private static final PhysicsCommandRecipe EMPTY = commands -> { - }; - - private PhysicsRecipes() { - } - - @Nonnull - public static PhysicsCommandRecipe applyImpulse(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f impulse) { - Objects.requireNonNull(bodyKey, "bodyKey"); - Objects.requireNonNull(impulse, "impulse"); - float x = finite(impulse.x, "impulse.x"); - float y = finite(impulse.y, "impulse.y"); - float z = finite(impulse.z, "impulse.z"); - return commands -> commands.applyBodyImpulse(bodyKey, x, y, z); - } - - @Nonnull - public static PhysicsCommandRecipe applyImpulse(@Nonnull Iterable bodyKeys, - @Nonnull Vector3f impulse) { - List keys = copyBodyKeys(bodyKeys); - Objects.requireNonNull(impulse, "impulse"); - float x = finite(impulse.x, "impulse.x"); - float y = finite(impulse.y, "impulse.y"); - float z = finite(impulse.z, "impulse.z"); - if (keys.isEmpty()) { - return EMPTY; - } - return commands -> { - for (RigidBodyKey bodyKey : keys) { - commands.applyBodyImpulse(bodyKey, x, y, z); - } - }; - } - - @Nonnull - public static PhysicsCommandRecipe applyForce(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f force) { - Objects.requireNonNull(bodyKey, "bodyKey"); - Objects.requireNonNull(force, "force"); - float x = finite(force.x, "force.x"); - float y = finite(force.y, "force.y"); - float z = finite(force.z, "force.z"); - return commands -> commands.applyBodyForce(bodyKey, x, y, z); - } - - @Nonnull - public static PhysicsCommandRecipe applyForce(@Nonnull Iterable bodyKeys, - @Nonnull Vector3f force) { - List keys = copyBodyKeys(bodyKeys); - Objects.requireNonNull(force, "force"); - float x = finite(force.x, "force.x"); - float y = finite(force.y, "force.y"); - float z = finite(force.z, "force.z"); - if (keys.isEmpty()) { - return EMPTY; - } - return commands -> { - for (RigidBodyKey bodyKey : keys) { - commands.applyBodyForce(bodyKey, x, y, z); - } - }; - } - - @Nonnull - public static PhysicsCommandRecipe radialImpulse( - @Nonnull Iterable bodies, - @Nonnull Vector3f origin, - float strength, - float radius, - @Nonnull PhysicsFalloff falloff) { - List commands = radialCommands(bodies, origin, strength, radius, falloff); - if (commands.isEmpty()) { - return EMPTY; - } - return context -> { - for (VectorCommand command : commands) { - context.applyBodyImpulse(command.bodyKey(), command.x(), command.y(), command.z()); - } - }; - } - - @Nonnull - public static PhysicsCommandRecipe radialForce( - @Nonnull Iterable bodies, - @Nonnull Vector3f origin, - float strength, - float radius, - @Nonnull PhysicsFalloff falloff) { - List commands = radialCommands(bodies, origin, strength, radius, falloff); - if (commands.isEmpty()) { - return EMPTY; - } - return context -> { - for (VectorCommand command : commands) { - context.applyBodyForce(command.bodyKey(), command.x(), command.y(), command.z()); - } - }; - } - - @Nonnull - private static List copyBodyKeys(@Nonnull Iterable bodyKeys) { - Objects.requireNonNull(bodyKeys, "bodyKeys"); - List keys = new ArrayList<>(); - for (RigidBodyKey bodyKey : bodyKeys) { - keys.add(Objects.requireNonNull(bodyKey, "bodyKey")); - } - return List.copyOf(keys); - } - - @Nonnull - private static List radialCommands( - @Nonnull Iterable bodies, - @Nonnull Vector3f origin, - float strength, - float radius, - @Nonnull PhysicsFalloff falloff) { - Objects.requireNonNull(bodies, "bodies"); - Objects.requireNonNull(origin, "origin"); - Objects.requireNonNull(falloff, "falloff"); - float originX = finite(origin.x, "origin.x"); - float originY = finite(origin.y, "origin.y"); - float originZ = finite(origin.z, "origin.z"); - float finiteStrength = finite(strength, "strength"); - float finiteRadius = finite(radius, "radius"); - if (finiteRadius <= 0.0f || finiteStrength == 0.0f) { - return List.of(); - } - - List commands = new ArrayList<>(); - for (PhysicsBodySnapshotEntry entry : bodies) { - Objects.requireNonNull(entry, "entry"); - if (!entry.snapshot().isDynamic() || entry.snapshot().sensor()) { - continue; - } - float dx = entry.snapshot().positionX() - originX; - float dy = entry.snapshot().positionY() - originY; - float dz = entry.snapshot().positionZ() - originZ; - float distanceSquared = dx * dx + dy * dy + dz * dz; - if (distanceSquared <= EPSILON || distanceSquared > finiteRadius * finiteRadius) { - continue; - } - float distance = (float) Math.sqrt(distanceSquared); - float scale = safeScale(falloff.scale(distance, finiteRadius)); - if (scale <= 0.0f) { - continue; - } - float magnitude = finiteStrength * scale / distance; - commands.add(new VectorCommand(entry.bodyKey(), - dx * magnitude, - dy * magnitude, - dz * magnitude)); - } - return List.copyOf(commands); - } - - private static float safeScale(float scale) { - if (!Float.isFinite(scale)) { - return 0.0f; - } - return Math.max(0.0f, Math.min(1.0f, scale)); - } - - private static float finite(float value, @Nonnull String name) { - if (!Float.isFinite(value)) { - throw new IllegalArgumentException(name + " must be finite"); - } - return value; - } - - private record VectorCommand(@Nonnull RigidBodyKey bodyKey, float x, float y, float z) { - - private VectorCommand { - Objects.requireNonNull(bodyKey, "bodyKey"); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/JointCommandRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/JointCommandRecorder.java deleted file mode 100644 index 3ead5c17..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/JointCommandRecorder.java +++ /dev/null @@ -1,101 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.recorder; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Fluent recorder for one joint creation command. - */ -public interface JointCommandRecorder { - - @Nonnull - JointCommandRecorder space(@Nonnull SpaceId spaceId); - - @Nonnull - JointCommandRecorder bodies(@Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB); - - @Nonnull - JointCommandRecorder fixed(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB); - - @Nonnull - JointCommandRecorder fixed(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ); - - @Nonnull - JointCommandRecorder point(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB); - - @Nonnull - JointCommandRecorder point(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ); - - @Nonnull - JointCommandRecorder hinge(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis); - - @Nonnull - JointCommandRecorder hinge(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ); - - @Nonnull - JointCommandRecorder slider(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis); - - @Nonnull - JointCommandRecorder slider(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ); - - @Nonnull - JointCommandRecorder spring(@Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping); - - @Nonnull - JointCommandRecorder spring(float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float restLength, - float stiffness, - float damping); - - @Nonnull - JointCommandRecorder limits(float lowerLimit, - float upperLimit); - - @Nonnull - JointCommandRecorder motor(float targetVelocity, - float maxForce); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/PhysicsCommandRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/PhysicsCommandRecorder.java deleted file mode 100644 index 96230f08..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/PhysicsCommandRecorder.java +++ /dev/null @@ -1,333 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import java.util.Objects; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Fluent recorder for copied physics simulation intent. - * - *

The word "command" here means a compact, value-copied request submitted to the physics - * owner. It is not a Hytale server command and it should not be modeled as one Java object per - * mechanical instruction for high-volume cases. Use the bulk/template spawn methods when many - * bodies share the same shape and settings.

- */ -public interface PhysicsCommandRecorder { - - @Nonnull - RigidBodyCommandRecorder body(@Nonnull RigidBodyKey bodyKey); - - @Nonnull - PhysicsCommandRecorder body(@Nonnull RigidBodyKey bodyKey, - @Nonnull Consumer recipe); - - @Nonnull - PhysicsCommandRecorder setSpaceGravity(@Nonnull SpaceId spaceId, - float x, - float y, - float z); - - @Nonnull - default PhysicsCommandRecorder setBodyTransform(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - return setBodyTransform(bodyKey, position, rotation, false); - } - - @Nonnull - default PhysicsCommandRecorder setBodyTransform(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - boolean activate) { - Objects.requireNonNull(position, "position"); - Objects.requireNonNull(rotation, "rotation"); - return setBodyTransform(bodyKey, - position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - activate); - } - - @Nonnull - default PhysicsCommandRecorder setBodyTransform(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate) { - body(bodyKey).setTransform(positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - activate); - return this; - } - - @Nonnull - default PhysicsCommandRecorder setBodyPosition(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position) { - return setBodyPosition(bodyKey, position, false); - } - - @Nonnull - default PhysicsCommandRecorder setBodyPosition(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position, - boolean activate) { - Objects.requireNonNull(position, "position"); - return setBodyPosition(bodyKey, position.x, position.y, position.z, activate); - } - - @Nonnull - default PhysicsCommandRecorder setBodyPosition(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ, - boolean activate) { - body(bodyKey).setPosition(positionX, positionY, positionZ, activate); - return this; - } - - @Nonnull - default PhysicsCommandRecorder setBodyVelocity(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - return setBodyVelocity(bodyKey, linearVelocity, angularVelocity, false); - } - - @Nonnull - default PhysicsCommandRecorder setBodyVelocity(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean activate) { - Objects.requireNonNull(linearVelocity, "linearVelocity"); - Objects.requireNonNull(angularVelocity, "angularVelocity"); - return setBodyVelocity(bodyKey, - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z, - activate); - } - - @Nonnull - default PhysicsCommandRecorder setBodyVelocity(@Nonnull RigidBodyKey bodyKey, - float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate) { - body(bodyKey).setVelocity(linearX, - linearY, - linearZ, - angularX, - angularY, - angularZ, - activate); - return this; - } - - @Nonnull - default PhysicsCommandRecorder setBodyType(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType) { - return setBodyType(bodyKey, bodyType, false); - } - - @Nonnull - default PhysicsCommandRecorder setBodyType(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType, - boolean activate) { - body(bodyKey).setType(bodyType, activate); - return this; - } - - @Nonnull - default PhysicsCommandRecorder activateBody(@Nonnull RigidBodyKey bodyKey) { - body(bodyKey).activate(); - return this; - } - - @Nonnull - default PhysicsCommandRecorder applyBodyImpulse(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f impulse) { - Objects.requireNonNull(impulse, "impulse"); - return applyBodyImpulse(bodyKey, impulse.x, impulse.y, impulse.z); - } - - @Nonnull - default PhysicsCommandRecorder applyBodyImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - body(bodyKey).applyImpulse(x, y, z); - return this; - } - - @Nonnull - default PhysicsCommandRecorder applyBodyImpulse(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f impulse, - @Nonnull Vector3f offset) { - Objects.requireNonNull(impulse, "impulse"); - Objects.requireNonNull(offset, "offset"); - return applyBodyImpulse(bodyKey, impulse.x, impulse.y, impulse.z, offset.x, offset.y, offset.z); - } - - @Nonnull - default PhysicsCommandRecorder applyBodyImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - body(bodyKey).applyImpulse(x, y, z, offsetX, offsetY, offsetZ); - return this; - } - - @Nonnull - default PhysicsCommandRecorder applyBodyTorqueImpulse(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f impulse) { - Objects.requireNonNull(impulse, "impulse"); - return applyBodyTorqueImpulse(bodyKey, impulse.x, impulse.y, impulse.z); - } - - @Nonnull - default PhysicsCommandRecorder applyBodyTorqueImpulse(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - body(bodyKey).applyTorqueImpulse(x, y, z); - return this; - } - - @Nonnull - default PhysicsCommandRecorder applyBodyForce(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f force) { - Objects.requireNonNull(force, "force"); - return applyBodyForce(bodyKey, force.x, force.y, force.z); - } - - @Nonnull - default PhysicsCommandRecorder applyBodyForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - body(bodyKey).applyForce(x, y, z); - return this; - } - - @Nonnull - default PhysicsCommandRecorder applyBodyForce(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f force, - @Nonnull Vector3f offset) { - Objects.requireNonNull(force, "force"); - Objects.requireNonNull(offset, "offset"); - return applyBodyForce(bodyKey, force.x, force.y, force.z, offset.x, offset.y, offset.z); - } - - @Nonnull - default PhysicsCommandRecorder applyBodyForce(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - body(bodyKey).applyForce(x, y, z, offsetX, offsetY, offsetZ); - return this; - } - - @Nonnull - default PhysicsCommandRecorder applyBodyTorque(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f force) { - Objects.requireNonNull(force, "force"); - return applyBodyTorque(bodyKey, force.x, force.y, force.z); - } - - @Nonnull - default PhysicsCommandRecorder applyBodyTorque(@Nonnull RigidBodyKey bodyKey, - float x, - float y, - float z) { - body(bodyKey).applyTorque(x, y, z); - return this; - } - - @Nonnull - default PhysicsCommandRecorder destroyBody(@Nonnull RigidBodyKey bodyKey) { - body(bodyKey).destroy(); - return this; - } - - @Nonnull - RigidBodySpawnRecorder spawnBody(@Nonnull RigidBodyKey bodyKey); - - @Nonnull - PhysicsCommandRecorder spawnBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull Consumer recipe); - - @Nonnull - PhysicsCommandRecorder spawnBodies(@Nonnull Consumer recipe); - - /** - * Records a bulk spawn batch with a capacity hint for the number of bodies the recipe will add. - */ - @Nonnull - PhysicsCommandRecorder spawnBodies(int expectedBodies, - @Nonnull Consumer recipe); - - /** - * Records a compact template spawn batch where each body differs only by key and position. - */ - @Nonnull - PhysicsCommandRecorder spawnBodies(int expectedBodies, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull Consumer recipe); - - @Nonnull - JointCommandRecorder joint(@Nonnull JointKey jointKey); - - @Nonnull - PhysicsCommandRecorder joint(@Nonnull JointKey jointKey, - @Nonnull Consumer recipe); - - @Nonnull - PhysicsCommandRecorder destroyJoint(@Nonnull JointKey jointKey); - - @Nonnull - PhysicsCommandRecorder destroyJointBetween(@Nullable JointKey preferredJointKey, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodyCommandRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodyCommandRecorder.java deleted file mode 100644 index 142d768b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodyCommandRecorder.java +++ /dev/null @@ -1,131 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Fluent recorder for operations targeting one rigid body key. - */ -public interface RigidBodyCommandRecorder { - - @Nonnull - RigidBodyCommandRecorder setTransform(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation); - - @Nonnull - RigidBodyCommandRecorder setTransform(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - boolean activate); - - @Nonnull - RigidBodyCommandRecorder setTransform(float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - boolean activate); - - @Nonnull - RigidBodyCommandRecorder setPosition(@Nonnull Vector3f position); - - @Nonnull - RigidBodyCommandRecorder setPosition(@Nonnull Vector3f position, - boolean activate); - - @Nonnull - RigidBodyCommandRecorder setPosition(float positionX, - float positionY, - float positionZ, - boolean activate); - - @Nonnull - RigidBodyCommandRecorder setVelocity(@Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity); - - @Nonnull - RigidBodyCommandRecorder setVelocity(@Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean activate); - - @Nonnull - RigidBodyCommandRecorder setVelocity(float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ, - boolean activate); - - @Nonnull - RigidBodyCommandRecorder setType(@Nonnull PhysicsBodyType bodyType); - - @Nonnull - RigidBodyCommandRecorder setType(@Nonnull PhysicsBodyType bodyType, - boolean activate); - - @Nonnull - RigidBodyCommandRecorder activate(); - - @Nonnull - RigidBodyCommandRecorder applyImpulse(@Nonnull Vector3f impulse); - - @Nonnull - RigidBodyCommandRecorder applyImpulse(float x, - float y, - float z); - - @Nonnull - RigidBodyCommandRecorder applyImpulse(@Nonnull Vector3f impulse, - @Nonnull Vector3f offset); - - @Nonnull - RigidBodyCommandRecorder applyImpulse(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ); - - @Nonnull - RigidBodyCommandRecorder applyTorqueImpulse(@Nonnull Vector3f impulse); - - @Nonnull - RigidBodyCommandRecorder applyTorqueImpulse(float x, - float y, - float z); - - @Nonnull - RigidBodyCommandRecorder applyForce(@Nonnull Vector3f force); - - @Nonnull - RigidBodyCommandRecorder applyForce(float x, - float y, - float z); - - @Nonnull - RigidBodyCommandRecorder applyForce(@Nonnull Vector3f force, - @Nonnull Vector3f offset); - - @Nonnull - RigidBodyCommandRecorder applyForce(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ); - - @Nonnull - RigidBodyCommandRecorder applyTorque(@Nonnull Vector3f force); - - @Nonnull - RigidBodyCommandRecorder applyTorque(float x, - float y, - float z); - - @Nonnull - RigidBodyCommandRecorder destroy(); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnBatchRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnBatchRecorder.java deleted file mode 100644 index a91ac3c5..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnBatchRecorder.java +++ /dev/null @@ -1,49 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; - -import java.util.function.Consumer; -import javax.annotation.Nonnull; - -/** - * Fluent recorder for a bulk rigid body spawn command. - */ -public interface RigidBodySpawnBatchRecorder { - - @Nonnull - RigidBodySpawnBatchRecorder body(@Nonnull RigidBodyKey bodyKey, - @Nonnull Consumer recipe); - - @Nonnull - RigidBodySpawnBatchRecorder body(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode); - - @Nonnull - RigidBodySpawnBatchRecorder body(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnRecorder.java deleted file mode 100644 index 2ee4d985..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnRecorder.java +++ /dev/null @@ -1,95 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.recorder; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import javax.annotation.Nonnull; - -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import org.joml.Vector3f; - -/** - * Fluent recorder for one rigid body spawn request. - */ -public interface RigidBodySpawnRecorder { - - @Nonnull - RigidBodySpawnRecorder space(@Nonnull SpaceId spaceId); - - @Nonnull - RigidBodySpawnRecorder box(float halfX, - float halfY, - float halfZ); - - @Nonnull - RigidBodySpawnRecorder shape(@Nonnull PhysicsShapeSpec shape); - - @Nonnull - RigidBodySpawnRecorder sphere(float radius); - - @Nonnull - RigidBodySpawnRecorder capsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis); - - @Nonnull - RigidBodySpawnRecorder cylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis); - - @Nonnull - RigidBodySpawnRecorder cone(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis); - - @Nonnull - RigidBodySpawnRecorder plane(float groundY); - - @Nonnull - RigidBodySpawnRecorder mass(float mass); - - @Nonnull - RigidBodySpawnRecorder type(@Nonnull PhysicsBodyType bodyType); - - @Nonnull - RigidBodySpawnRecorder dynamic(); - - @Nonnull - RigidBodySpawnRecorder kinematic(); - - @Nonnull - RigidBodySpawnRecorder position(@Nonnull Vector3f position); - - @Nonnull - RigidBodySpawnRecorder position(float x, - float y, - float z); - - @Nonnull - RigidBodySpawnRecorder settings(@Nonnull RigidBodySpawnSettings settings); - - @Nonnull - RigidBodySpawnRecorder sensor(boolean sensor); - - @Nonnull - RigidBodySpawnRecorder collisionFilter(int group, - int mask); - - @Nonnull - RigidBodySpawnRecorder kind(@Nonnull PhysicsBodyKind kind); - - @Nonnull - RigidBodySpawnRecorder temporary(); - - @Nonnull - RigidBodySpawnRecorder persistence(@Nonnull PhysicsBodyPersistenceMode persistenceMode); - - @Nonnull - RigidBodySpawnRecorder runtimeOnly(); - - @Nonnull - RigidBodySpawnRecorder persistent(); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnTemplateRecorder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnTemplateRecorder.java deleted file mode 100644 index 2ace9ea2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/recorder/RigidBodySpawnTemplateRecorder.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.recorder; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Fluent recorder for high-volume rigid body spawns sharing one spawn template. - */ -public interface RigidBodySpawnTemplateRecorder { - - @Nonnull - RigidBodySpawnTemplateRecorder body(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position); - - @Nonnull - RigidBodySpawnTemplateRecorder body(@Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ); - - @Nonnull - RigidBodySpawnTemplateRecorder body(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits, - float positionX, - float positionY, - float positionZ); -} From f92fa0890699373535acedb8b6e0b75dc3481f26 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:48:01 +0200 Subject: [PATCH 106/534] refactor(physicsstore): remove queued read object guard Signed-off-by: Blovien --- .../PhysicsStoreReadQueueResource.java | 107 +----------------- .../physicsstore/PhysicsStoreThreading.java | 2 +- 2 files changed, 6 insertions(+), 103 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java index c2d6826f..6fbf21be 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -1,23 +1,15 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.resources; -import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.IdentityHashMap; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; @@ -29,8 +21,9 @@ * Owner-lane live backend read queue drained by PhysicsStore systems. * *

Enqueued reads must capture copied inputs only. They execute during PhysicsStore ticking and - * must return copied values rather than live {@code Ref}, runtime resources, or - * backend handles.

+ * must return copied values rather than live stores, refs, runtime resources, or backend + * handles. This is an explicit boundary contract for callers; do not enforce it with generic + * runtime object inspection in this hot path.

*/ public final class PhysicsStoreReadQueueResource implements Resource { @@ -41,7 +34,7 @@ public PhysicsStoreReadQueueResource() { } @Nonnull - public synchronized CompletionStage enqueue( + public synchronized CompletionStage enqueueCopiedRead( @Nonnull Function, R> read) { CompletableFuture completion = new CompletableFuture<>(); reads.add(new QueuedRead<>(read, completion)); @@ -101,9 +94,7 @@ private QueuedRead(@Nonnull Function, R> read, public void complete(@Nonnull Store store) { try { - R value = read.apply(store); - CopiedReadBoundary.requireCopied(value); - PhysicsStoreAsyncCompletions.complete(completion, value); + PhysicsStoreAsyncCompletions.complete(completion, read.apply(store)); } catch (RuntimeException | Error exception) { fail(exception); } @@ -113,92 +104,4 @@ public void fail(@Nonnull Throwable failure) { PhysicsStoreAsyncCompletions.fail(completion, Objects.requireNonNull(failure, "failure")); } } - - private static final class CopiedReadBoundary { - - private static final String LIVE_BACKEND = "dev.hytalemodding.impulse.api.PhysicsBackend"; - private static final String LIVE_SPACE = "dev.hytalemodding.impulse.api.PhysicsSpace"; - private static final String LIVE_BODY = "dev.hytalemodding.impulse.api.PhysicsBody"; - private static final String LIVE_JOINT = "dev.hytalemodding.impulse.api.PhysicsJoint"; - - private CopiedReadBoundary() { - } - - private static void requireCopied(Object value) { - requireCopied(value, new IdentityHashMap<>()); - } - - private static void requireCopied(Object value, IdentityHashMap seen) { - if (value == null - || isClearlyCopiedScalar(value) - || seen.put(value, Boolean.TRUE) != null) { - return; - } - rejectLiveValue(value); - if (value instanceof Optional optional) { - optional.ifPresent(contained -> requireCopied(contained, seen)); - return; - } - if (value instanceof Iterable iterable) { - for (Object contained : iterable) { - requireCopied(contained, seen); - } - return; - } - if (value instanceof Map map) { - for (Map.Entry entry : map.entrySet()) { - requireCopied(entry.getKey(), seen); - requireCopied(entry.getValue(), seen); - } - return; - } - if (value.getClass().isArray() && !value.getClass().componentType().isPrimitive()) { - Object[] values = (Object[]) value; - for (Object contained : values) { - requireCopied(contained, seen); - } - } - } - - private static boolean isClearlyCopiedScalar(Object value) { - return value instanceof String - || value instanceof Number - || value instanceof Boolean - || value instanceof Character - || value instanceof Enum - || value instanceof java.util.UUID; - } - - private static void rejectLiveValue(Object value) { - if (value instanceof Store - || value instanceof Ref - || value instanceof Resource - || value instanceof PhysicsBackendRuntime - || value instanceof CompletionStage - || implementsNamedType(value.getClass(), LIVE_BACKEND) - || implementsNamedType(value.getClass(), LIVE_SPACE) - || implementsNamedType(value.getClass(), LIVE_BODY) - || implementsNamedType(value.getClass(), LIVE_JOINT) - || value instanceof BackendSpaceHandle - || value instanceof BackendBodyHandle - || value instanceof BackendJointHandle) { - throw new IllegalStateException("PhysicsStore queued reads must complete with " - + "copied values, not live " + value.getClass().getName()); - } - } - - private static boolean implementsNamedType(@Nonnull Class type, - @Nonnull String typeName) { - if (typeName.equals(type.getName())) { - return true; - } - for (Class interfaceType : type.getInterfaces()) { - if (implementsNamedType(interfaceType, typeName)) { - return true; - } - } - Class superType = type.getSuperclass(); - return superType != null && implementsNamedType(superType, typeName); - } - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java index 1997540c..b062fb76 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -109,7 +109,7 @@ private static void enqueueRead(@Nonnull World world, Store store = store(world); requireWorldThread(store, operation); store.getResource(PhysicsStoreReadQueueResource.getResourceType()) - .enqueue(read) + .enqueueCopiedRead(read) .whenComplete((value, failure) -> { if (failure != null) { PhysicsStoreAsyncCompletions.fail(completion, failure); From cf739dc7ff0e3e00157e73c2c2739336a56d2e8b Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 17:55:00 +0200 Subject: [PATCH 107/534] refactor(physicsstore): remove command-batch event metadata Signed-off-by: Blovien --- .../WorldCollisionPerfReportCommand.java | 35 ------ .../resources/PhysicsEventResource.java | 3 - .../resources/PhysicsWorldEventState.java | 12 +- .../resources/PhysicsWorldLifecycleState.java | 1 - .../resources/PhysicsWorldSnapshotState.java | 2 - .../events/PhysicsCommandBatchEvent.java | 106 ---------------- .../core/plugin/events/PhysicsEventFrame.java | 61 +--------- .../PhysicsSnapshotPublicationEvent.java | 31 ----- .../core/plugin/events/PhysicsStepEvent.java | 3 - .../PublishedPhysicsSnapshotFrame.java | 113 +----------------- 10 files changed, 3 insertions(+), 364 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsCommandBatchEvent.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index 7625695c..64a55140 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -11,7 +11,6 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.VisualSnapshot; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsCommandBatchEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; @@ -416,12 +415,8 @@ static String formatEventFrameSummary(@Nonnull PhysicsEventFrame frame) { .append(frame.latestCapturedSnapshotStepSequence()) .append(" latestCapturedSnapshotTick=") .append(frame.latestCapturedSnapshotServerTick()) - .append(" latestCapturedSnapshotLastIncludedCommandBatch=") - .append(frame.latestCapturedSnapshotLastIncludedCommandBatchSequence()) .append(" events=") .append(frame.eventCount()) - .append(" commandBatches=") - .append(frame.commandBatchCount()) .append(" steps=") .append(frame.stepCount()) .append(" publications=") @@ -430,36 +425,6 @@ static String formatEventFrameSummary(@Nonnull PhysicsEventFrame frame) { .append(frame.physicsEventCount()) .append(" droppedBackendEvents=") .append(frame.droppedBackendEventCount()); - PhysicsCommandBatchEvent latestCommand = frame.latestCommandBatch(); - if (latestCommand != null) { - boolean capturedSnapshotIncluded = frame.latestCapturedSnapshotIncludes(latestCommand); - builder.append(" latestCommand=") - .append(latestCommand.commandBatchSequence()) - .append(" submittedTick=") - .append(latestCommand.submittedServerTick()) - .append(" bodyRefs=") - .append(latestCommand.bodyKeyReferenceCount()) - .append(" jointRefs=") - .append(latestCommand.jointKeyReferenceCount()) - .append(" capturedSnapshotIncluded=") - .append(capturedSnapshotIncluded); - if (latestCommand.firstBodyKey() != null) { - builder.append(" firstBody=") - .append(latestCommand.firstBodyKey()); - } - if (latestCommand.firstJointKey() != null) { - builder.append(" firstJoint=") - .append(latestCommand.firstJointKey()); - } - if (capturedSnapshotIncluded) { - builder.append(" capturedSnapshotTickLatency=") - .append(frame.capturedSnapshotServerTickLatency(latestCommand)); - } - if (!latestCommand.allApplied()) { - builder.append(" firstRejected=") - .append(latestCommand.firstRejectedCommandSequence()); - } - } var latestPublication = frame.latestSnapshotPublication(); if (latestPublication != null) { builder.append(" publishedTick=") diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java index 1968551d..cf394616 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java @@ -42,7 +42,6 @@ public PhysicsEventFrame publishStepFrame(long snapshotSequence, serverTick, safeSnapshotSequence, PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, bodyCount, stepNanos, snapshotNanos); @@ -51,8 +50,6 @@ public PhysicsEventFrame publishStepFrame(long snapshotSequence, safeSnapshotSequence, safeSnapshotSequence, serverTick, - 0L, - List.of(), List.of(stepEvent), List.of(), Objects.requireNonNull(physicsEvents, "physicsEvents"), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java index e574b42c..b18505ce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.internal.resources; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsCommandBatchEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; @@ -47,13 +46,11 @@ public PhysicsEventFrame publishStepCaptured(long worldEpoch, snapshotFrame.serverTick(), snapshotFrame.frameEpoch(), snapshotFrame.status(), - snapshotFrame.lastIncludedCommandBatchSequence(), snapshotFrame.bodyCount(), snapshotFrame.stepNanos(), snapshotFrame.snapshotNanos()); return publishFrame(worldEpoch, snapshotFrame, - List.of(), List.of(stepEvent), List.of(), physicsEvents, @@ -78,11 +75,10 @@ public PhysicsEventFrame publishSnapshotPublication(long worldEpoch, snapshotFrame.worldEpoch(), snapshotFrame.stepSequence(), snapshotFrame.serverTick(), - snapshotFrame.lastIncludedCommandBatchSequence(), publicationServerTick, System.nanoTime(), appliedBodyCount); - return publishFrame(worldEpoch, snapshotFrame, List.of(), List.of(), List.of(publicationEvent)); + return publishFrame(worldEpoch, snapshotFrame, List.of(), List.of(publicationEvent)); } @Nonnull @@ -91,19 +87,16 @@ public PhysicsEventFrame publishEmpty(long worldEpoch, return publishFrame(worldEpoch, Objects.requireNonNull(latestCapturedSnapshotFrame, "latestCapturedSnapshotFrame"), List.of(), - List.of(), List.of()); } @Nonnull private PhysicsEventFrame publishFrame(long worldEpoch, @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame, - @Nonnull List commandEvents, @Nonnull List stepEvents, @Nonnull List publicationEvents) { return publishFrame(worldEpoch, latestCapturedSnapshotFrame, - commandEvents, stepEvents, publicationEvents, List.of(), @@ -113,7 +106,6 @@ private PhysicsEventFrame publishFrame(long worldEpoch, @Nonnull private PhysicsEventFrame publishFrame(long worldEpoch, @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame, - @Nonnull List commandEvents, @Nonnull List stepEvents, @Nonnull List publicationEvents, @Nonnull List physicsEvents, @@ -123,8 +115,6 @@ private PhysicsEventFrame publishFrame(long worldEpoch, latestCapturedSnapshotFrame.frameEpoch(), latestCapturedSnapshotFrame.stepSequence(), latestCapturedSnapshotFrame.serverTick(), - latestCapturedSnapshotFrame.lastIncludedCommandBatchSequence(), - commandEvents, stepEvents, publicationEvents, physicsEvents, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index 0ad6da52..c003121c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -90,7 +90,6 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( bodyRegistry, stepSequence, serverTick, - 0L, status, stepNanos, profilingEnabled); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index 38f555db..e328a91e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -71,7 +71,6 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( @Nonnull PhysicsBodyRegistry bodyRegistry, long stepSequence, long serverTick, - long lastIncludedCommandBatchSequence, @Nonnull PublishedPhysicsSnapshotFrame.Status status, long stepNanos, boolean profilingEnabled) { @@ -95,7 +94,6 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( frameWorldEpoch, stepSequence, serverTick, - lastIncludedCommandBatchSequence, status, spatialIndexCellCount, stepNanos, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsCommandBatchEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsCommandBatchEvent.java deleted file mode 100644 index 2a7ffd61..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsCommandBatchEvent.java +++ /dev/null @@ -1,106 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.events; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import javax.annotation.Nullable; - -/** - * Value-only owner-lane outcome for one submitted physics command batch. - * - * @param commandBatchSequence owner FIFO sequence assigned when the command batch was submitted - * @param submittedServerTick server tick supplied by the caller when the batch was recorded - * @param ownerCompletedNanoTime monotonic nano time sampled when the owner-lane command-batch - * outcome event was published, or {@code 0} when unavailable - * @param commandCount number of recorded command operations in the batch - * @param bodyKeyReferenceCount number of body-key references copied from recorded operations - * @param firstBodyKey first copied body key seen in recorded order, if any - * @param jointKeyReferenceCount number of joint-key references copied from recorded operations - * @param firstJointKey first copied joint key seen in recorded order, if any - * @param allApplied whether every command operation in the batch applied successfully - * @param firstRejectedCommandSequence one-based command sequence for the first rejection, or - * {@code 0} when no rejection is known - * @param firstRejectedMessage copied rejection reason for the first rejection, if any - */ -public record PhysicsCommandBatchEvent(long commandBatchSequence, - long submittedServerTick, - long ownerCompletedNanoTime, - int commandCount, - int bodyKeyReferenceCount, - @Nullable RigidBodyKey firstBodyKey, - int jointKeyReferenceCount, - @Nullable JointKey firstJointKey, - boolean allApplied, - long firstRejectedCommandSequence, - @Nullable String firstRejectedMessage) { - - public PhysicsCommandBatchEvent(long commandBatchSequence, - long submittedServerTick, - long ownerCompletedNanoTime, - int commandCount, - boolean allApplied, - long firstRejectedCommandSequence, - @Nullable String firstRejectedMessage) { - this(commandBatchSequence, - submittedServerTick, - ownerCompletedNanoTime, - commandCount, - 0, - null, - 0, - null, - allApplied, - firstRejectedCommandSequence, - firstRejectedMessage); - } - - public PhysicsCommandBatchEvent(long commandBatchSequence, - long submittedServerTick, - int commandCount, - boolean allApplied, - long firstRejectedCommandSequence, - @Nullable String firstRejectedMessage) { - this(commandBatchSequence, - submittedServerTick, - 0L, - commandCount, - 0, - null, - 0, - null, - allApplied, - firstRejectedCommandSequence, - firstRejectedMessage); - } - - public PhysicsCommandBatchEvent { - commandBatchSequence = Math.max(0L, commandBatchSequence); - submittedServerTick = Math.max(0L, submittedServerTick); - ownerCompletedNanoTime = Math.max(0L, ownerCompletedNanoTime); - commandCount = Math.max(0, commandCount); - bodyKeyReferenceCount = Math.max(0, bodyKeyReferenceCount); - if (bodyKeyReferenceCount == 0) { - firstBodyKey = null; - } - jointKeyReferenceCount = Math.max(0, jointKeyReferenceCount); - if (jointKeyReferenceCount == 0) { - firstJointKey = null; - } - firstRejectedCommandSequence = Math.max(0L, firstRejectedCommandSequence); - if (allApplied) { - firstRejectedCommandSequence = 0L; - firstRejectedMessage = null; - } - } - - public boolean hasOwnerCompletionTimestamp() { - return ownerCompletedNanoTime > 0L; - } - - public boolean referencesBodies() { - return bodyKeyReferenceCount > 0; - } - - public boolean referencesJoints() { - return jointKeyReferenceCount > 0; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java index 3b276e23..c172a679 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java @@ -21,9 +21,6 @@ * @param latestCapturedSnapshotStepSequence step-scheduler sequence carried by that latest captured snapshot * frame * @param latestCapturedSnapshotServerTick server tick carried by that latest captured snapshot frame - * @param latestCapturedSnapshotLastIncludedCommandBatchSequence latest command-batch sequence - * included by that captured snapshot frame - * @param commandBatches copied command-batch outcome events in this frame * @param steps copied physics step snapshot-capture events in this frame * @param snapshotPublications copied reader-side snapshot-publication events in this frame * @param physicsEvents copied stable physics events in this frame @@ -34,8 +31,6 @@ public record PhysicsEventFrame(long frameSequence, long latestCapturedSnapshotFrameEpoch, long latestCapturedSnapshotStepSequence, long latestCapturedSnapshotServerTick, - long latestCapturedSnapshotLastIncludedCommandBatchSequence, - @Nonnull List commandBatches, @Nonnull List steps, @Nonnull List snapshotPublications, @Nonnull List physicsEvents, @@ -47,9 +42,6 @@ public record PhysicsEventFrame(long frameSequence, latestCapturedSnapshotFrameEpoch = Math.max(0L, latestCapturedSnapshotFrameEpoch); latestCapturedSnapshotStepSequence = Math.max(0L, latestCapturedSnapshotStepSequence); latestCapturedSnapshotServerTick = Math.max(0L, latestCapturedSnapshotServerTick); - latestCapturedSnapshotLastIncludedCommandBatchSequence = - Math.max(0L, latestCapturedSnapshotLastIncludedCommandBatchSequence); - commandBatches = List.copyOf(Objects.requireNonNull(commandBatches, "commandBatches")); steps = List.copyOf(Objects.requireNonNull(steps, "steps")); snapshotPublications = List.copyOf(Objects.requireNonNull(snapshotPublications, "snapshotPublications")); @@ -60,8 +52,6 @@ public record PhysicsEventFrame(long frameSequence, public PhysicsEventFrame(long frameSequence, long worldEpoch, long latestCapturedSnapshotFrameEpoch, - long latestCapturedSnapshotLastIncludedCommandBatchSequence, - @Nonnull List commandBatches, @Nonnull List steps, @Nonnull List snapshotPublications) { this(frameSequence, @@ -69,32 +59,12 @@ public PhysicsEventFrame(long frameSequence, latestCapturedSnapshotFrameEpoch, 0L, 0L, - latestCapturedSnapshotLastIncludedCommandBatchSequence, - commandBatches, steps, snapshotPublications, List.of(), 0); } - public PhysicsEventFrame(long frameSequence, - long worldEpoch, - long latestCapturedSnapshotFrameEpoch, - long latestCapturedSnapshotLastIncludedCommandBatchSequence, - @Nonnull List commandBatches) { - this(frameSequence, - worldEpoch, - latestCapturedSnapshotFrameEpoch, - 0L, - 0L, - latestCapturedSnapshotLastIncludedCommandBatchSequence, - commandBatches, - List.of(), - List.of(), - List.of(), - 0); - } - @Nonnull public static PhysicsEventFrame empty(long worldEpoch) { return new PhysicsEventFrame(0L, @@ -102,18 +72,12 @@ public static PhysicsEventFrame empty(long worldEpoch) { 0L, 0L, 0L, - 0L, - List.of(), List.of(), List.of(), List.of(), 0); } - public int commandBatchCount() { - return commandBatches.size(); - } - public int stepCount() { return steps.size(); } @@ -127,19 +91,13 @@ public int physicsEventCount() { } public int eventCount() { - return commandBatchCount() + stepCount() + snapshotPublicationCount() + physicsEventCount(); + return stepCount() + snapshotPublicationCount() + physicsEventCount(); } public boolean isEmpty() { return eventCount() == 0; } - @Nullable - public PhysicsCommandBatchEvent latestCommandBatch() { - int count = commandBatches.size(); - return count == 0 ? null : commandBatches.get(count - 1); - } - @Nullable public PhysicsStepEvent latestStep() { int count = steps.size(); @@ -152,23 +110,6 @@ public PhysicsSnapshotPublicationEvent latestSnapshotPublication() { return count == 0 ? null : snapshotPublications.get(count - 1); } - public boolean latestCapturedSnapshotIncludes(@Nonnull PhysicsCommandBatchEvent event) { - return latestCapturedSnapshotIncludesCommandBatch( - Objects.requireNonNull(event, "event").commandBatchSequence()); - } - - public boolean latestCapturedSnapshotIncludesCommandBatch(long commandBatchSequence) { - return commandBatchSequence > 0L - && latestCapturedSnapshotLastIncludedCommandBatchSequence >= commandBatchSequence; - } - - public long capturedSnapshotServerTickLatency(@Nonnull PhysicsCommandBatchEvent event) { - if (!latestCapturedSnapshotIncludes(event)) { - return 0L; - } - return latestCapturedSnapshotServerTickLatencyFromSubmittedTick(event.submittedServerTick()); - } - public long latestCapturedSnapshotServerTickLatencyFromSubmittedTick(long submittedServerTick) { long submitted = Math.max(0L, submittedServerTick); if (latestCapturedSnapshotServerTick <= 0L || latestCapturedSnapshotServerTick < submitted) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java index b87e7a79..bf35e90b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java @@ -1,7 +1,5 @@ package dev.hytalemodding.impulse.core.plugin.events; -import javax.annotation.Nonnull; - /** * Value-only event for applying a published snapshot frame to reader-side stores. * @@ -9,7 +7,6 @@ * @param worldEpoch frame world epoch * @param stepSequence Impulse step-scheduler sequence carried by the frame * @param serverTick Hytale server tick carried by the frame - * @param lastIncludedCommandBatchSequence latest completed command batch included by the frame * @param publicationServerTick Hytale server tick observed when this frame was applied to * reader-side stores, or {@code 0} when unavailable * @param publicationNanoTime monotonic nano time sampled when this publication event was created, @@ -20,7 +17,6 @@ public record PhysicsSnapshotPublicationEvent(long snapshotFrameEpoch, long worldEpoch, long stepSequence, long serverTick, - long lastIncludedCommandBatchSequence, long publicationServerTick, long publicationNanoTime, int appliedBodyCount) { @@ -29,13 +25,11 @@ public PhysicsSnapshotPublicationEvent(long snapshotFrameEpoch, long worldEpoch, long stepSequence, long serverTick, - long lastIncludedCommandBatchSequence, int appliedBodyCount) { this(snapshotFrameEpoch, worldEpoch, stepSequence, serverTick, - lastIncludedCommandBatchSequence, 0L, 0L, appliedBodyCount); @@ -46,40 +40,15 @@ public PhysicsSnapshotPublicationEvent(long snapshotFrameEpoch, worldEpoch = Math.max(0L, worldEpoch); stepSequence = Math.max(0L, stepSequence); serverTick = Math.max(0L, serverTick); - lastIncludedCommandBatchSequence = Math.max(0L, lastIncludedCommandBatchSequence); publicationServerTick = Math.max(0L, publicationServerTick); publicationNanoTime = Math.max(0L, publicationNanoTime); appliedBodyCount = Math.max(0, appliedBodyCount); } - public boolean includesCommandBatch(long commandBatchSequence) { - return commandBatchSequence > 0L - && lastIncludedCommandBatchSequence >= commandBatchSequence; - } - - public boolean includesCommandBatch(@Nonnull PhysicsCommandBatchEvent event) { - return includesCommandBatch(event.commandBatchSequence()); - } - public long frameToPublicationServerTickLatency() { if (publicationServerTick <= 0L || publicationServerTick < serverTick) { return 0L; } return publicationServerTick - serverTick; } - - public long commandToPublicationServerTickLatency(@Nonnull PhysicsCommandBatchEvent event) { - if (!includesCommandBatch(event)) { - return 0L; - } - return serverTickLatencyFrom(event.submittedServerTick()); - } - - private long serverTickLatencyFrom(long submittedServerTick) { - long submitted = Math.max(0L, submittedServerTick); - if (publicationServerTick <= 0L || publicationServerTick < submitted) { - return 0L; - } - return publicationServerTick - submitted; - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java index 584862ad..e3682329 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java @@ -11,7 +11,6 @@ * @param serverTick Hytale server tick copied into the captured snapshot * @param snapshotFrameEpoch captured snapshot frame epoch * @param snapshotStatus captured snapshot status - * @param lastIncludedCommandBatchSequence latest completed command batch included by the capture * @param bodyCount number of body snapshots captured in the frame * @param stepNanos profiled step duration, or {@code 0} when profiling was disabled * @param snapshotNanos profiled snapshot capture duration, or {@code 0} when profiling was disabled @@ -20,7 +19,6 @@ public record PhysicsStepEvent(long stepSequence, long serverTick, long snapshotFrameEpoch, @Nonnull PublishedPhysicsSnapshotFrame.Status snapshotStatus, - long lastIncludedCommandBatchSequence, int bodyCount, long stepNanos, long snapshotNanos) { @@ -30,7 +28,6 @@ public record PhysicsStepEvent(long stepSequence, serverTick = Math.max(0L, serverTick); snapshotFrameEpoch = Math.max(0L, snapshotFrameEpoch); Objects.requireNonNull(snapshotStatus, "snapshotStatus"); - lastIncludedCommandBatchSequence = Math.max(0L, lastIncludedCommandBatchSequence); bodyCount = Math.max(0, bodyCount); stepNanos = Math.max(0L, stepNanos); snapshotNanos = Math.max(0L, snapshotNanos); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java index 65074814..1c3ace9a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java @@ -44,12 +44,6 @@ public enum Status { */ private final long serverTick; - /** - * Latest command-batch sequence whose owner-lane execution completed before this frame was - * captured. - * Command completion can precede inclusion in a later captured frame. - */ - private final long lastIncludedCommandBatchSequence; @Nonnull private final Status status; private final int spatialIndexCellCount; @@ -73,30 +67,6 @@ public PublishedPhysicsSnapshotFrame(long frameEpoch, worldEpoch, stepSequence, serverTick, - 0L, - status, - spatialIndexCellCount, - stepNanos, - snapshotNanos, - null, - copyAndValidateSpaces(frameEpoch, worldEpoch, spaces)); - } - - public PublishedPhysicsSnapshotFrame(long frameEpoch, - long worldEpoch, - long stepSequence, - long serverTick, - long lastIncludedCommandBatchSequence, - @Nonnull Status status, - int spatialIndexCellCount, - long stepNanos, - long snapshotNanos, - @Nonnull List spaces) { - this(frameEpoch, - worldEpoch, - stepSequence, - serverTick, - lastIncludedCommandBatchSequence, status, spatialIndexCellCount, stepNanos, @@ -109,7 +79,6 @@ private PublishedPhysicsSnapshotFrame(long frameEpoch, long worldEpoch, long stepSequence, long serverTick, - long lastIncludedCommandBatchSequence, @Nonnull Status status, int spatialIndexCellCount, long stepNanos, @@ -119,7 +88,6 @@ private PublishedPhysicsSnapshotFrame(long frameEpoch, worldEpoch, stepSequence, serverTick, - lastIncludedCommandBatchSequence, status, spatialIndexCellCount, stepNanos, @@ -132,7 +100,6 @@ private PublishedPhysicsSnapshotFrame(long frameEpoch, long worldEpoch, long stepSequence, long serverTick, - long lastIncludedCommandBatchSequence, @Nonnull Status status, int spatialIndexCellCount, long stepNanos, @@ -143,7 +110,6 @@ private PublishedPhysicsSnapshotFrame(long frameEpoch, worldEpoch, stepSequence, serverTick, - lastIncludedCommandBatchSequence, status, spatialIndexCellCount, stepNanos, @@ -152,7 +118,6 @@ private PublishedPhysicsSnapshotFrame(long frameEpoch, this.worldEpoch = worldEpoch; this.stepSequence = stepSequence; this.serverTick = serverTick; - this.lastIncludedCommandBatchSequence = lastIncludedCommandBatchSequence; this.status = status; this.spatialIndexCellCount = spatialIndexCellCount; this.stepNanos = stepNanos; @@ -167,7 +132,6 @@ public static PublishedPhysicsSnapshotFrame empty(long frameEpoch, long worldEpo worldEpoch, 0L, 0L, - 0L, Status.EMPTY, 0, 0L, @@ -198,31 +162,6 @@ public static Builder compactBuilder(long frameEpoch, expectedBodies); } - @Nonnull - public static Builder compactBuilder(long frameEpoch, - long worldEpoch, - long stepSequence, - long serverTick, - long lastIncludedCommandBatchSequence, - @Nonnull Status status, - int spatialIndexCellCount, - long stepNanos, - long snapshotNanos, - int expectedSpaces, - int expectedBodies) { - return new Builder(frameEpoch, - worldEpoch, - stepSequence, - serverTick, - lastIncludedCommandBatchSequence, - status, - spatialIndexCellCount, - stepNanos, - snapshotNanos, - expectedSpaces, - expectedBodies); - } - public long frameEpoch() { return frameEpoch; } @@ -239,25 +178,8 @@ public long serverTick() { return serverTick; } - public long lastIncludedCommandBatchSequence() { - return lastIncludedCommandBatchSequence; - } - /** - * Returns whether this snapshot frame is known to include owner-lane execution of the - * submitted command batch sequence. - * - *

This is the latest owner-executed command-batch sequence included by the captured - * snapshot. It does not mean a separate ECS visual sync pass has already consumed the frame. - * Batches at or below this sequence are included in this frame's copied body data.

- */ - public boolean includesCommandBatch(long commandBatchSequence) { - return commandBatchSequence > 0L - && lastIncludedCommandBatchSequence >= commandBatchSequence; - } - - /** - * Returns the server-tick distance from a submitted command to this snapshot frame, or + * Returns the server-tick distance from a submitted action to this snapshot frame, or * {@code 0} when this frame has no usable server-tick metadata. */ public long serverTickLatencyFromSubmittedTick(long submittedServerTick) { @@ -367,7 +289,6 @@ public boolean equals(@Nullable Object other) { && worldEpoch == that.worldEpoch && stepSequence == that.stepSequence && serverTick == that.serverTick - && lastIncludedCommandBatchSequence == that.lastIncludedCommandBatchSequence && spatialIndexCellCount == that.spatialIndexCellCount && stepNanos == that.stepNanos && snapshotNanos == that.snapshotNanos @@ -381,7 +302,6 @@ public int hashCode() { result = 31 * result + Long.hashCode(worldEpoch); result = 31 * result + Long.hashCode(stepSequence); result = 31 * result + Long.hashCode(serverTick); - result = 31 * result + Long.hashCode(lastIncludedCommandBatchSequence); result = 31 * result + status.hashCode(); result = 31 * result + Integer.hashCode(spatialIndexCellCount); result = 31 * result + Long.hashCode(stepNanos); @@ -397,7 +317,6 @@ public String toString() { + ", worldEpoch=" + worldEpoch + ", stepSequence=" + stepSequence + ", serverTick=" + serverTick - + ", lastIncludedCommandBatchSequence=" + lastIncludedCommandBatchSequence + ", status=" + status + ", spatialIndexCellCount=" + spatialIndexCellCount + ", stepNanos=" + stepNanos @@ -443,7 +362,6 @@ private static void requireFrameValues(long frameEpoch, long worldEpoch, long stepSequence, long serverTick, - long lastIncludedCommandBatchSequence, @Nonnull Status status, int spatialIndexCellCount, long stepNanos, @@ -452,7 +370,6 @@ private static void requireFrameValues(long frameEpoch, requireNonNegativeEpoch(worldEpoch, "worldEpoch"); requireNonNegativeEpoch(stepSequence, "stepSequence"); requireNonNegativeEpoch(serverTick, "serverTick"); - requireNonNegativeEpoch(lastIncludedCommandBatchSequence, "lastIncludedCommandBatchSequence"); Objects.requireNonNull(status, "status"); if (spatialIndexCellCount < 0) { throw new IllegalArgumentException("spatialIndexCellCount cannot be negative"); @@ -484,7 +401,6 @@ public static final class Builder { private final long worldEpoch; private final long stepSequence; private final long serverTick; - private final long lastIncludedCommandBatchSequence; @Nonnull private final Status status; private final int spatialIndexCellCount; @@ -502,35 +418,10 @@ private Builder(long frameEpoch, long snapshotNanos, int expectedSpaces, int expectedBodies) { - this(frameEpoch, - worldEpoch, - stepSequence, - serverTick, - 0L, - status, - spatialIndexCellCount, - stepNanos, - snapshotNanos, - expectedSpaces, - expectedBodies); - } - - private Builder(long frameEpoch, - long worldEpoch, - long stepSequence, - long serverTick, - long lastIncludedCommandBatchSequence, - @Nonnull Status status, - int spatialIndexCellCount, - long stepNanos, - long snapshotNanos, - int expectedSpaces, - int expectedBodies) { requireFrameValues(frameEpoch, worldEpoch, stepSequence, serverTick, - lastIncludedCommandBatchSequence, status, spatialIndexCellCount, stepNanos, @@ -539,7 +430,6 @@ private Builder(long frameEpoch, this.worldEpoch = worldEpoch; this.stepSequence = stepSequence; this.serverTick = serverTick; - this.lastIncludedCommandBatchSequence = lastIncludedCommandBatchSequence; this.status = status; this.spatialIndexCellCount = spatialIndexCellCount; this.stepNanos = stepNanos; @@ -580,7 +470,6 @@ public PublishedPhysicsSnapshotFrame build() { worldEpoch, stepSequence, serverTick, - lastIncludedCommandBatchSequence, status, spatialIndexCellCount, stepNanos, From b7f17cc5aa97b5dcd1bd34b0d2f9c64a4e8dec0d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:03:37 +0200 Subject: [PATCH 108/534] refactor(control): store session body ids as uuids Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 9 +-- .../control/PhysicsControlRuntimeState.java | 56 ++++++++++++++++--- .../PhysicsControlSessionComponent.java | 43 ++++++++++++-- .../systems/PhysicsControlSessionCleanup.java | 10 ++-- .../PhysicsControlSessionCleanupSystem.java | 4 +- .../PhysicsKinematicControlSystem.java | 54 +++++++++--------- .../PhysicsStoreControlSessionMutations.java | 13 ++--- .../PhysicsWorldRuntimeResource.java | 12 ++++ .../control/PhysicsControlSessions.java | 49 ++++++++++++---- 9 files changed, 179 insertions(+), 71 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index d1e9ec1f..9ea4b3ca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; +import java.util.UUID; import java.util.concurrent.atomic.AtomicIntegerArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -242,8 +243,8 @@ private static boolean controlSessionSelected( @Nonnull Set selectedBodyKeys, @Nonnull Vector3d center, double radiusSquared) { - if (containsBody(selectedBodyKeys, session.getBodyKey()) - || containsBody(selectedBodyKeys, session.getAnchorBodyKey()) + if (containsBody(selectedBodyKeys, session.getBodyUuid()) + || containsBody(selectedBodyKeys, session.getAnchorBodyUuid()) || entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { return true; } @@ -268,8 +269,8 @@ private static ComponentType contro } private static boolean containsBody(@Nonnull Set bodyKeys, - @Nullable RigidBodyKey bodyKey) { - return bodyKey != null && bodyKeys.contains(bodyKey); + @Nullable UUID bodyUuid) { + return bodyUuid != null && bodyKeys.contains(RigidBodyKey.of(bodyUuid)); } private static boolean entityWithinRadius(@Nonnull ArchetypeChunk archetypeChunk, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java index 3e0ec1b8..78f0558f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java @@ -1,8 +1,10 @@ package dev.hytalemodding.impulse.core.internal.modules.control; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.Set; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.util.UUID; import javax.annotation.Nonnull; /** @@ -10,25 +12,63 @@ */ public final class PhysicsControlRuntimeState { - private final Set controlledBodies = new ObjectOpenHashSet<>(); + private final Long2ObjectOpenHashMap controlledBodyLeastBitsByMostBits = + new Long2ObjectOpenHashMap<>(); + + public synchronized void markBodyControlled(@Nonnull UUID bodyUuid) { + add(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); + } public synchronized void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { - controlledBodies.add(bodyKey); + add(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); + } + + public synchronized void clearControlledBody(@Nonnull UUID bodyUuid) { + remove(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); } public synchronized void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { - controlledBodies.remove(bodyKey); + remove(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); + } + + public synchronized boolean isBodyControlled(@Nonnull UUID bodyUuid) { + return contains(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); } public synchronized boolean isBodyControlled(@Nonnull RigidBodyKey bodyKey) { - return controlledBodies.contains(bodyKey); + return contains(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); + } + + public synchronized void clearBody(@Nonnull UUID bodyUuid) { + remove(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); } public synchronized void clearBody(@Nonnull RigidBodyKey bodyKey) { - controlledBodies.remove(bodyKey); + remove(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); } public synchronized void clear() { - controlledBodies.clear(); + controlledBodyLeastBitsByMostBits.clear(); + } + + private void add(long mostSignificantBits, long leastSignificantBits) { + controlledBodyLeastBitsByMostBits.computeIfAbsent(mostSignificantBits, + _ -> new LongOpenHashSet()).add(leastSignificantBits); + } + + private void remove(long mostSignificantBits, long leastSignificantBits) { + LongSet leastBits = controlledBodyLeastBitsByMostBits.get(mostSignificantBits); + if (leastBits == null) { + return; + } + leastBits.remove(leastSignificantBits); + if (leastBits.isEmpty()) { + controlledBodyLeastBitsByMostBits.remove(mostSignificantBits); + } + } + + private boolean contains(long mostSignificantBits, long leastSignificantBits) { + LongSet leastBits = controlledBodyLeastBitsByMostBits.get(mostSignificantBits); + return leastBits != null && leastBits.contains(leastSignificantBits); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java index 95e5d8c9..0033e5c5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; @@ -21,9 +22,9 @@ public class PhysicsControlSessionComponent implements Component { private static ComponentType componentType; @Nullable - private RigidBodyKey bodyKey; + private UUID bodyUuid; @Nullable - private RigidBodyKey anchorBodyKey; + private UUID anchorBodyUuid; @Nullable private JointKey controlJointKey; @Nullable @@ -55,8 +56,28 @@ public PhysicsControlSessionComponent(@Nonnull RigidBodyKey bodyKey, float grabDistance, @Nonnull Vector3f viewOffset, @Nonnull Vector3f previousTarget) { - this.bodyKey = bodyKey; - this.anchorBodyKey = anchorBodyKey; + this(bodyKey.value(), + anchorBodyKey.value(), + controlJointKey, + targetRef, + spaceId, + originalBodyType, + grabDistance, + viewOffset, + previousTarget); + } + + public PhysicsControlSessionComponent(@Nonnull UUID bodyUuid, + @Nonnull UUID anchorBodyUuid, + @Nullable JointKey controlJointKey, + @Nullable Ref targetRef, + @Nullable SpaceId spaceId, + @Nonnull PhysicsBodyType originalBodyType, + float grabDistance, + @Nonnull Vector3f viewOffset, + @Nonnull Vector3f previousTarget) { + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + this.anchorBodyUuid = Objects.requireNonNull(anchorBodyUuid, "anchorBodyUuid"); this.controlJointKey = controlJointKey; this.targetRef = targetRef; this.spaceId = spaceId; @@ -67,6 +88,16 @@ public PhysicsControlSessionComponent(@Nonnull RigidBodyKey bodyKey, this.active = true; } + @Nullable + public RigidBodyKey getBodyKey() { + return bodyUuid != null ? RigidBodyKey.of(bodyUuid) : null; + } + + @Nullable + public RigidBodyKey getAnchorBodyKey() { + return anchorBodyUuid != null ? RigidBodyKey.of(anchorBodyUuid) : null; + } + public static void setComponentType( @Nonnull ComponentType type) { componentType = Objects.requireNonNull(type, "type"); @@ -96,8 +127,8 @@ public void deactivate() { @Override public PhysicsControlSessionComponent clone() { PhysicsControlSessionComponent copy = new PhysicsControlSessionComponent(); - copy.bodyKey = bodyKey; - copy.anchorBodyKey = anchorBodyKey; + copy.bodyUuid = bodyUuid; + copy.anchorBodyUuid = anchorBodyUuid; copy.controlJointKey = controlJointKey; copy.targetRef = targetRef; copy.spaceId = spaceId; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java index 1426527f..a47c0d33 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import java.util.UUID; import javax.annotation.Nonnull; public final class PhysicsControlSessionCleanup { @@ -26,14 +26,14 @@ public static void cleanup(@Nonnull Store store, private static void cleanupInternal(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull PhysicsControlSessionComponent session) { - PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyKey()); + PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyUuid()); if (!session.isActive()) { return; } - RigidBodyKey bodyKey = session.getBodyKey(); - if (bodyKey != null) { - resource.clearControlledBody(bodyKey); + UUID bodyUuid = session.getBodyUuid(); + if (bodyUuid != null) { + resource.clearControlledBody(bodyUuid); } PhysicsStoreControlSessionMutations.applyRelease(store, session); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java index aaca490a..1aa1c9a3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java @@ -86,8 +86,8 @@ public Query getQuery() { private static boolean sameSessionOwner(@Nonnull PhysicsControlSessionComponent first, @Nonnull PhysicsControlSessionComponent second) { - return Objects.equals(first.getBodyKey(), second.getBodyKey()) - && Objects.equals(first.getAnchorBodyKey(), second.getAnchorBodyKey()) + return Objects.equals(first.getBodyUuid(), second.getBodyUuid()) + && Objects.equals(first.getAnchorBodyUuid(), second.getAnchorBodyUuid()) && Objects.equals(first.getControlJointKey(), second.getControlJointKey()) && Objects.equals(first.getSpaceId(), second.getSpaceId()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 1e984e68..c71ee559 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -22,9 +22,7 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; @@ -93,11 +91,11 @@ public void tick(float dt, return; } - RigidBodyKey bodyKey = session.getBodyKey(); - RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); + UUID bodyUuid = session.getBodyUuid(); + UUID anchorBodyUuid = session.getAnchorBodyUuid(); Ref targetRef = session.getTargetRef(); - if (bodyKey == null || anchorBodyKey == null || (targetRef != null && !targetRef.isValid())) { - stateFor(store).clear(anchorBodyKey); + if (bodyUuid == null || anchorBodyUuid == null || (targetRef != null && !targetRef.isValid())) { + stateFor(store).clear(anchorBodyUuid); PhysicsControlSessionCleanup.cleanup(store, session); commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); return; @@ -141,24 +139,24 @@ public void tick(float dt, previousTarget.set(local.target); ControlMutationState state = stateFor(store); - ControlAnchorUpdate update = new ControlAnchorUpdate(bodyKey, - anchorBodyKey, + ControlAnchorUpdate update = new ControlAnchorUpdate(bodyUuid, + anchorBodyUuid, local.target, releaseVelocity); - ControlAnchorUpdate readyUpdate = state.selectReadyUpdate(anchorBodyKey, update); + ControlAnchorUpdate readyUpdate = state.selectReadyUpdate(anchorBodyUuid, update); if (readyUpdate == null) { return; } PhysicsStoreControlTargets physicsStoreTargets = - resolvePhysicsStoreTargets(store, bodyKey, anchorBodyKey); + resolvePhysicsStoreTargets(store, bodyUuid, anchorBodyUuid); if (physicsStoreTargets != null) { physicsStoreTargets.apply(readyUpdate); - state.trackSubmittedMutation(anchorBodyKey, readyUpdate); + state.trackSubmittedMutation(anchorBodyUuid, readyUpdate); return; } - stateFor(store).clear(anchorBodyKey); + stateFor(store).clear(anchorBodyUuid); PhysicsControlSessionCleanup.cleanup(store, session); commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); } @@ -166,8 +164,8 @@ public void tick(float dt, @Nullable private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( @Nonnull Store store, - @Nonnull RigidBodyKey bodyKey, - @Nonnull RigidBodyKey anchorBodyKey) { + @Nonnull UUID bodyUuid, + @Nonnull UUID anchorBodyUuid) { PhysicsStore physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); Store physics = physicsStore.getStore(); @@ -175,8 +173,6 @@ private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( "resolve PhysicsStore kinematic control targets"); PhysicsIdentityIndexResource identity = physics.getResource( PhysicsIdentityIndexResource.getResourceType()); - UUID bodyUuid = bodyKey.value(); - UUID anchorBodyUuid = anchorBodyKey.value(); Ref bodyRef = bodyRef(physics, identity, bodyUuid); Ref anchorBodyRef = bodyRef(physics, identity, anchorBodyUuid); if (bodyRef == null || anchorBodyRef == null) { @@ -251,14 +247,14 @@ static ControlMutationState stateFor(@Nonnull Store store) { } public static void clearMutationState(@Nonnull Store store, - @Nullable RigidBodyKey anchorBodyKey) { - if (anchorBodyKey != null) { - stateFor(store).clear(anchorBodyKey); + @Nullable UUID anchorBodyUuid) { + if (anchorBodyUuid != null) { + stateFor(store).clear(anchorBodyUuid); } } - record ControlAnchorUpdate(@Nonnull RigidBodyKey bodyKey, - @Nonnull RigidBodyKey anchorBodyKey, + record ControlAnchorUpdate(@Nonnull UUID bodyUuid, + @Nonnull UUID anchorBodyUuid, @Nonnull Vector3f target, @Nonnull Vector3f releaseVelocity) { @@ -308,27 +304,27 @@ private static TargetComponent target(@Nonnull Vector3f position, static final class ControlMutationState { @Nonnull - private final Object2ObjectMap submittedUpdates = + private final Object2ObjectMap submittedUpdates = new Object2ObjectOpenHashMap<>(); @Nullable - synchronized ControlAnchorUpdate selectReadyUpdate(@Nonnull RigidBodyKey bodyKey, + synchronized ControlAnchorUpdate selectReadyUpdate(@Nonnull UUID bodyUuid, @Nonnull ControlAnchorUpdate currentUpdate) { - ControlAnchorUpdate submittedUpdate = submittedUpdates.get(bodyKey); + ControlAnchorUpdate submittedUpdate = submittedUpdates.get(bodyUuid); if (sameTarget(currentUpdate, submittedUpdate)) { return null; } return currentUpdate; } - synchronized void trackSubmittedMutation(@Nonnull RigidBodyKey bodyKey, + synchronized void trackSubmittedMutation(@Nonnull UUID bodyUuid, @Nonnull ControlAnchorUpdate submittedUpdate) { - submittedUpdates.put(bodyKey, submittedUpdate); + submittedUpdates.put(bodyUuid, submittedUpdate); } - synchronized void clear(@Nullable RigidBodyKey bodyKey) { - if (bodyKey != null) { - submittedUpdates.remove(bodyKey); + synchronized void clear(@Nullable UUID bodyUuid) { + if (bodyUuid != null) { + submittedUpdates.remove(bodyUuid); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 09b6f1ab..48d0375c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -9,7 +9,6 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; @@ -45,18 +44,18 @@ public static void applyRelease(@Nonnull Store store, disableJoint(physicsStore, identity, controlJointKey.value()); } - RigidBodyKey bodyKey = session.getBodyKey(); - if (bodyKey != null) { + UUID bodyUuid = session.getBodyUuid(); + if (bodyUuid != null) { restoreControlledBody(physicsStore, identity, - bodyKey.value(), + bodyUuid, session.getOriginalBodyType(), releaseVelocity(session)); } - RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); - if (anchorBodyKey != null) { - removeRow(physicsStore, identity, anchorBodyKey.value(), refForUuid(identity, anchorBodyKey.value())); + UUID anchorBodyUuid = session.getAnchorBodyUuid(); + if (anchorBodyUuid != null) { + removeRow(physicsStore, identity, anchorBodyUuid, refForUuid(identity, anchorBodyUuid)); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index fe68426d..5e7d764d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1890,14 +1890,26 @@ public long advanceVisualInterestTick() { return visualInterestTick.incrementAndGet(); } + public void markBodyControlled(@Nonnull UUID bodyUuid) { + controlRuntime.markBodyControlled(bodyUuid); + } + public void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { controlRuntime.markBodyControlled(bodyKey); } + public void clearControlledBody(@Nonnull UUID bodyUuid) { + controlRuntime.clearControlledBody(bodyUuid); + } + public void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { controlRuntime.clearControlledBody(bodyKey); } + public boolean isBodyControlled(@Nonnull UUID bodyUuid) { + return controlRuntime.isBodyControlled(bodyUuid); + } + public boolean isBodyControlled(@Nonnull RigidBodyKey bodyKey) { return controlRuntime.isBodyControlled(bodyKey); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 63cdab64..c783f736 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -63,8 +64,8 @@ public static void startSession(@Nonnull Store store, @Nonnull Vector3f previousTarget) { startSession(store, controllerRef, - bodyKey, - anchorBodyKey, + bodyKey.value(), + anchorBodyKey.value(), null, targetRef, spaceId, @@ -89,6 +90,34 @@ public static void startSession(@Nonnull Store store, float grabDistance, @Nonnull Vector3f viewOffset, @Nonnull Vector3f previousTarget) { + startSession(store, + controllerRef, + bodyKey.value(), + anchorBodyKey.value(), + controlJointKey, + targetRef, + spaceId, + originalBodyType, + grabDistance, + viewOffset, + previousTarget); + } + + /** + * Starts or replaces the controller entity's Impulse control session with the created control + * joint handle. + */ + public static void startSession(@Nonnull Store store, + @Nonnull Ref controllerRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID anchorBodyUuid, + @Nullable JointKey controlJointKey, + @Nullable Ref targetRef, + @Nullable SpaceId spaceId, + @Nonnull PhysicsBodyType originalBodyType, + float grabDistance, + @Nonnull Vector3f viewOffset, + @Nonnull Vector3f previousTarget) { requireAvailable(); ControlLifecycle.registerStore(store); PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); @@ -97,8 +126,8 @@ public static void startSession(@Nonnull Store store, releaseSession(resource, store, controllerRef, sessionType); store.putComponent(controllerRef, sessionType, - new PhysicsControlSessionComponent(bodyKey, - anchorBodyKey, + new PhysicsControlSessionComponent(bodyUuid, + anchorBodyUuid, controlJointKey, targetRef, spaceId, @@ -106,7 +135,7 @@ public static void startSession(@Nonnull Store store, grabDistance, viewOffset, previousTarget)); - resource.markBodyControlled(bodyKey); + resource.markBodyControlled(bodyUuid); } /** @@ -144,11 +173,11 @@ private static void releaseSession(@Nonnull PhysicsWorldRuntimeResource resource @Nonnull Ref controllerRef, @Nonnull ComponentType sessionType, @Nonnull PhysicsControlSessionComponent session) { - RigidBodyKey bodyKey = session.getBodyKey(); - RigidBodyKey anchorBodyKey = session.getAnchorBodyKey(); - PhysicsKinematicControlSystem.clearMutationState(store, anchorBodyKey); - if (bodyKey != null) { - resource.clearControlledBody(bodyKey); + UUID bodyUuid = session.getBodyUuid(); + UUID anchorBodyUuid = session.getAnchorBodyUuid(); + PhysicsKinematicControlSystem.clearMutationState(store, anchorBodyUuid); + if (bodyUuid != null) { + resource.clearControlledBody(bodyUuid); } PhysicsStoreControlSessionMutations.applyRelease(store, session); From 5dacbf29db158ac794ace9f7d4614ac35a605cbd Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:09:53 +0200 Subject: [PATCH 109/534] refactor(physicsstore): remove attachment space duplication Signed-off-by: Blovien --- .../projection/BodyAttachmentComponent.java | 122 +----------------- 1 file changed, 1 insertion(+), 121 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java index d04acd6e..133e7296 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; import java.util.Objects; @@ -35,11 +34,6 @@ public class BodyAttachmentComponent implements Component { (component, value) -> component.bodyUuid = value != null ? value : UUID.randomUUID(), BodyAttachmentComponent::getBodyUuid) .add() - .append(new KeyedCodec<>("SpaceId", Codec.INTEGER, false), - (component, _) -> { - }, - _ -> null) - .add() .append(new KeyedCodec<>("TransformAuthority", new EnumCodec<>(TransformAuthority.class), false), (component, value) -> component.transformAuthority = value != null ? value @@ -93,13 +87,7 @@ public BodyAttachmentComponent() { } public BodyAttachmentComponent(@Nonnull UUID bodyUuid) { - this(bodyUuid, null); - } - - public BodyAttachmentComponent(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId) { this(bodyUuid, - spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY, new Vector3f(), @@ -109,32 +97,15 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nonnull TransformAuthority transformAuthority, @Nonnull AttachmentLifecycle lifecycle) { - this(bodyUuid, null, transformAuthority, lifecycle); - } - - public BodyAttachmentComponent(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId, - @Nonnull TransformAuthority transformAuthority, - @Nonnull AttachmentLifecycle lifecycle) { - this(bodyUuid, spaceId, transformAuthority, lifecycle, new Vector3f(), new Quaternionf()); + this(bodyUuid, transformAuthority, lifecycle, new Vector3f(), new Quaternionf()); } public BodyAttachmentComponent(@Nonnull UUID bodyUuid, - @Nonnull TransformAuthority transformAuthority, - @Nonnull AttachmentLifecycle lifecycle, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset) { - this(bodyUuid, null, transformAuthority, lifecycle, localPositionOffset, localRotationOffset); - } - - public BodyAttachmentComponent(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @Nonnull AttachmentLifecycle lifecycle, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset) { this(bodyUuid, - spaceId, transformAuthority, lifecycle, localPositionOffset, @@ -143,22 +114,6 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, } public BodyAttachmentComponent(@Nonnull UUID bodyUuid, - @Nonnull TransformAuthority transformAuthority, - @Nonnull AttachmentLifecycle lifecycle, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - float visualOriginOffsetY) { - this(bodyUuid, - null, - transformAuthority, - lifecycle, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY); - } - - public BodyAttachmentComponent(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId, @Nonnull TransformAuthority transformAuthority, @Nonnull AttachmentLifecycle lifecycle, @Nonnull Vector3f localPositionOffset, @@ -176,32 +131,16 @@ public BodyAttachmentComponent(@Nonnull UUID bodyUuid, @Nonnull public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid) { - return externalEntity(bodyUuid, null); - } - - @Nonnull - public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId) { return new BodyAttachmentComponent(bodyUuid, - spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY); } @Nonnull public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset) { - return externalEntity(bodyUuid, null, localPositionOffset, localRotationOffset); - } - - @Nonnull - public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset) { return new BodyAttachmentComponent(bodyUuid, - spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY, localPositionOffset, @@ -210,24 +149,10 @@ public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nonnull public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - float visualOriginOffsetY) { - return externalEntity(bodyUuid, - null, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY); - } - - @Nonnull - public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY) { return new BodyAttachmentComponent(bodyUuid, - spaceId, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY, localPositionOffset, @@ -237,24 +162,10 @@ public static BodyAttachmentComponent externalEntity(@Nonnull UUID bodyUuid, @Nonnull public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - float visualOriginOffsetY) { - return impulseOwnedVisual(bodyUuid, - null, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY); - } - - @Nonnull - public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY) { return new BodyAttachmentComponent(bodyUuid, - spaceId, TransformAuthority.BODY, AttachmentLifecycle.IMPULSE_OWNED_VISUAL, localPositionOffset, @@ -264,24 +175,10 @@ public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, @Nonnull public static BodyAttachmentComponent generatedProxy(@Nonnull UUID bodyUuid, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - float visualOriginOffsetY) { - return generatedProxy(bodyUuid, - null, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY); - } - - @Nonnull - public static BodyAttachmentComponent generatedProxy(@Nonnull UUID bodyUuid, - @Nullable SpaceId spaceId, @Nonnull Vector3f localPositionOffset, @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY) { return new BodyAttachmentComponent(bodyUuid, - spaceId, TransformAuthority.BODY, AttachmentLifecycle.GENERATED_PROXY, localPositionOffset, @@ -298,22 +195,6 @@ public void setBodyUuid(@Nonnull UUID bodyUuid) { this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); } - /** - * Legacy compatibility hook. Space ownership lives in the PhysicsStore body row. - */ - @Deprecated(forRemoval = true) - @Nullable - public SpaceId getSpaceId() { - return null; - } - - /** - * Legacy compatibility hook. Space ownership lives in the PhysicsStore body row. - */ - @Deprecated(forRemoval = true) - public void setSpaceId(@Nullable SpaceId spaceId) { - } - @Nonnull public TransformAuthority getTransformAuthority() { return transformAuthority; @@ -349,7 +230,6 @@ public static ComponentType getComponentTy @Override public BodyAttachmentComponent clone() { return new BodyAttachmentComponent(bodyUuid, - null, transformAuthority, lifecycle, localPositionOffset, From 30f6c6c1862adf5bda3d866f142e4676926f841f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:15:15 +0200 Subject: [PATCH 110/534] refactor(control): remove session space duplication Signed-off-by: Blovien --- .../components/PhysicsControlSessionComponent.java | 8 -------- .../systems/PhysicsControlSessionCleanupSystem.java | 3 +-- .../plugin/modules/control/PhysicsControlSessions.java | 7 ------- .../impulse/examples/commands/GrabCommand.java | 1 - 4 files changed, 1 insertion(+), 18 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java index 0033e5c5..01c3ea12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import java.util.Objects; @@ -29,8 +28,6 @@ public class PhysicsControlSessionComponent implements Component { private JointKey controlJointKey; @Nullable private Ref targetRef; - @Nullable - private SpaceId spaceId; @Nonnull private PhysicsBodyType originalBodyType = PhysicsBodyType.DYNAMIC; @Getter @@ -51,7 +48,6 @@ public PhysicsControlSessionComponent(@Nonnull RigidBodyKey bodyKey, @Nonnull RigidBodyKey anchorBodyKey, @Nullable JointKey controlJointKey, @Nullable Ref targetRef, - @Nullable SpaceId spaceId, @Nonnull PhysicsBodyType originalBodyType, float grabDistance, @Nonnull Vector3f viewOffset, @@ -60,7 +56,6 @@ public PhysicsControlSessionComponent(@Nonnull RigidBodyKey bodyKey, anchorBodyKey.value(), controlJointKey, targetRef, - spaceId, originalBodyType, grabDistance, viewOffset, @@ -71,7 +66,6 @@ public PhysicsControlSessionComponent(@Nonnull UUID bodyUuid, @Nonnull UUID anchorBodyUuid, @Nullable JointKey controlJointKey, @Nullable Ref targetRef, - @Nullable SpaceId spaceId, @Nonnull PhysicsBodyType originalBodyType, float grabDistance, @Nonnull Vector3f viewOffset, @@ -80,7 +74,6 @@ public PhysicsControlSessionComponent(@Nonnull UUID bodyUuid, this.anchorBodyUuid = Objects.requireNonNull(anchorBodyUuid, "anchorBodyUuid"); this.controlJointKey = controlJointKey; this.targetRef = targetRef; - this.spaceId = spaceId; this.originalBodyType = originalBodyType; this.grabDistance = grabDistance; this.viewOffset.set(viewOffset); @@ -131,7 +124,6 @@ public PhysicsControlSessionComponent clone() { copy.anchorBodyUuid = anchorBodyUuid; copy.controlJointKey = controlJointKey; copy.targetRef = targetRef; - copy.spaceId = spaceId; copy.originalBodyType = originalBodyType; copy.grabDistance = grabDistance; copy.viewOffset.set(viewOffset); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java index 1aa1c9a3..3841276b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java @@ -88,7 +88,6 @@ private static boolean sameSessionOwner(@Nonnull PhysicsControlSessionComponent @Nonnull PhysicsControlSessionComponent second) { return Objects.equals(first.getBodyUuid(), second.getBodyUuid()) && Objects.equals(first.getAnchorBodyUuid(), second.getAnchorBodyUuid()) - && Objects.equals(first.getControlJointKey(), second.getControlJointKey()) - && Objects.equals(first.getSpaceId(), second.getSpaceId()); + && Objects.equals(first.getControlJointKey(), second.getControlJointKey()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index c783f736..04d6f673 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; @@ -57,7 +56,6 @@ public static void startSession(@Nonnull Store store, @Nonnull RigidBodyKey bodyKey, @Nonnull RigidBodyKey anchorBodyKey, @Nullable Ref targetRef, - @Nullable SpaceId spaceId, @Nonnull PhysicsBodyType originalBodyType, float grabDistance, @Nonnull Vector3f viewOffset, @@ -68,7 +66,6 @@ public static void startSession(@Nonnull Store store, anchorBodyKey.value(), null, targetRef, - spaceId, originalBodyType, grabDistance, viewOffset, @@ -85,7 +82,6 @@ public static void startSession(@Nonnull Store store, @Nonnull RigidBodyKey anchorBodyKey, @Nullable JointKey controlJointKey, @Nullable Ref targetRef, - @Nullable SpaceId spaceId, @Nonnull PhysicsBodyType originalBodyType, float grabDistance, @Nonnull Vector3f viewOffset, @@ -96,7 +92,6 @@ public static void startSession(@Nonnull Store store, anchorBodyKey.value(), controlJointKey, targetRef, - spaceId, originalBodyType, grabDistance, viewOffset, @@ -113,7 +108,6 @@ public static void startSession(@Nonnull Store store, @Nonnull UUID anchorBodyUuid, @Nullable JointKey controlJointKey, @Nullable Ref targetRef, - @Nullable SpaceId spaceId, @Nonnull PhysicsBodyType originalBodyType, float grabDistance, @Nonnull Vector3f viewOffset, @@ -130,7 +124,6 @@ public static void startSession(@Nonnull Store store, anchorBodyUuid, controlJointKey, targetRef, - spaceId, originalBodyType, grabDistance, viewOffset, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index ab3c6405..9c667c4a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -160,7 +160,6 @@ private static void finishGrab(@Nonnull CommandContext ctx, physicsState.anchorBodyKey(), physicsState.controlJointKey(), selection.attachment(), - selectedSpaceId, physicsState.originalBodyType(), Math.max(selection.distance(), MIN_HOLD_DISTANCE), VIEW_OFFSET, From 623e5bbb0cd8f7120b3b8149c66cfc169ef3111b Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:17:47 +0200 Subject: [PATCH 111/534] refactor(physicsstore): remove stale profiling counters Signed-off-by: Blovien --- .../resources/PhysicsProfilingResource.java | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java index 8d742aec..4119bb41 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java @@ -14,14 +14,10 @@ public final class PhysicsProfilingResource implements Resource { private boolean enabled; - private long requestDrainNanos; - private long bindingNanos; private long snapshotNanos; - private long persistenceCaptureNanos; private long stepSubmitNanos; private int spaces; private int substeps; - private int queuedRequests; private int publishedBodies; @Nonnull private PhysicsStepPhaseStats nativePhaseStats = PhysicsStepPhaseStats.unavailable(); @@ -53,14 +49,10 @@ public void recordSnapshot(long snapshotNanos, int publishedBodies) { } public void reset() { - requestDrainNanos = 0L; - bindingNanos = 0L; snapshotNanos = 0L; - persistenceCaptureNanos = 0L; stepSubmitNanos = 0L; spaces = 0; substeps = 0; - queuedRequests = 0; publishedBodies = 0; nativePhaseStats = PhysicsStepPhaseStats.unavailable(); } @@ -75,22 +67,10 @@ public StepSample latestStepSample() { nativePhaseStats); } - public long getRequestDrainNanos() { - return requestDrainNanos; - } - - public long getBindingNanos() { - return bindingNanos; - } - public long getSnapshotNanos() { return snapshotNanos; } - public long getPersistenceCaptureNanos() { - return persistenceCaptureNanos; - } - public long getStepSubmitNanos() { return stepSubmitNanos; } @@ -103,10 +83,6 @@ public int getSubsteps() { return substeps; } - public int getQueuedRequests() { - return queuedRequests; - } - public int getPublishedBodies() { return publishedBodies; } @@ -121,14 +97,10 @@ public PhysicsStepPhaseStats getNativePhaseStats() { public PhysicsProfilingResource clone() { PhysicsProfilingResource copy = new PhysicsProfilingResource(); copy.enabled = enabled; - copy.requestDrainNanos = requestDrainNanos; - copy.bindingNanos = bindingNanos; copy.snapshotNanos = snapshotNanos; - copy.persistenceCaptureNanos = persistenceCaptureNanos; copy.stepSubmitNanos = stepSubmitNanos; copy.spaces = spaces; copy.substeps = substeps; - copy.queuedRequests = queuedRequests; copy.publishedBodies = publishedBodies; copy.nativePhaseStats = nativePhaseStats; return copy; From f0f65453bbd94bca1b5f6f108b12d917b54f894b Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:19:56 +0200 Subject: [PATCH 112/534] refactor(examples): rename block body batch builder Signed-off-by: Blovien --- .../core/plugin/simulation/JointType.java | 2 +- .../commands/ExamplePhysicsUtils.java | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java index 636f710f..58109752 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.simulation; /** - * Public joint kinds supported by the copied command recorder. + * Public joint kinds supported by PhysicsStore joint rows and snapshot views. */ public enum JointType { FIXED, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 9017de17..ef3c425b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -547,7 +547,7 @@ public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World worl @Nonnull RigidBodySpawnSettings settings, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull Consumer recipe) { + @Nonnull Consumer builder) { DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(world, spaceId, expectedBodies, @@ -556,7 +556,7 @@ public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World worl settings, kind, persistenceMode, - recipe); + builder); if (plan.isEmpty()) { return new BodyRowBatchTiming(0, plan.setupWallNanos(), 0L); } @@ -578,7 +578,7 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, @Nonnull RigidBodySpawnSettings settings, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull Consumer recipe) { + @Nonnull Consumer builder) { Objects.requireNonNull(world, "world"); Objects.requireNonNull(spaceId, "spaceId"); Objects.requireNonNull(shape, "shape"); @@ -587,8 +587,8 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, Objects.requireNonNull(persistenceMode, "persistenceMode"); long setupStartNanos = System.nanoTime(); - BlockBodyBatchRecorder batch = new BlockBodyBatchRecorder(expectedBodies); - Objects.requireNonNull(recipe, "recipe").accept(batch); + BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); + Objects.requireNonNull(builder, "builder").accept(batch); batch.seal(); if (batch.isEmpty()) { return new DynamicBodyBatchPlan(List.of(), 0L); @@ -642,7 +642,7 @@ public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store st @Nonnull PhysicsShapeSpec shape, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer recipe) { + @Nonnull Consumer builder) { return spawnBlockBodiesInternal(store, time, resource, @@ -653,7 +653,7 @@ public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store st shape, mass, settings, - recipe, + builder, true).collectedBodies(); } @@ -668,7 +668,7 @@ public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store recipe) { + @Nonnull Consumer builder) { return spawnBlockBodiesInternal(store, time, resource, @@ -679,7 +679,7 @@ public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store recipe, + @Nonnull Consumer builder, boolean collectBodies) { - BlockBodyBatchRecorder batch = new BlockBodyBatchRecorder(expectedBodies); - Objects.requireNonNull(recipe, "recipe").accept(batch); + BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); + Objects.requireNonNull(builder, "builder").accept(batch); batch.seal(); if (batch.isEmpty()) { return new BlockBodyBatchResult(collectBodies ? new SpawnedBlockBody[0] : null, @@ -940,7 +940,7 @@ public record PendingBlockBody(@Nonnull RigidBodyKey bodyKey, } } - public static final class BlockBodyBatchRecorder { + public static final class BlockBodyBatchBuilder { private static final int POSITION_STRIDE = 3; @@ -951,7 +951,7 @@ public static final class BlockBodyBatchRecorder { private int size; private boolean sealed; - private BlockBodyBatchRecorder(int expectedBodies) { + private BlockBodyBatchBuilder(int expectedBodies) { int capacity = Math.max(1, expectedBodies); bodyKeyMostSignificantBits = new long[capacity]; bodyKeyLeastSignificantBits = new long[capacity]; @@ -959,7 +959,7 @@ private BlockBodyBatchRecorder(int expectedBodies) { } @Nonnull - public BlockBodyBatchRecorder addBody(float positionX, + public BlockBodyBatchBuilder addBody(float positionX, float positionY, float positionZ) { return addBody(bodyKeyRunId, @@ -970,7 +970,7 @@ public BlockBodyBatchRecorder addBody(float positionX, } @Nonnull - public BlockBodyBatchRecorder addBody(@Nonnull RigidBodyKey bodyKey, + public BlockBodyBatchBuilder addBody(@Nonnull RigidBodyKey bodyKey, float positionX, float positionY, float positionZ) { @@ -1001,7 +1001,7 @@ public RigidBodyKey body(@Nonnull RigidBodyKey bodyKey, } @Nonnull - private BlockBodyBatchRecorder addBody(long bodyKeyMostSignificantBits, + private BlockBodyBatchBuilder addBody(long bodyKeyMostSignificantBits, long bodyKeyLeastSignificantBits, float positionX, float positionY, @@ -1083,7 +1083,7 @@ private void checkIndex(int index) { private void assertMutable() { if (sealed) { - throw new IllegalStateException("Block body batch recorder is already sealed"); + throw new IllegalStateException("Block body batch builder is already sealed"); } } } From d67ba45c0646e4da0fcba8ff9d38e9e8e6308984 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:36:16 +0200 Subject: [PATCH 113/534] refactor(physicsstore): rename queued read system Signed-off-by: Blovien --- .../physicsstore/registration/PhysicsStoreRegistration.java | 4 ++-- .../physicsstore/systems/PersistenceCaptureSystem.java | 2 +- ...adRequestSystem.java => PhysicsStoreQueuedReadSystem.java} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/{PhysicsStoreReadRequestSystem.java => PhysicsStoreQueuedReadSystem.java} (95%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 6e1c1b10..2a01715d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -28,7 +28,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.JointBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceCaptureSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceHydrationSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PhysicsStoreReadRequestSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PhysicsStoreQueuedReadSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; @@ -203,7 +203,7 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); - registry.registerSystem(new PhysicsStoreReadRequestSystem()); + registry.registerSystem(new PhysicsStoreQueuedReadSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); registry.registerSystem(new PersistenceCaptureSystem()); registry.registerSystem(new StepSubmissionSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index eedaf447..591ee826 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -60,7 +60,7 @@ public final class PersistenceCaptureSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PhysicsStoreReadRequestSystem.class) + new SystemDependency<>(Order.AFTER, PhysicsStoreQueuedReadSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java index 73a1e8cb..16711ad0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreReadRequestSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java @@ -15,7 +15,7 @@ /** * Resolves queued live backend reads on the PhysicsStore owner lane. */ -public final class PhysicsStoreReadRequestSystem extends TickingSystem { +public final class PhysicsStoreQueuedReadSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, CompletedStepPublicationSystem.class) From 5d10880311a2cfdd7d504a92ef2e7ae140839bb6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:39:04 +0200 Subject: [PATCH 114/534] docs(physicsstore): clarify space compatibility index Signed-off-by: Blovien --- .../resources/PhysicsSpaceCompatibilityIndexResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java index 197f4329..cb5b6372 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java @@ -15,7 +15,7 @@ import javax.annotation.Nullable; /** - * Runtime-only compatibility map for legacy SpaceId query and command boundaries. + * Runtime-only compatibility map between public SpaceId values and PhysicsStore space UUIDs. */ public final class PhysicsSpaceCompatibilityIndexResource implements Resource { From 0543b6e872bde3accdf824d147ae466ae8c30962 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:42:52 +0200 Subject: [PATCH 115/534] refactor(physicsstore): rename queued read API Signed-off-by: Blovien --- .../resources/PhysicsStoreReadQueueResource.java | 12 ++++++------ .../plugin/physicsstore/PhysicsStoreThreading.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java index 6fbf21be..042693fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -18,12 +18,12 @@ import javax.annotation.Nonnull; /** - * Owner-lane live backend read queue drained by PhysicsStore systems. + * Owner-lane backend read queue drained by PhysicsStore systems. * - *

Enqueued reads must capture copied inputs only. They execute during PhysicsStore ticking and - * must return copied values rather than live stores, refs, runtime resources, or backend - * handles. This is an explicit boundary contract for callers; do not enforce it with generic - * runtime object inspection in this hot path.

+ *

Reads execute during PhysicsStore ticking. Callers must pass value-copied inputs and return + * immutable values rather than live stores, refs, runtime resources, or backend handles. This is a + * reviewable caller contract; do not enforce it with generic runtime object inspection in this hot + * path.

*/ public final class PhysicsStoreReadQueueResource implements Resource { @@ -34,7 +34,7 @@ public PhysicsStoreReadQueueResource() { } @Nonnull - public synchronized CompletionStage enqueueCopiedRead( + public synchronized CompletionStage enqueueRead( @Nonnull Function, R> read) { CompletableFuture completion = new CompletableFuture<>(); reads.add(new QueuedRead<>(read, completion)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java index b062fb76..372ddd95 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -109,7 +109,7 @@ private static void enqueueRead(@Nonnull World world, Store store = store(world); requireWorldThread(store, operation); store.getResource(PhysicsStoreReadQueueResource.getResourceType()) - .enqueueCopiedRead(read) + .enqueueRead(read) .whenComplete((value, failure) -> { if (failure != null) { PhysicsStoreAsyncCompletions.fail(completion, failure); From 1e91215ae311ba768787a58566d3a04fb2e593b0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:49:29 +0200 Subject: [PATCH 116/534] refactor(physicsstore): simplify runtime resources Signed-off-by: Blovien --- .../resources/PhysicsDebugResource.java | 28 +++---------------- .../resources/PhysicsRuntimeResource.java | 16 ++++------- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java index 8987f695..157443a1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java @@ -4,11 +4,15 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import lombok.Getter; +import lombok.Setter; import javax.annotation.Nonnull; /** * Runtime-only debug toggles owned by PhysicsStore. */ +@Setter +@Getter public final class PhysicsDebugResource implements Resource { private boolean debugBodiesEnabled; @@ -18,30 +22,6 @@ public final class PhysicsDebugResource implements Resource { public PhysicsDebugResource() { } - public boolean isDebugBodiesEnabled() { - return debugBodiesEnabled; - } - - public void setDebugBodiesEnabled(boolean debugBodiesEnabled) { - this.debugBodiesEnabled = debugBodiesEnabled; - } - - public boolean isDebugContactsEnabled() { - return debugContactsEnabled; - } - - public void setDebugContactsEnabled(boolean debugContactsEnabled) { - this.debugContactsEnabled = debugContactsEnabled; - } - - public boolean isDebugJointsEnabled() { - return debugJointsEnabled; - } - - public void setDebugJointsEnabled(boolean debugJointsEnabled) { - this.debugJointsEnabled = debugJointsEnabled; - } - @Nonnull @Override public PhysicsDebugResource clone() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 8e2646d7..56f8a204 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -18,6 +18,8 @@ import it.unimi.dsi.fastutil.longs.LongList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.Getter; +import lombok.Setter; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -79,19 +81,13 @@ public final class PhysicsRuntimeResource implements Resource { private final List pendingBodyOperations = new ArrayList<>(); @Nonnull private final ObjectOpenHashSet pendingSpaceSettings = new ObjectOpenHashSet<>(); + @Setter + @Getter private boolean started; public PhysicsRuntimeResource() { } - public boolean isStarted() { - return started; - } - - public void setStarted(boolean started) { - this.started = started; - } - public void putRuntime(@Nonnull BackendId backendId, @Nonnull PhysicsBackendRuntime runtime) { runtimesByBackend.put(backendId, runtime); } @@ -299,7 +295,7 @@ public void forEachTerrainBodyHandle(@Nonnull UUID terrainUuid, public void removeTerrainHandles(@Nonnull UUID terrainUuid) { LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); if (bodyHandles != null) { - bodyHandles.forEach((long bodyHandle) -> bodyHitMetadataByHandle.remove(bodyHandle)); + bodyHandles.forEach(bodyHitMetadataByHandle::remove); } terrainBodyHandlesByUuid.remove(terrainUuid); terrainVoxelBodyHandlesByUuid.remove(terrainUuid); @@ -584,7 +580,7 @@ private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandl UUID terrainUuid = entry.getKey(); LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); if (bodyHandles != null) { - bodyHandles.forEach((long bodyHandle) -> bodyHitMetadataByHandle.remove(bodyHandle)); + bodyHandles.forEach(bodyHitMetadataByHandle::remove); } terrainBodyHandlesByUuid.remove(terrainUuid); terrainVoxelBodyHandlesByUuid.remove(terrainUuid); From 1843207896fe3181ed6b908ac2fd99d8d4bf55e4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 18:53:35 +0200 Subject: [PATCH 117/534] fix(physicsstore): avoid early plugin link in owner cleanup Signed-off-by: Blovien --- .../owner/PhysicsOwnerLifecycleSystem.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java index 827f43e4..db8b8043 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java @@ -7,11 +7,11 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -141,8 +141,17 @@ private static RuntimeException tryClearSpaces(@Nonnull Store store @Nullable private static Store physicsStoreOrNull(@Nonnull Store store) { World world = store.getExternalData().getWorld(); - return world instanceof PhysicsStoreWorld physicsWorld - ? physicsWorld.getPhysicsStore().getStore() - : null; + try { + Method accessor = world.getClass().getMethod("getPhysicsStore"); + Object physicsStore = accessor.invoke(world); + return physicsStore instanceof PhysicsStore typedPhysicsStore + ? typedPhysicsStore.getStore() + : null; + } catch (NoSuchMethodException exception) { + return null; + } catch (ReflectiveOperationException exception) { + throw new IllegalStateException("Failed to access authoritative PhysicsStore for world " + + world.getName(), exception); + } } } From ebd9287a7c0459d56553497fd8f9ccc04dc013f9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 19:08:00 +0200 Subject: [PATCH 118/534] refactor(physicsstore): keep generated proxy marker internal Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 2 +- .../core/internal/commands/CleanCommand.java | 2 +- .../GeneratedVisualProxyComponent.java | 2 +- .../systems/StepSubmissionSystem.java | 21 ++++++++-- ...PersistentPhysicsSpaceBootstrapSystem.java | 2 +- ...csDetachedVisualMaterializationSystem.java | 2 +- .../physicsstore/PhysicsStoreDiagnostics.java | 38 ++++++++++++++++--- 7 files changed, 55 insertions(+), 14 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{plugin/physicsstore/projection => internal/components}/GeneratedVisualProxyComponent.java (93%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index e92dea8c..db508cbe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -20,6 +20,7 @@ import dev.hytalemodding.impulse.api.PhysicsBackend; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; +import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; @@ -38,7 +39,6 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.nio.file.Path; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 9ea4b3ca..d21855b5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -15,13 +15,13 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/GeneratedVisualProxyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/GeneratedVisualProxyComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java index 5d4a7f74..0fbad7c6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/GeneratedVisualProxyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.projection; +package dev.hytalemodding.impulse.core.internal.components; import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.component.Component; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index 9d66d33b..2cbca923 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -154,14 +154,14 @@ private static void resetStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) @Nonnull private static PhysicsStepPhaseStats collectStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) { - PhysicsStepPhaseStats[] stats = {PhysicsStepPhaseStats.unavailable()}; + StepPhaseStatsAccumulator stats = new StepPhaseStatsAccumulator(); StepPhaseStatsCapture capture = new StepPhaseStatsCapture(); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { capture.reset(); backendRuntime.stepPhaseStats(spaceHandle.value(), capture); - stats[0] = stats[0].add(capture.value()); + stats.add(capture.value()); }); - return stats[0]; + return stats.value(); } @Nonnull @@ -355,6 +355,21 @@ private PhysicsStepPhaseStats value() { } } + private static final class StepPhaseStatsAccumulator { + + @Nonnull + private PhysicsStepPhaseStats value = PhysicsStepPhaseStats.unavailable(); + + private void add(@Nonnull PhysicsStepPhaseStats stats) { + value = value.add(stats); + } + + @Nonnull + private PhysicsStepPhaseStats value() { + return value; + } + } + private static final class StepCounters { private int spaceCount; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java index fe7e6547..0fcd7f75 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java @@ -12,6 +12,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRestorePreflight; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsSpaceState; @@ -22,7 +23,6 @@ import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.util.logging.Level; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 7cd63bc5..1dcd5f26 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; @@ -36,7 +37,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index 3b46f9ff..c791b766 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -78,10 +78,10 @@ public static CompletionStage bodyCountAsync(@Nonnull Store store) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - int[] count = {0}; + JointCountCapture count = new JointCountCapture(); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> - count[0] += backendRuntime.jointCount(spaceHandle.value())); - return count[0]; + count.add(backendRuntime.jointCount(spaceHandle.value()))); + return count.value(); } @Nonnull @@ -101,13 +101,13 @@ public static CompletionStage runtimeJointCountAsync(@Nonnull Store store) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - boolean[] supported = {false}; + CcdSupportCapture supported = new CcdSupportCapture(); runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { if (backendRuntime.supportsContinuousCollision(spaceHandle.value())) { - supported[0] = true; + supported.markSupported(); } }); - return supported[0]; + return supported.value(); } @Nonnull @@ -334,4 +334,30 @@ private static SolverCapabilitySummary solverCapability(@Nonnull SpaceId spaceId space.backendRuntime().supportsActivationTuning(space.spaceHandle().value())); } + private static final class JointCountCapture { + + private int value; + + private void add(int count) { + value += count; + } + + private int value() { + return value; + } + } + + private static final class CcdSupportCapture { + + private boolean value; + + private void markSupported() { + value = true; + } + + private boolean value() { + return value; + } + } + } From bf3a63e02609250ac58db1d3fa739cf10b11e36c Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 19:09:54 +0200 Subject: [PATCH 119/534] docs(physicsstore): remove stale request wording Signed-off-by: Blovien --- .../systems/PhysicsStoreWorldCollisionProducerSystem.java | 2 +- .../internal/resources/PhysicsWorldRuntimeResource.java | 2 +- .../core/internal/systems/sync/PhysicsSyncSystem.java | 6 +++--- .../impulse/core/plugin/body/RigidBodyKey.java | 2 +- .../modules/worldcollision/ImpulseWorldCollisionPlugin.java | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index c084e121..7ac4f1c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -48,7 +48,7 @@ import org.joml.Vector3f; /** - * Produces copied PhysicsStore terrain requests from EntityStore and ChunkStore state. + * Produces copied PhysicsStore terrain mutations from EntityStore and ChunkStore state. */ public final class PhysicsStoreWorldCollisionProducerSystem extends TickingSystem implements QuerySystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 5e7d764d..3621c50b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -215,7 +215,7 @@ private void requireLegacyMutationAllowed(@Nonnull String operation) { } throw new IllegalStateException("Legacy PhysicsWorldResource mutation is disabled while " + "authoritative PhysicsStore is active: " + operation - + ". Route this operation through PhysicsStore requests or a PhysicsStore-backed " + + ". Route this operation through PhysicsStore rows or a PhysicsStore-backed " + "compatibility bridge."); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 5ceceaf0..5ed63f0d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -51,7 +51,7 @@ * body transforms.

* *

Entities attach to authoritative PhysicsStore body UUIDs. Backend body destruction is explicit - * through PhysicsStore requests; removing an EntityStore attachment only removes the projection.

+ * through PhysicsStore rows; removing an EntityStore attachment only removes the projection.

*/ public class PhysicsSyncSystem extends EntityTickingSystem { @@ -192,8 +192,8 @@ private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transf private static void clearMissingPhysicsStoreAttachment(@Nonnull Ref entityRef, @Nonnull BodyAttachmentComponent attachment, @Nonnull CommandBuffer commandBuffer) { - // PhysicsStore snapshot publication is intentionally one completed frame behind request - // ingestion. Absence from the latest frame is not enough evidence that the body row is gone. + // PhysicsStore snapshot publication is intentionally one completed frame behind row + // mutation. Absence from the latest frame is not enough evidence that the body row is gone. } private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java index 7db6ed12..a2d50b0e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java @@ -11,7 +11,7 @@ * *

Backend {@code PhysicsBody} handles may change when spaces migrate between * backends. This id is the durable handle used by ECS attachments, persistence, - * snapshots, and physics-owner command queues.

+ * snapshots, compatibility lookups, and PhysicsStore row-local body commands.

*/ public final class RigidBodyKey { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java index d3f38cff..decdfb69 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java @@ -34,7 +34,7 @@ protected void setup() { PhysicsStoreWorldCollisionStreamingResource::new)); entityRegistry.registerSystem(new PhysicsStoreWorldCollisionProducerSystem()); WorldCollisionLifecycle.enable(); - LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore request producer enabled."); + LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore terrain producer enabled."); } @Override From 2e551b3c2d7a9f6421a00918f9db04039bd933ed Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 19:31:57 +0200 Subject: [PATCH 120/534] fix(physicsstore): route cleanup through store topology Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 333 ++++++++++++++++++ .../PhysicsBodyRegistrationResource.java | 12 + .../resources/PhysicsRuntimeResource.java | 42 ++- .../resources/PhysicsSnapshotResource.java | 29 ++ .../PhysicsTerrainMutationQueueResource.java | 8 + .../systems/BodyCommandApplicationSystem.java | 8 +- .../systems/TargetBindingSystem.java | 8 +- .../systems/TerrainColliderBindingSystem.java | 8 +- .../PhysicsWorldRuntimeResource.java | 209 ++++++++++- .../resources/owner/PhysicsOwnerGateway.java | 4 +- impulse-core/src/module-info/module-info.java | 5 +- 11 files changed, 634 insertions(+), 32 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java new file mode 100644 index 00000000..d64c8627 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -0,0 +1,333 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Owner-lane topology mutations for public compatibility cleanup paths. + */ +public final class PhysicsStoreTopologyMutations { + + private PhysicsStoreTopologyMutations() { + } + + public static void destroyBody(@Nonnull Store store, + @Nonnull RigidBodyKey bodyKey) { + destroyBody(store, Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public static void destroyBody(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + PhysicsStoreThreading.requireWorldThread(store, "destroy a PhysicsStore body row"); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + List removals = collectRows(store, null, bodyUuid); + for (RowRemoval removal : removals) { + if (removal.kind() == RowKind.JOINT) { + removeRuntimeJoint(runtime, identity, removal.rowUuid()); + } + } + removeRuntimeBody(runtime, identity, bodyUuid); + removeRows(store, removals); + } + + @Nonnull + public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( + @Nonnull Store store) { + PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore body rows"); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + TopologyCounts removed = countBackendTopology(runtime); + for (BackendSpaceHandle spaceHandle : spaceHandles(runtime)) { + removeRuntimeContentsForSpace(runtime, identity, spaceHandle); + } + removeRows(store, collectRows(store, null, null)); + clearCopiedBodyState(store); + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear(); + store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); + runtime.clearTransientBodyOperations(); + int keptSpaces = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .size(); + return new PhysicsRuntimeResetResult(removed.bodyCount(), + removed.jointCount(), + keptSpaces); + } + + public static void removeSpaceWithContents(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId); + removeSpaceWithContents(store, spaceUuid); + } + + public static void removeSpaceWithContents(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space row"); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); + if (spaceHandle != null) { + removeRuntimeContentsForSpace(runtime, identity, spaceHandle); + } + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) + .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); + removeRows(store, collectRows(store, spaceUuid, null)); + PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceUuid); + } + + private static void removeRuntimeContentsForSpace(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull BackendSpaceHandle spaceHandle) { + for (UUID jointUuid : runtime.jointUuidsForSpaceHandle(spaceHandle)) { + removeRuntimeJoint(runtime, identity, jointUuid); + } + for (UUID terrainUuid : runtime.terrainUuidsForSpaceHandle(spaceHandle)) { + removeRuntimeTerrain(runtime, terrainUuid); + } + for (UUID bodyUuid : runtime.bodyUuidsForSpaceHandle(spaceHandle)) { + removeRuntimeBody(runtime, identity, bodyUuid); + } + } + + private static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID jointUuid) { + BackendJointHandle jointHandle = runtime.getJointHandle(jointUuid); + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); + if (jointHandle == null) { + runtime.removeJointHandle(jointUuid); + return false; + } + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); + } + identity.removeJointHandle(jointHandle); + runtime.removeJointHandle(jointUuid); + return true; + } + + private static int removeRuntimeTerrain(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID terrainUuid) { + BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(terrainUuid); + LongArrayList bodyHandles = new LongArrayList(); + runtime.forEachTerrainBodyHandle(terrainUuid, bodyId -> bodyHandles.add(bodyId)); + if (spaceHandle != null) { + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (backendRuntime != null) { + for (int index = 0; index < bodyHandles.size(); index++) { + backendRuntime.removeBody(spaceHandle.value(), bodyHandles.getLong(index)); + } + } + } + runtime.removeTerrainHandles(terrainUuid); + return bodyHandles.size(); + } + + private static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + if (bodyHandle == null) { + runtime.removeBodyHandle(bodyUuid); + return false; + } + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); + } + identity.removeBodyHandle(bodyHandle); + runtime.removeBodyHandle(bodyUuid); + return true; + } + + @Nonnull + private static List spaceHandles(@Nonnull PhysicsRuntimeResource runtime) { + List handles = new ArrayList<>(); + runtime.forEachSpaceBinding((_, _, spaceHandle, _) -> handles.add(spaceHandle)); + return handles; + } + + @Nonnull + private static TopologyCounts countBackendTopology(@Nonnull PhysicsRuntimeResource runtime) { + TopologyCounts counts = new TopologyCounts(); + runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + counts.addBodies(backendRuntime.bodyCount(spaceHandle.value())); + counts.addJoints(backendRuntime.jointCount(spaceHandle.value())); + }); + return counts; + } + + @Nonnull + private static List collectRows(@Nonnull Store store, + @Nullable UUID spaceUuid, + @Nullable UUID bodyUuid) { + ComponentType uuidType = UuidComponent.getComponentType(); + ConcurrentLinkedQueue removals = new ConcurrentLinkedQueue<>(); + store.forEachEntityParallel(uuidType, (index, chunk, _) -> { + UuidComponent uuid = chunk.getComponent(index, uuidType); + if (uuid == null) { + return; + } + UUID rowUuid = uuid.getUuid(); + Ref ref = chunk.getReferenceTo(index); + JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); + if (matchesJoint(joint, spaceUuid, bodyUuid)) { + removals.add(new RowRemoval(ref, rowUuid, RowKind.JOINT, null)); + return; + } + TerrainColliderComponent terrain = chunk.getComponent(index, + TerrainColliderComponent.getComponentType()); + if (matchesTerrain(terrain, spaceUuid, bodyUuid)) { + removals.add(new RowRemoval(ref, + rowUuid, + RowKind.TERRAIN, + terrain.getPayloadResourceKey())); + return; + } + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (matchesBody(body, rowUuid, spaceUuid, bodyUuid)) { + removals.add(new RowRemoval(ref, rowUuid, RowKind.BODY, null)); + } + }); + return new ArrayList<>(removals); + } + + private static boolean matchesJoint(@Nullable JointComponent joint, + @Nullable UUID spaceUuid, + @Nullable UUID bodyUuid) { + if (joint == null) { + return false; + } + if (spaceUuid != null && !spaceUuid.equals(joint.getSpaceUuid())) { + return false; + } + return bodyUuid == null + || bodyUuid.equals(joint.getBodyAUuid()) + || bodyUuid.equals(joint.getBodyBUuid()); + } + + private static boolean matchesTerrain(@Nullable TerrainColliderComponent terrain, + @Nullable UUID spaceUuid, + @Nullable UUID bodyUuid) { + return bodyUuid == null + && terrain != null + && (spaceUuid == null || spaceUuid.equals(terrain.getSpaceUuid())); + } + + private static boolean matchesBody(@Nullable BodyComponent body, + @Nonnull UUID rowUuid, + @Nullable UUID spaceUuid, + @Nullable UUID bodyUuid) { + if (body == null) { + return false; + } + if (spaceUuid != null && !spaceUuid.equals(body.getSpaceUuid())) { + return false; + } + return bodyUuid == null || bodyUuid.equals(rowUuid); + } + + private static void removeRows(@Nonnull Store store, + @Nonnull List removals) { + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + PhysicsTerrainPayloadResource terrainPayloads = + store.getResource(PhysicsTerrainPayloadResource.getResourceType()); + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); + for (RowRemoval removal : removals) { + if (!removal.ref().isValid()) { + continue; + } + identity.removeUuid(removal.rowUuid(), removal.ref()); + if (removal.kind() == RowKind.TERRAIN + && removal.payloadResourceKey() != null + && !removal.payloadResourceKey().isBlank()) { + terrainPayloads.remove(removal.payloadResourceKey()); + } + if (removal.kind() == RowKind.BODY) { + snapshots.removeBody(removal.rowUuid()); + registrations.removeBody(RigidBodyKey.of(removal.rowUuid())); + } + store.removeEntity(removal.ref(), + store.getRegistry().newHolder(), + RemoveReason.REMOVE); + } + } + + private static void clearCopiedBodyState(@Nonnull Store store) { + store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()).clear(); + store.getResource(PhysicsEventResource.getResourceType()).clear(); + } + + private enum RowKind { + BODY, + JOINT, + TERRAIN + } + + private record RowRemoval(@Nonnull Ref ref, + @Nonnull UUID rowUuid, + @Nonnull RowKind kind, + @Nullable String payloadResourceKey) { + } + + private static final class TopologyCounts { + + private int bodyCount; + private int jointCount; + + private void addBodies(int count) { + bodyCount += count; + } + + private void addJoints(int count) { + jointCount += count; + } + + private int bodyCount() { + return bodyCount; + } + + private int jointCount() { + return jointCount; + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index 5b087e7b..3784d385 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -78,6 +78,18 @@ public void publish(@Nonnull Collection views) { Map.copyOf(viewsByKey)); } + public void removeBody(@Nonnull RigidBodyKey bodyKey) { + PublishedRegistrations current = registrations; + if (!current.viewsByKey().containsKey(bodyKey)) { + return; + } + Object2ObjectLinkedOpenHashMap viewsByKey = + new Object2ObjectLinkedOpenHashMap<>(current.viewsByKey()); + viewsByKey.remove(bodyKey); + registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), + Map.copyOf(viewsByKey)); + } + public void clear() { registrations = PublishedRegistrations.EMPTY; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 56f8a204..6d4c524f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -167,6 +167,18 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid) { } } + @Nonnull + public List bodyUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandle) { + List bodyUuids = new ArrayList<>(); + int targetSpaceHandle = spaceHandle.value(); + bodySpaceHandlesByUuid.forEach((bodyUuid, handle) -> { + if (handle.value() == targetSpaceHandle) { + bodyUuids.add(bodyUuid); + } + }); + return bodyUuids; + } + public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, @Nullable RigidBodyKey bodyKey, @Nonnull PhysicsBodyType bodyType, @@ -248,6 +260,18 @@ public void removeJointHandle(@Nonnull UUID jointUuid) { jointSpaceHandlesByUuid.remove(jointUuid); } + @Nonnull + public List jointUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandle) { + List jointUuids = new ArrayList<>(); + int targetSpaceHandle = spaceHandle.value(); + jointSpaceHandlesByUuid.forEach((jointUuid, handle) -> { + if (handle.value() == targetSpaceHandle) { + jointUuids.add(jointUuid); + } + }); + return jointUuids; + } + public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle handle, @@ -303,6 +327,18 @@ public void removeTerrainHandles(@Nonnull UUID terrainUuid) { terrainPayloadKeysByUuid.remove(terrainUuid); } + @Nonnull + public List terrainUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandle) { + List terrainUuids = new ArrayList<>(); + int targetSpaceHandle = spaceHandle.value(); + terrainSpaceHandlesByUuid.forEach((terrainUuid, handle) -> { + if (handle.value() == targetSpaceHandle) { + terrainUuids.add(terrainUuid); + } + }); + return terrainUuids; + } + public void forEachSpaceBinding(@Nonnull SpaceBindingConsumer consumer) { spaceHandlesByUuid.forEach((spaceUuid, spaceHandle) -> { BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); @@ -342,6 +378,10 @@ public void clear() { started = false; } + public void clearTransientBodyOperations() { + pendingBodyOperations.clear(); + } + public void destroyBackendBindings() { RuntimeException failure = null; for (Map.Entry entry @@ -406,7 +446,7 @@ public void destroyBackendBindings() { } @Nullable - private PhysicsBackendRuntime runtimeForSpaceHandle(@Nullable BackendSpaceHandle target) { + public PhysicsBackendRuntime runtimeForSpaceHandle(@Nullable BackendSpaceHandle target) { if (target == null) { return null; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index afbe8a5f..f9d1f799 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -7,6 +7,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.UUID; import javax.annotation.Nonnull; @@ -41,10 +43,37 @@ public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { snapshot = new PublishedSnapshot(frame, Map.copyOf(bodiesByUuid)); } + public void removeBody(@Nonnull UUID bodyUuid) { + PublishedSnapshot current = snapshot; + if (!current.bodiesByUuid().containsKey(bodyUuid)) { + return; + } + snapshot = withoutBody(current, bodyUuid); + } + public void clear() { snapshot = PublishedSnapshot.EMPTY; } + @Nonnull + private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, + @Nonnull UUID bodyUuid) { + List bodies = new ArrayList<>(); + Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); + for (PhysicsStoreBodySnapshot body : current.frame().bodies()) { + if (bodyUuid.equals(body.bodyUuid())) { + continue; + } + bodies.add(body); + bodiesByUuid.put(body.bodyUuid(), body); + } + return new PublishedSnapshot( + new PhysicsStoreSnapshotFrame(current.frame().sequence(), + current.frame().dt(), + bodies), + Map.copyOf(bodiesByUuid)); + } + @Nonnull @Override public PhysicsSnapshotResource clone() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java index 88fc0b7c..f1445e07 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Objects; import java.util.Queue; +import java.util.function.Predicate; import javax.annotation.Nonnull; /** @@ -41,6 +42,13 @@ public synchronized int size() { return mutations.size(); } + public synchronized int removeIf(@Nonnull Predicate predicate) { + Objects.requireNonNull(predicate, "predicate"); + int before = mutations.size(); + mutations.removeIf(predicate); + return before - mutations.size(); + } + public synchronized void clear() { mutations.clear(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java index ec8c4c34..3a3d2697 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java @@ -222,13 +222,7 @@ private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeReso private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull BackendSpaceHandle spaceHandle) { - final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; - runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { - if (handle.value() == spaceHandle.value()) { - resolved[0] = backendRuntime; - } - }); - return resolved[0]; + return runtime.runtimeForSpaceHandle(spaceHandle); } private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtime, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index 98f402e3..f6043e41 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -165,13 +165,7 @@ private static void applyForce(@Nonnull PhysicsBackendRuntime backendRuntime, private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull BackendSpaceHandle spaceHandle) { - final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; - runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { - if (handle.value() == spaceHandle.value()) { - resolved[0] = backendRuntime; - } - }); - return resolved[0]; + return runtime.runtimeForSpaceHandle(spaceHandle); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index ab061fc8..5f2370f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -258,13 +258,7 @@ private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeReso @Nullable private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull BackendSpaceHandle spaceHandle) { - final PhysicsBackendRuntime[] resolved = new PhysicsBackendRuntime[1]; - runtime.forEachSpaceBinding((_, _, handle, backendRuntime) -> { - if (handle.value() == spaceHandle.value()) { - resolved[0] = backendRuntime; - } - }); - return resolved[0]; + return runtime.runtimeForSpaceHandle(spaceHandle); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 3621c50b..6685465c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -19,12 +19,16 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; @@ -35,6 +39,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundarySafeState; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerCallable; @@ -88,6 +93,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicLong; @@ -1041,6 +1047,26 @@ public WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { + if (isAuthoritativePhysicsStoreActive()) { + requireWorldCollisionLifecycleEnabled(); + Store store = authoritativePhysicsStore("rebuild world collision"); + SpaceWorldCollisionSettings settings = + requireAuthoritativeWorldCollisionSettings(store, spaceId); + PhysicsTerrainMutationQueueResource queue = + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); + int removed = authoritativeWorldCollisionStreaming() + .clearSpace(settings.spaceUuid(), queue); + WorldCollisionPrewarmStats stats = authoritativeWorldCollisionStreaming() + .ensureAround(world, + settings.spaceUuid(), + queue, + List.of(center), + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + return withRemovedBodies(stats.buildStats(), stats.buildStats().removedBodies() + removed); + } requireLegacyMutationAllowed("rebuild world collision"); requireWorldCollisionLifecycleEnabled(); return callOwner("rebuild world collision", () -> { @@ -1063,6 +1089,20 @@ public WorldCollisionBuildStats refreshWorldCollisionAround(@Nonnull World world @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { + if (isAuthoritativePhysicsStoreActive()) { + requireWorldCollisionLifecycleEnabled(); + Store store = authoritativePhysicsStore("refresh world collision"); + SpaceWorldCollisionSettings settings = + requireAuthoritativeWorldCollisionSettings(store, spaceId); + return authoritativeWorldCollisionStreaming().refreshAround(world, + settings.spaceUuid(), + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + center, + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + } requireLegacyMutationAllowed("refresh world collision"); requireWorldCollisionLifecycleEnabled(); return callOwner("refresh world collision", () -> { @@ -1086,6 +1126,21 @@ public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World worl @Nonnull Iterable centers, int radius, long tick) { + if (isAuthoritativePhysicsStoreActive()) { + requireWorldCollisionLifecycleEnabled(); + Objects.requireNonNull(centers, "centers"); + Store store = authoritativePhysicsStore("ensure world collision"); + SpaceWorldCollisionSettings settings = + requireAuthoritativeWorldCollisionSettings(store, spaceId); + return authoritativeWorldCollisionStreaming().ensureAround(world, + settings.spaceUuid(), + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + centers, + radius, + tick, + null, + settings.buildOptions()); + } requireLegacyMutationAllowed("ensure world collision"); Objects.requireNonNull(centers, "centers"); requireWorldCollisionLifecycleEnabled(); @@ -1106,6 +1161,11 @@ public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World worl @Override public int clearWorldCollision(@Nonnull SpaceId spaceId) { + if (isAuthoritativePhysicsStoreActive()) { + Store store = authoritativePhysicsStore("clear world collision"); + UUID spaceUuid = requireSpaceUuid(store, spaceId); + return clearAuthoritativeWorldCollisionSpace(store, spaceUuid); + } requireLegacyMutationAllowed("clear world collision"); return callOwner("clear world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); @@ -1120,9 +1180,90 @@ public long worldCollisionStreamingRevision(@Nonnull SpaceId spaceId) { @Nonnull @Override public WorldCollisionStats getWorldCollisionStats() { + if (isAuthoritativePhysicsStoreActive()) { + return WorldCollisionLifecycle.isEnabled() + ? authoritativeWorldCollisionStreaming().stats() + : new WorldCollisionStats(0, 0, 0, 0); + } return callOwner("read world collision stats", collisionRuntime::getStats); } + @Nonnull + private PhysicsStoreWorldCollisionStreamingResource authoritativeWorldCollisionStreaming() { + Store entityStore = owningStore; + if (entityStore == null) { + throw new IllegalStateException("Cannot access PhysicsStore world-collision streaming " + + "before this resource is attached to an EntityStore"); + } + return entityStore.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()); + } + + @Nonnull + private SpaceWorldCollisionSettings requireAuthoritativeWorldCollisionSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + if (spaceRef == null || !spaceRef.isValid()) { + throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() + + " is not bound yet"); + } + WorldCollisionComponent component = + store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); + WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); + if (settings.getMode() == WorldCollisionMode.NONE) { + throw new IllegalStateException("World collision is disabled for space " + spaceId); + } + return new SpaceWorldCollisionSettings(spaceUuid, + settings.getMode(), + settings.getEntityChunkBoundaryMode(), + settings.isNativeVoxelTerrainEnabled(), + settings.getRadius(), + settings.getBodyRadius(), + settings.getTtlTicks(), + settings.getTerrainFriction(), + settings.getTerrainRestitution()); + } + + private void clearAuthoritativeWorldCollisionStreaming(@Nonnull Store store) { + if (!WorldCollisionLifecycle.isEnabled() || owningStore == null) { + return; + } + PhysicsTerrainMutationQueueResource queue = + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); + authoritativeWorldCollisionStreaming().retainSpaces(Set.of(), queue); + queue.clear(); + } + + private int clearAuthoritativeWorldCollisionSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + if (!WorldCollisionLifecycle.isEnabled() || owningStore == null) { + return 0; + } + PhysicsTerrainMutationQueueResource queue = + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); + int removed = authoritativeWorldCollisionStreaming().clearSpace(spaceUuid, queue); + queue.removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); + return removed; + } + + @Nonnull + private static WorldCollisionBuildStats withRemovedBodies( + @Nonnull WorldCollisionBuildStats stats, + int removedBodies) { + return new WorldCollisionBuildStats(stats.scannedBlocks(), + stats.solidBlocks(), + stats.culledInteriorBlocks(), + stats.fullCubeRuns(), + stats.detailBoxes(), + stats.colliderBodies(), + removedBodies, + stats.sectionsBuilt(), + stats.sectionsRebuilt(), + stats.voxelBodies()); + } + public void disableWorldCollisionLifecycle() { if (isAuthoritativePhysicsStoreActive()) { return; @@ -1268,9 +1409,10 @@ public void removeSpace(@Nonnull SpaceId spaceId) { @Override public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreSpaceMutations.removeEmptySpace( - authoritativePhysicsStore("remove physics space"), - spaceId); + Store store = authoritativePhysicsStore("remove physics space"); + UUID spaceUuid = requireSpaceUuid(store, spaceId); + clearAuthoritativeWorldCollisionSpace(store, spaceUuid); + PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); return; } requireLegacyMutationAllowed("remove physics space"); @@ -1284,7 +1426,11 @@ public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, if (isAuthoritativePhysicsStoreActive()) { return enqueueAuthoritativePhysicsStoreMutation("remove physics space", spaceId, - store -> PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId)); + store -> { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + clearAuthoritativeWorldCollisionSpace(store, spaceUuid); + PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); + }); } requireLegacyMutationAllowed("remove physics space"); return enqueueOwnerMutation("remove physics space", @@ -1320,6 +1466,12 @@ private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldNa @Override public void clearAllSpaces(@Nonnull String worldName) { + if (isAuthoritativePhysicsStoreActive()) { + Store store = authoritativePhysicsStore("clear physics spaces"); + clearAuthoritativeWorldCollisionStreaming(store); + PhysicsStoreRuntimeCleaner.clearAll(store); + return; + } requireLegacyMutationAllowed("clear physics spaces"); runOwnerMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); } @@ -1327,6 +1479,14 @@ public void clearAllSpaces(@Nonnull String worldName) { @Nonnull @Override public PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName) { + if (isAuthoritativePhysicsStoreActive()) { + return enqueueAuthoritativePhysicsStoreMutation("clear physics spaces", + null, + store -> { + clearAuthoritativeWorldCollisionStreaming(store); + PhysicsStoreRuntimeCleaner.clearAll(store); + }); + } requireLegacyMutationAllowed("clear physics spaces"); return enqueueOwnerMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); @@ -1362,6 +1522,11 @@ private static RuntimeException collectFailure(@Nullable RuntimeException failur */ @Nonnull public PhysicsRuntimeResetResult resetRuntimeStateKeepingSpaces(@Nonnull String worldName) { + if (isAuthoritativePhysicsStoreActive()) { + Store store = authoritativePhysicsStore("reset physics runtime state"); + clearAuthoritativeWorldCollisionStreaming(store); + return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); + } requireLegacyMutationAllowed("reset physics runtime state"); return callOwner("reset physics runtime state", () -> resetRuntimeStateKeepingSpacesDirect(worldName)); @@ -1490,6 +1655,12 @@ private RigidBodyKey addBodyDirect(@Nonnull RigidBodyKey bodyKey, @Override public void destroyBody(@Nonnull RigidBodyKey bodyKey) { + if (isAuthoritativePhysicsStoreActive()) { + PhysicsStoreTopologyMutations.destroyBody( + authoritativePhysicsStore("destroy physics body"), + bodyKey); + return; + } requireLegacyMutationAllowed("destroy physics body"); destroyBody(bodyKey, true); } @@ -1497,11 +1668,22 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey) { @Nonnull @Override public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey) { + if (isAuthoritativePhysicsStoreActive()) { + return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", + bodyKey, + store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyKey)); + } requireLegacyMutationAllowed("destroy physics body"); return destroyBodyAsync(bodyKey, true); } public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { + if (isAuthoritativePhysicsStoreActive()) { + PhysicsStoreTopologyMutations.destroyBody( + authoritativePhysicsStore("destroy physics body"), + bodyKey); + return; + } requireLegacyMutationAllowed("destroy physics body"); runOwnerMutation("destroy physics body", () -> destroyBodyDirect(bodyKey, removeFromSpace)); } @@ -1509,6 +1691,11 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) @Nonnull public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { + if (isAuthoritativePhysicsStoreActive()) { + return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", + bodyKey, + store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyKey)); + } requireLegacyMutationAllowed("destroy physics body"); return enqueueOwnerMutation("destroy physics body", bodyKey, @@ -1836,6 +2023,12 @@ public void clearSyntheticVisualInterests() { @Override public void clearBodies() { + if (isAuthoritativePhysicsStoreActive()) { + Store store = authoritativePhysicsStore("clear physics bodies"); + clearAuthoritativeWorldCollisionStreaming(store); + PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); + return; + } requireLegacyMutationAllowed("clear physics bodies"); runOwnerMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); } @@ -1843,6 +2036,14 @@ public void clearBodies() { @Nonnull @Override public PhysicsMutationHandle clearBodiesAsync() { + if (isAuthoritativePhysicsStoreActive()) { + return enqueueAuthoritativePhysicsStoreMutation("clear physics bodies", + null, + store -> { + clearAuthoritativeWorldCollisionStreaming(store); + PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); + }); + } requireLegacyMutationAllowed("clear physics bodies"); return enqueueOwnerMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java index b4b8a81b..f439760d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java @@ -43,8 +43,8 @@ public void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { Objects.requireNonNull(operation, "operation"); if (!canAccessLiveBackendDirectly()) { throw new IllegalStateException("Impulse live backend operation " + operation - + " must run in the physics owner lane. Use copied simulation commands, " - + "copied queries, or an internal owner-routed resource method."); + + " must run in the physics owner lane. Use PhysicsStore row mutation, " + + "queued reads, or an internal owner-routed resource method."); } } diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index d0085c38..87f244f0 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -6,7 +6,6 @@ exports dev.hytalemodding.impulse.core.plugin.body; exports dev.hytalemodding.impulse.core.plugin.codec; - exports dev.hytalemodding.impulse.core.plugin.components; exports dev.hytalemodding.impulse.core.plugin.events; exports dev.hytalemodding.impulse.core.plugin.joint; exports dev.hytalemodding.impulse.core.plugin.modules.control; @@ -14,13 +13,11 @@ exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physicsstore; exports dev.hytalemodding.impulse.core.plugin.physicsstore.components; - exports dev.hytalemodding.impulse.core.plugin.physicsstore.requests; + exports dev.hytalemodding.impulse.core.plugin.physicsstore.projection; exports dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; exports dev.hytalemodding.impulse.core.plugin.resources; exports dev.hytalemodding.impulse.core.plugin.settings; exports dev.hytalemodding.impulse.core.plugin.simulation; - exports dev.hytalemodding.impulse.core.plugin.simulation.query; - exports dev.hytalemodding.impulse.core.plugin.simulation.recorder; exports dev.hytalemodding.impulse.core.plugin.simulation.view; exports dev.hytalemodding.impulse.core.plugin.snapshot; } From 075d4dd0783362b3ff215164c4f8b4faa34da311 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 19:39:34 +0200 Subject: [PATCH 121/534] fix(physicsstore): clear terrain rows for world collision reset Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 40 +++++++++++++++++++ .../PhysicsWorldRuntimeResource.java | 18 ++++----- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index d64c8627..e3e032e3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -108,6 +108,23 @@ public static void removeSpaceWithContents(@Nonnull Store store, PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceUuid); } + public static int clearTerrainForSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore terrain rows"); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + int removedBodies = 0; + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); + if (spaceHandle != null) { + for (UUID terrainUuid : runtime.terrainUuidsForSpaceHandle(spaceHandle)) { + removedBodies += removeRuntimeTerrain(runtime, terrainUuid); + } + } + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) + .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); + removeRows(store, collectTerrainRows(store, spaceUuid)); + return removedBodies; + } + private static void removeRuntimeContentsForSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull BackendSpaceHandle spaceHandle) { @@ -227,6 +244,29 @@ private static List collectRows(@Nonnull Store store, return new ArrayList<>(removals); } + @Nonnull + private static List collectTerrainRows(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + ComponentType uuidType = UuidComponent.getComponentType(); + ConcurrentLinkedQueue removals = new ConcurrentLinkedQueue<>(); + store.forEachEntityParallel(uuidType, (index, chunk, _) -> { + TerrainColliderComponent terrain = chunk.getComponent(index, + TerrainColliderComponent.getComponentType()); + if (terrain == null || !spaceUuid.equals(terrain.getSpaceUuid())) { + return; + } + UuidComponent uuid = chunk.getComponent(index, uuidType); + if (uuid == null) { + return; + } + removals.add(new RowRemoval(chunk.getReferenceTo(index), + uuid.getUuid(), + RowKind.TERRAIN, + terrain.getPayloadResourceKey())); + }); + return new ArrayList<>(removals); + } + private static boolean matchesJoint(@Nullable JointComponent joint, @Nullable UUID spaceUuid, @Nullable UUID bodyUuid) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 6685465c..44132379 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1054,8 +1054,7 @@ public WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world requireAuthoritativeWorldCollisionSettings(store, spaceId); PhysicsTerrainMutationQueueResource queue = store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); - int removed = authoritativeWorldCollisionStreaming() - .clearSpace(settings.spaceUuid(), queue); + int removed = clearAuthoritativeWorldCollisionSpace(store, settings.spaceUuid()); WorldCollisionPrewarmStats stats = authoritativeWorldCollisionStreaming() .ensureAround(world, settings.spaceUuid(), @@ -1238,14 +1237,15 @@ private void clearAuthoritativeWorldCollisionStreaming(@Nonnull Store store, @Nonnull UUID spaceUuid) { - if (!WorldCollisionLifecycle.isEnabled() || owningStore == null) { - return 0; + int removed = 0; + if (WorldCollisionLifecycle.isEnabled() && owningStore != null) { + PhysicsTerrainMutationQueueResource queue = + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); + removed = authoritativeWorldCollisionStreaming().clearSpace(spaceUuid, queue); } - PhysicsTerrainMutationQueueResource queue = - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); - int removed = authoritativeWorldCollisionStreaming().clearSpace(spaceUuid, queue); - queue.removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); - return removed; + int directlyRemoved = + PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); + return removed != 0 ? removed : directlyRemoved; } @Nonnull From d9309b3add4283e7b7c5a153a497ef61659deb61 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 19:42:34 +0200 Subject: [PATCH 122/534] refactor(examples): use physics world collision facade Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 110 +----------------- 1 file changed, 5 insertions(+), 105 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index ef3c425b..018fcd76 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -21,16 +21,12 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; @@ -42,7 +38,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -117,19 +112,7 @@ public static WorldCollisionBuildStats rebuildPhysicsStoreWorldCollisionAround( @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { - PhysicsStoreWorldCollisionStreamingResource streaming = worldCollisionStreaming(store); - SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); - PhysicsTerrainMutationQueueResource queue = terrainMutationQueue(world); - int removed = streaming.clearSpace(settings.spaceUuid(), queue); - WorldCollisionPrewarmStats stats = streaming.ensureAround(world, - settings.spaceUuid(), - queue, - List.of(center), - radius, - Math.max(0L, world.getTick()), - null, - settings.buildOptions()); - return withRemovedBodies(stats.buildStats(), stats.buildStats().removedBodies() + removed); + return resource(store).rebuildWorldCollisionAround(world, spaceId, center, radius); } @Nonnull @@ -139,15 +122,7 @@ public static WorldCollisionBuildStats refreshPhysicsStoreWorldCollisionAround( @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { - SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); - return worldCollisionStreaming(store).refreshAround(world, - settings.spaceUuid(), - terrainMutationQueue(world), - center, - radius, - Math.max(0L, world.getTick()), - null, - settings.buildOptions()); + return resource(store).refreshWorldCollisionAround(world, spaceId, center, radius); } @Nonnull @@ -158,94 +133,19 @@ public static WorldCollisionPrewarmStats ensurePhysicsStoreWorldCollisionAround( @Nonnull Iterable centers, int radius, long tick) { - SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); - return worldCollisionStreaming(store).ensureAround(world, - settings.spaceUuid(), - terrainMutationQueue(world), - centers, - radius, - tick, - null, - settings.buildOptions()); + return resource(store).ensureWorldCollisionAround(world, spaceId, centers, radius, tick); } public static int clearPhysicsStoreWorldCollision(@Nonnull Store store, @Nonnull World world, @Nonnull SpaceId spaceId) { - SpaceWorldCollisionSettings settings = requireWorldCollisionSettings(world, spaceId); - return worldCollisionStreaming(store).clearSpace(settings.spaceUuid(), - terrainMutationQueue(world)); + return resource(store).clearWorldCollision(spaceId); } @Nonnull public static WorldCollisionStats physicsStoreWorldCollisionStats( @Nonnull Store store) { - return worldCollisionStreaming(store).stats(); - } - - @Nonnull - private static PhysicsStoreWorldCollisionStreamingResource worldCollisionStreaming( - @Nonnull Store store) { - return store.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()); - } - - @Nonnull - private static PhysicsTerrainMutationQueueResource terrainMutationQueue(@Nonnull World world) { - Store store = physicsStore(world); - PhysicsStoreThreading.requireWorldThread(store, - "read PhysicsStore terrain mutation queue"); - return store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); - } - - @Nonnull - private static SpaceWorldCollisionSettings requireWorldCollisionSettings(@Nonnull World world, - @Nonnull SpaceId spaceId) { - Store physics = physicsStore(world); - PhysicsStoreThreading.requireWorldThread(physics, - "read PhysicsStore world-collision settings"); - UUID spaceUuid = resolvePhysicsStoreSpaceUuid(world, spaceId); - if (spaceUuid == null) { - throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() - + " is not bound yet"); - } - Ref spaceRef = physics - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - if (spaceRef == null || !spaceRef.isValid()) { - throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() - + " is not bound yet"); - } - WorldCollisionComponent component = physics.getComponent(spaceRef, - WorldCollisionComponent.getComponentType()); - WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); - if (settings.getMode() == WorldCollisionMode.NONE) { - throw new IllegalStateException("World collision is disabled for space " + spaceId); - } - return new SpaceWorldCollisionSettings(spaceUuid, - settings.getMode(), - settings.getEntityChunkBoundaryMode(), - settings.isNativeVoxelTerrainEnabled(), - settings.getRadius(), - settings.getBodyRadius(), - settings.getTtlTicks(), - settings.getTerrainFriction(), - settings.getTerrainRestitution()); - } - - @Nonnull - private static WorldCollisionBuildStats withRemovedBodies( - @Nonnull WorldCollisionBuildStats stats, - int removedBodies) { - return new WorldCollisionBuildStats(stats.scannedBlocks(), - stats.solidBlocks(), - stats.culledInteriorBlocks(), - stats.fullCubeRuns(), - stats.detailBoxes(), - stats.colliderBodies(), - removedBodies, - stats.sectionsBuilt(), - stats.sectionsRebuilt(), - stats.voxelBodies()); + return resource(store).getWorldCollisionStats(); } @Nonnull From 0e636fdd59465ff1ab63cd7d536b3c2f4e30e7e9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 19:46:32 +0200 Subject: [PATCH 123/534] test(physicsstore): migrate coverage to store rows Signed-off-by: Blovien --- .../PhysicsKinematicControlSystemTest.java | 344 +------- .../WorldCollisionLifecycleTest.java | 38 +- .../PhysicsCollisionLodSystemTest.java | 6 +- .../PhysicsStoreResourceIndexTest.java | 111 +++ .../PhysicsWorldResourceStateTest.java | 571 ------------ .../PhysicsWorldLifecycleStateTest.java | 63 -- .../PhysicsCommandVisibilityStateTest.java | 225 ----- ...icsWorldResourceCommandVisibilityTest.java | 519 ----------- .../PhysicsCommandCompletionTest.java | 67 -- .../PhysicsCommandOperationsTest.java | 39 - .../simulation/PhysicsRecipesTest.java | 165 ---- .../PhysicsSimulationExecutorTest.java | 812 ------------------ .../PhysicsBodyIdentityCleanupSystemTest.java | 117 --- .../body/RigidBodyCommandBatchTest.java | 100 --- .../systems/body/RigidBodySpawnPlanTest.java | 127 --- .../systems/debug/PhysicsDebugSystemTest.java | 11 +- .../systems/sync/PhysicsSyncSystemTest.java | 22 +- ...tachedVisualMaterializationSystemTest.java | 18 +- .../components/PhysicsBodyComponentsTest.java | 164 ---- .../physicsstore/PhysicsBodyRowsTest.java | 63 ++ .../components/BodyCommandComponentTest.java | 72 ++ .../BodyAttachmentComponentTest.java | 69 ++ .../simulation/PhysicsCommandContextTest.java | 375 -------- .../RaycastClosestBatchQueryTest.java | 31 - .../examples/commands/EventsCommandTest.java | 2 - .../commands/ExamplePhysicsUtilsTest.java | 46 +- .../events/PhysicsEventTrackerTest.java | 2 - 27 files changed, 403 insertions(+), 3776 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityStateTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsWorldResourceCommandVisibilityTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandCompletionTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperationsTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsRecipesTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutorTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatchTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlanTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentsTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContextTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchQueryTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java index e8c6fbc9..2f456652 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.control.systems; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -12,13 +11,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsJoint; import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem.ControlAnchorUpdate; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem.ControlMutationState; import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; @@ -26,12 +20,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nonnull; import org.joml.Vector3f; @@ -43,8 +32,8 @@ class PhysicsKinematicControlSystemTest { @Test void controlAnchorUpdateCopiesMutableVectors() { - RigidBodyKey bodyId = RigidBodyKey.random(); - RigidBodyKey anchorBodyId = RigidBodyKey.random(); + UUID bodyId = UUID.randomUUID(); + UUID anchorBodyId = UUID.randomUUID(); Vector3f target = new Vector3f(1.0f, 2.0f, 3.0f); Vector3f releaseVelocity = new Vector3f(4.0f, 5.0f, 6.0f); ControlAnchorUpdate update = new ControlAnchorUpdate(bodyId, @@ -59,67 +48,49 @@ void controlAnchorUpdateCopiesMutableVectors() { } @Test - void pendingControlMutationBlocksAnotherSubmissionUntilComplete() { + void submittedControlMutationSuppressesIdenticalTarget() { ControlMutationState state = new ControlMutationState(); - RigidBodyKey bodyId = RigidBodyKey.random(); - CompletableFuture completion = new CompletableFuture<>(); - PhysicsMutationHandle handle = PhysicsMutationHandle.fromCompletion("test", - null, - completion); + UUID bodyId = UUID.randomUUID(); ControlAnchorUpdate first = update(bodyId, bodyId, 1.0f); - ControlAnchorUpdate second = update(bodyId, bodyId, 2.0f); + ControlAnchorUpdate sameTarget = update(bodyId, bodyId, 1.0f); + ControlAnchorUpdate changedTarget = update(bodyId, bodyId, 2.0f); - state.trackPendingMutation(bodyId, handle, first); + state.trackSubmittedMutation(bodyId, first); - assertTrue(state.hasPendingMutation(bodyId)); - assertNull(state.selectReadyUpdate(bodyId, second)); - - completion.complete(null); - - assertFalse(state.hasPendingMutation(bodyId)); + assertNull(state.selectReadyUpdate(bodyId, sameTarget)); + assertSame(changedTarget, state.selectReadyUpdate(bodyId, changedTarget)); } @Test - void clearingControlMutationStateAllowsImmediateRetry() { + void clearingControlMutationStateAllowsIdenticalTargetRetry() { ControlMutationState state = new ControlMutationState(); - RigidBodyKey bodyId = RigidBodyKey.random(); - CompletableFuture completion = new CompletableFuture<>(); - PhysicsMutationHandle handle = PhysicsMutationHandle.fromCompletion("test", - null, - completion); + UUID bodyId = UUID.randomUUID(); + ControlAnchorUpdate first = update(bodyId, bodyId, 1.0f); + ControlAnchorUpdate retry = update(bodyId, bodyId, 1.0f); - state.trackPendingMutation(bodyId, handle, update(bodyId, bodyId, 1.0f)); + state.trackSubmittedMutation(bodyId, first); state.clear(bodyId); - assertFalse(state.hasPendingMutation(bodyId)); - - completion.complete(null); - assertFalse(state.hasPendingMutation(bodyId)); + assertSame(retry, state.selectReadyUpdate(bodyId, retry)); } @Test - void pendingControlMutationCoalescesLatestTargetUntilCompletion() { + void trackingSubmittedControlMutationUpdatesSuppressionTarget() { ControlMutationState state = new ControlMutationState(); - RigidBodyKey bodyId = RigidBodyKey.random(); - RigidBodyKey anchorBodyId = RigidBodyKey.random(); - CompletableFuture completion = new CompletableFuture<>(); - PhysicsMutationHandle handle = PhysicsMutationHandle.fromCompletion("test", - null, - completion); + UUID bodyId = UUID.randomUUID(); + UUID anchorBodyId = UUID.randomUUID(); ControlAnchorUpdate first = update(bodyId, anchorBodyId, 1.0f); ControlAnchorUpdate second = update(bodyId, anchorBodyId, 2.0f); + ControlAnchorUpdate sameSecondTarget = update(bodyId, anchorBodyId, 2.0f); ControlAnchorUpdate third = update(bodyId, anchorBodyId, 3.0f); - ControlAnchorUpdate current = update(bodyId, anchorBodyId, 4.0f); - state.trackPendingMutation(anchorBodyId, handle, first); + state.trackSubmittedMutation(anchorBodyId, first); + assertSame(second, state.selectReadyUpdate(anchorBodyId, second)); - assertNull(state.selectReadyUpdate(anchorBodyId, second)); - assertNull(state.selectReadyUpdate(anchorBodyId, third)); + state.trackSubmittedMutation(anchorBodyId, second); - completion.complete(null); - ControlAnchorUpdate ready = state.selectReadyUpdate(anchorBodyId, current); - - assertSame(third, ready); + assertNull(state.selectReadyUpdate(anchorBodyId, sameSecondTarget)); + assertSame(third, state.selectReadyUpdate(anchorBodyId, third)); } @Test @@ -130,19 +101,14 @@ void clearingSystemMutationStateAfterReleaseAllowsIdenticalTargetRetry() { EmptyResourceStorage.get()); try { ControlMutationState state = PhysicsKinematicControlSystem.stateFor(store); - RigidBodyKey bodyId = RigidBodyKey.random(); - RigidBodyKey anchorBodyId = RigidBodyKey.random(); - CompletableFuture completion = new CompletableFuture<>(); - PhysicsMutationHandle handle = PhysicsMutationHandle.fromCompletion("test", - null, - completion); + UUID bodyId = UUID.randomUUID(); + UUID anchorBodyId = UUID.randomUUID(); ControlAnchorUpdate first = update(bodyId, anchorBodyId, 1.0f); ControlAnchorUpdate queued = update(bodyId, anchorBodyId, 1.0f); ControlAnchorUpdate afterRelease = update(bodyId, anchorBodyId, 1.0f); - state.trackPendingMutation(anchorBodyId, handle, first); + state.trackSubmittedMutation(anchorBodyId, first); assertNull(state.selectReadyUpdate(anchorBodyId, queued)); - completion.complete(null); PhysicsKinematicControlSystem.clearMutationState(store, anchorBodyId); @@ -153,37 +119,6 @@ void clearingSystemMutationStateAfterReleaseAllowsIdenticalTargetRetry() { } } - @Test - void pendingAnchorCreationIsUsableBeforePublishedRegistrationViewArrives() throws Exception { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:control-pending-anchor-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("control-pending-anchor"); - resource.attachOwnerExecutor(owner); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - RigidBodyKey anchorBodyId = RigidBodyKey.random(); - - resource.submitCommands(10L, commands -> commands - .spawnBody(anchorBodyId, spawn -> spawn - .space(space.id()) - .sphere(0.08f) - .kinematic() - .temporary() - .runtimeOnly())) - .completionSummary() - .toCompletableFuture() - .get(2L, TimeUnit.SECONDS); - - assertNull(resource.getBodyRegistrationView(anchorBodyId)); - assertTrue(resource.hasPublishedOrPendingBodyRegistration(anchorBodyId)); - - resource.detachOwnerExecutor(owner); - } - } - @Test void controlJointCleanupResolvesJointFromBodyIds() { FakePhysicsBackend backend = @@ -217,215 +152,9 @@ void controlJointCleanupResolvesJointFromBodyIds() { assertEquals(0, space.jointCount()); } - @Test - void sessionCleanupDestroysAnchorBodyAndClearsControlledState() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:control-cleanup-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody anchorBody = space.createSphere(0.1f, 1.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey anchorBodyId = resource.addBody(space.id(), - anchorBody, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsJoint controlJoint = - space.createPointJoint(anchorBody, body, new Vector3f(), new Vector3f()); - JointKey controlJointId = resource.addJoint(space.id(), controlJoint); - resource.markBodyControlled(bodyId); - PhysicsControlSessionComponent session = new PhysicsControlSessionComponent(bodyId, - anchorBodyId, - controlJointId, - null, - space.id(), - body.getBodyType(), - 4.0f, - new Vector3f(), - new Vector3f()); - - assertEquals(controlJointId, session.getControlJointKey()); - assertEquals(controlJointId, session.clone().getControlJointKey()); - assertSame(controlJoint, resource.getJoint(controlJointId)); - - PhysicsControlSessionCleanup.cleanup(resource, session); - - assertFalse(resource.isBodyControlled(bodyId)); - assertNull(resource.getJoint(controlJointId)); - assertEquals(1, space.bodyCount()); - assertEquals(0, space.jointCount()); - assertTrue(space.containsBody(body)); - assertFalse(space.containsBody(anchorBody)); - assertEquals(body, resource.getBody(bodyId)); - assertNull(resource.getBodyRegistrationView(anchorBodyId)); - } - - @Test - void sessionCleanupRestoresOriginalBodyState() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:control-cleanup-state-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody anchorBody = space.createSphere(0.1f, 1.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey anchorBodyId = resource.addBody(space.id(), - anchorBody, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsJoint controlJoint = - space.createPointJoint(anchorBody, body, new Vector3f(), new Vector3f()); - JointKey controlJointId = resource.addJoint(space.id(), controlJoint); - body.setBodyType(PhysicsBodyType.KINEMATIC); - body.setLinearVelocity(0.0f, 0.0f, 0.0f); - PhysicsControlSessionComponent session = new PhysicsControlSessionComponent(bodyId, - anchorBodyId, - controlJointId, - null, - space.id(), - PhysicsBodyType.DYNAMIC, - 4.0f, - new Vector3f(), - new Vector3f()); - session.getReleaseVelocity().set(3.0f, 4.0f, 5.0f); - - PhysicsControlSessionCleanup.cleanup(resource, session); - - assertEquals(PhysicsBodyType.DYNAMIC, body.getBodyType()); - assertEquals(new Vector3f(3.0f, 4.0f, 5.0f), body.getLinearVelocity()); - } - - @Test - void sessionCleanupQueuesOwnerReleaseWithoutBlocking() throws Exception { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:control-cleanup-owner-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody anchorBody = space.createSphere(0.1f, 1.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey anchorBodyId = resource.addBody(space.id(), - anchorBody, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsJoint controlJoint = - space.createPointJoint(anchorBody, body, new Vector3f(), new Vector3f()); - JointKey controlJointId = resource.addJoint(space.id(), controlJoint); - resource.markBodyControlled(bodyId); - PhysicsControlSessionComponent session = new PhysicsControlSessionComponent(bodyId, - anchorBodyId, - controlJointId, - null, - space.id(), - body.getBodyType(), - 4.0f, - new Vector3f(), - new Vector3f()); - - CountDownLatch mutationStarted = new CountDownLatch(1); - CountDownLatch releaseMutation = new CountDownLatch(1); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("control-cleanup-owner"); - resource.attachOwnerExecutor(owner); - owner.submitMutation("blocking mutation", () -> { - mutationStarted.countDown(); - assertTrue(releaseMutation.await(2L, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(mutationStarted.await(2L, TimeUnit.SECONDS)); - - CompletableFuture cleanup = CompletableFuture.runAsync( - () -> PhysicsControlSessionCleanup.cleanup(resource, session)); - cleanup.get(200L, TimeUnit.MILLISECONDS); - - assertFalse(resource.isBodyControlled(bodyId)); - assertEquals(2, owner.pendingMutations()); - - releaseMutation.countDown(); - pollMutationCompletions(owner, 2); - resource.detachOwnerExecutor(owner); - } finally { - releaseMutation.countDown(); - } - } - - @Test - void lifecycleSessionCleanupWaitsForOwnerRelease() throws Exception { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:control-cleanup-lifecycle-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody anchorBody = space.createSphere(0.1f, 1.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey anchorBodyId = resource.addBody(space.id(), - anchorBody, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsJoint controlJoint = - space.createPointJoint(anchorBody, body, new Vector3f(), new Vector3f()); - JointKey controlJointId = resource.addJoint(space.id(), controlJoint); - PhysicsControlSessionComponent session = new PhysicsControlSessionComponent(bodyId, - anchorBodyId, - controlJointId, - null, - space.id(), - body.getBodyType(), - 4.0f, - new Vector3f(), - new Vector3f()); - - CountDownLatch mutationStarted = new CountDownLatch(1); - CountDownLatch releaseMutation = new CountDownLatch(1); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("control-cleanup-lifecycle"); - resource.attachOwnerExecutor(owner); - owner.submitMutation("blocking mutation", () -> { - mutationStarted.countDown(); - assertTrue(releaseMutation.await(2L, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(mutationStarted.await(2L, TimeUnit.SECONDS)); - - CompletableFuture cleanup = CompletableFuture.runAsync( - () -> PhysicsControlSessionCleanup.cleanupAndWait(resource, session)); - - Thread.sleep(100L); - assertFalse(cleanup.isDone()); - - releaseMutation.countDown(); - cleanup.get(2L, TimeUnit.SECONDS); - - assertTrue(resource.callOwner("verify lifecycle control cleanup", - () -> resource.getJoint(controlJointId) == null - && !space.containsBody(anchorBody))); - resource.detachOwnerExecutor(owner); - } finally { - releaseMutation.countDown(); - } - } - @Nonnull - private static ControlAnchorUpdate update(@Nonnull RigidBodyKey bodyId, - @Nonnull RigidBodyKey anchorBodyId, + private static ControlAnchorUpdate update(@Nonnull UUID bodyId, + @Nonnull UUID anchorBodyId, float coordinate) { return new ControlAnchorUpdate(bodyId, anchorBodyId, @@ -433,17 +162,4 @@ private static ControlAnchorUpdate update(@Nonnull RigidBodyKey bodyId, new Vector3f(coordinate + 1.0f, coordinate + 1.0f, coordinate + 1.0f)); } - private static void pollMutationCompletions(@Nonnull TestPhysicsOwnerLane owner, - int expected) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - int completed = 0; - while (System.nanoTime() < deadline) { - completed += owner.pollCompletedMutations(8).size(); - if (completed >= expected) { - return; - } - Thread.sleep(10L); - } - assertEquals(expected, completed); - } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java index 903398b6..e52fc06a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java @@ -16,10 +16,12 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -267,17 +269,31 @@ private static Class nestedCollisionCacheClass(@Nonnull String simpleName) { private static RigidBodyKey spawnDynamicBody(PhysicsWorldRuntimeResource resource, SpaceId spaceId) { RigidBodyKey bodyKey = RigidBodyKey.random(); - assertTrue(resource.submitCommands(1L, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(spaceId) - .box(0.5f, 0.5f, 0.5f) - .mass(1.0f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .runtimeOnly())) - .allApplied() - .toCompletableFuture() - .join()); + PhysicsSpaceBinding space = resource.getSpaceBinding(spaceId); + assertNotNull(space); + long backendBodyId = space.runtime().createBody(space.backendSpaceHandle().value(), + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 1.0f, + 0.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + resource.addBodyOnOwner(bodyKey, + spaceId, + new BackendBodyHandle(backendBodyId), + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); return bodyKey; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java index 512d7f94..90920b23 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java @@ -123,7 +123,7 @@ void tierStateCommitsAfterSuccessfulOwnerMutation() { RigidBodyKey bodyId = RigidBodyKey.random(); List updates = new ArrayList<>(); - state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, updates); + state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, false, updates); assertNull(state.tier(bodyId)); state.trackPendingMutation(PhysicsMutationHandle.completed("test", null), updates); @@ -141,7 +141,7 @@ void tierStateRetriesAfterFailedOwnerMutation() { List updates = new ArrayList<>(); assertTrue(state.shouldRefresh(spaceId, 20, 1L)); - state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, updates); + state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, false, updates); state.trackPendingMutation(PhysicsMutationHandle.failed("test", null, new IllegalStateException("boom")), @@ -159,7 +159,7 @@ void restoreClearsTierStateAfterSuccessfulOwnerMutation() { RigidBodyKey bodyId = RigidBodyKey.random(); List updates = new ArrayList<>(); - state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, updates); + state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, false, updates); state.trackPendingMutation(PhysicsMutationHandle.completed("test", null), updates); state.refreshPendingMutation(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java new file mode 100644 index 00000000..638e8e1e --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java @@ -0,0 +1,111 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsStoreResourceIndexTest { + + @Test + void compatibilityIndexMaintainsBothDirectionsWhenMappingsMove() { + PhysicsSpaceCompatibilityIndexResource index = new PhysicsSpaceCompatibilityIndexResource(); + UUID firstSpaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000001"); + UUID secondSpaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000002"); + + index.putSpace(new SpaceId(7), firstSpaceUuid); + index.putSpace(new SpaceId(8), firstSpaceUuid); + index.putSpace(new SpaceId(8), secondSpaceUuid); + + assertNull(index.getSpaceUuid(new SpaceId(7))); + assertNull(index.getSpaceId(firstSpaceUuid)); + assertEquals(secondSpaceUuid, index.getSpaceUuid(new SpaceId(8))); + assertEquals(new SpaceId(8), index.getSpaceId(secondSpaceUuid)); + assertEquals(List.of(new SpaceId(8)), List.copyOf(index.spaceIds())); + } + + @Test + void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { + PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); + BackendId backendId = new BackendId("test:runtime-index"); + PhysicsBackendRuntime backendRuntime = + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000003"); + UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000004"); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(31); + BackendBodyHandle bodyHandle = new BackendBodyHandle(42L); + RigidBodyKey bodyKey = RigidBodyKey.random(); + + runtime.putRuntime(backendId, backendRuntime); + runtime.putSpaceBinding(spaceUuid, backendId, spaceHandle); + runtime.putBodyHandle(bodyUuid, spaceUuid, spaceHandle, bodyHandle); + runtime.putBodyHitMetadata(bodyHandle, bodyKey, PhysicsBodyType.DYNAMIC, ShapeType.BOX); + + assertSame(backendRuntime, runtime.getRuntime(backendId)); + assertEquals(spaceHandle, runtime.getSpaceHandle(spaceUuid)); + assertEquals(backendId, runtime.getSpaceBackendId(spaceUuid)); + assertEquals(bodyHandle, runtime.getBodyHandle(bodyUuid)); + assertEquals(spaceHandle, runtime.getBodySpaceHandle(bodyUuid)); + assertEquals(bodyUuid, runtime.getBodySnapshotMetadata(bodyHandle.value()).bodyUuid()); + assertEquals(bodyKey, runtime.getBodyHitMetadata(bodyHandle).bodyKey()); + + List handles = new ArrayList<>(); + runtime.forEachBodyHandle(spaceHandle, handles::add); + assertEquals(List.of(bodyHandle.value()), handles); + + runtime.removeBodyHandle(bodyUuid); + + assertNull(runtime.getBodyHandle(bodyUuid)); + assertNull(runtime.getBodySpaceHandle(bodyUuid)); + assertNull(runtime.getBodySnapshotMetadata(bodyHandle.value())); + assertNull(runtime.getBodyHitMetadata(bodyHandle)); + handles.clear(); + runtime.forEachBodyHandle(spaceHandle, handles::add); + assertEquals(List.of(), handles); + } + + @Test + void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { + PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000005"); + UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000006"); + PhysicsStoreBodySnapshot body = new PhysicsStoreBodySnapshot(bodyUuid, + spaceUuid, + PhysicsBodyType.KINEMATIC, + new Vector3f(1.0f, 2.0f, 3.0f), + new Quaternionf(), + new Vector3f(4.0f, 5.0f, 6.0f), + new Vector3f(), + 0.25f, + false); + PhysicsStoreSnapshotFrame frame = new PhysicsStoreSnapshotFrame(11L, 0.05f, List.of(body)); + + resource.publish(frame); + + assertEquals(frame, resource.getLatestFrame()); + assertEquals(body, resource.getBody(bodyUuid)); + assertNull(resource.getBody(UUID.randomUUID())); + + resource.clear(); + + assertEquals(PhysicsStoreSnapshotFrame.EMPTY, resource.getLatestFrame()); + assertNull(resource.getBody(bodyUuid)); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java index 2c9ea611..215f0819 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java @@ -30,7 +30,6 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState; import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; -import dev.hytalemodding.impulse.core.internal.simulation.recorder.MutablePhysicsCommandContext; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCommand; import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; @@ -39,20 +38,9 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsCommandBatchEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsStepEvent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RigidBodyStateQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.time.Duration; import java.util.ArrayList; @@ -204,431 +192,6 @@ void chunkBoundaryPauseStateCopiesTargetChunkFootprint() { assertArrayEquals(new long[] {10L, 11L}, state.getTargetChunkIndices()); } - @Test - void simulationCommandBufferMutatesBodiesOnOwnerLane() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-command-buffer-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - var handle = resource.submitCommands(99L, 2, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .mass(1.0f) - .dynamic() - .position(1.0f, 2.0f, 3.0f) - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY)) - .body(bodyKey) - .setVelocity(4.0f, 5.0f, 6.0f, 0.1f, 0.2f, 0.3f, true)); - var results = handle.completion().toCompletableFuture().join(); - - assertTrue(handle.completionSummary().toCompletableFuture().join().allApplied()); - assertEquals(2, results.size()); - assertEquals(1L, results.getFirst().commandSequence()); - assertEquals(2L, results.get(1).commandSequence()); - PhysicsBody body = resource.getBody(bodyKey); - assertNotNull(body); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), body.getPosition()); - assertEquals(new Vector3f(4.0f, 5.0f, 6.0f), body.getLinearVelocity()); - assertEquals(new Vector3f(0.1f, 0.2f, 0.3f), body.getAngularVelocity()); - } - - @Test - void staleCommandBatchRejectsWithoutMutatingAfterWorldEpochChange() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-stale-command-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - MutablePhysicsCommandContext commands = resource.createMutableCommandContext(107L); - commands.spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - - resource.resetRuntimeStateKeepingSpaces("test-world"); - - var results = resource.submitRecordedCommands(commands) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(1, results.size()); - assertEquals(PhysicsCommandResult.Status.REJECTED, results.getFirst().status()); - assertTrue(results.getFirst().commandBatchSequence() > 0L); - assertEquals(107L, results.getFirst().submittedServerTick()); - assertEquals(0L, results.getFirst().includedSnapshotFrameEpoch()); - assertTrue(results.getFirst().message().contains("stale")); - assertNull(resource.getBody(bodyKey)); - assertEquals(0, resource.query(new SpaceBodyCountQuery(space.id())) - .completion() - .toCompletableFuture() - .join()); - } - - @Test - void sameEpochCommandBuffersExecuteInFifoOrderAfterTopologyMutation() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-fifo-command-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey firstKey = RigidBodyKey.random(); - RigidBodyKey secondKey = RigidBodyKey.random(); - MutablePhysicsCommandContext first = resource.createMutableCommandContext(108L); - first.spawnBody(firstKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - MutablePhysicsCommandContext second = resource.createMutableCommandContext(108L); - second.spawnBody(secondKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - - var firstResults = resource.submitRecordedCommands(first) - .completion() - .toCompletableFuture() - .join(); - var secondResults = resource.submitRecordedCommands(second) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(PhysicsCommandResult.Status.APPLIED, firstResults.getFirst().status()); - assertEquals(PhysicsCommandResult.Status.APPLIED, secondResults.getFirst().status()); - assertNotNull(resource.getBody(firstKey)); - assertNotNull(resource.getBody(secondKey)); - assertEquals(2, resource.query(new SpaceBodyCountQuery(space.id())) - .completion() - .toCompletableFuture() - .join()); - } - - @Test - void simulationCommandCanSetSpaceGravity() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-space-gravity-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - - var results = resource.submitCommands(104L, - commands -> commands.setSpaceGravity(space.id(), 0.0f, -9.81f, 0.0f)) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(1, results.size()); - assertEquals(PhysicsCommandResult.Status.APPLIED, results.getFirst().status()); - assertEquals(new Vector3f(0.0f, -9.81f, 0.0f), space.getGravity()); - } - - @Test - void simulationDslRecipesSubmitCopiedCommands() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-dsl-submit-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - - var results = resource.submitCommands(104L, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .position(1.0f, 2.0f, 3.0f) - .dynamic()) - .body(bodyKey) - .setVelocity(new Vector3f(2.0f, 0.0f, 0.0f), new Vector3f(), true)) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(2, results.size()); - PhysicsBody body = resource.getBody(bodyKey); - assertNotNull(body); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), body.getPosition()); - assertEquals(new Vector3f(2.0f, 0.0f, 0.0f), body.getLinearVelocity()); - } - - @Test - void commandCompletionExposesOwnerExecutionMetadataBeforeSnapshotPublication() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-command-latency-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - - var results = resource.submitCommands(123L, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())) - .completion() - .toCompletableFuture() - .join(); - - PhysicsCommandResult result = results.getFirst(); - assertEquals(PhysicsCommandResult.Status.APPLIED, result.status()); - assertEquals(1L, result.commandSequence()); - assertTrue(result.commandBatchSequence() > 0L); - assertEquals(123L, result.submittedServerTick()); - assertEquals(0L, result.includedSnapshotFrameEpoch()); - PublishedPhysicsSnapshotFrame frameAtCompletion = resource.getLatestPublishedFrame(); - assertEquals(PublishedPhysicsSnapshotFrame.Status.EMPTY, frameAtCompletion.status()); - assertEquals(0, frameAtCompletion.bodyCount()); - - PublishedPhysicsSnapshotFrame published = resource.capturePublishedSnapshotFrame(77L, - 124L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - assertTrue(published.frameEpoch() > result.includedSnapshotFrameEpoch()); - assertEquals(1, published.bodyCount()); - assertEquals(124L, published.serverTick()); - } - - @Test - void commandCompletionPublishesValueOnlyEventFrameBeforeSnapshotVisibility() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-command-event-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - - var handle = resource.submitCommands(127L, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())); - var results = handle.completion() - .toCompletableFuture() - .join(); - - PhysicsCommandResult result = results.getFirst(); - PhysicsEventFrame eventFrame = resource.getLatestEventFrame(); - PhysicsCommandBatchEvent event = eventFrame.commandBatches().getFirst(); - - assertEquals(1, eventFrame.commandBatchCount()); - assertEquals(resource.worldEpoch(), eventFrame.worldEpoch()); - assertEquals(0L, eventFrame.latestCapturedSnapshotFrameEpoch()); - assertEquals(0L, eventFrame.latestCapturedSnapshotLastIncludedCommandBatchSequence()); - assertFalse(handle.isIncludedInLatestCapturedSnapshot(eventFrame)); - assertEquals(0L, handle.capturedSnapshotServerTickLatency(eventFrame)); - assertEquals(result.commandBatchSequence(), event.commandBatchSequence()); - assertEquals(127L, event.submittedServerTick()); - assertEquals(1, event.commandCount()); - assertTrue(event.allApplied()); - assertEquals(0L, event.firstRejectedCommandSequence()); - assertNull(event.firstRejectedMessage()); - - PublishedPhysicsSnapshotFrame published = resource.capturePublishedSnapshotFrame(79L, - 128L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - assertTrue(published.lastIncludedCommandBatchSequence() >= event.commandBatchSequence()); - assertTrue(handle.isIncludedInSnapshotFrame(published)); - assertEquals(1L, handle.capturedSnapshotServerTickLatency(published)); - PhysicsEventFrame capturedEventFrame = resource.getLatestEventFrame(); - assertTrue(handle.isIncludedInLatestCapturedSnapshot(capturedEventFrame)); - assertEquals(1L, handle.capturedSnapshotServerTickLatency(capturedEventFrame)); - assertEquals(0L, result.includedSnapshotFrameEpoch()); - } - - @Test - void snapshotCaptureAndPublicationPublishValueOnlyEventFrames() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-snapshot-event-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - resource.submitCommands(129L, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())) - .completion() - .toCompletableFuture() - .join(); - - PublishedPhysicsSnapshotFrame captured = resource.capturePublishedSnapshotFrame(80L, - 130L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 12L, - false); - PhysicsEventFrame stepFrame = resource.getLatestEventFrame(); - PhysicsStepEvent stepEvent = stepFrame.steps().getFirst(); - - assertEquals(1, stepFrame.stepCount()); - assertEquals(0, stepFrame.commandBatchCount()); - assertEquals(captured.frameEpoch(), stepFrame.latestCapturedSnapshotFrameEpoch()); - assertEquals(captured.lastIncludedCommandBatchSequence(), - stepFrame.latestCapturedSnapshotLastIncludedCommandBatchSequence()); - assertEquals(80L, stepEvent.stepSequence()); - assertEquals(130L, stepEvent.serverTick()); - assertEquals(captured.frameEpoch(), stepEvent.snapshotFrameEpoch()); - assertEquals(captured.status(), stepEvent.snapshotStatus()); - assertEquals(captured.lastIncludedCommandBatchSequence(), stepEvent.lastIncludedCommandBatchSequence()); - assertEquals(1, stepEvent.bodyCount()); - - int applied = resource.applyPublishedSnapshotFrame(captured); - PhysicsEventFrame publicationFrame = resource.getLatestEventFrame(); - PhysicsSnapshotPublicationEvent publicationEvent = publicationFrame.snapshotPublications().getFirst(); - - assertEquals(1, applied); - assertEquals(1, publicationFrame.snapshotPublicationCount()); - assertEquals(0, publicationFrame.stepCount()); - assertEquals(captured.frameEpoch(), publicationEvent.snapshotFrameEpoch()); - assertEquals(captured.stepSequence(), publicationEvent.stepSequence()); - assertEquals(captured.serverTick(), publicationEvent.serverTick()); - assertEquals(captured.lastIncludedCommandBatchSequence(), - publicationEvent.lastIncludedCommandBatchSequence()); - assertEquals(applied, publicationEvent.appliedBodyCount()); - } - - @Test - void eventFrameDistinguishesOlderCapturedSnapshotFromLaterCommandCompletion() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-event-latency-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - - PublishedPhysicsSnapshotFrame capturedBeforeCommand = resource.capturePublishedSnapshotFrame(81L, - 131L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - long capturedLastIncludedCommandBatchSequence = capturedBeforeCommand.lastIncludedCommandBatchSequence(); - - var handle = resource.submitCommands(132L, commands -> commands - .setSpaceGravity(space.id(), 0.0f, -4.0f, 0.0f)); - var results = handle.completion() - .toCompletableFuture() - .join(); - PhysicsCommandResult commandResult = results.getFirst(); - PhysicsEventFrame commandEventFrame = resource.getLatestEventFrame(); - PhysicsCommandBatchEvent commandEvent = commandEventFrame.commandBatches().getFirst(); - - assertEquals(commandResult.commandBatchSequence(), commandEvent.commandBatchSequence()); - assertTrue(commandEvent.commandBatchSequence() > capturedLastIncludedCommandBatchSequence); - assertFalse(commandEventFrame.latestCapturedSnapshotIncludesCommandBatch( - commandEvent.commandBatchSequence())); - assertFalse(commandEventFrame.latestCapturedSnapshotIncludes(commandEvent)); - assertFalse(handle.isIncludedInLatestCapturedSnapshot(commandEventFrame)); - - int applied = resource.applyPublishedSnapshotFrame(capturedBeforeCommand); - PhysicsEventFrame publicationEventFrame = resource.getLatestEventFrame(); - PhysicsSnapshotPublicationEvent publicationEvent = - publicationEventFrame.snapshotPublications().getFirst(); - - assertEquals(0, applied); - assertEquals(capturedBeforeCommand.frameEpoch(), publicationEvent.snapshotFrameEpoch()); - assertEquals(capturedLastIncludedCommandBatchSequence, publicationEvent.lastIncludedCommandBatchSequence()); - assertFalse(publicationEventFrame.latestCapturedSnapshotIncludesCommandBatch( - commandEvent.commandBatchSequence())); - assertFalse(handle.isIncludedInLatestCapturedSnapshot(publicationEventFrame)); - } - - @Test - void publishedSnapshotCaptureKeepsBodiesInCompactStorageUntilPublicListsAreRequested() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:compact-published-frame-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - resource.submitCommands(125L, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())) - .completion() - .toCompletableFuture() - .join(); - - PublishedPhysicsSnapshotFrame published = resource.capturePublishedSnapshotFrame(78L, - 126L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - List visitedBodies = new ArrayList<>(); - - published.forEachBodyCursor(body -> visitedBodies.add(body.bodyKey())); - - assertEquals(1, published.bodyCount()); - assertEquals(List.of(bodyKey), visitedBodies); - assertNotNull(published.spaces()); - } - - @Test - void simulationQueryReturnsCopiedOwnerDataWithoutLiveHandles() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-query-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - PhysicsBody body = space.createSphere(0.5f, 1.0f); - body.setPosition(0.0f, 0.0f, 0.0f); - resource.addBody(bodyKey, - space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - int bodyCount = resource.query(new SpaceBodyCountQuery(space.id())) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(1, bodyCount); - } - @Test void duplicateBodyKeyDoesNotLeaveUnregisteredBackendBodyInSpace() { FakePhysicsBackend backend = @@ -660,140 +223,6 @@ void duplicateBodyKeyDoesNotLeaveUnregisteredBackendBodyInSpace() { assertSame(first, resource.getBody(bodyKey)); } - @Test - void simulationSpawnSettingsAndStateQueryUseCopiedData() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-spawn-settings-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - resource.submitCommands(101L, commands -> commands - .spawnBody(bodyKey, spawn -> spawn - .space(space.id()) - .sphere(0.25f) - .mass(1.0f) - .type(PhysicsBodyType.KINEMATIC) - .position(7.0f, 8.0f, 9.0f) - .settings(RigidBodySpawnSettings.of(0.35f, 0.2f, 0.03f, 0.4f, 7, 11, true)) - .kind(PhysicsBodyKind.TEMPORARY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY))) - .completion() - .toCompletableFuture() - .join(); - - PhysicsBody body = resource.getBody(bodyKey); - assertNotNull(body); - assertEquals(PhysicsBodyType.KINEMATIC, body.getBodyType()); - assertEquals(new Vector3f(7.0f, 8.0f, 9.0f), body.getPosition()); - assertEquals(0.35f, body.getFriction(), 0.0001f); - assertEquals(0.2f, body.getRestitution(), 0.0001f); - assertEquals(0.03f, body.getLinearDamping(), 0.0001f); - assertEquals(0.4f, body.getAngularDamping(), 0.0001f); - assertEquals(7, body.getCollisionGroup()); - assertEquals(11, body.getCollisionMask()); - assertTrue(body.isSensor()); - - RigidBodyStateView state = resource.query(new RigidBodyStateQuery(bodyKey)) - .completion() - .toCompletableFuture() - .join() - .orElseThrow(); - assertEquals(bodyKey, state.bodyKey()); - assertEquals(PhysicsBodyType.KINEMATIC, state.bodyType()); - assertEquals(new Vector3f(7.0f, 8.0f, 9.0f), state.pose().position()); - } - - @Test - void simulationTemplatedBulkSpawnAddsBodiesWithSharedCopiedSettings() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-template-spawn-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey firstKey = RigidBodyKey.random(); - RigidBodyKey secondKey = RigidBodyKey.random(); - resource.submitCommands(101L, commands -> commands.spawnBodies(2, - space.id(), - PhysicsShapeSpec.box(0.4f, 0.4f, 0.4f), - 1.0f, - PhysicsBodyType.DYNAMIC, - RigidBodySpawnSettings.material(0.25f, 0.1f), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> spawns - .body(firstKey, 1.0f, 2.0f, 3.0f) - .body(secondKey, 4.0f, 5.0f, 6.0f))) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(2, space.bodyCount()); - PhysicsBody first = resource.getBody(firstKey); - PhysicsBody second = resource.getBody(secondKey); - assertNotNull(first); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), first.getPosition()); - assertNotNull(second); - assertEquals(new Vector3f(4.0f, 5.0f, 6.0f), second.getPosition()); - assertEquals(0.25f, first.getFriction(), 0.0001f); - assertEquals(0.1f, second.getRestitution(), 0.0001f); - } - - @Test - void simulationCommandCanDestroyJointBetweenBodiesWithoutStoredJointKey() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:simulation-destroy-joint-between-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyAKey = RigidBodyKey.random(); - RigidBodyKey bodyBKey = RigidBodyKey.random(); - PhysicsBody bodyA = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody bodyB = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - resource.addBody(bodyAKey, - space.id(), - bodyA, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - resource.addBody(bodyBKey, - space.id(), - bodyB, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsJoint pointJoint = space.createPointJoint(bodyA, bodyB, new Vector3f(), new Vector3f()); - resource.addJoint(space.id(), pointJoint); - - resource.submitCommands(102L, - commands -> commands.destroyJointBetween(null, space.id(), bodyAKey, bodyBKey)) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(0, space.jointCount()); - - JointKey jointKey = JointKey.random(); - resource.submitCommands(103L, 2, commands -> commands - .joint(jointKey, joint -> joint - .space(space.id()) - .bodies(bodyAKey, bodyBKey) - .point(new Vector3f(), new Vector3f())) - .destroyJointBetween(jointKey, space.id(), bodyAKey, bodyBKey)) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(0, space.jointCount()); - } - @Test void resetRuntimeStateKeepingSpacesReplacesNativeSpacesAndClearsRuntimeState() { FakePhysicsBackend backend = diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java index 31667417..e4e95176 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java @@ -11,17 +11,13 @@ import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.time.Duration; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; @@ -65,64 +61,6 @@ void currentPublishedFrameAppliesReaderSnapshotState() { assertTrue(fixture.resource.getLatestSnapshotAppliedNanos() >= appliedBefore); } - @Test - void commandRegistrationViewsPublishOnlyWhenCurrentFrameApplies() throws Exception { - Fixture fixture = createFixture("registration-view"); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("lifecycle-registration-view"); - fixture.resource.attachOwnerExecutor(owner); - - RigidBodyKey bodyId = RigidBodyKey.random(); - var handle = fixture.resource.submitCommands(30L, commands -> commands - .spawnBody(bodyId, spawn -> spawn - .space(fixture.space.id()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())); - handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertNull(fixture.resource.getBodyRegistrationView(bodyId)); - assertTrue(fixture.resource.isBodyCreationPending(bodyId)); - - PublishedPhysicsSnapshotFrame frame = fixture.resource.capturePublishedSnapshotFrame(12L, - 31L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - assertNull(fixture.resource.getBodyRegistrationView(bodyId)); - assertEquals(1, fixture.resource.applyPublishedSnapshotFrame(frame)); - assertNotNull(fixture.resource.getBodyRegistrationView(bodyId)); - assertFalse(fixture.resource.isBodyCreationPending(bodyId)); - - fixture.resource.detachOwnerExecutor(owner); - } - } - - @Test - void appliedFrameUpdatesCapturedSnapshotCommandInclusion() { - Fixture fixture = createFixture("command-last-included"); - PhysicsCommandResult result = fixture.resource.submitCommands(40L, - commands -> commands.setSpaceGravity(fixture.space.id(), 0.0f, -4.0f, 0.0f)) - .completion() - .toCompletableFuture() - .join() - .getFirst(); - PublishedPhysicsSnapshotFrame frame = fixture.resource.capturePublishedSnapshotFrame(13L, - 41L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - fixture.resource.applyPublishedSnapshotFrame(frame); - PhysicsEventFrame eventFrame = fixture.resource.getLatestEventFrame(); - - assertTrue(frame.lastIncludedCommandBatchSequence() >= result.commandBatchSequence()); - assertTrue(eventFrame.latestCapturedSnapshotIncludesCommandBatch(result.commandBatchSequence())); - assertEquals(frame.lastIncludedCommandBatchSequence(), - eventFrame.latestCapturedSnapshotLastIncludedCommandBatchSequence()); - } - @Test void currentFramePublicationCreatesSnapshotPublicationEvent() { Fixture fixture = createFixture("publication-event"); @@ -144,7 +82,6 @@ void currentFramePublicationCreatesSnapshotPublicationEvent() { assertEquals(frame.worldEpoch(), event.worldEpoch()); assertEquals(frame.stepSequence(), event.stepSequence()); assertEquals(frame.serverTick(), event.serverTick()); - assertEquals(frame.lastIncludedCommandBatchSequence(), event.lastIncludedCommandBatchSequence()); assertEquals(43L, event.publicationServerTick()); assertTrue(event.publicationNanoTime() > 0L); assertEquals(applied, event.appliedBodyCount()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityStateTest.java deleted file mode 100644 index 7d67932b..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsCommandVisibilityStateTest.java +++ /dev/null @@ -1,225 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.recorder.MutablePhysicsCommandContext; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import org.junit.jupiter.api.Test; - -class PhysicsCommandVisibilityStateTest { - - @Test - void singleSpawnBodyCreationUsesExactPendingKeyUntilSnapshotIncludesBatch() { - PhysicsCommandVisibilityState state = new PhysicsCommandVisibilityState(); - RigidBodyKey bodyKey = RigidBodyKey.random(); - RecordedPhysicsCommandBatch batch = singleSpawnBatch(7L, bodyKey); - - assertTrue(state.trackBodyCreationPublication(batch, true)); - - assertTrue(state.isBodyCreationPending(bodyKey, false)); - assertFalse(state.isBodyCreationPending(RigidBodyKey.random(), false)); - - state.applyLastIncludedCommandBatchSequence(7L); - - assertFalse(state.isBodyCreationPending(bodyKey, false)); - } - - @Test - void completedSpawnBodyCreationStaysPendingUntilSnapshotIncludesBatch() { - PhysicsCommandVisibilityState state = new PhysicsCommandVisibilityState(); - RigidBodyKey bodyKey = RigidBodyKey.random(); - RecordedPhysicsCommandBatch batch = singleSpawnBatch(8L, bodyKey); - - assertTrue(state.trackBodyCreationPublication(batch, true)); - - state.markCommandBatchCompleted(8L); - - assertEquals(8L, state.completedCommandBatchSequence()); - assertTrue(state.isBodyCreationPending(bodyKey, false)); - assertFalse(state.isBodyCreationPending(RigidBodyKey.random(), false)); - - state.applyLastIncludedCommandBatchSequence(7L); - - assertTrue(state.isBodyCreationPending(bodyKey, false)); - - state.applyLastIncludedCommandBatchSequence(8L); - - assertFalse(state.isBodyCreationPending(bodyKey, false)); - } - - @Test - void multiSingleSpawnBodyCreationTracksOnlySpawnedKeysUntilSnapshotIncludesBatch() { - PhysicsCommandVisibilityState state = new PhysicsCommandVisibilityState(); - RigidBodyKey firstBodyKey = RigidBodyKey.random(); - RigidBodyKey secondBodyKey = RigidBodyKey.random(); - RecordedPhysicsCommandBatch batch = multiSingleSpawnBatch(11L, firstBodyKey, secondBodyKey); - - assertTrue(state.trackBodyCreationPublication(batch, true)); - - assertTrue(state.isBodyCreationPending(firstBodyKey, false)); - assertTrue(state.isBodyCreationPending(secondBodyKey, false)); - assertFalse(state.isBodyCreationPending(RigidBodyKey.random(), false)); - - state.applyLastIncludedCommandBatchSequence(10L); - assertTrue(state.isBodyCreationPending(firstBodyKey, false)); - assertTrue(state.isBodyCreationPending(secondBodyKey, false)); - assertFalse(state.isBodyCreationPending(RigidBodyKey.random(), false)); - - state.applyLastIncludedCommandBatchSequence(11L); - assertFalse(state.isBodyCreationPending(firstBodyKey, false)); - assertFalse(state.isBodyCreationPending(secondBodyKey, false)); - } - - @Test - void spawnBatchBodyCreationTracksOnlySpawnedKeysUntilSnapshotIncludesBatch() { - PhysicsCommandVisibilityState state = new PhysicsCommandVisibilityState(); - RigidBodyKey firstBodyKey = RigidBodyKey.random(); - RigidBodyKey secondBodyKey = RigidBodyKey.random(); - RecordedPhysicsCommandBatch batch = spawnBatch(12L, firstBodyKey, secondBodyKey); - - assertTrue(state.trackBodyCreationPublication(batch, true)); - - assertTrue(state.isBodyCreationPending(firstBodyKey, false)); - assertTrue(state.isBodyCreationPending(secondBodyKey, false)); - assertFalse(state.isBodyCreationPending(RigidBodyKey.random(), false)); - - state.applyLastIncludedCommandBatchSequence(12L); - - assertFalse(state.isBodyCreationPending(firstBodyKey, false)); - assertFalse(state.isBodyCreationPending(secondBodyKey, false)); - } - - @Test - void templateSpawnBodyCreationTracksOnlySpawnedKeysUntilSnapshotIncludesBatch() { - PhysicsCommandVisibilityState state = new PhysicsCommandVisibilityState(); - RigidBodyKey firstBodyKey = RigidBodyKey.random(); - RigidBodyKey secondBodyKey = RigidBodyKey.random(); - RecordedPhysicsCommandBatch batch = templateSpawnBatch(13L, firstBodyKey, secondBodyKey); - - assertTrue(state.trackBodyCreationPublication(batch, true)); - - assertTrue(state.isBodyCreationPending(firstBodyKey, false)); - assertTrue(state.isBodyCreationPending(secondBodyKey, false)); - assertFalse(state.isBodyCreationPending(RigidBodyKey.random(), false)); - - state.applyLastIncludedCommandBatchSequence(13L); - - assertFalse(state.isBodyCreationPending(firstBodyKey, false)); - assertFalse(state.isBodyCreationPending(secondBodyKey, false)); - } - - @Test - void commandWorldEpochOnlyChangesOutsideCommandBatchExecution() { - PhysicsCommandVisibilityState state = new PhysicsCommandVisibilityState(); - - assertEquals(0L, state.commandWorldEpoch()); - assertTrue(state.markWorldChanged()); - assertEquals(1L, state.commandWorldEpoch()); - - int previousDepth = state.enterCommandBatchExecution(); - try { - assertFalse(state.markWorldChanged()); - assertEquals(1L, state.commandWorldEpoch()); - } finally { - state.exitCommandBatchExecution(previousDepth); - } - - assertTrue(state.markWorldChanged()); - assertEquals(2L, state.commandWorldEpoch()); - } - - @Test - void commandCompletionSequenceTracksMaxCompletedBatch() { - PhysicsCommandVisibilityState state = new PhysicsCommandVisibilityState(); - - state.markCommandBatchCompleted(4L); - state.markCommandBatchCompleted(2L); - - assertEquals(4L, state.completedCommandBatchSequence()); - } - - private static RecordedPhysicsCommandBatch singleSpawnBatch(long sequence, - RigidBodyKey bodyKey) { - MutablePhysicsCommandContext context = new MutablePhysicsCommandContext(1L, 0L); - context.spawnBody(bodyKey, spawn -> spawn - .space(new SpaceId(1)) - .box(0.5f, 0.5f, 0.5f) - .mass(1.0f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - return context.freezeInternal(sequence); - } - - private static RecordedPhysicsCommandBatch multiSingleSpawnBatch(long sequence, - RigidBodyKey firstBodyKey, - RigidBodyKey secondBodyKey) { - MutablePhysicsCommandContext context = new MutablePhysicsCommandContext(1L, 0L); - context.spawnBody(firstBodyKey, spawn -> spawn - .space(new SpaceId(1)) - .box(0.5f, 0.5f, 0.5f) - .mass(1.0f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - context.spawnBody(secondBodyKey, spawn -> spawn - .space(new SpaceId(1)) - .box(0.5f, 0.5f, 0.5f) - .mass(1.0f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - return context.freezeInternal(sequence); - } - - private static RecordedPhysicsCommandBatch spawnBatch(long sequence, - RigidBodyKey firstBodyKey, - RigidBodyKey secondBodyKey) { - MutablePhysicsCommandContext context = new MutablePhysicsCommandContext(1L, 0L); - context.spawnBodies(2, spawns -> { - spawns.body(firstBodyKey, spawn -> spawn - .space(new SpaceId(1)) - .box(0.5f, 0.5f, 0.5f) - .mass(1.0f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - spawns.body(secondBodyKey, spawn -> spawn - .space(new SpaceId(1)) - .box(0.5f, 0.5f, 0.5f) - .mass(1.0f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - }); - return context.freezeInternal(sequence); - } - - private static RecordedPhysicsCommandBatch templateSpawnBatch(long sequence, - RigidBodyKey firstBodyKey, - RigidBodyKey secondBodyKey) { - MutablePhysicsCommandContext context = new MutablePhysicsCommandContext(1L, 0L); - context.spawnBodies(2, - new SpaceId(1), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - PhysicsBodyType.DYNAMIC, - RigidBodySpawnSettings.defaults(), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { - spawns.body(firstBodyKey, 0.0f, 0.0f, 0.0f); - spawns.body(secondBodyKey, 1.0f, 0.0f, 0.0f); - }); - return context.freezeInternal(sequence); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsWorldResourceCommandVisibilityTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsWorldResourceCommandVisibilityTest.java deleted file mode 100644 index 748a94dd..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsWorldResourceCommandVisibilityTest.java +++ /dev/null @@ -1,519 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; -import dev.hytalemodding.impulse.core.plugin.simulation.query.SpaceBodyCountQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; - -class PhysicsWorldResourceCommandVisibilityTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void ownerCommandSpawnPublishesRegistrationViewsWithSnapshotFrame() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-registration-view"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-registration-view"); - resource.attachOwnerExecutor(owner); - - CountDownLatch blockerStarted = new CountDownLatch(1); - CountDownLatch releaseBlocker = new CountDownLatch(1); - owner.submitMutation("block command-buffer spawn", () -> { - blockerStarted.countDown(); - assertTrue(releaseBlocker.await(2, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(blockerStarted.await(2, TimeUnit.SECONDS)); - - RigidBodyKey bodyId = RigidBodyKey.random(); - var handle = resource.submitCommands(201L, commands -> commands - .spawnBody(bodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())); - - assertFalse(handle.completion().toCompletableFuture().isDone()); - assertNull(resource.getBodyRegistrationView(bodyId)); - assertEquals(0, resource.getBodyRegistrationCount()); - - releaseBlocker.countDown(); - var results = handle.completion() - .toCompletableFuture() - .get(2, TimeUnit.SECONDS); - - assertEquals(PhysicsCommandResult.Status.APPLIED, results.getFirst().status()); - assertNull(resource.getBodyRegistrationView(bodyId)); - assertEquals(0, resource.getBodyRegistrationCount()); - assertTrue(resource.isBodyCreationPending(bodyId)); - assertFalse(resource.isBodyCreationPending(RigidBodyKey.random())); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 202L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - assertEquals(1, frame.bodyCount()); - assertNull(resource.getBodyRegistrationView(bodyId)); - assertEquals(0, resource.getBodyRegistrationCount()); - - resource.applyPublishedSnapshotFrame(frame); - - assertNotNull(resource.getBodyRegistrationView(bodyId)); - assertEquals(1, resource.getBodyRegistrationCount()); - assertFalse(resource.isBodyCreationPending(bodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void multiSingleSpawnCommandTracksOnlySpawnedPendingKeys() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-registration-multi-single"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-registration-multi-single"); - resource.attachOwnerExecutor(owner); - - RigidBodyKey firstBodyId = RigidBodyKey.random(); - RigidBodyKey secondBodyId = RigidBodyKey.random(); - var handle = resource.submitCommands(301L, commands -> { - commands.spawnBody(firstBodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - commands.spawnBody(secondBodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - }); - - handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertTrue(resource.isBodyCreationPending(firstBodyId)); - assertTrue(resource.isBodyCreationPending(secondBodyId)); - assertTrue(resource.hasPublishedOrPendingBodyRegistration(firstBodyId)); - assertTrue(resource.hasPublishedOrPendingBodyRegistration(secondBodyId)); - assertFalse(resource.isBodyCreationPending(RigidBodyKey.random())); - assertFalse(resource.hasPublishedOrPendingBodyRegistration(RigidBodyKey.random())); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 302L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - resource.applyPublishedSnapshotFrame(frame); - - assertFalse(resource.isBodyCreationPending(firstBodyId)); - assertFalse(resource.isBodyCreationPending(secondBodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void commandSpawnedBodyIsIndexedForInternalReadersBeforePublishedRegistration() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-indexed-snapshot"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-indexed-snapshot"); - resource.attachOwnerExecutor(owner); - - RigidBodyKey bodyId = RigidBodyKey.random(); - var handle = resource.submitCommands(306L, commands -> commands - .spawnBody(bodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .runtimeOnly())); - - handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertNull(resource.getBodyRegistrationView(bodyId)); - assertTrue(resource.isBodyCreationPending(bodyId)); - - AtomicInteger indexedSnapshots = new AtomicInteger(); - AtomicBoolean foundBody = new AtomicBoolean(); - resource.callOwner("inspect command-spawned indexed body snapshot", () -> { - resource.forEachIndexedBodySnapshot(fixture.spaceId(), - (snapshotBodyId, snapshot, snapshotSpaceId, kind, persistenceMode) -> { - indexedSnapshots.incrementAndGet(); - if (bodyId.equals(snapshotBodyId)) { - foundBody.set(true); - assertEquals(fixture.spaceId(), snapshotSpaceId); - assertTrue(snapshot.isDynamic()); - assertEquals(PhysicsBodyKind.BODY, kind); - assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, persistenceMode); - } - }); - return null; - }); - - assertEquals(1, indexedSnapshots.get()); - assertTrue(foundBody.get()); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void bulkSpawnCommandTracksOnlySpawnedPendingKeys() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-registration-bulk"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-registration-bulk"); - resource.attachOwnerExecutor(owner); - - RigidBodyKey firstBodyId = RigidBodyKey.random(); - RigidBodyKey secondBodyId = RigidBodyKey.random(); - var handle = resource.submitCommands(311L, commands -> commands.spawnBodies(2, spawns -> { - spawns.body(firstBodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - spawns.body(secondBodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - })); - - handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertTrue(resource.isBodyCreationPending(firstBodyId)); - assertTrue(resource.isBodyCreationPending(secondBodyId)); - assertTrue(resource.hasPublishedOrPendingBodyRegistration(firstBodyId)); - assertTrue(resource.hasPublishedOrPendingBodyRegistration(secondBodyId)); - assertFalse(resource.isBodyCreationPending(RigidBodyKey.random())); - assertFalse(resource.hasPublishedOrPendingBodyRegistration(RigidBodyKey.random())); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 312L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - resource.applyPublishedSnapshotFrame(frame); - - assertFalse(resource.isBodyCreationPending(firstBodyId)); - assertFalse(resource.isBodyCreationPending(secondBodyId)); - assertNotNull(resource.getBodyRegistrationView(firstBodyId)); - assertNotNull(resource.getBodyRegistrationView(secondBodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void templateSpawnCommandTracksOnlySpawnedPendingKeys() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-registration-template"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-registration-template"); - resource.attachOwnerExecutor(owner); - - RigidBodyKey firstBodyId = RigidBodyKey.random(); - RigidBodyKey secondBodyId = RigidBodyKey.random(); - var handle = resource.submitCommands(321L, commands -> commands.spawnBodies(2, - fixture.spaceId(), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - PhysicsBodyType.DYNAMIC, - RigidBodySpawnSettings.defaults(), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> spawns - .body(firstBodyId, 0.0f, 0.0f, 0.0f) - .body(secondBodyId, 1.0f, 0.0f, 0.0f))); - - handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertTrue(resource.isBodyCreationPending(firstBodyId)); - assertTrue(resource.isBodyCreationPending(secondBodyId)); - assertTrue(resource.hasPublishedOrPendingBodyRegistration(firstBodyId)); - assertTrue(resource.hasPublishedOrPendingBodyRegistration(secondBodyId)); - assertFalse(resource.isBodyCreationPending(RigidBodyKey.random())); - assertFalse(resource.hasPublishedOrPendingBodyRegistration(RigidBodyKey.random())); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 322L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - resource.applyPublishedSnapshotFrame(frame); - - assertFalse(resource.isBodyCreationPending(firstBodyId)); - assertFalse(resource.isBodyCreationPending(secondBodyId)); - assertNotNull(resource.getBodyRegistrationView(firstBodyId)); - assertNotNull(resource.getBodyRegistrationView(secondBodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void rejectedSpawnCommandClearsPendingKeyAfterIncludedSnapshotFrame() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-registration-rejected"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-registration-rejected"); - resource.attachOwnerExecutor(owner); - - RigidBodyKey bodyId = RigidBodyKey.random(); - var handle = resource.submitCommands(331L, commands -> commands - .spawnBody(bodyId, spawn -> spawn - .space(new SpaceId(Integer.MAX_VALUE)) - .box(0.5f, 0.5f, 0.5f) - .dynamic())); - - var results = handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertEquals(PhysicsCommandResult.Status.REJECTED, results.getFirst().status()); - assertTrue(resource.isBodyCreationPending(bodyId)); - assertFalse(resource.isBodyCreationPending(RigidBodyKey.random())); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 332L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - resource.applyPublishedSnapshotFrame(frame); - - assertFalse(resource.isBodyCreationPending(bodyId)); - assertNull(resource.getBodyRegistrationView(bodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void partiallyRejectedBulkSpawnClearsAllRequestedPendingKeysAfterIncludedSnapshotFrame() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-registration-partial"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - RigidBodyKey createdBodyId = RigidBodyKey.random(); - RigidBodyKey duplicateBodyId = RigidBodyKey.random(); - spawnPublishedBody(resource, fixture.spaceId(), duplicateBodyId); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-registration-partial"); - resource.attachOwnerExecutor(owner); - - var handle = resource.submitCommands(341L, commands -> commands.spawnBodies(2, spawns -> { - spawns.body(createdBodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - spawns.body(duplicateBodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic()); - })); - - var results = handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertEquals(PhysicsCommandResult.Status.REJECTED, results.getFirst().status()); - assertTrue(resource.isBodyCreationPending(createdBodyId)); - assertTrue(resource.isBodyCreationPending(duplicateBodyId)); - assertFalse(resource.isBodyCreationPending(RigidBodyKey.random())); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 342L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - resource.applyPublishedSnapshotFrame(frame); - - assertNotNull(resource.getBodyRegistrationView(createdBodyId)); - assertNotNull(resource.getBodyRegistrationView(duplicateBodyId)); - assertFalse(resource.isBodyCreationPending(createdBodyId)); - assertFalse(resource.isBodyCreationPending(duplicateBodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void nullableSnapshotLookupToleratesStalePublishedRegistrationAfterDestroy() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-stale-registration-snapshot"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-stale-registration-snapshot"); - resource.attachOwnerExecutor(owner); - - RigidBodyKey bodyId = RigidBodyKey.random(); - spawnPublishedBody(resource, fixture.spaceId(), bodyId); - - resource.destroyBodyAsync(bodyId).completion() - .toCompletableFuture() - .get(2, TimeUnit.SECONDS); - - assertNotNull(resource.getBodyRegistrationView(bodyId)); - assertNull(resource.getBodySnapshotIfRegistered(bodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void queuedDestroyAfterCommandSpawnRemovesBodyBeforePublication() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-spawn-destroy-before-publication"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-spawn-destroy-before-publication"); - resource.attachOwnerExecutor(owner); - - CountDownLatch blockerStarted = new CountDownLatch(1); - CountDownLatch releaseBlocker = new CountDownLatch(1); - owner.submitMutation("block command-buffer spawn before destroy", () -> { - blockerStarted.countDown(); - assertTrue(releaseBlocker.await(2, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(blockerStarted.await(2, TimeUnit.SECONDS)); - - RigidBodyKey bodyId = RigidBodyKey.random(); - var spawnHandle = resource.submitCommands(361L, commands -> commands - .spawnBody(bodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())); - CompletableFuture destroyFuture = - CompletableFuture.runAsync(() -> resource.destroyBody(bodyId)); - - assertTrue(resource.isBodyCreationPending(bodyId)); - assertFalse(spawnHandle.completion().toCompletableFuture().isDone()); - assertFalse(destroyFuture.isDone()); - - releaseBlocker.countDown(); - - var spawnResults = spawnHandle.completion() - .toCompletableFuture() - .get(2, TimeUnit.SECONDS); - destroyFuture.get(2, TimeUnit.SECONDS); - - assertEquals(PhysicsCommandResult.Status.APPLIED, spawnResults.getFirst().status()); - assertFalse(resource.isBodyCreationPending(bodyId)); - assertNull(resource.getBodyRegistrationView(bodyId)); - assertEquals(0, resource.query(new SpaceBodyCountQuery(fixture.spaceId())) - .completion() - .toCompletableFuture() - .get(2, TimeUnit.SECONDS)); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 362L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - assertEquals(0, frame.bodyCount()); - resource.applyPublishedSnapshotFrame(frame); - assertFalse(resource.isBodyCreationPending(bodyId)); - assertNull(resource.getBodyRegistrationView(bodyId)); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void ownerDetachPublishesCommandSpawnRegistrationViewsAndClearsPendingKeys() throws Exception { - RuntimeFixture fixture = createRuntimeFixture("owner-command-registration-detach"); - PhysicsWorldRuntimeResource resource = fixture.resource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-command-registration-detach"); - resource.attachOwnerExecutor(owner); - - RigidBodyKey bodyId = RigidBodyKey.random(); - var handle = resource.submitCommands(351L, commands -> commands - .spawnBody(bodyId, spawn -> spawn - .space(fixture.spaceId()) - .box(0.5f, 0.5f, 0.5f) - .dynamic())); - - handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS); - - assertNull(resource.getBodyRegistrationView(bodyId)); - assertTrue(resource.isBodyCreationPending(bodyId)); - - resource.detachOwnerExecutor(owner); - - assertNotNull(resource.getBodyRegistrationView(bodyId)); - assertFalse(resource.isBodyCreationPending(bodyId)); - } - } - - @Nonnull - private static RuntimeFixture createRuntimeFixture(@Nonnull String name) { - BackendId backendId = new BackendId("test:" + name + "-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider(backendId, - false, - false)); - PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = resource.createSpace(backendId, - "test-world", - PhysicsSpaceSettings.defaults()); - return new RuntimeFixture(resource, spaceId); - } - - private static void spawnPublishedBody(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyId) { - var handle = resource.submitCommands(0L, commands -> commands - .spawnBody(bodyId, spawn -> spawn - .space(spaceId) - .box(0.5f, 0.5f, 0.5f) - .dynamic() - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY))); - - var results = handle.completion().toCompletableFuture().join(); - assertEquals(PhysicsCommandResult.Status.APPLIED, results.getFirst().status()); - - PublishedPhysicsSnapshotFrame frame = resource.capturePublishedSnapshotFrame(1L, - 0L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - resource.applyPublishedSnapshotFrame(frame); - assertNotNull(resource.getBodyRegistrationView(bodyId)); - assertFalse(resource.isBodyCreationPending(bodyId)); - } - - private record RuntimeFixture(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull SpaceId spaceId) { - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandCompletionTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandCompletionTest.java deleted file mode 100644 index ee6744c3..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandCompletionTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandBatch; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandCompletion; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandMetadata; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; -import java.util.concurrent.CompletableFuture; -import org.junit.jupiter.api.Test; - -final class PhysicsCommandCompletionTest { - - @Test - void commandHandleSummariesUseCompletionSummary() { - PhysicsCommandMetadata metadata = new PhysicsCommandMetadata(10L, 7L); - PhysicsCommandCompletion completion = PhysicsCommandCompletion.allApplied(metadata, 10_000); - PhysicsCommandHandle handle = PhysicsCommandHandle.fromCompletionSummary( - new PhysicsCommandBatch(metadata, 10_000), - CompletableFuture.completedFuture(completion)); - - assertSame(completion, handle.completionSummary().toCompletableFuture().join()); - assertTrue(handle.allApplied().toCompletableFuture().join()); - assertTrue(handle.firstRejected().toCompletableFuture().join().isEmpty()); - } - - @Test - void compactAllAppliedCompletionExposesLazyResultList() { - PhysicsCommandMetadata metadata = new PhysicsCommandMetadata(10L, 7L); - - PhysicsCommandCompletion completion = PhysicsCommandCompletion.allApplied(metadata, 10_000); - - assertTrue(completion.allApplied()); - assertTrue(completion.firstRejected().isEmpty()); - assertEquals(10_000, completion.results().size()); - PhysicsCommandResult first = completion.results().getFirst(); - PhysicsCommandResult last = completion.results().get(9_999); - assertEquals(1L, first.commandSequence()); - assertEquals(10_000L, last.commandSequence()); - assertEquals(7L, first.commandBatchSequence()); - assertEquals(10L, last.submittedServerTick()); - } - - @Test - void compactAllRejectedCompletionExposesLazyResultList() { - PhysicsCommandMetadata metadata = new PhysicsCommandMetadata(11L, 8L); - - PhysicsCommandCompletion completion = PhysicsCommandCompletion.allRejected(metadata, - 10_000, - "stale batch"); - - assertFalse(completion.allApplied()); - PhysicsCommandResult firstRejected = completion.firstRejected().orElseThrow(); - assertEquals(1L, firstRejected.commandSequence()); - assertEquals("stale batch", firstRejected.message()); - assertEquals(10_000, completion.results().size()); - PhysicsCommandResult last = completion.results().get(9_999); - assertEquals(10_000L, last.commandSequence()); - assertEquals(PhysicsCommandResult.Status.REJECTED, last.status()); - assertEquals(8L, last.commandBatchSequence()); - assertEquals(11L, last.submittedServerTick()); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperationsTest.java deleted file mode 100644 index f28ab613..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsCommandOperationsTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import org.junit.jupiter.api.Test; - -class PhysicsCommandOperationsTest { - - @Test - void requiredObjectAtReturnsTypedSlotValue() { - PhysicsCommandOperations operations = new PhysicsCommandOperations(1); - RigidBodyKey bodyKey = RigidBodyKey.random(); - operations.addDestroyBody(bodyKey); - - RigidBodyKey value = operations.requiredObjectAt(0, 0, RigidBodyKey.class); - - assertSame(bodyKey, value); - } - - @Test - void requiredObjectAtRejectsMissingSlotValue() { - PhysicsCommandOperations operations = new PhysicsCommandOperations(1); - operations.addDestroyJointBetween(null, - new SpaceId(1), - RigidBodyKey.random(), - RigidBodyKey.random()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> operations.requiredObjectAt(0, 0, JointKey.class)); - - assertEquals("Missing physics command object at index=0 slot=0 type=JointKey", - exception.getMessage()); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsRecipesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsRecipesTest.java deleted file mode 100644 index 6cd0981d..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsRecipesTest.java +++ /dev/null @@ -1,165 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.batch.RecordedPhysicsCommandBatch; -import dev.hytalemodding.impulse.core.internal.simulation.recorder.MutablePhysicsCommandContext; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsFalloff; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsRecipes; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import java.util.List; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsRecipesTest { - - @Test - void applyImpulseRecipeCopiesVectorValuesAtConstructionTime() { - RigidBodyKey bodyKey = RigidBodyKey.of(0L, 701L); - Vector3f impulse = new Vector3f(1.0f, 2.0f, 3.0f); - - var recipe = PhysicsRecipes.applyImpulse(bodyKey, impulse); - impulse.set(9.0f, 9.0f, 9.0f); - - PhysicsCommandOperations operations = record(recipe).operations(); - - assertEquals(1, operations.size()); - assertEquals(PhysicsCommandOperations.APPLY_RIGID_BODY_IMPULSE, operations.opcode(0)); - assertEquals(bodyKey, - operations.requiredObjectAt(0, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class)); - assertVector(operations, 0, 1.0f, 2.0f, 3.0f); - } - - @Test - void applyForceRecipeRecordsOneOperationPerBody() { - RigidBodyKey first = RigidBodyKey.of(0L, 702L); - RigidBodyKey second = RigidBodyKey.of(0L, 703L); - - PhysicsCommandOperations operations = record( - PhysicsRecipes.applyForce(List.of(first, second), new Vector3f(4.0f, 5.0f, 6.0f))) - .operations(); - - assertEquals(2, operations.size()); - assertEquals(PhysicsCommandOperations.APPLY_RIGID_BODY_FORCE, operations.opcode(0)); - assertEquals(PhysicsCommandOperations.APPLY_RIGID_BODY_FORCE, operations.opcode(1)); - assertEquals(first, - operations.requiredObjectAt(0, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class)); - assertEquals(second, - operations.requiredObjectAt(1, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class)); - assertVector(operations, 0, 4.0f, 5.0f, 6.0f); - assertVector(operations, 1, 4.0f, 5.0f, 6.0f); - } - - @Test - void radialImpulseUsesSnapshotsFalloffAndDynamicBodiesOnly() { - RigidBodyKey near = RigidBodyKey.of(0L, 704L); - RigidBodyKey far = RigidBodyKey.of(0L, 705L); - RigidBodyKey outside = RigidBodyKey.of(0L, 706L); - RigidBodyKey staticBody = RigidBodyKey.of(0L, 707L); - List entries = List.of( - entry(near, 1.0f, PhysicsBodyType.DYNAMIC), - entry(far, 3.0f, PhysicsBodyType.DYNAMIC), - entry(outside, 6.0f, PhysicsBodyType.DYNAMIC), - entry(staticBody, 1.0f, PhysicsBodyType.STATIC)); - - PhysicsCommandOperations operations = record(PhysicsRecipes.radialImpulse(entries, - new Vector3f(), - 10.0f, - 5.0f, - PhysicsFalloff.linear())).operations(); - - assertEquals(2, operations.size()); - assertEquals(near, - operations.requiredObjectAt(0, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class)); - assertEquals(far, - operations.requiredObjectAt(1, - PhysicsCommandOperations.BODY_COMMAND_BODY_KEY_OBJECT_SLOT, - RigidBodyKey.class)); - assertVector(operations, 0, 8.0f, 0.0f, 0.0f); - assertVector(operations, 1, 4.0f, 0.0f, 0.0f); - } - - private static RecordedPhysicsCommandBatch record( - dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandRecipe recipe) { - MutablePhysicsCommandContext context = new MutablePhysicsCommandContext(1L, 1L); - context.compose(recipe); - return context.freezeInternal(1L); - } - - private static PhysicsBodySnapshotEntry entry(RigidBodyKey bodyKey, - float positionX, - PhysicsBodyType bodyType) { - return new PhysicsBodySnapshotEntry(bodyKey, - PhysicsBodySnapshot.of(positionX, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - bodyType, - false, - false, - 1.0f, - 0.5f, - 0.0f, - 0.0f, - 0.0f, - 1, - 1, - false, - 0.0f, - ShapeType.BOX, - true, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - PhysicsAxis.Y), - new SpaceId(1), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - } - - private static void assertVector(PhysicsCommandOperations operations, - int operationIndex, - float x, - float y, - float z) { - assertEquals(x, - operations.floatAt(operationIndex, - PhysicsCommandOperations.VECTOR_COMMAND_X_FLOAT_SLOT), - 0.0001f); - assertEquals(y, - operations.floatAt(operationIndex, - PhysicsCommandOperations.VECTOR_COMMAND_Y_FLOAT_SLOT), - 0.0001f); - assertEquals(z, - operations.floatAt(operationIndex, - PhysicsCommandOperations.VECTOR_COMMAND_Z_FLOAT_SLOT), - 0.0001f); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutorTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutorTest.java deleted file mode 100644 index fb385aca..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/simulation/PhysicsSimulationExecutorTest.java +++ /dev/null @@ -1,812 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.simulation; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; -import dev.hytalemodding.impulse.core.internal.simulation.query.BenchmarkSpaceStatsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsDebugContactsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.query.PhysicsDebugJointsQuery; -import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsCommandResult; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsSimulationExecutorTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void templatedBulkSpawnExecutesAsSingleCommandResult() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:bulk-spawn-executor-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - SpaceId spaceId = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()) - .id(); - RigidBodyKey first = RigidBodyKey.random(); - RigidBodyKey second = RigidBodyKey.random(); - - List results = resource.submitCommands(1L, 1, commands -> commands.spawnBodies(2, - spaceId, - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - PhysicsBodyType.DYNAMIC, - RigidBodySpawnSettings.defaults(), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> spawns - .body(first, 1.0f, 2.0f, 3.0f) - .body(second, 4.0f, 5.0f, 6.0f))) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(1, results.size()); - assertEquals(PhysicsCommandResult.Status.APPLIED, results.getFirst().status()); - assertEquals(2, resource.getBodyRegistrationCount()); - } - - @Test - void forceAndTorqueCommandsUseScalarBodyApi() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:scalar-body-force-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - SpaceId spaceId = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()) - .id(); - RigidBodyKey bodyKey = RigidBodyKey.random(); - ScalarRecordingBody body = new ScalarRecordingBody(); - resource.addBodyOnOwner(bodyKey, - spaceId, - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - resource.submitCommands(1L, 4, commands -> commands - .applyBodyImpulse(bodyKey, 1.0f, 2.0f, 3.0f, 0.1f, 0.2f, 0.3f) - .applyBodyTorqueImpulse(bodyKey, 4.0f, 5.0f, 6.0f) - .applyBodyForce(bodyKey, 7.0f, 8.0f, 9.0f, 0.4f, 0.5f, 0.6f) - .applyBodyTorque(bodyKey, 10.0f, 11.0f, 12.0f)) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(1, body.scalarImpulseCalls); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), body.lastImpulse); - assertEquals(new Vector3f(0.1f, 0.2f, 0.3f), body.lastImpulseOffset); - assertEquals(1, body.scalarTorqueImpulseCalls); - assertEquals(new Vector3f(4.0f, 5.0f, 6.0f), body.lastTorqueImpulse); - assertEquals(1, body.scalarForceCalls); - assertEquals(new Vector3f(7.0f, 8.0f, 9.0f), body.lastForce); - assertEquals(new Vector3f(0.4f, 0.5f, 0.6f), body.lastForceOffset); - assertEquals(1, body.scalarTorqueCalls); - assertEquals(new Vector3f(10.0f, 11.0f, 12.0f), body.lastTorque); - assertEquals(4, body.activateCalls); - } - - @Test - void transformCommandsUseScalarRotationApi() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:scalar-transform-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - SpaceId spaceId = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()) - .id(); - RigidBodyKey bodyKey = RigidBodyKey.random(); - ScalarRecordingBody body = new ScalarRecordingBody(); - resource.addBodyOnOwner(bodyKey, - spaceId, - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - Quaternionf rotation = new Quaternionf().rotateXYZ(0.1f, 0.2f, 0.3f); - resource.submitCommands(1L, 1, commands -> commands.setBodyTransform(bodyKey, - 1.0f, - 2.0f, - 3.0f, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - true)).completion().toCompletableFuture().join(); - - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), body.position); - assertEquals(1, body.scalarRotationCalls); - assertEquals(0, body.objectRotationCalls); - assertEquals(1, body.activateCalls); - } - - @Test - void positionCommandsUseScalarBodyApiWithoutTouchingRotation() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:scalar-position-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - SpaceId spaceId = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()) - .id(); - RigidBodyKey bodyKey = RigidBodyKey.random(); - ScalarRecordingBody body = new ScalarRecordingBody(); - resource.addBodyOnOwner(bodyKey, - spaceId, - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - resource.submitCommands(1L, 1, commands -> commands.setBodyPosition(bodyKey, - 5.0f, - 6.0f, - 7.0f, - true)) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(new Vector3f(5.0f, 6.0f, 7.0f), body.position); - assertEquals(1, body.scalarPositionCalls); - assertEquals(0, body.objectPositionCalls); - assertEquals(0, body.scalarRotationCalls); - assertEquals(0, body.objectRotationCalls); - assertEquals(1, body.activateCalls); - } - - @Test - void debugContactQueryReturnsPrimitiveCopiedViewsWithinRadius() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:debug-contact-query-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - InMemoryPhysicsSpace space = (InMemoryPhysicsSpace) resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody bodyA = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody bodyB = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - resource.addBody(space.id(), - bodyA, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - resource.addBody(space.id(), - bodyB, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - space.addContact(new PhysicsContact(bodyA, - bodyB, - new Vector3f(0.0f, 0.0f, 0.0f), - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(0.0f, 2.0f, 0.0f), - -0.1f, - 20.0f)); - - List visible = resource.queryInternal(new PhysicsDebugContactsQuery(space.id(), - 1.0f, - 2.0f, - 3.0f, - 0.5f, - 4)) - .toCompletableFuture() - .join(); - - assertEquals(1, visible.size()); - PhysicsDebugContactView contact = visible.getFirst(); - assertEquals(1.0f, contact.pointX(), 0.00001f); - assertEquals(2.0f, contact.pointY(), 0.00001f); - assertEquals(3.0f, contact.pointZ(), 0.00001f); - assertTrue(contact.hasNormal()); - assertEquals(0.0f, contact.normalX(), 0.00001f); - assertEquals(1.0f, contact.normalY(), 0.00001f); - assertEquals(0.0f, contact.normalZ(), 0.00001f); - - assertTrue(resource.queryInternal(new PhysicsDebugContactsQuery(space.id(), - 3.0f, - 2.0f, - 3.0f, - 0.5f, - 4)) - .toCompletableFuture() - .join() - .isEmpty()); - assertTrue(resource.queryInternal(new PhysicsDebugContactsQuery(space.id(), - 1.0f, - 2.0f, - 3.0f, - 0.5f, - 0)) - .toCompletableFuture() - .join() - .isEmpty()); - } - - @Test - void debugJointQueryReturnsPrimitiveCopiedViewsWithinRadius() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:debug-joint-query-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - InMemoryPhysicsSpace space = (InMemoryPhysicsSpace) resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody bodyA = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody bodyB = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - bodyA.setPosition(1.0f, 0.0f, 0.0f); - bodyB.setPosition(5.0f, 0.0f, 0.0f); - resource.addBody(space.id(), - bodyA, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - resource.addBody(space.id(), - bodyB, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - var liveJoint = space.createHingeJoint(bodyA, - bodyB, - new Vector3f(1.0f, 0.0f, 0.0f), - new Vector3f(-1.0f, 0.0f, 0.0f), - new Vector3f(0.0f, 1.0f, 0.0f)); - resource.addJoint(space.id(), liveJoint); - - List visible = resource.queryInternal(new PhysicsDebugJointsQuery(space.id(), - 3.0f, - 0.0f, - 0.0f, - 1.0f, - 4)) - .toCompletableFuture() - .join(); - - assertEquals(1, visible.size()); - PhysicsDebugJointView joint = visible.getFirst(); - assertEquals(2.0f, joint.anchorAX(), 0.00001f); - assertEquals(0.0f, joint.anchorAY(), 0.00001f); - assertEquals(0.0f, joint.anchorAZ(), 0.00001f); - assertEquals(4.0f, joint.anchorBX(), 0.00001f); - assertEquals(0.0f, joint.anchorBY(), 0.00001f); - assertEquals(0.0f, joint.anchorBZ(), 0.00001f); - assertTrue(joint.hasAxis()); - assertEquals(0.0f, joint.axisX(), 0.00001f); - assertEquals(0.9f, joint.axisY(), 0.00001f); - assertEquals(0.0f, joint.axisZ(), 0.00001f); - - assertTrue(resource.queryInternal(new PhysicsDebugJointsQuery(space.id(), - 8.0f, - 0.0f, - 0.0f, - 1.0f, - 4)) - .toCompletableFuture() - .join() - .isEmpty()); - assertTrue(resource.queryInternal(new PhysicsDebugJointsQuery(space.id(), - 3.0f, - 0.0f, - 0.0f, - 1.0f, - 0)) - .toCompletableFuture() - .join() - .isEmpty()); - } - - @Test - void raycastClosestBatchReturnsMissesByIndex() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:raycast-batch-result-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - SpaceId spaceId = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()) - .id(); - - RaycastClosestBatchResult result = resource.query(new RaycastClosestBatchQuery(spaceId, - List.of(new RaycastSegment(0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f), - new RaycastSegment(1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f)))) - .completion() - .toCompletableFuture() - .join(); - - assertEquals(2, result.rayCount()); - assertEquals(0, result.hitCount()); - assertFalse(result.hasHit(0)); - assertNull(result.hit(0)); - } - - @Test - void benchmarkSpaceStatsQueryClassifiesBodiesWithOneOwnerAggregate() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:benchmark-space-stats-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - InMemoryPhysicsSpace space = (InMemoryPhysicsSpace) resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody registered = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - registered.setPosition(0.0f, 124.0f, 0.0f); - resource.addBodyOnOwner(RigidBodyKey.random(), - space.id(), - registered, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsBody raw = space.createSphere(1.0f, 1.0f); - raw.setPosition(0.0f, -40.0f, 0.0f); - resource.addBody(space.id(), - raw, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - resource.addBody(space.id(), - space.createStaticPlane(122.0f), - PhysicsBodyKind.WORLD_COLLISION, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - BenchmarkSpaceStatsView stats = resource.queryInternal(new BenchmarkSpaceStatsQuery(space.id(), - 122.0f, - 1.0f, - -32.0f, - -128.0f, - false)) - .toCompletableFuture() - .join(); - - assertEquals(3, stats.bodies()); - assertEquals(2, stats.dynamicBodies()); - assertEquals(2, stats.awakeDynamicBodies()); - assertEquals(0, stats.sleepingDynamicBodies()); - assertEquals(1, stats.detachedBodies()); - assertEquals(1, stats.rawBodies()); - assertEquals(0, stats.worldCollisionBodies()); - assertEquals(1, stats.belowPlaneBodies()); - assertEquals(1, stats.belowWorldMinBodies()); - assertEquals(0, stats.belowVoidBodies()); - assertEquals(-40.0, stats.minDynamicBodyY(), 0.00001); - assertEquals(124.0, stats.maxDynamicBodyY(), 0.00001); - } - - @Test - void benchmarkSpaceStatsQueryCountsCacheOwnedWorldCollisionBodies() throws Exception { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:benchmark-space-stats-cache-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - InMemoryPhysicsSpace space = (InMemoryPhysicsSpace) resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - putCachedWorldCollisionBody(resource.worldCollisionCache(), space.id(), 42L); - - BenchmarkSpaceStatsView stats = resource.queryInternal(new BenchmarkSpaceStatsQuery(space.id(), - 122.0f, - 1.0f, - -32.0f, - -128.0f, - false)) - .toCompletableFuture() - .join(); - - assertEquals(1, stats.bodies()); - assertEquals(1, stats.worldCollisionBodies()); - assertEquals(0, stats.rawBodies()); - } - - @SuppressWarnings("unchecked") - private static void putCachedWorldCollisionBody(@Nonnull WorldVoxelCollisionCache worldCache, - @Nonnull SpaceId spaceId, - long backendBodyId) throws Exception { - Object spaceCache = newSpaceCollisionCache(); - Object section = newCachedSection(); - Field backendBodyIds = section.getClass().getDeclaredField("backendBodyIds"); - backendBodyIds.setAccessible(true); - ((List) backendBodyIds.get(section)).add(backendBodyId); - putCachedSection(spaceCache, section); - - Field spaces = WorldVoxelCollisionCache.class.getDeclaredField("spaces"); - spaces.setAccessible(true); - ((Map) spaces.get(worldCache)).put(spaceId.value(), spaceCache); - } - - private static Object newSpaceCollisionCache() throws Exception { - Constructor constructor = nestedCacheClass("SpaceCollisionCache").getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor.newInstance(); - } - - private static Object newCachedSection() throws Exception { - Constructor constructor = nestedCacheClass("CachedSection") - .getDeclaredConstructor(int.class, int.class, int.class, long.class, long.class); - constructor.setAccessible(true); - return constructor.newInstance(0, 0, 0, 0L, 1L); - } - - @SuppressWarnings("unchecked") - private static void putCachedSection(@Nonnull Object cache, @Nonnull Object section) throws Exception { - Method keyMethod = WorldVoxelCollisionCache.class.getDeclaredMethod("packSectionKey", - int.class, - int.class, - int.class); - keyMethod.setAccessible(true); - long key = (long) keyMethod.invoke(null, - intField(section, "chunkX"), - intField(section, "sectionY"), - intField(section, "chunkZ")); - Field sections = cache.getClass().getDeclaredField("sections"); - sections.setAccessible(true); - ((Map) sections.get(cache)).put(key, section); - } - - private static int intField(@Nonnull Object target, @Nonnull String fieldName) throws Exception { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - return field.getInt(target); - } - - @Nonnull - private static Class nestedCacheClass(@Nonnull String simpleName) { - return Arrays.stream(WorldVoxelCollisionCache.class.getDeclaredClasses()) - .filter(candidate -> candidate.getSimpleName().equals(simpleName)) - .findFirst() - .orElseThrow(); - } - - private static final class ScalarRecordingBody implements PhysicsBody { - - private final Vector3f position = new Vector3f(); - private final Quaternionf rotation = new Quaternionf(); - private final Vector3f linearVelocity = new Vector3f(); - private final Vector3f angularVelocity = new Vector3f(); - private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; - private int activateCalls; - private int scalarForceCalls; - private int scalarImpulseCalls; - private int scalarPositionCalls; - private int scalarRotationCalls; - private int scalarTorqueCalls; - private int scalarTorqueImpulseCalls; - private int objectPositionCalls; - private int objectRotationCalls; - private final Vector3f lastForce = new Vector3f(); - private final Vector3f lastForceOffset = new Vector3f(); - private final Vector3f lastImpulse = new Vector3f(); - private final Vector3f lastImpulseOffset = new Vector3f(); - private final Vector3f lastTorque = new Vector3f(); - private final Vector3f lastTorqueImpulse = new Vector3f(); - - @Override - public void setPosition(float x, float y, float z) { - scalarPositionCalls++; - position.set(x, y, z); - } - - @Override - public void setPosition(@Nonnull Vector3f pos) { - objectPositionCalls++; - position.set(pos); - } - - @Nonnull - @Override - public Vector3f getPosition() { - return new Vector3f(position); - } - - @Override - public void setRotation(float x, float y, float z, float w) { - scalarRotationCalls++; - rotation.set(x, y, z, w); - } - - @Override - public void setRotation(@Nonnull Quaternionf rot) { - objectRotationCalls++; - rotation.set(rot); - } - - @Nonnull - @Override - public Quaternionf getRotation() { - return new Quaternionf(rotation); - } - - @Override - public void setRestitution(float restitution) { - } - - @Override - public float getRestitution() { - return 0.0f; - } - - @Override - public void setFriction(float friction) { - } - - @Override - public float getFriction() { - return 0.0f; - } - - @Nonnull - @Override - public PhysicsBodyType getBodyType() { - return bodyType; - } - - @Override - public void setBodyType(@Nonnull PhysicsBodyType bodyType) { - this.bodyType = bodyType; - } - - @Override - public boolean isStatic() { - return bodyType == PhysicsBodyType.STATIC; - } - - @Override - public boolean isKinematic() { - return bodyType == PhysicsBodyType.KINEMATIC; - } - - @Override - public void setKinematic(boolean kinematic) { - bodyType = kinematic ? PhysicsBodyType.KINEMATIC : PhysicsBodyType.DYNAMIC; - } - - @Override - public void activate() { - activateCalls++; - } - - @Override - public boolean isActive() { - return true; - } - - @Override - public boolean isSleeping() { - return false; - } - - @Override - public void sleep() { - } - - @Override - public float getMass() { - return 1.0f; - } - - @Override - public void setMass(float mass) { - } - - @Nonnull - @Override - public Vector3f getLinearVelocity() { - return new Vector3f(linearVelocity); - } - - @Override - public void setLinearVelocity(@Nonnull Vector3f vel) { - linearVelocity.set(vel); - } - - @Override - public void setLinearVelocity(float x, float y, float z) { - linearVelocity.set(x, y, z); - } - - @Nonnull - @Override - public Vector3f getAngularVelocity() { - return new Vector3f(angularVelocity); - } - - @Override - public void setAngularVelocity(@Nonnull Vector3f vel) { - angularVelocity.set(vel); - } - - @Override - public void setAngularVelocity(float x, float y, float z) { - angularVelocity.set(x, y, z); - } - - @Override - public float getLinearDamping() { - return 0.0f; - } - - @Override - public void setLinearDamping(float damping) { - } - - @Override - public float getAngularDamping() { - return 0.0f; - } - - @Override - public void setAngularDamping(float damping) { - } - - @Override - public void applyCentralForce(@Nonnull Vector3f force) { - throw new AssertionError("central vector force should not be used"); - } - - @Override - public void applyCentralForce(float x, float y, float z) { - } - - @Override - public void applyForce(@Nonnull Vector3f force, @Nonnull Vector3f offset) { - throw new AssertionError("offset vector force should not be used"); - } - - @Override - public void applyForce(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - scalarForceCalls++; - lastForce.set(x, y, z); - lastForceOffset.set(offsetX, offsetY, offsetZ); - } - - @Override - public void applyCentralImpulse(@Nonnull Vector3f impulse) { - throw new AssertionError("central vector impulse should not be used"); - } - - @Override - public void applyCentralImpulse(float x, float y, float z) { - } - - @Override - public void applyImpulse(@Nonnull Vector3f impulse, @Nonnull Vector3f offset) { - throw new AssertionError("offset vector impulse should not be used"); - } - - @Override - public void applyImpulse(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - scalarImpulseCalls++; - lastImpulse.set(x, y, z); - lastImpulseOffset.set(offsetX, offsetY, offsetZ); - } - - @Override - public void applyTorque(@Nonnull Vector3f torque) { - throw new AssertionError("vector torque should not be used"); - } - - @Override - public void applyTorque(float x, float y, float z) { - scalarTorqueCalls++; - lastTorque.set(x, y, z); - } - - @Override - public void applyTorqueImpulse(@Nonnull Vector3f torqueImpulse) { - throw new AssertionError("vector torque impulse should not be used"); - } - - @Override - public void applyTorqueImpulse(float x, float y, float z) { - scalarTorqueImpulseCalls++; - lastTorqueImpulse.set(x, y, z); - } - - @Override - public void clearForces() { - } - - @Override - public boolean isSensor() { - return false; - } - - @Override - public void setSensor(boolean sensor) { - } - - @Override - public int getCollisionGroup() { - return 0; - } - - @Override - public int getCollisionMask() { - return 0; - } - - @Override - public void setCollisionFilter(int group, int mask) { - } - - @Override - public boolean isContinuousCollisionEnabled() { - return false; - } - - @Override - public void setContinuousCollisionEnabled(boolean enabled) { - } - - @Nonnull - @Override - public ShapeType getShapeType() { - return ShapeType.SPHERE; - } - - @Nullable - @Override - public Vector3f getBoxHalfExtents() { - return null; - } - - @Override - public float getSphereRadius() { - return 0.5f; - } - - @Override - public float getHalfHeight() { - return 0.0f; - } - - @Nonnull - @Override - public PhysicsAxis getShapeAxis() { - return PhysicsAxis.Y; - } - - @Override - public float getCenterOfMassOffsetY() { - return 0.0f; - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystemTest.java deleted file mode 100644 index a4335307..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/PhysicsBodyIdentityCleanupSystemTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.EmptyResourceStorage; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import org.junit.jupiter.api.Test; - -class PhysicsBodyIdentityCleanupSystemTest { - - @Test - void removingEntityAuthoredIdentityDestroysBackendBody() { - FakePhysicsBackend backend = new FakePhysicsBackend("test:identity-cleanup"); - Impulse.registerBackend(backend); - ComponentRegistry registry = new ComponentRegistry<>(); - ComponentType identityType = - registry.registerComponent(PhysicsBodyIdentityComponent.class, - "PhysicsBodyIdentity", - PhysicsBodyIdentityComponent.CODEC); - ResourceType resourceType = - registry.registerResource(PhysicsWorldResource.class, - LegacyLiveHandleTestResource::new); - Store store = registry.addStore( - new EntityStore(TestInstanceFactory.world("identity-cleanup-test")), - EmptyResourceStorage.get()); - try { - LegacyLiveHandleTestResource resource = - (LegacyLiveHandleTestResource) store.getResource(resourceType); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey bodyKey = RigidBodyKey.random(); - resource.addBody(bodyKey, - space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - new PhysicsBodyIdentityCleanupSystem(identityType, resourceType).onComponentRemoved(null, - new PhysicsBodyIdentityComponent(bodyKey, - space.id(), - PhysicsBodyPersistenceMode.PERSISTENT), - store, - null); - - assertNull(resource.getBody(bodyKey)); - } finally { - registry.removeStore(store); - registry.shutdown(); - } - } - - @Test - void replacingEntityAuthoredIdentityDestroysOldBackendBodyOnly() { - FakePhysicsBackend backend = new FakePhysicsBackend("test:identity-replacement-cleanup"); - Impulse.registerBackend(backend); - ComponentRegistry registry = new ComponentRegistry<>(); - ComponentType identityType = - registry.registerComponent(PhysicsBodyIdentityComponent.class, - "PhysicsBodyIdentity", - PhysicsBodyIdentityComponent.CODEC); - ResourceType resourceType = - registry.registerResource(PhysicsWorldResource.class, - LegacyLiveHandleTestResource::new); - Store store = registry.addStore( - new EntityStore(TestInstanceFactory.world("identity-replacement-cleanup-test")), - EmptyResourceStorage.get()); - try { - LegacyLiveHandleTestResource resource = - (LegacyLiveHandleTestResource) store.getResource(resourceType); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - RigidBodyKey oldKey = RigidBodyKey.random(); - RigidBodyKey newKey = RigidBodyKey.random(); - resource.addBody(oldKey, - space.id(), - space.createBox(0.5f, 0.5f, 0.5f, 1.0f), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.addBody(newKey, - space.id(), - space.createBox(0.5f, 0.5f, 0.5f, 1.0f), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - new PhysicsBodyIdentityCleanupSystem(identityType, resourceType).onComponentSet(null, - new PhysicsBodyIdentityComponent(oldKey, - space.id(), - PhysicsBodyPersistenceMode.PERSISTENT), - new PhysicsBodyIdentityComponent(newKey, - space.id(), - PhysicsBodyPersistenceMode.PERSISTENT), - store, - null); - - assertNull(resource.getBody(oldKey)); - assertNotNull(resource.getBody(newKey)); - } finally { - registry.removeStore(store); - registry.shutdown(); - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatchTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatchTest.java deleted file mode 100644 index 1bed478c..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodyCommandBatchTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.recorder.MutablePhysicsCommandContext; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class RigidBodyCommandBatchTest { - - @Test - void recordsMultipleSpawnsAndTargetsIntoOneCommandContext() { - RigidBodyKey firstKey = RigidBodyKey.of(0L, 101L); - RigidBodyKey secondKey = RigidBodyKey.of(0L, 102L); - RigidBodyCommandBatch batch = new RigidBodyCommandBatch(); - - batch.addSpawn(spawnPlan(firstKey), new Vector3f(1.0f, 2.0f, 3.0f)); - batch.addSpawn(spawnPlan(secondKey), new Vector3f(4.0f, 5.0f, 6.0f)); - batch.addKinematicTarget(firstKey, target(7.0f, true, true)); - - assertTrue(batch.hasPendingBody(firstKey)); - assertTrue(batch.hasPendingBody(secondKey)); - assertEquals(4, batch.expectedOperations()); - - MutablePhysicsCommandContext context = new MutablePhysicsCommandContext(25L, - 9L, - batch.expectedOperations()); - batch.record(context); - - assertEquals(4, context.freezeInternal(31L).publicBatch().commandCount()); - } - - @Test - void kinematicTargetStateAcceptsOnlyChangedTargets() { - RigidBodyKey bodyKey = RigidBodyKey.of(0L, 103L); - RigidBodyKinematicTargetState state = new RigidBodyKinematicTargetState(); - PhysicsBodyKinematicTargetComponent target = target(1.0f, true, false); - - state.beginTick(); - assertTrue(state.shouldSubmit(bodyKey, target)); - state.finishTick(); - state.beginTick(); - assertFalse(state.shouldSubmit(bodyKey, target(1.0f, true, false))); - assertTrue(state.shouldSubmit(bodyKey, target(2.0f, true, false))); - state.finishTick(); - } - - @Test - void kinematicTargetStatePrunesTargetsThatAreNoLongerObserved() { - RigidBodyKey bodyKey = RigidBodyKey.of(0L, 104L); - RigidBodyKinematicTargetState state = new RigidBodyKinematicTargetState(); - - state.beginTick(); - assertTrue(state.shouldSubmit(bodyKey, target(1.0f, true, false))); - state.finishTick(); - - state.beginTick(); - state.finishTick(); - - assertEquals(0, state.trackedTargetCount()); - } - - private static RigidBodySpawnPlan spawnPlan(RigidBodyKey bodyKey) { - return RigidBodySpawnPlan.create( - new PhysicsBodyIdentityComponent(bodyKey, - new SpaceId(7), - PhysicsBodyPersistenceMode.RUNTIME_ONLY), - PhysicsBodyShapeComponent.box(0.5f, 0.5f, 0.5f), - new PhysicsBodyDynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f), - new PhysicsBodyMaterialComponent(), - new PhysicsBodyCollisionComponent()); - } - - private static PhysicsBodyKinematicTargetComponent target(float positionX, - boolean transformEnabled, - boolean velocityEnabled) { - return new PhysicsBodyKinematicTargetComponent( - new Vector3f(positionX, 2.0f, 3.0f), - new Quaternionf(), - new Vector3f(0.1f, 0.2f, 0.3f), - new Vector3f(0.4f, 0.5f, 0.6f), - transformEnabled, - velocityEnabled, - true); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlanTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlanTest.java deleted file mode 100644 index 47e092fe..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/body/RigidBodySpawnPlanTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.body; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyLifecycleComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; -import org.junit.jupiter.api.Test; - -class RigidBodySpawnPlanTest { - - @Test - void entityAuthoredBodiesProduceSpawnPlansWithExplicitSpace() { - RigidBodyKey bodyKey = RigidBodyKey.of(0L, 81L); - - RigidBodySpawnPlan plan = RigidBodySpawnPlan.create( - identity(bodyKey, new SpaceId(3)), - shape(), - dynamics(), - material(), - collision()); - - assertEquals(bodyKey, plan.bodyKey()); - assertEquals(new SpaceId(3), plan.spaceId()); - assertEquals(PhysicsBodyType.DYNAMIC, plan.bodyType()); - assertEquals(2.0f, plan.mass(), 0.0001f); - assertEquals(PhysicsBodyPersistenceMode.PERSISTENT, plan.persistenceMode()); - } - - @Test - void missingExplicitSpaceFailsInsteadOfChoosingDefault() { - IllegalArgumentException failure = assertThrows(IllegalArgumentException.class, - () -> RigidBodySpawnPlan.create(identity(RigidBodyKey.of(0L, 84L), null), - shape(), - dynamics(), - material(), - collision())); - - assertEquals("PhysicsBodyIdentityComponent must hold a positive explicit SpaceId", - failure.getMessage()); - } - - @Test - void missingShapeFailsInsteadOfSilentlySkippingTheEntity() { - IllegalArgumentException failure = assertThrows(IllegalArgumentException.class, - () -> RigidBodySpawnPlan.create(identity(RigidBodyKey.of(0L, 82L), new SpaceId(4)), - null, - dynamics(), - material(), - collision())); - - assertEquals("PhysicsBodyShapeComponent is required", failure.getMessage()); - } - - @Test - void missingDynamicsFailsInsteadOfApplyingImplicitDefaults() { - IllegalArgumentException failure = assertThrows(IllegalArgumentException.class, - () -> RigidBodySpawnPlan.create(identity(RigidBodyKey.of(0L, 83L), new SpaceId(5)), - shape(), - null, - material(), - collision())); - - assertEquals("PhysicsBodyDynamicsComponent is required", failure.getMessage()); - } - - @Test - void destroyedLifecycleSuppressesEntityAuthoredRespawn() { - RigidBodyKey bodyKey = RigidBodyKey.of(0L, 85L); - PhysicsBodyLifecycleComponent lifecycle = PhysicsBodyLifecycleComponent.destroyed(bodyKey); - - assertTrue(RigidBodyReconciliationPolicy.shouldSuppressBodyReconciliation(lifecycle, - null, - bodyKey)); - } - - @Test - void skippedRestoreKeySuppressesEntityAuthoredRespawn() { - RigidBodyKey bodyKey = RigidBodyKey.of(0L, 86L); - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - persistent.recordRuntimeBodySkipped(bodyKey, "invalid position"); - - assertTrue(RigidBodyReconciliationPolicy.shouldSuppressBodyReconciliation(null, - persistent, - bodyKey)); - assertFalse(RigidBodyReconciliationPolicy.shouldSuppressBodyReconciliation(null, - persistent, - RigidBodyKey.of(0L, 87L))); - } - - private static PhysicsBodyIdentityComponent identity(RigidBodyKey bodyKey, SpaceId spaceId) { - return new PhysicsBodyIdentityComponent(bodyKey, - spaceId, - PhysicsBodyPersistenceMode.PERSISTENT); - } - - private static PhysicsBodyShapeComponent shape() { - return PhysicsBodyShapeComponent.capsule(0.75f, 0.5f, PhysicsAxis.Y); - } - - private static PhysicsBodyDynamicsComponent dynamics() { - return new PhysicsBodyDynamicsComponent(PhysicsBodyType.DYNAMIC, - 2.0f, - 0.0f, - 0.0f); - } - - private static PhysicsBodyMaterialComponent material() { - return new PhysicsBodyMaterialComponent(); - } - - private static PhysicsBodyCollisionComponent collision() { - return new PhysicsBodyCollisionComponent(); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java index 5a2d1a26..a01492d3 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; @@ -62,8 +62,7 @@ void debugCenterInvertsSyncedAttachmentLocalPositionOffset() { Vector3d syncedVisualPosition = new Vector3d(snapshot.positionX(), snapshot.positionY() - snapshot.centerOfMassOffsetY(), snapshot.positionZ()).add(localOffset.x, localOffset.y, localOffset.z); - PhysicsBodyAttachmentComponent attachment = PhysicsBodyAttachmentComponent.externalEntity(RigidBodyKey.random(), - null, + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(RigidBodyKey.random().value(), localOffset, new Quaternionf()); @@ -112,8 +111,7 @@ void debugPoseUsesSyncedTransformRotationWhenSnapshotRotationIsStale() { syncedVisualPosition.add(syncedBodyRotation.transform(new Vector3d(localOffset.x, localOffset.y, localOffset.z))); - PhysicsBodyAttachmentComponent attachment = PhysicsBodyAttachmentComponent.externalEntity(RigidBodyKey.random(), - null, + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(RigidBodyKey.random().value(), localOffset, new Quaternionf()); @@ -159,8 +157,7 @@ void debugCenterUsesAttachmentVisualOriginOffset() { 0.0f, PhysicsAxis.Y); Vector3f localOffset = new Vector3f(0.0f, -0.5f, 0.0f); - PhysicsBodyAttachmentComponent attachment = PhysicsBodyAttachmentComponent.externalEntity(RigidBodyKey.random(), - null, + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(RigidBodyKey.random().value(), localOffset, new Quaternionf(), 0.5f); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java index 0dcbd896..2110b761 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java @@ -4,12 +4,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import org.joml.Quaterniond; import org.joml.Quaternionf; @@ -51,18 +50,14 @@ void visualPredictionSecondsStayZeroWhenDisabledOrMissingFrame() { @Test void bodyTransformSyncOnlyAppliesToBodyAuthoritativeAttachments() { RigidBodyKey bodyKey = RigidBodyKey.random(); - SpaceId spaceId = new SpaceId(1); - assertTrue(PhysicsTransformAuthority.shouldApplyBodyTransform(new PhysicsBodyAttachmentComponent(bodyKey, - spaceId, + assertTrue(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyKey.value(), TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY))); - assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new PhysicsBodyAttachmentComponent(bodyKey, - spaceId, + assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyKey.value(), TransformAuthority.CONTROLLER, AttachmentLifecycle.EXTERNAL_ENTITY))); - assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new PhysicsBodyAttachmentComponent(bodyKey, - spaceId, + assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyKey.value(), TransformAuthority.ENTITY_KINEMATIC, AttachmentLifecycle.EXTERNAL_ENTITY))); } @@ -122,9 +117,8 @@ void bodyCenterInvertsWorldUpCenterOfMassOffsetAndRotatedLocalOffset() { @Test void attachmentVisualOriginOffsetOverridesBodyShapeOffset() { - PhysicsBodyAttachmentComponent attachment = PhysicsBodyAttachmentComponent.externalEntity( - RigidBodyKey.random(), - null, + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity( + RigidBodyKey.random().value(), new Vector3f(0.0f, -0.5f, 0.0f), new Quaternionf(), 0.5f); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java index 7faa6624..d6d4d8ee 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java @@ -17,11 +17,14 @@ import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.List; import java.util.Set; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.joml.Quaternionf; @@ -55,15 +58,13 @@ void materializationTreatsEcsGameplayAttachmentSnapshotAsAttachmentWhenRuntimeIn @Test void impulseOwnedVisualAttachmentIsDisposableWhenBodyMissing() { - PhysicsBodyAttachmentComponent ownedVisual = PhysicsBodyAttachmentComponent.impulseOwnedVisual( - RigidBodyKey.random(), - null, + BodyAttachmentComponent ownedVisual = BodyAttachmentComponent.impulseOwnedVisual( + RigidBodyKey.random().value(), new Vector3f(), new Quaternionf(), 0.5f); - PhysicsBodyAttachmentComponent gameplayEntity = PhysicsBodyAttachmentComponent.externalEntity( - RigidBodyKey.random(), - null); + BodyAttachmentComponent gameplayEntity = BodyAttachmentComponent.externalEntity( + RigidBodyKey.random().value()); assertTrue(ownedVisual.shouldRemoveEntityWhenBodyMissing()); assertFalse(gameplayEntity.shouldRemoveEntityWhenBodyMissing()); @@ -181,6 +182,9 @@ void firstOcclusionFrameKeepsCandidateVisibleWhileRaycastIsPending() { PhysicsAxis.Y); List interests = List.of( new PhysicsVisualRuntime.VisualInterest(new Vector3f(), null)); + resource.getOrCreateBodyVisualInterestState(bodyKey) + .startPendingRaycast(new CompletableFuture>() + .minimalCompletionStage()); DetachedVisualOcclusion.Result result = assertDoesNotThrow(() -> DetachedVisualOcclusion.resolve(resource, bodyKey, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentsTest.java deleted file mode 100644 index 567279e0..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsBodyComponentsTest.java +++ /dev/null @@ -1,164 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsBodyComponentsTest { - - @Test - void splitBodyComponentsCloneValueState() { - RigidBodyKey bodyKey = RigidBodyKey.of(0L, 42L); - PhysicsBodyIdentityComponent identity = new PhysicsBodyIdentityComponent( - bodyKey, - new SpaceId(7), - PhysicsBodyPersistenceMode.PERSISTENT); - PhysicsBodyShapeComponent shape = PhysicsBodyShapeComponent.capsule(0.35f, - 0.8f, - PhysicsAxis.Y); - PhysicsBodyDynamicsComponent dynamics = new PhysicsBodyDynamicsComponent( - PhysicsBodyType.KINEMATIC, - 3.0f, - 0.05f, - 0.1f); - PhysicsBodyMaterialComponent material = new PhysicsBodyMaterialComponent(0.4f, - 0.2f); - PhysicsBodyCollisionComponent collision = new PhysicsBodyCollisionComponent( - false, - PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - PhysicsBodyLifecycleComponent lifecycle = PhysicsBodyLifecycleComponent.pending(bodyKey); - - PhysicsBodyIdentityComponent identityCopy = identity.clone(); - PhysicsBodyShapeComponent shapeCopy = shape.clone(); - PhysicsBodyDynamicsComponent dynamicsCopy = dynamics.clone(); - PhysicsBodyMaterialComponent materialCopy = material.clone(); - PhysicsBodyCollisionComponent collisionCopy = collision.clone(); - PhysicsBodyLifecycleComponent lifecycleCopy = lifecycle.clone(); - - assertNotSame(identity, identityCopy); - assertEquals(bodyKey, identityCopy.getBodyKey()); - assertEquals(new SpaceId(7), identityCopy.getSpaceId()); - assertEquals(PhysicsBodyPersistenceMode.PERSISTENT, identityCopy.getPersistenceMode()); - assertEquals(ShapeType.CAPSULE, shapeCopy.getShapeType()); - assertEquals(0.35f, shapeCopy.getRadius(), 0.0001f); - assertEquals(PhysicsBodyType.KINEMATIC, dynamicsCopy.getBodyType()); - assertEquals(3.0f, dynamicsCopy.getMass(), 0.0001f); - assertEquals(0.4f, materialCopy.getFriction(), 0.0001f); - assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, collisionCopy.getCollisionGroup()); - assertEquals(PhysicsBodyLifecycleComponent.State.PENDING, lifecycleCopy.getState()); - assertEquals(bodyKey, lifecycleCopy.getBodyKey()); - } - - @Test - void spawnIntentCopiesSplitComponentValuesIntoExistingCommandTypes() { - PhysicsBodyShapeComponent shape = PhysicsBodyShapeComponent.capsule(0.35f, - 0.8f, - PhysicsAxis.Y); - PhysicsBodyDynamicsComponent dynamics = new PhysicsBodyDynamicsComponent( - PhysicsBodyType.DYNAMIC, - 1.0f, - 0.05f, - 0.1f); - PhysicsBodyMaterialComponent material = new PhysicsBodyMaterialComponent(0.4f, - 0.2f); - PhysicsBodyCollisionComponent collision = new PhysicsBodyCollisionComponent( - false, - PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - - PhysicsShapeSpec shapeSpec = PhysicsBodyComponentValues.toShapeSpec(shape); - RigidBodySpawnSettings settings = PhysicsBodyComponentValues.toSpawnSettings( - dynamics, - material, - collision); - - assertEquals(ShapeType.CAPSULE, shapeSpec.type()); - assertEquals(0.35f, shapeSpec.radius(), 0.0001f); - assertEquals(0.8f, shapeSpec.halfHeight(), 0.0001f); - assertEquals(PhysicsAxis.Y, shapeSpec.axis()); - assertTrue(settings.hasFriction()); - assertEquals(0.4f, settings.friction(), 0.0001f); - assertTrue(settings.hasLinearDamping()); - assertEquals(0.05f, settings.linearDamping(), 0.0001f); - assertTrue(settings.hasCollisionFilter()); - assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, settings.collisionGroup()); - assertFalse(settings.sensor()); - } - - @Test - void defaultIntentValuesStayFriendlyButExplicitSpaceIsRequired() { - PhysicsBodyIdentityComponent identity = new PhysicsBodyIdentityComponent(); - PhysicsBodyShapeComponent shape = new PhysicsBodyShapeComponent(); - PhysicsBodyDynamicsComponent dynamics = new PhysicsBodyDynamicsComponent(); - PhysicsBodyMaterialComponent material = new PhysicsBodyMaterialComponent(); - PhysicsBodyCollisionComponent collision = new PhysicsBodyCollisionComponent(); - - assertFalse(PhysicsBodyComponentValues.hasExplicitSpace(identity)); - assertEquals(ShapeType.BOX, shape.getShapeType()); - assertEquals(0.5f, shape.getHalfExtentX(), 0.0001f); - assertEquals(PhysicsBodyType.DYNAMIC, dynamics.getBodyType()); - assertEquals(1.0f, dynamics.getMass(), 0.0001f); - assertEquals(0.5f, material.getFriction(), 0.0001f); - assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, collision.getCollisionGroup()); - assertEquals(PhysicsCollisionFilters.ALL, collision.getCollisionMask()); - assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, identity.getPersistenceMode()); - } - - @Test - void kinematicTargetCopiesValueState() { - PhysicsBodyKinematicTargetComponent target = new PhysicsBodyKinematicTargetComponent( - new Vector3f(1.0f, 2.0f, 3.0f), - new Quaternionf(), - new Vector3f(0.25f, 0.0f, 0.0f), - new Vector3f(0.0f, 0.5f, 0.0f), - true, - true, - false); - - PhysicsBodyKinematicTargetComponent copy = target.clone(); - - assertNotSame(target, copy); - assertEquals(1.0f, copy.getPosition().x, 0.0001f); - assertEquals(0.25f, copy.getLinearVelocity().x, 0.0001f); - assertTrue(copy.isVelocityEnabled()); - assertFalse(copy.isActivate()); - } - - @Test - void nonNullPublicComponentFieldsRejectNulls() { - assertThrows(NullPointerException.class, () -> new PhysicsBodyIdentityComponent( - null, - new SpaceId(1), - PhysicsBodyPersistenceMode.PERSISTENT)); - assertThrows(NullPointerException.class, - () -> PhysicsBodyShapeComponent.capsule(0.25f, 0.5f, null)); - assertThrows(NullPointerException.class, - () -> new PhysicsBodyDynamicsComponent(null, 1.0f, 0.0f, 0.0f)); - assertThrows(NullPointerException.class, () -> new PhysicsBodyKinematicTargetComponent( - null, - new Quaternionf(), - new Vector3f(), - new Vector3f(), - true, - false, - true)); - assertThrows(NullPointerException.class, - () -> new PhysicsBodyLifecycleComponent(null, null, null)); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java new file mode 100644 index 00000000..622029b6 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java @@ -0,0 +1,63 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.util.UUID; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsBodyRowsTest { + + @Test + void dynamicBodyRowBuildsSingleBodyGraph() { + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000007"); + UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000042"); + + BodyRowDescriptor row = PhysicsBodyRows.body(spaceUuid, + bodyUuid, + new Vector3f(1.0f, 2.0f, 3.0f), + PhysicsShapeSpec.box(0.5f, 0.75f, 1.0f), + PhysicsBodyType.DYNAMIC, + 2.0f, + RigidBodySpawnSettings.fromOptionalValues(0.4f, + 0.2f, + 0.05f, + 0.1f, + PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.TERRAIN, + true), + null, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.PERSISTENT); + + assertEquals(bodyUuid, row.bodyUuid()); + assertEquals(spaceUuid, row.body().getSpaceUuid()); + assertEquals(PhysicsBodyKind.BODY, row.body().getKind()); + assertEquals(PhysicsBodyPersistenceMode.PERSISTENT, row.body().getPersistenceMode()); + assertEquals(PhysicsBodyType.DYNAMIC, row.dynamics().getBodyType()); + assertEquals(2.0f, row.dynamics().getMass(), 0.0001f); + assertEquals(0.05f, row.dynamics().getLinearDamping(), 0.0001f); + assertEquals(bodyUuid, row.colliderUuid()); + assertEquals(bodyUuid, row.shapeUuid()); + assertEquals(bodyUuid, row.materialUuid()); + assertEquals(bodyUuid, row.filterUuid()); + assertEquals(ShapeType.BOX, row.shape().getShapeType()); + assertEquals(0.75f, row.shape().getHalfExtentY(), 0.0001f); + assertEquals(0.4f, row.material().getFriction(), 0.0001f); + assertEquals(PhysicsCollisionFilters.TERRAIN, row.filter().getCollisionMask()); + assertTrue(row.collider().isSensor()); + assertFalse(row.target().isActive()); + assertTrue(row.target().isTransformEnabled()); + assertFalse(row.target().isVelocityEnabled()); + assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), row.target().getPosition()); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java new file mode 100644 index 00000000..c69f0b04 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java @@ -0,0 +1,72 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.components; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class BodyCommandComponentTest { + + @Test + void appendPreservesOrderAndCopiesEntries() { + BodyCommandComponent first = BodyCommandComponent.wake(); + BodyCommandComponent second = BodyCommandComponent.setVelocity(new Vector3f(1.0f, + 2.0f, + 3.0f), + new Vector3f(0.1f, 0.2f, 0.3f), + true); + + BodyCommandComponent merged = first.append(second); + + BodyCommandComponent.Entry[] entries = merged.entries(); + assertEquals(2, entries.length); + assertEquals(BodyCommandComponent.Kind.WAKE, entries[0].getKind()); + assertEquals(BodyCommandComponent.Kind.SET_VELOCITY, entries[1].getKind()); + assertEquals(1.0f, entries[1].getX(), 0.0001f); + assertEquals(0.3f, entries[1].getAngularZ(), 0.0001f); + assertNotSame(entries[0], merged.entries()[0]); + } + + @Test + void factoriesStoreCommandSpecificFields() { + BodyCommandComponent type = BodyCommandComponent.setType(PhysicsBodyType.KINEMATIC, true); + BodyCommandComponent filter = BodyCommandComponent.setCollisionFilter( + PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.TERRAIN, + false); + BodyCommandComponent impulse = BodyCommandComponent.vector( + BodyCommandComponent.Kind.IMPULSE, + 0.0f, + 6.0f, + 0.0f, + true, + 0.0f, + -0.5f, + 0.0f); + + assertEquals(PhysicsBodyType.KINEMATIC, type.entries()[0].getBodyType()); + assertTrue(type.entries()[0].isActivate()); + assertEquals(PhysicsCollisionFilters.TERRAIN, filter.entries()[0].getCollisionMask()); + assertEquals(BodyCommandComponent.Kind.IMPULSE, impulse.entries()[0].getKind()); + assertTrue(impulse.entries()[0].hasOffset()); + assertEquals(-0.5f, impulse.entries()[0].getOffsetY(), 0.0001f); + } + + @Test + void vectorRejectsNonVectorKind() { + assertThrows(IllegalArgumentException.class, + () -> BodyCommandComponent.vector(BodyCommandComponent.Kind.SET_TYPE, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f)); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java new file mode 100644 index 00000000..9a371684 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java @@ -0,0 +1,69 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore.projection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class BodyAttachmentComponentTest { + + @Test + void attachmentClonesUuidAndProjectionState() { + UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000042"); + BodyAttachmentComponent attachment = BodyAttachmentComponent.impulseOwnedVisual( + bodyUuid, + new Vector3f(1.0f, 2.0f, 3.0f), + new Quaternionf().rotateY(0.5f), + 0.25f); + + BodyAttachmentComponent copy = attachment.clone(); + + assertNotSame(attachment, copy); + assertEquals(bodyUuid, copy.getBodyUuid()); + assertEquals(BodyAttachmentComponent.TransformAuthority.BODY, + copy.getTransformAuthority()); + assertEquals(BodyAttachmentComponent.AttachmentLifecycle.IMPULSE_OWNED_VISUAL, + copy.getLifecycle()); + assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), copy.getLocalPositionOffset()); + assertEquals(0.25f, copy.getVisualOriginOffsetY(), 0.0001f); + assertTrue(copy.shouldRemoveEntityWhenBodyMissing()); + } + + @Test + void externalEntityDefaultsToBodyAuthorityAndKeepsEntityWhenMissing() { + UUID bodyUuid = UUID.randomUUID(); + + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(bodyUuid); + + assertEquals(bodyUuid, attachment.getBodyUuid()); + assertEquals(BodyAttachmentComponent.TransformAuthority.BODY, + attachment.getTransformAuthority()); + assertEquals(BodyAttachmentComponent.AttachmentLifecycle.EXTERNAL_ENTITY, + attachment.getLifecycle()); + assertFalse(attachment.shouldRemoveEntityWhenBodyMissing()); + } + + @Test + void normalizesInvalidVisualOriginOffsetToBodyValue() { + BodyAttachmentComponent attachment = BodyAttachmentComponent.generatedProxy( + UUID.randomUUID(), + new Vector3f(), + new Quaternionf(), + Float.NaN); + + assertEquals(-1.0f, attachment.getVisualOriginOffsetY(), 0.0001f); + assertEquals(0.75f, attachment.resolveVisualOriginOffsetY(0.75f), 0.0001f); + } + + @Test + void rejectsMissingBodyUuid() { + assertThrows(NullPointerException.class, + () -> BodyAttachmentComponent.externalEntity(null)); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContextTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContextTest.java deleted file mode 100644 index 8c39ad3c..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsCommandContextTest.java +++ /dev/null @@ -1,375 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.simulation.recorder.MutablePhysicsCommandContext; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import java.util.concurrent.atomic.AtomicReference; -import java.util.UUID; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.JointCommandRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodyCommandRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnBatchRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnRecorder; -import dev.hytalemodding.impulse.core.plugin.simulation.recorder.RigidBodySpawnTemplateRecorder; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsCommandContextTest { - - @Test - void freezeCapturesMetadataCountAndRejectsFurtherAppends() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000001")); - Vector3f linearVelocity = new Vector3f(1.0f, 2.0f, 3.0f); - Vector3f angularVelocity = new Vector3f(4.0f, 5.0f, 6.0f); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(42L, 7L, 4); - - buffer.body(bodyKey) - .setVelocity(linearVelocity.x, linearVelocity.y, linearVelocity.z, - angularVelocity.x, angularVelocity.y, angularVelocity.z, true); - PhysicsCommandBatch batch = buffer.freezeInternal(11L).publicBatch(); - linearVelocity.set(9.0f, 9.0f, 9.0f); - angularVelocity.set(8.0f, 8.0f, 8.0f); - - assertThrows(IllegalStateException.class, - () -> buffer.body(bodyKey).destroy()); - assertEquals(42L, batch.metadata().submittedServerTick()); - assertEquals(11L, batch.metadata().commandBatchSequence()); - assertEquals(1, batch.commandCount()); - } - - @Test - void fluentDslRecordsComposableRecorderOperations() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000002")); - Vector3f linearVelocity = new Vector3f(1.0f, 0.0f, 0.0f); - Vector3f angularVelocity = new Vector3f(0.0f, 1.0f, 0.0f); - PhysicsCommandRecipe wakeAndMove = commands -> commands.body(bodyKey, - body -> body.setVelocity(linearVelocity, angularVelocity) - .activate()); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(43L, 8L); - - buffer.compose(wakeAndMove); - linearVelocity.set(99.0f, 99.0f, 99.0f); - angularVelocity.set(88.0f, 88.0f, 88.0f); - - PhysicsCommandBatch batch = buffer.freezeInternal(12L).publicBatch(); - - assertEquals(2, batch.commandCount()); - } - - @Test - void thinBodyOperationsRecordThroughRecipesWithoutBodyRecorder() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000012")); - PhysicsCommandRecipe wakeAndMove = commands -> commands - .setBodyVelocity(bodyKey, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, true) - .applyBodyImpulse(bodyKey, 7.0f, 8.0f, 9.0f) - .applyBodyForce(bodyKey, 1.5f, 2.5f, 3.5f, 0.5f, 0.6f, 0.7f) - .setBodyType(bodyKey, PhysicsBodyType.KINEMATIC, true) - .activateBody(bodyKey) - .destroyBody(bodyKey); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(43L, 8L, 6); - - buffer.compose(wakeAndMove); - - PhysicsCommandBatch batch = buffer.freezeInternal(12L).publicBatch(); - - assertEquals(6, batch.commandCount()); - } - - @Test - void bodyRecipeScopesRecorderToCallback() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000017")); - AtomicReference captured = new AtomicReference<>(); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(43L, 8L, 2); - - buffer.body(bodyKey, body -> { - captured.set(body); - body.setPosition(1.0f, 2.0f, 3.0f, true) - .applyForce(4.0f, 5.0f, 6.0f); - }); - - assertThrows(IllegalStateException.class, () -> captured.get().activate()); - assertEquals(2, buffer.freezeInternal(12L).publicBatch().commandCount()); - } - - @Test - void fluentDslRecordsSpawnCommandsWithoutManualCommandConstruction() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000003")); - SpaceId spaceId = new SpaceId(4); - Vector3f position = new Vector3f(2.0f, 3.0f, 4.0f); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(44L, 9L); - - buffer.spawnBody(bodyKey, spawn -> spawn - .space(spaceId) - .box(0.5f, 0.6f, 0.7f) - .mass(2.0f) - .type(PhysicsBodyType.DYNAMIC) - .position(position) - .settings(RigidBodySpawnSettings.material(0.4f, 0.1f)) - .kind(PhysicsBodyKind.BODY) - .persistence(PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - position.set(9.0f, 9.0f, 9.0f); - - PhysicsCommandBatch batch = buffer.freezeInternal(13L).publicBatch(); - - assertEquals(1, batch.commandCount()); - } - - @Test - void bareSpawnRecorderRecordsWhenContextFreezes() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000023")); - SpaceId spaceId = new SpaceId(4); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(44L, 9L); - - buffer.spawnBody(bodyKey) - .space(spaceId) - .box(0.5f, 0.5f, 0.5f) - .position(1.0f, 2.0f, 3.0f); - - PhysicsCommandBatch batch = buffer.freezeInternal(13L).publicBatch(); - - assertEquals(1, batch.commandCount()); - } - - @Test - void singleSpawnRecipeScopesRecorderToCallback() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000021")); - SpaceId spaceId = new SpaceId(4); - AtomicReference captured = new AtomicReference<>(); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(44L, 9L); - - buffer.spawnBody(bodyKey, spawn -> { - captured.set(spawn); - spawn.space(spaceId).box(0.5f, 0.5f, 0.5f); - }); - - assertThrows(IllegalStateException.class, () -> captured.get().mass(2.0f)); - assertEquals(1, buffer.freezeInternal(13L).publicBatch().commandCount()); - } - - @Test - void spawnRecipesSealCapturedRecordersWhenRecipeThrows() { - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000022")); - SpaceId spaceId = new SpaceId(4); - PhysicsShapeSpec box = PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f); - RigidBodySpawnSettings settings = RigidBodySpawnSettings.defaults(); - AtomicReference singleSpawn = new AtomicReference<>(); - AtomicReference bulkSpawns = new AtomicReference<>(); - AtomicReference templatedSpawns = new AtomicReference<>(); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(44L, 9L); - - assertThrows(IllegalStateException.class, - () -> buffer.spawnBody(bodyKey, spawn -> { - singleSpawn.set(spawn); - throw new IllegalStateException("single spawn failed"); - })); - assertThrows(IllegalStateException.class, - () -> buffer.spawnBodies(1, spawns -> { - bulkSpawns.set(spawns); - throw new IllegalStateException("bulk spawn failed"); - })); - assertThrows(IllegalStateException.class, - () -> buffer.spawnBodies(1, - spaceId, - box, - 1.0f, - PhysicsBodyType.DYNAMIC, - settings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { - templatedSpawns.set(spawns); - throw new IllegalStateException("templated spawn failed"); - })); - - assertThrows(IllegalStateException.class, () -> singleSpawn.get().space(spaceId)); - assertThrows(IllegalStateException.class, - () -> bulkSpawns.get().body(bodyKey, spawn -> spawn.space(spaceId).shape(box))); - assertThrows(IllegalStateException.class, - () -> templatedSpawns.get().body(bodyKey, 1.0f, 2.0f, 3.0f)); - } - - @Test - void fluentDslRecordsBulkSpawnCommandForRepeatedBodies() { - SpaceId spaceId = new SpaceId(5); - RigidBodyKey first = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000004")); - RigidBodyKey second = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000005")); - PhysicsShapeSpec box = PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f); - RigidBodySpawnSettings settings = RigidBodySpawnSettings.material(0.6f, 0.2f); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(45L, 10L); - - buffer.spawnBodies(2, spawns -> spawns - .body(first, spawn -> spawn - .space(spaceId) - .shape(box) - .position(1.0f, 2.0f, 3.0f) - .settings(settings)) - .body(second, - spaceId, - box, - 1.0f, - PhysicsBodyType.DYNAMIC, - 4.0f, - 5.0f, - 6.0f, - settings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - - PhysicsCommandBatch batch = buffer.freezeInternal(14L).publicBatch(); - - assertEquals(1, batch.commandCount()); - } - - @Test - void fluentDslRecordsTemplatedBulkSpawnCommandForRepeatedBodies() { - SpaceId spaceId = new SpaceId(5); - RigidBodyKey first = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000014")); - RigidBodyKey second = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000015")); - PhysicsShapeSpec box = PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f); - RigidBodySpawnSettings settings = RigidBodySpawnSettings.material(0.6f, 0.2f); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(45L, 10L); - - buffer.spawnBodies(2, - spaceId, - box, - 1.0f, - PhysicsBodyType.DYNAMIC, - settings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> spawns - .body(first, 1.0f, 2.0f, 3.0f) - .body(second, 4.0f, 5.0f, 6.0f)); - - PhysicsCommandBatch batch = buffer.freezeInternal(14L).publicBatch(); - - assertEquals(1, batch.commandCount()); - } - - @Test - void bulkSpawnRecorderRejectsMutationAfterRecipeReturns() { - SpaceId spaceId = new SpaceId(5); - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000009")); - PhysicsShapeSpec box = PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f); - RigidBodySpawnSettings settings = RigidBodySpawnSettings.defaults(); - AtomicReference captured = new AtomicReference<>(); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(45L, 10L); - - buffer.spawnBodies(1, spawns -> { - captured.set(spawns); - spawns.body(bodyKey, - spaceId, - box, - 1.0f, - PhysicsBodyType.DYNAMIC, - 1.0f, - 2.0f, - 3.0f, - settings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - }); - - assertThrows(IllegalStateException.class, - () -> captured.get().body(bodyKey, spawn -> spawn.space(spaceId).shape(box))); - assertEquals(1, buffer.freezeInternal(14L).publicBatch().commandCount()); - } - - @Test - void templatedBulkSpawnRecorderRejectsMutationAfterRecipeReturns() { - SpaceId spaceId = new SpaceId(5); - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000016")); - PhysicsShapeSpec box = PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f); - RigidBodySpawnSettings settings = RigidBodySpawnSettings.defaults(); - AtomicReference captured = new AtomicReference<>(); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(45L, 10L); - - buffer.spawnBodies(1, - spaceId, - box, - 1.0f, - PhysicsBodyType.DYNAMIC, - settings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, - spawns -> { - captured.set(spawns); - spawns.body(bodyKey, 1.0f, 2.0f, 3.0f); - }); - - assertThrows(IllegalStateException.class, - () -> captured.get().body(bodyKey, 4.0f, 5.0f, 6.0f)); - assertEquals(1, buffer.freezeInternal(14L).publicBatch().commandCount()); - } - - @Test - void fluentDslRecordsJointCommandsWithoutManualCommandConstruction() { - SpaceId spaceId = new SpaceId(6); - JointKey jointKey = JointKey.of(UUID.fromString("00000000-0000-0000-0000-000000000006")); - RigidBodyKey bodyA = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000007")); - RigidBodyKey bodyB = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000008")); - Vector3f anchorA = new Vector3f(0.0f, -0.5f, 0.0f); - Vector3f anchorB = new Vector3f(0.0f, 0.5f, 0.0f); - Vector3f axis = new Vector3f(0.0f, 0.0f, 1.0f); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(46L, 11L); - - buffer.joint(jointKey, joint -> joint - .space(spaceId) - .bodies(bodyA, bodyB) - .hinge(anchorA, anchorB, axis) - .limits(-0.75f, 0.75f) - .motor(1.25f, 2.5f)); - anchorA.set(9.0f, 9.0f, 9.0f); - anchorB.set(8.0f, 8.0f, 8.0f); - axis.set(7.0f, 7.0f, 7.0f); - - PhysicsCommandBatch batch = buffer.freezeInternal(15L).publicBatch(); - - assertEquals(1, batch.commandCount()); - } - - @Test - void bareJointRecorderRecordsWhenContextFreezes() { - SpaceId spaceId = new SpaceId(6); - JointKey jointKey = JointKey.of(UUID.fromString("00000000-0000-0000-0000-000000000024")); - RigidBodyKey bodyA = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000025")); - RigidBodyKey bodyB = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000026")); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(46L, 11L); - - buffer.joint(jointKey) - .space(spaceId) - .bodies(bodyA, bodyB) - .fixed(0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); - - PhysicsCommandBatch batch = buffer.freezeInternal(15L).publicBatch(); - - assertEquals(1, batch.commandCount()); - } - - @Test - void jointRecipeScopesRecorderToCallback() { - SpaceId spaceId = new SpaceId(6); - JointKey jointKey = JointKey.of(UUID.fromString("00000000-0000-0000-0000-000000000018")); - RigidBodyKey bodyA = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000019")); - RigidBodyKey bodyB = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000020")); - AtomicReference captured = new AtomicReference<>(); - MutablePhysicsCommandContext buffer = new MutablePhysicsCommandContext(46L, 11L); - - buffer.joint(jointKey, joint -> { - captured.set(joint); - joint.space(spaceId) - .bodies(bodyA, bodyB) - .fixed(0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); - }); - - assertThrows(IllegalStateException.class, () -> captured.get().motor(1.0f, 2.0f)); - assertEquals(1, buffer.freezeInternal(15L).publicBatch().commandCount()); - } - -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchQueryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchQueryTest.java deleted file mode 100644 index f29584e7..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchQueryTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import dev.hytalemodding.impulse.api.SpaceId; -import java.util.ArrayList; -import java.util.List; - -import dev.hytalemodding.impulse.core.plugin.simulation.query.RaycastClosestBatchQuery; -import org.junit.jupiter.api.Test; - -class RaycastClosestBatchQueryTest { - - @Test - void reusesImmutableSegmentsWhileFreezingTheInputList() { - RaycastSegment first = new RaycastSegment(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f); - RaycastSegment second = new RaycastSegment(7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f); - List source = new ArrayList<>(List.of(first, second)); - - RaycastClosestBatchQuery query = new RaycastClosestBatchQuery(new SpaceId(3), source); - source.clear(); - - assertEquals(2, query.rayCount()); - assertSame(first, query.ray(0)); - assertSame(second, query.ray(1)); - assertThrows(UnsupportedOperationException.class, - () -> query.rays().add(new RaycastSegment(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f))); - } -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java index 787a9578..e8f33bde 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java @@ -23,8 +23,6 @@ void formatsLatestContactEventSummary() { 56L, 78L, 90L, - 11L, - List.of(), List.of(), List.of(), List.of(new PhysicsContactEvent(new SpaceId(5), diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtilsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtilsTest.java index 1457a86b..e4b8c261 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtilsTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtilsTest.java @@ -14,13 +14,7 @@ import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyDynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyIdentityComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyKinematicTargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyMaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsBodyShapeComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import java.lang.reflect.Field; @@ -84,40 +78,10 @@ private void registerEntityModuleTypes() throws Exception { private void registerImpulsePluginTypes() throws Exception { ImpulsePlugin plugin = allocate(ImpulsePlugin.class); setField(plugin, - "physicsBodyAttachmentComponentType", - registry.registerComponent(PhysicsBodyAttachmentComponent.class, - "PhysicsBodyAttachment", - PhysicsBodyAttachmentComponent.CODEC)); - setField(plugin, - "physicsBodyIdentityComponentType", - registry.registerComponent(PhysicsBodyIdentityComponent.class, - "PhysicsBodyIdentity", - PhysicsBodyIdentityComponent.CODEC)); - setField(plugin, - "physicsBodyShapeComponentType", - registry.registerComponent(PhysicsBodyShapeComponent.class, - "PhysicsBodyShape", - PhysicsBodyShapeComponent.CODEC)); - setField(plugin, - "physicsBodyDynamicsComponentType", - registry.registerComponent(PhysicsBodyDynamicsComponent.class, - "PhysicsBodyDynamics", - PhysicsBodyDynamicsComponent.CODEC)); - setField(plugin, - "physicsBodyMaterialComponentType", - registry.registerComponent(PhysicsBodyMaterialComponent.class, - "PhysicsBodyMaterial", - PhysicsBodyMaterialComponent.CODEC)); - setField(plugin, - "physicsBodyCollisionComponentType", - registry.registerComponent(PhysicsBodyCollisionComponent.class, - "PhysicsBodyCollision", - PhysicsBodyCollisionComponent.CODEC)); - setField(plugin, - "physicsBodyKinematicTargetComponentType", - registry.registerComponent(PhysicsBodyKinematicTargetComponent.class, - "PhysicsBodyKinematicTarget", - PhysicsBodyKinematicTargetComponent.CODEC)); + "bodyAttachmentComponentType", + registry.registerComponent(BodyAttachmentComponent.class, + "BodyAttachment", + BodyAttachmentComponent.CODEC)); staticField(ImpulsePlugin.class, "instance").set(null, plugin); } diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java index bb0ccd59..84740410 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java @@ -23,8 +23,6 @@ void tracksContactEventsFromPublishedFrames() { 56L, 78L, 90L, - 11L, - List.of(), List.of(), List.of(), List.of(new PhysicsContactEvent(new SpaceId(5), From 74d97c6081ebd583728d1565798ad6ae0bcae8e6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 21:15:09 +0200 Subject: [PATCH 124/534] refactor(examples): use hytale targeting helpers Signed-off-by: Blovien --- .../PhysicsKinematicControlSystem.java | 22 +++------ .../impulse/examples/commands/EcsCommand.java | 14 +++--- .../commands/ExamplePhysicsUtils.java | 45 +++---------------- .../examples/commands/GrabCommand.java | 6 ++- .../examples/commands/RaycastCommand.java | 6 ++- 5 files changed, 28 insertions(+), 65 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index c71ee559..1f601b32 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -36,7 +36,6 @@ import java.util.WeakHashMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.joml.Quaterniond; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; @@ -106,14 +105,11 @@ public void tick(float dt, eye.set(transform.getPosition()); eye.y += eyeHeight(chunk, index, chunk.getReferenceTo(index), store); - Vector3d direction = lookDirection(chunk, index, transform, local.direction); + Rotation3f viewRotation = rotation(chunk, index, transform); + Vector3d direction = lookDirection(viewRotation, local.direction); Vector3f viewOffset = session.getViewOffset(); - local.right.set(Vector3dUtil.RIGHT); - local.up.set(Vector3dUtil.UP); - local.rotation.identity(); - rotation(chunk, index, transform).getQuaternion(local.rotation); - local.rotation.transform(local.right); - local.rotation.transform(local.up); + viewRotation.transform(Vector3dUtil.RIGHT, local.right); + viewRotation.transform(Vector3dUtil.UP, local.up); local.target.set( (float) (eye.x + direction.x * session.getGrabDistance() @@ -215,14 +211,9 @@ private float eyeHeight(@Nonnull ArchetypeChunk chunk, } @Nonnull - private Vector3d lookDirection(@Nonnull ArchetypeChunk chunk, - int index, - @Nonnull TransformComponent transform, + private Vector3d lookDirection(@Nonnull Rotation3f rotation, @Nonnull Vector3d out) { - Rotation3f rotation = rotation(chunk, index, transform); - Quaterniond quaternion = rotation.getQuaternion(new Quaterniond()); - out.set(Vector3dUtil.FORWARD); - quaternion.transform(out); + rotation.transform(Vector3dUtil.FORWARD, out); if (out.lengthSquared() == 0.0) { out.set(Vector3dUtil.FORWARD); } else { @@ -343,7 +334,6 @@ private static final class Scratch { private final Vector3d direction = new Vector3d(); private final Vector3d right = new Vector3d(); private final Vector3d up = new Vector3d(); - private final Quaterniond rotation = new Quaterniond(); private final Vector3f target = new Vector3f(); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java index 4317dfe8..f10564d0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; import com.hypixel.hytale.server.core.command.system.CommandContext; @@ -177,7 +178,8 @@ private void applyImpulse(@Nonnull CommandContext ctx, return; } int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); - Vector3d impulse = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(strength); + Vector3d impulse = new Vector3d(ExamplePhysicsUtils.lookTransform(store, ref) + .getDirection()).mul(strength); boolean applied = ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(world, hit.bodyKey().value(), BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, @@ -498,8 +500,9 @@ private static Vector3d spawnPosition(@Nonnull Store store, @Nonnull Ref ref, @Nonnull TransformComponent transform, @Nonnull World world) { - Vector3d eye = ExamplePhysicsUtils.eyePosition(store, ref, transform); - Vector3d direction = ExamplePhysicsUtils.lookDirection(store, ref, transform); + Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Vector3d eye = new Vector3d(look.getPosition()); + Vector3d direction = new Vector3d(look.getDirection()); Vector3i target = TargetUtil.getTargetBlock(world, ExplosiveBlockPolicy::isSimpleFullCubeFragmentBlock, eye.x, @@ -544,9 +547,10 @@ private static CompletionStage raycastAsync(@Nonnull CommandCont return CompletableFuture.completedFuture(null); } - Vector3d start = ExamplePhysicsUtils.eyePosition(store, ref, transform); + Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Vector3d start = new Vector3d(look.getPosition()); Vector3d end = new Vector3d(start) - .add(ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(RAY_LENGTH)); + .add(new Vector3d(look.getDirection()).mul(RAY_LENGTH)); return PhysicsStoreRaycasts.closestAsync(store.getExternalData().getWorld(), spaceId, vector(start), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 018fcd76..0e8eea2b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -5,19 +5,16 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.math.vector.Rotation3f; -import com.hypixel.hytale.math.vector.Vector3dUtil; +import com.hypixel.hytale.math.vector.Transform; import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.asset.type.model.config.Model; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; -import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; -import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; @@ -53,7 +50,6 @@ import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.joml.Quaterniond; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; @@ -66,8 +62,6 @@ public final class ExamplePhysicsUtils { TransformComponent.getComponentType(); private static final ComponentType ATTACHMENT_TYPE = BodyAttachmentComponent.getComponentType(); - private static final ComponentType MODEL_TYPE = ModelComponent.getComponentType(); - private static final ComponentType HEAD_ROTATION_TYPE = HeadRotation.getComponentType(); private ExamplePhysicsUtils() { } @@ -732,38 +726,9 @@ public static String resolveBlockType(@Nullable String blockType) { } @Nonnull - static Vector3d eyePosition(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull TransformComponent transform) { - return new Vector3d(transform.getPosition()).add(0.0, eyeHeight(store, ref), 0.0); - } - - static double eyeHeight(@Nonnull Store store, @Nonnull Ref ref) { - ModelComponent modelComponent = store.getComponent(ref, MODEL_TYPE); - if (modelComponent == null) { - return 1.6; - } - - Model model = modelComponent.getModel(); - if (model == null) { - return 1.6; - } - return model.getEyeHeight(ref, store); - } - - @Nonnull - public static Vector3d lookDirection(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull TransformComponent transform) { - HeadRotation headRotation = store.getComponent(ref, HEAD_ROTATION_TYPE); - Rotation3f rotation = headRotation != null ? headRotation.getRotation() : transform.getRotation(); - Quaterniond quaternion = rotation.getQuaternion(new Quaterniond()); - Vector3d direction = new Vector3d(Vector3dUtil.FORWARD); - quaternion.transform(direction); - if (direction.lengthSquared() == 0.0) { - return new Vector3d(Vector3dUtil.FORWARD); - } - return direction.normalize(); + static Transform lookTransform(@Nonnull Store store, + @Nonnull Ref ref) { + return TargetUtil.getLook(ref, store); } public static int optionalInt(@Nonnull CommandContext ctx, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 9c667c4a..2bf7f906 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; @@ -102,8 +103,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - Vector3d start = ExamplePhysicsUtils.eyePosition(store, ref, transform); - Vector3d direction = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(RAY_LENGTH); + Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Vector3d start = new Vector3d(look.getPosition()); + Vector3d direction = new Vector3d(look.getDirection()).mul(RAY_LENGTH); Vector3d end = new Vector3d(start).add(direction); return PhysicsStoreAsync.acceptOnWorldThread(world, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index 9bf241fb..2946a629 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; @@ -55,8 +56,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - Vector3d start = ExamplePhysicsUtils.eyePosition(store, ref, transform); - Vector3d direction = ExamplePhysicsUtils.lookDirection(store, ref, transform).mul(RAY_LENGTH); + Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Vector3d start = new Vector3d(look.getPosition()); + Vector3d direction = new Vector3d(look.getDirection()).mul(RAY_LENGTH); Vector3d end = new Vector3d(start).add(direction); DebugUtils.addArrow(world, start, direction, DebugUtils.COLOR_WHITE, 0.8f, 4.0f, From 3501466eda2637f0b5eaad20f8ab5912c91ecbc8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 14 Jun 2026 21:40:22 +0200 Subject: [PATCH 125/534] refactor(examples): flatten physics store commands Signed-off-by: Blovien --- .../examples/commands/DropCommand.java | 19 +- .../commands/ExampleBlockEntityVisuals.java | 4 +- .../commands/ExamplePhysicsUtils.java | 126 ------------ .../examples/commands/ForcesCommand.java | 7 +- .../examples/commands/GrabCommand.java | 14 +- .../examples/commands/ImpulseCommand.java | 5 +- .../examples/commands/JointsCommand.java | 7 +- .../examples/commands/MaterialsCommand.java | 17 +- ....java => PhysicsStoreExampleCommands.java} | 190 +++--------------- .../examples/commands/RaycastCommand.java | 15 +- .../examples/commands/ShapesCommand.java | 23 +-- .../commands/WorldCollisionCommand.java | 70 ++++++- .../stress/StressBenchmarkCommand.java | 11 +- .../commands/stress/StressBodiesCommand.java | 15 +- .../commands/stress/StressJointsCommand.java | 10 +- .../stress/StressRawBodiesCommand.java | 7 +- .../commands/stress/StressRaycastCommand.java | 7 +- .../commands/stress/StressShapesCommand.java | 8 +- .../explosive/ExplosiveBlockRuntime.java | 8 +- 19 files changed, 149 insertions(+), 414 deletions(-) rename impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/{EcsCommand.java => PhysicsStoreExampleCommands.java} (69%) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index 11ba9bbc..e7486e64 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.examples.commands; -import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -8,7 +7,6 @@ import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; @@ -26,8 +24,6 @@ */ public class DropCommand extends AbstractAsyncPlayerCommand { - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); private final OptionalArg blockTypeArg = this.withOptionalArg( "blockType", "Hytale block type used for the attached visual entity", @@ -48,14 +44,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - TransformComponent playerTransform = store.getComponent(ref, TRANSFORM_TYPE); - - if (playerTransform == null) { - ctx.sender().sendMessage(Message.raw("Cannot determine player position.")); - return CompletableFuture.completedFuture(null); - } - - Vector3d playerPos = playerTransform.getPosition(); + Vector3d playerPos = playerRef.getTransform().getPosition(); float spawnX = (float) playerPos.x(); float spawnY = (float) playerPos.y() + 5f; float spawnZ = (float) playerPos.z(); @@ -69,13 +58,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.spawnBlockBody(store, time, - resource, spaceId, new Vector3d(spawnX, spawnY, spawnZ), blockType(ctx), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, - RigidBodySpawnSettings.material(0.5f, 0.5f)); + RigidBodySpawnSettings.material(0.5f, 0.5f), + null); ctx.sender() .sendMessage(Message.raw("Dropped box at " + spawnX + ", " + spawnY + ", " + spawnZ)); @@ -86,7 +75,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull private String blockType(@Nonnull CommandContext ctx) { return blockTypeArg.provided(ctx) - ? ExamplePhysicsUtils.resolveBlockType(blockTypeArg.get(ctx)) + ? ExampleBlockEntityVisuals.resolveBlockType(blockTypeArg.get(ctx)) : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisuals.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisuals.java index 637fc7c3..feb7f30e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisuals.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisuals.java @@ -12,7 +12,7 @@ import javax.annotation.Nullable; import org.joml.Vector3d; -final class ExampleBlockEntityVisuals { +public final class ExampleBlockEntityVisuals { private ExampleBlockEntityVisuals() { } @@ -38,7 +38,7 @@ static void stripHytaleRuntimeComponents(@Nonnull Holder holder, } @Nonnull - static String resolveBlockType(@Nullable String blockType) { + public static String resolveBlockType(@Nullable String blockType) { return blockType == null || blockType.isBlank() ? PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE : blockType.trim(); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 0e8eea2b..06963b19 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -5,16 +5,13 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.math.vector.Transform; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; @@ -23,9 +20,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; @@ -58,31 +52,12 @@ public final class ExamplePhysicsUtils { public static final String DEFAULT_BLOCK_TYPE = PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); private static final ComponentType ATTACHMENT_TYPE = BodyAttachmentComponent.getComponentType(); private ExamplePhysicsUtils() { } - @Nullable - public static Vector3d playerPosition(@Nonnull CommandContext ctx, - @Nonnull Store store, - @Nonnull Ref ref) { - TransformComponent playerTransform = store.getComponent(ref, TRANSFORM_TYPE); - if (playerTransform == null) { - ctx.sender().sendMessage(Message.raw("Cannot determine player position.")); - return null; - } - return new Vector3d(playerTransform.getPosition()); - } - - @Nonnull - public static PhysicsWorldResource resource(@Nonnull Store store) { - return store.getResource(PhysicsWorldResource.getResourceType()); - } - @Nonnull public static Store physicsStore(@Nonnull World world) { return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() @@ -99,49 +74,6 @@ public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); } - @Nonnull - public static WorldCollisionBuildStats rebuildPhysicsStoreWorldCollisionAround( - @Nonnull Store store, - @Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - return resource(store).rebuildWorldCollisionAround(world, spaceId, center, radius); - } - - @Nonnull - public static WorldCollisionBuildStats refreshPhysicsStoreWorldCollisionAround( - @Nonnull Store store, - @Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - return resource(store).refreshWorldCollisionAround(world, spaceId, center, radius); - } - - @Nonnull - public static WorldCollisionPrewarmStats ensurePhysicsStoreWorldCollisionAround( - @Nonnull Store store, - @Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Iterable centers, - int radius, - long tick) { - return resource(store).ensureWorldCollisionAround(world, spaceId, centers, radius, tick); - } - - public static int clearPhysicsStoreWorldCollision(@Nonnull Store store, - @Nonnull World world, - @Nonnull SpaceId spaceId) { - return resource(store).clearWorldCollision(spaceId); - } - - @Nonnull - public static WorldCollisionStats physicsStoreWorldCollisionStats( - @Nonnull Store store) { - return resource(store).getWorldCollisionStats(); - } - @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyRowDescriptor row) { @@ -282,42 +214,6 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, @Nonnull public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings) { - return spawnBlockBody(store, time, resource, spaceId, visualPosition, DEFAULT_BLOCK_TYPE, - shape, mass, settings); - } - - @Nonnull - public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings) { - return spawnBlockBody(store, - time, - resource, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - null); - } - - @Nonnull - public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId spaceId, @Nonnull Vector3d visualPosition, @Nullable String blockType, @@ -528,7 +424,6 @@ public static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, long serverTick, @Nonnull SpaceId spaceId, int expectedBodies, @@ -539,7 +434,6 @@ public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store st @Nonnull Consumer builder) { return spawnBlockBodiesInternal(store, time, - resource, serverTick, spaceId, expectedBodies, @@ -554,7 +448,6 @@ public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store st @Nonnull public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, long serverTick, @Nonnull SpaceId spaceId, int expectedBodies, @@ -565,7 +458,6 @@ public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store builder) { return spawnBlockBodiesInternal(store, time, - resource, serverTick, spaceId, expectedBodies, @@ -580,7 +472,6 @@ public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, long serverTick, @Nonnull SpaceId spaceId, int expectedBodies, @@ -667,12 +558,6 @@ public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store spawnAttachedPhysicsStoreBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, @@ -720,17 +605,6 @@ private static Holder blockEntityHolder(@Nonnull TimeResource time, return ExampleBlockEntityVisuals.impulseOwnedBlockVisual(time, blockType, visualPosition); } - @Nonnull - public static String resolveBlockType(@Nullable String blockType) { - return ExampleBlockEntityVisuals.resolveBlockType(blockType); - } - - @Nonnull - static Transform lookTransform(@Nonnull Store store, - @Nonnull Ref ref) { - return TargetUtil.getLook(ref, store); - } - public static int optionalInt(@Nonnull CommandContext ctx, @Nonnull OptionalArg arg, int defaultValue, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index a097e897..57e55304 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -44,12 +44,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 2bf7f906..8c66e085 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -9,11 +9,11 @@ import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; @@ -63,8 +63,6 @@ public class GrabCommand extends AbstractAsyncPlayerCommand { private static final double RAY_LENGTH = 24.0; private static final float MIN_HOLD_DISTANCE = 4.0f; private static final Vector3f VIEW_OFFSET = new Vector3f(0.85f, -0.35f, 0.0f); - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); private static final ComponentType ATTACHMENT_TYPE = BodyAttachmentComponent.getComponentType(); private final OptionalArg spaceArg = this.withOptionalArg( @@ -83,12 +81,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - TransformComponent transform = store.getComponent(ref, TRANSFORM_TYPE); - if (transform == null) { - ctx.sender().sendMessage(Message.raw("Cannot determine player position.")); - return CompletableFuture.completedFuture(null); - } - if (!PhysicsControlSessions.isAvailable()) { ctx.sender().sendMessage(Message.raw( "Impulse control is disabled. Enable HytaleModding:ImpulseControl to use grab.")); @@ -97,13 +89,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ComponentType controllableType = ImpulseControllableComponent.getComponentType(); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId targetSpaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (targetSpaceId == null) { return CompletableFuture.completedFuture(null); } - Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); Vector3d direction = new Vector3d(look.getDirection()).mul(RAY_LENGTH); Vector3d end = new Vector3d(start).add(direction); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java index 7e2605e5..797d175f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java @@ -7,13 +7,16 @@ public class ImpulseCommand extends AbstractCommandCollection { public ImpulseCommand() { super("impulse-examples", "Impulse example and stress test commands"); - addSubCommand(new EcsCommand()); addSubCommand(new DropCommand()); addSubCommand(new ShapesCommand()); addSubCommand(new MaterialsCommand()); addSubCommand(new ForcesCommand()); addSubCommand(new JointsCommand()); addSubCommand(new RaycastCommand()); + addSubCommand(new PhysicsStoreExampleCommands.BumperCommand()); + addSubCommand(new PhysicsStoreExampleCommands.PlatformCommand()); + addSubCommand(new PhysicsStoreExampleCommands.PickupCommand()); + addSubCommand(new PhysicsStoreExampleCommands.ExplosiveCommand()); addSubCommand(new EventsCommand()); addSubCommand(new GrabCommand()); addSubCommand(new ReleaseCommand()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index 4af8815b..c6d062bd 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -50,12 +50,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 0b9dc8f9..e8007dc6 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -38,12 +38,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); @@ -51,12 +48,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-3.0, 5.0, 4.0); - spawnSphere(store, time, resource, spaceId, new Vector3d(origin), 0.05f, 0.9f, 3.0f); - spawnSphere(store, time, resource, spaceId, new Vector3d(origin).add(2.0, 0.0, 0.0), + spawnSphere(store, time, spaceId, new Vector3d(origin), 0.05f, 0.9f, 3.0f); + spawnSphere(store, time, spaceId, new Vector3d(origin).add(2.0, 0.0, 0.0), 0.95f, 0.9f, 3.0f); - spawnSphere(store, time, resource, spaceId, new Vector3d(origin).add(4.0, 0.0, 0.0), + spawnSphere(store, time, spaceId, new Vector3d(origin).add(4.0, 0.0, 0.0), 0.5f, 0.0f, 2.0f); - spawnSphere(store, time, resource, spaceId, new Vector3d(origin).add(6.0, 0.0, 0.0), + spawnSphere(store, time, spaceId, new Vector3d(origin).add(6.0, 0.0, 0.0), 0.5f, 0.95f, 2.0f); ctx.sender().sendMessage(Message.raw( @@ -66,7 +63,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawnSphere(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float restitution, @@ -74,7 +70,6 @@ private static void spawnSphere(@Nonnull Store store, float speed) { ExamplePhysicsUtils.spawnBlockBody(store, time, - resource, spaceId, position, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java similarity index 69% rename from impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java rename to impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index f10564d0..486542f4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/EcsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -11,8 +11,6 @@ import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; @@ -46,31 +44,21 @@ import org.joml.Vector3f; import org.joml.Vector3i; -/** - * ECS-first examples for durable rigid body definitions. - */ -public class EcsCommand extends AbstractCommandCollection { +final class PhysicsStoreExampleCommands { private static final double RAY_LENGTH = 24.0; - public EcsCommand() { - super("ecs", "ECS rigid-body examples"); - addSubCommand(new DropCommand()); - addSubCommand(new BumperCommand()); - addSubCommand(new PlatformCommand()); - addSubCommand(new PickupCommand()); - addSubCommand(new WorldCollisionCommand()); - addSubCommand(new ExplosiveCommand()); + private PhysicsStoreExampleCommands() { } - private abstract static class EcsPlayerCommand extends AbstractAsyncPlayerCommand { + abstract static class PhysicsStorePlayerCommand extends AbstractAsyncPlayerCommand { protected final OptionalArg spaceArg = withOptionalArg( "space", "Physics space id to target", ArgTypes.INTEGER); - protected EcsPlayerCommand(@Nonnull String name, @Nonnull String description) { + protected PhysicsStorePlayerCommand(@Nonnull String name, @Nonnull String description) { super(name, description); } @@ -78,71 +66,19 @@ protected EcsPlayerCommand(@Nonnull String name, @Nonnull String description) { protected SpaceId resolveSpace(@Nonnull CommandContext ctx, @Nonnull Store store) { return ExamplePhysicsUtils.spaceId(ctx, - ExamplePhysicsUtils.resource(store), + store.getResource(PhysicsWorldResource.getResourceType()), spaceArg); } } - private static final class DropCommand extends EcsPlayerCommand { - - private final OptionalArg blockTypeArg = withOptionalArg( - "blockType", - "Hytale block type used for the attached visual entity", - ArgTypes.STRING); - - private DropCommand() { - super("drop", "Spawn a dynamic physics body from split ECS components"); - } - - @Nonnull - @Override - protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, - @Nonnull Store store, - @Nonnull Ref ref, - @Nonnull PlayerRef playerRef, - @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } - SpaceId spaceId = resolveSpace(ctx, store); - if (spaceId == null) { - return CompletableFuture.completedFuture(null); - } - - Vector3d spawn = new Vector3d(playerPos).add(0.0, 5.0, 0.0); - TimeResource time = store.getResource(TimeResource.getResourceType()); - ExamplePhysicsUtils.SpawnedBlockBody body = ExamplePhysicsUtils.spawnBlockBody(store, - time, - ExamplePhysicsUtils.resource(store), - spaceId, - spawn, - blockType(ctx), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - RigidBodySpawnSettings.material(0.5f, 0.2f)); - - ctx.sender().sendMessage(Message.raw("Queued ECS-authored physics body " + body.bodyKey() - + " in space " + spaceId.value() + ".")); - return CompletableFuture.completedFuture(null); - } - - @Nonnull - private String blockType(@Nonnull CommandContext ctx) { - return blockTypeArg.provided(ctx) - ? ExamplePhysicsUtils.resolveBlockType(blockTypeArg.get(ctx)) - : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; - } - } - - private static final class BumperCommand extends EcsPlayerCommand { + static final class BumperCommand extends PhysicsStorePlayerCommand { private final OptionalArg strengthArg = withOptionalArg( "strength", "Impulse strength", ArgTypes.INTEGER); - private BumperCommand() { + BumperCommand() { super("bumper", "Apply an impulse to the rigid body in the player view"); } @@ -158,7 +94,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } return PhysicsStoreAsync.acceptOnWorldThread(world, - raycastAsync(ctx, store, ref, spaceId), + raycastAsync(store, ref, spaceId), hit -> applyImpulse(ctx, store, ref, world, hit)); } @@ -172,13 +108,8 @@ private void applyImpulse(@Nonnull CommandContext ctx, return; } - TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType()); - if (transform == null) { - ctx.sender().sendMessage(Message.raw("Cannot determine player direction.")); - return; - } int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); - Vector3d impulse = new Vector3d(ExamplePhysicsUtils.lookTransform(store, ref) + Vector3d impulse = new Vector3d(TargetUtil.getLook(ref, store) .getDirection()).mul(strength); boolean applied = ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(world, hit.bodyKey().value(), @@ -196,15 +127,15 @@ private void applyImpulse(@Nonnull CommandContext ctx, return; } - ctx.sender().sendMessage(Message.raw("Queued ECS impulse command for " + ctx.sender().sendMessage(Message.raw("Queued PhysicsStore impulse command for " + hit.bodyKey() + ".")); } } - private static final class PlatformCommand extends EcsPlayerCommand { + static final class PlatformCommand extends PhysicsStorePlayerCommand { - private PlatformCommand() { - super("platform", "Spawn a kinematic rigid body target from ECS data"); + PlatformCommand() { + super("platform", "Spawn a kinematic PhysicsStore body target"); } @Nonnull @@ -214,10 +145,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); SpaceId spaceId = resolveSpace(ctx, store); if (spaceId == null) { return CompletableFuture.completedFuture(null); @@ -259,15 +187,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, (float) spawn.z, false)); - ctx.sender().sendMessage(Message.raw("Queued ECS kinematic platform " + bodyKey + ctx.sender().sendMessage(Message.raw("Queued PhysicsStore kinematic platform " + bodyKey + " in space " + spaceId.value() + ".")); return CompletableFuture.completedFuture(null); } } - private static final class PickupCommand extends EcsPlayerCommand { + static final class PickupCommand extends PhysicsStorePlayerCommand { - private PickupCommand() { + PickupCommand() { super("pickup", "Attach a view entity to the physics body in view"); } @@ -283,7 +211,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } return PhysicsStoreAsync.acceptOnWorldThread(world, - raycastAsync(ctx, store, ref, spaceId), + raycastAsync(store, ref, spaceId), hit -> attachView(ctx, store, hit)); } @@ -303,58 +231,12 @@ private static void attachView(@Nonnull CommandContext ctx, point, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE); - ctx.sender().sendMessage(Message.raw("Attached view-only ECS entity to " + ctx.sender().sendMessage(Message.raw("Attached view-only entity to " + hit.bodyKey() + ".")); } } - private static final class WorldCollisionCommand extends EcsPlayerCommand { - - private final OptionalArg radiusArg = withOptionalArg( - "radius", - "Block radius around the player to scan", - ArgTypes.INTEGER); - - private WorldCollisionCommand() { - super("world-collision", "Ensure voxel collision around the player for an explicit space"); - } - - @Nonnull - @Override - protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, - @Nonnull Store store, - @Nonnull Ref ref, - @Nonnull PlayerRef playerRef, - @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } - SpaceId spaceId = resolveSpace(ctx, store); - if (spaceId == null) { - return CompletableFuture.completedFuture(null); - } - - int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, 8, 1, 24); - WorldCollisionPrewarmStats stats = ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, - world, - spaceId, - List.of(playerPos), - radius, - 0L); - - ctx.sender().sendMessage(Message.raw("Ensured ECS world collision: targets " - + stats.sectionTargets() - + ", bodies " - + stats.buildStats().colliderBodies() - + ", removed " - + stats.buildStats().removedBodies() - + ".")); - return CompletableFuture.completedFuture(null); - } - } - - private static final class ExplosiveCommand extends EcsPlayerCommand { + static final class ExplosiveCommand extends PhysicsStorePlayerCommand { private static final double FALLBACK_SPAWN_DISTANCE = 5.0; private static final int DEFAULT_EXPLOSION_RADIUS = 8; @@ -385,8 +267,8 @@ private static final class ExplosiveCommand extends EcsPlayerCommand { "Upward lift fraction applied to spawned fragments", ArgTypes.FLOAT); - private ExplosiveCommand() { - super("explosive", "Drop an ECS explosive block that fragments terrain on impact"); + ExplosiveCommand() { + super("explosive", "Drop an explosive block that fragments terrain on impact"); } @Nonnull @@ -396,18 +278,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType()); - if (transform == null) { - ctx.sender().sendMessage(Message.raw("Cannot determine player position.")); - return CompletableFuture.completedFuture(null); - } SpaceId spaceId = resolveSpace(ctx, store); if (spaceId == null) { return CompletableFuture.completedFuture(null); } String blockType = blockTypeArg.provided(ctx) - ? ExamplePhysicsUtils.resolveBlockType(blockTypeArg.get(ctx)) + ? ExampleBlockEntityVisuals.resolveBlockType(blockTypeArg.get(ctx)) : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, @@ -422,12 +299,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, float strength = optionalFloat(ctx, strengthArg, 12.0f, 0.0f, 128.0f); float verticalLift = optionalFloat(ctx, verticalLiftArg, 0.35f, 0.0f, 2.0f); - Vector3d spawn = spawnPosition(store, ref, transform, world); + Vector3d spawn = spawnPosition(store, ref, world); if (blockType(blockType) == null) { ctx.sender().sendMessage(Message.raw("No valid explosive block type is available.")); return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); boolean contactEventsEnabled = contactEventsEnabled(resource); UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); if (spaceUuid == null) { @@ -435,8 +312,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - WorldCollisionPrewarmStats stats = ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, - world, + WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, spaceId, List.of(spawn), Math.max(8, radius + 6), @@ -498,9 +374,8 @@ private static boolean contactEventsEnabled(@Nonnull PhysicsWorldResource resour @Nonnull private static Vector3d spawnPosition(@Nonnull Store store, @Nonnull Ref ref, - @Nonnull TransformComponent transform, @Nonnull World world) { - Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Transform look = TargetUtil.getLook(ref, store); Vector3d eye = new Vector3d(look.getPosition()); Vector3d direction = new Vector3d(look.getDirection()); Vector3i target = TargetUtil.getTargetBlock(world, @@ -537,17 +412,10 @@ private static float optionalFloat(@Nonnull CommandContext ctx, } @Nonnull - private static CompletionStage raycastAsync(@Nonnull CommandContext ctx, - @Nonnull Store store, + private static CompletionStage raycastAsync(@Nonnull Store store, @Nonnull Ref ref, @Nonnull SpaceId spaceId) { - TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType()); - if (transform == null) { - ctx.sender().sendMessage(Message.raw("Cannot determine player position.")); - return CompletableFuture.completedFuture(null); - } - - Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); Vector3d end = new Vector3d(start) .add(new Vector3d(look.getDirection()).mul(RAY_LENGTH)); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index 2946a629..b93def52 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.examples.commands; -import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.vector.Transform; @@ -10,10 +9,10 @@ import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; import com.hypixel.hytale.server.core.modules.debug.DebugUtils; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; @@ -26,8 +25,6 @@ public class RaycastCommand extends AbstractAsyncPlayerCommand { private static final double RAY_LENGTH = 24.0; - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); private final OptionalArg spaceArg = this.withOptionalArg( "space", "Physics space id to target", @@ -44,19 +41,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - TransformComponent transform = store.getComponent(ref, TRANSFORM_TYPE); - if (transform == null) { - ctx.sender().sendMessage(Message.raw("Cannot determine player position.")); - return CompletableFuture.completedFuture(null); - } - - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Transform look = ExamplePhysicsUtils.lookTransform(store, ref); + Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); Vector3d direction = new Vector3d(look.getDirection()).mul(RAY_LENGTH); Vector3d end = new Vector3d(start).add(direction); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index fba299b0..898312ba 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -38,12 +38,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); @@ -51,12 +48,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-4.0, 3.0, 3.0); - spawn(store, time, resource, spaceId, ShapeType.BOX, PhysicsAxis.Y, + spawn(store, time, spaceId, ShapeType.BOX, PhysicsAxis.Y, origin, 0); - spawn(store, time, resource, spaceId, ShapeType.SPHERE, PhysicsAxis.Y, origin, 2); - spawn(store, time, resource, spaceId, ShapeType.CAPSULE, PhysicsAxis.Y, origin, 4); - spawn(store, time, resource, spaceId, ShapeType.CYLINDER, PhysicsAxis.Y, origin, 6); - spawn(store, time, resource, spaceId, ShapeType.CONE, PhysicsAxis.Y, origin, 8); + spawn(store, time, spaceId, ShapeType.SPHERE, PhysicsAxis.Y, origin, 2); + spawn(store, time, spaceId, ShapeType.CAPSULE, PhysicsAxis.Y, origin, 4); + spawn(store, time, spaceId, ShapeType.CYLINDER, PhysicsAxis.Y, origin, 6); + spawn(store, time, spaceId, ShapeType.CONE, PhysicsAxis.Y, origin, 8); ctx.sender().sendMessage(Message.raw("Spawned shape demo.")); return CompletableFuture.completedFuture(null); @@ -64,7 +61,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawn(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId spaceId, @Nonnull ShapeType type, @Nonnull PhysicsAxis axis, @@ -72,12 +68,13 @@ private static void spawn(@Nonnull Store store, int xOffset) { ExamplePhysicsUtils.spawnBlockBody(store, time, - resource, spaceId, new Vector3d(origin).add(xOffset, 0.0, 0.0), + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, shape(type, axis), 1.0f, - RigidBodySpawnSettings.material(0.7f, 0.35f)); + RigidBodySpawnSettings.material(0.7f, 0.35f), + null); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java index 4c58b09d..8f715b12 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java @@ -14,7 +14,9 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; +import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -27,6 +29,7 @@ public class WorldCollisionCommand extends AbstractCommandCollection { public WorldCollisionCommand() { super("world-collision", "Build static Impulse voxel collision from nearby world blocks"); addSubCommand(new BuildCommand()); + addSubCommand(new EnsureCommand()); addSubCommand(new ClearCommand()); addSubCommand(new StatsCommand()); } @@ -56,19 +59,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, DEFAULT_RADIUS, 1, MAX_RADIUS); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } - WorldCollisionBuildStats stats = ExamplePhysicsUtils.rebuildPhysicsStoreWorldCollisionAround(store, - world, + WorldCollisionBuildStats stats = resource.rebuildWorldCollisionAround(world, spaceId, playerPos, radius); @@ -89,6 +88,56 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } } + private static final class EnsureCommand extends AbstractAsyncPlayerCommand { + + private static final int DEFAULT_RADIUS = 8; + private static final int MAX_RADIUS = 24; + + private final OptionalArg radiusArg = withOptionalArg( + "radius", + "Block radius around the player to scan", + ArgTypes.INTEGER); + private final OptionalArg spaceArg = withOptionalArg( + "space", + "Physics space id to target", + ArgTypes.INTEGER); + + private EnsureCommand() { + super("ensure", "Ensure nearby static voxel collision is available"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world) { + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); + int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, DEFAULT_RADIUS, 1, MAX_RADIUS); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + if (spaceId == null) { + return CompletableFuture.completedFuture(null); + } + + WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, + spaceId, + List.of(playerPos), + radius, + Math.max(0L, world.getTick())); + + ctx.sender().sendMessage(Message.raw("Ensured world voxel collision: targets " + + stats.sectionTargets() + + ", bodies " + + stats.buildStats().colliderBodies() + + ", removed " + + stats.buildStats().removedBodies() + + ".")); + return CompletableFuture.completedFuture(null); + } + } + private static final class ClearCommand extends AbstractAsyncPlayerCommand { private final OptionalArg spaceArg = withOptionalArg( @@ -107,12 +156,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } - int removed = ExamplePhysicsUtils.clearPhysicsStoreWorldCollision(store, world, spaceId); + int removed = resource.clearWorldCollision(spaceId); ctx.sender().sendMessage(Message.raw("Removed " + removed + " world voxel collision bodies.")); return CompletableFuture.completedFuture(null); @@ -132,7 +181,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - WorldCollisionStats stats = ExamplePhysicsUtils.physicsStoreWorldCollisionStats(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + WorldCollisionStats stats = resource.getWorldCollisionStats(); ctx.sender().sendMessage(Message.raw("World voxel collision: " + stats.spaces() + " spaces, " + stats.sections() + " sections, " diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index d0c20869..38fd1e5e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -69,12 +70,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); @@ -196,7 +194,6 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); ExamplePhysicsUtils.BlockBodyBatchTiming timing = ExamplePhysicsUtils.spawnBlockBodiesMeasured(store, time, - resource, serverTick, spaceId, count, @@ -220,7 +217,7 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st @Nonnull private String blockType(@Nonnull CommandContext ctx) { return blockTypeArg.provided(ctx) - ? ExamplePhysicsUtils.resolveBlockType(blockTypeArg.get(ctx)) + ? ExampleBlockEntityVisuals.resolveBlockType(blockTypeArg.get(ctx)) : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index f34ec516..5bfb1515 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -29,6 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; import java.util.Iterator; import java.util.Locale; @@ -117,10 +118,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); StressMode mode = parseMode(ctx); if (mode == null) { @@ -144,7 +142,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (visualSettings == null) { return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); @@ -175,7 +173,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); ExamplePhysicsUtils.BlockBodyBatchTiming batchTiming = ExamplePhysicsUtils.spawnBlockBodiesMeasured(store, time, - resource, serverTick, spaceId, count, @@ -329,8 +326,8 @@ private static int prewarmStressWorldCollision(@Nonnull Store store return 0; } - WorldCollisionPrewarmStats stats = ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, - world, + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, spaceId, layout.positions(count), worldCollisionSettings.getWorldCollisionBodyRadius(), @@ -467,7 +464,7 @@ private StressVisualSettings parseVisualSettings(@Nonnull CommandContext ctx) { @Nonnull private String blockType(@Nonnull CommandContext ctx) { return blockTypeArg.provided(ctx) - ? ExamplePhysicsUtils.resolveBlockType(blockTypeArg.get(ctx)) + ? ExampleBlockEntityVisuals.resolveBlockType(blockTypeArg.get(ctx)) : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index e38aa636..4ec96297 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; import java.util.ArrayList; @@ -69,15 +70,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int totalJoints = ExamplePhysicsUtils.optionalInt(ctx, countArg, DEFAULT_JOINTS, 1, MAX_JOINTS); String blockType = blockType(ctx); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); @@ -189,7 +187,7 @@ private static int appendRow(@Nonnull List pendingBodies, @Nonnull private String blockType(@Nonnull CommandContext ctx) { return blockTypeArg.provided(ctx) - ? ExamplePhysicsUtils.resolveBlockType(blockTypeArg.get(ctx)) + ? ExampleBlockEntityVisuals.resolveBlockType(blockTypeArg.get(ctx)) : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index bb472f7d..a11e89f3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -54,13 +54,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int count = ExamplePhysicsUtils.optionalInt(ctx, countArg, DEFAULT_COUNT, 1, MAX_COUNT); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index ad5389a8..da511e12 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -50,13 +50,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int rays = ExamplePhysicsUtils.optionalInt(ctx, raysArg, DEFAULT_RAYS, 1, MAX_RAYS); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index b1f5acde..23605383 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -51,13 +51,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Vector3d playerPos = ExamplePhysicsUtils.playerPosition(ctx, store, ref); - if (playerPos == null) { - return CompletableFuture.completedFuture(null); - } + Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int sets = ExamplePhysicsUtils.optionalInt(ctx, setsArg, DEFAULT_SETS, 1, MAX_SETS); - PhysicsWorldResource resource = ExamplePhysicsUtils.resource(store); + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); @@ -98,7 +95,6 @@ private static void spawn(@Nonnull Store store, double xOffset) { ExamplePhysicsUtils.spawnBlockBody(store, time, - resource, spaceId, new Vector3d(base).add(xOffset, 0.0, 0.0), ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index e46b892f..82fc6aa2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -21,6 +21,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; @@ -130,13 +131,12 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e } List groups = groupFragments(fragments, center, settings.getRadius()); - ExamplePhysicsUtils.refreshPhysicsStoreWorldCollisionAround(store, - world, + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + resource.refreshWorldCollisionAround(world, spaceId, center, Math.max(8, settings.getRadius() + 4)); - ExamplePhysicsUtils.ensurePhysicsStoreWorldCollisionAround(store, - world, + resource.ensureWorldCollisionAround(world, spaceId, groupCenters(groups), Math.max(8, maxGroupCollisionRadius(groups) + 4), From 152f856029b7b7068e8d3194df45fc750e6c7120 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:23:06 +0200 Subject: [PATCH 126/534] fix(physicsstore): cascade stale body joint cleanup Signed-off-by: Blovien --- .../systems/StaleBodyRemovalSystem.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 5a5e8efa..0a59fe1e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -13,12 +14,17 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nonnull; /** @@ -61,6 +67,11 @@ private static void removeStaleBodies(@Nonnull Store store, if (restore.isFailed()) { return; } + Set staleBodyUuids = new ObjectOpenHashSet<>(); + staleBodies.forEach(body -> staleBodyUuids.add(body.bodyUuid())); + if (!removeDependentJoints(store, runtime, identity, restore, staleBodyUuids)) { + return; + } for (BoundBody body : staleBodies) { try { body.backendRuntime().removeBody(body.spaceHandle().value(), body.bodyHandle().value()); @@ -70,10 +81,74 @@ private static void removeStaleBodies(@Nonnull Store store, return; } identity.removeBodyHandle(body.bodyHandle()); + Ref ref = identity.getByUuid(body.bodyUuid()); + if (ref != null) { + identity.removeUuid(body.bodyUuid(), ref); + } runtime.removeBodyHandle(body.bodyUuid()); } } + private static boolean removeDependentJoints(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set staleBodyUuids) { + if (staleBodyUuids.isEmpty()) { + return true; + } + for (BoundJoint joint : collectDependentJoints(store, staleBodyUuids)) { + BackendJointHandle jointHandle = runtime.getJointHandle(joint.jointUuid()); + if (jointHandle != null) { + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(joint.jointUuid()); + if (spaceHandle == null) { + spaceHandle = runtime.getSpaceHandle(joint.spaceUuid()); + } + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (spaceHandle != null && backendRuntime != null) { + try { + backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); + } catch (RuntimeException exception) { + restore.markFailed("PhysicsStore joint " + joint.jointUuid() + + " failed backend removal: " + exception.getMessage()); + return false; + } + } + identity.removeJointHandle(jointHandle); + } + runtime.removeJointHandle(joint.jointUuid()); + if (joint.ref().isValid()) { + identity.removeUuid(joint.jointUuid(), joint.ref()); + store.removeEntity(joint.ref(), + store.getRegistry().newHolder(), + RemoveReason.REMOVE); + } + } + return true; + } + + @Nonnull + private static List collectDependentJoints(@Nonnull Store store, + @Nonnull Set staleBodyUuids) { + ConcurrentLinkedQueue joints = new ConcurrentLinkedQueue<>(); + store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { + JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); + if (joint == null + || (!staleBodyUuids.contains(joint.getBodyAUuid()) + && !staleBodyUuids.contains(joint.getBodyBUuid()))) { + return; + } + UUID jointUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(jointUuid)) { + return; + } + joints.add(new BoundJoint(jointUuid, + chunk.getReferenceTo(index), + joint.getSpaceUuid())); + }); + return new ArrayList<>(joints); + } + private static void collectStaleBody(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @@ -112,4 +187,9 @@ private record BoundBody(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } + + private record BoundJoint(@Nonnull UUID jointUuid, + @Nonnull Ref ref, + @Nonnull UUID spaceUuid) { + } } From a72783f56ca9c7f58e3906a4f8fadfce973daa67 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:25:49 +0200 Subject: [PATCH 127/534] fix(physicsstore): clear copied state for stale bodies Signed-off-by: Blovien --- .../physicsstore/systems/StaleBodyRemovalSystem.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 0a59fe1e..600862e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -9,13 +9,16 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; @@ -72,6 +75,9 @@ private static void removeStaleBodies(@Nonnull Store store, if (!removeDependentJoints(store, runtime, identity, restore, staleBodyUuids)) { return; } + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); for (BoundBody body : staleBodies) { try { body.backendRuntime().removeBody(body.spaceHandle().value(), body.bodyHandle().value()); @@ -85,6 +91,8 @@ private static void removeStaleBodies(@Nonnull Store store, if (ref != null) { identity.removeUuid(body.bodyUuid(), ref); } + snapshots.removeBody(body.bodyUuid()); + registrations.removeBody(RigidBodyKey.of(body.bodyUuid())); runtime.removeBodyHandle(body.bodyUuid()); } } From 4ad86daff99adc00c1d6d78a8b8f6a1aa25f95d1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:35:49 +0200 Subject: [PATCH 128/534] refactor(examples): resolve spaces through physics store Signed-off-by: Blovien --- .../impulse/examples/commands/DropCommand.java | 4 +--- .../examples/commands/ExamplePhysicsUtils.java | 11 +++++++---- .../impulse/examples/commands/ForcesCommand.java | 4 +--- .../impulse/examples/commands/GrabCommand.java | 4 ++-- .../impulse/examples/commands/JointsCommand.java | 4 +--- .../examples/commands/MaterialsCommand.java | 4 +--- .../commands/PhysicsStoreExampleCommands.java | 14 ++++++-------- .../impulse/examples/commands/RaycastCommand.java | 4 +--- .../impulse/examples/commands/ShapesCommand.java | 4 +--- .../examples/commands/WorldCollisionCommand.java | 12 ++++++------ .../commands/stress/StressBenchmarkCommand.java | 4 ++-- .../commands/stress/StressBodiesCommand.java | 4 ++-- .../commands/stress/StressJointsCommand.java | 4 +--- .../commands/stress/StressRawBodiesCommand.java | 4 +--- .../commands/stress/StressRaycastCommand.java | 4 +--- .../commands/stress/StressShapesCommand.java | 15 ++++++--------- 16 files changed, 40 insertions(+), 60 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index e7486e64..376d06bc 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -12,7 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.concurrent.CompletableFuture; @@ -49,8 +48,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, float spawnY = (float) playerPos.y() + 5f; float spawnZ = (float) playerPos.z(); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 06963b19..f9613762 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -31,7 +31,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -184,8 +183,12 @@ public static void appendPhysicsStoreBodyCommand(@Nonnull Store st @Nullable public static SpaceId spaceId(@Nonnull CommandContext ctx, - @Nonnull PhysicsWorldResource resource, + @Nonnull World world, @Nonnull OptionalArg spaceArg) { + Store store = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(store, "select a PhysicsStore space"); + PhysicsSpaceCompatibilityIndexResource compatibility = store + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()); if (spaceArg.provided(ctx)) { int rawSpaceId = spaceArg.get(ctx); if (rawSpaceId <= 0) { @@ -193,14 +196,14 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, return null; } SpaceId spaceId = new SpaceId(rawSpaceId); - if (!resource.hasSpace(spaceId)) { + if (!compatibility.hasSpace(spaceId)) { ctx.sender().sendMessage(Message.raw("No physics space id=" + rawSpaceId + " exists.")); return null; } return spaceId; } - SpaceId firstSpaceId = resource.getSpaceIds() + SpaceId firstSpaceId = compatibility.spaceIds() .stream() .min(Comparator.comparingInt(SpaceId::value)) .orElse(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index 57e55304..75be8a40 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; @@ -46,8 +45,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 8c66e085..ad6ef56b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -89,11 +89,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ComponentType controllableType = ImpulseControllableComponent.getComponentType(); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId targetSpaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId targetSpaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (targetSpaceId == null) { return CompletableFuture.completedFuture(null); } + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index c6d062bd..791931d2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -52,8 +51,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index e8007dc6..1d033b01 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -12,7 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.concurrent.CompletableFuture; @@ -40,8 +39,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 486542f4..657d8c5b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -64,10 +64,8 @@ protected PhysicsStorePlayerCommand(@Nonnull String name, @Nonnull String descri @Nullable protected SpaceId resolveSpace(@Nonnull CommandContext ctx, - @Nonnull Store store) { - return ExamplePhysicsUtils.spaceId(ctx, - store.getResource(PhysicsWorldResource.getResourceType()), - spaceArg); + @Nonnull World world) { + return ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); } } @@ -89,7 +87,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - SpaceId spaceId = resolveSpace(ctx, store); + SpaceId spaceId = resolveSpace(ctx, world); if (spaceId == null) { return CompletableFuture.completedFuture(null); } @@ -146,7 +144,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - SpaceId spaceId = resolveSpace(ctx, store); + SpaceId spaceId = resolveSpace(ctx, world); if (spaceId == null) { return CompletableFuture.completedFuture(null); } @@ -206,7 +204,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - SpaceId spaceId = resolveSpace(ctx, store); + SpaceId spaceId = resolveSpace(ctx, world); if (spaceId == null) { return CompletableFuture.completedFuture(null); } @@ -278,7 +276,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - SpaceId spaceId = resolveSpace(ctx, store); + SpaceId spaceId = resolveSpace(ctx, world); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index b93def52..ee8191cf 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -16,7 +16,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -41,8 +40,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index 898312ba..8ec87138 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -13,7 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.concurrent.CompletableFuture; @@ -40,8 +39,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java index 8f715b12..f4bab0f9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java @@ -62,11 +62,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, DEFAULT_RADIUS, 1, MAX_RADIUS); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); WorldCollisionBuildStats stats = resource.rebuildWorldCollisionAround(world, spaceId, playerPos, @@ -115,12 +115,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, DEFAULT_RADIUS, 1, MAX_RADIUS); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, spaceId, List.of(playerPos), @@ -156,11 +156,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); int removed = resource.clearWorldCollision(spaceId); ctx.sender().sendMessage(Message.raw("Removed " + removed + " world voxel collision bodies.")); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 38fd1e5e..1bcdbc92 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -72,11 +72,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); BenchmarkLayout layout = BenchmarkLayout.around(playerPos, request.count()); return PhysicsStoreAsync.acceptOnWorldThread(world, PhysicsStoreDiagnostics.bodyCountAsync(world, spaceId), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 5bfb1515..f1dac95b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -142,11 +142,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (visualSettings == null) { return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } + PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); PhysicsSpaceSettings settings = configureStressRuntime(resource, spaceId, mode, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 4ec96297..c85aff60 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -75,8 +74,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int totalJoints = ExamplePhysicsUtils.optionalInt(ctx, countArg, DEFAULT_JOINTS, 1, MAX_JOINTS); String blockType = blockType(ctx); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index a11e89f3..6cab7462 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -13,7 +13,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; @@ -57,8 +56,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int count = ExamplePhysicsUtils.optionalInt(ctx, countArg, DEFAULT_COUNT, 1, MAX_COUNT); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index da511e12..af52a61c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -13,7 +13,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; import java.util.ArrayList; @@ -53,8 +52,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int rays = ExamplePhysicsUtils.optionalInt(ctx, raysArg, DEFAULT_RAYS, 1, MAX_RAYS); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index 23605383..424a73ef 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -13,7 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; @@ -54,8 +53,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int sets = ExamplePhysicsUtils.optionalInt(ctx, setsArg, DEFAULT_SETS, 1, MAX_SETS); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, resource, spaceArg); + SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } @@ -68,15 +66,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int col = set % 4; Vector3d base = new Vector3d(origin).add(col * 7.0, row * 2.2, row * 1.5); - spawn(store, time, resource, spaceId, ShapeType.BOX, axis, + spawn(store, time, spaceId, ShapeType.BOX, axis, base, 0.0); - spawn(store, time, resource, spaceId, ShapeType.SPHERE, axis, + spawn(store, time, spaceId, ShapeType.SPHERE, axis, base, 1.2); - spawn(store, time, resource, spaceId, ShapeType.CAPSULE, axis, + spawn(store, time, spaceId, ShapeType.CAPSULE, axis, base, 2.4); - spawn(store, time, resource, spaceId, ShapeType.CYLINDER, axis, + spawn(store, time, spaceId, ShapeType.CYLINDER, axis, base, 3.6); - spawn(store, time, resource, spaceId, ShapeType.CONE, axis, + spawn(store, time, spaceId, ShapeType.CONE, axis, base, 4.8); } @@ -87,7 +85,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawn(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PhysicsWorldResource resource, @Nonnull SpaceId spaceId, @Nonnull ShapeType type, @Nonnull PhysicsAxis axis, From 153e0fbb0d6c68f0850a572e7e1d680c7448a067 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:39:45 +0200 Subject: [PATCH 129/534] refactor(core): select spaces through physics store Signed-off-by: Blovien --- .../internal/commands/SpaceSelection.java | 32 ++++++++++++++----- .../settings/SolverSettingsCommand.java | 2 +- .../VisualMaterializationSettingsCommand.java | 2 +- .../settings/VisualSyncSettingsCommand.java | 2 +- .../commands/CollisionLodSettingsCommand.java | 2 +- .../WorldCollisionSettingsCommand.java | 2 +- 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java index 442b444f..9ef24bcf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java @@ -1,12 +1,17 @@ package dev.hytalemodding.impulse.core.internal.commands; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import java.util.Comparator; +import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -18,15 +23,15 @@ private SpaceSelection() { @Nullable public static SpaceId resolve(@Nonnull CommandContext context, @Nonnull World world, - @Nonnull PhysicsWorldResource resource, @Nonnull OptionalArg spaceArg) { + PhysicsSpaceCompatibilityIndexResource compatibility = compatibility(world); if (spaceArg.provided(context)) { int rawSpaceId = spaceArg.get(context); if (rawSpaceId <= 0) { context.sendMessage(Message.raw("Space id must be a positive integer.")); return null; } - SpaceId spaceId = specifiedSpaceId(resource, rawSpaceId); + SpaceId spaceId = specifiedSpaceId(compatibility, rawSpaceId); if (spaceId == null) { context.sendMessage(Message.raw("No physics space id=" + rawSpaceId + " exists in world " + world.getName() + ".")); @@ -35,7 +40,7 @@ public static SpaceId resolve(@Nonnull CommandContext context, return spaceId; } - SpaceId firstSpaceId = firstRegisteredSpaceId(resource); + SpaceId firstSpaceId = firstRegisteredSpaceId(compatibility); if (firstSpaceId == null) { context.sendMessage(Message.raw("No physics space exists. Run " + "`/impulse space create --backend=` before targeting space settings.")); @@ -44,19 +49,30 @@ public static SpaceId resolve(@Nonnull CommandContext context, } @Nullable - static SpaceId specifiedSpaceId(@Nonnull PhysicsWorldResource resource, int rawSpaceId) { + static SpaceId specifiedSpaceId(@Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + int rawSpaceId) { if (rawSpaceId <= 0) { return null; } SpaceId spaceId = new SpaceId(rawSpaceId); - return resource.hasSpace(spaceId) ? spaceId : null; + return compatibility.hasSpace(spaceId) ? spaceId : null; } @Nullable - static SpaceId firstRegisteredSpaceId(@Nonnull PhysicsWorldResource resource) { - return resource.getSpaceIds() + static SpaceId firstRegisteredSpaceId( + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility) { + return compatibility.spaceIds() .stream() .min(Comparator.comparingInt(SpaceId::value)) .orElse(null); } + + @Nonnull + private static PhysicsSpaceCompatibilityIndexResource compatibility(@Nonnull World world) { + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); + PhysicsStoreThreading.requireWorldThread(store, "select a PhysicsStore space"); + return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index ccdd7e86..8bc4a318 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -55,7 +55,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Store store = world.getEntityStore().getStore(); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, resource, spaceArg); + SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java index 277a355d..9c4d6725 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java @@ -88,7 +88,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, resource, spaceArg); + SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java index 5681d3dd..40f39e74 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java @@ -111,7 +111,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, resource, spaceArg); + SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java index ca6ad0dc..8069ea86 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java @@ -71,7 +71,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, resource, spaceArg); + SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java index 11d6a4ad..63667cd4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java @@ -71,7 +71,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, resource, spaceArg); + SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); if (spaceId == null) { return CompletableFuture.completedFuture(null); } From a0ca937bc2e28cc6e932fff31a5a48c0842f100b Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:44:36 +0200 Subject: [PATCH 130/534] refactor(physicsstore): keep body refs in runtime metadata Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 7 ++++++- .../physicsstore/systems/BodyBindingSystem.java | 2 +- .../physicsstore/systems/StepSubmissionSystem.java | 14 +++----------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 6d4c524f..a9ca6b7a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -131,6 +132,7 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { } public void putBodyHandle(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull UUID spaceUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle handle) { @@ -138,7 +140,8 @@ public void putBodyHandle(@Nonnull UUID bodyUuid, bodySpaceHandlesByUuid.put(bodyUuid, spaceHandle); bodyHandlesBySpaceHandle.computeIfAbsent(spaceHandle.value(), _ -> new LongArrayList()) .add(handle.value()); - bodySnapshotMetadataByHandle.put(handle.value(), new BodySnapshotMetadata(bodyUuid, spaceUuid)); + bodySnapshotMetadataByHandle.put(handle.value(), + new BodySnapshotMetadata(bodyUuid, bodyRef, spaceUuid)); } @Nullable @@ -520,10 +523,12 @@ public record BodyHitMetadata(@Nullable RigidBodyKey bodyKey, } public record BodySnapshotMetadata(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull UUID spaceUuid) { public BodySnapshotMetadata { Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(bodyRef, "bodyRef"); Objects.requireNonNull(spaceUuid, "spaceUuid"); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index d907b06a..e51a81be 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -159,7 +159,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); } applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); - runtime.putBodyHandle(bodyUuid, body.getSpaceUuid(), spaceHandle, bodyHandle); + runtime.putBodyHandle(bodyUuid, bodyRef, body.getSpaceUuid(), spaceHandle, bodyHandle); runtime.putBodyHitMetadata(bodyHandle, RigidBodyKey.of(bodyUuid), bodyType, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index 2cbca923..112eaa0f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.systems; -import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -14,7 +13,6 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; @@ -70,10 +68,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) float stepDt = safeDt / steps; boolean ccdMode = stepMode == PhysicsStepMode.CCD; if (ccdMode || settingsResource.isCcdStepModeActive()) { - syncContinuousCollisionMode(store, - runtime, - store.getResource(PhysicsIdentityIndexResource.getResourceType()), - ccdMode); + syncContinuousCollisionMode(store, runtime, ccdMode); } settingsResource.setCcdStepModeActive(ccdMode); PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); @@ -117,7 +112,6 @@ private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runt private static void syncContinuousCollisionMode(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, boolean forceDynamicBodies) { runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { @@ -126,7 +120,7 @@ private static void syncContinuousCollisionMode(@Nonnull Store sto runtime.forEachBodyHandle(spaceHandle, bodyId -> { BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); boolean authoredCcd = metadata != null - && authoredContinuousCollision(store, identity, metadata); + && authoredContinuousCollision(store, metadata); backendRuntime.bodySnapshot(spaceHandle.value(), bodyId, new ContinuousCollisionSync(backendRuntime, @@ -138,11 +132,9 @@ private static void syncContinuousCollisionMode(@Nonnull Store sto } private static boolean authoredContinuousCollision(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull BodySnapshotMetadata metadata) { - Ref ref = PhysicsStoreSystemSupport.refForUuid(identity, metadata.bodyUuid()); DynamicsComponent dynamics = PhysicsStoreSystemSupport.component(store, - ref, + metadata.bodyRef(), DynamicsComponent.getComponentType()); return dynamics != null && dynamics.isContinuousCollisionEnabled(); } From 7769275a937d4a5a770921b6ca5366fbfa0d2fba Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:47:32 +0200 Subject: [PATCH 131/534] refactor(physicsstore): route body operations by ref Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 46 +++++++++++++++++-- .../systems/BodyCommandApplicationSystem.java | 32 ++++++++----- .../systems/TargetBindingSystem.java | 4 +- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index a9ca6b7a..6361fc02 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -52,6 +52,12 @@ public final class PhysicsRuntimeResource implements Resource { private final Map bodySpaceHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map, BackendBodyHandle> bodyHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, BackendSpaceHandle> bodySpaceHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Map jointHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull @@ -124,7 +130,11 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { if (bodyHandles != null) { bodyHandles.forEach((long bodyHandle) -> { bodyHitMetadataByHandle.remove(bodyHandle); - bodySnapshotMetadataByHandle.remove(bodyHandle); + BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(bodyHandle); + if (metadata != null) { + bodyHandlesByRef.remove(metadata.bodyRef()); + bodySpaceHandlesByRef.remove(metadata.bodyRef()); + } }); } removeTerrainHandlesForSpace(removed); @@ -138,6 +148,8 @@ public void putBodyHandle(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle handle) { bodyHandlesByUuid.put(bodyUuid, handle); bodySpaceHandlesByUuid.put(bodyUuid, spaceHandle); + bodyHandlesByRef.put(bodyRef, handle); + bodySpaceHandlesByRef.put(bodyRef, spaceHandle); bodyHandlesBySpaceHandle.computeIfAbsent(spaceHandle.value(), _ -> new LongArrayList()) .add(handle.value()); bodySnapshotMetadataByHandle.put(handle.value(), @@ -149,11 +161,21 @@ public BackendBodyHandle getBodyHandle(@Nonnull UUID bodyUuid) { return bodyHandlesByUuid.get(bodyUuid); } + @Nullable + public BackendBodyHandle getBodyHandle(@Nonnull Ref bodyRef) { + return bodyHandlesByRef.get(bodyRef); + } + @Nullable public BackendSpaceHandle getBodySpaceHandle(@Nonnull UUID bodyUuid) { return bodySpaceHandlesByUuid.get(bodyUuid); } + @Nullable + public BackendSpaceHandle getBodySpaceHandle(@Nonnull Ref bodyRef) { + return bodySpaceHandlesByRef.get(bodyRef); + } + public void removeBodyHandle(@Nonnull UUID bodyUuid) { BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); @@ -166,7 +188,11 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid) { } } bodyHitMetadataByHandle.remove(removed.value()); - bodySnapshotMetadataByHandle.remove(removed.value()); + BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(removed.value()); + if (metadata != null) { + bodyHandlesByRef.remove(metadata.bodyRef()); + bodySpaceHandlesByRef.remove(metadata.bodyRef()); + } } } @@ -367,6 +393,8 @@ public void clear() { backendIdsBySpaceUuid.clear(); bodyHandlesByUuid.clear(); bodySpaceHandlesByUuid.clear(); + bodyHandlesByRef.clear(); + bodySpaceHandlesByRef.clear(); jointHandlesByUuid.clear(); jointSpaceHandlesByUuid.clear(); terrainBodyHandlesByUuid.clear(); @@ -482,6 +510,8 @@ public PhysicsRuntimeResource clone() { copy.backendIdsBySpaceUuid.putAll(backendIdsBySpaceUuid); copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); + copy.bodyHandlesByRef.putAll(bodyHandlesByRef); + copy.bodySpaceHandlesByRef.putAll(bodySpaceHandlesByRef); copy.jointHandlesByUuid.putAll(jointHandlesByUuid); copy.jointSpaceHandlesByUuid.putAll(jointSpaceHandlesByUuid); terrainBodyHandlesByUuid.forEach((terrainUuid, bodyHandles) -> @@ -535,6 +565,7 @@ public record BodySnapshotMetadata(@Nonnull UUID bodyUuid, public record PendingBodyOperation(@Nonnull Kind kind, @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nullable BackendSpaceHandle spaceHandle, @Nullable BackendBodyHandle bodyHandle, float x, @@ -548,25 +579,29 @@ public record PendingBodyOperation(@Nonnull Kind kind, public PendingBodyOperation { Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(bodyRef, "bodyRef"); } @Nonnull public static PendingBodyOperation wake(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nullable BackendSpaceHandle spaceHandle, @Nullable BackendBodyHandle bodyHandle) { - return empty(Kind.WAKE, bodyUuid, spaceHandle, bodyHandle); + return empty(Kind.WAKE, bodyUuid, bodyRef, spaceHandle, bodyHandle); } @Nonnull public static PendingBodyOperation sleep(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nullable BackendSpaceHandle spaceHandle, @Nullable BackendBodyHandle bodyHandle) { - return empty(Kind.SLEEP, bodyUuid, spaceHandle, bodyHandle); + return empty(Kind.SLEEP, bodyUuid, bodyRef, spaceHandle, bodyHandle); } @Nonnull public static PendingBodyOperation vector(@Nonnull Kind kind, @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nullable BackendSpaceHandle spaceHandle, @Nullable BackendBodyHandle bodyHandle, float x, @@ -578,6 +613,7 @@ public static PendingBodyOperation vector(@Nonnull Kind kind, float offsetZ) { return new PendingBodyOperation(kind, bodyUuid, + bodyRef, spaceHandle, bodyHandle, x, @@ -592,10 +628,12 @@ public static PendingBodyOperation vector(@Nonnull Kind kind, @Nonnull private static PendingBodyOperation empty(@Nonnull Kind kind, @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nullable BackendSpaceHandle spaceHandle, @Nullable BackendBodyHandle bodyHandle) { return new PendingBodyOperation(kind, bodyUuid, + bodyRef, spaceHandle, bodyHandle, 0.0f, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java index 3a3d2697..4a71330a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java @@ -83,20 +83,23 @@ private static void applyCommand(@Nonnull Store store, @Nonnull BodyCommandComponent.Entry command) { switch (command.getKind()) { case WAKE -> runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + ref, null, null)); case SLEEP -> runtime.enqueuePendingBodyOperation(PendingBodyOperation.sleep(bodyUuid, + ref, null, null)); - case IMPULSE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.IMPULSE); + case IMPULSE -> enqueueVector(runtime, ref, bodyUuid, command, PendingBodyOperation.Kind.IMPULSE); case TORQUE_IMPULSE -> enqueueVector(runtime, + ref, bodyUuid, command, PendingBodyOperation.Kind.TORQUE_IMPULSE); - case FORCE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.FORCE); - case TORQUE -> enqueueVector(runtime, bodyUuid, command, PendingBodyOperation.Kind.TORQUE); + case FORCE -> enqueueVector(runtime, ref, bodyUuid, command, PendingBodyOperation.Kind.FORCE); + case TORQUE -> enqueueVector(runtime, ref, bodyUuid, command, PendingBodyOperation.Kind.TORQUE); case SET_TYPE -> applyBodyType(store, runtime, restore, ref, bodyUuid, command); - case SET_VELOCITY -> applyVelocity(runtime, restore, bodyUuid, command); + case SET_VELOCITY -> applyVelocity(runtime, restore, ref, bodyUuid, command); case SET_COLLISION_FILTER -> applyCollisionFilter(runtime, restore, store, ref, bodyUuid, command); } } @@ -114,10 +117,10 @@ private static void applyBodyType(@Nonnull Store store, updated.setBodyType(command.getBodyType()); store.putComponent(ref, DynamicsComponent.getComponentType(), updated); - RuntimeBodyBinding binding = runtimeBodyBinding(runtime, bodyUuid, restore, false); + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, ref, bodyUuid, restore, false); if (binding == null) { if (command.isActivate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, null, null)); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref, null, null)); } return; } @@ -127,6 +130,7 @@ private static void applyBodyType(@Nonnull Store store, updateBodyHitMetadata(runtime, binding.bodyHandle(), command.getBodyType()); if (command.isActivate()) { runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + ref, binding.spaceHandle(), binding.bodyHandle())); } @@ -141,10 +145,10 @@ private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime store.putComponent(ref, CollisionFilterComponent.getComponentType(), new CollisionFilterComponent(command.getCollisionGroup(), command.getCollisionMask())); - RuntimeBodyBinding binding = runtimeBodyBinding(runtime, bodyUuid, restore, false); + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, ref, bodyUuid, restore, false); if (binding == null) { if (command.isActivate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, null, null)); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref, null, null)); } return; } @@ -154,6 +158,7 @@ private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime command.getCollisionMask()); if (command.isActivate()) { runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + ref, binding.spaceHandle(), binding.bodyHandle())); } @@ -161,9 +166,10 @@ private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime private static void applyVelocity(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Ref ref, @Nonnull UUID bodyUuid, @Nonnull BodyCommandComponent.Entry command) { - RuntimeBodyBinding binding = runtimeBodyBinding(runtime, bodyUuid, restore, true); + RuntimeBodyBinding binding = runtimeBodyBinding(runtime, ref, bodyUuid, restore, true); if (binding == null) { return; } @@ -177,17 +183,20 @@ private static void applyVelocity(@Nonnull PhysicsRuntimeResource runtime, command.getAngularZ()); if (command.isActivate()) { runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, + ref, binding.spaceHandle(), binding.bodyHandle())); } } private static void enqueueVector(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull Ref ref, @Nonnull UUID bodyUuid, @Nonnull BodyCommandComponent.Entry command, @Nonnull PendingBodyOperation.Kind kind) { runtime.enqueuePendingBodyOperation(PendingBodyOperation.vector(kind, bodyUuid, + ref, null, null, command.getX(), @@ -201,11 +210,12 @@ private static void enqueueVector(@Nonnull PhysicsRuntimeResource runtime, @Nullable private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull Ref ref, @Nonnull UUID bodyUuid, @Nonnull PhysicsRestoreStatusResource restore, boolean requireBound) { - BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + BackendBodyHandle bodyHandle = runtime.getBodyHandle(ref); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(ref); if (bodyHandle == null || spaceHandle == null) { if (requireBound) { restore.recordSoftSkip("Body command target is unbound: " + bodyUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index f6043e41..a90ecee0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -100,8 +100,8 @@ private static void applyPendingBodyOperations(@Nonnull PhysicsRuntimeResource r BackendSpaceHandle spaceHandle = operation.spaceHandle(); BackendBodyHandle bodyHandle = operation.bodyHandle(); if (spaceHandle == null || bodyHandle == null) { - spaceHandle = runtime.getBodySpaceHandle(operation.bodyUuid()); - bodyHandle = runtime.getBodyHandle(operation.bodyUuid()); + spaceHandle = runtime.getBodySpaceHandle(operation.bodyRef()); + bodyHandle = runtime.getBodyHandle(operation.bodyRef()); } if (spaceHandle == null || bodyHandle == null) { restore.recordSoftSkip("Pending body operation body is unbound: " From ef8ef56afdc74be536bce5169ee1cf2def6ef481 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:51:50 +0200 Subject: [PATCH 132/534] refactor(physicsstore): route space settings by ref Signed-off-by: Blovien --- .../PhysicsStoreSpaceMutations.java | 6 +++--- .../resources/PhysicsRuntimeResource.java | 17 +++++++++-------- .../systems/PhysicsStoreSystemSupport.java | 6 ++++++ .../systems/SpaceBindingSystem.java | 2 +- .../systems/SpaceSettingsApplicationSystem.java | 12 ++++++------ 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 280083e1..b56efc87 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -77,7 +77,7 @@ public static Ref addSpace(@Nonnull Store store, compatibility.putSpace(compatibilitySpaceId, spaceUuid); SpaceId.reserveAtLeast(compatibilitySpaceId.value()); store.getResource(PhysicsRuntimeResource.getResourceType()) - .markSpaceSettingsPending(spaceUuid); + .markSpaceSettingsPending(ref); return ref; } @@ -115,7 +115,7 @@ public static void putSpaceGravity(@Nonnull Store store, updated.setGravity(gravity); store.putComponent(ref, SpaceComponent.getComponentType(), updated); store.getResource(PhysicsRuntimeResource.getResourceType()) - .markSpaceSettingsPending(spaceUuid); + .markSpaceSettingsPending(ref); } public static void putSpaceSettings(@Nonnull Store store, @@ -138,7 +138,7 @@ public static void putSpaceSettings(@Nonnull Store store, new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), new ExtensionSettingsComponent(settings.getExtensionSettings())); store.getResource(PhysicsRuntimeResource.getResourceType()) - .markSpaceSettingsPending(spaceUuid); + .markSpaceSettingsPending(ref); } public static void removeEmptySpace(@Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 6361fc02..0e254675 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -87,7 +87,8 @@ public final class PhysicsRuntimeResource implements Resource { @Nonnull private final List pendingBodyOperations = new ArrayList<>(); @Nonnull - private final ObjectOpenHashSet pendingSpaceSettings = new ObjectOpenHashSet<>(); + private final ObjectOpenHashSet> pendingSpaceSettings = + new ObjectOpenHashSet<>(); @Setter @Getter private boolean started; @@ -124,7 +125,6 @@ public BackendId getSpaceBackendId(@Nonnull UUID spaceUuid) { public void removeSpaceHandle(@Nonnull UUID spaceUuid) { BackendSpaceHandle removed = spaceHandlesByUuid.remove(spaceUuid); backendIdsBySpaceUuid.remove(spaceUuid); - pendingSpaceSettings.remove(spaceUuid); if (removed != null) { LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); if (bodyHandles != null) { @@ -239,20 +239,20 @@ public void enqueuePendingBodyOperation(@Nonnull PendingBodyOperation operation) pendingBodyOperations.add(Objects.requireNonNull(operation, "operation")); } - public void markSpaceSettingsPending(@Nonnull UUID spaceUuid) { - pendingSpaceSettings.add(Objects.requireNonNull(spaceUuid, "spaceUuid")); + public void markSpaceSettingsPending(@Nonnull Ref spaceRef) { + pendingSpaceSettings.add(Objects.requireNonNull(spaceRef, "spaceRef")); } - public void clearPendingSpaceSettings(@Nonnull UUID spaceUuid) { - pendingSpaceSettings.remove(spaceUuid); + public void clearPendingSpaceSettings(@Nonnull Ref spaceRef) { + pendingSpaceSettings.remove(spaceRef); } @Nonnull - public Set drainPendingSpaceSettings() { + public Set> drainPendingSpaceSettings() { if (pendingSpaceSettings.isEmpty()) { return Set.of(); } - Set drained = new ObjectOpenHashSet<>(pendingSpaceSettings); + Set> drained = new ObjectOpenHashSet<>(pendingSpaceSettings); pendingSpaceSettings.clear(); return drained; } @@ -524,6 +524,7 @@ public PhysicsRuntimeResource clone() { copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); copy.bodySnapshotMetadataByHandle.putAll(bodySnapshotMetadataByHandle); copy.pendingBodyOperations.addAll(pendingBodyOperations); + copy.pendingSpaceSettings.addAll(pendingSpaceSettings); copy.started = started; return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java index 182e12cc..f9f6b256 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java @@ -29,6 +29,12 @@ static UUID rowUuid(@Nonnull ArchetypeChunk chunk, int index) { return uuid != null ? uuid.getUuid() : NIL_UUID; } + @Nonnull + static UUID rowUuid(@Nonnull Ref ref) { + UuidComponent uuid = component(ref.getStore(), ref, UUID_TYPE); + return uuid != null ? uuid.getUuid() : NIL_UUID; + } + static boolean isNil(@Nonnull UUID uuid) { return NIL_UUID.equals(uuid); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index 4ab33216..faa17fb1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -137,7 +137,7 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, solverSettings != null ? solverSettings : new SolverSettingsComponent(), extensionSettings); runtime.putSpaceBinding(spaceUuid, backendId, handle); - runtime.clearPendingSpaceSettings(spaceUuid); + runtime.clearPendingSpaceSettings(ref); compatibility.putSpace(compatibilitySpaceId, spaceUuid); identity.putSpaceHandle(handle, ref); } catch (RuntimeException exception) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java index 82ebdd81..fe7a43c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java @@ -11,7 +11,6 @@ import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; @@ -45,17 +44,18 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - Set pending = runtime.drainPendingSpaceSettings(); + Set> pending = runtime.drainPendingSpaceSettings(); if (pending.isEmpty()) { return; } - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - for (UUID spaceUuid : pending) { - Ref ref = identity.getByUuid(spaceUuid); + for (Ref ref : pending) { if (ref == null || !ref.isValid()) { continue; } + UUID spaceUuid = PhysicsStoreSystemSupport.rowUuid(ref); + if (PhysicsStoreSystemSupport.isNil(spaceUuid)) { + continue; + } try { applyIfBound(store, runtime, ref, spaceUuid); } catch (RuntimeException exception) { From df98dfc63398196678adfc6b929ad6c34515e14b Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 21:53:30 +0200 Subject: [PATCH 133/534] refactor(physicsstore): bind targets by ref Signed-off-by: Blovien --- .../physicsstore/systems/TargetBindingSystem.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index a90ecee0..9b944947 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -2,6 +2,7 @@ import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -18,7 +19,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import java.util.Set; -import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nonnull; import org.joml.Quaternionf; @@ -53,9 +53,9 @@ private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, if (target == null || !target.isActive()) { continue; } - UUID bodyUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + Ref ref = chunk.getReferenceTo(index); + BackendBodyHandle bodyHandle = runtime.getBodyHandle(ref); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(ref); if (bodyHandle == null || spaceHandle == null) { continue; } From dd22ec75535c7969189fb77a7ceb2f17ad7ff2e3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:00:53 +0200 Subject: [PATCH 134/534] refactor(examples): expose physics body uuids Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 17 +++-- .../examples/commands/ForcesCommand.java | 7 +- .../examples/commands/JointsCommand.java | 72 +++++++++---------- .../commands/PhysicsStoreExampleCommands.java | 2 +- .../commands/stress/StressJointsCommand.java | 2 +- .../explosive/ExplosiveBlockRuntime.java | 8 +-- 6 files changed, 51 insertions(+), 57 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index f9613762..f0c909db 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -266,8 +266,7 @@ private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store entity = spawnAttachedPhysicsStoreBlockEntity(store, time, - pending.bodyKey().value(), + pending.bodyUuid(), pending.blockType(), new Vector3d(pending.positionX(), pending.positionY(), pending.positionZ()), pending.controllable()); assert entity != null; - return new SpawnedBlockBody(pending.bodyKey(), pending.spaceId(), entity); + return new SpawnedBlockBody(pending.bodyUuid(), pending.spaceId(), entity); } @Nonnull @@ -529,7 +528,7 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store 0.0f); if (spawned != null) { assert entity != null; - spawned[i] = new SpawnedBlockBody(bodyKey, spaceId, entity); + spawned[i] = new SpawnedBlockBody(bodyKey.value(), spaceId, entity); } } long entityAttachNanos = System.nanoTime() - entityAttachStartNanos; @@ -624,7 +623,7 @@ static Vector3f toVector3f(@Nonnull Vector3d vector) { return new Vector3f((float) vector.x, (float) vector.y, (float) vector.z); } - public record SpawnedBlockBody(@Nonnull RigidBodyKey bodyKey, + public record SpawnedBlockBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @Nonnull Ref entity) { } @@ -668,7 +667,7 @@ private boolean isEmpty() { } } - public record PendingBlockBody(@Nonnull RigidBodyKey bodyKey, + public record PendingBlockBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @Nullable String blockType, float positionX, @@ -677,7 +676,7 @@ public record PendingBlockBody(@Nonnull RigidBodyKey bodyKey, boolean controllable) { public PendingBlockBody { - Objects.requireNonNull(bodyKey, "bodyKey"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(spaceId, "spaceId"); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index 75be8a40..7fa8846f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -13,7 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -162,17 +161,17 @@ private static PendingBlockBody spawnBox(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, @Nonnull BodyCommandComponent command) { - RigidBodyKey bodyKey = RigidBodyKey.random(); + UUID bodyUuid = UUID.randomUUID(); ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, - bodyKey.value(), + bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.material(0.5f, 0.25f), null), command); - return new PendingBlockBody(bodyKey, + return new PendingBlockBody(bodyUuid, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, (float) position.x, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index 791931d2..bea520d5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -12,8 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -107,14 +105,14 @@ private static void createFixed(@Nonnull List pendingBodies, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey childKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, + UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID childUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); ExamplePhysicsUtils.addPhysicsStoreJoint(world, - JointKey.random().value(), + UUID.randomUUID(), joint(spaceUuid, - anchorKey, - childKey, + anchorUuid, + childUuid, JointType.FIXED, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -126,8 +124,8 @@ private static void createPoint(@Nonnull List pendingBodies, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey bobKey = spawnBox(pendingBodies, + UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID bobUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, @@ -135,10 +133,10 @@ private static void createPoint(@Nonnull List pendingBodies, 1.0f, new Vector3f(1.5f, 0.0f, 0.0f)); ExamplePhysicsUtils.addPhysicsStoreJoint(world, - JointKey.random().value(), + UUID.randomUUID(), joint(spaceUuid, - anchorKey, - bobKey, + anchorUuid, + bobUuid, JointType.POINT, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -150,12 +148,12 @@ private static void createHinge(@Nonnull List pendingBodies, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey armKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, + UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID armUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); JointComponent joint = joint(spaceUuid, - anchorKey, - armKey, + anchorUuid, + armUuid, JointType.HINGE, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -165,7 +163,7 @@ private static void createHinge(@Nonnull List pendingBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.5f); joint.setMotorMaxForce(3.0f); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, JointKey.random().value(), joint); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); } private static void createSlider(@Nonnull List pendingBodies, @@ -173,12 +171,12 @@ private static void createSlider(@Nonnull List pendingBodies, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey blockKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, + UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID blockUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(TOUCHING_SPACING, 0.0, 0.0), 1.0f); JointComponent joint = joint(spaceUuid, - anchorKey, - blockKey, + anchorUuid, + blockUuid, JointType.SLIDER, new Vector3f(HALF_SIZE, 0.0f, 0.0f), new Vector3f(-HALF_SIZE, 0.0f, 0.0f), @@ -188,7 +186,7 @@ private static void createSlider(@Nonnull List pendingBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.0f); joint.setMotorMaxForce(4.0f); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, JointKey.random().value(), joint); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); } private static void createSpring(@Nonnull List pendingBodies, @@ -196,8 +194,8 @@ private static void createSpring(@Nonnull List pendingBodies, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - RigidBodyKey anchorKey = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - RigidBodyKey bobKey = spawnBox(pendingBodies, + UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID bobUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, @@ -205,8 +203,8 @@ private static void createSpring(@Nonnull List pendingBodies, 1.0f, new Vector3f(1.0f, 0.0f, 0.0f)); JointComponent joint = joint(spaceUuid, - anchorKey, - bobKey, + anchorUuid, + bobUuid, JointType.SPRING, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -214,10 +212,10 @@ private static void createSpring(@Nonnull List pendingBodies, joint.setSpringRestLength(SPRING_REST_LENGTH); joint.setSpringStiffness(20.0f); joint.setSpringDamping(2.0f); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, JointKey.random().value(), joint); + ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); } - private static RigidBodyKey spawnBox(@Nonnull List pendingBodies, + private static UUID spawnBox(@Nonnull List pendingBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @@ -226,44 +224,44 @@ private static RigidBodyKey spawnBox(@Nonnull List pendingBodi return spawnBox(pendingBodies, world, spaceUuid, spaceId, position, mass, null); } - private static RigidBodyKey spawnBox(@Nonnull List pendingBodies, + private static UUID spawnBox(@Nonnull List pendingBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass, @Nullable Vector3f linearVelocity) { - RigidBodyKey bodyKey = RigidBodyKey.random(); + UUID bodyUuid = UUID.randomUUID(); ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, - bodyKey.value(), + bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), mass, RigidBodySpawnSettings.material(0.6f, 0.15f), linearVelocity)); - pendingBodies.add(new PendingBlockBody(bodyKey, + pendingBodies.add(new PendingBlockBody(bodyUuid, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, (float) position.x, (float) position.y, (float) position.z, mass > 0.0f)); - return bodyKey; + return bodyUuid; } @Nonnull private static JointComponent joint(@Nonnull UUID spaceUuid, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid, @Nonnull JointType type, @Nonnull Vector3f anchorA, @Nonnull Vector3f anchorB, @Nonnull Vector3f axis) { JointComponent joint = new JointComponent(); joint.setSpaceUuid(spaceUuid); - joint.setBodyAUuid(bodyA.value()); - joint.setBodyBUuid(bodyB.value()); + joint.setBodyAUuid(bodyAUuid); + joint.setBodyBUuid(bodyBUuid); joint.setType(type); joint.setAnchorA(anchorA); joint.setAnchorB(anchorB); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 657d8c5b..369264bf 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -177,7 +177,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, - new ExamplePhysicsUtils.PendingBlockBody(bodyKey, + new ExamplePhysicsUtils.PendingBlockBody(bodyUuid, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, (float) spawn.x, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index c85aff60..8b61080c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -166,7 +166,7 @@ private static int appendRow(@Nonnull List pendingBodies, spawnSettings, initialVelocity(jointType, i))); pendingBodies.add(new PendingBlockBody( - bodyKeys[i], + bodyKeys[i].value(), spaceId, blockType, positions[positionOffset], diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 82fc6aa2..8c488439 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -19,7 +19,6 @@ import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -145,8 +144,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e Vector3f centerF = toVector3f(center); List pending = new ArrayList<>(groups.size()); for (FragmentGroup group : groups) { - RigidBodyKey bodyKey = RigidBodyKey.random(); - UUID bodyUuid = bodyKey.value(); + UUID bodyUuid = UUID.randomUUID(); Vector3d groupCenter = group.center(); Vector3f impulse = ExplosiveBlockPolicy.outwardImpulse(centerF, toVector3f(groupCenter), @@ -169,7 +167,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e 0.0f, 0.0f, 0.0f)); - pending.add(new PendingBlockBody(bodyKey, + pending.add(new PendingBlockBody(bodyUuid, spaceId, group.blockType(), (float) groupCenter.x, @@ -192,7 +190,7 @@ private static void spawnGroupVisuals(@Nonnull TimeResource time, for (FragmentVisual visual : group.visualBlocks()) { boolean controllable = body.controllable() && !controllableAssigned; Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, - body.bodyKey().value(), + body.bodyUuid(), visual.blockType(), visual.position(), visual.localPositionOffset(), From f833034d9e56ad6c101e4269cee1298744ffc7dc Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:02:34 +0200 Subject: [PATCH 135/534] refactor(examples): author stress joints with uuids Signed-off-by: Blovien --- .../commands/stress/StressJointsCommand.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 8b61080c..5035125e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -12,8 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -140,16 +138,16 @@ private static int appendRow(@Nonnull List pendingBodies, double spacing = jointType == 4 ? TOUCHING_SPACING + SPRING_REST_LENGTH : TOUCHING_SPACING; int bodyCount = jointCount + 1; - RigidBodyKey[] bodyKeys = new RigidBodyKey[bodyCount]; + UUID[] bodyUuids = new UUID[bodyCount]; float[] positions = new float[bodyCount * 3]; - long bodyKeyRunId = RigidBodyKey.random().mostSignificantBits(); - long jointKeyRunId = JointKey.random().mostSignificantBits(); + long bodyUuidRunId = UUID.randomUUID().getMostSignificantBits(); + long jointUuidRunId = UUID.randomUUID().getMostSignificantBits(); PhysicsShapeSpec box = PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.1f); for (int i = 0; i < bodyCount; i++) { - RigidBodyKey bodyKey = RigidBodyKey.of(bodyKeyRunId, i + 1L); - bodyKeys[i] = bodyKey; + UUID bodyUuid = new UUID(bodyUuidRunId, i + 1L); + bodyUuids[i] = bodyUuid; int positionOffset = i * 3; positions[positionOffset] = (float) (origin.x + i * spacing); positions[positionOffset + 1] = (float) origin.y; @@ -157,7 +155,7 @@ private static int appendRow(@Nonnull List pendingBodies, float mass = i == 0 ? 0.0f : 1.0f; ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, - bodyKey.value(), + bodyUuid, new Vector3f(positions[positionOffset], positions[positionOffset + 1], positions[positionOffset + 2]), @@ -166,7 +164,7 @@ private static int appendRow(@Nonnull List pendingBodies, spawnSettings, initialVelocity(jointType, i))); pendingBodies.add(new PendingBlockBody( - bodyKeys[i].value(), + bodyUuid, spaceId, blockType, positions[positionOffset], @@ -176,8 +174,8 @@ private static int appendRow(@Nonnull List pendingBodies, } for (int i = 0; i < jointCount; i++) { ExamplePhysicsUtils.addPhysicsStoreJoint(world, - JointKey.of(jointKeyRunId, i + 1L).value(), - joint(spaceUuid, bodyKeys[i], bodyKeys[i + 1], jointType)); + new UUID(jointUuidRunId, i + 1L), + joint(spaceUuid, bodyUuids[i], bodyUuids[i + 1], jointType)); } return bodyCount; } @@ -191,8 +189,8 @@ private String blockType(@Nonnull CommandContext ctx) { @Nonnull private static JointComponent joint(@Nonnull UUID spaceUuid, - @Nonnull RigidBodyKey previousKey, - @Nonnull RigidBodyKey currentKey, + @Nonnull UUID previousUuid, + @Nonnull UUID currentUuid, int jointType) { JointType type = switch (jointType) { case 0 -> JointType.FIXED; @@ -203,8 +201,8 @@ private static JointComponent joint(@Nonnull UUID spaceUuid, }; JointComponent joint = new JointComponent(); joint.setSpaceUuid(spaceUuid); - joint.setBodyAUuid(previousKey.value()); - joint.setBodyBUuid(currentKey.value()); + joint.setBodyAUuid(previousUuid); + joint.setBodyBUuid(currentUuid); joint.setType(type); joint.setAnchorA(new Vector3f(HALF_SIZE, 0.0f, 0.0f)); joint.setAnchorB(new Vector3f(-HALF_SIZE, 0.0f, 0.0f)); From c6b1e8ec2a0d338d113f6408c2dd804a8e9645ac Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:04:43 +0200 Subject: [PATCH 136/534] refactor(examples): use uuids in block body batches Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 78 ++++++++----------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index f0c909db..7b0fef2b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -394,9 +394,9 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, List bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { - RigidBodyKey bodyKey = batch.bodyKey(i); + UUID bodyUuid = batch.bodyUuid(i); bodies.add(bodyRow(spaceUuid, - bodyKey.value(), + bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, mass, @@ -502,9 +502,9 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store rows = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { - RigidBodyKey bodyKey = batch.bodyKey(i); + UUID bodyUuid = batch.bodyUuid(i); rows.add(bodyRow(spaceUuid, - bodyKey.value(), + bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, mass, @@ -519,16 +519,16 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store entity = spawnAttachedPhysicsStoreBlockEntity(store, time, - bodyKey.value(), + bodyUuid, blockType, new Vector3d(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), mass > 0.0f); if (spawned != null) { assert entity != null; - spawned[i] = new SpawnedBlockBody(bodyKey.value(), spaceId, entity); + spawned[i] = new SpawnedBlockBody(bodyUuid, spaceId, entity); } } long entityAttachNanos = System.nanoTime() - entityAttachStartNanos; @@ -685,17 +685,17 @@ public static final class BlockBodyBatchBuilder { private static final int POSITION_STRIDE = 3; - private final long bodyKeyRunId = RigidBodyKey.random().mostSignificantBits(); - private long[] bodyKeyMostSignificantBits; - private long[] bodyKeyLeastSignificantBits; + private final long bodyUuidRunId = UUID.randomUUID().getMostSignificantBits(); + private long[] bodyUuidMostSignificantBits; + private long[] bodyUuidLeastSignificantBits; private float[] positions; private int size; private boolean sealed; private BlockBodyBatchBuilder(int expectedBodies) { int capacity = Math.max(1, expectedBodies); - bodyKeyMostSignificantBits = new long[capacity]; - bodyKeyLeastSignificantBits = new long[capacity]; + bodyUuidMostSignificantBits = new long[capacity]; + bodyUuidLeastSignificantBits = new long[capacity]; positions = new float[capacity * POSITION_STRIDE]; } @@ -703,7 +703,7 @@ private BlockBodyBatchBuilder(int expectedBodies) { public BlockBodyBatchBuilder addBody(float positionX, float positionY, float positionZ) { - return addBody(bodyKeyRunId, + return addBody(bodyUuidRunId, size + 1L, positionX, positionY, @@ -711,46 +711,46 @@ public BlockBodyBatchBuilder addBody(float positionX, } @Nonnull - public BlockBodyBatchBuilder addBody(@Nonnull RigidBodyKey bodyKey, + public BlockBodyBatchBuilder addBody(@Nonnull UUID bodyUuid, float positionX, float positionY, float positionZ) { - Objects.requireNonNull(bodyKey, "bodyKey"); - return addBody(bodyKey.mostSignificantBits(), - bodyKey.leastSignificantBits(), + Objects.requireNonNull(bodyUuid, "bodyUuid"); + return addBody(bodyUuid.getMostSignificantBits(), + bodyUuid.getLeastSignificantBits(), positionX, positionY, positionZ); } @Nonnull - public RigidBodyKey body(float positionX, + public UUID body(float positionX, float positionY, float positionZ) { long leastSignificantBits = size + 1L; - addBody(bodyKeyRunId, leastSignificantBits, positionX, positionY, positionZ); - return RigidBodyKey.of(bodyKeyRunId, leastSignificantBits); + addBody(bodyUuidRunId, leastSignificantBits, positionX, positionY, positionZ); + return new UUID(bodyUuidRunId, leastSignificantBits); } @Nonnull - public RigidBodyKey body(@Nonnull RigidBodyKey bodyKey, + public UUID body(@Nonnull UUID bodyUuid, float positionX, float positionY, float positionZ) { - addBody(bodyKey, positionX, positionY, positionZ); - return bodyKey; + addBody(bodyUuid, positionX, positionY, positionZ); + return bodyUuid; } @Nonnull - private BlockBodyBatchBuilder addBody(long bodyKeyMostSignificantBits, - long bodyKeyLeastSignificantBits, + private BlockBodyBatchBuilder addBody(long bodyUuidMostSignificantBits, + long bodyUuidLeastSignificantBits, float positionX, float positionY, float positionZ) { assertMutable(); ensureCapacity(size + 1); - this.bodyKeyMostSignificantBits[size] = bodyKeyMostSignificantBits; - this.bodyKeyLeastSignificantBits[size] = bodyKeyLeastSignificantBits; + this.bodyUuidMostSignificantBits[size] = bodyUuidMostSignificantBits; + this.bodyUuidLeastSignificantBits[size] = bodyUuidLeastSignificantBits; int positionOffset = size * POSITION_STRIDE; positions[positionOffset] = positionX; positions[positionOffset + 1] = positionY; @@ -772,20 +772,10 @@ private int size() { } @Nonnull - private RigidBodyKey bodyKey(int index) { - checkIndex(index); - return RigidBodyKey.of(bodyKeyMostSignificantBits[index], - bodyKeyLeastSignificantBits[index]); - } - - private long bodyKeyMostSignificantBits(int index) { - checkIndex(index); - return bodyKeyMostSignificantBits[index]; - } - - private long bodyKeyLeastSignificantBits(int index) { + private UUID bodyUuid(int index) { checkIndex(index); - return bodyKeyLeastSignificantBits[index]; + return new UUID(bodyUuidMostSignificantBits[index], + bodyUuidLeastSignificantBits[index]); } private float positionX(int index) { @@ -806,13 +796,13 @@ private float position(int index, int slot) { } private void ensureCapacity(int required) { - if (required <= bodyKeyMostSignificantBits.length) { + if (required <= bodyUuidMostSignificantBits.length) { return; } int nextCapacity = Math.max(required, - bodyKeyMostSignificantBits.length + (bodyKeyMostSignificantBits.length >> 1) + 1); - bodyKeyMostSignificantBits = Arrays.copyOf(bodyKeyMostSignificantBits, nextCapacity); - bodyKeyLeastSignificantBits = Arrays.copyOf(bodyKeyLeastSignificantBits, nextCapacity); + bodyUuidMostSignificantBits.length + (bodyUuidMostSignificantBits.length >> 1) + 1); + bodyUuidMostSignificantBits = Arrays.copyOf(bodyUuidMostSignificantBits, nextCapacity); + bodyUuidLeastSignificantBits = Arrays.copyOf(bodyUuidLeastSignificantBits, nextCapacity); positions = Arrays.copyOf(positions, nextCapacity * POSITION_STRIDE); } From 3eeb0aef9098139ec4f6e44822d319270ced77b2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:06:32 +0200 Subject: [PATCH 137/534] refactor(examples): spawn command bodies with uuids Signed-off-by: Blovien --- .../commands/PhysicsStoreExampleCommands.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 369264bf..2cf9a860 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -18,7 +18,6 @@ import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; @@ -149,8 +148,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - RigidBodyKey bodyKey = RigidBodyKey.random(); - UUID bodyUuid = bodyKey.value(); + UUID bodyUuid = UUID.randomUUID(); Vector3d spawn = new Vector3d(playerPos).add(0.0, 2.0, 0.0); UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); if (spaceUuid == null) { @@ -185,7 +183,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, (float) spawn.z, false)); - ctx.sender().sendMessage(Message.raw("Queued PhysicsStore kinematic platform " + bodyKey + ctx.sender().sendMessage(Message.raw("Queued PhysicsStore kinematic platform " + bodyUuid + " in space " + spaceId.value() + ".")); return CompletableFuture.completedFuture(null); } @@ -316,8 +314,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Math.max(8, radius + 6), Math.max(0L, world.getTick())); - RigidBodyKey bodyKey = RigidBodyKey.random(); - UUID bodyUuid = bodyKey.value(); + UUID bodyUuid = UUID.randomUUID(); ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, @@ -346,7 +343,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, holder.addComponent(ExplosiveFuseComponent.getComponentType(), new ExplosiveFuseComponent()); store.addEntity(holder, AddReason.SPAWN); - ctx.sender().sendMessage(Message.raw("Queued Impulse explosive body " + bodyKey + ctx.sender().sendMessage(Message.raw("Queued Impulse explosive body " + bodyUuid + " in space " + spaceId.value() + " radius=" + radius + " maxFragments=" + maxFragments From 5615267f435e67d8aca1e4738a6d1d6bb347a6ed Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:08:29 +0200 Subject: [PATCH 138/534] refactor(examples): attach external views by uuid Signed-off-by: Blovien --- .../impulse/examples/commands/ExamplePhysicsUtils.java | 5 ++--- .../examples/commands/PhysicsStoreExampleCommands.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 7b0fef2b..68ca1f5c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -19,7 +19,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; @@ -551,12 +550,12 @@ static void addControllableMarkerIfAvailable(@Nonnull Holder holder @Nullable public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID bodyUuid, @Nonnull Vector3d visualPosition, @Nullable String blockType) { Holder holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(ATTACHMENT_TYPE, - BodyAttachmentComponent.externalEntity(bodyKey.value())); + BodyAttachmentComponent.externalEntity(bodyUuid)); return store.addEntity(holder, AddReason.SPAWN); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 2cf9a860..e529cc53 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -223,7 +223,7 @@ private static void attachView(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.spawnExternalBodyViewBlockEntity(store, time, - hit.bodyKey(), + hit.bodyKey().value(), point, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE); From ae75608a22db356a7a86805b1ad3ec860e26c570 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:24:43 +0200 Subject: [PATCH 139/534] refactor(physicsstore): carry raycast hits by ref Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 7 ++-- .../systems/BodyBindingSystem.java | 3 +- .../systems/BodyCommandApplicationSystem.java | 2 +- .../CompletedStepPublicationSystem.java | 15 +++++--- .../systems/TerrainColliderBindingSystem.java | 35 +++++++++++++++---- .../visual/DetachedVisualOcclusion.java | 16 ++++++++- .../PhysicsStoreBackendAccess.java | 2 +- .../simulation/view/RaycastHitView.java | 11 +++--- .../commands/ExamplePhysicsUtils.java | 13 +++++++ .../examples/commands/GrabCommand.java | 11 ++++-- .../commands/PhysicsStoreExampleCommands.java | 28 ++++++++------- 11 files changed, 104 insertions(+), 39 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 0e254675..b9a3a8f3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -11,7 +11,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -209,11 +208,11 @@ public List bodyUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandl } public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, - @Nullable RigidBodyKey bodyKey, + @Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull ShapeType shapeType) { bodyHitMetadataByHandle.put(handle.value(), - new BodyHitMetadata(bodyKey, bodyType, shapeType)); + new BodyHitMetadata(bodyRef, bodyType, shapeType)); } @Nullable @@ -543,7 +542,7 @@ void accept(@Nonnull UUID spaceUuid, @Nonnull PhysicsBackendRuntime runtime); } - public record BodyHitMetadata(@Nullable RigidBodyKey bodyKey, + public record BodyHitMetadata(@Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull ShapeType shapeType) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index e51a81be..54f77ca8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -19,7 +19,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; @@ -161,7 +160,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); runtime.putBodyHandle(bodyUuid, bodyRef, body.getSpaceUuid(), spaceHandle, bodyHandle); runtime.putBodyHitMetadata(bodyHandle, - RigidBodyKey.of(bodyUuid), + bodyRef, bodyType, shape.getShapeType()); identity.putBodyHandle(bodyHandle, bodyRef); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java index 4a71330a..d6405f12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java @@ -241,7 +241,7 @@ private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtim PhysicsRuntimeResource.BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyHandle); if (metadata != null) { runtime.putBodyHitMetadata(bodyHandle, - metadata.bodyKey(), + metadata.bodyRef(), bodyType, metadata.shapeType()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 314f53b3..ea0f1fca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -299,16 +299,23 @@ private static void collectContactEvent(@Nonnull PhysicsRuntimeResource runtime, BodyHitMetadata bodyA = runtime.getBodyHitMetadata(bodyAId); BodyHitMetadata bodyB = runtime.getBodyHitMetadata(bodyBId); if (bodyA == null - || bodyA.bodyKey() == null + || bodyA.bodyRef() == null || bodyB == null - || bodyB.bodyKey() == null) { + || bodyB.bodyRef() == null) { + backendEvents.droppedBackendEventCount++; + return; + } + UUID bodyAUuid = PhysicsStoreSystemSupport.rowUuid(bodyA.bodyRef()); + UUID bodyBUuid = PhysicsStoreSystemSupport.rowUuid(bodyB.bodyRef()); + if (PhysicsStoreSystemSupport.isNil(bodyAUuid) + || PhysicsStoreSystemSupport.isNil(bodyBUuid)) { backendEvents.droppedBackendEventCount++; return; } backendEvents.physicsEvents.add(new PhysicsContactEvent(spaceId, PhysicsContactPhase.OBSERVED, - bodyA.bodyKey(), - bodyB.bodyKey(), + RigidBodyKey.of(bodyAUuid), + RigidBodyKey.of(bodyBUuid), new Vector3f(pointAX, pointAY, pointAZ), new Vector3f(pointBX, pointBY, pointBZ), new Vector3f(normalBX, normalBY, normalBZ), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 5f2370f7..3e5c88fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -2,6 +2,7 @@ import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -24,7 +25,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.TerrainNeighbor; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import java.util.Set; import java.util.UUID; @@ -83,13 +83,14 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, restore.recordSoftSkip("Terrain payload is missing: " + terrain.getSourceKey()); continue; } - bindTerrain(runtime, restore, terrainUuid, terrain, payload); + bindTerrain(runtime, restore, terrainUuid, chunk.getReferenceTo(index), terrain, payload); } } private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull UUID terrainUuid, + @Nonnull Ref terrainRef, @Nonnull TerrainColliderComponent terrain, @Nonnull TerrainColliderPayload payload) { BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(terrain.getSpaceUuid()); @@ -111,14 +112,32 @@ private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, && payload.hasFullCubeVoxels() && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); if (nativeVoxel) { - addVoxelTerrain(runtime, backendRuntime, spaceHandle, terrainUuid, terrain, payload); + addVoxelTerrain(runtime, + backendRuntime, + spaceHandle, + terrainUuid, + terrainRef, + terrain, + payload); } else { for (BoxPayload box : payload.mergedFullCubeBoxes()) { - addStaticBox(runtime, backendRuntime, spaceHandle, terrainUuid, box, payload); + addStaticBox(runtime, + backendRuntime, + spaceHandle, + terrainUuid, + terrainRef, + box, + payload); } } for (BoxPayload box : payload.detailBoxes()) { - addStaticBox(runtime, backendRuntime, spaceHandle, terrainUuid, box, payload); + addStaticBox(runtime, + backendRuntime, + spaceHandle, + terrainUuid, + terrainRef, + box, + payload); } if (!runtime.hasTerrainBodyHandles(terrainUuid)) { restore.recordSoftSkip("Terrain payload produced no backend bodies: " @@ -138,6 +157,7 @@ private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull UUID terrainUuid, + @Nonnull Ref terrainRef, @Nonnull TerrainColliderComponent terrain, @Nonnull TerrainColliderPayload payload) { long bodyId = backendRuntime.createVoxelTerrain(spaceHandle.value(), @@ -155,7 +175,7 @@ private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); runtime.putTerrainBodyHandle(terrainUuid, spaceHandle, bodyHandle, true); runtime.putBodyHitMetadata(bodyHandle, - RigidBodyKey.of(terrainUuid), + terrainRef, PhysicsBodyType.STATIC, ShapeType.VOXELS); } @@ -164,6 +184,7 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull UUID terrainUuid, + @Nonnull Ref terrainRef, @Nonnull BoxPayload box, @Nonnull TerrainColliderPayload payload) { if (box.halfX() <= 0.0 || box.halfY() <= 0.0 || box.halfZ() <= 0.0) { @@ -199,7 +220,7 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, bodyHandle, false); runtime.putBodyHitMetadata(bodyHandle, - RigidBodyKey.of(terrainUuid), + terrainRef, PhysicsBodyType.STATIC, ShapeType.BOX); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java index 1173f730..0b711cd8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java @@ -1,5 +1,7 @@ package dev.hytalemodding.impulse.core.internal.systems.visual; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; @@ -8,6 +10,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; @@ -66,7 +69,7 @@ static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, if (state.hasCompletedRaycast()) { Optional completedRaycast = state.pollCompletedRaycast(); raycastVisible = completedRaycast - .map(view -> bodyKey.equals(view.bodyKey())) + .map(view -> raycastHitMatchesBody(bodyKey, view)) .orElse(false); raycastDecisionKnown = true; raycastEvaluated = true; @@ -106,6 +109,17 @@ static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, return Result.visible(probe.distanceSquared(), priorityDistanceSquared); } + private static boolean raycastHitMatchesBody(@Nonnull RigidBodyKey bodyKey, + @Nonnull RaycastHitView view) { + Ref bodyRef = view.bodyRef(); + if (bodyRef == null || !bodyRef.isValid()) { + return false; + } + UuidComponent uuid = bodyRef.getStore().getComponent(bodyRef, + UuidComponent.getComponentType()); + return uuid != null && bodyKey.value().equals(uuid.getUuid()); + } + private static void submitRaycast(@Nonnull PhysicsWorldRuntimeResource resource, @Nonnull PhysicsSpaceBinding space, @Nonnull PhysicsVisualRuntime.BodyVisualInterestState state, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java index 9c8e5a75..0d2a9ab3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java @@ -96,7 +96,7 @@ static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, float fraction, float distance) { BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyId); - return new RaycastHitView(metadata != null ? metadata.bodyKey() : null, + return new RaycastHitView(metadata != null ? metadata.bodyRef() : null, metadata != null ? metadata.bodyType() : PhysicsBodyType.STATIC, pointX, pointY, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java index 12ffe4b5..c0ffbe3f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java @@ -1,17 +1,18 @@ package dev.hytalemodding.impulse.core.plugin.simulation.view; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; /** - * Copied raycast result that does not expose a live backend body. + * Copied raycast geometry plus the PhysicsStore row ref hit by the backend. */ -public record RaycastHitView(@Nullable RigidBodyKey bodyKey, +public record RaycastHitView(@Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, float pointX, float pointY, @@ -23,14 +24,14 @@ public record RaycastHitView(@Nullable RigidBodyKey bodyKey, float fraction, float distance) { - public RaycastHitView(@Nullable RigidBodyKey bodyKey, + public RaycastHitView(@Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull Vector3f point, @Nonnull Vector3f normal, @Nonnull ShapeType shapeType, float fraction, float distance) { - this(bodyKey, + this(bodyRef, bodyType, Objects.requireNonNull(point, "point").x, point.y, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 68ca1f5c..8bf869b6 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -28,6 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; @@ -156,6 +157,18 @@ public static boolean appendPhysicsStoreBodyCommand(@Nonnull World world, return true; } + @Nullable + public static UUID physicsStoreRowUuid(@Nonnull Ref ref) { + Objects.requireNonNull(ref, "ref"); + if (!ref.isValid()) { + return null; + } + Store store = ref.getStore(); + PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore row UUID"); + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + return uuid != null ? uuid.getUuid() : null; + } + @Nonnull public static Ref addPhysicsStoreJoint(@Nonnull World world, @Nonnull UUID jointUuid, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index ad6ef56b..01a7566c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -278,11 +278,18 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource @Nonnull List hits) { List candidates = new ArrayList<>(hits.size()); for (RaycastHitView hit : hits) { - if (hit.bodyType() != PhysicsBodyType.DYNAMIC || hit.bodyKey() == null) { + if (hit.bodyType() != PhysicsBodyType.DYNAMIC + || hit.bodyRef() == null + || !hit.bodyRef().isValid()) { continue; } + UUID hitBodyUuid = ExamplePhysicsUtils.physicsStoreRowUuid(hit.bodyRef()); + if (hitBodyUuid == null) { + continue; + } + RigidBodyKey hitBodyKey = RigidBodyKey.of(hitBodyUuid); PhysicsBodyRegistrationView registration = - resource.getBodyRegistrationView(hit.bodyKey()); + resource.getBodyRegistrationView(hitBodyKey); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { continue; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index e529cc53..df4f936c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -15,6 +15,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; @@ -100,7 +101,7 @@ private void applyImpulse(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull World world, @Nullable RaycastHitView hit) { - if (hit == null || hit.bodyKey() == null) { + if (hit == null || hit.bodyRef() == null || !hit.bodyRef().isValid()) { ctx.sender().sendMessage(Message.raw("No rigid body in view.")); return; } @@ -108,8 +109,10 @@ private void applyImpulse(@Nonnull CommandContext ctx, int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); Vector3d impulse = new Vector3d(TargetUtil.getLook(ref, store) .getDirection()).mul(strength); - boolean applied = ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(world, - hit.bodyKey().value(), + Ref bodyRef = hit.bodyRef(); + Store physicsStore = bodyRef.getStore(); + ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(physicsStore, + bodyRef, BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, (float) impulse.x, (float) impulse.y, @@ -118,14 +121,10 @@ private void applyImpulse(@Nonnull CommandContext ctx, 0.0f, 0.0f, 0.0f)); - if (!applied) { - ctx.sender().sendMessage(Message.raw("Rigid body " + hit.bodyKey() - + " is not bound in PhysicsStore.")); - return; - } + UUID bodyUuid = ExamplePhysicsUtils.physicsStoreRowUuid(bodyRef); ctx.sender().sendMessage(Message.raw("Queued PhysicsStore impulse command for " - + hit.bodyKey() + ".")); + + (bodyUuid != null ? bodyUuid : bodyRef) + ".")); } } @@ -214,21 +213,26 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void attachView(@Nonnull CommandContext ctx, @Nonnull Store store, @Nullable RaycastHitView hit) { - if (hit == null || hit.bodyKey() == null) { + if (hit == null || hit.bodyRef() == null || !hit.bodyRef().isValid()) { ctx.sender().sendMessage(Message.raw("No rigid body in view.")); return; } + UUID bodyUuid = ExamplePhysicsUtils.physicsStoreRowUuid(hit.bodyRef()); + if (bodyUuid == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore body has no persistent UUID.")); + return; + } Vector3d point = new Vector3d(hit.point().x, hit.point().y, hit.point().z); TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.spawnExternalBodyViewBlockEntity(store, time, - hit.bodyKey().value(), + bodyUuid, point, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE); ctx.sender().sendMessage(Message.raw("Attached view-only entity to " - + hit.bodyKey() + ".")); + + bodyUuid + ".")); } } From fdde216ddff5c858e3c44f4da13f8b6b01e60a05 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:32:01 +0200 Subject: [PATCH 140/534] refactor(physicsstore): resolve runtime relationships by ref Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 47 +++++++++++++ .../systems/BodyBindingSystem.java | 19 +++-- .../systems/JointBindingSystem.java | 70 ++++++++++++++----- .../systems/PhysicsStoreSystemSupport.java | 13 ++++ .../systems/SpaceBindingSystem.java | 2 +- .../SpaceSettingsApplicationSystem.java | 8 +-- .../systems/TerrainColliderBindingSystem.java | 34 +++++++-- .../components/BodyComponent.java | 18 ++++- .../components/JointComponent.java | 41 +++++++++++ .../components/TerrainColliderComponent.java | 18 ++++- 10 files changed, 236 insertions(+), 34 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index b9a3a8f3..b363b4f0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -45,6 +45,15 @@ public final class PhysicsRuntimeResource implements Resource { private final Map backendIdsBySpaceUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map> spaceRefsByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, BackendSpaceHandle> spaceHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, BackendId> backendIdsBySpaceRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Map bodyHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull @@ -107,8 +116,25 @@ public PhysicsBackendRuntime getRuntime(@Nonnull BackendId backendId) { public void putSpaceBinding(@Nonnull UUID spaceUuid, @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle handle) { + putSpaceBinding(spaceUuid, null, backendId, handle); + } + + public void putSpaceBinding(@Nonnull UUID spaceUuid, + @Nullable Ref spaceRef, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle handle) { + Ref previousRef = spaceRefsByUuid.remove(spaceUuid); + if (previousRef != null) { + backendIdsBySpaceRef.remove(previousRef); + spaceHandlesByRef.remove(previousRef); + } backendIdsBySpaceUuid.put(spaceUuid, backendId); spaceHandlesByUuid.put(spaceUuid, handle); + if (spaceRef != null) { + spaceRefsByUuid.put(spaceUuid, spaceRef); + backendIdsBySpaceRef.put(spaceRef, backendId); + spaceHandlesByRef.put(spaceRef, handle); + } } @Nullable @@ -116,14 +142,29 @@ public BackendSpaceHandle getSpaceHandle(@Nonnull UUID spaceUuid) { return spaceHandlesByUuid.get(spaceUuid); } + @Nullable + public BackendSpaceHandle getSpaceHandle(@Nonnull Ref spaceRef) { + return spaceHandlesByRef.get(spaceRef); + } + @Nullable public BackendId getSpaceBackendId(@Nonnull UUID spaceUuid) { return backendIdsBySpaceUuid.get(spaceUuid); } + @Nullable + public BackendId getSpaceBackendId(@Nonnull Ref spaceRef) { + return backendIdsBySpaceRef.get(spaceRef); + } + public void removeSpaceHandle(@Nonnull UUID spaceUuid) { BackendSpaceHandle removed = spaceHandlesByUuid.remove(spaceUuid); backendIdsBySpaceUuid.remove(spaceUuid); + Ref spaceRef = spaceRefsByUuid.remove(spaceUuid); + if (spaceRef != null) { + spaceHandlesByRef.remove(spaceRef); + backendIdsBySpaceRef.remove(spaceRef); + } if (removed != null) { LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); if (bodyHandles != null) { @@ -390,6 +431,9 @@ public void clear() { runtimesByBackend.clear(); spaceHandlesByUuid.clear(); backendIdsBySpaceUuid.clear(); + spaceRefsByUuid.clear(); + spaceHandlesByRef.clear(); + backendIdsBySpaceRef.clear(); bodyHandlesByUuid.clear(); bodySpaceHandlesByUuid.clear(); bodyHandlesByRef.clear(); @@ -507,6 +551,9 @@ public PhysicsRuntimeResource clone() { copy.runtimesByBackend.putAll(runtimesByBackend); copy.spaceHandlesByUuid.putAll(spaceHandlesByUuid); copy.backendIdsBySpaceUuid.putAll(backendIdsBySpaceUuid); + copy.spaceRefsByUuid.putAll(spaceRefsByUuid); + copy.spaceHandlesByRef.putAll(spaceHandlesByRef); + copy.backendIdsBySpaceRef.putAll(backendIdsBySpaceRef); copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); copy.bodyHandlesByRef.putAll(bodyHandlesByRef); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index 54f77ca8..ba0c0f3f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -101,12 +101,13 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, @Nullable ShapeComponent shape, @Nullable MaterialComponent material, @Nullable CollisionFilterComponent filter) { - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(body.getSpaceUuid()); + Ref spaceRef = resolveSpaceRef(identity, body); + BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; if (spaceHandle == null) { restore.recordSoftSkip("Body references unbound space: " + bodyUuid); return; } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, body.getSpaceUuid()); + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceRef); if (backendRuntime == null) { restore.recordSoftSkip("Body references missing backend runtime: " + bodyUuid); return; @@ -204,10 +205,20 @@ private static void applyInitialTargetState(@Nonnull PhysicsBackendRuntime backe } } + @Nullable + private static Ref resolveSpaceRef(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull BodyComponent body) { + Ref spaceRef = PhysicsStoreSystemSupport.resolvedRef(identity, + body.getSpaceUuid(), + body.getSpaceRef()); + body.setSpaceRef(spaceRef); + return spaceRef; + } + @Nullable private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID spaceUuid) { - var backendId = runtime.getSpaceBackendId(spaceUuid); + @Nonnull Ref spaceRef) { + var backendId = runtime.getSpaceBackendId(spaceRef); return backendId != null ? runtime.getRuntime(backendId) : null; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index d02e674e..e45de65b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -25,6 +25,7 @@ import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3f; /** @@ -71,7 +72,7 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, } BackendJointHandle existing = runtime.getJointHandle(jointUuid); if (existing != null) { - if (!endpointsBound(runtime, joint)) { + if (!endpointsBound(runtime, identity, joint)) { removeJoint(runtime, identity, jointUuid, joint); } continue; @@ -86,12 +87,15 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, @Nonnull Ref jointRef, @Nonnull UUID jointUuid, @Nonnull JointComponent joint) { - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); - BackendBodyHandle bodyA = runtime.getBodyHandle(joint.getBodyAUuid()); - BackendBodyHandle bodyB = runtime.getBodyHandle(joint.getBodyBUuid()); - BackendSpaceHandle bodyASpace = runtime.getBodySpaceHandle(joint.getBodyAUuid()); - BackendSpaceHandle bodyBSpace = runtime.getBodySpaceHandle(joint.getBodyBUuid()); - var backendId = runtime.getSpaceBackendId(joint.getSpaceUuid()); + Ref spaceRef = resolveSpaceRef(identity, joint); + Ref bodyARef = resolveBodyARef(identity, joint); + Ref bodyBRef = resolveBodyBRef(identity, joint); + BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; + BackendBodyHandle bodyA = bodyARef != null ? runtime.getBodyHandle(bodyARef) : null; + BackendBodyHandle bodyB = bodyBRef != null ? runtime.getBodyHandle(bodyBRef) : null; + BackendSpaceHandle bodyASpace = bodyARef != null ? runtime.getBodySpaceHandle(bodyARef) : null; + BackendSpaceHandle bodyBSpace = bodyBRef != null ? runtime.getBodySpaceHandle(bodyBRef) : null; + var backendId = spaceRef != null ? runtime.getSpaceBackendId(spaceRef) : null; PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; if (spaceHandle == null || bodyA == null || bodyB == null || backendRuntime == null) { restore.recordSoftSkip("Joint references unbound endpoint: " + jointUuid); @@ -147,19 +151,55 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, } private static boolean endpointsBound(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull JointComponent joint) { - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); - BackendSpaceHandle bodyASpace = runtime.getBodySpaceHandle(joint.getBodyAUuid()); - BackendSpaceHandle bodyBSpace = runtime.getBodySpaceHandle(joint.getBodyBUuid()); + Ref spaceRef = resolveSpaceRef(identity, joint); + Ref bodyARef = resolveBodyARef(identity, joint); + Ref bodyBRef = resolveBodyBRef(identity, joint); + BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; + BackendSpaceHandle bodyASpace = bodyARef != null ? runtime.getBodySpaceHandle(bodyARef) : null; + BackendSpaceHandle bodyBSpace = bodyBRef != null ? runtime.getBodySpaceHandle(bodyBRef) : null; return spaceHandle != null - && runtime.getBodyHandle(joint.getBodyAUuid()) != null - && runtime.getBodyHandle(joint.getBodyBUuid()) != null + && bodyARef != null + && bodyBRef != null + && runtime.getBodyHandle(bodyARef) != null + && runtime.getBodyHandle(bodyBRef) != null && bodyASpace != null && bodyBSpace != null && bodyASpace.value() == spaceHandle.value() && bodyBSpace.value() == spaceHandle.value(); } + @Nullable + private static Ref resolveSpaceRef(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull JointComponent joint) { + Ref spaceRef = PhysicsStoreSystemSupport.resolvedRef(identity, + joint.getSpaceUuid(), + joint.getSpaceRef()); + joint.setSpaceRef(spaceRef); + return spaceRef; + } + + @Nullable + private static Ref resolveBodyARef(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull JointComponent joint) { + Ref bodyRef = PhysicsStoreSystemSupport.resolvedRef(identity, + joint.getBodyAUuid(), + joint.getBodyARef()); + joint.setBodyARef(bodyRef); + return bodyRef; + } + + @Nullable + private static Ref resolveBodyBRef(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull JointComponent joint) { + Ref bodyRef = PhysicsStoreSystemSupport.resolvedRef(identity, + joint.getBodyBUuid(), + joint.getBodyBRef()); + joint.setBodyBRef(bodyRef); + return bodyRef; + } + private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull UUID jointUuid, @@ -169,11 +209,7 @@ private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, return; } BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); - if (spaceHandle == null) { - spaceHandle = runtime.getSpaceHandle(joint.getSpaceUuid()); - } - var backendId = runtime.getSpaceBackendId(joint.getSpaceUuid()); - PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeJoint(spaceHandle.value(), handle.value()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java index f9f6b256..ac21be90 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java @@ -55,4 +55,17 @@ static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identi Ref ref = identity.getByUuid(uuid); return ref != null && ref.isValid() ? ref : null; } + + @Nullable + static Ref resolvedRef(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID uuid, + @Nullable Ref current) { + if (isNil(uuid)) { + return null; + } + if (current != null && current.isValid() && uuid.equals(rowUuid(current))) { + return current; + } + return refForUuid(identity, uuid); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index faa17fb1..0c2f3df9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -136,7 +136,7 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, handle, solverSettings != null ? solverSettings : new SolverSettingsComponent(), extensionSettings); - runtime.putSpaceBinding(spaceUuid, backendId, handle); + runtime.putSpaceBinding(spaceUuid, ref, backendId, handle); runtime.clearPendingSpaceSettings(ref); compatibility.putSpace(compatibilitySpaceId, spaceUuid); identity.putSpaceHandle(handle, ref); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java index fe7a43c9..9777a27b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java @@ -70,11 +70,11 @@ static boolean applyIfBound(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull Ref ref, @Nonnull UUID spaceUuid) { - BackendSpaceHandle handle = runtime.getSpaceHandle(spaceUuid); + BackendSpaceHandle handle = runtime.getSpaceHandle(ref); if (handle == null) { return false; } - PhysicsBackendRuntime backendRuntime = backendRuntime(runtime, spaceUuid); + PhysicsBackendRuntime backendRuntime = backendRuntime(runtime, ref); if (backendRuntime == null) { return false; } @@ -124,8 +124,8 @@ static void applyBackendSettings(@Nonnull PhysicsBackendRuntime runtime, @Nullable private static PhysicsBackendRuntime backendRuntime(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID spaceUuid) { - BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + @Nonnull Ref spaceRef) { + BackendId backendId = runtime.getSpaceBackendId(spaceRef); if (backendId == null) { return null; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 3e5c88fa..bff84b1f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; @@ -47,18 +48,21 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsTerrainPayloadResource payloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); if (restore.isFailed()) { return; } BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindChunk(runtime, payloads, restore, chunk); + (chunk, _) -> bindChunk(runtime, payloads, identity, restore, chunk); store.forEachChunk(systemIndex, collector); } private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { @@ -83,22 +87,30 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, restore.recordSoftSkip("Terrain payload is missing: " + terrain.getSourceKey()); continue; } - bindTerrain(runtime, restore, terrainUuid, chunk.getReferenceTo(index), terrain, payload); + bindTerrain(runtime, + identity, + restore, + terrainUuid, + chunk.getReferenceTo(index), + terrain, + payload); } } private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull UUID terrainUuid, @Nonnull Ref terrainRef, @Nonnull TerrainColliderComponent terrain, @Nonnull TerrainColliderPayload payload) { - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(terrain.getSpaceUuid()); + Ref spaceRef = resolveSpaceRef(identity, terrain); + BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; if (spaceHandle == null) { restore.recordSoftSkip("Terrain references unbound space: " + terrain.getSourceKey()); return; } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, terrain.getSpaceUuid()); + PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceRef); if (backendRuntime == null) { restore.recordSoftSkip("Terrain references missing backend runtime: " + terrain.getSourceKey()); @@ -269,10 +281,20 @@ private static void removeTerrain(@Nonnull PhysicsRuntimeResource runtime, runtime.removeTerrainHandles(terrainUuid); } + @Nullable + private static Ref resolveSpaceRef(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull TerrainColliderComponent terrain) { + Ref spaceRef = PhysicsStoreSystemSupport.resolvedRef(identity, + terrain.getSpaceUuid(), + terrain.getSpaceRef()); + terrain.setSpaceRef(spaceRef); + return spaceRef; + } + @Nullable private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID spaceUuid) { - var backendId = runtime.getSpaceBackendId(spaceUuid); + @Nonnull Ref spaceRef) { + var backendId = runtime.getSpaceBackendId(spaceRef); return backendId != null ? runtime.getRuntime(backendId) : null; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java index eb5f349f..7370a6ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -13,6 +14,7 @@ import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Authored body identity, kind, and persistence policy. @@ -41,6 +43,8 @@ public final class BodyComponent implements Component { @Nonnull private UUID spaceUuid = new UUID(0L, 0L); + @Nullable + private transient Ref spaceRef; @Nonnull private PhysicsBodyKind kind = PhysicsBodyKind.BODY; @Nonnull @@ -64,6 +68,16 @@ public UUID getSpaceUuid() { public void setSpaceUuid(@Nonnull UUID spaceUuid) { this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.spaceRef = null; + } + + @Nullable + public Ref getSpaceRef() { + return spaceRef; + } + + public void setSpaceRef(@Nullable Ref spaceRef) { + this.spaceRef = spaceRef; } @Nonnull @@ -92,6 +106,8 @@ public static ComponentType getComponentType() { @Nonnull @Override public BodyComponent clone() { - return new BodyComponent(spaceUuid, kind, persistenceMode); + BodyComponent copy = new BodyComponent(spaceUuid, kind, persistenceMode); + copy.spaceRef = spaceRef; + return copy; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java index 2cb53e05..2123aea9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; @@ -13,6 +14,7 @@ import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3f; /** @@ -98,6 +100,12 @@ public final class JointComponent implements Component { private UUID bodyAUuid = new UUID(0L, 0L); @Nonnull private UUID bodyBUuid = new UUID(0L, 0L); + @Nullable + private transient Ref spaceRef; + @Nullable + private transient Ref bodyARef; + @Nullable + private transient Ref bodyBRef; @Nonnull private JointType type = JointType.FIXED; @Nonnull @@ -126,6 +134,16 @@ public UUID getSpaceUuid() { public void setSpaceUuid(@Nonnull UUID spaceUuid) { this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.spaceRef = null; + } + + @Nullable + public Ref getSpaceRef() { + return spaceRef; + } + + public void setSpaceRef(@Nullable Ref spaceRef) { + this.spaceRef = spaceRef; } @Nonnull @@ -135,6 +153,16 @@ public UUID getBodyAUuid() { public void setBodyAUuid(@Nonnull UUID bodyAUuid) { this.bodyAUuid = Objects.requireNonNull(bodyAUuid, "bodyAUuid"); + this.bodyARef = null; + } + + @Nullable + public Ref getBodyARef() { + return bodyARef; + } + + public void setBodyARef(@Nullable Ref bodyARef) { + this.bodyARef = bodyARef; } @Nonnull @@ -144,6 +172,16 @@ public UUID getBodyBUuid() { public void setBodyBUuid(@Nonnull UUID bodyBUuid) { this.bodyBUuid = Objects.requireNonNull(bodyBUuid, "bodyBUuid"); + this.bodyBRef = null; + } + + @Nullable + public Ref getBodyBRef() { + return bodyBRef; + } + + public void setBodyBRef(@Nullable Ref bodyBRef) { + this.bodyBRef = bodyBRef; } @Nonnull @@ -266,6 +304,9 @@ public JointComponent clone() { copy.spaceUuid = spaceUuid; copy.bodyAUuid = bodyAUuid; copy.bodyBUuid = bodyBUuid; + copy.spaceRef = spaceRef; + copy.bodyARef = bodyARef; + copy.bodyBRef = bodyBRef; copy.type = type; copy.anchorA.set(anchorA); copy.anchorB.set(anchorB); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java index 260432b0..02e2571f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java @@ -5,11 +5,13 @@ import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Terrain collider row mirrored from ChunkStore terrain source data. @@ -52,6 +54,8 @@ public final class TerrainColliderComponent implements Component { @Nonnull private UUID spaceUuid = new UUID(0L, 0L); + @Nullable + private transient Ref spaceRef; @Nonnull private String sourceKey = ""; private int chunkX; @@ -87,6 +91,16 @@ public UUID getSpaceUuid() { public void setSpaceUuid(@Nonnull UUID spaceUuid) { this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.spaceRef = null; + } + + @Nullable + public Ref getSpaceRef() { + return spaceRef; + } + + public void setSpaceRef(@Nullable Ref spaceRef) { + this.spaceRef = spaceRef; } @Nonnull @@ -147,12 +161,14 @@ public static ComponentType getComponent @Nonnull @Override public TerrainColliderComponent clone() { - return new TerrainColliderComponent(spaceUuid, + TerrainColliderComponent copy = new TerrainColliderComponent(spaceUuid, sourceKey, chunkX, sectionY, chunkZ, payloadResourceKey, retained); + copy.spaceRef = spaceRef; + return copy; } } From d064b475d80c640b5787b82f7d13778c022c2134 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:34:25 +0200 Subject: [PATCH 141/534] refactor(physicsstore): remove unused ref wrappers Signed-off-by: Blovien --- .../physicsstore/PhysicsPersistentRef.java | 48 -------------- .../core/plugin/physicsstore/PhysicsRef.java | 62 ------------------- 2 files changed, 110 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java deleted file mode 100644 index 8cd9070d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsPersistentRef.java +++ /dev/null @@ -1,48 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Durable reference to a PhysicsStore row. - */ -public final class PhysicsPersistentRef { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsPersistentRef.class, - PhysicsPersistentRef::new) - .append(new KeyedCodec<>("Uuid", Codec.UUID_BINARY, false), - (ref, value) -> ref.uuid = value != null ? value : UUID.randomUUID(), - PhysicsPersistentRef::getUuid) - .add() - .build(); - - @Nonnull - private UUID uuid = UUID.randomUUID(); - - public PhysicsPersistentRef() { - } - - public PhysicsPersistentRef(@Nonnull UUID uuid) { - this.uuid = Objects.requireNonNull(uuid, "uuid"); - } - - @Nonnull - public UUID getUuid() { - return uuid; - } - - public void setUuid(@Nonnull UUID uuid) { - this.uuid = Objects.requireNonNull(uuid, "uuid"); - } - - @Nonnull - public PhysicsPersistentRef copy() { - return new PhysicsPersistentRef(uuid); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java deleted file mode 100644 index d967d0fb..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRef.java +++ /dev/null @@ -1,62 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Runtime-resolved PhysicsStore reference paired with its durable UUID. - */ -public final class PhysicsRef { - - @Nonnull - private final UUID uuid; - @Nullable - private final Ref ref; - - private PhysicsRef(@Nonnull UUID uuid, @Nullable Ref ref) { - this.uuid = Objects.requireNonNull(uuid, "uuid"); - this.ref = ref; - } - - @Nonnull - public static PhysicsRef persistent(@Nonnull UUID uuid) { - return new PhysicsRef(uuid, null); - } - - @Nonnull - public static PhysicsRef resolved(@Nonnull UUID uuid, @Nonnull Ref ref) { - return new PhysicsRef(uuid, Objects.requireNonNull(ref, "ref")); - } - - @Nonnull - public UUID getUuid() { - return uuid; - } - - @Nonnull - public PhysicsPersistentRef toPersistentRef() { - return new PhysicsPersistentRef(uuid); - } - - public boolean isResolved() { - return ref != null && ref.isValid(); - } - - @Nullable - public Ref getRef() { - return ref; - } - - @Nonnull - public Ref requireRef() { - Ref current = ref; - if (current == null || !current.isValid()) { - throw new IllegalStateException("PhysicsStore reference " + uuid + " is not resolved"); - } - return current; - } -} From 29c9e7dc43663c4776826d6335dcd024939888c0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:43:50 +0200 Subject: [PATCH 142/534] refactor(physicsstore): remove uuid from step runtime path Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 19 ++++++++ .../systems/BodyBindingSystem.java | 2 +- .../CompletedStepPublicationSystem.java | 2 +- .../systems/IdentityIndexSystem.java | 2 +- .../systems/JointBindingSystem.java | 2 +- .../systems/PersistenceCaptureSystem.java | 2 +- .../systems/PhysicsStoreSystemSupport.java | 18 +++++--- .../systems/SpaceBindingSystem.java | 2 +- .../systems/StaleBodyRemovalSystem.java | 43 +++++++++++++------ .../systems/StepSubmissionSystem.java | 10 ++--- .../systems/TargetBindingSystem.java | 2 +- .../systems/TerrainColliderBindingSystem.java | 2 +- .../systems/WorldCollisionIndexSystem.java | 2 +- 13 files changed, 75 insertions(+), 33 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index b363b4f0..c5bd7c3f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -418,6 +418,16 @@ public void forEachSpaceBinding(@Nonnull SpaceBindingConsumer consumer) { }); } + public void forEachRuntimeSpaceBinding(@Nonnull RuntimeSpaceBindingConsumer consumer) { + spaceHandlesByRef.forEach((spaceRef, spaceHandle) -> { + BackendId backendId = backendIdsBySpaceRef.get(spaceRef); + PhysicsBackendRuntime runtime = backendId != null ? runtimesByBackend.get(backendId) : null; + if (backendId != null && runtime != null) { + consumer.accept(spaceRef, backendId, spaceHandle, runtime); + } + }); + } + public void forEachBodyHandle(@Nonnull BackendSpaceHandle spaceHandle, @Nonnull LongConsumer consumer) { LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); @@ -589,6 +599,15 @@ void accept(@Nonnull UUID spaceUuid, @Nonnull PhysicsBackendRuntime runtime); } + @FunctionalInterface + public interface RuntimeSpaceBindingConsumer { + + void accept(@Nonnull Ref spaceRef, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull PhysicsBackendRuntime runtime); + } + public record BodyHitMetadata(@Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull ShapeType shapeType) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index ba0c0f3f..f2143afd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -225,7 +225,7 @@ private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeReso @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index ea0f1fca..2ed492ac 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -332,7 +332,7 @@ public Set> getDependencies() { @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } private static final class StepBackendEvents { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java index a6bd16d9..93891699 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java @@ -55,7 +55,7 @@ private static void indexChunk(@Nonnull Store store, @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index e45de65b..40c65440 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -220,7 +220,7 @@ private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index 591ee826..16b6757d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -91,7 +91,7 @@ private static Map snapshotBodiesByUuid( @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java index ac21be90..9c7016b8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java @@ -16,22 +16,28 @@ final class PhysicsStoreSystemSupport { static final UUID NIL_UUID = new UUID(0L, 0L); - static final ComponentType UUID_TYPE = - UuidComponent.getComponentType(); - static final Query UUID_QUERY = UUID_TYPE; - private PhysicsStoreSystemSupport() { } + @Nonnull + static ComponentType uuidType() { + return UuidComponent.getComponentType(); + } + + @Nonnull + static Query uuidQuery() { + return uuidType(); + } + @Nonnull static UUID rowUuid(@Nonnull ArchetypeChunk chunk, int index) { - UuidComponent uuid = chunk.getComponent(index, UUID_TYPE); + UuidComponent uuid = chunk.getComponent(index, uuidType()); return uuid != null ? uuid.getUuid() : NIL_UUID; } @Nonnull static UUID rowUuid(@Nonnull Ref ref) { - UuidComponent uuid = component(ref.getStore(), ref, UUID_TYPE); + UuidComponent uuid = component(ref.getStore(), ref, uuidType()); return uuid != null ? uuid.getUuid() : NIL_UUID; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index 0c2f3df9..99ada072 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -156,7 +156,7 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 600862e6..648ebfd9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -57,11 +57,10 @@ private static void removeStaleBodies(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore) { List staleBodies = new ArrayList<>(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> runtime.forEachBodyHandle(spaceHandle, bodyId -> collectStaleBody(store, runtime, - identity, restore, staleBodies, spaceHandle, @@ -87,10 +86,7 @@ private static void removeStaleBodies(@Nonnull Store store, return; } identity.removeBodyHandle(body.bodyHandle()); - Ref ref = identity.getByUuid(body.bodyUuid()); - if (ref != null) { - identity.removeUuid(body.bodyUuid(), ref); - } + identity.removeUuid(body.bodyUuid(), body.bodyRef()); snapshots.removeBody(body.bodyUuid()); registrations.removeBody(RigidBodyKey.of(body.bodyUuid())); runtime.removeBodyHandle(body.bodyUuid()); @@ -105,7 +101,7 @@ private static boolean removeDependentJoints(@Nonnull Store store, if (staleBodyUuids.isEmpty()) { return true; } - for (BoundJoint joint : collectDependentJoints(store, staleBodyUuids)) { + for (BoundJoint joint : collectDependentJoints(store, identity, staleBodyUuids)) { BackendJointHandle jointHandle = runtime.getJointHandle(joint.jointUuid()); if (jointHandle != null) { BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(joint.jointUuid()); @@ -125,7 +121,7 @@ private static boolean removeDependentJoints(@Nonnull Store store, identity.removeJointHandle(jointHandle); } runtime.removeJointHandle(joint.jointUuid()); - if (joint.ref().isValid()) { + if (joint.removeRow() && joint.ref().isValid()) { identity.removeUuid(joint.jointUuid(), joint.ref()); store.removeEntity(joint.ref(), store.getRegistry().newHolder(), @@ -137,6 +133,7 @@ private static boolean removeDependentJoints(@Nonnull Store store, @Nonnull private static List collectDependentJoints(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Set staleBodyUuids) { ConcurrentLinkedQueue joints = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { @@ -150,16 +147,34 @@ private static List collectDependentJoints(@Nonnull Store(joints); } + private static boolean shouldRemoveJointRow(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull JointComponent joint, + @Nonnull Set staleBodyUuids) { + return endpointRemoved(identity, joint.getBodyAUuid(), joint.getBodyARef(), staleBodyUuids) + || endpointRemoved(identity, joint.getBodyBUuid(), joint.getBodyBRef(), staleBodyUuids); + } + + private static boolean endpointRemoved(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid, + Ref currentRef, + @Nonnull Set staleBodyUuids) { + if (!staleBodyUuids.contains(bodyUuid)) { + return false; + } + return PhysicsStoreSystemSupport.resolvedRef(identity, bodyUuid, currentRef) == null; + } + private static void collectStaleBody(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List staleBodies, @Nonnull BackendSpaceHandle spaceHandle, @@ -171,14 +186,14 @@ private static void collectStaleBody(@Nonnull Store store, + " has no runtime snapshot metadata"); return; } - Ref ref = PhysicsStoreSystemSupport.refForUuid(identity, metadata.bodyUuid()); BodyComponent body = PhysicsStoreSystemSupport.component(store, - ref, + metadata.bodyRef(), BodyComponent.getComponentType()); if (body != null) { return; } staleBodies.add(new BoundBody(metadata.bodyUuid(), + metadata.bodyRef(), spaceHandle, new BackendBodyHandle(bodyId), backendRuntime)); @@ -191,6 +206,7 @@ public Set> getDependencies() { } private record BoundBody(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { @@ -198,6 +214,7 @@ private record BoundBody(@Nonnull UUID bodyUuid, private record BoundJoint(@Nonnull UUID jointUuid, @Nonnull Ref ref, - @Nonnull UUID spaceUuid) { + @Nonnull UUID spaceUuid, + boolean removeRow) { } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index 112eaa0f..2bcb5931 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -78,7 +78,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } long stepStartNanos = profilingEnabled ? System.nanoTime() : 0L; StepCounters counters = new StepCounters(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { counters.spaceCount++; for (int step = 0; step < steps; step++) { backendRuntime.step(spaceHandle.value(), stepDt); @@ -103,7 +103,7 @@ private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runt simulationSteps, maxStepDt); StepRisk risk = new StepRisk(dt, minimumSteps); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> backendRuntime.snapshotBodies(spaceHandle.value(), bodyIds -> runtime.forEachBodyHandle(spaceHandle, bodyIds::accept), risk)); @@ -113,7 +113,7 @@ private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runt private static void syncContinuousCollisionMode(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, boolean forceDynamicBodies) { - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { return; } @@ -140,7 +140,7 @@ private static boolean authoredContinuousCollision(@Nonnull Store } private static void resetStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) { - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> backendRuntime.resetStepPhaseStats(spaceHandle.value())); } @@ -148,7 +148,7 @@ private static void resetStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) private static PhysicsStepPhaseStats collectStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) { StepPhaseStatsAccumulator stats = new StepPhaseStatsAccumulator(); StepPhaseStatsCapture capture = new StepPhaseStatsCapture(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { capture.reset(); backendRuntime.stepPhaseStats(spaceHandle.value(), capture); stats.add(capture.value()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index 9b944947..dd65d115 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -171,7 +171,7 @@ private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeReso @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index bff84b1f..9b68a45c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -307,7 +307,7 @@ private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeReso @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java index ad5c906c..35c9e67b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java @@ -74,7 +74,7 @@ private static void collectChunk( @Nonnull @Override public Query getQuery() { - return PhysicsStoreSystemSupport.UUID_QUERY; + return PhysicsStoreSystemSupport.uuidQuery(); } @Nonnull From 72f9e3a6a0f1c8c6e17a4efc5979f9704f072728 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:49:13 +0200 Subject: [PATCH 143/534] refactor(physicsstore): bind projections by runtime ref Signed-off-by: Blovien --- .../resources/PhysicsSnapshotResource.java | 30 +++- .../CompletedStepPublicationSystem.java | 3 +- .../PhysicsProjectionIndexResource.java | 153 ++++++++++++++++-- .../PhysicsBodyAttachmentIndexSystem.java | 41 +++-- .../systems/sync/PhysicsSyncSystem.java | 43 ++++- .../projection/BodyAttachmentComponent.java | 19 ++- .../snapshots/PhysicsStoreBodySnapshot.java | 27 +++- 7 files changed, 278 insertions(+), 38 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index f9d1f799..9a34eeba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -35,12 +36,25 @@ public PhysicsStoreBodySnapshot getBody(@Nonnull UUID bodyUuid) { return snapshot.bodiesByUuid().get(bodyUuid); } + @Nullable + public PhysicsStoreBodySnapshot getBody(@Nonnull Ref bodyRef) { + return snapshot.bodiesByRef().get(bodyRef); + } + public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); + Map, PhysicsStoreBodySnapshot> bodiesByRef = + new Object2ObjectOpenHashMap<>(); for (PhysicsStoreBodySnapshot body : frame.bodies()) { bodiesByUuid.put(body.bodyUuid(), body); + Ref bodyRef = body.bodyRef(); + if (bodyRef != null) { + bodiesByRef.put(bodyRef, body); + } } - snapshot = new PublishedSnapshot(frame, Map.copyOf(bodiesByUuid)); + snapshot = new PublishedSnapshot(frame, + Map.copyOf(bodiesByUuid), + Map.copyOf(bodiesByRef)); } public void removeBody(@Nonnull UUID bodyUuid) { @@ -60,18 +74,25 @@ private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, @Nonnull UUID bodyUuid) { List bodies = new ArrayList<>(); Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); + Map, PhysicsStoreBodySnapshot> bodiesByRef = + new Object2ObjectOpenHashMap<>(); for (PhysicsStoreBodySnapshot body : current.frame().bodies()) { if (bodyUuid.equals(body.bodyUuid())) { continue; } bodies.add(body); bodiesByUuid.put(body.bodyUuid(), body); + Ref bodyRef = body.bodyRef(); + if (bodyRef != null) { + bodiesByRef.put(bodyRef, body); + } } return new PublishedSnapshot( new PhysicsStoreSnapshotFrame(current.frame().sequence(), current.frame().dt(), bodies), - Map.copyOf(bodiesByUuid)); + Map.copyOf(bodiesByUuid), + Map.copyOf(bodiesByRef)); } @Nonnull @@ -89,9 +110,10 @@ public static ResourceType getResourceTyp private record PublishedSnapshot( @Nonnull PhysicsStoreSnapshotFrame frame, - @Nonnull Map bodiesByUuid) { + @Nonnull Map bodiesByUuid, + @Nonnull Map, PhysicsStoreBodySnapshot> bodiesByRef) { private static final PublishedSnapshot EMPTY = - new PublishedSnapshot(PhysicsStoreSnapshotFrame.EMPTY, Map.of()); + new PublishedSnapshot(PhysicsStoreSnapshotFrame.EMPTY, Map.of(), Map.of()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 2ed492ac..e0092351 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -167,7 +167,8 @@ private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, return; } snapshotBodyUuids.add(metadata.bodyUuid()); - bodies.add(new PhysicsStoreBodySnapshot(metadata.bodyUuid(), + bodies.add(new PhysicsStoreBodySnapshot(metadata.bodyRef(), + metadata.bodyUuid(), metadata.spaceUuid(), BackendRuntimeCodes.bodyType(bodyTypeCode), new Vector3f(positionX, positionY, positionZ), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index 1caf3d4b..0361dacc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.ImpulsePlugin; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -24,32 +25,66 @@ public final class PhysicsProjectionIndexResource implements Resource>> bodyAttachments = new Object2ObjectOpenHashMap<>(); + private final Map, Set>> bodyAttachmentsByRef = + new Object2ObjectOpenHashMap<>(); private final Map> generatedVisualProxies = new Object2ObjectOpenHashMap<>(); + private final Map, Ref> generatedVisualProxiesByRef = + new Object2ObjectOpenHashMap<>(); public synchronized void registerAttachment(@Nonnull UUID bodyUuid, + @Nonnull Ref attachment) { + registerAttachment(bodyUuid, null, attachment); + } + + public synchronized void registerAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, @Nonnull Ref attachment) { bodyAttachments.computeIfAbsent(bodyUuid, _ -> new ObjectOpenHashSet<>()) .add(attachment); + if (bodyRef != null) { + bodyAttachmentsByRef.computeIfAbsent(bodyRef, _ -> new ObjectOpenHashSet<>()) + .add(attachment); + } + } + + public synchronized void unregisterAttachment(@Nonnull UUID bodyUuid, + @Nonnull Ref attachment) { + unregisterAttachment(bodyUuid, null, attachment); } public synchronized void unregisterAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, @Nonnull Ref attachment) { Set> attachments = bodyAttachments.get(bodyUuid); - if (attachments == null) { - return; + if (attachments != null) { + attachments.remove(attachment); + if (attachments.isEmpty()) { + bodyAttachments.remove(bodyUuid); + } } - attachments.remove(attachment); - if (attachments.isEmpty()) { - bodyAttachments.remove(bodyUuid); + if (bodyRef != null) { + unregisterAttachmentRef(bodyRef, attachment); } } @Nonnull public Collection> getAttachments(@Nonnull UUID bodyUuid) { + return liveAttachments(bodyAttachments, bodyUuid); + } + + @Nonnull + public Collection> getAttachments(@Nonnull Ref bodyRef) { + return liveAttachments(bodyAttachmentsByRef, bodyRef); + } + + @Nonnull + private Collection> liveAttachments( + @Nonnull Map>> attachmentsByKey, + @Nonnull K key) { List> liveAttachments = new ArrayList<>(); synchronized (this) { - Set> attachments = bodyAttachments.get(bodyUuid); + Set> attachments = attachmentsByKey.get(key); if (attachments == null || attachments.isEmpty()) { return List.of(); } @@ -62,15 +97,24 @@ public Collection> getAttachments(@Nonnull UUID bodyUuid) { } } if (attachments.isEmpty()) { - bodyAttachments.remove(bodyUuid); + attachmentsByKey.remove(key); } } return liveAttachments; } public boolean hasAttachments(@Nonnull UUID bodyUuid) { + return hasLiveAttachments(bodyAttachments, bodyUuid); + } + + public boolean hasAttachments(@Nonnull Ref bodyRef) { + return hasLiveAttachments(bodyAttachmentsByRef, bodyRef); + } + + private boolean hasLiveAttachments(@Nonnull Map>> attachmentsByKey, + @Nonnull K key) { synchronized (this) { - Set> attachments = bodyAttachments.get(bodyUuid); + Set> attachments = attachmentsByKey.get(key); if (attachments == null || attachments.isEmpty()) { return false; } @@ -84,7 +128,7 @@ public boolean hasAttachments(@Nonnull UUID bodyUuid) { } } if (attachments.isEmpty()) { - bodyAttachments.remove(bodyUuid); + attachmentsByKey.remove(key); } return hasLiveAttachment; } @@ -92,20 +136,41 @@ public boolean hasAttachments(@Nonnull UUID bodyUuid) { @Nullable public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid) { + return liveGeneratedVisualProxy(generatedVisualProxies, bodyUuid); + } + + @Nullable + public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { + return liveGeneratedVisualProxy(generatedVisualProxiesByRef, bodyRef); + } + + @Nullable + private Ref liveGeneratedVisualProxy( + @Nonnull Map> proxiesByKey, + @Nonnull K key) { synchronized (this) { - Ref proxy = generatedVisualProxies.get(bodyUuid); + Ref proxy = proxiesByKey.get(key); if (proxy != null && proxy.isValid()) { return proxy; } - generatedVisualProxies.remove(bodyUuid); + proxiesByKey.remove(key); return null; } } public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nonnull Ref proxy) { + setGeneratedVisualProxy(bodyUuid, null, proxy); + } + + public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, @Nonnull Ref proxy) { synchronized (this) { generatedVisualProxies.put(bodyUuid, proxy); + if (bodyRef != null) { + generatedVisualProxiesByRef.put(bodyRef, proxy); + } } } @@ -116,12 +181,46 @@ public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid) { } public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nonnull Ref expectedProxy) { + clearGeneratedVisualProxy(bodyUuid, null, expectedProxy); + } + + public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, @Nonnull Ref expectedProxy) { synchronized (this) { Ref proxy = generatedVisualProxies.get(bodyUuid); if (sameRef(proxy, expectedProxy)) { generatedVisualProxies.remove(bodyUuid); } + if (bodyRef != null) { + clearGeneratedVisualProxyRef(bodyRef, expectedProxy); + } + } + } + + public void updateAttachmentBodyRef(@Nonnull UUID bodyUuid, + @Nullable Ref oldBodyRef, + @Nullable Ref newBodyRef, + @Nonnull Ref attachment, + boolean generatedProxy) { + synchronized (this) { + if (sameRef(oldBodyRef, newBodyRef)) { + return; + } + if (oldBodyRef != null) { + unregisterAttachmentRef(oldBodyRef, attachment); + if (generatedProxy) { + clearGeneratedVisualProxyRef(oldBodyRef, attachment); + } + } + if (newBodyRef != null) { + bodyAttachmentsByRef.computeIfAbsent(newBodyRef, _ -> new ObjectOpenHashSet<>()) + .add(attachment); + if (generatedProxy) { + generatedVisualProxiesByRef.put(newBodyRef, attachment); + } + } } } @@ -133,7 +232,13 @@ public PhysicsProjectionIndexResource clone() { for (Map.Entry>> entry : bodyAttachments.entrySet()) { copy.bodyAttachments.put(entry.getKey(), new ObjectOpenHashSet<>(entry.getValue())); } + for (Map.Entry, Set>> entry : + bodyAttachmentsByRef.entrySet()) { + copy.bodyAttachmentsByRef.put(entry.getKey(), + new ObjectOpenHashSet<>(entry.getValue())); + } copy.generatedVisualProxies.putAll(generatedVisualProxies); + copy.generatedVisualProxiesByRef.putAll(generatedVisualProxiesByRef); } return copy; } @@ -142,8 +247,28 @@ public static ResourceType getResou return ImpulsePlugin.get().getPhysicsProjectionIndexResourceType(); } - private static boolean sameRef(@Nullable Ref first, - @Nonnull Ref second) { - return first != null && (first == second || first.equals(second)); + private void unregisterAttachmentRef(@Nonnull Ref bodyRef, + @Nonnull Ref attachment) { + Set> attachments = bodyAttachmentsByRef.get(bodyRef); + if (attachments == null) { + return; + } + attachments.remove(attachment); + if (attachments.isEmpty()) { + bodyAttachmentsByRef.remove(bodyRef); + } + } + + private void clearGeneratedVisualProxyRef(@Nonnull Ref bodyRef, + @Nonnull Ref expectedProxy) { + Ref proxy = generatedVisualProxiesByRef.get(bodyRef); + if (sameRef(proxy, expectedProxy)) { + generatedVisualProxiesByRef.remove(bodyRef); + } + } + + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second || (first != null && first.equals(second)); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index af98d8cc..d120a9fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -7,6 +7,7 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefChangeSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; @@ -56,25 +57,32 @@ private static void updateAttachment(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer) { UUID oldUuid = oldComponent.getBodyUuid(); UUID newUuid = newComponent.getBodyUuid(); + Ref oldBodyRef = oldComponent.getBodyRef(); + Ref newBodyRef = newComponent.getBodyRef(); boolean sameUuid = oldUuid.equals(newUuid); + boolean sameBodyRef = sameRef(oldBodyRef, newBodyRef); boolean oldGeneratedProxy = oldComponent.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY; boolean newGeneratedProxy = newComponent.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY; - if (sameUuid && oldGeneratedProxy == newGeneratedProxy) { + if (sameUuid && sameBodyRef && oldGeneratedProxy == newGeneratedProxy) { return; } PhysicsProjectionIndexResource resource = commandBuffer.getResource( PhysicsProjectionIndexResource.getResourceType()); if (!sameUuid) { - resource.unregisterAttachment(oldUuid, ref); - resource.registerAttachment(newUuid, ref); + resource.unregisterAttachment(oldUuid, oldBodyRef, ref); + resource.registerAttachment(newUuid, newBodyRef, ref); + } else if (!sameBodyRef) { + resource.updateAttachmentBodyRef(newUuid, + oldBodyRef, + newBodyRef, + ref, + newGeneratedProxy); } - if (!sameUuid || oldGeneratedProxy != newGeneratedProxy) { - if (oldGeneratedProxy) { - resource.clearGeneratedVisualProxy(oldUuid, ref); - } - if (newGeneratedProxy) { - resource.setGeneratedVisualProxy(newUuid, ref); - } + if (oldGeneratedProxy && (!sameUuid || !sameBodyRef || !newGeneratedProxy)) { + resource.clearGeneratedVisualProxy(oldUuid, oldBodyRef, ref); + } + if (newGeneratedProxy && (!sameUuid || !sameBodyRef || !oldGeneratedProxy)) { + resource.setGeneratedVisualProxy(newUuid, newBodyRef, ref); } } @@ -84,9 +92,9 @@ private static void registerAttachment(@Nonnull Ref ref, UUID bodyUuid = component.getBodyUuid(); PhysicsProjectionIndexResource resource = commandBuffer.getResource( PhysicsProjectionIndexResource.getResourceType()); - resource.registerAttachment(bodyUuid, ref); + resource.registerAttachment(bodyUuid, component.getBodyRef(), ref); if (component.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { - resource.setGeneratedVisualProxy(bodyUuid, ref); + resource.setGeneratedVisualProxy(bodyUuid, component.getBodyRef(), ref); } } @@ -96,12 +104,17 @@ private static void unregisterAttachment(@Nonnull Ref ref, UUID bodyUuid = component.getBodyUuid(); PhysicsProjectionIndexResource resource = commandBuffer.getResource( PhysicsProjectionIndexResource.getResourceType()); - resource.unregisterAttachment(bodyUuid, ref); + resource.unregisterAttachment(bodyUuid, component.getBodyRef(), ref); if (component.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { - resource.clearGeneratedVisualProxy(bodyUuid, ref); + resource.clearGeneratedVisualProxy(bodyUuid, component.getBodyRef(), ref); } } + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second || (first != null && first.equals(second)); + } + @Nonnull @Override public ComponentType componentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 5ed63f0d..f93e0633 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -22,6 +22,7 @@ import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; @@ -36,7 +37,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.List; import java.util.Set; -import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; @@ -140,9 +140,11 @@ public void tick(float dt, if (collector != null) { collector.incrementBodiesInspected(); } - UUID bodyUuid = attachment.getBodyUuid(); PhysicsSnapshotResource snapshotResource = physicsStoreSnapshots.get(); - PhysicsStoreBodySnapshot physicsStoreSnapshot = snapshotResource.getBody(bodyUuid); + PhysicsStoreBodySnapshot physicsStoreSnapshot = resolvePhysicsStoreSnapshot(entityRef, + attachment, + snapshotResource, + store); if (physicsStoreSnapshot != null) { if (!PhysicsTransformAuthority.shouldApplyBodyTransform(attachment)) { return; @@ -156,6 +158,41 @@ public void tick(float dt, clearMissingPhysicsStoreAttachment(entityRef, attachment, commandBuffer); } + @Nullable + private static PhysicsStoreBodySnapshot resolvePhysicsStoreSnapshot( + @Nonnull Ref entityRef, + @Nonnull BodyAttachmentComponent attachment, + @Nonnull PhysicsSnapshotResource snapshotResource, + @Nonnull Store store) { + Ref oldBodyRef = attachment.getBodyRef(); + PhysicsStoreBodySnapshot snapshot = null; + if (oldBodyRef != null && oldBodyRef.isValid()) { + snapshot = snapshotResource.getBody(oldBodyRef); + if (snapshot != null && !snapshot.bodyUuid().equals(attachment.getBodyUuid())) { + snapshot = null; + } + } + if (snapshot == null) { + snapshot = snapshotResource.getBody(attachment.getBodyUuid()); + } + Ref newBodyRef = snapshot != null ? snapshot.bodyRef() : null; + if (!sameRef(oldBodyRef, newBodyRef)) { + store.getResource(PhysicsProjectionIndexResource.getResourceType()) + .updateAttachmentBodyRef(attachment.getBodyUuid(), + oldBodyRef, + newBodyRef, + entityRef, + attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY); + attachment.setBodyRef(newBodyRef); + } + return snapshot; + } + + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second || (first != null && first.equals(second)); + } + @Nonnull private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( @Nonnull Store store) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java index 133e7296..4e6f021c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java @@ -6,8 +6,10 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; import java.util.Objects; @@ -67,6 +69,9 @@ public class BodyAttachmentComponent implements Component { @Nonnull private UUID bodyUuid = UUID.randomUUID(); + @Nullable + private transient Ref bodyRef; + @Setter private TransformAuthority transformAuthority = TransformAuthority.BODY; @@ -193,6 +198,16 @@ public UUID getBodyUuid() { public void setBodyUuid(@Nonnull UUID bodyUuid) { this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + bodyRef = null; + } + + @Nullable + public Ref getBodyRef() { + return bodyRef; + } + + public void setBodyRef(@Nullable Ref bodyRef) { + this.bodyRef = bodyRef; } @Nonnull @@ -229,12 +244,14 @@ public static ComponentType getComponentTy @Nonnull @Override public BodyAttachmentComponent clone() { - return new BodyAttachmentComponent(bodyUuid, + BodyAttachmentComponent copy = new BodyAttachmentComponent(bodyUuid, transformAuthority, lifecycle, localPositionOffset, localRotationOffset, visualOriginOffsetY); + copy.bodyRef = bodyRef; + return copy; } private static float normalizeVisualOriginOffsetY(@Nullable Float value) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java index 1bd01817..11e488f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java @@ -1,16 +1,20 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; /** * Copied body snapshot published out of PhysicsStore for projection and queries. */ -public record PhysicsStoreBodySnapshot(@Nonnull UUID bodyUuid, +public record PhysicsStoreBodySnapshot(@Nullable Ref bodyRef, + @Nonnull UUID bodyUuid, @Nonnull UUID spaceUuid, @Nonnull PhysicsBodyType bodyType, @Nonnull Vector3f position, @@ -20,6 +24,27 @@ public record PhysicsStoreBodySnapshot(@Nonnull UUID bodyUuid, float centerOfMassOffsetY, boolean sleeping) { + public PhysicsStoreBodySnapshot(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + float centerOfMassOffsetY, + boolean sleeping) { + this(null, + bodyUuid, + spaceUuid, + bodyType, + position, + rotation, + linearVelocity, + angularVelocity, + centerOfMassOffsetY, + sleeping); + } + public PhysicsStoreBodySnapshot { Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(spaceUuid, "spaceUuid"); From 8456a24e3500c15f96f66b708bd3d66a532d9878 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:55:12 +0200 Subject: [PATCH 144/534] refactor(physicsstore): bind joints by runtime ref Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 61 +++++++++++++++++++ .../systems/JointBindingSystem.java | 21 ++++--- .../systems/StaleBodyRemovalSystem.java | 12 +++- 3 files changed, 81 insertions(+), 13 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index c5bd7c3f..be1b8beb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -72,6 +72,15 @@ public final class PhysicsRuntimeResource implements Resource { private final Map jointSpaceHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map> jointRefsByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, BackendJointHandle> jointHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, BackendSpaceHandle> jointSpaceHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Map terrainBodyHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull @@ -310,8 +319,32 @@ public List drainPendingBodyOperations() { public void putJointHandle(@Nonnull UUID jointUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendJointHandle handle) { + putJointHandle(jointUuid, null, spaceHandle, handle); + } + + public void putJointHandle(@Nonnull UUID jointUuid, + @Nullable Ref jointRef, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendJointHandle handle) { + Ref previousRef = jointRefsByUuid.remove(jointUuid); + if (previousRef != null) { + jointHandlesByRef.remove(previousRef); + jointSpaceHandlesByRef.remove(previousRef); + } jointHandlesByUuid.put(jointUuid, handle); jointSpaceHandlesByUuid.put(jointUuid, spaceHandle); + if (jointRef != null) { + jointRefsByUuid.put(jointUuid, jointRef); + jointHandlesByRef.put(jointRef, handle); + jointSpaceHandlesByRef.put(jointRef, spaceHandle); + } + } + + public void putJointHandle(@Nonnull Ref jointRef, + @Nonnull UUID jointUuid, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendJointHandle handle) { + putJointHandle(jointUuid, jointRef, spaceHandle, handle); } @Nullable @@ -319,14 +352,36 @@ public BackendJointHandle getJointHandle(@Nonnull UUID jointUuid) { return jointHandlesByUuid.get(jointUuid); } + @Nullable + public BackendJointHandle getJointHandle(@Nonnull Ref jointRef) { + return jointHandlesByRef.get(jointRef); + } + @Nullable public BackendSpaceHandle getJointSpaceHandle(@Nonnull UUID jointUuid) { return jointSpaceHandlesByUuid.get(jointUuid); } + @Nullable + public BackendSpaceHandle getJointSpaceHandle(@Nonnull Ref jointRef) { + return jointSpaceHandlesByRef.get(jointRef); + } + public void removeJointHandle(@Nonnull UUID jointUuid) { jointHandlesByUuid.remove(jointUuid); jointSpaceHandlesByUuid.remove(jointUuid); + Ref jointRef = jointRefsByUuid.remove(jointUuid); + if (jointRef != null) { + jointHandlesByRef.remove(jointRef); + jointSpaceHandlesByRef.remove(jointRef); + } + } + + public void removeJointHandle(@Nonnull UUID jointUuid, + @Nonnull Ref jointRef) { + removeJointHandle(jointUuid); + jointHandlesByRef.remove(jointRef); + jointSpaceHandlesByRef.remove(jointRef); } @Nonnull @@ -450,6 +505,9 @@ public void clear() { bodySpaceHandlesByRef.clear(); jointHandlesByUuid.clear(); jointSpaceHandlesByUuid.clear(); + jointRefsByUuid.clear(); + jointHandlesByRef.clear(); + jointSpaceHandlesByRef.clear(); terrainBodyHandlesByUuid.clear(); terrainVoxelBodyHandlesByUuid.clear(); terrainSpaceHandlesByUuid.clear(); @@ -570,6 +628,9 @@ public PhysicsRuntimeResource clone() { copy.bodySpaceHandlesByRef.putAll(bodySpaceHandlesByRef); copy.jointHandlesByUuid.putAll(jointHandlesByUuid); copy.jointSpaceHandlesByUuid.putAll(jointSpaceHandlesByUuid); + copy.jointRefsByUuid.putAll(jointRefsByUuid); + copy.jointHandlesByRef.putAll(jointHandlesByRef); + copy.jointSpaceHandlesByRef.putAll(jointSpaceHandlesByRef); terrainBodyHandlesByUuid.forEach((terrainUuid, bodyHandles) -> copy.terrainBodyHandlesByUuid.put(terrainUuid, new LongArrayList(bodyHandles))); copy.terrainVoxelBodyHandlesByUuid.putAll(terrainVoxelBodyHandlesByUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index 40c65440..3f9592e7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -66,18 +66,19 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, if (PhysicsStoreSystemSupport.isNil(jointUuid)) { continue; } + Ref jointRef = chunk.getReferenceTo(index); if (!joint.isEnabled()) { - removeJoint(runtime, identity, jointUuid, joint); + removeJoint(runtime, identity, jointRef, jointUuid); continue; } - BackendJointHandle existing = runtime.getJointHandle(jointUuid); + BackendJointHandle existing = runtime.getJointHandle(jointRef); if (existing != null) { if (!endpointsBound(runtime, identity, joint)) { - removeJoint(runtime, identity, jointUuid, joint); + removeJoint(runtime, identity, jointRef, jointUuid); } continue; } - bindJoint(runtime, identity, restore, chunk.getReferenceTo(index), jointUuid, joint); + bindJoint(runtime, identity, restore, jointRef, jointUuid, joint); } } @@ -135,7 +136,7 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, joint.getMotorTargetVelocity(), joint.getMotorMaxForce()); BackendJointHandle handle = new BackendJointHandle(jointId); - runtime.putJointHandle(jointUuid, spaceHandle, handle); + runtime.putJointHandle(jointRef, jointUuid, spaceHandle, handle); identity.putJointHandle(handle, jointRef); } catch (RuntimeException exception) { if (jointId != Long.MIN_VALUE) { @@ -202,19 +203,19 @@ private static Ref resolveBodyBRef(@Nonnull PhysicsIdentityIndexRe private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID jointUuid, - @Nonnull JointComponent joint) { - BackendJointHandle handle = runtime.getJointHandle(jointUuid); + @Nonnull Ref jointRef, + @Nonnull UUID jointUuid) { + BackendJointHandle handle = runtime.getJointHandle(jointRef); if (handle == null) { return; } - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointRef); PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeJoint(spaceHandle.value(), handle.value()); } identity.removeJointHandle(handle); - runtime.removeJointHandle(jointUuid); + runtime.removeJointHandle(jointUuid, jointRef); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 648ebfd9..767f9335 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -102,9 +102,15 @@ private static boolean removeDependentJoints(@Nonnull Store store, return true; } for (BoundJoint joint : collectDependentJoints(store, identity, staleBodyUuids)) { - BackendJointHandle jointHandle = runtime.getJointHandle(joint.jointUuid()); + BackendJointHandle jointHandle = runtime.getJointHandle(joint.ref()); + if (jointHandle == null) { + jointHandle = runtime.getJointHandle(joint.jointUuid()); + } if (jointHandle != null) { - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(joint.jointUuid()); + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(joint.ref()); + if (spaceHandle == null) { + spaceHandle = runtime.getJointSpaceHandle(joint.jointUuid()); + } if (spaceHandle == null) { spaceHandle = runtime.getSpaceHandle(joint.spaceUuid()); } @@ -120,7 +126,7 @@ private static boolean removeDependentJoints(@Nonnull Store store, } identity.removeJointHandle(jointHandle); } - runtime.removeJointHandle(joint.jointUuid()); + runtime.removeJointHandle(joint.jointUuid(), joint.ref()); if (joint.removeRow() && joint.ref().isValid()) { identity.removeUuid(joint.jointUuid(), joint.ref()); store.removeEntity(joint.ref(), From 0d70f3386cb2a5d0ecb0b6d4996a331dd29f0bc1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:56:50 +0200 Subject: [PATCH 145/534] refactor(physicsstore): check body bindings by ref Signed-off-by: Blovien --- .../internal/physicsstore/systems/BodyBindingSystem.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index f2143afd..4cbd676f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -70,14 +70,17 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, continue; } UUID bodyUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (PhysicsStoreSystemSupport.isNil(bodyUuid) - || runtime.getBodyHandle(bodyUuid) != null) { + if (PhysicsStoreSystemSupport.isNil(bodyUuid)) { + continue; + } + Ref bodyRef = chunk.getReferenceTo(index); + if (runtime.getBodyHandle(bodyRef) != null) { continue; } bindBody(runtime, identity, restore, - chunk.getReferenceTo(index), + bodyRef, bodyUuid, body, chunk.getComponent(index, DynamicsComponent.getComponentType()), From 53d5eeecd4ed2b755d0bce794014b80ce269163f Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 22:58:32 +0200 Subject: [PATCH 146/534] refactor(physicsstore): check space bindings by ref Signed-off-by: Blovien --- .../physicsstore/systems/SpaceBindingSystem.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index 99ada072..a4e3e39f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -75,8 +75,11 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, continue; } UUID spaceUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (PhysicsStoreSystemSupport.isNil(spaceUuid) - || runtime.getSpaceHandle(spaceUuid) != null) { + if (PhysicsStoreSystemSupport.isNil(spaceUuid)) { + continue; + } + Ref spaceRef = chunk.getReferenceTo(index); + if (runtime.getSpaceHandle(spaceRef) != null) { continue; } bindSpace(runtime, @@ -84,7 +87,7 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, identity, restore, stepMode, - chunk.getReferenceTo(index), + spaceRef, spaceUuid, space, chunk.getComponent(index, SolverSettingsComponent.getComponentType()), From a22038c866c3a0c9b2fc72357a4c4cc8e57c757a Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 15 Jun 2026 23:03:56 +0200 Subject: [PATCH 147/534] refactor(physicsstore): bind terrain by runtime ref Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 133 +++++++++++++++++- .../systems/TerrainColliderBindingSystem.java | 41 +++--- 2 files changed, 154 insertions(+), 20 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index be1b8beb..5668146e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -93,6 +93,21 @@ public final class PhysicsRuntimeResource implements Resource { private final Map terrainPayloadKeysByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map> terrainRefsByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, LongList> terrainBodyHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, BackendBodyHandle> terrainVoxelBodyHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, BackendSpaceHandle> terrainSpaceHandlesByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map, String> terrainPayloadKeysByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap bodyHandlesBySpaceHandle = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -400,37 +415,90 @@ public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle handle, boolean voxelTerrainBody) { + putTerrainBodyHandle(terrainUuid, null, spaceHandle, handle, voxelTerrainBody); + } + + public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, + @Nullable Ref terrainRef, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle, + boolean voxelTerrainBody) { + bindTerrainRef(terrainUuid, terrainRef); terrainSpaceHandlesByUuid.put(terrainUuid, spaceHandle); terrainBodyHandlesByUuid.computeIfAbsent(terrainUuid, _ -> new LongArrayList()) .add(handle.value()); if (voxelTerrainBody) { terrainVoxelBodyHandlesByUuid.put(terrainUuid, handle); } + if (terrainRef != null) { + terrainSpaceHandlesByRef.put(terrainRef, spaceHandle); + terrainBodyHandlesByRef.computeIfAbsent(terrainRef, _ -> new LongArrayList()) + .add(handle.value()); + if (voxelTerrainBody) { + terrainVoxelBodyHandlesByRef.put(terrainRef, handle); + } + } + } + + public void putTerrainBodyHandle(@Nonnull Ref terrainRef, + @Nonnull UUID terrainUuid, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle, + boolean voxelTerrainBody) { + putTerrainBodyHandle(terrainUuid, terrainRef, spaceHandle, handle, voxelTerrainBody); } public void markTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String payloadKey) { terrainPayloadKeysByUuid.put(terrainUuid, payloadKey); } + public void markTerrainPayloadBound(@Nonnull Ref terrainRef, + @Nonnull UUID terrainUuid, + @Nonnull String payloadKey) { + bindTerrainRef(terrainUuid, terrainRef); + terrainPayloadKeysByUuid.put(terrainUuid, payloadKey); + terrainPayloadKeysByRef.put(terrainRef, payloadKey); + } + public boolean isTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String payloadKey) { return payloadKey.equals(terrainPayloadKeysByUuid.get(terrainUuid)); } + public boolean isTerrainPayloadBound(@Nonnull Ref terrainRef, + @Nonnull String payloadKey) { + return payloadKey.equals(terrainPayloadKeysByRef.get(terrainRef)); + } + public boolean hasTerrainBodyHandles(@Nonnull UUID terrainUuid) { LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); return bodyHandles != null && !bodyHandles.isEmpty(); } + public boolean hasTerrainBodyHandles(@Nonnull Ref terrainRef) { + LongList bodyHandles = terrainBodyHandlesByRef.get(terrainRef); + return bodyHandles != null && !bodyHandles.isEmpty(); + } + @Nullable public BackendSpaceHandle getTerrainSpaceHandle(@Nonnull UUID terrainUuid) { return terrainSpaceHandlesByUuid.get(terrainUuid); } + @Nullable + public BackendSpaceHandle getTerrainSpaceHandle(@Nonnull Ref terrainRef) { + return terrainSpaceHandlesByRef.get(terrainRef); + } + @Nullable public BackendBodyHandle getTerrainVoxelBodyHandle(@Nonnull UUID terrainUuid) { return terrainVoxelBodyHandlesByUuid.get(terrainUuid); } + @Nullable + public BackendBodyHandle getTerrainVoxelBodyHandle(@Nonnull Ref terrainRef) { + return terrainVoxelBodyHandlesByRef.get(terrainRef); + } + public void forEachTerrainBodyHandle(@Nonnull UUID terrainUuid, @Nonnull LongConsumer consumer) { LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); @@ -440,15 +508,38 @@ public void forEachTerrainBodyHandle(@Nonnull UUID terrainUuid, bodyHandles.forEach(consumer); } + public void forEachTerrainBodyHandle(@Nonnull Ref terrainRef, + @Nonnull LongConsumer consumer) { + LongList bodyHandles = terrainBodyHandlesByRef.get(terrainRef); + if (bodyHandles == null) { + return; + } + bodyHandles.forEach(consumer); + } + public void removeTerrainHandles(@Nonnull UUID terrainUuid) { - LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); + LongList bodyHandles = terrainBodyHandlesByUuid.remove(terrainUuid); if (bodyHandles != null) { bodyHandles.forEach(bodyHitMetadataByHandle::remove); } - terrainBodyHandlesByUuid.remove(terrainUuid); terrainVoxelBodyHandlesByUuid.remove(terrainUuid); terrainSpaceHandlesByUuid.remove(terrainUuid); terrainPayloadKeysByUuid.remove(terrainUuid); + Ref terrainRef = terrainRefsByUuid.remove(terrainUuid); + if (terrainRef != null) { + removeTerrainRefMaps(terrainRef); + } + } + + public void removeTerrainHandles(@Nonnull UUID terrainUuid, + @Nonnull Ref terrainRef) { + removeTerrainHandles(terrainUuid); + removeTerrainRefMaps(terrainRef); + } + + public void removeTerrainHandles(@Nonnull Ref terrainRef, + @Nonnull UUID terrainUuid) { + removeTerrainHandles(terrainUuid, terrainRef); } @Nonnull @@ -512,6 +603,11 @@ public void clear() { terrainVoxelBodyHandlesByUuid.clear(); terrainSpaceHandlesByUuid.clear(); terrainPayloadKeysByUuid.clear(); + terrainRefsByUuid.clear(); + terrainBodyHandlesByRef.clear(); + terrainVoxelBodyHandlesByRef.clear(); + terrainSpaceHandlesByRef.clear(); + terrainPayloadKeysByRef.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); bodySnapshotMetadataByHandle.clear(); @@ -636,6 +732,12 @@ public PhysicsRuntimeResource clone() { copy.terrainVoxelBodyHandlesByUuid.putAll(terrainVoxelBodyHandlesByUuid); copy.terrainSpaceHandlesByUuid.putAll(terrainSpaceHandlesByUuid); copy.terrainPayloadKeysByUuid.putAll(terrainPayloadKeysByUuid); + copy.terrainRefsByUuid.putAll(terrainRefsByUuid); + terrainBodyHandlesByRef.forEach((terrainRef, bodyHandles) -> + copy.terrainBodyHandlesByRef.put(terrainRef, new LongArrayList(bodyHandles))); + copy.terrainVoxelBodyHandlesByRef.putAll(terrainVoxelBodyHandlesByRef); + copy.terrainSpaceHandlesByRef.putAll(terrainSpaceHandlesByRef); + copy.terrainPayloadKeysByRef.putAll(terrainPayloadKeysByRef); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); @@ -795,7 +897,34 @@ private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandl terrainBodyHandlesByUuid.remove(terrainUuid); terrainVoxelBodyHandlesByUuid.remove(terrainUuid); terrainPayloadKeysByUuid.remove(terrainUuid); + Ref terrainRef = terrainRefsByUuid.remove(terrainUuid); + if (terrainRef != null) { + removeTerrainRefMaps(terrainRef); + } return true; }); } + + private void bindTerrainRef(@Nonnull UUID terrainUuid, + @Nullable Ref terrainRef) { + if (terrainRef == null) { + return; + } + Ref previousRef = terrainRefsByUuid.put(terrainUuid, terrainRef); + if (previousRef != null && !sameRef(previousRef, terrainRef)) { + removeTerrainRefMaps(previousRef); + } + } + + private void removeTerrainRefMaps(@Nonnull Ref terrainRef) { + terrainBodyHandlesByRef.remove(terrainRef); + terrainVoxelBodyHandlesByRef.remove(terrainRef); + terrainSpaceHandlesByRef.remove(terrainRef); + terrainPayloadKeysByRef.remove(terrainRef); + } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first == second || first.equals(second); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 9b68a45c..4928297a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -75,11 +75,12 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, if (PhysicsStoreSystemSupport.isNil(terrainUuid)) { continue; } + Ref terrainRef = chunk.getReferenceTo(index); if (!terrain.isRetained()) { - removeTerrain(runtime, terrainUuid); + removeTerrain(runtime, terrainUuid, terrainRef); continue; } - if (runtime.isTerrainPayloadBound(terrainUuid, terrain.getPayloadResourceKey())) { + if (runtime.isTerrainPayloadBound(terrainRef, terrain.getPayloadResourceKey())) { continue; } TerrainColliderPayload payload = payloads.get(terrain.getPayloadResourceKey()); @@ -91,7 +92,7 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, identity, restore, terrainUuid, - chunk.getReferenceTo(index), + terrainRef, terrain, payload); } @@ -116,8 +117,8 @@ private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, + terrain.getSourceKey()); return; } - if (runtime.hasTerrainBodyHandles(terrainUuid)) { - removeTerrain(runtime, terrainUuid); + if (runtime.hasTerrainBodyHandles(terrainRef)) { + removeTerrain(runtime, terrainUuid, terrainRef); } try { boolean nativeVoxel = payload.nativeVoxelTerrainEnabled() @@ -151,15 +152,17 @@ private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, box, payload); } - if (!runtime.hasTerrainBodyHandles(terrainUuid)) { + if (!runtime.hasTerrainBodyHandles(terrainRef)) { restore.recordSoftSkip("Terrain payload produced no backend bodies: " + terrain.getSourceKey()); return; } - runtime.markTerrainPayloadBound(terrainUuid, terrain.getPayloadResourceKey()); - stitchNeighbors(runtime, backendRuntime, spaceHandle, terrainUuid, terrain, payload); + runtime.markTerrainPayloadBound(terrainRef, + terrainUuid, + terrain.getPayloadResourceKey()); + stitchNeighbors(runtime, backendRuntime, spaceHandle, terrainRef, terrain, payload); } catch (RuntimeException exception) { - removeTerrain(runtime, terrainUuid); + removeTerrain(runtime, terrainUuid, terrainRef); restore.markFailed("PhysicsStore terrain " + terrain.getSourceKey() + " failed backend binding: " + exception.getMessage()); } @@ -185,7 +188,7 @@ private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, payload.collisionGroup(), payload.collisionMask()); BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); - runtime.putTerrainBodyHandle(terrainUuid, spaceHandle, bodyHandle, true); + runtime.putTerrainBodyHandle(terrainRef, terrainUuid, spaceHandle, bodyHandle, true); runtime.putBodyHitMetadata(bodyHandle, terrainRef, PhysicsBodyType.STATIC, @@ -227,7 +230,8 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, payload.collisionGroup(), payload.collisionMask()); BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); - runtime.putTerrainBodyHandle(terrainUuid, + runtime.putTerrainBodyHandle(terrainRef, + terrainUuid, spaceHandle, bodyHandle, false); @@ -240,10 +244,10 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull UUID terrainUuid, + @Nonnull Ref terrainRef, @Nonnull TerrainColliderComponent terrain, @Nonnull TerrainColliderPayload payload) { - BackendBodyHandle voxelBody = runtime.getTerrainVoxelBodyHandle(terrainUuid); + BackendBodyHandle voxelBody = runtime.getTerrainVoxelBodyHandle(terrainRef); if (voxelBody == null) { return; } @@ -267,18 +271,19 @@ private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, } private static void removeTerrain(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID terrainUuid) { - BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(terrainUuid); + @Nonnull UUID terrainUuid, + @Nonnull Ref terrainRef) { + BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(terrainRef); if (spaceHandle == null) { - runtime.removeTerrainHandles(terrainUuid); + runtime.removeTerrainHandles(terrainRef, terrainUuid); return; } PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); if (backendRuntime != null) { - runtime.forEachTerrainBodyHandle(terrainUuid, + runtime.forEachTerrainBodyHandle(terrainRef, bodyId -> backendRuntime.removeBody(spaceHandle.value(), bodyId)); } - runtime.removeTerrainHandles(terrainUuid); + runtime.removeTerrainHandles(terrainRef, terrainUuid); } @Nullable From 110ffadf7bdfe582653d03f1fbb73b7f7321712f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:21:11 +0200 Subject: [PATCH 148/534] refactor(physicsstore): clean topology by runtime refs Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 91 ++++++++----------- .../resources/PhysicsRuntimeResource.java | 84 ++++++++++++++--- 2 files changed, 109 insertions(+), 66 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index e3e032e3..111a3113 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -54,12 +54,7 @@ public static void destroyBody(@Nonnull Store store, PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); List removals = collectRows(store, null, bodyUuid); - for (RowRemoval removal : removals) { - if (removal.kind() == RowKind.JOINT) { - removeRuntimeJoint(runtime, identity, removal.rowUuid()); - } - } - removeRuntimeBody(runtime, identity, bodyUuid); + removeRuntimeRows(runtime, identity, removals); removeRows(store, removals); } @@ -71,10 +66,9 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); TopologyCounts removed = countBackendTopology(runtime); - for (BackendSpaceHandle spaceHandle : spaceHandles(runtime)) { - removeRuntimeContentsForSpace(runtime, identity, spaceHandle); - } - removeRows(store, collectRows(store, null, null)); + List removals = collectRows(store, null, null); + removeRuntimeRows(runtime, identity, removals); + removeRows(store, removals); clearCopiedBodyState(store); store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear(); store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); @@ -98,13 +92,11 @@ public static void removeSpaceWithContents(@Nonnull Store store, PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); - if (spaceHandle != null) { - removeRuntimeContentsForSpace(runtime, identity, spaceHandle); - } + List removals = collectRows(store, spaceUuid, null); + removeRuntimeRows(runtime, identity, removals); store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); - removeRows(store, collectRows(store, spaceUuid, null)); + removeRows(store, removals); PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceUuid); } @@ -113,39 +105,43 @@ public static int clearTerrainForSpace(@Nonnull Store store, PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore terrain rows"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); int removedBodies = 0; - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); - if (spaceHandle != null) { - for (UUID terrainUuid : runtime.terrainUuidsForSpaceHandle(spaceHandle)) { - removedBodies += removeRuntimeTerrain(runtime, terrainUuid); - } + List removals = collectTerrainRows(store, spaceUuid); + for (RowRemoval removal : removals) { + removedBodies += removeRuntimeTerrain(runtime, removal); } store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); - removeRows(store, collectTerrainRows(store, spaceUuid)); + removeRows(store, removals); return removedBodies; } - private static void removeRuntimeContentsForSpace(@Nonnull PhysicsRuntimeResource runtime, + private static void removeRuntimeRows(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull BackendSpaceHandle spaceHandle) { - for (UUID jointUuid : runtime.jointUuidsForSpaceHandle(spaceHandle)) { - removeRuntimeJoint(runtime, identity, jointUuid); + @Nonnull List removals) { + for (RowRemoval removal : removals) { + if (removal.kind() == RowKind.JOINT) { + removeRuntimeJoint(runtime, identity, removal); + } } - for (UUID terrainUuid : runtime.terrainUuidsForSpaceHandle(spaceHandle)) { - removeRuntimeTerrain(runtime, terrainUuid); + for (RowRemoval removal : removals) { + if (removal.kind() == RowKind.TERRAIN) { + removeRuntimeTerrain(runtime, removal); + } } - for (UUID bodyUuid : runtime.bodyUuidsForSpaceHandle(spaceHandle)) { - removeRuntimeBody(runtime, identity, bodyUuid); + for (RowRemoval removal : removals) { + if (removal.kind() == RowKind.BODY) { + removeRuntimeBody(runtime, identity, removal); + } } } private static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID jointUuid) { - BackendJointHandle jointHandle = runtime.getJointHandle(jointUuid); - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointUuid); + @Nonnull RowRemoval removal) { + BackendJointHandle jointHandle = runtime.getJointHandle(removal.ref()); + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(removal.ref()); if (jointHandle == null) { - runtime.removeJointHandle(jointUuid); + runtime.removeJointHandle(removal.rowUuid(), removal.ref()); return false; } PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); @@ -153,15 +149,15 @@ private static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtim backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); } identity.removeJointHandle(jointHandle); - runtime.removeJointHandle(jointUuid); + runtime.removeJointHandle(removal.rowUuid(), removal.ref()); return true; } private static int removeRuntimeTerrain(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID terrainUuid) { - BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(terrainUuid); + @Nonnull RowRemoval removal) { + BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(removal.ref()); LongArrayList bodyHandles = new LongArrayList(); - runtime.forEachTerrainBodyHandle(terrainUuid, bodyId -> bodyHandles.add(bodyId)); + runtime.forEachTerrainBodyHandle(removal.ref(), bodyId -> bodyHandles.add(bodyId)); if (spaceHandle != null) { PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); if (backendRuntime != null) { @@ -170,17 +166,17 @@ private static int removeRuntimeTerrain(@Nonnull PhysicsRuntimeResource runtime, } } } - runtime.removeTerrainHandles(terrainUuid); + runtime.removeTerrainHandles(removal.ref(), removal.rowUuid()); return bodyHandles.size(); } private static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID bodyUuid) { - BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyUuid); - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyUuid); + @Nonnull RowRemoval removal) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(removal.ref()); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(removal.ref()); if (bodyHandle == null) { - runtime.removeBodyHandle(bodyUuid); + runtime.removeBodyHandle(removal.rowUuid(), removal.ref()); return false; } PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); @@ -188,21 +184,14 @@ private static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); } identity.removeBodyHandle(bodyHandle); - runtime.removeBodyHandle(bodyUuid); + runtime.removeBodyHandle(removal.rowUuid(), removal.ref()); return true; } - @Nonnull - private static List spaceHandles(@Nonnull PhysicsRuntimeResource runtime) { - List handles = new ArrayList<>(); - runtime.forEachSpaceBinding((_, _, spaceHandle, _) -> handles.add(spaceHandle)); - return handles; - } - @Nonnull private static TopologyCounts countBackendTopology(@Nonnull PhysicsRuntimeResource runtime) { TopologyCounts counts = new TopologyCounts(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { counts.addBodies(backendRuntime.bodyCount(spaceHandle.value())); counts.addJoints(backendRuntime.jointCount(spaceHandle.value())); }); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 5668146e..caab9cd9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -243,21 +243,16 @@ public BackendSpaceHandle getBodySpaceHandle(@Nonnull Ref bodyRef) public void removeBodyHandle(@Nonnull UUID bodyUuid) { BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); - if (removed != null && spaceHandle != null) { - LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); - if (bodyHandles != null) { - bodyHandles.rem(removed.value()); - if (bodyHandles.isEmpty()) { - bodyHandlesBySpaceHandle.remove(spaceHandle.value()); - } - } - bodyHitMetadataByHandle.remove(removed.value()); - BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(removed.value()); - if (metadata != null) { - bodyHandlesByRef.remove(metadata.bodyRef()); - bodySpaceHandlesByRef.remove(metadata.bodyRef()); - } - } + removeBodyHandleIndexes(removed, spaceHandle); + } + + public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { + BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); + BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); + BackendBodyHandle removedByRef = bodyHandlesByRef.remove(bodyRef); + BackendSpaceHandle spaceHandleByRef = bodySpaceHandlesByRef.remove(bodyRef); + removeBodyHandleIndexes(removed != null ? removed : removedByRef, + spaceHandle != null ? spaceHandle : spaceHandleByRef); } @Nonnull @@ -272,6 +267,19 @@ public List bodyUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandl return bodyUuids; } + @Nonnull + public List> bodyRefsForSpaceHandle( + @Nonnull BackendSpaceHandle spaceHandle) { + List> bodyRefs = new ArrayList<>(); + int targetSpaceHandle = spaceHandle.value(); + bodySpaceHandlesByRef.forEach((bodyRef, handle) -> { + if (handle.value() == targetSpaceHandle) { + bodyRefs.add(bodyRef); + } + }); + return bodyRefs; + } + public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, @Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @@ -411,6 +419,19 @@ public List jointUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHand return jointUuids; } + @Nonnull + public List> jointRefsForSpaceHandle( + @Nonnull BackendSpaceHandle spaceHandle) { + List> jointRefs = new ArrayList<>(); + int targetSpaceHandle = spaceHandle.value(); + jointSpaceHandlesByRef.forEach((jointRef, handle) -> { + if (handle.value() == targetSpaceHandle) { + jointRefs.add(jointRef); + } + }); + return jointRefs; + } + public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle handle, @@ -554,6 +575,19 @@ public List terrainUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHa return terrainUuids; } + @Nonnull + public List> terrainRefsForSpaceHandle( + @Nonnull BackendSpaceHandle spaceHandle) { + List> terrainRefs = new ArrayList<>(); + int targetSpaceHandle = spaceHandle.value(); + terrainSpaceHandlesByRef.forEach((terrainRef, handle) -> { + if (handle.value() == targetSpaceHandle) { + terrainRefs.add(terrainRef); + } + }); + return terrainRefs; + } + public void forEachSpaceBinding(@Nonnull SpaceBindingConsumer consumer) { spaceHandlesByUuid.forEach((spaceUuid, spaceHandle) -> { BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); @@ -620,6 +654,26 @@ public void clearTransientBodyOperations() { pendingBodyOperations.clear(); } + private void removeBodyHandleIndexes(@Nullable BackendBodyHandle removed, + @Nullable BackendSpaceHandle spaceHandle) { + if (removed == null || spaceHandle == null) { + return; + } + LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); + if (bodyHandles != null) { + bodyHandles.rem(removed.value()); + if (bodyHandles.isEmpty()) { + bodyHandlesBySpaceHandle.remove(spaceHandle.value()); + } + } + bodyHitMetadataByHandle.remove(removed.value()); + BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(removed.value()); + if (metadata != null) { + bodyHandlesByRef.remove(metadata.bodyRef()); + bodySpaceHandlesByRef.remove(metadata.bodyRef()); + } + } + public void destroyBackendBindings() { RuntimeException failure = null; for (Map.Entry entry From 6ca230b91d6a1c0e6f532aa80a7b610788f37d04 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:23:15 +0200 Subject: [PATCH 149/534] refactor(physicsstore): resolve space cleanup by ref Signed-off-by: Blovien --- .../internal/physicsstore/PhysicsStoreSpaceMutations.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index b56efc87..a123f608 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -157,9 +157,12 @@ public static void removeEmptySpace(@Nonnull Store store, store.getResource(PhysicsIdentityIndexResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - BackendSpaceHandle handle = runtime.getSpaceHandle(spaceUuid); + Ref ref = identity.getByUuid(spaceUuid); + BackendSpaceHandle handle = ref != null && ref.isValid() + ? runtime.getSpaceHandle(ref) + : null; if (handle != null) { - BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + BackendId backendId = runtime.getSpaceBackendId(ref); PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; if (backendRuntime == null) { @@ -175,7 +178,6 @@ public static void removeEmptySpace(@Nonnull Store store, runtime.removeSpaceHandle(spaceUuid); } compatibility.removeBySpaceUuid(spaceUuid); - Ref ref = identity.getByUuid(spaceUuid); if (ref != null && ref.isValid()) { identity.removeUuid(spaceUuid, ref); store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); From b6518964bf2465290f666513a515f3223b8940af Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:25:10 +0200 Subject: [PATCH 150/534] refactor(physicsstore): remove stale joint uuid fallback Signed-off-by: Blovien --- .../systems/StaleBodyRemovalSystem.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 767f9335..c2d45371 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -103,17 +103,8 @@ private static boolean removeDependentJoints(@Nonnull Store store, } for (BoundJoint joint : collectDependentJoints(store, identity, staleBodyUuids)) { BackendJointHandle jointHandle = runtime.getJointHandle(joint.ref()); - if (jointHandle == null) { - jointHandle = runtime.getJointHandle(joint.jointUuid()); - } if (jointHandle != null) { BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(joint.ref()); - if (spaceHandle == null) { - spaceHandle = runtime.getJointSpaceHandle(joint.jointUuid()); - } - if (spaceHandle == null) { - spaceHandle = runtime.getSpaceHandle(joint.spaceUuid()); - } PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); if (spaceHandle != null && backendRuntime != null) { try { @@ -154,10 +145,7 @@ private static List collectDependentJoints(@Nonnull Store(joints); } @@ -220,7 +208,6 @@ private record BoundBody(@Nonnull UUID bodyUuid, private record BoundJoint(@Nonnull UUID jointUuid, @Nonnull Ref ref, - @Nonnull UUID spaceUuid, boolean removeRow) { } } From a5ae03e4e9ac80791cd8089ecfee6712958b9743 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:31:26 +0200 Subject: [PATCH 151/534] refactor(physicsstore): resolve backend access by ref Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 13 ++++++++ .../PhysicsStoreBackendAccess.java | 19 +++++++----- .../physicsstore/PhysicsStoreDiagnostics.java | 30 +++++++------------ 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index caab9cd9..03822a31 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -48,6 +48,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Map> spaceRefsByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map, UUID> spaceUuidsByRef = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Map, BackendSpaceHandle> spaceHandlesByRef = new Object2ObjectOpenHashMap<>(); @Nonnull @@ -149,6 +152,7 @@ public void putSpaceBinding(@Nonnull UUID spaceUuid, @Nonnull BackendSpaceHandle handle) { Ref previousRef = spaceRefsByUuid.remove(spaceUuid); if (previousRef != null) { + spaceUuidsByRef.remove(previousRef); backendIdsBySpaceRef.remove(previousRef); spaceHandlesByRef.remove(previousRef); } @@ -156,6 +160,7 @@ public void putSpaceBinding(@Nonnull UUID spaceUuid, spaceHandlesByUuid.put(spaceUuid, handle); if (spaceRef != null) { spaceRefsByUuid.put(spaceUuid, spaceRef); + spaceUuidsByRef.put(spaceRef, spaceUuid); backendIdsBySpaceRef.put(spaceRef, backendId); spaceHandlesByRef.put(spaceRef, handle); } @@ -171,6 +176,11 @@ public BackendSpaceHandle getSpaceHandle(@Nonnull Ref spaceRef) { return spaceHandlesByRef.get(spaceRef); } + @Nullable + public UUID getSpaceUuid(@Nonnull Ref spaceRef) { + return spaceUuidsByRef.get(spaceRef); + } + @Nullable public BackendId getSpaceBackendId(@Nonnull UUID spaceUuid) { return backendIdsBySpaceUuid.get(spaceUuid); @@ -186,6 +196,7 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { backendIdsBySpaceUuid.remove(spaceUuid); Ref spaceRef = spaceRefsByUuid.remove(spaceUuid); if (spaceRef != null) { + spaceUuidsByRef.remove(spaceRef); spaceHandlesByRef.remove(spaceRef); backendIdsBySpaceRef.remove(spaceRef); } @@ -622,6 +633,7 @@ public void clear() { spaceHandlesByUuid.clear(); backendIdsBySpaceUuid.clear(); spaceRefsByUuid.clear(); + spaceUuidsByRef.clear(); spaceHandlesByRef.clear(); backendIdsBySpaceRef.clear(); bodyHandlesByUuid.clear(); @@ -770,6 +782,7 @@ public PhysicsRuntimeResource clone() { copy.spaceHandlesByUuid.putAll(spaceHandlesByUuid); copy.backendIdsBySpaceUuid.putAll(backendIdsBySpaceUuid); copy.spaceRefsByUuid.putAll(spaceRefsByUuid); + copy.spaceUuidsByRef.putAll(spaceUuidsByRef); copy.spaceHandlesByRef.putAll(spaceHandlesByRef); copy.backendIdsBySpaceRef.putAll(backendIdsBySpaceRef); copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java index 0d2a9ab3..8d8d2865 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; @@ -7,6 +8,7 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -25,27 +27,30 @@ private PhysicsStoreBackendAccess() { @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId spaceId) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); UUID spaceUuid = compatibility.getSpaceUuid(spaceId); - return spaceUuid != null ? space(runtime, spaceUuid) : null; + return spaceUuid != null ? space(store, spaceUuid) : null; } @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull UUID spaceUuid) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - return space(runtime, spaceUuid); + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + return spaceRef != null && spaceRef.isValid() ? space(runtime, spaceRef) : null; } @Nullable - static SpaceContext space(@Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID spaceUuid) { - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); - BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + static SpaceContext space(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull Ref spaceRef) { + UUID spaceUuid = runtime.getSpaceUuid(spaceRef); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceRef); + BackendId backendId = runtime.getSpaceBackendId(spaceRef); PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; - if (spaceHandle == null || backendId == null || backendRuntime == null) { + if (spaceUuid == null || spaceHandle == null || backendId == null || backendRuntime == null) { return null; } return new SpaceContext(spaceUuid, backendId, spaceHandle, backendRuntime); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index c791b766..d3aa4ae4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -79,7 +79,7 @@ public static int runtimeJointCount(@Nonnull Store store) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); JointCountCapture count = new JointCountCapture(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> count.add(backendRuntime.jointCount(spaceHandle.value()))); return count.value(); } @@ -102,7 +102,7 @@ public static boolean ccdSupported(@Nonnull Store store) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); CcdSupportCapture supported = new CcdSupportCapture(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { if (backendRuntime.supportsContinuousCollision(spaceHandle.value())) { supported.markSupported(); } @@ -193,13 +193,11 @@ public static List spaceSummaries(@Nonnull Store sto PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); List summaries = new ArrayList<>(); - runtime.forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { - SpaceId spaceId = compatibility.getSpaceId(spaceUuid); - if (spaceId != null) { - summaries.add(new SpaceSummary(spaceId, - backendId, - backendRuntime.bodyCount(spaceHandle.value()), - backendRuntime.jointCount(spaceHandle.value()))); + runtime.forEachRuntimeSpaceBinding((spaceRef, _, _, _) -> { + PhysicsStoreBackendAccess.SpaceContext context = + PhysicsStoreBackendAccess.space(runtime, spaceRef); + if (context != null && compatibility.getSpaceId(context.spaceUuid()) != null) { + summaries.add(PhysicsStoreBackendAccess.summary(compatibility, context)); } }); return summaries.isEmpty() ? List.of() : List.copyOf(summaries); @@ -224,15 +222,10 @@ public static CompletionStage> spaceSummariesAsync( public static List spaceSummaries(@Nonnull Store store, @Nonnull SpaceId spaceId) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - UUID spaceUuid = compatibility.getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); - if (spaceUuid == null) { - return List.of(); - } PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(runtime, spaceUuid); + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); return space != null ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) : List.of(); @@ -261,11 +254,10 @@ public static CompletionStage> spaceSummariesAsync( public static List spaceSummaries(@Nonnull Store store, @Nonnull UUID spaceUuid) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(runtime, Objects.requireNonNull(spaceUuid, "spaceUuid")); + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); return space != null && compatibility.getSpaceId(space.spaceUuid()) != null ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) : List.of(); @@ -297,10 +289,10 @@ public static List unsupportedCcdSpaces(@Nonnull Store spaces = new ArrayList<>(); - runtime.forEachSpaceBinding((spaceUuid, _, spaceHandle, backendRuntime) -> { + runtime.forEachRuntimeSpaceBinding((spaceRef, _, spaceHandle, backendRuntime) -> { if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { PhysicsStoreBackendAccess.SpaceContext context = - PhysicsStoreBackendAccess.space(runtime, spaceUuid); + PhysicsStoreBackendAccess.space(runtime, spaceRef); if (context != null) { spaces.add(PhysicsStoreBackendAccess.summary(compatibility, context)); } From 787172c18bbe6da8752fc342455eb229435252de Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:34:33 +0200 Subject: [PATCH 152/534] refactor(physicsstore): publish completed steps by ref Signed-off-by: Blovien --- .../systems/CompletedStepPublicationSystem.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index e0092351..640c2cdf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -64,7 +64,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; List bodies = new ArrayList<>(); Set snapshotBodyUuids = new ObjectOpenHashSet<>(); - runtime.forEachSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> backendRuntime.snapshotBodies(spaceHandle.value(), bodyConsumer -> runtime.forEachBodyHandle(spaceHandle, bodyConsumer::accept), (bodyId, @@ -243,7 +243,12 @@ private static StepBackendEvents collectBackendEvents(@Nonnull Store { + runtime.forEachRuntimeSpaceBinding((spaceRef, _, spaceHandle, backendRuntime) -> { + UUID spaceUuid = runtime.getSpaceUuid(spaceRef); + if (spaceUuid == null) { + backendEvents.droppedBackendEventCount += backendRuntime.contactCount(spaceHandle.value()); + return; + } SpaceId spaceId = compatibility.getSpaceId(spaceUuid); if (spaceId == null) { backendEvents.droppedBackendEventCount += backendRuntime.contactCount(spaceHandle.value()); From 0d1fca57acc4e53336bf9ce849843146503ad02b Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:36:23 +0200 Subject: [PATCH 153/534] refactor(physicsstore): stitch terrain neighbors by ref Signed-off-by: Blovien --- .../systems/TerrainColliderBindingSystem.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 4928297a..8a549759 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -160,7 +160,13 @@ private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, runtime.markTerrainPayloadBound(terrainRef, terrainUuid, terrain.getPayloadResourceKey()); - stitchNeighbors(runtime, backendRuntime, spaceHandle, terrainRef, terrain, payload); + stitchNeighbors(runtime, + identity, + backendRuntime, + spaceHandle, + terrainRef, + terrain, + payload); } catch (RuntimeException exception) { removeTerrain(runtime, terrainUuid, terrainRef); restore.markFailed("PhysicsStore terrain " + terrain.getSourceKey() @@ -242,6 +248,7 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, } private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull Ref terrainRef, @@ -254,8 +261,13 @@ private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, for (TerrainNeighbor neighbor : payload.neighbors()) { UUID neighborUuid = TerrainColliderMutation.terrainColliderUuid(terrain.getSpaceUuid(), neighbor.sourceKey()); - BackendBodyHandle neighborBody = runtime.getTerrainVoxelBodyHandle(neighborUuid); - BackendSpaceHandle neighborSpace = runtime.getTerrainSpaceHandle(neighborUuid); + Ref neighborRef = PhysicsStoreSystemSupport.refForUuid(identity, + neighborUuid); + if (neighborRef == null) { + continue; + } + BackendBodyHandle neighborBody = runtime.getTerrainVoxelBodyHandle(neighborRef); + BackendSpaceHandle neighborSpace = runtime.getTerrainSpaceHandle(neighborRef); if (neighborBody == null || neighborSpace == null || neighborSpace.value() != spaceHandle.value()) { From ed5ccc094a191d37517d8532d799f9dee64031bf Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:38:02 +0200 Subject: [PATCH 154/534] refactor(physicsstore): resolve debug backend reads by ref Signed-off-by: Blovien --- .../systems/debug/PhysicsStoreDebugQueries.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index fd019656..2ad18a04 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -2,11 +2,13 @@ import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -288,9 +290,14 @@ private static SpaceContext space(@Nonnull Store store, if (spaceUuid == null) { return null; } + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + if (spaceRef == null || !spaceRef.isValid()) { + return null; + } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceUuid); - BackendId backendId = runtime.getSpaceBackendId(spaceUuid); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceRef); + BackendId backendId = runtime.getSpaceBackendId(spaceRef); PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; if (spaceHandle == null || backendRuntime == null) { From 18a524f3bd6e2989eb2e15dac40f6fff8524369f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:39:23 +0200 Subject: [PATCH 155/534] refactor(physicsstore): validate facade spaces by ref Signed-off-by: Blovien --- .../resources/PhysicsWorldRuntimeResource.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 44132379..548338f4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -322,12 +322,14 @@ private static void validateAuthoritativeStepModeSupported( return; } List unsupportedSpaces = new ArrayList<>(); - store.getResource(PhysicsRuntimeResource.getResourceType()) - .forEachSpaceBinding((spaceUuid, backendId, spaceHandle, backendRuntime) -> { - if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { - unsupportedSpaces.add(spaceUuid + " backend=" + backendId.value()); - } - }); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.forEachRuntimeSpaceBinding((spaceRef, backendId, spaceHandle, backendRuntime) -> { + if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + UUID spaceUuid = runtime.getSpaceUuid(spaceRef); + unsupportedSpaces.add((spaceUuid != null ? spaceUuid : spaceRef) + + " backend=" + backendId.value()); + } + }); if (!unsupportedSpaces.isEmpty()) { throw new IllegalArgumentException("CCD step mode is not supported by PhysicsStore " + "spaces: " + unsupportedSpaces); From edfc12dca7d80ec60014e4e57cb17447805877db Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:41:05 +0200 Subject: [PATCH 156/534] refactor(physicsstore): publish terrain registrations by ref Signed-off-by: Blovien --- .../physicsstore/systems/CompletedStepPublicationSystem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 640c2cdf..5025d743 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -219,7 +219,7 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run } TerrainColliderComponent terrain = chunk.getComponent(index, TerrainColliderComponent.getComponentType()); - if (terrain != null && runtime.hasTerrainBodyHandles(rowUuid)) { + if (terrain != null && runtime.hasTerrainBodyHandles(chunk.getReferenceTo(index))) { SpaceId spaceId = compatibility.getSpaceId(terrain.getSpaceUuid()); if (spaceId != null) { registrations.add(new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), From 257cb868ad34330c6e38b251c8391644321f69b8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:45:35 +0200 Subject: [PATCH 157/534] refactor(physicsstore): remove uuid runtime read api Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 153 ++---------------- .../systems/StaleBodyRemovalSystem.java | 2 +- 2 files changed, 13 insertions(+), 142 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 03822a31..a9a84ff8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -141,15 +141,10 @@ public PhysicsBackendRuntime getRuntime(@Nonnull BackendId backendId) { } public void putSpaceBinding(@Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle handle) { - putSpaceBinding(spaceUuid, null, backendId, handle); - } - - public void putSpaceBinding(@Nonnull UUID spaceUuid, - @Nullable Ref spaceRef, - @Nonnull BackendId backendId, - @Nonnull BackendSpaceHandle handle) { + Ref checkedSpaceRef = Objects.requireNonNull(spaceRef, "spaceRef"); Ref previousRef = spaceRefsByUuid.remove(spaceUuid); if (previousRef != null) { spaceUuidsByRef.remove(previousRef); @@ -158,17 +153,10 @@ public void putSpaceBinding(@Nonnull UUID spaceUuid, } backendIdsBySpaceUuid.put(spaceUuid, backendId); spaceHandlesByUuid.put(spaceUuid, handle); - if (spaceRef != null) { - spaceRefsByUuid.put(spaceUuid, spaceRef); - spaceUuidsByRef.put(spaceRef, spaceUuid); - backendIdsBySpaceRef.put(spaceRef, backendId); - spaceHandlesByRef.put(spaceRef, handle); - } - } - - @Nullable - public BackendSpaceHandle getSpaceHandle(@Nonnull UUID spaceUuid) { - return spaceHandlesByUuid.get(spaceUuid); + spaceRefsByUuid.put(spaceUuid, checkedSpaceRef); + spaceUuidsByRef.put(checkedSpaceRef, spaceUuid); + backendIdsBySpaceRef.put(checkedSpaceRef, backendId); + spaceHandlesByRef.put(checkedSpaceRef, handle); } @Nullable @@ -181,11 +169,6 @@ public UUID getSpaceUuid(@Nonnull Ref spaceRef) { return spaceUuidsByRef.get(spaceRef); } - @Nullable - public BackendId getSpaceBackendId(@Nonnull UUID spaceUuid) { - return backendIdsBySpaceUuid.get(spaceUuid); - } - @Nullable public BackendId getSpaceBackendId(@Nonnull Ref spaceRef) { return backendIdsBySpaceRef.get(spaceRef); @@ -231,32 +214,16 @@ public void putBodyHandle(@Nonnull UUID bodyUuid, new BodySnapshotMetadata(bodyUuid, bodyRef, spaceUuid)); } - @Nullable - public BackendBodyHandle getBodyHandle(@Nonnull UUID bodyUuid) { - return bodyHandlesByUuid.get(bodyUuid); - } - @Nullable public BackendBodyHandle getBodyHandle(@Nonnull Ref bodyRef) { return bodyHandlesByRef.get(bodyRef); } - @Nullable - public BackendSpaceHandle getBodySpaceHandle(@Nonnull UUID bodyUuid) { - return bodySpaceHandlesByUuid.get(bodyUuid); - } - @Nullable public BackendSpaceHandle getBodySpaceHandle(@Nonnull Ref bodyRef) { return bodySpaceHandlesByRef.get(bodyRef); } - public void removeBodyHandle(@Nonnull UUID bodyUuid) { - BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); - BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); - removeBodyHandleIndexes(removed, spaceHandle); - } - public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); @@ -266,18 +233,6 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref spaceHandle != null ? spaceHandle : spaceHandleByRef); } - @Nonnull - public List bodyUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandle) { - List bodyUuids = new ArrayList<>(); - int targetSpaceHandle = spaceHandle.value(); - bodySpaceHandlesByUuid.forEach((bodyUuid, handle) -> { - if (handle.value() == targetSpaceHandle) { - bodyUuids.add(bodyUuid); - } - }); - return bodyUuids; - } - @Nonnull public List> bodyRefsForSpaceHandle( @Nonnull BackendSpaceHandle spaceHandle) { @@ -350,16 +305,11 @@ public List drainPendingBodyOperations() { return drained; } - public void putJointHandle(@Nonnull UUID jointUuid, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendJointHandle handle) { - putJointHandle(jointUuid, null, spaceHandle, handle); - } - - public void putJointHandle(@Nonnull UUID jointUuid, - @Nullable Ref jointRef, + private void putJointHandle(@Nonnull UUID jointUuid, + @Nonnull Ref jointRef, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendJointHandle handle) { + Ref checkedJointRef = Objects.requireNonNull(jointRef, "jointRef"); Ref previousRef = jointRefsByUuid.remove(jointUuid); if (previousRef != null) { jointHandlesByRef.remove(previousRef); @@ -367,11 +317,9 @@ public void putJointHandle(@Nonnull UUID jointUuid, } jointHandlesByUuid.put(jointUuid, handle); jointSpaceHandlesByUuid.put(jointUuid, spaceHandle); - if (jointRef != null) { - jointRefsByUuid.put(jointUuid, jointRef); - jointHandlesByRef.put(jointRef, handle); - jointSpaceHandlesByRef.put(jointRef, spaceHandle); - } + jointRefsByUuid.put(jointUuid, checkedJointRef); + jointHandlesByRef.put(checkedJointRef, handle); + jointSpaceHandlesByRef.put(checkedJointRef, spaceHandle); } public void putJointHandle(@Nonnull Ref jointRef, @@ -381,21 +329,11 @@ public void putJointHandle(@Nonnull Ref jointRef, putJointHandle(jointUuid, jointRef, spaceHandle, handle); } - @Nullable - public BackendJointHandle getJointHandle(@Nonnull UUID jointUuid) { - return jointHandlesByUuid.get(jointUuid); - } - @Nullable public BackendJointHandle getJointHandle(@Nonnull Ref jointRef) { return jointHandlesByRef.get(jointRef); } - @Nullable - public BackendSpaceHandle getJointSpaceHandle(@Nonnull UUID jointUuid) { - return jointSpaceHandlesByUuid.get(jointUuid); - } - @Nullable public BackendSpaceHandle getJointSpaceHandle(@Nonnull Ref jointRef) { return jointSpaceHandlesByRef.get(jointRef); @@ -418,18 +356,6 @@ public void removeJointHandle(@Nonnull UUID jointUuid, jointSpaceHandlesByRef.remove(jointRef); } - @Nonnull - public List jointUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandle) { - List jointUuids = new ArrayList<>(); - int targetSpaceHandle = spaceHandle.value(); - jointSpaceHandlesByUuid.forEach((jointUuid, handle) -> { - if (handle.value() == targetSpaceHandle) { - jointUuids.add(jointUuid); - } - }); - return jointUuids; - } - @Nonnull public List> jointRefsForSpaceHandle( @Nonnull BackendSpaceHandle spaceHandle) { @@ -501,45 +427,21 @@ public boolean isTerrainPayloadBound(@Nonnull Ref terrainRef, return payloadKey.equals(terrainPayloadKeysByRef.get(terrainRef)); } - public boolean hasTerrainBodyHandles(@Nonnull UUID terrainUuid) { - LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); - return bodyHandles != null && !bodyHandles.isEmpty(); - } - public boolean hasTerrainBodyHandles(@Nonnull Ref terrainRef) { LongList bodyHandles = terrainBodyHandlesByRef.get(terrainRef); return bodyHandles != null && !bodyHandles.isEmpty(); } - @Nullable - public BackendSpaceHandle getTerrainSpaceHandle(@Nonnull UUID terrainUuid) { - return terrainSpaceHandlesByUuid.get(terrainUuid); - } - @Nullable public BackendSpaceHandle getTerrainSpaceHandle(@Nonnull Ref terrainRef) { return terrainSpaceHandlesByRef.get(terrainRef); } - @Nullable - public BackendBodyHandle getTerrainVoxelBodyHandle(@Nonnull UUID terrainUuid) { - return terrainVoxelBodyHandlesByUuid.get(terrainUuid); - } - @Nullable public BackendBodyHandle getTerrainVoxelBodyHandle(@Nonnull Ref terrainRef) { return terrainVoxelBodyHandlesByRef.get(terrainRef); } - public void forEachTerrainBodyHandle(@Nonnull UUID terrainUuid, - @Nonnull LongConsumer consumer) { - LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); - if (bodyHandles == null) { - return; - } - bodyHandles.forEach(consumer); - } - public void forEachTerrainBodyHandle(@Nonnull Ref terrainRef, @Nonnull LongConsumer consumer) { LongList bodyHandles = terrainBodyHandlesByRef.get(terrainRef); @@ -574,18 +476,6 @@ public void removeTerrainHandles(@Nonnull Ref terrainRef, removeTerrainHandles(terrainUuid, terrainRef); } - @Nonnull - public List terrainUuidsForSpaceHandle(@Nonnull BackendSpaceHandle spaceHandle) { - List terrainUuids = new ArrayList<>(); - int targetSpaceHandle = spaceHandle.value(); - terrainSpaceHandlesByUuid.forEach((terrainUuid, handle) -> { - if (handle.value() == targetSpaceHandle) { - terrainUuids.add(terrainUuid); - } - }); - return terrainUuids; - } - @Nonnull public List> terrainRefsForSpaceHandle( @Nonnull BackendSpaceHandle spaceHandle) { @@ -599,16 +489,6 @@ public List> terrainRefsForSpaceHandle( return terrainRefs; } - public void forEachSpaceBinding(@Nonnull SpaceBindingConsumer consumer) { - spaceHandlesByUuid.forEach((spaceUuid, spaceHandle) -> { - BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); - PhysicsBackendRuntime runtime = backendId != null ? runtimesByBackend.get(backendId) : null; - if (backendId != null && runtime != null) { - consumer.accept(spaceUuid, backendId, spaceHandle, runtime); - } - }); - } - public void forEachRuntimeSpaceBinding(@Nonnull RuntimeSpaceBindingConsumer consumer) { spaceHandlesByRef.forEach((spaceRef, spaceHandle) -> { BackendId backendId = backendIdsBySpaceRef.get(spaceRef); @@ -820,15 +700,6 @@ public static ResourceType getResourceType return PhysicsStoreTypes.runtimeResourceType(); } - @FunctionalInterface - public interface SpaceBindingConsumer { - - void accept(@Nonnull UUID spaceUuid, - @Nonnull BackendId backendId, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull PhysicsBackendRuntime runtime); - } - @FunctionalInterface public interface RuntimeSpaceBindingConsumer { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index c2d45371..923dccc8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -89,7 +89,7 @@ private static void removeStaleBodies(@Nonnull Store store, identity.removeUuid(body.bodyUuid(), body.bodyRef()); snapshots.removeBody(body.bodyUuid()); registrations.removeBody(RigidBodyKey.of(body.bodyUuid())); - runtime.removeBodyHandle(body.bodyUuid()); + runtime.removeBodyHandle(body.bodyUuid(), body.bodyRef()); } } From dbb36980cf378f56bb58037d256a2da0635026e8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:47:12 +0200 Subject: [PATCH 158/534] refactor(examples): name block body row creation directly Signed-off-by: Blovien --- .../impulse/examples/commands/ExamplePhysicsUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 8bf869b6..8ad2c264 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -236,7 +236,7 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - PendingBlockBody physicsStoreBody = tryRecordPhysicsStoreBlockBody(store, + PendingBlockBody physicsStoreBody = tryCreatePhysicsStoreBlockBody(store, spaceId, visualPosition, blockType, @@ -253,7 +253,7 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, } @Nullable - private static PendingBlockBody tryRecordPhysicsStoreBlockBody(@Nonnull Store store, + private static PendingBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3d visualPosition, @Nullable String blockType, From 3d9e294462a017bf7df132360ba0a0cdb1a70205 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:53:51 +0200 Subject: [PATCH 159/534] refactor(examples): carry created physics row refs Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 28 ++++---- .../examples/commands/ForcesCommand.java | 33 ++++----- .../examples/commands/JointsCommand.java | 67 ++++++++++--------- .../commands/PhysicsStoreExampleCommands.java | 5 +- .../commands/stress/StressJointsCommand.java | 21 +++--- .../explosive/ExplosiveBlockRuntime.java | 13 ++-- 6 files changed, 88 insertions(+), 79 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 8ad2c264..72672a95 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -236,7 +236,7 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - PendingBlockBody physicsStoreBody = tryCreatePhysicsStoreBlockBody(store, + CreatedBlockBody physicsStoreBody = tryCreatePhysicsStoreBlockBody(store, spaceId, visualPosition, blockType, @@ -253,7 +253,7 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, } @Nullable - private static PendingBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store store, + private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3d visualPosition, @Nullable String blockType, @@ -280,8 +280,9 @@ private static PendingBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store bodyRef; try { - addPhysicsStoreBody(world, + bodyRef = addPhysicsStoreBody(world, bodyRow(spaceUuid, bodyUuid, bodyCenter, @@ -293,7 +294,8 @@ private static PendingBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull PendingBlockBody pending) { + @Nonnull CreatedBlockBody created) { Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, time, - pending.bodyUuid(), - pending.blockType(), - new Vector3d(pending.positionX(), pending.positionY(), pending.positionZ()), - pending.controllable()); + created.bodyUuid(), + created.blockType(), + new Vector3d(created.positionX(), created.positionY(), created.positionZ()), + created.controllable()); assert entity != null; - return new SpawnedBlockBody(pending.bodyUuid(), pending.spaceId(), entity); + return new SpawnedBlockBody(created.bodyUuid(), created.spaceId(), entity); } @Nonnull @@ -679,7 +681,8 @@ private boolean isEmpty() { } } - public record PendingBlockBody(@Nonnull UUID bodyUuid, + public record CreatedBlockBody(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull SpaceId spaceId, @Nullable String blockType, float positionX, @@ -687,8 +690,9 @@ public record PendingBlockBody(@Nonnull UUID bodyUuid, float positionZ, boolean controllable) { - public PendingBlockBody { + public CreatedBlockBody { Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(bodyRef, "bodyRef"); Objects.requireNonNull(spaceId, "spaceId"); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index 7fa8846f..fb842613 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -66,10 +66,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, "Cannot spawn force demo because the target space is not bound in PhysicsStore.")); return CompletableFuture.completedFuture(null); } - PendingBlockBody central = bodies.central(); - PendingBlockBody offCenter = bodies.offCenter(); - PendingBlockBody torque = bodies.torque(); - PendingBlockBody force = bodies.force(); + CreatedBlockBody central = bodies.central(); + CreatedBlockBody offCenter = bodies.offCenter(); + CreatedBlockBody torque = bodies.torque(); + CreatedBlockBody force = bodies.force(); drawArrow(world, centralPosition, new Vector3d(2.0, 1.0, 0.0), DebugUtils.COLOR_GREEN); drawArrow(world, offCenterPosition, new Vector3d(2.0, 0.0, 0.0), DebugUtils.COLOR_YELLOW); drawArrow(world, torquePosition, new Vector3d(0.0, 0.0, 2.0), DebugUtils.COLOR_MAGENTA); @@ -102,7 +102,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, } try { - PendingBlockBody central = spawnBox(world, + CreatedBlockBody central = spawnBox(world, spaceUuid, spaceId, centralPosition, @@ -114,7 +114,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, 0.0f, 0.0f, 0.0f)); - PendingBlockBody offCenter = spawnBox(world, + CreatedBlockBody offCenter = spawnBox(world, spaceUuid, spaceId, offCenterPosition, @@ -126,7 +126,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, 0.0f, 0.5f, 0.5f)); - PendingBlockBody torque = spawnBox(world, + CreatedBlockBody torque = spawnBox(world, spaceUuid, spaceId, torquePosition, @@ -138,7 +138,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, 0.0f, 0.0f, 0.0f)); - PendingBlockBody force = spawnBox(world, + CreatedBlockBody force = spawnBox(world, spaceUuid, spaceId, forcePosition, @@ -156,13 +156,13 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, } } - private static PendingBlockBody spawnBox(@Nonnull World world, + private static CreatedBlockBody spawnBox(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, @Nonnull BodyCommandComponent command) { UUID bodyUuid = UUID.randomUUID(); - ExamplePhysicsUtils.addPhysicsStoreBody(world, + var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, ExamplePhysicsUtils.toVector3f(position), @@ -171,7 +171,8 @@ private static PendingBlockBody spawnBox(@Nonnull World world, RigidBodySpawnSettings.material(0.5f, 0.25f), null), command); - return new PendingBlockBody(bodyUuid, + return new CreatedBlockBody(bodyUuid, + bodyRef, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, (float) position.x, @@ -180,10 +181,10 @@ private static PendingBlockBody spawnBox(@Nonnull World world, true); } - private record ForceDemoBodies(@Nonnull PendingBlockBody central, - @Nonnull PendingBlockBody offCenter, - @Nonnull PendingBlockBody torque, - @Nonnull PendingBlockBody force) { + private record ForceDemoBodies(@Nonnull CreatedBlockBody central, + @Nonnull CreatedBlockBody offCenter, + @Nonnull CreatedBlockBody torque, + @Nonnull CreatedBlockBody force) { } private static void drawArrow(@Nonnull World world, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index bea520d5..9c1d7a23 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -56,16 +56,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-5.0, 5.0, 5.0); - List pendingBodies = tryCreatePhysicsStoreDemo(world, + List createdBodies = tryCreatePhysicsStoreDemo(world, spaceId, new Vector3d(origin)); - if (pendingBodies == null) { + if (createdBodies == null) { ctx.sender().sendMessage(Message.raw( "Cannot spawn joint demo because the target space is not bound in PhysicsStore.")); return CompletableFuture.completedFuture(null); } - for (PendingBlockBody pending : pendingBodies) { - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, pending); + for (CreatedBlockBody created : createdBodies) { + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, created); } ctx.sender().sendMessage(Message.raw( @@ -74,7 +74,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } @Nullable - private static List tryCreatePhysicsStoreDemo(@Nonnull World world, + private static List tryCreatePhysicsStoreDemo(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { UUID spaceUuid; @@ -87,26 +87,26 @@ private static List tryCreatePhysicsStoreDemo(@Nonnull World w return null; } - List pendingBodies = new ArrayList<>(10); + List createdBodies = new ArrayList<>(10); try { - createFixed(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin)); - createPoint(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); - createHinge(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); - createSlider(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); - createSpring(pendingBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); + createFixed(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin)); + createPoint(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); + createHinge(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); + createSlider(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); + createSpring(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); } catch (IllegalStateException exception) { return null; } - return pendingBodies; + return createdBodies; } - private static void createFixed(@Nonnull List pendingBodies, + private static void createFixed(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID childUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, + UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID childUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), @@ -119,13 +119,13 @@ private static void createFixed(@Nonnull List pendingBodies, new Vector3f())); } - private static void createPoint(@Nonnull List pendingBodies, + private static void createPoint(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID bobUuid = spawnBox(pendingBodies, + UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID bobUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, @@ -143,13 +143,13 @@ private static void createPoint(@Nonnull List pendingBodies, new Vector3f())); } - private static void createHinge(@Nonnull List pendingBodies, + private static void createHinge(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID armUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, + UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID armUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); JointComponent joint = joint(spaceUuid, anchorUuid, @@ -166,13 +166,13 @@ private static void createHinge(@Nonnull List pendingBodies, ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); } - private static void createSlider(@Nonnull List pendingBodies, + private static void createSlider(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID blockUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, + UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID blockUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(TOUCHING_SPACING, 0.0, 0.0), 1.0f); JointComponent joint = joint(spaceUuid, anchorUuid, @@ -189,13 +189,13 @@ private static void createSlider(@Nonnull List pendingBodies, ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); } - private static void createSpring(@Nonnull List pendingBodies, + private static void createSpring(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(pendingBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID bobUuid = spawnBox(pendingBodies, + UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); + UUID bobUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, @@ -215,16 +215,16 @@ private static void createSpring(@Nonnull List pendingBodies, ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); } - private static UUID spawnBox(@Nonnull List pendingBodies, + private static UUID spawnBox(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass) { - return spawnBox(pendingBodies, world, spaceUuid, spaceId, position, mass, null); + return spawnBox(createdBodies, world, spaceUuid, spaceId, position, mass, null); } - private static UUID spawnBox(@Nonnull List pendingBodies, + private static UUID spawnBox(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @@ -232,7 +232,7 @@ private static UUID spawnBox(@Nonnull List pendingBodies, float mass, @Nullable Vector3f linearVelocity) { UUID bodyUuid = UUID.randomUUID(); - ExamplePhysicsUtils.addPhysicsStoreBody(world, + var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, ExamplePhysicsUtils.toVector3f(position), @@ -240,7 +240,8 @@ private static UUID spawnBox(@Nonnull List pendingBodies, mass, RigidBodySpawnSettings.material(0.6f, 0.15f), linearVelocity)); - pendingBodies.add(new PendingBlockBody(bodyUuid, + createdBodies.add(new CreatedBlockBody(bodyUuid, + bodyRef, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, (float) position.x, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index df4f936c..cfefeba6 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -156,7 +156,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Vector3f targetPosition = vector(spawn); - ExamplePhysicsUtils.addPhysicsStoreBody(world, + var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, targetPosition, @@ -174,7 +174,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, - new ExamplePhysicsUtils.PendingBlockBody(bodyUuid, + new ExamplePhysicsUtils.CreatedBlockBody(bodyUuid, + bodyRef, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, (float) spawn.x, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 5035125e..18b621ea 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -91,8 +91,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d origin = new Vector3d(playerPos).add(-totalJoints * 0.1, 7.0, 5.0); int createdJoints = 0; - int createdBodies = 0; - List pendingBodies = new ArrayList<>(totalJoints + ROWS); + int createdBodyCount = 0; + List createdBodyRows = new ArrayList<>(totalJoints + ROWS); int baseJointsPerRow = totalJoints / ROWS; int remainder = totalJoints % ROWS; try { @@ -103,7 +103,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } Vector3d rowOrigin = new Vector3d(origin).add(0.0, 0.0, row * ROW_SPACING); - createdBodies += appendRow(pendingBodies, + createdBodyCount += appendRow(createdBodyRows, world, spaceUuid, spaceId, @@ -117,17 +117,17 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Cannot create stress joint demo: " + exception.getMessage())); return CompletableFuture.completedFuture(null); } - for (PendingBlockBody pendingBody : pendingBodies) { - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, pendingBody); + for (CreatedBlockBody createdBody : createdBodyRows) { + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, createdBody); } ctx.sender().sendMessage(Message.raw("Queued " + createdJoints + " stress joints across fixed/point/hinge/slider/spring rows with " - + createdBodies + " bodies and attached visuals. blockType=" + blockType + ".")); + + createdBodyCount + " bodies and attached visuals. blockType=" + blockType + ".")); return CompletableFuture.completedFuture(null); } - private static int appendRow(@Nonnull List pendingBodies, + private static int appendRow(@Nonnull List createdBodies, @Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull SpaceId spaceId, @@ -153,7 +153,7 @@ private static int appendRow(@Nonnull List pendingBodies, positions[positionOffset + 1] = (float) origin.y; positions[positionOffset + 2] = (float) origin.z; float mass = i == 0 ? 0.0f : 1.0f; - ExamplePhysicsUtils.addPhysicsStoreBody(world, + var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, new Vector3f(positions[positionOffset], @@ -163,8 +163,9 @@ private static int appendRow(@Nonnull List pendingBodies, mass, spawnSettings, initialVelocity(jointType, i))); - pendingBodies.add(new PendingBlockBody( + createdBodies.add(new CreatedBlockBody( bodyUuid, + bodyRef, spaceId, blockType, positions[positionOffset], diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 8c488439..cf1a3103 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -24,7 +24,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.PendingBlockBody; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -142,7 +142,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e Math.max(0L, world.getTick())); Vector3f centerF = toVector3f(center); - List pending = new ArrayList<>(groups.size()); + List created = new ArrayList<>(groups.size()); for (FragmentGroup group : groups) { UUID bodyUuid = UUID.randomUUID(); Vector3d groupCenter = group.center(); @@ -151,7 +151,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e settings.getImpulseStrength(), settings.getVerticalLift()) .mul(group.mass()); - ExamplePhysicsUtils.addPhysicsStoreBody(world, + var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyRow(spaceUuid, bodyUuid, toVector3f(groupCenter), @@ -167,7 +167,8 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e 0.0f, 0.0f, 0.0f)); - pending.add(new PendingBlockBody(bodyUuid, + created.add(new CreatedBlockBody(bodyUuid, + bodyRef, spaceId, group.blockType(), (float) groupCenter.x, @@ -177,7 +178,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e } for (int i = 0; i < groups.size(); i++) { - spawnGroupVisuals(time, fragmentSpawner, groups.get(i), pending.get(i)); + spawnGroupVisuals(time, fragmentSpawner, groups.get(i), created.get(i)); } return new ExplosionResult(groups.size()); } @@ -185,7 +186,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e private static void spawnGroupVisuals(@Nonnull TimeResource time, @Nonnull Consumer> fragmentSpawner, @Nonnull FragmentGroup group, - @Nonnull PendingBlockBody body) { + @Nonnull CreatedBlockBody body) { boolean controllableAssigned = false; for (FragmentVisual visual : group.visualBlocks()) { boolean controllable = body.controllable() && !controllableAssigned; From 40c91d467311de1383060f1c5692a608c4b0b127 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:55:02 +0200 Subject: [PATCH 160/534] refactor(examples): wake grabbed bodies by ref Signed-off-by: Blovien --- .../examples/commands/GrabCommand.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 01a7566c..cd33b7a7 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -188,12 +188,13 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, RigidBodyKey anchorBodyKey = RigidBodyKey.random(); JointKey controlJointKey = JointKey.random(); - boolean selectedBound = ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(world, - selection.bodyKey().value(), - BodyCommandComponent.wake()); - if (!selectedBound) { + Ref selectedBodyRef = selection.bodyRef(); + if (!selectedBodyRef.isValid()) { return null; } + ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(selectedBodyRef.getStore(), + selectedBodyRef, + BodyCommandComponent.wake()); try { ExamplePhysicsUtils.addPhysicsStoreBody(world, anchorBodyRow(spaceUuid, anchorBodyKey.value(), hitPoint)); @@ -293,7 +294,8 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { continue; } - candidates.add(new HitCandidate(registration.bodyKey(), + candidates.add(new HitCandidate(hit.bodyRef(), + registration.bodyKey(), registration.spaceId(), hit.point(), hit.fraction(), @@ -308,7 +310,8 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource } if (best == null || candidate.fraction() < best.fraction()) { - best = new HitSelection(candidate.bodyKey(), + best = new HitSelection(candidate.bodyRef(), + candidate.bodyKey(), attachments.controllableAttachment(), candidate.spaceId(), candidate.point(), @@ -357,7 +360,8 @@ private static AttachmentSelection inspectGameplayAttachments(@Nonnull PhysicsWo return new AttachmentSelection(null, hasGameplayAttachment); } - private record HitSelection(@Nonnull RigidBodyKey bodyKey, + private record HitSelection(@Nonnull Ref bodyRef, + @Nonnull RigidBodyKey bodyKey, @Nullable Ref attachment, @Nullable SpaceId spaceId, @Nonnull Vector3f point, @@ -365,7 +369,8 @@ private record HitSelection(@Nonnull RigidBodyKey bodyKey, float distance) { } - private record HitCandidate(@Nonnull RigidBodyKey bodyKey, + private record HitCandidate(@Nonnull Ref bodyRef, + @Nonnull RigidBodyKey bodyKey, @Nullable SpaceId spaceId, @Nonnull Vector3f point, float fraction, From 7a3876c5b055a67f23f16bf0ae5538a375d93633 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:56:12 +0200 Subject: [PATCH 161/534] refactor(examples): remove uuid body command helper Signed-off-by: Blovien --- .../examples/commands/ExamplePhysicsUtils.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 72672a95..554acb0d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -141,22 +140,6 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store store = physicsStore(world); - PhysicsStoreThreading.requireWorldThread(store, "append a PhysicsStore body command"); - Ref bodyRef = store - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(Objects.requireNonNull(bodyUuid, "bodyUuid")); - if (bodyRef == null || !bodyRef.isValid()) { - return false; - } - appendPhysicsStoreBodyCommand(store, bodyRef, command); - return true; - } - @Nullable public static UUID physicsStoreRowUuid(@Nonnull Ref ref) { Objects.requireNonNull(ref, "ref"); From 0e96d89021a4bf8569c90a6f109718054375f818 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 08:57:09 +0200 Subject: [PATCH 162/534] refactor(examples): guard created body attachments Signed-off-by: Blovien --- .../impulse/examples/commands/ExamplePhysicsUtils.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 554acb0d..09485c6a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -410,6 +410,13 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, public static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull CreatedBlockBody created) { + Ref bodyRef = created.bodyRef(); + PhysicsStoreThreading.requireWorldThread(bodyRef.getStore(), + "attach a visual to a created PhysicsStore body row"); + if (!bodyRef.isValid()) { + throw new IllegalStateException("Cannot attach visual because PhysicsStore body row " + + "is no longer valid: " + created.bodyUuid()); + } Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, time, created.bodyUuid(), From bd441a3f37f1d568329a119e343bb162afe11cc6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 09:02:58 +0200 Subject: [PATCH 163/534] refactor(physicsstore): index registrations by row ref Signed-off-by: Blovien --- .../PhysicsBodyRegistrationResource.java | 64 ++++++++++++++++--- .../CompletedStepPublicationSystem.java | 28 ++++---- .../examples/commands/GrabCommand.java | 11 ++-- .../systems/ExplosiveFuseTickSystem.java | 13 ++-- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index 3784d385..83f82035 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -14,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -33,6 +35,16 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey return registrations.viewsByKey().get(Objects.requireNonNull(bodyKey, "bodyKey")); } + @Nullable + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { + return registrations.viewsByUuid().get(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + @Nullable + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { + return registrations.viewsByRef().get(Objects.requireNonNull(bodyRef, "bodyRef")); + } + @Nonnull public Collection getBodyRegistrationViews() { return registrations.views(); @@ -66,16 +78,31 @@ public Collection getBodyRegistrationViews( return views; } - public void publish(@Nonnull Collection views) { + public void publish(@Nonnull Collection publications) { + Object2ObjectLinkedOpenHashMap publicationsByKey = + new Object2ObjectLinkedOpenHashMap<>(); + for (BodyRegistrationPublication publication : publications) { + BodyRegistrationPublication checkedPublication = + Objects.requireNonNull(publication, "publication"); + publicationsByKey.put(checkedPublication.view().bodyKey(), checkedPublication); + } + Object2ObjectLinkedOpenHashMap viewsByKey = new Object2ObjectLinkedOpenHashMap<>(); - for (PhysicsBodyRegistrationView view : views) { - PhysicsBodyRegistrationView registration = - Objects.requireNonNull(view, "view"); + Object2ObjectLinkedOpenHashMap viewsByUuid = + new Object2ObjectLinkedOpenHashMap<>(); + Object2ObjectLinkedOpenHashMap, PhysicsBodyRegistrationView> viewsByRef = + new Object2ObjectLinkedOpenHashMap<>(); + for (BodyRegistrationPublication publication : publicationsByKey.values()) { + PhysicsBodyRegistrationView registration = publication.view(); viewsByKey.put(registration.bodyKey(), registration); + viewsByUuid.put(registration.bodyKey().value(), registration); + viewsByRef.put(publication.bodyRef(), registration); } registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), - Map.copyOf(viewsByKey)); + Map.copyOf(viewsByKey), + Map.copyOf(viewsByUuid), + Map.copyOf(viewsByRef)); } public void removeBody(@Nonnull RigidBodyKey bodyKey) { @@ -86,8 +113,17 @@ public void removeBody(@Nonnull RigidBodyKey bodyKey) { Object2ObjectLinkedOpenHashMap viewsByKey = new Object2ObjectLinkedOpenHashMap<>(current.viewsByKey()); viewsByKey.remove(bodyKey); + Object2ObjectLinkedOpenHashMap viewsByUuid = + new Object2ObjectLinkedOpenHashMap<>(current.viewsByUuid()); + viewsByUuid.remove(bodyKey.value()); + Object2ObjectLinkedOpenHashMap, PhysicsBodyRegistrationView> viewsByRef = + new Object2ObjectLinkedOpenHashMap<>(current.viewsByRef()); + viewsByRef.object2ObjectEntrySet() + .removeIf(entry -> entry.getValue().bodyKey().equals(bodyKey)); registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), - Map.copyOf(viewsByKey)); + Map.copyOf(viewsByKey), + Map.copyOf(viewsByUuid), + Map.copyOf(viewsByRef)); } public void clear() { @@ -107,11 +143,23 @@ public static ResourceType getRes return PhysicsStoreTypes.bodyRegistrationResourceType(); } + public record BodyRegistrationPublication( + @Nonnull Ref bodyRef, + @Nonnull PhysicsBodyRegistrationView view) { + + public BodyRegistrationPublication { + Objects.requireNonNull(bodyRef, "bodyRef"); + Objects.requireNonNull(view, "view"); + } + } + private record PublishedRegistrations( @Nonnull List views, - @Nonnull Map viewsByKey) { + @Nonnull Map viewsByKey, + @Nonnull Map viewsByUuid, + @Nonnull Map, PhysicsBodyRegistrationView> viewsByRef) { private static final PublishedRegistrations EMPTY = - new PublishedRegistrations(List.of(), Map.of()); + new PublishedRegistrations(List.of(), Map.of(), Map.of(), Map.of()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 5025d743..85d46eb9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -14,6 +14,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource.BodyRegistrationPublication; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; @@ -180,13 +181,13 @@ private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, } @Nonnull - private static List collectRegistrationViews( + private static List collectRegistrationViews( @Nonnull Store store, int systemIndex, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull Set snapshotBodyUuids) { - List registrations = new ArrayList<>(); + List registrations = new ArrayList<>(); BiConsumer, CommandBuffer> collector = (chunk, _) -> collectRegistrationViews(runtime, compatibility, @@ -200,32 +201,35 @@ private static List collectRegistrationViews( private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull Set snapshotBodyUuids, - @Nonnull List registrations, + @Nonnull List registrations, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { UUID rowUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); if (PhysicsStoreSystemSupport.isNil(rowUuid)) { continue; } + var rowRef = chunk.getReferenceTo(index); BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); if (body != null && snapshotBodyUuids.contains(rowUuid)) { SpaceId spaceId = compatibility.getSpaceId(body.getSpaceUuid()); if (spaceId != null) { - registrations.add(new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), - spaceId, - body.getKind(), - body.getPersistenceMode())); + registrations.add(new BodyRegistrationPublication(rowRef, + new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), + spaceId, + body.getKind(), + body.getPersistenceMode()))); } } TerrainColliderComponent terrain = chunk.getComponent(index, TerrainColliderComponent.getComponentType()); - if (terrain != null && runtime.hasTerrainBodyHandles(chunk.getReferenceTo(index))) { + if (terrain != null && runtime.hasTerrainBodyHandles(rowRef)) { SpaceId spaceId = compatibility.getSpaceId(terrain.getSpaceUuid()); if (spaceId != null) { - registrations.add(new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), - spaceId, - PhysicsBodyKind.WORLD_COLLISION, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); + registrations.add(new BodyRegistrationPublication(rowRef, + new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), + spaceId, + PhysicsBodyKind.WORLD_COLLISION, + PhysicsBodyPersistenceMode.RUNTIME_ONLY))); } } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index cd33b7a7..32626eb1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -20,6 +20,7 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; @@ -284,13 +285,11 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource || !hit.bodyRef().isValid()) { continue; } - UUID hitBodyUuid = ExamplePhysicsUtils.physicsStoreRowUuid(hit.bodyRef()); - if (hitBodyUuid == null) { - continue; - } - RigidBodyKey hitBodyKey = RigidBodyKey.of(hitBodyUuid); PhysicsBodyRegistrationView registration = - resource.getBodyRegistrationView(hitBodyKey); + hit.bodyRef() + .getStore() + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(hit.bodyRef()); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { continue; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index f40140d9..f7ffbadd 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -15,13 +15,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -119,9 +118,13 @@ private static Vector3d explosionCenter(@Nullable BodyMotionSnapshot snapshot, @Nullable private static SpaceId attachmentSpaceId(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - PhysicsBodyRegistrationView registration = - resource.getBodyRegistrationView(RigidBodyKey.of(attachment.getBodyUuid())); + Store physics = ((PhysicsStoreWorld) store.getExternalData().getWorld()) + .getPhysicsStore().getStore(); + PhysicsStoreThreading.requireWorldThread(physics, + "read copied PhysicsStore explosive body registration"); + PhysicsBodyRegistrationView registration = physics + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(attachment.getBodyUuid()); return registration != null ? registration.spaceId() : null; } From 6da97a56506f71bc36923bbbb0f84abefc6e6aa6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 09:20:05 +0200 Subject: [PATCH 164/534] refactor(control): store sessions by physics refs Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 18 +- .../PhysicsControlSessionComponent.java | 62 ++----- .../systems/PhysicsControlSessionCleanup.java | 17 +- .../PhysicsControlSessionCleanupSystem.java | 18 +- .../PhysicsKinematicControlSystem.java | 82 ++++----- .../PhysicsStoreControlSessionMutations.java | 68 ++++--- .../control/PhysicsControlSessions.java | 166 ++++++++++++++++-- .../examples/commands/GrabCommand.java | 43 ++--- 8 files changed, 302 insertions(+), 172 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index d21855b5..1ee456fe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -14,6 +14,7 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; @@ -21,6 +22,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; @@ -243,8 +245,8 @@ private static boolean controlSessionSelected( @Nonnull Set selectedBodyKeys, @Nonnull Vector3d center, double radiusSquared) { - if (containsBody(selectedBodyKeys, session.getBodyUuid()) - || containsBody(selectedBodyKeys, session.getAnchorBodyUuid()) + if (containsBody(selectedBodyKeys, session.getBodyRef()) + || containsBody(selectedBodyKeys, session.getAnchorBodyRef()) || entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { return true; } @@ -269,10 +271,20 @@ private static ComponentType contro } private static boolean containsBody(@Nonnull Set bodyKeys, - @Nullable UUID bodyUuid) { + @Nullable Ref bodyRef) { + UUID bodyUuid = rowUuid(bodyRef); return bodyUuid != null && bodyKeys.contains(RigidBodyKey.of(bodyUuid)); } + @Nullable + private static UUID rowUuid(@Nullable Ref bodyRef) { + if (bodyRef == null || !bodyRef.isValid()) { + return null; + } + UuidComponent uuid = bodyRef.getStore().getComponent(bodyRef, UuidComponent.getComponentType()); + return uuid != null ? uuid.getUuid() : null; + } + private static boolean entityWithinRadius(@Nonnull ArchetypeChunk archetypeChunk, int index, @Nonnull Vector3d center, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java index 01c3ea12..d0a86a73 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java @@ -4,11 +4,9 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import java.util.Objects; -import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; @@ -21,11 +19,11 @@ public class PhysicsControlSessionComponent implements Component { private static ComponentType componentType; @Nullable - private UUID bodyUuid; + private Ref bodyRef; @Nullable - private UUID anchorBodyUuid; + private Ref anchorBodyRef; @Nullable - private JointKey controlJointKey; + private Ref controlJointRef; @Nullable private Ref targetRef; @Nonnull @@ -44,53 +42,25 @@ public class PhysicsControlSessionComponent implements Component { public PhysicsControlSessionComponent() { } - public PhysicsControlSessionComponent(@Nonnull RigidBodyKey bodyKey, - @Nonnull RigidBodyKey anchorBodyKey, - @Nullable JointKey controlJointKey, + public PhysicsControlSessionComponent(@Nonnull Ref bodyRef, + @Nonnull Ref anchorBodyRef, + @Nullable Ref controlJointRef, @Nullable Ref targetRef, @Nonnull PhysicsBodyType originalBodyType, float grabDistance, @Nonnull Vector3f viewOffset, @Nonnull Vector3f previousTarget) { - this(bodyKey.value(), - anchorBodyKey.value(), - controlJointKey, - targetRef, - originalBodyType, - grabDistance, - viewOffset, - previousTarget); - } - - public PhysicsControlSessionComponent(@Nonnull UUID bodyUuid, - @Nonnull UUID anchorBodyUuid, - @Nullable JointKey controlJointKey, - @Nullable Ref targetRef, - @Nonnull PhysicsBodyType originalBodyType, - float grabDistance, - @Nonnull Vector3f viewOffset, - @Nonnull Vector3f previousTarget) { - this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - this.anchorBodyUuid = Objects.requireNonNull(anchorBodyUuid, "anchorBodyUuid"); - this.controlJointKey = controlJointKey; + this.bodyRef = Objects.requireNonNull(bodyRef, "bodyRef"); + this.anchorBodyRef = Objects.requireNonNull(anchorBodyRef, "anchorBodyRef"); + this.controlJointRef = controlJointRef; this.targetRef = targetRef; - this.originalBodyType = originalBodyType; + this.originalBodyType = Objects.requireNonNull(originalBodyType, "originalBodyType"); this.grabDistance = grabDistance; - this.viewOffset.set(viewOffset); - this.previousTarget.set(previousTarget); + this.viewOffset.set(Objects.requireNonNull(viewOffset, "viewOffset")); + this.previousTarget.set(Objects.requireNonNull(previousTarget, "previousTarget")); this.active = true; } - @Nullable - public RigidBodyKey getBodyKey() { - return bodyUuid != null ? RigidBodyKey.of(bodyUuid) : null; - } - - @Nullable - public RigidBodyKey getAnchorBodyKey() { - return anchorBodyUuid != null ? RigidBodyKey.of(anchorBodyUuid) : null; - } - public static void setComponentType( @Nonnull ComponentType type) { componentType = Objects.requireNonNull(type, "type"); @@ -120,9 +90,9 @@ public void deactivate() { @Override public PhysicsControlSessionComponent clone() { PhysicsControlSessionComponent copy = new PhysicsControlSessionComponent(); - copy.bodyUuid = bodyUuid; - copy.anchorBodyUuid = anchorBodyUuid; - copy.controlJointKey = controlJointKey; + copy.bodyRef = bodyRef; + copy.anchorBodyRef = anchorBodyRef; + copy.controlJointRef = controlJointRef; copy.targetRef = targetRef; copy.originalBodyType = originalBodyType; copy.grabDistance = grabDistance; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java index a47c0d33..a5697d39 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java @@ -1,11 +1,15 @@ package dev.hytalemodding.impulse.core.internal.modules.control.systems; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class PhysicsControlSessionCleanup { @@ -26,12 +30,12 @@ public static void cleanup(@Nonnull Store store, private static void cleanupInternal(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull PhysicsControlSessionComponent session) { - PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyUuid()); + PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyRef()); if (!session.isActive()) { return; } - UUID bodyUuid = session.getBodyUuid(); + UUID bodyUuid = rowUuid(session.getBodyRef()); if (bodyUuid != null) { resource.clearControlledBody(bodyUuid); } @@ -39,4 +43,13 @@ private static void cleanupInternal(@Nonnull Store store, PhysicsStoreControlSessionMutations.applyRelease(store, session); session.deactivate(); } + + @Nullable + private static UUID rowUuid(@Nullable Ref ref) { + if (ref == null || !ref.isValid()) { + return null; + } + UuidComponent uuid = ref.getStore().getComponent(ref, UuidComponent.getComponentType()); + return uuid != null ? uuid.getUuid() : null; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java index 3841276b..cce1ef24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java @@ -7,6 +7,7 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefChangeSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import java.util.Objects; @@ -86,8 +87,19 @@ public Query getQuery() { private static boolean sameSessionOwner(@Nonnull PhysicsControlSessionComponent first, @Nonnull PhysicsControlSessionComponent second) { - return Objects.equals(first.getBodyUuid(), second.getBodyUuid()) - && Objects.equals(first.getAnchorBodyUuid(), second.getAnchorBodyUuid()) - && Objects.equals(first.getControlJointKey(), second.getControlJointKey()); + return sameRef(first.getBodyRef(), second.getBodyRef()) + && sameRef(first.getAnchorBodyRef(), second.getAnchorBodyRef()) + && sameRef(first.getControlJointRef(), second.getControlJointRef()); + } + + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + if (first == second) { + return true; + } + return first != null + && second != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 1f601b32..f183a8ab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -21,18 +21,16 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.UUID; import java.util.WeakHashMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -90,11 +88,15 @@ public void tick(float dt, return; } - UUID bodyUuid = session.getBodyUuid(); - UUID anchorBodyUuid = session.getAnchorBodyUuid(); + Ref bodyRef = session.getBodyRef(); + Ref anchorBodyRef = session.getAnchorBodyRef(); Ref targetRef = session.getTargetRef(); - if (bodyUuid == null || anchorBodyUuid == null || (targetRef != null && !targetRef.isValid())) { - stateFor(store).clear(anchorBodyUuid); + if (bodyRef == null + || anchorBodyRef == null + || !bodyRef.isValid() + || !anchorBodyRef.isValid() + || (targetRef != null && !targetRef.isValid())) { + stateFor(store).clear(anchorBodyRef); PhysicsControlSessionCleanup.cleanup(store, session); commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); return; @@ -135,24 +137,24 @@ public void tick(float dt, previousTarget.set(local.target); ControlMutationState state = stateFor(store); - ControlAnchorUpdate update = new ControlAnchorUpdate(bodyUuid, - anchorBodyUuid, + ControlAnchorUpdate update = new ControlAnchorUpdate(bodyRef, + anchorBodyRef, local.target, releaseVelocity); - ControlAnchorUpdate readyUpdate = state.selectReadyUpdate(anchorBodyUuid, update); + ControlAnchorUpdate readyUpdate = state.selectReadyUpdate(anchorBodyRef, update); if (readyUpdate == null) { return; } PhysicsStoreControlTargets physicsStoreTargets = - resolvePhysicsStoreTargets(store, bodyUuid, anchorBodyUuid); + resolvePhysicsStoreTargets(store, bodyRef, anchorBodyRef); if (physicsStoreTargets != null) { physicsStoreTargets.apply(readyUpdate); - state.trackSubmittedMutation(anchorBodyUuid, readyUpdate); + state.trackSubmittedMutation(anchorBodyRef, readyUpdate); return; } - stateFor(store).clear(anchorBodyUuid); + stateFor(store).clear(anchorBodyRef); PhysicsControlSessionCleanup.cleanup(store, session); commandBuffer.removeComponent(chunk.getReferenceTo(index), sessionType); } @@ -160,18 +162,14 @@ public void tick(float dt, @Nullable private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( @Nonnull Store store, - @Nonnull UUID bodyUuid, - @Nonnull UUID anchorBodyUuid) { + @Nonnull Ref bodyRef, + @Nonnull Ref anchorBodyRef) { PhysicsStore physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); Store physics = physicsStore.getStore(); PhysicsStoreThreading.requireWorldThread(physics, "resolve PhysicsStore kinematic control targets"); - PhysicsIdentityIndexResource identity = physics.getResource( - PhysicsIdentityIndexResource.getResourceType()); - Ref bodyRef = bodyRef(physics, identity, bodyUuid); - Ref anchorBodyRef = bodyRef(physics, identity, anchorBodyUuid); - if (bodyRef == null || anchorBodyRef == null) { + if (!validBodyRef(physics, bodyRef) || !validBodyRef(physics, anchorBodyRef)) { return null; } return new PhysicsStoreControlTargets( @@ -180,17 +178,11 @@ private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( anchorBodyRef); } - @Nullable - private static Ref bodyRef(@Nonnull Store physics, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID bodyUuid) { - Ref ref = identity.getByUuid(bodyUuid); - if (ref != null + private static boolean validBodyRef(@Nonnull Store physics, + @Nonnull Ref ref) { + return ref.getStore() == physics && ref.isValid() - && physics.getComponent(ref, BodyComponent.getComponentType()) != null) { - return ref; - } - return null; + && physics.getComponent(ref, BodyComponent.getComponentType()) != null; } private float eyeHeight(@Nonnull ArchetypeChunk chunk, @@ -238,14 +230,14 @@ static ControlMutationState stateFor(@Nonnull Store store) { } public static void clearMutationState(@Nonnull Store store, - @Nullable UUID anchorBodyUuid) { - if (anchorBodyUuid != null) { - stateFor(store).clear(anchorBodyUuid); + @Nullable Ref anchorBodyRef) { + if (anchorBodyRef != null) { + stateFor(store).clear(anchorBodyRef); } } - record ControlAnchorUpdate(@Nonnull UUID bodyUuid, - @Nonnull UUID anchorBodyUuid, + record ControlAnchorUpdate(@Nonnull Ref bodyRef, + @Nonnull Ref anchorBodyRef, @Nonnull Vector3f target, @Nonnull Vector3f releaseVelocity) { @@ -295,27 +287,27 @@ private static TargetComponent target(@Nonnull Vector3f position, static final class ControlMutationState { @Nonnull - private final Object2ObjectMap submittedUpdates = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectMap submittedUpdates = + new Int2ObjectOpenHashMap<>(); @Nullable - synchronized ControlAnchorUpdate selectReadyUpdate(@Nonnull UUID bodyUuid, + synchronized ControlAnchorUpdate selectReadyUpdate(@Nonnull Ref anchorBodyRef, @Nonnull ControlAnchorUpdate currentUpdate) { - ControlAnchorUpdate submittedUpdate = submittedUpdates.get(bodyUuid); + ControlAnchorUpdate submittedUpdate = submittedUpdates.get(anchorBodyRef.getIndex()); if (sameTarget(currentUpdate, submittedUpdate)) { return null; } return currentUpdate; } - synchronized void trackSubmittedMutation(@Nonnull UUID bodyUuid, + synchronized void trackSubmittedMutation(@Nonnull Ref anchorBodyRef, @Nonnull ControlAnchorUpdate submittedUpdate) { - submittedUpdates.put(bodyUuid, submittedUpdate); + submittedUpdates.put(anchorBodyRef.getIndex(), submittedUpdate); } - synchronized void clear(@Nullable UUID bodyUuid) { - if (bodyUuid != null) { - submittedUpdates.remove(bodyUuid); + synchronized void clear(@Nullable Ref anchorBodyRef) { + if (anchorBodyRef != null) { + submittedUpdates.remove(anchorBodyRef.getIndex()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 48d0375c..8a8026a8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -9,12 +9,11 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import java.util.UUID; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -36,36 +35,32 @@ public static void applyRelease(@Nonnull Store store, .getStore(); PhysicsStoreThreading.requireWorldThread(physicsStore, "apply PhysicsStore control-session release mutations"); - PhysicsIdentityIndexResource identity = physicsStore.getResource( - PhysicsIdentityIndexResource.getResourceType()); - JointKey controlJointKey = session.getControlJointKey(); - if (controlJointKey != null) { - disableJoint(physicsStore, identity, controlJointKey.value()); + Ref controlJointRef = session.getControlJointRef(); + if (controlJointRef != null) { + disableJoint(physicsStore, controlJointRef); } - UUID bodyUuid = session.getBodyUuid(); - if (bodyUuid != null) { + Ref bodyRef = session.getBodyRef(); + if (bodyRef != null) { restoreControlledBody(physicsStore, - identity, - bodyUuid, + bodyRef, session.getOriginalBodyType(), releaseVelocity(session)); } - UUID anchorBodyUuid = session.getAnchorBodyUuid(); - if (anchorBodyUuid != null) { - removeRow(physicsStore, identity, anchorBodyUuid, refForUuid(identity, anchorBodyUuid)); + Ref anchorBodyRef = session.getAnchorBodyRef(); + if (anchorBodyRef != null) { + removeRow(physicsStore, anchorBodyRef); } } private static void restoreControlledBody(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull PhysicsBodyType originalBodyType, @Nonnull Vector3f releaseVelocity) { - Ref bodyRef = refForUuid(identity, bodyUuid); - if (bodyRef == null || store.getComponent(bodyRef, BodyComponent.getComponentType()) == null) { + if (!isValidStoreRef(store, bodyRef) + || store.getComponent(bodyRef, BodyComponent.getComponentType()) == null) { return; } appendBodyCommand(store, bodyRef, BodyCommandComponent.setType(originalBodyType, true)); @@ -83,21 +78,13 @@ private static void appendBodyCommand(@Nonnull Store store, store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); } - @Nullable - private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID uuid) { - Ref ref = identity.getByUuid(uuid); - return ref != null && ref.isValid() ? ref : null; - } - private static void disableJoint(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID jointUuid) { - Ref ref = refForUuid(identity, jointUuid); - JointComponent joint = ref != null - ? store.getComponent(ref, JointComponent.getComponentType()) - : null; - if (ref == null || joint == null) { + @Nonnull Ref ref) { + if (!isValidStoreRef(store, ref)) { + return; + } + JointComponent joint = store.getComponent(ref, JointComponent.getComponentType()); + if (joint == null) { return; } JointComponent disabled = joint.clone(); @@ -106,16 +93,23 @@ private static void disableJoint(@Nonnull Store store, } private static void removeRow(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID uuid, - @Nullable Ref ref) { - if (ref == null || !ref.isValid()) { + @Nonnull Ref ref) { + if (!isValidStoreRef(store, ref)) { return; } - identity.removeUuid(uuid, ref); + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + if (uuid != null) { + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .removeUuid(uuid.getUuid(), ref); + } store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); } + private static boolean isValidStoreRef(@Nonnull Store store, + @Nullable Ref ref) { + return ref != null && ref.getStore() == store && ref.isValid(); + } + @Nonnull private static Vector3f releaseVelocity(@Nonnull PhysicsControlSessionComponent session) { if (session.getOriginalBodyType() == PhysicsBodyType.DYNAMIC) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 04d6f673..2618586e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -4,14 +4,19 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -48,9 +53,10 @@ public static boolean hasSession(@Nonnull Store store, } /** - * Starts or replaces the controller entity's Impulse control session and marks the body as - * externally controlled. + * Compatibility adapter for legacy body keys. Prefer the PhysicsStore ref overload when the + * caller already has live rows. */ + @Deprecated(forRemoval = true) public static void startSession(@Nonnull Store store, @Nonnull Ref controllerRef, @Nonnull RigidBodyKey bodyKey, @@ -60,7 +66,7 @@ public static void startSession(@Nonnull Store store, float grabDistance, @Nonnull Vector3f viewOffset, @Nonnull Vector3f previousTarget) { - startSession(store, + startSessionFromUuids(store, controllerRef, bodyKey.value(), anchorBodyKey.value(), @@ -73,9 +79,10 @@ public static void startSession(@Nonnull Store store, } /** - * Starts or replaces the controller entity's Impulse control session with the created control - * joint handle. + * Compatibility adapter for legacy body and joint keys. Prefer the PhysicsStore ref overload + * when the caller already has live rows. */ + @Deprecated(forRemoval = true) public static void startSession(@Nonnull Store store, @Nonnull Ref controllerRef, @Nonnull RigidBodyKey bodyKey, @@ -86,11 +93,11 @@ public static void startSession(@Nonnull Store store, float grabDistance, @Nonnull Vector3f viewOffset, @Nonnull Vector3f previousTarget) { - startSession(store, + startSessionFromUuids(store, controllerRef, bodyKey.value(), anchorBodyKey.value(), - controlJointKey, + controlJointKey != null ? controlJointKey.value() : null, targetRef, originalBodyType, grabDistance, @@ -99,9 +106,10 @@ public static void startSession(@Nonnull Store store, } /** - * Starts or replaces the controller entity's Impulse control session with the created control - * joint handle. + * Compatibility adapter for durable body UUIDs. Prefer the PhysicsStore ref overload when the + * caller already has live rows. */ + @Deprecated(forRemoval = true) public static void startSession(@Nonnull Store store, @Nonnull Ref controllerRef, @Nonnull UUID bodyUuid, @@ -112,23 +120,84 @@ public static void startSession(@Nonnull Store store, float grabDistance, @Nonnull Vector3f viewOffset, @Nonnull Vector3f previousTarget) { + startSessionFromUuids(store, + controllerRef, + bodyUuid, + anchorBodyUuid, + controlJointKey != null ? controlJointKey.value() : null, + targetRef, + originalBodyType, + grabDistance, + viewOffset, + previousTarget); + } + + /** + * Starts or replaces the controller entity's Impulse control session from durable row UUIDs. + * Prefer the ref overload when the caller already has live PhysicsStore row refs. + */ + private static void startSessionFromUuids(@Nonnull Store store, + @Nonnull Ref controllerRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID anchorBodyUuid, + @Nullable UUID controlJointUuid, + @Nullable Ref targetRef, + @Nonnull PhysicsBodyType originalBodyType, + float grabDistance, + @Nonnull Vector3f viewOffset, + @Nonnull Vector3f previousTarget) { + Store physicsStore = physicsStore(store); + PhysicsStoreThreading.requireWorldThread(physicsStore, + "resolve PhysicsStore control-session UUIDs"); + Ref bodyRef = requireRef(physicsStore, bodyUuid, "body"); + Ref anchorBodyRef = requireRef(physicsStore, anchorBodyUuid, "anchor body"); + Ref controlJointRef = controlJointUuid != null + ? requireRef(physicsStore, controlJointUuid, "control joint") + : null; + startSession(store, + controllerRef, + bodyRef, + anchorBodyRef, + controlJointRef, + targetRef, + originalBodyType, + grabDistance, + viewOffset, + previousTarget); + } + + /** + * Starts or replaces the controller entity's Impulse control session with live PhysicsStore + * row refs. + */ + public static void startSession(@Nonnull Store store, + @Nonnull Ref controllerRef, + @Nonnull Ref bodyRef, + @Nonnull Ref anchorBodyRef, + @Nullable Ref controlJointRef, + @Nullable Ref targetRef, + @Nonnull PhysicsBodyType originalBodyType, + float grabDistance, + @Nonnull Vector3f viewOffset, + @Nonnull Vector3f previousTarget) { requireAvailable(); ControlLifecycle.registerStore(store); + validateControlRefs(bodyRef, anchorBodyRef, controlJointRef); PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); ComponentType sessionType = PhysicsControlSessionComponent.getComponentType(); releaseSession(resource, store, controllerRef, sessionType); store.putComponent(controllerRef, sessionType, - new PhysicsControlSessionComponent(bodyUuid, - anchorBodyUuid, - controlJointKey, + new PhysicsControlSessionComponent(bodyRef, + anchorBodyRef, + controlJointRef, targetRef, originalBodyType, grabDistance, viewOffset, previousTarget)); - resource.markBodyControlled(bodyUuid); + resource.markBodyControlled(requireRowUuid(bodyRef, "body")); } /** @@ -166,9 +235,9 @@ private static void releaseSession(@Nonnull PhysicsWorldRuntimeResource resource @Nonnull Ref controllerRef, @Nonnull ComponentType sessionType, @Nonnull PhysicsControlSessionComponent session) { - UUID bodyUuid = session.getBodyUuid(); - UUID anchorBodyUuid = session.getAnchorBodyUuid(); - PhysicsKinematicControlSystem.clearMutationState(store, anchorBodyUuid); + Ref bodyRef = session.getBodyRef(); + PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyRef()); + UUID bodyUuid = rowUuid(bodyRef); if (bodyUuid != null) { resource.clearControlledBody(bodyUuid); } @@ -178,6 +247,71 @@ private static void releaseSession(@Nonnull PhysicsWorldRuntimeResource resource store.removeComponent(controllerRef, sessionType); } + @Nonnull + private static Store physicsStore(@Nonnull Store store) { + return ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() + .getStore(); + } + + @Nonnull + private static Ref requireRef(@Nonnull Store store, + @Nonnull UUID uuid, + @Nonnull String role) { + Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(uuid); + if (ref == null || !ref.isValid()) { + throw new IllegalArgumentException("PhysicsStore " + role + + " row is not loaded for uuid=" + uuid); + } + return ref; + } + + private static void validateControlRefs(@Nonnull Ref bodyRef, + @Nonnull Ref anchorBodyRef, + @Nullable Ref controlJointRef) { + Store store = bodyRef.getStore(); + PhysicsStoreThreading.requireWorldThread(store, + "start PhysicsStore control session"); + requireValidRef(bodyRef, "body"); + requireValidRef(anchorBodyRef, "anchor body"); + if (anchorBodyRef.getStore() != store + || (controlJointRef != null && controlJointRef.getStore() != store)) { + throw new IllegalArgumentException("PhysicsStore control-session refs must belong " + + "to the same PhysicsStore"); + } + if (controlJointRef != null) { + requireValidRef(controlJointRef, "control joint"); + } + } + + private static void requireValidRef(@Nonnull Ref ref, + @Nonnull String role) { + if (!ref.isValid()) { + throw new IllegalArgumentException("PhysicsStore control-session " + role + + " ref is not valid"); + } + } + + @Nullable + private static UUID rowUuid(@Nullable Ref ref) { + if (ref == null || !ref.isValid()) { + return null; + } + UuidComponent uuid = ref.getStore().getComponent(ref, UuidComponent.getComponentType()); + return uuid != null ? uuid.getUuid() : null; + } + + @Nonnull + private static UUID requireRowUuid(@Nonnull Ref ref, + @Nonnull String role) { + UUID uuid = rowUuid(ref); + if (uuid == null) { + throw new IllegalArgumentException("PhysicsStore control-session " + role + + " row has no UUID component"); + } + return uuid; + } + private static void requireAvailable() { ControlLifecycle.requireEnabled(); if (!ImpulseControllableComponent.isComponentTypeRegistered() diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 32626eb1..9610ce5e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -29,7 +29,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; @@ -151,9 +150,9 @@ private static void finishGrab(@Nonnull CommandContext ctx, PhysicsControlSessions.startSession(store, ref, - selection.bodyKey(), - physicsState.anchorBodyKey(), - physicsState.controlJointKey(), + selection.bodyRef(), + physicsState.anchorBodyRef(), + physicsState.controlJointRef(), selection.attachment(), physicsState.originalBodyType(), Math.max(selection.distance(), MIN_HOLD_DISTANCE), @@ -168,7 +167,7 @@ private static void finishGrab(@Nonnull CommandContext ctx, private static GrabPhysicsState createGrabControl(@Nonnull World world, @Nonnull SpaceId selectedSpaceId, @Nonnull HitSelection selection) { - RigidBodyStateView selectedState = bodyState(world, selection.bodyKey()); + RigidBodyStateView selectedState = bodyState(world, selection.bodyRef(), selection.bodyKey()); if (selectedState == null) { return null; } @@ -187,8 +186,8 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, Quaternionf inverseBodyRotation = selectedState.pose().rotation(); inverseBodyRotation.invert().transform(bodyLocalHit); - RigidBodyKey anchorBodyKey = RigidBodyKey.random(); - JointKey controlJointKey = JointKey.random(); + UUID anchorBodyUuid = UUID.randomUUID(); + UUID controlJointUuid = UUID.randomUUID(); Ref selectedBodyRef = selection.bodyRef(); if (!selectedBodyRef.isValid()) { return null; @@ -197,15 +196,18 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, selectedBodyRef, BodyCommandComponent.wake()); try { - ExamplePhysicsUtils.addPhysicsStoreBody(world, - anchorBodyRow(spaceUuid, anchorBodyKey.value(), hitPoint)); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, - controlJointKey.value(), - controlJoint(spaceUuid, anchorBodyKey, selection.bodyKey(), bodyLocalHit)); + Ref anchorBodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, + anchorBodyRow(spaceUuid, anchorBodyUuid, hitPoint)); + Ref controlJointRef = ExamplePhysicsUtils.addPhysicsStoreJoint(world, + controlJointUuid, + controlJoint(spaceUuid, anchorBodyUuid, selection.bodyKey().value(), bodyLocalHit)); + return new GrabPhysicsState(selectedState.bodyType(), + anchorBodyRef, + controlJointRef, + hitPoint); } catch (IllegalStateException exception) { return null; } - return new GrabPhysicsState(selectedState.bodyType(), anchorBodyKey, controlJointKey, hitPoint); } @Nonnull @@ -258,13 +260,13 @@ private static TargetComponent initialAnchorTarget(@Nonnull Vector3f hitPoint) { @Nonnull private static JointComponent controlJoint(@Nonnull UUID spaceUuid, - @Nonnull RigidBodyKey anchorBodyKey, - @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID anchorBodyUuid, + @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyLocalHit) { JointComponent joint = new JointComponent(); joint.setSpaceUuid(spaceUuid); - joint.setBodyAUuid(anchorBodyKey.value()); - joint.setBodyBUuid(bodyKey.value()); + joint.setBodyAUuid(anchorBodyUuid); + joint.setBodyBUuid(bodyUuid); joint.setType(JointType.POINT); joint.setAnchorA(new Vector3f()); joint.setAnchorB(bodyLocalHit); @@ -323,13 +325,14 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource @Nullable private static RigidBodyStateView bodyState(@Nonnull World world, + @Nonnull Ref bodyRef, @Nonnull RigidBodyKey bodyKey) { Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); PhysicsStoreThreading.requireWorldThread(store, "read copied PhysicsStore grab body snapshot"); PhysicsStoreBodySnapshot body = store .getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(bodyKey.value()); + .getBody(bodyRef); return body != null ? new RigidBodyStateView(bodyKey, body.bodyType(), @@ -381,8 +384,8 @@ private record AttachmentSelection(@Nullable Ref controllableAttach } private record GrabPhysicsState(@Nonnull PhysicsBodyType originalBodyType, - @Nonnull RigidBodyKey anchorBodyKey, - @Nonnull JointKey controlJointKey, + @Nonnull Ref anchorBodyRef, + @Nonnull Ref controlJointRef, @Nonnull Vector3f hitPoint) { } } From 55276c055e7cf2aa4ddf538da5a39afdc3892dc0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 09:28:35 +0200 Subject: [PATCH 165/534] refactor(examples): keep physics row refs in command flow Signed-off-by: Blovien --- .../examples/commands/ExamplePhysicsUtils.java | 13 ------------- .../commands/PhysicsStoreExampleCommands.java | 13 ++++++++++--- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 09485c6a..af1965b2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -27,7 +27,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; @@ -140,18 +139,6 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store ref) { - Objects.requireNonNull(ref, "ref"); - if (!ref.isValid()) { - return null; - } - Store store = ref.getStore(); - PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore row UUID"); - UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); - return uuid != null ? uuid.getUuid() : null; - } - @Nonnull public static Ref addPhysicsStoreJoint(@Nonnull World world, @Nonnull UUID jointUuid, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index cfefeba6..124c8543 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -25,6 +25,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -122,9 +123,8 @@ private void applyImpulse(@Nonnull CommandContext ctx, 0.0f, 0.0f)); - UUID bodyUuid = ExamplePhysicsUtils.physicsStoreRowUuid(bodyRef); ctx.sender().sendMessage(Message.raw("Queued PhysicsStore impulse command for " - + (bodyUuid != null ? bodyUuid : bodyRef) + ".")); + + bodyRef + ".")); } } @@ -218,7 +218,7 @@ private static void attachView(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("No rigid body in view.")); return; } - UUID bodyUuid = ExamplePhysicsUtils.physicsStoreRowUuid(hit.bodyRef()); + UUID bodyUuid = physicsStoreBodyUuid(hit.bodyRef()); if (bodyUuid == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore body has no persistent UUID.")); return; @@ -235,6 +235,13 @@ private static void attachView(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Attached view-only entity to " + bodyUuid + ".")); } + + @Nullable + private static UUID physicsStoreBodyUuid(@Nonnull Ref bodyRef) { + UuidComponent uuid = bodyRef.getStore() + .getComponent(bodyRef, UuidComponent.getComponentType()); + return uuid != null ? uuid.getUuid() : null; + } } static final class ExplosiveCommand extends PhysicsStorePlayerCommand { From a716f5883eeeecaf8bf4d3b5b4518ac976801d19 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 09:31:27 +0200 Subject: [PATCH 166/534] refactor(physicsstore): key copied ref indexes by row Signed-off-by: Blovien --- .../PhysicsBodyRegistrationResource.java | 27 ++++++----- .../resources/PhysicsSnapshotResource.java | 27 ++++++----- .../PhysicsProjectionIndexResource.java | 48 +++++++++++-------- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index 83f82035..5c656903 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import java.util.ArrayList; import java.util.Collection; @@ -42,7 +43,8 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUui @Nullable public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { - return registrations.viewsByRef().get(Objects.requireNonNull(bodyRef, "bodyRef")); + return registrations.viewsByRowIndex().get(Objects.requireNonNull(bodyRef, "bodyRef") + .getIndex()); } @Nonnull @@ -91,18 +93,18 @@ public void publish(@Nonnull Collection publication new Object2ObjectLinkedOpenHashMap<>(); Object2ObjectLinkedOpenHashMap viewsByUuid = new Object2ObjectLinkedOpenHashMap<>(); - Object2ObjectLinkedOpenHashMap, PhysicsBodyRegistrationView> viewsByRef = - new Object2ObjectLinkedOpenHashMap<>(); + Int2ObjectOpenHashMap viewsByRowIndex = + new Int2ObjectOpenHashMap<>(); for (BodyRegistrationPublication publication : publicationsByKey.values()) { PhysicsBodyRegistrationView registration = publication.view(); viewsByKey.put(registration.bodyKey(), registration); viewsByUuid.put(registration.bodyKey().value(), registration); - viewsByRef.put(publication.bodyRef(), registration); + viewsByRowIndex.put(publication.bodyRef().getIndex(), registration); } registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), Map.copyOf(viewsByKey), Map.copyOf(viewsByUuid), - Map.copyOf(viewsByRef)); + viewsByRowIndex); } public void removeBody(@Nonnull RigidBodyKey bodyKey) { @@ -116,14 +118,14 @@ public void removeBody(@Nonnull RigidBodyKey bodyKey) { Object2ObjectLinkedOpenHashMap viewsByUuid = new Object2ObjectLinkedOpenHashMap<>(current.viewsByUuid()); viewsByUuid.remove(bodyKey.value()); - Object2ObjectLinkedOpenHashMap, PhysicsBodyRegistrationView> viewsByRef = - new Object2ObjectLinkedOpenHashMap<>(current.viewsByRef()); - viewsByRef.object2ObjectEntrySet() + Int2ObjectOpenHashMap viewsByRowIndex = + new Int2ObjectOpenHashMap<>(current.viewsByRowIndex()); + viewsByRowIndex.int2ObjectEntrySet() .removeIf(entry -> entry.getValue().bodyKey().equals(bodyKey)); registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), Map.copyOf(viewsByKey), Map.copyOf(viewsByUuid), - Map.copyOf(viewsByRef)); + viewsByRowIndex); } public void clear() { @@ -157,9 +159,12 @@ private record PublishedRegistrations( @Nonnull List views, @Nonnull Map viewsByKey, @Nonnull Map viewsByUuid, - @Nonnull Map, PhysicsBodyRegistrationView> viewsByRef) { + @Nonnull Int2ObjectOpenHashMap viewsByRowIndex) { private static final PublishedRegistrations EMPTY = - new PublishedRegistrations(List.of(), Map.of(), Map.of(), Map.of()); + new PublishedRegistrations(List.of(), + Map.of(), + Map.of(), + new Int2ObjectOpenHashMap<>()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index 9a34eeba..148b04e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -7,10 +7,12 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -38,23 +40,24 @@ public PhysicsStoreBodySnapshot getBody(@Nonnull UUID bodyUuid) { @Nullable public PhysicsStoreBodySnapshot getBody(@Nonnull Ref bodyRef) { - return snapshot.bodiesByRef().get(bodyRef); + return snapshot.bodiesByRowIndex().get(Objects.requireNonNull(bodyRef, "bodyRef") + .getIndex()); } public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); - Map, PhysicsStoreBodySnapshot> bodiesByRef = - new Object2ObjectOpenHashMap<>(); + Int2ObjectOpenHashMap bodiesByRowIndex = + new Int2ObjectOpenHashMap<>(); for (PhysicsStoreBodySnapshot body : frame.bodies()) { bodiesByUuid.put(body.bodyUuid(), body); Ref bodyRef = body.bodyRef(); if (bodyRef != null) { - bodiesByRef.put(bodyRef, body); + bodiesByRowIndex.put(bodyRef.getIndex(), body); } } snapshot = new PublishedSnapshot(frame, Map.copyOf(bodiesByUuid), - Map.copyOf(bodiesByRef)); + bodiesByRowIndex); } public void removeBody(@Nonnull UUID bodyUuid) { @@ -74,8 +77,8 @@ private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, @Nonnull UUID bodyUuid) { List bodies = new ArrayList<>(); Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); - Map, PhysicsStoreBodySnapshot> bodiesByRef = - new Object2ObjectOpenHashMap<>(); + Int2ObjectOpenHashMap bodiesByRowIndex = + new Int2ObjectOpenHashMap<>(); for (PhysicsStoreBodySnapshot body : current.frame().bodies()) { if (bodyUuid.equals(body.bodyUuid())) { continue; @@ -84,7 +87,7 @@ private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, bodiesByUuid.put(body.bodyUuid(), body); Ref bodyRef = body.bodyRef(); if (bodyRef != null) { - bodiesByRef.put(bodyRef, body); + bodiesByRowIndex.put(bodyRef.getIndex(), body); } } return new PublishedSnapshot( @@ -92,7 +95,7 @@ private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, current.frame().dt(), bodies), Map.copyOf(bodiesByUuid), - Map.copyOf(bodiesByRef)); + bodiesByRowIndex); } @Nonnull @@ -111,9 +114,11 @@ public static ResourceType getResourceTyp private record PublishedSnapshot( @Nonnull PhysicsStoreSnapshotFrame frame, @Nonnull Map bodiesByUuid, - @Nonnull Map, PhysicsStoreBodySnapshot> bodiesByRef) { + @Nonnull Int2ObjectOpenHashMap bodiesByRowIndex) { private static final PublishedSnapshot EMPTY = - new PublishedSnapshot(PhysicsStoreSnapshotFrame.EMPTY, Map.of(), Map.of()); + new PublishedSnapshot(PhysicsStoreSnapshotFrame.EMPTY, + Map.of(), + new Int2ObjectOpenHashMap<>()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index 0361dacc..89230c49 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.ImpulsePlugin; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; @@ -25,12 +26,12 @@ public final class PhysicsProjectionIndexResource implements Resource>> bodyAttachments = new Object2ObjectOpenHashMap<>(); - private final Map, Set>> bodyAttachmentsByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap>> bodyAttachmentsByRowIndex = + new Int2ObjectOpenHashMap<>(); private final Map> generatedVisualProxies = new Object2ObjectOpenHashMap<>(); - private final Map, Ref> generatedVisualProxiesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap> generatedVisualProxiesByRowIndex = + new Int2ObjectOpenHashMap<>(); public synchronized void registerAttachment(@Nonnull UUID bodyUuid, @Nonnull Ref attachment) { @@ -43,7 +44,8 @@ public synchronized void registerAttachment(@Nonnull UUID bodyUuid, bodyAttachments.computeIfAbsent(bodyUuid, _ -> new ObjectOpenHashSet<>()) .add(attachment); if (bodyRef != null) { - bodyAttachmentsByRef.computeIfAbsent(bodyRef, _ -> new ObjectOpenHashSet<>()) + bodyAttachmentsByRowIndex.computeIfAbsent(bodyRef.getIndex(), + _ -> new ObjectOpenHashSet<>()) .add(attachment); } } @@ -75,7 +77,7 @@ public Collection> getAttachments(@Nonnull UUID bodyUuid) { @Nonnull public Collection> getAttachments(@Nonnull Ref bodyRef) { - return liveAttachments(bodyAttachmentsByRef, bodyRef); + return liveAttachments(bodyAttachmentsByRowIndex, bodyRef.getIndex()); } @Nonnull @@ -108,7 +110,7 @@ public boolean hasAttachments(@Nonnull UUID bodyUuid) { } public boolean hasAttachments(@Nonnull Ref bodyRef) { - return hasLiveAttachments(bodyAttachmentsByRef, bodyRef); + return hasLiveAttachments(bodyAttachmentsByRowIndex, bodyRef.getIndex()); } private boolean hasLiveAttachments(@Nonnull Map>> attachmentsByKey, @@ -141,7 +143,7 @@ public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid) { @Nullable public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { - return liveGeneratedVisualProxy(generatedVisualProxiesByRef, bodyRef); + return liveGeneratedVisualProxy(generatedVisualProxiesByRowIndex, bodyRef.getIndex()); } @Nullable @@ -169,7 +171,7 @@ public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, synchronized (this) { generatedVisualProxies.put(bodyUuid, proxy); if (bodyRef != null) { - generatedVisualProxiesByRef.put(bodyRef, proxy); + generatedVisualProxiesByRowIndex.put(bodyRef.getIndex(), proxy); } } } @@ -215,10 +217,11 @@ public void updateAttachmentBodyRef(@Nonnull UUID bodyUuid, } } if (newBodyRef != null) { - bodyAttachmentsByRef.computeIfAbsent(newBodyRef, _ -> new ObjectOpenHashSet<>()) + bodyAttachmentsByRowIndex.computeIfAbsent(newBodyRef.getIndex(), + _ -> new ObjectOpenHashSet<>()) .add(attachment); if (generatedProxy) { - generatedVisualProxiesByRef.put(newBodyRef, attachment); + generatedVisualProxiesByRowIndex.put(newBodyRef.getIndex(), attachment); } } } @@ -232,13 +235,12 @@ public PhysicsProjectionIndexResource clone() { for (Map.Entry>> entry : bodyAttachments.entrySet()) { copy.bodyAttachments.put(entry.getKey(), new ObjectOpenHashSet<>(entry.getValue())); } - for (Map.Entry, Set>> entry : - bodyAttachmentsByRef.entrySet()) { - copy.bodyAttachmentsByRef.put(entry.getKey(), + for (var entry : bodyAttachmentsByRowIndex.int2ObjectEntrySet()) { + copy.bodyAttachmentsByRowIndex.put(entry.getIntKey(), new ObjectOpenHashSet<>(entry.getValue())); } copy.generatedVisualProxies.putAll(generatedVisualProxies); - copy.generatedVisualProxiesByRef.putAll(generatedVisualProxiesByRef); + copy.generatedVisualProxiesByRowIndex.putAll(generatedVisualProxiesByRowIndex); } return copy; } @@ -249,26 +251,32 @@ public static ResourceType getResou private void unregisterAttachmentRef(@Nonnull Ref bodyRef, @Nonnull Ref attachment) { - Set> attachments = bodyAttachmentsByRef.get(bodyRef); + int rowIndex = bodyRef.getIndex(); + Set> attachments = bodyAttachmentsByRowIndex.get(rowIndex); if (attachments == null) { return; } attachments.remove(attachment); if (attachments.isEmpty()) { - bodyAttachmentsByRef.remove(bodyRef); + bodyAttachmentsByRowIndex.remove(rowIndex); } } private void clearGeneratedVisualProxyRef(@Nonnull Ref bodyRef, @Nonnull Ref expectedProxy) { - Ref proxy = generatedVisualProxiesByRef.get(bodyRef); + int rowIndex = bodyRef.getIndex(); + Ref proxy = generatedVisualProxiesByRowIndex.get(rowIndex); if (sameRef(proxy, expectedProxy)) { - generatedVisualProxiesByRef.remove(bodyRef); + generatedVisualProxiesByRowIndex.remove(rowIndex); } } private static boolean sameRef(@Nullable Ref first, @Nullable Ref second) { - return first == second || (first != null && first.equals(second)); + return first == second + || (first != null + && second != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); } } From 82ee5cfa4dd76bb490dbe94cfb75a8d9cccbf781 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 09:35:58 +0200 Subject: [PATCH 167/534] refactor(physicsstore): key runtime bindings by row Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 277 +++++++++++------- 1 file changed, 168 insertions(+), 109 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index a9a84ff8..e919aa4c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -48,14 +48,14 @@ public final class PhysicsRuntimeResource implements Resource { private final Map> spaceRefsByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull - private final Map, UUID> spaceUuidsByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap spaceUuidsByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendSpaceHandle> spaceHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap spaceHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendId> backendIdsBySpaceRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap backendIdsBySpaceRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull private final Map bodyHandlesByUuid = new Object2ObjectOpenHashMap<>(); @@ -63,11 +63,14 @@ public final class PhysicsRuntimeResource implements Resource { private final Map bodySpaceHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendBodyHandle> bodyHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap> bodyRefsByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendSpaceHandle> bodySpaceHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap bodyHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); + @Nonnull + private final Int2ObjectOpenHashMap bodySpaceHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull private final Map jointHandlesByUuid = new Object2ObjectOpenHashMap<>(); @@ -78,11 +81,11 @@ public final class PhysicsRuntimeResource implements Resource { private final Map> jointRefsByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendJointHandle> jointHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap jointHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendSpaceHandle> jointSpaceHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap jointSpaceHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull private final Map terrainBodyHandlesByUuid = new Object2ObjectOpenHashMap<>(); @@ -99,17 +102,17 @@ public final class PhysicsRuntimeResource implements Resource { private final Map> terrainRefsByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull - private final Map, LongList> terrainBodyHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap terrainBodyHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendBodyHandle> terrainVoxelBodyHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap terrainVoxelBodyHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map, BackendSpaceHandle> terrainSpaceHandlesByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap terrainSpaceHandlesByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map, String> terrainPayloadKeysByRef = - new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap terrainPayloadKeysByRowIndex = + new Int2ObjectOpenHashMap<>(); @Nonnull private final Int2ObjectOpenHashMap bodyHandlesBySpaceHandle = new Int2ObjectOpenHashMap<>(); @@ -122,8 +125,8 @@ public final class PhysicsRuntimeResource implements Resource { @Nonnull private final List pendingBodyOperations = new ArrayList<>(); @Nonnull - private final ObjectOpenHashSet> pendingSpaceSettings = - new ObjectOpenHashSet<>(); + private final Int2ObjectOpenHashMap> pendingSpaceSettingsByRowIndex = + new Int2ObjectOpenHashMap<>(); @Setter @Getter private boolean started; @@ -147,31 +150,33 @@ public void putSpaceBinding(@Nonnull UUID spaceUuid, Ref checkedSpaceRef = Objects.requireNonNull(spaceRef, "spaceRef"); Ref previousRef = spaceRefsByUuid.remove(spaceUuid); if (previousRef != null) { - spaceUuidsByRef.remove(previousRef); - backendIdsBySpaceRef.remove(previousRef); - spaceHandlesByRef.remove(previousRef); + int previousRowIndex = previousRef.getIndex(); + spaceUuidsByRowIndex.remove(previousRowIndex); + backendIdsBySpaceRowIndex.remove(previousRowIndex); + spaceHandlesByRowIndex.remove(previousRowIndex); } + int rowIndex = checkedSpaceRef.getIndex(); backendIdsBySpaceUuid.put(spaceUuid, backendId); spaceHandlesByUuid.put(spaceUuid, handle); spaceRefsByUuid.put(spaceUuid, checkedSpaceRef); - spaceUuidsByRef.put(checkedSpaceRef, spaceUuid); - backendIdsBySpaceRef.put(checkedSpaceRef, backendId); - spaceHandlesByRef.put(checkedSpaceRef, handle); + spaceUuidsByRowIndex.put(rowIndex, spaceUuid); + backendIdsBySpaceRowIndex.put(rowIndex, backendId); + spaceHandlesByRowIndex.put(rowIndex, handle); } @Nullable public BackendSpaceHandle getSpaceHandle(@Nonnull Ref spaceRef) { - return spaceHandlesByRef.get(spaceRef); + return spaceHandlesByRowIndex.get(spaceRef.getIndex()); } @Nullable public UUID getSpaceUuid(@Nonnull Ref spaceRef) { - return spaceUuidsByRef.get(spaceRef); + return spaceUuidsByRowIndex.get(spaceRef.getIndex()); } @Nullable public BackendId getSpaceBackendId(@Nonnull Ref spaceRef) { - return backendIdsBySpaceRef.get(spaceRef); + return backendIdsBySpaceRowIndex.get(spaceRef.getIndex()); } public void removeSpaceHandle(@Nonnull UUID spaceUuid) { @@ -179,9 +184,10 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { backendIdsBySpaceUuid.remove(spaceUuid); Ref spaceRef = spaceRefsByUuid.remove(spaceUuid); if (spaceRef != null) { - spaceUuidsByRef.remove(spaceRef); - spaceHandlesByRef.remove(spaceRef); - backendIdsBySpaceRef.remove(spaceRef); + int rowIndex = spaceRef.getIndex(); + spaceUuidsByRowIndex.remove(rowIndex); + spaceHandlesByRowIndex.remove(rowIndex); + backendIdsBySpaceRowIndex.remove(rowIndex); } if (removed != null) { LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); @@ -190,8 +196,10 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { bodyHitMetadataByHandle.remove(bodyHandle); BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(bodyHandle); if (metadata != null) { - bodyHandlesByRef.remove(metadata.bodyRef()); - bodySpaceHandlesByRef.remove(metadata.bodyRef()); + int rowIndex = metadata.bodyRef().getIndex(); + bodyRefsByRowIndex.remove(rowIndex); + bodyHandlesByRowIndex.remove(rowIndex); + bodySpaceHandlesByRowIndex.remove(rowIndex); } }); } @@ -206,8 +214,10 @@ public void putBodyHandle(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle handle) { bodyHandlesByUuid.put(bodyUuid, handle); bodySpaceHandlesByUuid.put(bodyUuid, spaceHandle); - bodyHandlesByRef.put(bodyRef, handle); - bodySpaceHandlesByRef.put(bodyRef, spaceHandle); + int rowIndex = bodyRef.getIndex(); + bodyRefsByRowIndex.put(rowIndex, bodyRef); + bodyHandlesByRowIndex.put(rowIndex, handle); + bodySpaceHandlesByRowIndex.put(rowIndex, spaceHandle); bodyHandlesBySpaceHandle.computeIfAbsent(spaceHandle.value(), _ -> new LongArrayList()) .add(handle.value()); bodySnapshotMetadataByHandle.put(handle.value(), @@ -216,19 +226,21 @@ public void putBodyHandle(@Nonnull UUID bodyUuid, @Nullable public BackendBodyHandle getBodyHandle(@Nonnull Ref bodyRef) { - return bodyHandlesByRef.get(bodyRef); + return bodyHandlesByRowIndex.get(bodyRef.getIndex()); } @Nullable public BackendSpaceHandle getBodySpaceHandle(@Nonnull Ref bodyRef) { - return bodySpaceHandlesByRef.get(bodyRef); + return bodySpaceHandlesByRowIndex.get(bodyRef.getIndex()); } public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); - BackendBodyHandle removedByRef = bodyHandlesByRef.remove(bodyRef); - BackendSpaceHandle spaceHandleByRef = bodySpaceHandlesByRef.remove(bodyRef); + int rowIndex = bodyRef.getIndex(); + bodyRefsByRowIndex.remove(rowIndex); + BackendBodyHandle removedByRef = bodyHandlesByRowIndex.remove(rowIndex); + BackendSpaceHandle spaceHandleByRef = bodySpaceHandlesByRowIndex.remove(rowIndex); removeBodyHandleIndexes(removed != null ? removed : removedByRef, spaceHandle != null ? spaceHandle : spaceHandleByRef); } @@ -238,9 +250,12 @@ public List> bodyRefsForSpaceHandle( @Nonnull BackendSpaceHandle spaceHandle) { List> bodyRefs = new ArrayList<>(); int targetSpaceHandle = spaceHandle.value(); - bodySpaceHandlesByRef.forEach((bodyRef, handle) -> { + bodySpaceHandlesByRowIndex.forEach((rowIndex, handle) -> { if (handle.value() == targetSpaceHandle) { - bodyRefs.add(bodyRef); + Ref bodyRef = bodyRefsByRowIndex.get((int) rowIndex); + if (bodyRef != null) { + bodyRefs.add(bodyRef); + } } }); return bodyRefs; @@ -278,20 +293,23 @@ public void enqueuePendingBodyOperation(@Nonnull PendingBodyOperation operation) } public void markSpaceSettingsPending(@Nonnull Ref spaceRef) { - pendingSpaceSettings.add(Objects.requireNonNull(spaceRef, "spaceRef")); + Ref checkedSpaceRef = Objects.requireNonNull(spaceRef, "spaceRef"); + pendingSpaceSettingsByRowIndex.put(checkedSpaceRef.getIndex(), checkedSpaceRef); } public void clearPendingSpaceSettings(@Nonnull Ref spaceRef) { - pendingSpaceSettings.remove(spaceRef); + pendingSpaceSettingsByRowIndex.remove(Objects.requireNonNull(spaceRef, "spaceRef") + .getIndex()); } @Nonnull public Set> drainPendingSpaceSettings() { - if (pendingSpaceSettings.isEmpty()) { + if (pendingSpaceSettingsByRowIndex.isEmpty()) { return Set.of(); } - Set> drained = new ObjectOpenHashSet<>(pendingSpaceSettings); - pendingSpaceSettings.clear(); + Set> drained = new ObjectOpenHashSet<>( + pendingSpaceSettingsByRowIndex.values()); + pendingSpaceSettingsByRowIndex.clear(); return drained; } @@ -312,14 +330,16 @@ private void putJointHandle(@Nonnull UUID jointUuid, Ref checkedJointRef = Objects.requireNonNull(jointRef, "jointRef"); Ref previousRef = jointRefsByUuid.remove(jointUuid); if (previousRef != null) { - jointHandlesByRef.remove(previousRef); - jointSpaceHandlesByRef.remove(previousRef); + int previousRowIndex = previousRef.getIndex(); + jointHandlesByRowIndex.remove(previousRowIndex); + jointSpaceHandlesByRowIndex.remove(previousRowIndex); } + int rowIndex = checkedJointRef.getIndex(); jointHandlesByUuid.put(jointUuid, handle); jointSpaceHandlesByUuid.put(jointUuid, spaceHandle); jointRefsByUuid.put(jointUuid, checkedJointRef); - jointHandlesByRef.put(checkedJointRef, handle); - jointSpaceHandlesByRef.put(checkedJointRef, spaceHandle); + jointHandlesByRowIndex.put(rowIndex, handle); + jointSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); } public void putJointHandle(@Nonnull Ref jointRef, @@ -331,12 +351,12 @@ public void putJointHandle(@Nonnull Ref jointRef, @Nullable public BackendJointHandle getJointHandle(@Nonnull Ref jointRef) { - return jointHandlesByRef.get(jointRef); + return jointHandlesByRowIndex.get(jointRef.getIndex()); } @Nullable public BackendSpaceHandle getJointSpaceHandle(@Nonnull Ref jointRef) { - return jointSpaceHandlesByRef.get(jointRef); + return jointSpaceHandlesByRowIndex.get(jointRef.getIndex()); } public void removeJointHandle(@Nonnull UUID jointUuid) { @@ -344,16 +364,18 @@ public void removeJointHandle(@Nonnull UUID jointUuid) { jointSpaceHandlesByUuid.remove(jointUuid); Ref jointRef = jointRefsByUuid.remove(jointUuid); if (jointRef != null) { - jointHandlesByRef.remove(jointRef); - jointSpaceHandlesByRef.remove(jointRef); + int rowIndex = jointRef.getIndex(); + jointHandlesByRowIndex.remove(rowIndex); + jointSpaceHandlesByRowIndex.remove(rowIndex); } } public void removeJointHandle(@Nonnull UUID jointUuid, @Nonnull Ref jointRef) { removeJointHandle(jointUuid); - jointHandlesByRef.remove(jointRef); - jointSpaceHandlesByRef.remove(jointRef); + int rowIndex = jointRef.getIndex(); + jointHandlesByRowIndex.remove(rowIndex); + jointSpaceHandlesByRowIndex.remove(rowIndex); } @Nonnull @@ -361,9 +383,12 @@ public List> jointRefsForSpaceHandle( @Nonnull BackendSpaceHandle spaceHandle) { List> jointRefs = new ArrayList<>(); int targetSpaceHandle = spaceHandle.value(); - jointSpaceHandlesByRef.forEach((jointRef, handle) -> { + jointSpaceHandlesByRowIndex.forEach((rowIndex, handle) -> { if (handle.value() == targetSpaceHandle) { - jointRefs.add(jointRef); + Ref jointRef = jointRefForRowIndex((int) rowIndex); + if (jointRef != null) { + jointRefs.add(jointRef); + } } }); return jointRefs; @@ -389,11 +414,12 @@ public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, terrainVoxelBodyHandlesByUuid.put(terrainUuid, handle); } if (terrainRef != null) { - terrainSpaceHandlesByRef.put(terrainRef, spaceHandle); - terrainBodyHandlesByRef.computeIfAbsent(terrainRef, _ -> new LongArrayList()) + int rowIndex = terrainRef.getIndex(); + terrainSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); + terrainBodyHandlesByRowIndex.computeIfAbsent(rowIndex, _ -> new LongArrayList()) .add(handle.value()); if (voxelTerrainBody) { - terrainVoxelBodyHandlesByRef.put(terrainRef, handle); + terrainVoxelBodyHandlesByRowIndex.put(rowIndex, handle); } } } @@ -415,7 +441,7 @@ public void markTerrainPayloadBound(@Nonnull Ref terrainRef, @Nonnull String payloadKey) { bindTerrainRef(terrainUuid, terrainRef); terrainPayloadKeysByUuid.put(terrainUuid, payloadKey); - terrainPayloadKeysByRef.put(terrainRef, payloadKey); + terrainPayloadKeysByRowIndex.put(terrainRef.getIndex(), payloadKey); } public boolean isTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String payloadKey) { @@ -424,27 +450,27 @@ public boolean isTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String public boolean isTerrainPayloadBound(@Nonnull Ref terrainRef, @Nonnull String payloadKey) { - return payloadKey.equals(terrainPayloadKeysByRef.get(terrainRef)); + return payloadKey.equals(terrainPayloadKeysByRowIndex.get(terrainRef.getIndex())); } public boolean hasTerrainBodyHandles(@Nonnull Ref terrainRef) { - LongList bodyHandles = terrainBodyHandlesByRef.get(terrainRef); + LongList bodyHandles = terrainBodyHandlesByRowIndex.get(terrainRef.getIndex()); return bodyHandles != null && !bodyHandles.isEmpty(); } @Nullable public BackendSpaceHandle getTerrainSpaceHandle(@Nonnull Ref terrainRef) { - return terrainSpaceHandlesByRef.get(terrainRef); + return terrainSpaceHandlesByRowIndex.get(terrainRef.getIndex()); } @Nullable public BackendBodyHandle getTerrainVoxelBodyHandle(@Nonnull Ref terrainRef) { - return terrainVoxelBodyHandlesByRef.get(terrainRef); + return terrainVoxelBodyHandlesByRowIndex.get(terrainRef.getIndex()); } public void forEachTerrainBodyHandle(@Nonnull Ref terrainRef, @Nonnull LongConsumer consumer) { - LongList bodyHandles = terrainBodyHandlesByRef.get(terrainRef); + LongList bodyHandles = terrainBodyHandlesByRowIndex.get(terrainRef.getIndex()); if (bodyHandles == null) { return; } @@ -481,19 +507,24 @@ public List> terrainRefsForSpaceHandle( @Nonnull BackendSpaceHandle spaceHandle) { List> terrainRefs = new ArrayList<>(); int targetSpaceHandle = spaceHandle.value(); - terrainSpaceHandlesByRef.forEach((terrainRef, handle) -> { + terrainSpaceHandlesByRowIndex.forEach((rowIndex, handle) -> { if (handle.value() == targetSpaceHandle) { - terrainRefs.add(terrainRef); + Ref terrainRef = terrainRefForRowIndex((int) rowIndex); + if (terrainRef != null) { + terrainRefs.add(terrainRef); + } } }); return terrainRefs; } public void forEachRuntimeSpaceBinding(@Nonnull RuntimeSpaceBindingConsumer consumer) { - spaceHandlesByRef.forEach((spaceRef, spaceHandle) -> { - BackendId backendId = backendIdsBySpaceRef.get(spaceRef); + spaceRefsByUuid.values().forEach(spaceRef -> { + int rowIndex = spaceRef.getIndex(); + BackendSpaceHandle spaceHandle = spaceHandlesByRowIndex.get(rowIndex); + BackendId backendId = backendIdsBySpaceRowIndex.get(rowIndex); PhysicsBackendRuntime runtime = backendId != null ? runtimesByBackend.get(backendId) : null; - if (backendId != null && runtime != null) { + if (spaceHandle != null && backendId != null && runtime != null) { consumer.accept(spaceRef, backendId, spaceHandle, runtime); } }); @@ -513,32 +544,33 @@ public void clear() { spaceHandlesByUuid.clear(); backendIdsBySpaceUuid.clear(); spaceRefsByUuid.clear(); - spaceUuidsByRef.clear(); - spaceHandlesByRef.clear(); - backendIdsBySpaceRef.clear(); + spaceUuidsByRowIndex.clear(); + spaceHandlesByRowIndex.clear(); + backendIdsBySpaceRowIndex.clear(); bodyHandlesByUuid.clear(); bodySpaceHandlesByUuid.clear(); - bodyHandlesByRef.clear(); - bodySpaceHandlesByRef.clear(); + bodyRefsByRowIndex.clear(); + bodyHandlesByRowIndex.clear(); + bodySpaceHandlesByRowIndex.clear(); jointHandlesByUuid.clear(); jointSpaceHandlesByUuid.clear(); jointRefsByUuid.clear(); - jointHandlesByRef.clear(); - jointSpaceHandlesByRef.clear(); + jointHandlesByRowIndex.clear(); + jointSpaceHandlesByRowIndex.clear(); terrainBodyHandlesByUuid.clear(); terrainVoxelBodyHandlesByUuid.clear(); terrainSpaceHandlesByUuid.clear(); terrainPayloadKeysByUuid.clear(); terrainRefsByUuid.clear(); - terrainBodyHandlesByRef.clear(); - terrainVoxelBodyHandlesByRef.clear(); - terrainSpaceHandlesByRef.clear(); - terrainPayloadKeysByRef.clear(); + terrainBodyHandlesByRowIndex.clear(); + terrainVoxelBodyHandlesByRowIndex.clear(); + terrainSpaceHandlesByRowIndex.clear(); + terrainPayloadKeysByRowIndex.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); bodySnapshotMetadataByHandle.clear(); pendingBodyOperations.clear(); - pendingSpaceSettings.clear(); + pendingSpaceSettingsByRowIndex.clear(); started = false; } @@ -561,8 +593,10 @@ private void removeBodyHandleIndexes(@Nullable BackendBodyHandle removed, bodyHitMetadataByHandle.remove(removed.value()); BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(removed.value()); if (metadata != null) { - bodyHandlesByRef.remove(metadata.bodyRef()); - bodySpaceHandlesByRef.remove(metadata.bodyRef()); + int rowIndex = metadata.bodyRef().getIndex(); + bodyRefsByRowIndex.remove(rowIndex); + bodyHandlesByRowIndex.remove(rowIndex); + bodySpaceHandlesByRowIndex.remove(rowIndex); } } @@ -662,35 +696,37 @@ public PhysicsRuntimeResource clone() { copy.spaceHandlesByUuid.putAll(spaceHandlesByUuid); copy.backendIdsBySpaceUuid.putAll(backendIdsBySpaceUuid); copy.spaceRefsByUuid.putAll(spaceRefsByUuid); - copy.spaceUuidsByRef.putAll(spaceUuidsByRef); - copy.spaceHandlesByRef.putAll(spaceHandlesByRef); - copy.backendIdsBySpaceRef.putAll(backendIdsBySpaceRef); + copy.spaceUuidsByRowIndex.putAll(spaceUuidsByRowIndex); + copy.spaceHandlesByRowIndex.putAll(spaceHandlesByRowIndex); + copy.backendIdsBySpaceRowIndex.putAll(backendIdsBySpaceRowIndex); copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); - copy.bodyHandlesByRef.putAll(bodyHandlesByRef); - copy.bodySpaceHandlesByRef.putAll(bodySpaceHandlesByRef); + copy.bodyRefsByRowIndex.putAll(bodyRefsByRowIndex); + copy.bodyHandlesByRowIndex.putAll(bodyHandlesByRowIndex); + copy.bodySpaceHandlesByRowIndex.putAll(bodySpaceHandlesByRowIndex); copy.jointHandlesByUuid.putAll(jointHandlesByUuid); copy.jointSpaceHandlesByUuid.putAll(jointSpaceHandlesByUuid); copy.jointRefsByUuid.putAll(jointRefsByUuid); - copy.jointHandlesByRef.putAll(jointHandlesByRef); - copy.jointSpaceHandlesByRef.putAll(jointSpaceHandlesByRef); + copy.jointHandlesByRowIndex.putAll(jointHandlesByRowIndex); + copy.jointSpaceHandlesByRowIndex.putAll(jointSpaceHandlesByRowIndex); terrainBodyHandlesByUuid.forEach((terrainUuid, bodyHandles) -> copy.terrainBodyHandlesByUuid.put(terrainUuid, new LongArrayList(bodyHandles))); copy.terrainVoxelBodyHandlesByUuid.putAll(terrainVoxelBodyHandlesByUuid); copy.terrainSpaceHandlesByUuid.putAll(terrainSpaceHandlesByUuid); copy.terrainPayloadKeysByUuid.putAll(terrainPayloadKeysByUuid); copy.terrainRefsByUuid.putAll(terrainRefsByUuid); - terrainBodyHandlesByRef.forEach((terrainRef, bodyHandles) -> - copy.terrainBodyHandlesByRef.put(terrainRef, new LongArrayList(bodyHandles))); - copy.terrainVoxelBodyHandlesByRef.putAll(terrainVoxelBodyHandlesByRef); - copy.terrainSpaceHandlesByRef.putAll(terrainSpaceHandlesByRef); - copy.terrainPayloadKeysByRef.putAll(terrainPayloadKeysByRef); + terrainBodyHandlesByRowIndex.forEach((rowIndex, bodyHandles) -> + copy.terrainBodyHandlesByRowIndex.put((int) rowIndex, + new LongArrayList(bodyHandles))); + copy.terrainVoxelBodyHandlesByRowIndex.putAll(terrainVoxelBodyHandlesByRowIndex); + copy.terrainSpaceHandlesByRowIndex.putAll(terrainSpaceHandlesByRowIndex); + copy.terrainPayloadKeysByRowIndex.putAll(terrainPayloadKeysByRowIndex); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); copy.bodySnapshotMetadataByHandle.putAll(bodySnapshotMetadataByHandle); copy.pendingBodyOperations.addAll(pendingBodyOperations); - copy.pendingSpaceSettings.addAll(pendingSpaceSettings); + copy.pendingSpaceSettingsByRowIndex.putAll(pendingSpaceSettingsByRowIndex); copy.started = started; return copy; } @@ -855,14 +891,37 @@ private void bindTerrainRef(@Nonnull UUID terrainUuid, } private void removeTerrainRefMaps(@Nonnull Ref terrainRef) { - terrainBodyHandlesByRef.remove(terrainRef); - terrainVoxelBodyHandlesByRef.remove(terrainRef); - terrainSpaceHandlesByRef.remove(terrainRef); - terrainPayloadKeysByRef.remove(terrainRef); + int rowIndex = terrainRef.getIndex(); + terrainBodyHandlesByRowIndex.remove(rowIndex); + terrainVoxelBodyHandlesByRowIndex.remove(rowIndex); + terrainSpaceHandlesByRowIndex.remove(rowIndex); + terrainPayloadKeysByRowIndex.remove(rowIndex); + } + + @Nullable + private Ref jointRefForRowIndex(int rowIndex) { + for (Ref jointRef : jointRefsByUuid.values()) { + if (jointRef.getIndex() == rowIndex) { + return jointRef; + } + } + return null; + } + + @Nullable + private Ref terrainRefForRowIndex(int rowIndex) { + for (Ref terrainRef : terrainRefsByUuid.values()) { + if (terrainRef.getIndex() == rowIndex) { + return terrainRef; + } + } + return null; } private static boolean sameRef(@Nonnull Ref first, @Nonnull Ref second) { - return first == second || first.equals(second); + return first == second + || (first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); } } From 6d47735326b0b638577ae56f00f4af0fd398b3bb Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 09:43:12 +0200 Subject: [PATCH 168/534] refactor(control): track controlled bodies by ref Signed-off-by: Blovien --- .../control/PhysicsControlRuntimeState.java | 50 +++++++++++++++ .../systems/PhysicsControlSessionCleanup.java | 18 +----- .../PhysicsWorldRuntimeResource.java | 63 ++++++++++++++++++- .../control/PhysicsControlSessions.java | 28 +-------- 4 files changed, 117 insertions(+), 42 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java index 78f0558f..b9ee596d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java @@ -1,6 +1,9 @@ package dev.hytalemodding.impulse.core.internal.modules.control; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; @@ -12,9 +15,15 @@ */ public final class PhysicsControlRuntimeState { + private final Int2ObjectOpenHashMap> controlledBodyRefsByRowIndex = + new Int2ObjectOpenHashMap<>(); private final Long2ObjectOpenHashMap controlledBodyLeastBitsByMostBits = new Long2ObjectOpenHashMap<>(); + public synchronized void markBodyControlled(@Nonnull Ref bodyRef) { + controlledBodyRefsByRowIndex.put(bodyRef.getIndex(), bodyRef); + } + public synchronized void markBodyControlled(@Nonnull UUID bodyUuid) { add(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); } @@ -27,10 +36,26 @@ public synchronized void clearControlledBody(@Nonnull UUID bodyUuid) { remove(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); } + public synchronized void clearControlledBody(@Nonnull Ref bodyRef) { + remove(bodyRef); + } + public synchronized void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { remove(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); } + public synchronized boolean isBodyControlled(@Nonnull Ref bodyRef) { + Ref controlledRef = controlledBodyRefsByRowIndex.get(bodyRef.getIndex()); + if (controlledRef == null) { + return false; + } + if (!controlledRef.isValid()) { + controlledBodyRefsByRowIndex.remove(bodyRef.getIndex()); + return false; + } + return controlledRef == bodyRef || sameLiveRef(controlledRef, bodyRef); + } + public synchronized boolean isBodyControlled(@Nonnull UUID bodyUuid) { return contains(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); } @@ -43,14 +68,39 @@ public synchronized void clearBody(@Nonnull UUID bodyUuid) { remove(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); } + public synchronized void clearBody(@Nonnull Ref bodyRef) { + remove(bodyRef); + } + public synchronized void clearBody(@Nonnull RigidBodyKey bodyKey) { remove(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); } public synchronized void clear() { + controlledBodyRefsByRowIndex.clear(); controlledBodyLeastBitsByMostBits.clear(); } + private void remove(@Nonnull Ref bodyRef) { + Ref controlledRef = controlledBodyRefsByRowIndex.get(bodyRef.getIndex()); + if (controlledRef == null) { + return; + } + if (controlledRef == bodyRef + || !controlledRef.isValid() + || sameLiveRef(controlledRef, bodyRef)) { + controlledBodyRefsByRowIndex.remove(bodyRef.getIndex()); + } + } + + private static boolean sameLiveRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.isValid() + && second.isValid() + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); + } + private void add(long mostSignificantBits, long leastSignificantBits) { controlledBodyLeastBitsByMostBits.computeIfAbsent(mostSignificantBits, _ -> new LongOpenHashSet()).add(leastSignificantBits); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java index a5697d39..f11c2cbc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java @@ -6,10 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import java.util.UUID; import javax.annotation.Nonnull; -import javax.annotation.Nullable; public final class PhysicsControlSessionCleanup { @@ -35,21 +32,12 @@ private static void cleanupInternal(@Nonnull Store store, return; } - UUID bodyUuid = rowUuid(session.getBodyRef()); - if (bodyUuid != null) { - resource.clearControlledBody(bodyUuid); + Ref bodyRef = session.getBodyRef(); + if (bodyRef != null) { + resource.clearControlledBody(bodyRef); } PhysicsStoreControlSessionMutations.applyRelease(store, session); session.deactivate(); } - - @Nullable - private static UUID rowUuid(@Nullable Ref ref) { - if (ref == null || !ref.isValid()) { - return null; - } - UuidComponent uuid = ref.getStore().getComponent(ref, UuidComponent.getComponentType()); - return uuid != null ? uuid.getUuid() : null; - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 548338f4..122eb2ef 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2093,28 +2093,82 @@ public long advanceVisualInterestTick() { return visualInterestTick.incrementAndGet(); } + public void markBodyControlled(@Nonnull Ref bodyRef) { + controlRuntime.markBodyControlled(bodyRef); + } + public void markBodyControlled(@Nonnull UUID bodyUuid) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, + "resolve controlled body UUID"); + if (bodyRef != null) { + controlRuntime.markBodyControlled(bodyRef); + } controlRuntime.markBodyControlled(bodyUuid); } public void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve controlled body key"); + if (bodyRef != null) { + controlRuntime.markBodyControlled(bodyRef); + } controlRuntime.markBodyControlled(bodyKey); } + public void clearControlledBody(@Nonnull Ref bodyRef) { + controlRuntime.clearControlledBody(bodyRef); + } + public void clearControlledBody(@Nonnull UUID bodyUuid) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, + "resolve controlled body UUID"); + if (bodyRef != null) { + controlRuntime.clearControlledBody(bodyRef); + } controlRuntime.clearControlledBody(bodyUuid); } public void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve controlled body key"); + if (bodyRef != null) { + controlRuntime.clearControlledBody(bodyRef); + } controlRuntime.clearControlledBody(bodyKey); } + public boolean isBodyControlled(@Nonnull Ref bodyRef) { + return controlRuntime.isBodyControlled(bodyRef); + } + public boolean isBodyControlled(@Nonnull UUID bodyUuid) { - return controlRuntime.isBodyControlled(bodyUuid); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, + "resolve controlled body UUID"); + return bodyRef != null && controlRuntime.isBodyControlled(bodyRef) + || controlRuntime.isBodyControlled(bodyUuid); } public boolean isBodyControlled(@Nonnull RigidBodyKey bodyKey) { - return controlRuntime.isBodyControlled(bodyKey); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve controlled body key"); + return bodyRef != null && controlRuntime.isBodyControlled(bodyRef) + || controlRuntime.isBodyControlled(bodyKey); + } + + @Nullable + private Ref resolvePhysicsStoreBodyRef(@Nonnull UUID bodyUuid, + @Nonnull String operation) { + if (!hasAttachedAuthoritativePhysicsStore()) { + return null; + } + World world = requireAuthoritativeWorld(operation); + if (!world.isInThread()) { + return null; + } + Ref ref = physicsStore(world) + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(bodyUuid); + return ref != null && ref.isValid() ? ref : null; } public void disableControlLifecycle() { @@ -2186,6 +2240,11 @@ public PhysicsMutationHandle clearBodyRuntimeStateAsync( } private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve cleared body runtime key"); + if (bodyRef != null) { + controlRuntime.clearBody(bodyRef); + } bodyRuntime.clearBodyRuntimeState(bodyKey); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 2618586e..2895d78a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -16,7 +16,6 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -197,7 +196,7 @@ public static void startSession(@Nonnull Store store, grabDistance, viewOffset, previousTarget)); - resource.markBodyControlled(requireRowUuid(bodyRef, "body")); + resource.markBodyControlled(bodyRef); } /** @@ -237,9 +236,8 @@ private static void releaseSession(@Nonnull PhysicsWorldRuntimeResource resource @Nonnull PhysicsControlSessionComponent session) { Ref bodyRef = session.getBodyRef(); PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyRef()); - UUID bodyUuid = rowUuid(bodyRef); - if (bodyUuid != null) { - resource.clearControlledBody(bodyUuid); + if (bodyRef != null) { + resource.clearControlledBody(bodyRef); } PhysicsStoreControlSessionMutations.applyRelease(store, session); @@ -292,26 +290,6 @@ private static void requireValidRef(@Nonnull Ref ref, } } - @Nullable - private static UUID rowUuid(@Nullable Ref ref) { - if (ref == null || !ref.isValid()) { - return null; - } - UuidComponent uuid = ref.getStore().getComponent(ref, UuidComponent.getComponentType()); - return uuid != null ? uuid.getUuid() : null; - } - - @Nonnull - private static UUID requireRowUuid(@Nonnull Ref ref, - @Nonnull String role) { - UUID uuid = rowUuid(ref); - if (uuid == null) { - throw new IllegalArgumentException("PhysicsStore control-session " + role - + " row has no UUID component"); - } - return uuid; - } - private static void requireAvailable() { ControlLifecycle.requireEnabled(); if (!ImpulseControllableComponent.isComponentTypeRegistered() From 5cef639f4439bf18edb39df5ab0a458fd13c4c7a Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 09:56:32 +0200 Subject: [PATCH 169/534] refactor(worldcollision): track chunk boundary bodies by ref Signed-off-by: Blovien --- .../PhysicsChunkBoundaryRuntime.java | 188 +++++++++++++++++- .../systems/PhysicsChunkBoundarySystem.java | 184 ++++++++++++----- .../PhysicsBodyRegistrationResource.java | 33 ++- .../resources/PhysicsSnapshotResource.java | 12 +- .../PhysicsWorldRuntimeResource.java | 72 ++++++- 5 files changed, 430 insertions(+), 59 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java index 1eaea764..1987a9c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java @@ -1,15 +1,21 @@ package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; @@ -26,6 +32,12 @@ public final class PhysicsChunkBoundaryRuntime { new Object2ObjectOpenHashMap<>(); private final Map chunkBoundaryPauseStates = new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap> forcedContinuousCollisionBodyRefsByRowIndex = + new Int2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap> chunkBoundarySafeStatesByRowIndex = + new Int2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap> chunkBoundaryPauseStatesByRowIndex = + new Int2ObjectOpenHashMap<>(); public void updateChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey, @Nonnull Vector3f position, @@ -42,12 +54,35 @@ public void updateChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey, state.set(snapshot); } + public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation) { + ChunkBoundarySafeState state = rowState(chunkBoundarySafeStatesByRowIndex, + bodyRef, + ChunkBoundarySafeState::new); + state.set(position, rotation); + } + + public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, + @Nonnull PhysicsBodySnapshot snapshot) { + ChunkBoundarySafeState state = rowState(chunkBoundarySafeStatesByRowIndex, + bodyRef, + ChunkBoundarySafeState::new); + state.set(snapshot); + } + @Nullable public ChunkBoundarySafeState getChunkBoundarySafeState( @Nonnull RigidBodyKey bodyKey) { return chunkBoundarySafeStates.get(bodyKey); } + @Nullable + public ChunkBoundarySafeState getChunkBoundarySafeState( + @Nonnull Ref bodyRef) { + return getRowState(chunkBoundarySafeStatesByRowIndex, bodyRef); + } + public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, long targetChunkIndex, @Nonnull PhysicsBodyType originalBodyType, @@ -75,40 +110,109 @@ public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, state.set(targetChunkIndex, snapshot); } + public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, + long targetChunkIndex, + @Nonnull PhysicsBodyType originalBodyType, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity) { + ChunkBoundaryPauseState state = rowState(chunkBoundaryPauseStatesByRowIndex, + bodyRef, + ChunkBoundaryPauseState::new); + state.set(targetChunkIndex, originalBodyType, linearVelocity, angularVelocity); + } + + public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, + long targetChunkIndex, + @Nonnull long[] targetChunkIndices, + @Nonnull PhysicsBodySnapshot snapshot) { + ChunkBoundaryPauseState state = rowState(chunkBoundaryPauseStatesByRowIndex, + bodyRef, + ChunkBoundaryPauseState::new); + state.set(targetChunkIndex, targetChunkIndices, snapshot); + } + + public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, + long targetChunkIndex, + @Nonnull PhysicsBodySnapshot snapshot) { + ChunkBoundaryPauseState state = rowState(chunkBoundaryPauseStatesByRowIndex, + bodyRef, + ChunkBoundaryPauseState::new); + state.set(targetChunkIndex, snapshot); + } + @Nullable public ChunkBoundaryPauseState getChunkBoundaryPauseState( @Nonnull RigidBodyKey bodyKey) { return chunkBoundaryPauseStates.get(bodyKey); } + @Nullable + public ChunkBoundaryPauseState getChunkBoundaryPauseState( + @Nonnull Ref bodyRef) { + return getRowState(chunkBoundaryPauseStatesByRowIndex, bodyRef); + } + public void clearChunkBoundaryPauseState(@Nonnull RigidBodyKey bodyKey) { chunkBoundaryPauseStates.remove(bodyKey); } + public void clearChunkBoundaryPauseState(@Nonnull Ref bodyRef) { + removeRowState(chunkBoundaryPauseStatesByRowIndex, bodyRef); + } + @Nonnull public Collection getChunkBoundaryPausedBodyKeys() { return new ArrayList<>(chunkBoundaryPauseStates.keySet()); } + @Nonnull + public Collection> getChunkBoundaryPausedBodyRefs() { + return liveRefs(chunkBoundaryPauseStatesByRowIndex); + } + public void markContinuousCollisionForced(@Nonnull RigidBodyKey bodyKey) { forcedContinuousCollisionBodyKeys.add(bodyKey); } + public void markContinuousCollisionForced(@Nonnull Ref bodyRef) { + forcedContinuousCollisionBodyRefsByRowIndex.put(rowIndex(bodyRef), bodyRef); + } + @Nonnull public Collection getForcedContinuousCollisionBodyKeys() { return new ArrayList<>(forcedContinuousCollisionBodyKeys); } + @Nonnull + public Collection> getForcedContinuousCollisionBodyRefs() { + ArrayList> refs = new ArrayList<>(); + forcedContinuousCollisionBodyRefsByRowIndex.values() + .removeIf(ref -> ref == null || !ref.isValid()); + refs.addAll(forcedContinuousCollisionBodyRefsByRowIndex.values()); + return refs; + } + public boolean hasForcedContinuousCollisionBodies() { - return !forcedContinuousCollisionBodyKeys.isEmpty(); + forcedContinuousCollisionBodyRefsByRowIndex.values() + .removeIf(ref -> ref == null || !ref.isValid()); + return !forcedContinuousCollisionBodyKeys.isEmpty() + || !forcedContinuousCollisionBodyRefsByRowIndex.isEmpty(); } public void forEachForcedContinuousCollisionBody(@Nonnull Consumer consumer) { forcedContinuousCollisionBodyKeys.forEach(consumer); } + public void forEachForcedContinuousCollisionBodyRef( + @Nonnull Consumer> consumer) { + for (Ref ref : getForcedContinuousCollisionBodyRefs()) { + consumer.accept(ref); + } + } + public void clearForcedContinuousCollisionBodies() { forcedContinuousCollisionBodyKeys.clear(); + forcedContinuousCollisionBodyRefsByRowIndex.clear(); } public void clearBody(@Nonnull RigidBodyKey bodyKey) { @@ -117,15 +221,97 @@ public void clearBody(@Nonnull RigidBodyKey bodyKey) { chunkBoundaryPauseStates.remove(bodyKey); } + public void clearBody(@Nonnull Ref bodyRef) { + Ref forcedRef = + forcedContinuousCollisionBodyRefsByRowIndex.get(rowIndex(bodyRef)); + if (forcedRef != null && sameRef(forcedRef, bodyRef)) { + forcedContinuousCollisionBodyRefsByRowIndex.remove(bodyRef.getIndex()); + } + removeRowState(chunkBoundarySafeStatesByRowIndex, bodyRef); + removeRowState(chunkBoundaryPauseStatesByRowIndex, bodyRef); + } + public void clear() { forcedContinuousCollisionBodyKeys.clear(); chunkBoundarySafeStates.clear(); chunkBoundaryPauseStates.clear(); + forcedContinuousCollisionBodyRefsByRowIndex.clear(); + chunkBoundarySafeStatesByRowIndex.clear(); + chunkBoundaryPauseStatesByRowIndex.clear(); } public void clearChunkBoundaryStates() { chunkBoundarySafeStates.clear(); chunkBoundaryPauseStates.clear(); + chunkBoundarySafeStatesByRowIndex.clear(); + chunkBoundaryPauseStatesByRowIndex.clear(); + } + + @Nonnull + private static T rowState(@Nonnull Int2ObjectOpenHashMap> states, + @Nonnull Ref bodyRef, + @Nonnull Supplier factory) { + int rowIndex = rowIndex(bodyRef); + RowState row = states.get(rowIndex); + if (row == null || !sameRef(row.bodyRef(), bodyRef)) { + T state = factory.get(); + states.put(rowIndex, new RowState<>(bodyRef, state)); + return state; + } + return row.state(); + } + + @Nullable + private static T getRowState(@Nonnull Int2ObjectOpenHashMap> states, + @Nonnull Ref bodyRef) { + RowState row = states.get(rowIndex(bodyRef)); + return row != null && sameRef(row.bodyRef(), bodyRef) ? row.state() : null; + } + + private static void removeRowState(@Nonnull Int2ObjectOpenHashMap> states, + @Nonnull Ref bodyRef) { + RowState row = states.get(rowIndex(bodyRef)); + if (row != null && sameRef(row.bodyRef(), bodyRef)) { + states.remove(bodyRef.getIndex()); + } + } + + @Nonnull + private static Collection> liveRefs( + @Nonnull Int2ObjectOpenHashMap> states) { + ArrayList> refs = new ArrayList<>(); + ArrayList staleRows = new ArrayList<>(); + for (Int2ObjectMap.Entry> entry : states.int2ObjectEntrySet()) { + Ref ref = entry.getValue().bodyRef(); + if (ref != null && ref.isValid()) { + refs.add(ref); + } else { + staleRows.add(entry.getIntKey()); + } + } + for (int row : staleRows) { + states.remove(row); + } + return refs; + } + + private static int rowIndex(@Nonnull Ref bodyRef) { + return Objects.requireNonNull(bodyRef, "bodyRef").getIndex(); + } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getIndex() == second.getIndex() + && first.getStore() == second.getStore(); + } + + private record RowState(@Nonnull Ref bodyRef, + @Nonnull T state) { + + private RowState { + Objects.requireNonNull(bodyRef, "bodyRef"); + Objects.requireNonNull(state, "state"); + } } public static final class ChunkBoundaryPauseState { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java index ce17f0e9..83a78a97 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java @@ -17,6 +17,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; @@ -24,7 +25,8 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundarySafeState; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; @@ -38,12 +40,12 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.Set; -import java.util.UUID; import java.util.function.Consumer; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -55,9 +57,9 @@ /** * Keeps registered dynamic physics bodies from drifting into unloaded chunks. * - *

The body key is the identity boundary here. Entity views may be absent, stale, - * or generated later, so this system uses the body's last known safe pose instead - * of entity transforms.

+ *

Authoritative PhysicsStore bodies are tracked by live row refs. Legacy backend bodies keep + * the key compatibility path. Entity views may be absent, stale, or generated later, so this + * system uses the body's last known safe pose instead of entity transforms.

*/ public class PhysicsChunkBoundarySystem extends TickingSystem { @@ -84,19 +86,59 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { ChunkStore chunkStore = world.getChunkStore(); Store chunkComponentStore = chunkStore.getStore(); PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); + if (isAuthoritativePhysicsStoreActive()) { + processAuthoritativeBodies(world, + resource, + store, + chunkStore, + chunkComponentStore); + return; + } + for (PhysicsBodyRegistrationView registration : resource.getBodyRegistrationViews(PhysicsBodyKind.BODY)) { processBody(registration, resource, store, chunkStore, chunkComponentStore); } } + private void processAuthoritativeBodies(@Nonnull World world, + @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull Store store, + @Nonnull ChunkStore chunkStore, + @Nonnull Store chunkComponentStore) { + Store physics = ((PhysicsStoreWorld) world).getPhysicsStore() + .getStore(); + PhysicsStoreThreading.requireWorldThread(physics, + "process chunk-boundary PhysicsStore bodies"); + PhysicsBodyRegistrationResource registrations = + physics.getResource(PhysicsBodyRegistrationResource.getResourceType()); + for (PhysicsStoreBodySnapshot body : physics.getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame() + .bodies()) { + Ref bodyRef = body.bodyRef(); + if (bodyRef == null || !bodyRef.isValid()) { + continue; + } + PhysicsBodyRegistrationView registration = + registrations.getBodyRegistrationView(bodyRef); + if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { + continue; + } + processAuthoritativeBody(bodyRef, + registration, + resource, + store, + chunkStore, + chunkComponentStore); + } + } + private void processBody(@Nonnull PhysicsBodyRegistrationView registration, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Store store, @Nonnull ChunkStore chunkStore, @Nonnull Store chunkComponentStore) { - boolean authoritative = isAuthoritativePhysicsStoreActive(); - if (!authoritative && resource.getSpaceBinding(registration.spaceId()) == null) { + if (resource.getSpaceBinding(registration.spaceId()) == null) { return; } @@ -109,9 +151,7 @@ private void processBody(@Nonnull PhysicsBodyRegistrationView registration, return; } - PhysicsSpaceSettings settings = authoritative - ? resource.getSpaceSettings(registration.spaceId()) - : resource.getLiveSpaceSettings(registration.spaceId()); + PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(registration.spaceId()); EntityChunkBoundaryMode mode = settings.getWorldCollisionSettings().getEntityChunkBoundaryMode(); PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState = resource.getChunkBoundaryPauseState(bodyKey); @@ -123,8 +163,7 @@ private void processBody(@Nonnull PhysicsBodyRegistrationView registration, resource, store, chunkStore, - chunkComponentStore, - authoritative); + chunkComponentStore); return; } @@ -139,12 +178,49 @@ private void processBody(@Nonnull PhysicsBodyRegistrationView registration, return; } - if (authoritative) { - pauseBodyAuthoritative(store, bodyKey, snapshot, targetChunkIndices, resource); - } else { - PhysicsOwnerBridge.run(store, "pause chunk-boundary physics body", - () -> pauseBody(bodyKey, snapshot, targetChunkIndices, resource)); + PhysicsOwnerBridge.run(store, "pause chunk-boundary physics body", + () -> pauseBody(bodyKey, snapshot, targetChunkIndices, resource)); + } + + private void processAuthoritativeBody(@Nonnull Ref bodyRef, + @Nonnull PhysicsBodyRegistrationView registration, + @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull Store store, + @Nonnull ChunkStore chunkStore, + @Nonnull Store chunkComponentStore) { + PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyRef); + if (snapshot == null || snapshot.isStatic()) { + return; + } + + PhysicsSpaceSettings settings = resource.getSpaceSettings(registration.spaceId()); + EntityChunkBoundaryMode mode = settings.getWorldCollisionSettings().getEntityChunkBoundaryMode(); + PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState = + resource.getChunkBoundaryPauseState(bodyRef); + if (pauseState != null) { + handlePausedBody(bodyRef, + snapshot, + pauseState, + mode, + resource, + store, + chunkStore, + chunkComponentStore); + return; + } + + long[] targetChunkIndices = chunkIndices(snapshot); + if (areChunksTicking(targetChunkIndices, chunkStore, chunkComponentStore)) { + recordSafePose(bodyRef, snapshot, resource); + return; + } + + if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { + requestTickingChunks(chunkStore, targetChunkIndices); + return; } + + pauseBodyAuthoritative(store, bodyRef, snapshot, targetChunkIndices, resource); } private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, @@ -154,8 +230,7 @@ private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Store entityStore, @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore, - boolean authoritative) { + @Nonnull Store chunkComponentStore) { long[] targetChunkIndices = pauseState.getTargetChunkIndices(); if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { requestTickingChunks(chunkStore, targetChunkIndices); @@ -165,11 +240,6 @@ private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, return; } - if (authoritative) { - resumeBodyAuthoritative(entityStore, bodyKey, snapshot, pauseState, resource); - return; - } - PhysicsOwnerBridge.run(entityStore, "resume chunk-boundary physics body", () -> { var registration = resource.getRegistration(bodyKey); if (registration == null) { @@ -198,31 +268,51 @@ private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, }); } + private void handlePausedBody(@Nonnull Ref bodyRef, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState, + @Nonnull EntityChunkBoundaryMode mode, + @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull Store entityStore, + @Nonnull ChunkStore chunkStore, + @Nonnull Store chunkComponentStore) { + long[] targetChunkIndices = pauseState.getTargetChunkIndices(); + if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { + requestTickingChunks(chunkStore, targetChunkIndices); + } + + if (!areChunksTicking(targetChunkIndices, chunkStore, chunkComponentStore)) { + return; + } + + resumeBodyAuthoritative(entityStore, bodyRef, snapshot, pauseState, resource); + } + private static void pauseBodyAuthoritative(@Nonnull Store entityStore, - @Nonnull RigidBodyKey bodyKey, + @Nonnull Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull long[] targetChunkIndices, @Nonnull PhysicsWorldRuntimeResource resource) { - ChunkBoundarySafeState safeState = resource.getChunkBoundarySafeState(bodyKey); + ChunkBoundarySafeState safeState = resource.getChunkBoundarySafeState(bodyRef); Vector3f safePosition = safeState != null ? new Vector3f(safeState.getPosition()) : null; Quaternionf safeRotation = safeState != null ? new Quaternionf(safeState.getRotation()) : null; - resource.pauseChunkBoundaryBody(bodyKey, + resource.pauseChunkBoundaryBody(bodyRef, primaryChunkIndex(targetChunkIndices, snapshot), targetChunkIndices, snapshot); scheduleAuthoritativeMutation(entityStore, "pause chunk-boundary PhysicsStore body", physics -> applyPauseBody(physics, - bodyKey.value(), + bodyRef, snapshot.bodyType(), safePosition, safeRotation)); } private static void resumeBodyAuthoritative(@Nonnull Store entityStore, - @Nonnull RigidBodyKey bodyKey, + @Nonnull Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState, @Nonnull PhysicsWorldRuntimeResource resource) { @@ -232,21 +322,20 @@ private static void resumeBodyAuthoritative(@Nonnull Store entitySt scheduleAuthoritativeMutation(entityStore, "resume chunk-boundary PhysicsStore body", physics -> applyResumeBody(physics, - bodyKey.value(), + bodyRef, originalBodyType, linearVelocity, angularVelocity)); - resource.clearChunkBoundaryPauseState(bodyKey); - recordSafePose(bodyKey, snapshot, resource); + resource.clearChunkBoundaryPauseState(bodyRef); + recordSafePose(bodyRef, snapshot, resource); } private static void applyPauseBody(@Nonnull Store store, - @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull PhysicsBodyType originalBodyType, @Nullable Vector3f safePosition, @Nullable Quaternionf safeRotation) { - Ref bodyRef = bodyRef(store, bodyUuid); - if (bodyRef == null) { + if (!isValidBodyRef(store, bodyRef)) { return; } if (originalBodyType != PhysicsBodyType.KINEMATIC) { @@ -260,12 +349,11 @@ private static void applyPauseBody(@Nonnull Store store, } private static void applyResumeBody(@Nonnull Store store, - @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull PhysicsBodyType originalBodyType, @Nonnull Vector3f linearVelocity, @Nonnull Vector3f angularVelocity) { - Ref bodyRef = bodyRef(store, bodyUuid); - if (bodyRef == null) { + if (!isValidBodyRef(store, bodyRef)) { return; } appendBodyCommand(store, bodyRef, BodyCommandComponent.setType(originalBodyType, true)); @@ -301,17 +389,13 @@ private static void appendBodyCommand(@Nonnull Store store, store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); } - @Nullable - private static Ref bodyRef(@Nonnull Store store, - @Nonnull UUID bodyUuid) { - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - Ref bodyRef = identity.getByUuid(bodyUuid); - if (bodyRef == null || !bodyRef.isValid()) { - return null; + private static boolean isValidBodyRef(@Nonnull Store store, + @Nonnull Ref bodyRef) { + if (bodyRef.getStore() != store || !bodyRef.isValid()) { + return false; } BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); - return body != null && body.getKind() == PhysicsBodyKind.BODY ? bodyRef : null; + return body != null && body.getKind() == PhysicsBodyKind.BODY; } private static void scheduleAuthoritativeMutation(@Nonnull Store entityStore, @@ -390,6 +474,12 @@ static void recordSafePose(@Nonnull RigidBodyKey bodyKey, resource.updateChunkBoundarySafeState(bodyKey, snapshot); } + static void recordSafePose(@Nonnull Ref bodyRef, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull PhysicsWorldRuntimeResource resource) { + resource.updateChunkBoundarySafeState(bodyRef, snapshot); + } + private void requestTickingChunk(@Nonnull ChunkStore chunkStore, long chunkIndex) { if (!requestedChunkIndices.add(chunkIndex)) { return; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index 5c656903..ae243e46 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -43,8 +43,11 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUui @Nullable public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { - return registrations.viewsByRowIndex().get(Objects.requireNonNull(bodyRef, "bodyRef") - .getIndex()); + RegistrationByRef registration = registrations.viewsByRowIndex() + .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); + return registration != null && sameRef(registration.bodyRef(), bodyRef) + ? registration.view() + : null; } @Nonnull @@ -93,13 +96,14 @@ public void publish(@Nonnull Collection publication new Object2ObjectLinkedOpenHashMap<>(); Object2ObjectLinkedOpenHashMap viewsByUuid = new Object2ObjectLinkedOpenHashMap<>(); - Int2ObjectOpenHashMap viewsByRowIndex = + Int2ObjectOpenHashMap viewsByRowIndex = new Int2ObjectOpenHashMap<>(); for (BodyRegistrationPublication publication : publicationsByKey.values()) { PhysicsBodyRegistrationView registration = publication.view(); viewsByKey.put(registration.bodyKey(), registration); viewsByUuid.put(registration.bodyKey().value(), registration); - viewsByRowIndex.put(publication.bodyRef().getIndex(), registration); + viewsByRowIndex.put(publication.bodyRef().getIndex(), + new RegistrationByRef(publication.bodyRef(), registration)); } registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), Map.copyOf(viewsByKey), @@ -118,10 +122,10 @@ public void removeBody(@Nonnull RigidBodyKey bodyKey) { Object2ObjectLinkedOpenHashMap viewsByUuid = new Object2ObjectLinkedOpenHashMap<>(current.viewsByUuid()); viewsByUuid.remove(bodyKey.value()); - Int2ObjectOpenHashMap viewsByRowIndex = + Int2ObjectOpenHashMap viewsByRowIndex = new Int2ObjectOpenHashMap<>(current.viewsByRowIndex()); viewsByRowIndex.int2ObjectEntrySet() - .removeIf(entry -> entry.getValue().bodyKey().equals(bodyKey)); + .removeIf(entry -> entry.getValue().view().bodyKey().equals(bodyKey)); registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), Map.copyOf(viewsByKey), Map.copyOf(viewsByUuid), @@ -155,11 +159,20 @@ public record BodyRegistrationPublication( } } + private record RegistrationByRef(@Nonnull Ref bodyRef, + @Nonnull PhysicsBodyRegistrationView view) { + + private RegistrationByRef { + Objects.requireNonNull(bodyRef, "bodyRef"); + Objects.requireNonNull(view, "view"); + } + } + private record PublishedRegistrations( @Nonnull List views, @Nonnull Map viewsByKey, @Nonnull Map viewsByUuid, - @Nonnull Int2ObjectOpenHashMap viewsByRowIndex) { + @Nonnull Int2ObjectOpenHashMap viewsByRowIndex) { private static final PublishedRegistrations EMPTY = new PublishedRegistrations(List.of(), @@ -167,4 +180,10 @@ private record PublishedRegistrations( Map.of(), new Int2ObjectOpenHashMap<>()); } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getIndex() == second.getIndex() + && first.getStore() == second.getStore(); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index 148b04e6..50e09a0f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -40,8 +40,9 @@ public PhysicsStoreBodySnapshot getBody(@Nonnull UUID bodyUuid) { @Nullable public PhysicsStoreBodySnapshot getBody(@Nonnull Ref bodyRef) { - return snapshot.bodiesByRowIndex().get(Objects.requireNonNull(bodyRef, "bodyRef") - .getIndex()); + PhysicsStoreBodySnapshot body = snapshot.bodiesByRowIndex() + .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); + return body != null && sameRef(body.bodyRef(), bodyRef) ? body : null; } public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { @@ -121,4 +122,11 @@ private record PublishedSnapshot( Map.of(), new Int2ObjectOpenHashMap<>()); } + + private static boolean sameRef(@Nullable Ref first, + @Nonnull Ref second) { + return first != null + && first.getIndex() == second.getIndex() + && first.getStore() == second.getStore(); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 122eb2ef..8bafd77e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -631,6 +631,15 @@ public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull RigidBodyKey bod () -> getBodySnapshotIfRegisteredDirect(bodyKey)); } + @Nullable + public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull Ref bodyRef) { + Store store = Objects.requireNonNull(bodyRef, "bodyRef").getStore(); + PhysicsStoreThreading.requireWorldThread(store, "read optional copied physics body snapshot"); + PhysicsStoreBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(bodyRef); + return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; + } + @Nonnull private PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull RigidBodyKey bodyKey) { PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); @@ -834,8 +843,11 @@ private static boolean withinRadius(@Nonnull PhysicsBodySnapshot snapshot, @Nonnull private static PhysicsBodySnapshot toPublicBodySnapshot(@Nonnull Store store, @Nonnull PhysicsStoreBodySnapshot body) { - Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(body.bodyUuid()); + Ref ref = body.bodyRef(); + if (ref == null || ref.getStore() != store || !ref.isValid()) { + ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(body.bodyUuid()); + } boolean validRef = ref != null && ref.isValid(); DynamicsComponent dynamics = validRef ? store.getComponent(ref, DynamicsComponent.getComponentType()) @@ -2186,11 +2198,27 @@ public void updateChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey, chunkRuntime.updateChunkBoundarySafeState(bodyKey, snapshot); } + public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation) { + chunkRuntime.updateChunkBoundarySafeState(bodyRef, position, rotation); + } + + public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, + @Nonnull PhysicsBodySnapshot snapshot) { + chunkRuntime.updateChunkBoundarySafeState(bodyRef, snapshot); + } + @Nullable public ChunkBoundarySafeState getChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey) { return chunkRuntime.getChunkBoundarySafeState(bodyKey); } + @Nullable + public ChunkBoundarySafeState getChunkBoundarySafeState(@Nonnull Ref bodyRef) { + return chunkRuntime.getChunkBoundarySafeState(bodyRef); + } + public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, long targetChunkIndex, @Nonnull PhysicsBodyType originalBodyType, @@ -2216,15 +2244,50 @@ public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, chunkRuntime.pauseChunkBoundaryBody(bodyKey, targetChunkIndex, targetChunkIndices, snapshot); } + public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, + long targetChunkIndex, + @Nonnull PhysicsBodyType originalBodyType, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity) { + chunkRuntime.pauseChunkBoundaryBody(bodyRef, + targetChunkIndex, + originalBodyType, + linearVelocity, + angularVelocity); + } + + public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, + long targetChunkIndex, + @Nonnull PhysicsBodySnapshot snapshot) { + chunkRuntime.pauseChunkBoundaryBody(bodyRef, targetChunkIndex, snapshot); + } + + public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, + long targetChunkIndex, + @Nonnull long[] targetChunkIndices, + @Nonnull PhysicsBodySnapshot snapshot) { + chunkRuntime.pauseChunkBoundaryBody(bodyRef, targetChunkIndex, targetChunkIndices, snapshot); + } + @Nullable public ChunkBoundaryPauseState getChunkBoundaryPauseState(@Nonnull RigidBodyKey bodyKey) { return chunkRuntime.getChunkBoundaryPauseState(bodyKey); } + @Nullable + public ChunkBoundaryPauseState getChunkBoundaryPauseState( + @Nonnull Ref bodyRef) { + return chunkRuntime.getChunkBoundaryPauseState(bodyRef); + } + public void clearChunkBoundaryPauseState(@Nonnull RigidBodyKey bodyKey) { chunkRuntime.clearChunkBoundaryPauseState(bodyKey); } + public void clearChunkBoundaryPauseState(@Nonnull Ref bodyRef) { + chunkRuntime.clearChunkBoundaryPauseState(bodyRef); + } + public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { requireLegacyMutationAllowed("clear physics body runtime state"); runOwnerMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyKey)); @@ -2244,6 +2307,7 @@ private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { "resolve cleared body runtime key"); if (bodyRef != null) { controlRuntime.clearBody(bodyRef); + chunkRuntime.clearBody(bodyRef); } bodyRuntime.clearBodyRuntimeState(bodyKey); } @@ -2252,6 +2316,10 @@ public void markContinuousCollisionForced(@Nonnull RigidBodyKey bodyKey) { chunkRuntime.markContinuousCollisionForced(bodyKey); } + public void markContinuousCollisionForced(@Nonnull Ref bodyRef) { + chunkRuntime.markContinuousCollisionForced(bodyRef); + } + @Nonnull public Collection getForcedContinuousCollisionBodyKeys() { return chunkRuntime.getForcedContinuousCollisionBodyKeys(); From 7dccb1ec222ef6c3dacb6d3fa814e9670cc9dcca Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:01:47 +0200 Subject: [PATCH 170/534] refactor(worldcollision): carry lod body refs Signed-off-by: Blovien --- .../systems/PhysicsCollisionLodSystem.java | 221 +++++++++++++++++- 1 file changed, 212 insertions(+), 9 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java index 79b73a3b..9f1b5be2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java @@ -10,6 +10,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; @@ -20,20 +21,25 @@ import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -43,6 +49,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.WeakHashMap; @@ -102,11 +109,18 @@ private List collectUpdates(@Nonnull Store stor List interests = VisualInterestCollector.collectMaterializationInterests(store, resource); if (isAuthoritativePhysicsStoreActive()) { + Store physics = + ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() + .getStore(); + PhysicsStoreThreading.requireWorldThread(physics, + "collect collision LOD PhysicsStore bodies"); + PhysicsBodyRegistrationResource registrations = + physics.getResource(PhysicsBodyRegistrationResource.getResourceType()); for (SpaceId spaceId : resource.getSpaceIds()) { activeSpaces.add(spaceId.value()); PhysicsSpaceSettings settings = resource.getSpaceSettings(spaceId); if (!settings.getCollisionLodSettings().isCollisionLodEnabled()) { - state.collectRestoreUpdates(spaceId, updates); + state.collectRestoreRefUpdates(spaceId, updates); continue; } if (!state.shouldRefresh(spaceId, @@ -114,7 +128,14 @@ private List collectUpdates(@Nonnull Store stor tick)) { continue; } - collectSpaceUpdates(resource, spaceId, settings, interests, state, updates); + collectAuthoritativeSpaceUpdates(physics, + registrations, + resource, + spaceId, + settings, + interests, + state, + updates); } state.pruneRemovedSpaces(activeSpaces); return updates; @@ -173,6 +194,59 @@ private static void collectSpaceUpdates(@Nonnull PhysicsWorldRuntimeResource res state.pruneMissingBodies(spaceId, seenBodies); } + private static void collectAuthoritativeSpaceUpdates(@Nonnull Store physics, + @Nonnull PhysicsBodyRegistrationResource registrations, + @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsSpaceSettings settings, + @Nonnull List interests, + @Nonnull CollisionLodState state, + @Nonnull List updates) { + IntOpenHashSet seenRows = new IntOpenHashSet(); + for (PhysicsStoreBodySnapshot body : physics.getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame() + .bodies()) { + Ref bodyRef = body.bodyRef(); + if (bodyRef == null || !bodyRef.isValid()) { + continue; + } + PhysicsBodyRegistrationView registration = registrations.getBodyRegistrationView(bodyRef); + if (registration == null || !registration.spaceId().equals(spaceId)) { + continue; + } + seenRows.add(bodyRef.getIndex()); + PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyRef); + if (snapshot == null) { + continue; + } + if (registration.persistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) { + state.recordRestore(spaceId, bodyRef, updates); + continue; + } + if (!isCollisionLodCandidate(snapshot, + registration.kind(), + registration.persistenceMode())) { + continue; + } + + CollisionLodTier previousTier = state.tier(bodyRef); + CollisionLodTier tier = resource.isBodyControlled(bodyRef) + ? CollisionLodTier.NEAR_FULL + : resolveTier(settings, + previousTier, + snapshot.positionX(), + snapshot.positionY(), + snapshot.positionZ(), + interests); + state.recordTier(spaceId, + bodyRef, + tier, + settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled(), + updates); + } + state.pruneMissingBodyRefs(spaceId, seenRows); + } + static CollisionLodTier resolveTier(@Nonnull PhysicsSpaceSettings settings, @Nullable CollisionLodTier previousTier, @Nonnull Vector3f position, @@ -265,8 +339,6 @@ private static PhysicsMutationHandle applyAuthoritativeUpdatesAsync( private static void applyAuthoritativeUpdates(@Nonnull Store store, @Nonnull List updates) { - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); for (CollisionLodUpdate update : updates) { @@ -274,8 +346,10 @@ private static void applyAuthoritativeUpdates(@Nonnull Store store if (spaceUuid == null) { continue; } - Ref bodyRef = identity.getByUuid(update.bodyKey().value()); - if (bodyRef == null || !bodyRef.isValid()) { + Ref bodyRef = update.bodyRef(); + if (bodyRef == null + || bodyRef.getStore() != store + || !bodyRef.isValid()) { continue; } BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); @@ -394,10 +468,35 @@ enum CollisionLodTier { } record CollisionLodUpdate(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey, + @Nullable RigidBodyKey bodyKey, + @Nullable Ref bodyRef, @Nonnull CollisionLodTier tier, boolean farSleepEnabled, boolean trackTier) { + + CollisionLodUpdate(@Nonnull SpaceId spaceId, + @Nonnull RigidBodyKey bodyKey, + @Nonnull CollisionLodTier tier, + boolean farSleepEnabled, + boolean trackTier) { + this(spaceId, bodyKey, null, tier, farSleepEnabled, trackTier); + } + + CollisionLodUpdate(@Nonnull SpaceId spaceId, + @Nonnull Ref bodyRef, + @Nonnull CollisionLodTier tier, + boolean farSleepEnabled, + boolean trackTier) { + this(spaceId, null, bodyRef, tier, farSleepEnabled, trackTier); + } + + CollisionLodUpdate { + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(tier, "tier"); + if ((bodyKey == null) == (bodyRef == null)) { + throw new IllegalArgumentException("Exactly one body identity is required"); + } + } } private record BodyTier(@Nonnull SpaceId spaceId, @Nonnull CollisionLodTier tier) { @@ -409,6 +508,9 @@ static final class CollisionLodState { private final Object2ObjectMap tiers = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Int2ObjectOpenHashMap refTiersByRowIndex = + new Int2ObjectOpenHashMap<>(); + @Nonnull private final Int2LongOpenHashMap nextRefreshTicks = new Int2LongOpenHashMap(); @Nonnull private final Queue pendingUpdates = new ArrayDeque<>(); @@ -465,6 +567,14 @@ CollisionLodTier tier(@Nonnull RigidBodyKey bodyKey) { return bodyTier != null ? bodyTier.tier() : null; } + @Nullable + CollisionLodTier tier(@Nonnull Ref bodyRef) { + RefBodyTier bodyTier = refTiersByRowIndex.get(rowIndex(bodyRef)); + return bodyTier != null && sameRef(bodyTier.bodyRef(), bodyRef) + ? bodyTier.tier().tier() + : null; + } + void recordTier(@Nonnull SpaceId spaceId, @Nonnull RigidBodyKey bodyKey, @Nonnull CollisionLodTier tier, @@ -477,6 +587,21 @@ void recordTier(@Nonnull SpaceId spaceId, updates.add(new CollisionLodUpdate(spaceId, bodyKey, tier, farSleepEnabled, true)); } + void recordTier(@Nonnull SpaceId spaceId, + @Nonnull Ref bodyRef, + @Nonnull CollisionLodTier tier, + boolean farSleepEnabled, + @Nonnull List updates) { + RefBodyTier previous = refTiersByRowIndex.get(rowIndex(bodyRef)); + if (previous != null + && sameRef(previous.bodyRef(), bodyRef) + && previous.tier().spaceId().equals(spaceId) + && previous.tier().tier() == tier) { + return; + } + updates.add(new CollisionLodUpdate(spaceId, bodyRef, tier, farSleepEnabled, true)); + } + void recordRestore(@Nonnull SpaceId spaceId, @Nonnull RigidBodyKey bodyKey, @Nonnull List updates) { @@ -491,6 +616,22 @@ void recordRestore(@Nonnull SpaceId spaceId, false)); } + void recordRestore(@Nonnull SpaceId spaceId, + @Nonnull Ref bodyRef, + @Nonnull List updates) { + RefBodyTier previous = refTiersByRowIndex.get(rowIndex(bodyRef)); + if (previous == null + || !sameRef(previous.bodyRef(), bodyRef) + || !previous.tier().spaceId().equals(spaceId)) { + return; + } + updates.add(new CollisionLodUpdate(spaceId, + bodyRef, + CollisionLodTier.NEAR_FULL, + false, + false)); + } + private void collectRestoreUpdates(@Nonnull SpaceId spaceId, @Nonnull List updates) { for (Object2ObjectMap.Entry entry @@ -507,9 +648,41 @@ private void collectRestoreUpdates(@Nonnull SpaceId spaceId, nextRefreshTicks.remove(spaceId.value()); } + private void collectRestoreRefUpdates(@Nonnull SpaceId spaceId, + @Nonnull List updates) { + for (Int2ObjectMap.Entry entry + : refTiersByRowIndex.int2ObjectEntrySet()) { + if (!entry.getValue().tier().spaceId().equals(spaceId)) { + continue; + } + Ref bodyRef = entry.getValue().bodyRef(); + if (bodyRef == null || !bodyRef.isValid()) { + continue; + } + updates.add(new CollisionLodUpdate(spaceId, + bodyRef, + CollisionLodTier.NEAR_FULL, + false, + false)); + } + nextRefreshTicks.remove(spaceId.value()); + } + private void commitPendingUpdates() { for (CollisionLodUpdate update : pendingUpdates) { - if (update.trackTier()) { + if (update.bodyRef() != null) { + int rowIndex = rowIndex(update.bodyRef()); + if (update.trackTier()) { + refTiersByRowIndex.put(rowIndex, + new RefBodyTier(update.bodyRef(), + new BodyTier(update.spaceId(), update.tier()))); + } else { + RefBodyTier previous = refTiersByRowIndex.get(rowIndex); + if (previous != null && sameRef(previous.bodyRef(), update.bodyRef())) { + refTiersByRowIndex.remove(rowIndex); + } + } + } else if (update.trackTier()) { tiers.put(update.bodyKey(), new BodyTier(update.spaceId(), update.tier())); } else { tiers.remove(update.bodyKey()); @@ -530,11 +703,41 @@ private void pruneMissingBodies(@Nonnull SpaceId spaceId, && !seenBodies.contains(entry.getKey())); } + private void pruneMissingBodyRefs(@Nonnull SpaceId spaceId, + @Nonnull IntOpenHashSet seenRows) { + refTiersByRowIndex.int2ObjectEntrySet() + .removeIf(entry -> entry.getValue().tier().spaceId().equals(spaceId) + && (!seenRows.contains(entry.getIntKey()) + || !entry.getValue().bodyRef().isValid())); + } + private void pruneRemovedSpaces(@Nonnull IntOpenHashSet activeSpaces) { tiers.object2ObjectEntrySet() .removeIf(entry -> !activeSpaces.contains(entry.getValue().spaceId().value())); + refTiersByRowIndex.int2ObjectEntrySet() + .removeIf(entry -> !activeSpaces.contains(entry.getValue().tier().spaceId().value()) + || !entry.getValue().bodyRef().isValid()); nextRefreshTicks.keySet().removeIf(spaceValue -> !activeSpaces.contains(spaceValue)); } + + private static int rowIndex(@Nonnull Ref bodyRef) { + return Objects.requireNonNull(bodyRef, "bodyRef").getIndex(); + } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getIndex() == second.getIndex() + && first.getStore() == second.getStore(); + } + } + + private record RefBodyTier(@Nonnull Ref bodyRef, + @Nonnull BodyTier tier) { + + private RefBodyTier { + Objects.requireNonNull(bodyRef, "bodyRef"); + Objects.requireNonNull(tier, "tier"); + } } @Nonnull From c6e9d37ccb58010d361a8a0aeee1b8925c4dfa7b Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:07:07 +0200 Subject: [PATCH 171/534] refactor(worldcollision): key streaming targets by ref Signed-off-by: Blovien --- .../PhysicsStoreTerrainMutationCache.java | 137 +++++++++++++++++- ...sStoreWorldCollisionStreamingResource.java | 27 ++++ ...sicsStoreWorldCollisionProducerSystem.java | 13 +- 3 files changed, 172 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java index 68e31acc..c0d95714 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java @@ -7,10 +7,13 @@ import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.MissingSectionReason; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -19,6 +22,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Iterator; +import java.util.Objects; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; @@ -315,6 +319,71 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID return TargetRefreshDecision.skip(); } + @Nonnull + public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, + @Nonnull Ref bodyRef, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + SpaceCollisionCache cache = spaces.computeIfAbsent(spaceUuid, _ -> new SpaceCollisionCache()); + CachedBodyStreamingTarget target = getBodyTarget(cache, bodyRef); + if (target == null) { + putBodyTarget(cache, + bodyRef, + new CachedBodyStreamingTarget(bounds, + sleeping, + currentTick, + BODY_TARGET_REFRESH_PENDING)); + if (profiling != null) { + profiling.incrementBodyTargetFirstSeen(); + } + return TargetRefreshDecision.refresh(TargetRefreshReason.FIRST_SEEN); + } + + target.lastSeenTick = currentTick; + target.sleeping = sleeping; + if (profiling != null) { + profiling.incrementBodyTargetCacheHits(); + } + + if (!target.bounds.equals(bounds)) { + target.bounds = bounds; + if (profiling != null) { + profiling.incrementBodyTargetBoundsChanged(); + } + return TargetRefreshDecision.refresh(TargetRefreshReason.BOUNDS_CHANGED); + } + if (target.lastRefreshTick == BODY_TARGET_REFRESH_PENDING) { + return TargetRefreshDecision.refresh(TargetRefreshReason.PENDING_APPLY); + } + + int interval = sleeping ? sleepingBodyStreamingInterval(ttlTicks) + : ACTIVE_BODY_STREAMING_INTERVAL_TICKS; + if (currentTick == 1L || currentTick - target.lastRefreshTick >= interval) { + if (profiling != null) { + if (sleeping) { + profiling.incrementBodyTargetSleepingRefreshes(); + } else { + profiling.incrementBodyTargetActiveRefreshes(); + } + } + return TargetRefreshDecision.refresh(sleeping + ? TargetRefreshReason.SLEEPING_INTERVAL + : TargetRefreshReason.ACTIVE_INTERVAL); + } + + if (profiling != null) { + if (sleeping) { + profiling.incrementBodyTargetSleepingStableSkips(); + } else { + profiling.incrementBodyTargetActiveStableSkips(); + } + } + return TargetRefreshDecision.skip(); + } + public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull WorldCollisionStreamingBounds bounds, @@ -335,6 +404,25 @@ public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, target.lastRefreshTick = currentTick; } + public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, + @Nonnull Ref bodyRef, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick) { + SpaceCollisionCache cache = spaces.computeIfAbsent(spaceUuid, _ -> new SpaceCollisionCache()); + CachedBodyStreamingTarget target = getBodyTarget(cache, bodyRef); + if (target == null) { + putBodyTarget(cache, + bodyRef, + new CachedBodyStreamingTarget(bounds, sleeping, currentTick, currentTick)); + return; + } + target.bounds = bounds; + target.sleeping = sleeping; + target.lastSeenTick = currentTick; + target.lastRefreshTick = currentTick; + } + public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, long currentTick, int ttlTicks, @@ -355,6 +443,17 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, iterator.remove(); removed++; } + Iterator> refIterator = + cache.bodyTargetsByRowIndex.int2ObjectEntrySet().iterator(); + while (refIterator.hasNext()) { + RefBodyStreamingTarget row = refIterator.next().getValue(); + if (row.bodyRef().isValid() + && currentTick - row.target().lastSeenTick <= maxAge) { + continue; + } + refIterator.remove(); + removed++; + } pruneExpiredMissingBackoffs(cache, currentTick); if (cache.isEmpty()) { spaces.remove(spaceUuid); @@ -605,6 +704,30 @@ private static int sleepingBodyStreamingInterval(int ttlTicks) { Math.min(SLEEPING_BODY_STREAMING_INTERVAL_TICKS, ttlBound)); } + @Nullable + private static CachedBodyStreamingTarget getBodyTarget(@Nonnull SpaceCollisionCache cache, + @Nonnull Ref bodyRef) { + RefBodyStreamingTarget row = cache.bodyTargetsByRowIndex.get(rowIndex(bodyRef)); + return row != null && sameRef(row.bodyRef(), bodyRef) ? row.target() : null; + } + + private static void putBodyTarget(@Nonnull SpaceCollisionCache cache, + @Nonnull Ref bodyRef, + @Nonnull CachedBodyStreamingTarget target) { + cache.bodyTargetsByRowIndex.put(rowIndex(bodyRef), + new RefBodyStreamingTarget(bodyRef, target)); + } + + private static int rowIndex(@Nonnull Ref bodyRef) { + return Objects.requireNonNull(bodyRef, "bodyRef").getIndex(); + } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getIndex() == second.getIndex() + && first.getStore() == second.getStore(); + } + private static long packSectionKey(int chunkX, int sectionY, int chunkZ) { long x = ((long) chunkX & 0x3FFFFFL) << 42; long y = ((long) sectionY & 0x3FFL) << 32; @@ -630,12 +753,15 @@ private static final class SpaceCollisionCache { private final Long2LongMap missingBlockSectionBackoffs = new Long2LongOpenHashMap(); private final Object2ObjectMap bodyTargets = new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap bodyTargetsByRowIndex = + new Int2ObjectOpenHashMap<>(); private boolean isEmpty() { return sections.isEmpty() && missingBlockChunkBackoffs.isEmpty() && missingBlockSectionBackoffs.isEmpty() - && bodyTargets.isEmpty(); + && bodyTargets.isEmpty() + && bodyTargetsByRowIndex.isEmpty(); } } @@ -689,6 +815,15 @@ private CachedBodyStreamingTarget(@Nonnull WorldCollisionStreamingBounds bounds, } } + private record RefBodyStreamingTarget(@Nonnull Ref bodyRef, + @Nonnull CachedBodyStreamingTarget target) { + + private RefBodyStreamingTarget { + Objects.requireNonNull(bodyRef, "bodyRef"); + Objects.requireNonNull(target, "target"); + } + } + public enum TargetRefreshReason { FIRST_SEEN, BOUNDS_CHANGED, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java index c45c0df1..801e318d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java @@ -1,9 +1,11 @@ package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; @@ -151,6 +153,23 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID profiling); } + @Nonnull + public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, + @Nonnull Ref bodyRef, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick, + int ttlTicks, + @Nullable Snapshot profiling) { + return cache.shouldRefreshBodyTarget(spaceUuid, + bodyRef, + bounds, + sleeping, + currentTick, + ttlTicks, + profiling); + } + public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull WorldCollisionStreamingBounds bounds, @@ -159,6 +178,14 @@ public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, cache.recordBodyTargetRefresh(spaceUuid, bodyUuid, bounds, sleeping, currentTick); } + public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, + @Nonnull Ref bodyRef, + @Nonnull WorldCollisionStreamingBounds bounds, + boolean sleeping, + long currentTick) { + cache.recordBodyTargetRefresh(spaceUuid, bodyRef, bounds, sleeping, currentTick); + } + public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, long currentTick, int ttlTicks, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index 7ac4f1c2..21559cd3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -172,7 +173,7 @@ private static void processSpace(@Nonnull World world, settings.buildOptions()); for (BodyStreamingRefresh refresh : target.refreshes()) { streaming.recordBodyTargetRefresh(settings.spaceUuid(), - refresh.bodyUuid(), + refresh.bodyRef(), target.bounds(), refresh.sleeping(), currentTick); @@ -209,6 +210,10 @@ private static List collectDynamicBodyTargets( if (body.bodyType() != PhysicsBodyType.DYNAMIC) { continue; } + Ref bodyRef = body.bodyRef(); + if (bodyRef == null || !bodyRef.isValid()) { + continue; + } dynamicCandidates++; Vector3f position = body.position(); WorldCollisionStreamingBounds bounds = WorldCollisionStreamingBounds.from(position.x, @@ -216,7 +221,7 @@ private static List collectDynamicBodyTargets( position.z, settings.bodyRadius()); TargetRefreshDecision decision = streaming.shouldRefreshBodyTarget(settings.spaceUuid(), - body.bodyUuid(), + bodyRef, bounds, body.sleeping(), currentTick, @@ -235,7 +240,7 @@ private static List collectDynamicBodyTargets( } else if (snapshot != null) { snapshot.incrementBodyTargetDedupeSkips(); } - target.refreshes().add(new BodyStreamingRefresh(body.bodyUuid(), body.sleeping())); + target.refreshes().add(new BodyStreamingRefresh(bodyRef, body.sleeping())); } if (snapshot != null) { @@ -331,7 +336,7 @@ private record BodyStreamingTarget(@Nonnull Vector3d position, @Nonnull List refreshes) { } - private record BodyStreamingRefresh(@Nonnull UUID bodyUuid, + private record BodyStreamingRefresh(@Nonnull Ref bodyRef, boolean sleeping) { } From ba85a104ca955cbfb2383dbf7572cd813a78f798 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:15:48 +0200 Subject: [PATCH 172/534] refactor(physicsstore): validate projection refs by row Signed-off-by: Blovien --- .../PhysicsProjectionIndexResource.java | 152 ++++++++++++++++-- .../resources/PhysicsVisualRuntime.java | 13 +- .../PhysicsBodyAttachmentIndexSystem.java | 7 +- .../systems/sync/PhysicsSyncSystem.java | 7 +- ...csDetachedVisualMaterializationSystem.java | 12 +- 5 files changed, 167 insertions(+), 24 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index 89230c49..f20d7072 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -26,11 +26,11 @@ public final class PhysicsProjectionIndexResource implements Resource>> bodyAttachments = new Object2ObjectOpenHashMap<>(); - private final Int2ObjectOpenHashMap>> bodyAttachmentsByRowIndex = + private final Int2ObjectOpenHashMap bodyAttachmentsByRowIndex = new Int2ObjectOpenHashMap<>(); private final Map> generatedVisualProxies = new Object2ObjectOpenHashMap<>(); - private final Int2ObjectOpenHashMap> generatedVisualProxiesByRowIndex = + private final Int2ObjectOpenHashMap generatedVisualProxiesByRowIndex = new Int2ObjectOpenHashMap<>(); public synchronized void registerAttachment(@Nonnull UUID bodyUuid, @@ -44,8 +44,7 @@ public synchronized void registerAttachment(@Nonnull UUID bodyUuid, bodyAttachments.computeIfAbsent(bodyUuid, _ -> new ObjectOpenHashSet<>()) .add(attachment); if (bodyRef != null) { - bodyAttachmentsByRowIndex.computeIfAbsent(bodyRef.getIndex(), - _ -> new ObjectOpenHashSet<>()) + bodyAttachmentRefs(bodyRef) .add(attachment); } } @@ -77,7 +76,7 @@ public Collection> getAttachments(@Nonnull UUID bodyUuid) { @Nonnull public Collection> getAttachments(@Nonnull Ref bodyRef) { - return liveAttachments(bodyAttachmentsByRowIndex, bodyRef.getIndex()); + return liveAttachments(bodyRef); } @Nonnull @@ -110,7 +109,7 @@ public boolean hasAttachments(@Nonnull UUID bodyUuid) { } public boolean hasAttachments(@Nonnull Ref bodyRef) { - return hasLiveAttachments(bodyAttachmentsByRowIndex, bodyRef.getIndex()); + return hasLiveAttachments(bodyRef); } private boolean hasLiveAttachments(@Nonnull Map>> attachmentsByKey, @@ -143,7 +142,7 @@ public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid) { @Nullable public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { - return liveGeneratedVisualProxy(generatedVisualProxiesByRowIndex, bodyRef.getIndex()); + return liveGeneratedVisualProxy(bodyRef); } @Nullable @@ -171,7 +170,8 @@ public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, synchronized (this) { generatedVisualProxies.put(bodyUuid, proxy); if (bodyRef != null) { - generatedVisualProxiesByRowIndex.put(bodyRef.getIndex(), proxy); + generatedVisualProxiesByRowIndex.put(bodyRef.getIndex(), + new GeneratedVisualProxyRef(bodyRef, proxy)); } } } @@ -217,11 +217,11 @@ public void updateAttachmentBodyRef(@Nonnull UUID bodyUuid, } } if (newBodyRef != null) { - bodyAttachmentsByRowIndex.computeIfAbsent(newBodyRef.getIndex(), - _ -> new ObjectOpenHashSet<>()) + bodyAttachmentRefs(newBodyRef) .add(attachment); if (generatedProxy) { - generatedVisualProxiesByRowIndex.put(newBodyRef.getIndex(), attachment); + generatedVisualProxiesByRowIndex.put(newBodyRef.getIndex(), + new GeneratedVisualProxyRef(newBodyRef, attachment)); } } } @@ -236,11 +236,17 @@ public PhysicsProjectionIndexResource clone() { copy.bodyAttachments.put(entry.getKey(), new ObjectOpenHashSet<>(entry.getValue())); } for (var entry : bodyAttachmentsByRowIndex.int2ObjectEntrySet()) { + BodyAttachmentRefs refs = entry.getValue(); copy.bodyAttachmentsByRowIndex.put(entry.getIntKey(), - new ObjectOpenHashSet<>(entry.getValue())); + new BodyAttachmentRefs(refs.bodyRef(), + new ObjectOpenHashSet<>(refs.attachments()))); } copy.generatedVisualProxies.putAll(generatedVisualProxies); - copy.generatedVisualProxiesByRowIndex.putAll(generatedVisualProxiesByRowIndex); + for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { + GeneratedVisualProxyRef proxy = entry.getValue(); + copy.generatedVisualProxiesByRowIndex.put(entry.getIntKey(), + new GeneratedVisualProxyRef(proxy.bodyRef(), proxy.proxy())); + } } return copy; } @@ -252,10 +258,14 @@ public static ResourceType getResou private void unregisterAttachmentRef(@Nonnull Ref bodyRef, @Nonnull Ref attachment) { int rowIndex = bodyRef.getIndex(); - Set> attachments = bodyAttachmentsByRowIndex.get(rowIndex); - if (attachments == null) { + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null || !sameRef(row.bodyRef(), bodyRef)) { + if (row != null) { + bodyAttachmentsByRowIndex.remove(rowIndex); + } return; } + Set> attachments = row.attachments(); attachments.remove(attachment); if (attachments.isEmpty()) { bodyAttachmentsByRowIndex.remove(rowIndex); @@ -265,9 +275,108 @@ private void unregisterAttachmentRef(@Nonnull Ref bodyRef, private void clearGeneratedVisualProxyRef(@Nonnull Ref bodyRef, @Nonnull Ref expectedProxy) { int rowIndex = bodyRef.getIndex(); - Ref proxy = generatedVisualProxiesByRowIndex.get(rowIndex); - if (sameRef(proxy, expectedProxy)) { + GeneratedVisualProxyRef row = generatedVisualProxiesByRowIndex.get(rowIndex); + if (row != null + && (!sameRef(row.bodyRef(), bodyRef) + || sameRef(row.proxy(), expectedProxy))) { + generatedVisualProxiesByRowIndex.remove(rowIndex); + } + } + + @Nonnull + private Set> bodyAttachmentRefs(@Nonnull Ref bodyRef) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null || !sameRef(row.bodyRef(), bodyRef)) { + row = new BodyAttachmentRefs(bodyRef, new ObjectOpenHashSet<>()); + bodyAttachmentsByRowIndex.put(rowIndex, row); + } + return row.attachments(); + } + + @Nonnull + private Collection> liveAttachments(@Nonnull Ref bodyRef) { + List> liveAttachments = new ArrayList<>(); + synchronized (this) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null) { + return List.of(); + } + if (!sameRef(row.bodyRef(), bodyRef)) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return List.of(); + } + Set> attachments = row.attachments(); + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return List.of(); + } + for (Iterator> iterator = attachments.iterator(); iterator.hasNext();) { + Ref attachment = iterator.next(); + if (attachment != null && attachment.isValid()) { + liveAttachments.add(attachment); + } else { + iterator.remove(); + } + } + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + } + } + return liveAttachments; + } + + private boolean hasLiveAttachments(@Nonnull Ref bodyRef) { + synchronized (this) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null) { + return false; + } + if (!sameRef(row.bodyRef(), bodyRef)) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return false; + } + Set> attachments = row.attachments(); + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return false; + } + boolean hasLiveAttachment = false; + for (Iterator> iterator = attachments.iterator(); iterator.hasNext();) { + Ref attachment = iterator.next(); + if (attachment != null && attachment.isValid()) { + hasLiveAttachment = true; + } else { + iterator.remove(); + } + } + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + } + return hasLiveAttachment; + } + } + + @Nullable + private Ref liveGeneratedVisualProxy(@Nonnull Ref bodyRef) { + synchronized (this) { + int rowIndex = bodyRef.getIndex(); + GeneratedVisualProxyRef row = generatedVisualProxiesByRowIndex.get(rowIndex); + if (row == null) { + return null; + } + if (!sameRef(row.bodyRef(), bodyRef)) { + generatedVisualProxiesByRowIndex.remove(rowIndex); + return null; + } + Ref proxy = row.proxy(); + if (proxy != null && proxy.isValid()) { + return proxy; + } generatedVisualProxiesByRowIndex.remove(rowIndex); + return null; } } @@ -276,7 +385,16 @@ private static boolean sameRef(@Nullable Ref first, return first == second || (first != null && second != null + && first.getStore() != null && first.getStore() == second.getStore() && first.getIndex() == second.getIndex()); } + + private record BodyAttachmentRefs(@Nonnull Ref bodyRef, + @Nonnull Set> attachments) { + } + + private record GeneratedVisualProxyRef(@Nonnull Ref bodyRef, + @Nonnull Ref proxy) { + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index f9a5ebd0..064492a0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -182,7 +182,7 @@ public void setGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, synchronized (this) { previousProxy = generatedVisualProxies.put(bodyKey, proxy); } - if (previousProxy != proxy) { + if (!sameRef(previousProxy, proxy)) { cleanSyncState(previousProxy); } } @@ -291,9 +291,14 @@ private void cleanSyncStates(@Nonnull Collection> refs) { } } - private static boolean sameRef(@Nonnull Ref first, - @Nonnull Ref second) { - return first == second || first.equals(second); + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); } /** diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index d120a9fd..db249c90 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -112,7 +112,12 @@ private static void unregisterAttachment(@Nonnull Ref ref, private static boolean sameRef(@Nullable Ref first, @Nullable Ref second) { - return first == second || (first != null && first.equals(second)); + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index f93e0633..cd075f08 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -190,7 +190,12 @@ private static PhysicsStoreBodySnapshot resolvePhysicsStoreSnapshot( private static boolean sameRef(@Nullable Ref first, @Nullable Ref second) { - return first == second || (first != null && first.equals(second)); + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 1dcd5f26..8e8908bb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -660,7 +660,7 @@ private static boolean hasGameplayAttachment(@Nonnull Store store, ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); for (Ref attachmentRef : resource.getBodyAttachments(bodyKey)) { - if (attachmentRef == proxy || attachmentRef.equals(proxy)) { + if (sameRef(attachmentRef, proxy)) { continue; } BodyAttachmentComponent attachment = store.getComponent(attachmentRef, @@ -779,6 +779,16 @@ private static boolean sameSpaceId(@Nullable SpaceId first, @Nullable SpaceId se return Objects.equals(first, second); } + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); + } + @Nullable private static Ref spawnProxy(@Nonnull Store store, @Nonnull RigidBodyKey bodyKey, From 973cc022773eb8691a863dbf3a17d6528a2e8a77 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:22:27 +0200 Subject: [PATCH 173/534] refactor(physicsstore): route projection attachments by ref Signed-off-by: Blovien --- .../PhysicsProjectionIndexResource.java | 35 ++++++ .../PhysicsWorldRuntimeResource.java | 110 ++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index f20d7072..af6be4de 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -145,6 +146,40 @@ public Ref getGeneratedVisualProxy(@Nonnull Ref bodyR return liveGeneratedVisualProxy(bodyRef); } + @Nonnull + public Collection getGeneratedVisualProxyBodyKeys() { + List bodyKeys = new ArrayList<>(); + synchronized (this) { + for (Iterator>> iterator = + generatedVisualProxies.entrySet().iterator(); iterator.hasNext();) { + Map.Entry> entry = iterator.next(); + Ref proxy = entry.getValue(); + if (proxy != null && proxy.isValid()) { + bodyKeys.add(RigidBodyKey.of(entry.getKey())); + } else { + iterator.remove(); + } + } + } + return bodyKeys; + } + + public int generatedVisualProxyCount() { + int count = 0; + synchronized (this) { + for (Iterator>> iterator = + generatedVisualProxies.entrySet().iterator(); iterator.hasNext();) { + Ref proxy = iterator.next().getValue(); + if (proxy != null && proxy.isValid()) { + count++; + } else { + iterator.remove(); + } + } + } + return count; + } + @Nullable private Ref liveGeneratedVisualProxy( @Nonnull Map> proxiesByKey, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 8bafd77e..c9bc5dea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -257,6 +257,27 @@ private static Store physicsStore(@Nonnull World world) { .getStore(); } + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); + } + + @Nonnull + private PhysicsProjectionIndexResource authoritativeProjectionIndex(@Nonnull String operation) { + Store entityStore = owningStore; + if (entityStore == null) { + throw new IllegalStateException("Cannot " + operation + + " through authoritative PhysicsStore projection before this resource is attached " + + "to an EntityStore"); + } + return entityStore.getResource(PhysicsProjectionIndexResource.getResourceType()); + } + @Nonnull private static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull SpaceId spaceId) { @@ -1974,14 +1995,47 @@ public Collection getBodyRegistrationViews(@Nonnull @Nonnull @Override public Collection> getBodyAttachments(@Nonnull RigidBodyKey bodyKey) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("read physics body attachments"); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve body attachment key"); + return bodyRef != null + ? projection.getAttachments(bodyRef) + : projection.getAttachments(bodyKey.value()); + } return visualRuntime.getAttachments(bodyKey); } + @Nonnull + public Collection> getBodyAttachments(@Nonnull Ref bodyRef) { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativeProjectionIndex("read physics body attachments") + .getAttachments(bodyRef); + } + return List.of(); + } + @Override public boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("check physics body attachments"); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve body attachment key"); + return bodyRef != null + ? projection.hasAttachments(bodyRef) + : projection.hasAttachments(bodyKey.value()); + } return visualRuntime.hasAttachments(bodyKey); } + public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { + return hasAttachedAuthoritativePhysicsStore() + && authoritativeProjectionIndex("check physics body attachments") + .hasAttachments(bodyRef); + } + public void registerBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { visualRuntime.registerAttachment(bodyKey, attachment); } @@ -1992,33 +2046,89 @@ public void unregisterBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref @Nullable public Ref getGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("read generated visual proxy"); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve generated visual proxy key"); + return bodyRef != null + ? projection.getGeneratedVisualProxy(bodyRef) + : projection.getGeneratedVisualProxy(bodyKey.value()); + } return visualRuntime.getGeneratedVisualProxy(bodyKey); } + @Nullable + public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativeProjectionIndex("read generated visual proxy") + .getGeneratedVisualProxy(bodyRef); + } + return null; + } + @Nonnull public Collection getGeneratedVisualProxyBodyKeys() { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativeProjectionIndex("list generated visual proxies") + .getGeneratedVisualProxyBodyKeys(); + } return visualRuntime.getGeneratedVisualProxyBodyKeys(); } public int getGeneratedVisualProxyCount() { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativeProjectionIndex("count generated visual proxies") + .generatedVisualProxyCount(); + } return visualRuntime.generatedVisualProxyCount(); } public void setGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxy) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("set generated visual proxy"); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve generated visual proxy key"); + projection.setGeneratedVisualProxy(bodyKey.value(), bodyRef, proxy); + return; + } visualRuntime.setGeneratedVisualProxy(bodyKey, proxy); } public void clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { + if (hasAttachedAuthoritativePhysicsStore()) { + authoritativeProjectionIndex("clear generated visual proxy") + .clearGeneratedVisualProxy(bodyKey.value()); + return; + } visualRuntime.clearGeneratedVisualProxy(bodyKey); } public boolean clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref expectedProxy) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("clear generated visual proxy"); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve generated visual proxy key"); + Ref registered = bodyRef != null + ? projection.getGeneratedVisualProxy(bodyRef) + : projection.getGeneratedVisualProxy(bodyKey.value()); + if (!sameRef(registered, expectedProxy)) { + return false; + } + projection.clearGeneratedVisualProxy(bodyKey.value(), bodyRef, expectedProxy); + return true; + } return visualRuntime.clearGeneratedVisualProxy(bodyKey, expectedProxy); } public boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxy) { + if (hasAttachedAuthoritativePhysicsStore()) { + return sameRef(getGeneratedVisualProxy(bodyKey), proxy); + } return visualRuntime.isGeneratedVisualProxy(bodyKey, proxy); } From 04f4f4543ecf3aa33a0b8ff75c750434e66eac13 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:25:52 +0200 Subject: [PATCH 174/534] refactor(physicsstore): bridge projection attachment writes Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 54 +++++++++++++++++++ .../visual/GeneratedProxyLifecycle.java | 8 +-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index c9bc5dea..a1e323e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2037,13 +2037,49 @@ && authoritativeProjectionIndex("check physics body attachments") } public void registerBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { + if (hasAttachedAuthoritativePhysicsStore()) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve body attachment key"); + authoritativeProjectionIndex("register physics body attachment") + .registerAttachment(bodyKey.value(), bodyRef, attachment); + return; + } visualRuntime.registerAttachment(bodyKey, attachment); } + public void registerBodyAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref attachment) { + if (hasAttachedAuthoritativePhysicsStore()) { + authoritativeProjectionIndex("register physics body attachment") + .registerAttachment(bodyUuid, bodyRef, attachment); + return; + } + visualRuntime.registerAttachment(RigidBodyKey.of(bodyUuid), attachment); + } + public void unregisterBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { + if (hasAttachedAuthoritativePhysicsStore()) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve body attachment key"); + authoritativeProjectionIndex("unregister physics body attachment") + .unregisterAttachment(bodyKey.value(), bodyRef, attachment); + return; + } visualRuntime.unregisterAttachment(bodyKey, attachment); } + public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref attachment) { + if (hasAttachedAuthoritativePhysicsStore()) { + authoritativeProjectionIndex("unregister physics body attachment") + .unregisterAttachment(bodyUuid, bodyRef, attachment); + return; + } + visualRuntime.unregisterAttachment(RigidBodyKey.of(bodyUuid), attachment); + } + @Nullable public Ref getGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { if (hasAttachedAuthoritativePhysicsStore()) { @@ -2124,6 +2160,24 @@ public boolean clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, return visualRuntime.clearGeneratedVisualProxy(bodyKey, expectedProxy); } + public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref expectedProxy) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("clear generated visual proxy"); + Ref registered = bodyRef != null + ? projection.getGeneratedVisualProxy(bodyRef) + : projection.getGeneratedVisualProxy(bodyUuid); + if (!sameRef(registered, expectedProxy)) { + return false; + } + projection.clearGeneratedVisualProxy(bodyUuid, bodyRef, expectedProxy); + return true; + } + return visualRuntime.clearGeneratedVisualProxy(RigidBodyKey.of(bodyUuid), expectedProxy); + } + public boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxy) { if (hasAttachedAuthoritativePhysicsStore()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index aeb69901..b2e23b67 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -12,6 +12,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -49,11 +50,12 @@ public static void clearMissingAttachment(@Nonnull Ref entityRef, @Nonnull BodyAttachmentComponent attachment, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull CommandBuffer commandBuffer) { - RigidBodyKey bodyKey = RigidBodyKey.of(attachment.getBodyUuid()); - resource.unregisterBodyAttachment(bodyKey, entityRef); + UUID bodyUuid = attachment.getBodyUuid(); + resource.unregisterBodyAttachment(bodyUuid, attachment.getBodyRef(), entityRef); resource.clearBodySyncState(entityRef); if (attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { - removeProxy(commandBuffer, resource, bodyKey, entityRef); + resource.clearGeneratedVisualProxy(bodyUuid, attachment.getBodyRef(), entityRef); + removeEntity(commandBuffer, entityRef); } else if (attachment.shouldRemoveEntityWhenBodyMissing()) { removeEntity(commandBuffer, entityRef); } else { From 7ddfc715cc3d7b54ae29f9df16fb0090cc1fcea5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:28:43 +0200 Subject: [PATCH 175/534] refactor(examples): read explosive fuse bodies by ref Signed-off-by: Blovien --- .../systems/ExplosiveFuseTickSystem.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index f7ffbadd..57085e5a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -122,9 +122,15 @@ private static SpaceId attachmentSpaceId(@Nonnull Store store, .getPhysicsStore().getStore(); PhysicsStoreThreading.requireWorldThread(physics, "read copied PhysicsStore explosive body registration"); - PhysicsBodyRegistrationView registration = physics - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(attachment.getBodyUuid()); + PhysicsBodyRegistrationResource registrations = physics + .getResource(PhysicsBodyRegistrationResource.getResourceType()); + Ref bodyRef = attachment.getBodyRef(); + PhysicsBodyRegistrationView registration = bodyRef != null && bodyRef.isValid() + ? registrations.getBodyRegistrationView(bodyRef) + : null; + if (registration == null) { + registration = registrations.getBodyRegistrationView(attachment.getBodyUuid()); + } return registration != null ? registration.spaceId() : null; } @@ -136,9 +142,18 @@ private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store .getPhysicsStore().getStore(); PhysicsStoreThreading.requireWorldThread(physics, "read copied PhysicsStore explosive body snapshot"); - PhysicsStoreBodySnapshot snapshot = physics - .getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(bodyUuid); + PhysicsSnapshotResource snapshots = physics + .getResource(PhysicsSnapshotResource.getResourceType()); + Ref bodyRef = attachment.getBodyRef(); + PhysicsStoreBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() + ? snapshots.getBody(bodyRef) + : null; + if (snapshot != null && !bodyUuid.equals(snapshot.bodyUuid())) { + snapshot = null; + } + if (snapshot == null) { + snapshot = snapshots.getBody(bodyUuid); + } return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; } From 5e08b1e73a8fd93ce729a77b5c8f4cdcad5c2486 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:32:25 +0200 Subject: [PATCH 176/534] refactor(physicsstore): expose ref attachment lookup Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 2 ++ .../resources/PhysicsWorldResource.java | 21 +++++++++++++++++++ .../examples/commands/GrabCommand.java | 6 +++--- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index a1e323e8..1fd6acea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2008,6 +2008,7 @@ public Collection> getBodyAttachments(@Nonnull RigidBodyKey bod } @Nonnull + @Override public Collection> getBodyAttachments(@Nonnull Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativeProjectionIndex("read physics body attachments") @@ -2030,6 +2031,7 @@ public boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey) { return visualRuntime.hasAttachments(bodyKey); } + @Override public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { return hasAttachedAuthoritativePhysicsStore() && authoritativeProjectionIndex("check physics body attachments") diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 9d837819..ceade79e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; @@ -21,6 +22,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import java.util.Collection; +import java.util.List; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -350,12 +352,31 @@ public abstract Collection getBodyRegistrationViews @Nonnull public abstract Collection> getBodyAttachments(@Nonnull RigidBodyKey bodyKey); + /** + * Returns ECS attachments associated with a live PhysicsStore body ref. + * + *

Prefer this overload when a caller already has a body row ref, such as from a PhysicsStore + * raycast or copied registration. The key overload remains the compatibility boundary.

+ */ + @Nonnull + public Collection> getBodyAttachments(@Nonnull Ref bodyRef) { + return List.of(); + } + /** * Returns whether a registered body has one or more ECS attachments without materializing the * attachment collection. */ public abstract boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey); + /** + * Returns whether a live PhysicsStore body ref has one or more ECS attachments without + * materializing the attachment collection. + */ + public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { + return false; + } + /** * Destroys all registered bodies while preserving registered physics spaces. */ diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 9610ce5e..9a486305 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -305,7 +305,7 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource HitSelection best = null; for (HitCandidate candidate : candidates) { AttachmentSelection attachments = - inspectGameplayAttachments(resource, store, controllableType, candidate.bodyKey()); + inspectGameplayAttachments(resource, store, controllableType, candidate.bodyRef()); if (attachments.controllableAttachment() == null && attachments.hasGameplayAttachment()) { continue; } @@ -344,9 +344,9 @@ private static RigidBodyStateView bodyState(@Nonnull World world, private static AttachmentSelection inspectGameplayAttachments(@Nonnull PhysicsWorldResource resource, @Nonnull Store store, @Nonnull ComponentType controllableType, - @Nonnull RigidBodyKey bodyKey) { + @Nonnull Ref bodyRef) { boolean hasGameplayAttachment = false; - for (Ref attachmentRef : resource.getBodyAttachments(bodyKey)) { + for (Ref attachmentRef : resource.getBodyAttachments(bodyRef)) { BodyAttachmentComponent attachment = store.getComponent(attachmentRef, ATTACHMENT_TYPE); if (attachment == null || attachment.getLifecycle() == BodyAttachmentComponent.AttachmentLifecycle.GENERATED_PROXY) { From 61734b315c7f74f7988b386e89bacc8d3a87634e Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:41:04 +0200 Subject: [PATCH 177/534] refactor(control): key controlled bodies by ref Signed-off-by: Blovien --- .../control/PhysicsControlRuntimeState.java | 61 ------------------- .../PhysicsWorldRuntimeResource.java | 10 +-- .../resources/body/PhysicsBodyRuntime.java | 1 - 3 files changed, 2 insertions(+), 70 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java index b9ee596d..76b86241 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java @@ -2,12 +2,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import java.util.UUID; import javax.annotation.Nonnull; /** @@ -17,33 +12,15 @@ public final class PhysicsControlRuntimeState { private final Int2ObjectOpenHashMap> controlledBodyRefsByRowIndex = new Int2ObjectOpenHashMap<>(); - private final Long2ObjectOpenHashMap controlledBodyLeastBitsByMostBits = - new Long2ObjectOpenHashMap<>(); public synchronized void markBodyControlled(@Nonnull Ref bodyRef) { controlledBodyRefsByRowIndex.put(bodyRef.getIndex(), bodyRef); } - public synchronized void markBodyControlled(@Nonnull UUID bodyUuid) { - add(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); - } - - public synchronized void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { - add(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); - } - - public synchronized void clearControlledBody(@Nonnull UUID bodyUuid) { - remove(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); - } - public synchronized void clearControlledBody(@Nonnull Ref bodyRef) { remove(bodyRef); } - public synchronized void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { - remove(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); - } - public synchronized boolean isBodyControlled(@Nonnull Ref bodyRef) { Ref controlledRef = controlledBodyRefsByRowIndex.get(bodyRef.getIndex()); if (controlledRef == null) { @@ -56,29 +33,12 @@ public synchronized boolean isBodyControlled(@Nonnull Ref bodyRef) return controlledRef == bodyRef || sameLiveRef(controlledRef, bodyRef); } - public synchronized boolean isBodyControlled(@Nonnull UUID bodyUuid) { - return contains(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); - } - - public synchronized boolean isBodyControlled(@Nonnull RigidBodyKey bodyKey) { - return contains(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); - } - - public synchronized void clearBody(@Nonnull UUID bodyUuid) { - remove(bodyUuid.getMostSignificantBits(), bodyUuid.getLeastSignificantBits()); - } - public synchronized void clearBody(@Nonnull Ref bodyRef) { remove(bodyRef); } - public synchronized void clearBody(@Nonnull RigidBodyKey bodyKey) { - remove(bodyKey.mostSignificantBits(), bodyKey.leastSignificantBits()); - } - public synchronized void clear() { controlledBodyRefsByRowIndex.clear(); - controlledBodyLeastBitsByMostBits.clear(); } private void remove(@Nonnull Ref bodyRef) { @@ -100,25 +60,4 @@ private static boolean sameLiveRef(@Nonnull Ref first, && first.getStore() == second.getStore() && first.getIndex() == second.getIndex(); } - - private void add(long mostSignificantBits, long leastSignificantBits) { - controlledBodyLeastBitsByMostBits.computeIfAbsent(mostSignificantBits, - _ -> new LongOpenHashSet()).add(leastSignificantBits); - } - - private void remove(long mostSignificantBits, long leastSignificantBits) { - LongSet leastBits = controlledBodyLeastBitsByMostBits.get(mostSignificantBits); - if (leastBits == null) { - return; - } - leastBits.remove(leastSignificantBits); - if (leastBits.isEmpty()) { - controlledBodyLeastBitsByMostBits.remove(mostSignificantBits); - } - } - - private boolean contains(long mostSignificantBits, long leastSignificantBits) { - LongSet leastBits = controlledBodyLeastBitsByMostBits.get(mostSignificantBits); - return leastBits != null && leastBits.contains(leastSignificantBits); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 1fd6acea..3bf9789a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2281,7 +2281,6 @@ public void markBodyControlled(@Nonnull UUID bodyUuid) { if (bodyRef != null) { controlRuntime.markBodyControlled(bodyRef); } - controlRuntime.markBodyControlled(bodyUuid); } public void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { @@ -2290,7 +2289,6 @@ public void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { if (bodyRef != null) { controlRuntime.markBodyControlled(bodyRef); } - controlRuntime.markBodyControlled(bodyKey); } public void clearControlledBody(@Nonnull Ref bodyRef) { @@ -2303,7 +2301,6 @@ public void clearControlledBody(@Nonnull UUID bodyUuid) { if (bodyRef != null) { controlRuntime.clearControlledBody(bodyRef); } - controlRuntime.clearControlledBody(bodyUuid); } public void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { @@ -2312,7 +2309,6 @@ public void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { if (bodyRef != null) { controlRuntime.clearControlledBody(bodyRef); } - controlRuntime.clearControlledBody(bodyKey); } public boolean isBodyControlled(@Nonnull Ref bodyRef) { @@ -2322,15 +2318,13 @@ public boolean isBodyControlled(@Nonnull Ref bodyRef) { public boolean isBodyControlled(@Nonnull UUID bodyUuid) { Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, "resolve controlled body UUID"); - return bodyRef != null && controlRuntime.isBodyControlled(bodyRef) - || controlRuntime.isBodyControlled(bodyUuid); + return bodyRef != null && controlRuntime.isBodyControlled(bodyRef); } public boolean isBodyControlled(@Nonnull RigidBodyKey bodyKey) { Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), "resolve controlled body key"); - return bodyRef != null && controlRuntime.isBodyControlled(bodyRef) - || controlRuntime.isBodyControlled(bodyKey); + return bodyRef != null && controlRuntime.isBodyControlled(bodyRef); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 8c336db7..5969aee2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -152,7 +152,6 @@ public void clearBodyStateWithoutMarkingWorldChanged() { public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { visualRuntime.clearBodyRuntimeState(bodyKey); - controlRuntime.clearBody(bodyKey); chunkRuntime.clearBody(bodyKey); lifecycleState.removeBodySnapshot(bodyKey); } From 4c2fe8ff71d6b3c85e84342a0fe928fcecb4ab1a Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:51:33 +0200 Subject: [PATCH 178/534] refactor(examples): author joints with physics refs Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 51 ++++++++ .../examples/commands/GrabCommand.java | 36 +++--- .../examples/commands/JointsCommand.java | 115 +++++++++--------- .../commands/stress/StressJointsCommand.java | 41 ++++--- 4 files changed, 155 insertions(+), 88 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index af1965b2..23a86059 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -15,6 +15,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -27,6 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; @@ -71,6 +73,36 @@ public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); } + @Nullable + public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world, + @Nonnull SpaceId spaceId) { + Store store = physicsStore(world); + PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space ref"); + UUID spaceUuid = store + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + if (spaceUuid == null) { + return null; + } + Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + return ref != null && ref.getStore() == store && ref.isValid() ? ref : null; + } + + @Nonnull + public static UUID physicsStoreRowUuid(@Nonnull Ref ref) { + Store store = ref.getStore(); + PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore row UUID"); + if (!ref.isValid()) { + throw new IllegalStateException("PhysicsStore row ref is not valid: " + ref); + } + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + if (uuid == null) { + throw new IllegalStateException("PhysicsStore row has no UUID component: " + ref); + } + return uuid.getUuid(); + } + @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyRowDescriptor row) { @@ -292,6 +324,25 @@ public static BodyRowDescriptor bodyRow(@Nonnull UUID spaceUuid, PhysicsBodyPersistenceMode.PERSISTENT); } + @Nonnull + public static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity) { + BodyRowDescriptor row = bodyRow(physicsStoreRowUuid(spaceRef), + bodyUuid, + bodyCenter, + shape, + mass, + settings, + linearVelocity); + row.body().setSpaceRef(spaceRef); + return row; + } + @Nonnull private static BodyRowDescriptor bodyRow(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 9a486305..a631bad8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -171,13 +171,13 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, if (selectedState == null) { return null; } - UUID spaceUuid; + Ref spaceRef; try { - spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, selectedSpaceId); + spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, selectedSpaceId); } catch (IllegalStateException exception) { return null; } - if (spaceUuid == null) { + if (spaceRef == null) { return null; } @@ -197,10 +197,10 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, BodyCommandComponent.wake()); try { Ref anchorBodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - anchorBodyRow(spaceUuid, anchorBodyUuid, hitPoint)); + anchorBodyRow(spaceRef, anchorBodyUuid, hitPoint)); Ref controlJointRef = ExamplePhysicsUtils.addPhysicsStoreJoint(world, controlJointUuid, - controlJoint(spaceUuid, anchorBodyUuid, selection.bodyKey().value(), bodyLocalHit)); + controlJoint(spaceRef, anchorBodyRef, selectedBodyRef, bodyLocalHit)); return new GrabPhysicsState(selectedState.bodyType(), anchorBodyRef, controlJointRef, @@ -211,13 +211,16 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, } @Nonnull - private static BodyRowDescriptor anchorBodyRow(@Nonnull UUID spaceUuid, + private static BodyRowDescriptor anchorBodyRow(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f hitPoint) { + UUID spaceUuid = ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef); + BodyComponent body = new BodyComponent(spaceUuid, + PhysicsBodyKind.TEMPORARY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + body.setSpaceRef(spaceRef); return BodyRowDescriptor.of(bodyUuid, - new BodyComponent(spaceUuid, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY), + body, new DynamicsComponent(PhysicsBodyType.KINEMATIC, 1.0f, 0.0f, @@ -259,14 +262,17 @@ private static TargetComponent initialAnchorTarget(@Nonnull Vector3f hitPoint) { } @Nonnull - private static JointComponent controlJoint(@Nonnull UUID spaceUuid, - @Nonnull UUID anchorBodyUuid, - @Nonnull UUID bodyUuid, + private static JointComponent controlJoint(@Nonnull Ref spaceRef, + @Nonnull Ref anchorBodyRef, + @Nonnull Ref bodyRef, @Nonnull Vector3f bodyLocalHit) { JointComponent joint = new JointComponent(); - joint.setSpaceUuid(spaceUuid); - joint.setBodyAUuid(anchorBodyUuid); - joint.setBodyBUuid(bodyUuid); + joint.setSpaceUuid(ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef)); + joint.setSpaceRef(spaceRef); + joint.setBodyAUuid(ExamplePhysicsUtils.physicsStoreRowUuid(anchorBodyRef)); + joint.setBodyARef(anchorBodyRef); + joint.setBodyBUuid(ExamplePhysicsUtils.physicsStoreRowUuid(bodyRef)); + joint.setBodyBRef(bodyRef); joint.setType(JointType.POINT); joint.setAnchorA(new Vector3f()); joint.setAnchorB(bodyLocalHit); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index 9c1d7a23..6696ae78 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; @@ -77,23 +78,23 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static List tryCreatePhysicsStoreDemo(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID spaceUuid; + Ref spaceRef; try { - spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); + spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); } catch (IllegalStateException exception) { return null; } - if (spaceUuid == null) { + if (spaceRef == null) { return null; } List createdBodies = new ArrayList<>(10); try { - createFixed(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin)); - createPoint(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); - createHinge(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); - createSlider(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); - createSpring(createdBodies, world, spaceUuid, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); + createFixed(createdBodies, world, spaceRef, spaceId, new Vector3d(origin)); + createPoint(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); + createHinge(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); + createSlider(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); + createSpring(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); } catch (IllegalStateException exception) { return null; } @@ -102,17 +103,17 @@ private static List tryCreatePhysicsStoreDemo(@Nonnull World w private static void createFixed(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID childUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, + CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody child = spawnBox(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), - joint(spaceUuid, - anchorUuid, - childUuid, + joint(spaceRef, + anchor, + child, JointType.FIXED, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -121,22 +122,22 @@ private static void createFixed(@Nonnull List createdBodies, private static void createPoint(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID bobUuid = spawnBox(createdBodies, + CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody bob = spawnBox(createdBodies, world, - spaceUuid, + spaceRef, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f, new Vector3f(1.5f, 0.0f, 0.0f)); ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), - joint(spaceUuid, - anchorUuid, - bobUuid, + joint(spaceRef, + anchor, + bob, JointType.POINT, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -145,15 +146,15 @@ private static void createPoint(@Nonnull List createdBodies, private static void createHinge(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID armUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, + CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody arm = spawnBox(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); - JointComponent joint = joint(spaceUuid, - anchorUuid, - armUuid, + JointComponent joint = joint(spaceRef, + anchor, + arm, JointType.HINGE, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -168,15 +169,15 @@ private static void createHinge(@Nonnull List createdBodies, private static void createSlider(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID blockUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, + CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody block = spawnBox(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(TOUCHING_SPACING, 0.0, 0.0), 1.0f); - JointComponent joint = joint(spaceUuid, - anchorUuid, - blockUuid, + JointComponent joint = joint(spaceRef, + anchor, + block, JointType.SLIDER, new Vector3f(HALF_SIZE, 0.0f, 0.0f), new Vector3f(-HALF_SIZE, 0.0f, 0.0f), @@ -191,20 +192,20 @@ private static void createSlider(@Nonnull List createdBodies, private static void createSpring(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - UUID anchorUuid = spawnBox(createdBodies, world, spaceUuid, spaceId, origin, 0.0f); - UUID bobUuid = spawnBox(createdBodies, + CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody bob = spawnBox(createdBodies, world, - spaceUuid, + spaceRef, spaceId, new Vector3d(origin).add(0.0, -(TOUCHING_SPACING + SPRING_REST_LENGTH), 0.0), 1.0f, new Vector3f(1.0f, 0.0f, 0.0f)); - JointComponent joint = joint(spaceUuid, - anchorUuid, - bobUuid, + JointComponent joint = joint(spaceRef, + anchor, + bob, JointType.SPRING, new Vector3f(0.0f, -HALF_SIZE, 0.0f), new Vector3f(0.0f, HALF_SIZE, 0.0f), @@ -215,54 +216,58 @@ private static void createSpring(@Nonnull List createdBodies, ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); } - private static UUID spawnBox(@Nonnull List createdBodies, + private static CreatedBlockBody spawnBox(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass) { - return spawnBox(createdBodies, world, spaceUuid, spaceId, position, mass, null); + return spawnBox(createdBodies, world, spaceRef, spaceId, position, mass, null); } - private static UUID spawnBox(@Nonnull List createdBodies, + private static CreatedBlockBody spawnBox(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass, @Nullable Vector3f linearVelocity) { UUID bodyUuid = UUID.randomUUID(); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), mass, RigidBodySpawnSettings.material(0.6f, 0.15f), linearVelocity)); - createdBodies.add(new CreatedBlockBody(bodyUuid, + CreatedBlockBody created = new CreatedBlockBody(bodyUuid, bodyRef, spaceId, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, (float) position.x, (float) position.y, (float) position.z, - mass > 0.0f)); - return bodyUuid; + mass > 0.0f); + createdBodies.add(created); + return created; } @Nonnull - private static JointComponent joint(@Nonnull UUID spaceUuid, - @Nonnull UUID bodyAUuid, - @Nonnull UUID bodyBUuid, + private static JointComponent joint(@Nonnull Ref spaceRef, + @Nonnull CreatedBlockBody bodyA, + @Nonnull CreatedBlockBody bodyB, @Nonnull JointType type, @Nonnull Vector3f anchorA, @Nonnull Vector3f anchorB, @Nonnull Vector3f axis) { JointComponent joint = new JointComponent(); - joint.setSpaceUuid(spaceUuid); - joint.setBodyAUuid(bodyAUuid); - joint.setBodyBUuid(bodyBUuid); + joint.setSpaceUuid(ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef)); + joint.setSpaceRef(spaceRef); + joint.setBodyAUuid(bodyA.bodyUuid()); + joint.setBodyARef(bodyA.bodyRef()); + joint.setBodyBUuid(bodyB.bodyUuid()); + joint.setBodyBRef(bodyB.bodyRef()); joint.setType(type); joint.setAnchorA(anchorA); joint.setAnchorB(anchorB); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 18b621ea..af6f4421 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; @@ -77,13 +78,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); - UUID spaceUuid; + Ref spaceRef; try { - spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); + spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); } catch (IllegalStateException exception) { - spaceUuid = null; + spaceRef = null; } - if (spaceUuid == null) { + if (spaceRef == null) { ctx.sender().sendMessage(Message.raw( "Cannot queue stress joint demo because the target space is not bound in PhysicsStore.")); return CompletableFuture.completedFuture(null); @@ -105,7 +106,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d rowOrigin = new Vector3d(origin).add(0.0, 0.0, row * ROW_SPACING); createdBodyCount += appendRow(createdBodyRows, world, - spaceUuid, + spaceRef, spaceId, rowOrigin, rowJoints, @@ -129,7 +130,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static int appendRow(@Nonnull List createdBodies, @Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin, int jointCount, @@ -138,7 +139,7 @@ private static int appendRow(@Nonnull List createdBodies, double spacing = jointType == 4 ? TOUCHING_SPACING + SPRING_REST_LENGTH : TOUCHING_SPACING; int bodyCount = jointCount + 1; - UUID[] bodyUuids = new UUID[bodyCount]; + CreatedBlockBody[] bodies = new CreatedBlockBody[bodyCount]; float[] positions = new float[bodyCount * 3]; long bodyUuidRunId = UUID.randomUUID().getMostSignificantBits(); long jointUuidRunId = UUID.randomUUID().getMostSignificantBits(); @@ -147,14 +148,13 @@ private static int appendRow(@Nonnull List createdBodies, for (int i = 0; i < bodyCount; i++) { UUID bodyUuid = new UUID(bodyUuidRunId, i + 1L); - bodyUuids[i] = bodyUuid; int positionOffset = i * 3; positions[positionOffset] = (float) (origin.x + i * spacing); positions[positionOffset + 1] = (float) origin.y; positions[positionOffset + 2] = (float) origin.z; float mass = i == 0 ? 0.0f : 1.0f; var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceRef, bodyUuid, new Vector3f(positions[positionOffset], positions[positionOffset + 1], @@ -163,7 +163,7 @@ private static int appendRow(@Nonnull List createdBodies, mass, spawnSettings, initialVelocity(jointType, i))); - createdBodies.add(new CreatedBlockBody( + CreatedBlockBody created = new CreatedBlockBody( bodyUuid, bodyRef, spaceId, @@ -171,12 +171,14 @@ private static int appendRow(@Nonnull List createdBodies, positions[positionOffset], positions[positionOffset + 1], positions[positionOffset + 2], - i > 0)); + i > 0); + bodies[i] = created; + createdBodies.add(created); } for (int i = 0; i < jointCount; i++) { ExamplePhysicsUtils.addPhysicsStoreJoint(world, new UUID(jointUuidRunId, i + 1L), - joint(spaceUuid, bodyUuids[i], bodyUuids[i + 1], jointType)); + joint(spaceRef, bodies[i], bodies[i + 1], jointType)); } return bodyCount; } @@ -189,9 +191,9 @@ private String blockType(@Nonnull CommandContext ctx) { } @Nonnull - private static JointComponent joint(@Nonnull UUID spaceUuid, - @Nonnull UUID previousUuid, - @Nonnull UUID currentUuid, + private static JointComponent joint(@Nonnull Ref spaceRef, + @Nonnull CreatedBlockBody previous, + @Nonnull CreatedBlockBody current, int jointType) { JointType type = switch (jointType) { case 0 -> JointType.FIXED; @@ -201,9 +203,12 @@ private static JointComponent joint(@Nonnull UUID spaceUuid, default -> JointType.SPRING; }; JointComponent joint = new JointComponent(); - joint.setSpaceUuid(spaceUuid); - joint.setBodyAUuid(previousUuid); - joint.setBodyBUuid(currentUuid); + joint.setSpaceUuid(ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef)); + joint.setSpaceRef(spaceRef); + joint.setBodyAUuid(previous.bodyUuid()); + joint.setBodyARef(previous.bodyRef()); + joint.setBodyBUuid(current.bodyUuid()); + joint.setBodyBRef(current.bodyRef()); joint.setType(type); joint.setAnchorA(new Vector3f(HALF_SIZE, 0.0f, 0.0f)); joint.setAnchorB(new Vector3f(-HALF_SIZE, 0.0f, 0.0f)); From 0c8f3939e2fadfc75175c8d29daf35889cbfe06b Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 10:56:33 +0200 Subject: [PATCH 179/534] refactor(examples): resolve body authoring spaces by ref Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 47 ++++++++++++++----- .../examples/commands/ForcesCommand.java | 19 ++++---- .../commands/PhysicsStoreExampleCommands.java | 14 +++--- .../explosive/ExplosiveBlockRuntime.java | 9 ++-- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 23a86059..5977726a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -64,7 +64,7 @@ public static Store physicsStore(@Nonnull World world) { } @Nullable - public static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, + private static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, @Nonnull SpaceId spaceId) { Store store = physicsStore(world); PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); @@ -270,13 +270,13 @@ private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store spaceRef; try { - spaceUuid = resolvePhysicsStoreSpaceUuid(world, spaceId); + spaceRef = resolvePhysicsStoreSpaceRef(world, spaceId); } catch (IllegalStateException exception) { return null; } - if (spaceUuid == null) { + if (spaceRef == null) { return null; } @@ -285,7 +285,7 @@ private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store bodyRef; try { bodyRef = addPhysicsStoreBody(world, - bodyRow(spaceUuid, + bodyRow(spaceRef, bodyUuid, bodyCenter, shape, @@ -307,7 +307,7 @@ private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store spaceRef, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + BodyRowDescriptor row = bodyRow(physicsStoreRowUuid(spaceRef), + bodyUuid, + bodyCenter, + shape, + mass, + settings, + linearVelocity, + kind, + persistenceMode); + row.body().setSpaceRef(spaceRef); + return row; + } + @Nonnull public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, @Nonnull SpaceId spaceId, @@ -421,8 +444,8 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, return new DynamicBodyBatchPlan(List.of(), 0L); } - UUID spaceUuid = resolvePhysicsStoreSpaceUuid(world, spaceId); - if (spaceUuid == null) { + Ref spaceRef = resolvePhysicsStoreSpaceRef(world, spaceId); + if (spaceRef == null) { throw new IllegalStateException("Cannot add dynamic body rows because the target space is not " + "bound in PhysicsStore: " + spaceId.value()); } @@ -430,7 +453,7 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, List bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); - bodies.add(bodyRow(spaceUuid, + bodies.add(bodyRow(spaceRef, bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -536,8 +559,8 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store spaceRef = resolvePhysicsStoreSpaceRef(world, spaceId); + if (spaceRef == null) { throw new IllegalStateException("Cannot spawn block body batch because the target space is not " + "bound in PhysicsStore: " + spaceId.value()); } @@ -545,7 +568,7 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store rows = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); - rows.add(bodyRow(spaceUuid, + rows.add(bodyRow(spaceRef, bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index fb842613..c867f8f4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -12,6 +12,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -91,19 +92,19 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, @Nonnull Vector3d offCenterPosition, @Nonnull Vector3d torquePosition, @Nonnull Vector3d forcePosition) { - UUID spaceUuid; + Ref spaceRef; try { - spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); + spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); } catch (IllegalStateException exception) { return null; } - if (spaceUuid == null) { + if (spaceRef == null) { return null; } try { CreatedBlockBody central = spawnBox(world, - spaceUuid, + spaceRef, spaceId, centralPosition, BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, @@ -115,7 +116,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, 0.0f, 0.0f)); CreatedBlockBody offCenter = spawnBox(world, - spaceUuid, + spaceRef, spaceId, offCenterPosition, BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, @@ -127,7 +128,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, 0.5f, 0.5f)); CreatedBlockBody torque = spawnBox(world, - spaceUuid, + spaceRef, spaceId, torquePosition, BodyCommandComponent.vector(BodyCommandComponent.Kind.TORQUE_IMPULSE, @@ -139,7 +140,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, 0.0f, 0.0f)); CreatedBlockBody force = spawnBox(world, - spaceUuid, + spaceRef, spaceId, forcePosition, BodyCommandComponent.vector(BodyCommandComponent.Kind.FORCE, @@ -157,13 +158,13 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, } private static CreatedBlockBody spawnBox(@Nonnull World world, - @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, @Nonnull BodyCommandComponent command) { UUID bodyUuid = UUID.randomUUID(); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 124c8543..53e3e3e4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -149,15 +149,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, UUID bodyUuid = UUID.randomUUID(); Vector3d spawn = new Vector3d(playerPos).add(0.0, 2.0, 0.0); - UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); - if (spaceUuid == null) { + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound yet.")); return CompletableFuture.completedFuture(null); } Vector3f targetPosition = vector(spawn); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceRef, bodyUuid, targetPosition, PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), @@ -314,8 +315,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); boolean contactEventsEnabled = contactEventsEnabled(resource); - UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); - if (spaceUuid == null) { + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound yet.")); return CompletableFuture.completedFuture(null); @@ -328,7 +330,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, UUID bodyUuid = UUID.randomUUID(); ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceRef, bodyUuid, vector(spawn), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index cf1a3103..24c04bd6 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentAccessor; import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.math.vector.Rotation3f; @@ -18,6 +19,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -107,8 +109,9 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @Nonnull ExplosiveBlockComponent settings) { - UUID spaceUuid = ExamplePhysicsUtils.resolvePhysicsStoreSpaceUuid(world, spaceId); - if (spaceUuid == null) { + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { throw new IllegalStateException("Cannot spawn explosive fragments because PhysicsStore " + "space id=" + spaceId.value() + " is not bound"); } @@ -152,7 +155,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e settings.getVerticalLift()) .mul(group.mass()); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceUuid, + ExamplePhysicsUtils.bodyRow(spaceRef, bodyUuid, toVector3f(groupCenter), group.shape(), From ad010add437aee395c5ae305b4f553d10c280c88 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:00:44 +0200 Subject: [PATCH 180/534] feat(physicsstore): add ref body row builders Signed-off-by: Blovien --- .../plugin/physicsstore/PhysicsBodyRows.java | 65 +++++++++++++++++++ .../commands/ExamplePhysicsUtils.java | 50 ++------------ 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java index 11a2e963..b3c52ebb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java @@ -1,5 +1,8 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -11,6 +14,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.Objects; @@ -49,6 +53,27 @@ public static BodyRowDescriptor dynamicBody(@Nonnull UUID spaceUuid, persistenceMode); } + @Nonnull + public static BodyRowDescriptor dynamicBody(@Nonnull Ref spaceRef, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + return body(spaceRef, + bodyUuid, + bodyCenter, + shape, + PhysicsBodyType.DYNAMIC, + mass, + settings, + linearVelocity, + PhysicsBodyKind.BODY, + persistenceMode); + } + @Nonnull public static BodyRowDescriptor body(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @@ -100,6 +125,31 @@ public static BodyRowDescriptor body(@Nonnull UUID spaceUuid, collisionFilter(settings)); } + @Nonnull + public static BodyRowDescriptor body(@Nonnull Ref spaceRef, + @Nonnull UUID bodyUuid, + @Nonnull Vector3f bodyCenter, + @Nonnull PhysicsShapeSpec shape, + @Nonnull PhysicsBodyType bodyType, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + BodyRowDescriptor row = body(rowUuid(spaceRef), + bodyUuid, + bodyCenter, + shape, + bodyType, + mass, + settings, + linearVelocity, + kind, + persistenceMode); + row.body().setSpaceRef(spaceRef); + return row; + } + @Nonnull private static TargetComponent initialTarget(@Nonnull Vector3f bodyCenter, @Nullable Vector3f linearVelocity) { @@ -125,4 +175,19 @@ private static CollisionFilterComponent collisionFilter(@Nonnull RigidBodySpawnS ? settings.collisionMask() : PhysicsCollisionFilters.ALL); } + + @Nonnull + private static UUID rowUuid(@Nonnull Ref ref) { + Objects.requireNonNull(ref, "ref"); + Store store = ref.getStore(); + PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore row UUID"); + if (!ref.isValid()) { + throw new IllegalStateException("PhysicsStore row ref is not valid: " + ref); + } + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + if (uuid == null) { + throw new IllegalStateException("PhysicsStore row has no UUID component: " + ref); + } + return uuid.getUuid(); + } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 5977726a..886bdace 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -306,24 +306,6 @@ private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store 0.0f); } - @Nonnull - private static BodyRowDescriptor bodyRow(@Nonnull UUID spaceUuid, - @Nonnull UUID bodyUuid, - @Nonnull Vector3f bodyCenter, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - return PhysicsBodyRows.dynamicBody(spaceUuid, - bodyUuid, - bodyCenter, - shape, - mass, - settings, - linearVelocity, - PhysicsBodyPersistenceMode.PERSISTENT); - } - @Nonnull public static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @@ -332,37 +314,14 @@ public static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - BodyRowDescriptor row = bodyRow(physicsStoreRowUuid(spaceRef), - bodyUuid, - bodyCenter, - shape, - mass, - settings, - linearVelocity); - row.body().setSpaceRef(spaceRef); - return row; - } - - @Nonnull - private static BodyRowDescriptor bodyRow(@Nonnull UUID spaceUuid, - @Nonnull UUID bodyUuid, - @Nonnull Vector3f bodyCenter, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return PhysicsBodyRows.body(spaceUuid, + return PhysicsBodyRows.dynamicBody(spaceRef, bodyUuid, bodyCenter, shape, - PhysicsBodyType.DYNAMIC, mass, settings, linearVelocity, - kind, - persistenceMode); + PhysicsBodyPersistenceMode.PERSISTENT); } @Nonnull @@ -375,17 +334,16 @@ private static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - BodyRowDescriptor row = bodyRow(physicsStoreRowUuid(spaceRef), + return PhysicsBodyRows.body(spaceRef, bodyUuid, bodyCenter, shape, + PhysicsBodyType.DYNAMIC, mass, settings, linearVelocity, kind, persistenceMode); - row.body().setSpaceRef(spaceRef); - return row; } @Nonnull From 44ef6239515c6c3077295ee3502a64069bb16a0c Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:06:36 +0200 Subject: [PATCH 181/534] feat(physicsstore): add ref joint row builders Signed-off-by: Blovien --- .../plugin/physicsstore/PhysicsBodyRows.java | 18 +----- .../plugin/physicsstore/PhysicsJointRows.java | 62 +++++++++++++++++++ .../physicsstore/PhysicsStoreRowRefs.java | 40 ++++++++++++ .../examples/commands/GrabCommand.java | 21 +++---- .../examples/commands/JointsCommand.java | 21 +++---- .../commands/stress/StressJointsCommand.java | 19 +++--- 6 files changed, 127 insertions(+), 54 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointRows.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRowRefs.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java index b3c52ebb..f9f082ce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; @@ -14,7 +13,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.Objects; @@ -136,7 +134,7 @@ public static BodyRowDescriptor body(@Nonnull Ref spaceRef, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - BodyRowDescriptor row = body(rowUuid(spaceRef), + BodyRowDescriptor row = body(PhysicsStoreRowRefs.rowUuid(spaceRef), bodyUuid, bodyCenter, shape, @@ -176,18 +174,4 @@ private static CollisionFilterComponent collisionFilter(@Nonnull RigidBodySpawnS : PhysicsCollisionFilters.ALL); } - @Nonnull - private static UUID rowUuid(@Nonnull Ref ref) { - Objects.requireNonNull(ref, "ref"); - Store store = ref.getStore(); - PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore row UUID"); - if (!ref.isValid()) { - throw new IllegalStateException("PhysicsStore row ref is not valid: " + ref); - } - UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); - if (uuid == null) { - throw new IllegalStateException("PhysicsStore row has no UUID component: " + ref); - } - return uuid.getUuid(); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointRows.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointRows.java new file mode 100644 index 00000000..2413a8f8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointRows.java @@ -0,0 +1,62 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; + +/** + * Factories for direct PhysicsStore joint components. + */ +public final class PhysicsJointRows { + + private PhysicsJointRows() { + } + + @Nonnull + public static JointComponent joint(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid, + @Nonnull JointType type, + @Nonnull Vector3f anchorA, + @Nonnull Vector3f anchorB, + @Nonnull Vector3f axis) { + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); + joint.setBodyAUuid(Objects.requireNonNull(bodyAUuid, "bodyAUuid")); + joint.setBodyBUuid(Objects.requireNonNull(bodyBUuid, "bodyBUuid")); + joint.setType(Objects.requireNonNull(type, "type")); + joint.setAnchorA(anchorA); + joint.setAnchorB(anchorB); + joint.setAxis(axis); + joint.setEnabled(true); + return joint; + } + + @Nonnull + public static JointComponent joint(@Nonnull Ref spaceRef, + @Nonnull Ref bodyARef, + @Nonnull Ref bodyBRef, + @Nonnull JointType type, + @Nonnull Vector3f anchorA, + @Nonnull Vector3f anchorB, + @Nonnull Vector3f axis) { + PhysicsStoreRowRefs.requireSameStore(spaceRef, bodyARef, "bodyARef"); + PhysicsStoreRowRefs.requireSameStore(spaceRef, bodyBRef, "bodyBRef"); + JointComponent joint = joint(PhysicsStoreRowRefs.rowUuid(spaceRef), + PhysicsStoreRowRefs.rowUuid(bodyARef), + PhysicsStoreRowRefs.rowUuid(bodyBRef), + type, + anchorA, + anchorB, + axis); + joint.setSpaceRef(spaceRef); + joint.setBodyARef(bodyARef); + joint.setBodyBRef(bodyBRef); + return joint; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRowRefs.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRowRefs.java new file mode 100644 index 00000000..9d18cad1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRowRefs.java @@ -0,0 +1,40 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +final class PhysicsStoreRowRefs { + + private PhysicsStoreRowRefs() { + } + + @Nonnull + static UUID rowUuid(@Nonnull Ref ref) { + Ref checkedRef = Objects.requireNonNull(ref, "ref"); + Store store = checkedRef.getStore(); + PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore row UUID"); + if (!checkedRef.isValid()) { + throw new IllegalStateException("PhysicsStore row ref is not valid: " + checkedRef); + } + UuidComponent uuid = store.getComponent(checkedRef, UuidComponent.getComponentType()); + if (uuid == null) { + throw new IllegalStateException("PhysicsStore row has no UUID component: " + checkedRef); + } + return uuid.getUuid(); + } + + static void requireSameStore(@Nonnull Ref expectedStoreRef, + @Nonnull Ref ref, + @Nonnull String name) { + if (Objects.requireNonNull(ref, name).getStore() + != Objects.requireNonNull(expectedStoreRef, "expectedStoreRef").getStore()) { + throw new IllegalArgumentException("PhysicsStore row ref belongs to a different store: " + + name); + } + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index a631bad8..6a2fa3b0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -29,6 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; @@ -266,19 +267,13 @@ private static JointComponent controlJoint(@Nonnull Ref spaceRef, @Nonnull Ref anchorBodyRef, @Nonnull Ref bodyRef, @Nonnull Vector3f bodyLocalHit) { - JointComponent joint = new JointComponent(); - joint.setSpaceUuid(ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef)); - joint.setSpaceRef(spaceRef); - joint.setBodyAUuid(ExamplePhysicsUtils.physicsStoreRowUuid(anchorBodyRef)); - joint.setBodyARef(anchorBodyRef); - joint.setBodyBUuid(ExamplePhysicsUtils.physicsStoreRowUuid(bodyRef)); - joint.setBodyBRef(bodyRef); - joint.setType(JointType.POINT); - joint.setAnchorA(new Vector3f()); - joint.setAnchorB(bodyLocalHit); - joint.setAxis(new Vector3f()); - joint.setEnabled(true); - return joint; + return PhysicsJointRows.joint(spaceRef, + anchorBodyRef, + bodyRef, + JointType.POINT, + new Vector3f(), + bodyLocalHit, + new Vector3f()); } @Nullable diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index 6696ae78..16a62f38 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -13,6 +13,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -261,19 +262,13 @@ private static JointComponent joint(@Nonnull Ref spaceRef, @Nonnull Vector3f anchorA, @Nonnull Vector3f anchorB, @Nonnull Vector3f axis) { - JointComponent joint = new JointComponent(); - joint.setSpaceUuid(ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef)); - joint.setSpaceRef(spaceRef); - joint.setBodyAUuid(bodyA.bodyUuid()); - joint.setBodyARef(bodyA.bodyRef()); - joint.setBodyBUuid(bodyB.bodyUuid()); - joint.setBodyBRef(bodyB.bodyRef()); - joint.setType(type); - joint.setAnchorA(anchorA); - joint.setAnchorB(anchorB); - joint.setAxis(axis); - joint.setEnabled(true); - return joint; + return PhysicsJointRows.joint(spaceRef, + bodyA.bodyRef(), + bodyB.bodyRef(), + type, + anchorA, + anchorB, + axis); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index af6f4421..182e666b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -13,6 +13,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -202,17 +203,13 @@ private static JointComponent joint(@Nonnull Ref spaceRef, case 3 -> JointType.SLIDER; default -> JointType.SPRING; }; - JointComponent joint = new JointComponent(); - joint.setSpaceUuid(ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef)); - joint.setSpaceRef(spaceRef); - joint.setBodyAUuid(previous.bodyUuid()); - joint.setBodyARef(previous.bodyRef()); - joint.setBodyBUuid(current.bodyUuid()); - joint.setBodyBRef(current.bodyRef()); - joint.setType(type); - joint.setAnchorA(new Vector3f(HALF_SIZE, 0.0f, 0.0f)); - joint.setAnchorB(new Vector3f(-HALF_SIZE, 0.0f, 0.0f)); - joint.setEnabled(true); + JointComponent joint = PhysicsJointRows.joint(spaceRef, + previous.bodyRef(), + current.bodyRef(), + type, + new Vector3f(HALF_SIZE, 0.0f, 0.0f), + new Vector3f(-HALF_SIZE, 0.0f, 0.0f), + new Vector3f()); switch (type) { case FIXED, POINT -> { } From 95156019c66aff0d0a2a0650a357b65198171cf9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:11:11 +0200 Subject: [PATCH 182/534] refactor(examples): build grab anchor through body rows Signed-off-by: Blovien --- .../examples/commands/GrabCommand.java | 65 ++++--------------- 1 file changed, 13 insertions(+), 52 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 6a2fa3b0..db9d3482 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -15,10 +15,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; @@ -29,23 +27,19 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; @@ -215,51 +209,18 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, private static BodyRowDescriptor anchorBodyRow(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f hitPoint) { - UUID spaceUuid = ExamplePhysicsUtils.physicsStoreRowUuid(spaceRef); - BodyComponent body = new BodyComponent(spaceUuid, + return PhysicsBodyRows.body(spaceRef, + bodyUuid, + hitPoint, + PhysicsShapeSpec.sphere(0.08f), + PhysicsBodyType.KINEMATIC, + 1.0f, + RigidBodySpawnSettings.material(0.5f, 0.0f) + .withSensor(true) + .withCollisionFilter(PhysicsCollisionFilters.TERRAIN, 0), + null, PhysicsBodyKind.TEMPORARY, PhysicsBodyPersistenceMode.RUNTIME_ONLY); - body.setSpaceRef(spaceRef); - return BodyRowDescriptor.of(bodyUuid, - body, - new DynamicsComponent(PhysicsBodyType.KINEMATIC, - 1.0f, - 0.0f, - 0.0f, - false), - initialAnchorTarget(hitPoint), - bodyUuid, - new ColliderComponent(new Vector3f(), - new Quaternionf(), - true), - bodyUuid, - new ShapeComponent(ShapeType.SPHERE, - 0.0f, - 0.0f, - 0.0f, - 0.08f, - 0.0f, - PhysicsAxis.Y, - 0.0f, - ""), - bodyUuid, - new MaterialComponent(0.5f, 0.0f), - bodyUuid, - new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, 0)); - } - - @Nonnull - private static TargetComponent initialAnchorTarget(@Nonnull Vector3f hitPoint) { - TargetComponent target = new TargetComponent(); - target.setActive(false); - target.setPosition(hitPoint); - target.setRotation(new Quaternionf()); - target.setLinearVelocity(new Vector3f()); - target.setAngularVelocity(new Vector3f()); - target.setTransformEnabled(true); - target.setVelocityEnabled(false); - target.setActivate(true); - return target; } @Nonnull From 217835795448e3ded74c5403c94042cfc79578d3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:15:19 +0200 Subject: [PATCH 183/534] refactor(physicsstore): cache terrain space refs Signed-off-by: Blovien --- .../systems/TerrainMutationDrainSystem.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java index c23345dd..59030770 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java @@ -116,7 +116,7 @@ private static void applyTerrainMutation(@Nonnull Store store, } PhysicsStoreEntities.putTerrainColliderComponent(store, ref, - removedTerrainComponent(mutation)); + removedTerrainComponent(identity, mutation)); } removePayload(terrainPayloads, mutation.payloadResourceKey()); return; @@ -127,7 +127,7 @@ private static void applyTerrainMutation(@Nonnull Store store, return; } terrainPayloads.put(mutation.payloadResourceKey(), payload); - TerrainColliderComponent component = activeTerrainComponent(mutation); + TerrainColliderComponent component = activeTerrainComponent(identity, mutation); if (ref != null) { TerrainColliderComponent existing = store.getComponent(ref, TerrainColliderComponent.getComponentType()); @@ -159,28 +159,33 @@ private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResourc @Nonnull private static TerrainColliderComponent activeTerrainComponent( + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull TerrainColliderMutation mutation) { - return terrainComponent(mutation, mutation.payloadResourceKey(), true); + return terrainComponent(identity, mutation, mutation.payloadResourceKey(), true); } @Nonnull private static TerrainColliderComponent removedTerrainComponent( + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull TerrainColliderMutation mutation) { - return terrainComponent(mutation, mutation.payloadResourceKey(), false); + return terrainComponent(identity, mutation, mutation.payloadResourceKey(), false); } @Nonnull private static TerrainColliderComponent terrainComponent( + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull TerrainColliderMutation mutation, @Nullable String payloadResourceKey, boolean retained) { - return new TerrainColliderComponent(mutation.spaceUuid(), + TerrainColliderComponent component = new TerrainColliderComponent(mutation.spaceUuid(), mutation.sourceKey(), mutation.chunkX(), mutation.sectionY(), mutation.chunkZ(), payloadResourceKey != null ? payloadResourceKey : "", retained); + component.setSpaceRef(PhysicsStoreSystemSupport.refForUuid(identity, mutation.spaceUuid())); + return component; } private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, From 58884288d916929267560f937bac9f1a823b54d7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:20:09 +0200 Subject: [PATCH 184/534] refactor(examples): remove uuid row helpers Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 886bdace..9bef27c1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -28,7 +28,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; @@ -63,16 +62,6 @@ public static Store physicsStore(@Nonnull World world) { .getStore(); } - @Nullable - private static UUID resolvePhysicsStoreSpaceUuid(@Nonnull World world, - @Nonnull SpaceId spaceId) { - Store store = physicsStore(world); - PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); - return store - .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); - } - @Nullable public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world, @Nonnull SpaceId spaceId) { @@ -89,20 +78,6 @@ public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world return ref != null && ref.getStore() == store && ref.isValid() ? ref : null; } - @Nonnull - public static UUID physicsStoreRowUuid(@Nonnull Ref ref) { - Store store = ref.getStore(); - PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore row UUID"); - if (!ref.isValid()) { - throw new IllegalStateException("PhysicsStore row ref is not valid: " + ref); - } - UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); - if (uuid == null) { - throw new IllegalStateException("PhysicsStore row has no UUID component: " + ref); - } - return uuid.getUuid(); - } - @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyRowDescriptor row) { From 57684dc3ef3744acc4c8978888d8f964e2461ac2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:27:50 +0200 Subject: [PATCH 185/534] refactor(debug): read physics joint snapshots by ref Signed-off-by: Blovien --- .../debug/PhysicsStoreDebugQueries.java | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 2ad18a04..d1b63089 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -153,6 +153,8 @@ private static List joints(@Nonnull Store s if (spaceUuid == null) { return List.of(); } + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); double maxDistanceSquared = viewRadius * viewRadius; @@ -160,6 +162,7 @@ private static List joints(@Nonnull Store s BiConsumer, CommandBuffer> collector = (chunk, _) -> collectJointChunk(chunk, snapshots, + spaceRef, spaceUuid, viewerX, viewerY, @@ -173,6 +176,7 @@ private static List joints(@Nonnull Store s private static void collectJointChunk(@Nonnull ArchetypeChunk chunk, @Nonnull PhysicsSnapshotResource snapshots, + @Nullable Ref spaceRef, @Nonnull UUID spaceUuid, double viewerX, double viewerY, @@ -185,7 +189,7 @@ private static void collectJointChunk(@Nonnull ArchetypeChunk chun return; } JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); - if (joint == null || !spaceUuid.equals(joint.getSpaceUuid()) || !joint.isEnabled()) { + if (joint == null || !matchesSpace(joint, spaceRef, spaceUuid) || !joint.isEnabled()) { continue; } PhysicsDebugJointView view = toDebugJointView(joint, snapshots); @@ -206,8 +210,10 @@ private static void collectJointChunk(@Nonnull ArchetypeChunk chun @Nullable private static PhysicsDebugJointView toDebugJointView(@Nonnull JointComponent joint, @Nonnull PhysicsSnapshotResource snapshots) { - PhysicsStoreBodySnapshot bodyA = snapshots.getBody(joint.getBodyAUuid()); - PhysicsStoreBodySnapshot bodyB = snapshots.getBody(joint.getBodyBUuid()); + PhysicsStoreBodySnapshot bodyA = + bodySnapshot(snapshots, joint.getBodyARef(), joint.getBodyAUuid()); + PhysicsStoreBodySnapshot bodyB = + bodySnapshot(snapshots, joint.getBodyBRef(), joint.getBodyBUuid()); if (bodyA == null || bodyB == null) { return null; } @@ -242,6 +248,30 @@ private static PhysicsDebugJointView toDebugJointView(@Nonnull JointComponent jo worldAxis.z); } + private static boolean matchesSpace(@Nonnull JointComponent joint, + @Nullable Ref spaceRef, + @Nonnull UUID spaceUuid) { + Ref jointSpaceRef = joint.getSpaceRef(); + if (jointSpaceRef != null && spaceRef != null) { + return sameRef(jointSpaceRef, spaceRef); + } + return spaceUuid.equals(joint.getSpaceUuid()); + } + + @Nullable + private static PhysicsStoreBodySnapshot bodySnapshot( + @Nonnull PhysicsSnapshotResource snapshots, + @Nullable Ref bodyRef, + @Nonnull UUID bodyUuid) { + return bodyRef != null ? snapshots.getBody(bodyRef) : snapshots.getBody(bodyUuid); + } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); + } + @Nonnull private static Vector3f worldAnchor(@Nonnull PhysicsStoreBodySnapshot body, @Nonnull Vector3f localAnchor) { From 3a0525c3c028fe9b33c34e68dac56ef3d9d47c90 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:31:50 +0200 Subject: [PATCH 186/534] refactor(physicsstore): match topology rows by ref Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 79 +++++++++++++++---- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 111a3113..7a300cd3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -53,7 +53,8 @@ public static void destroyBody(@Nonnull Store store, PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); - List removals = collectRows(store, null, bodyUuid); + Ref bodyRef = identity.getByUuid(bodyUuid); + List removals = collectRows(store, null, null, bodyUuid, bodyRef); removeRuntimeRows(runtime, identity, removals); removeRows(store, removals); } @@ -66,7 +67,7 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); TopologyCounts removed = countBackendTopology(runtime); - List removals = collectRows(store, null, null); + List removals = collectRows(store, null, null, null, null); removeRuntimeRows(runtime, identity, removals); removeRows(store, removals); clearCopiedBodyState(store); @@ -92,7 +93,8 @@ public static void removeSpaceWithContents(@Nonnull Store store, PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); - List removals = collectRows(store, spaceUuid, null); + Ref spaceRef = identity.getByUuid(spaceUuid); + List removals = collectRows(store, spaceUuid, spaceRef, null, null); removeRuntimeRows(runtime, identity, removals); store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); @@ -105,7 +107,9 @@ public static int clearTerrainForSpace(@Nonnull Store store, PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore terrain rows"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); int removedBodies = 0; - List removals = collectTerrainRows(store, spaceUuid); + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + List removals = collectTerrainRows(store, spaceUuid, spaceRef); for (RowRemoval removal : removals) { removedBodies += removeRuntimeTerrain(runtime, removal); } @@ -201,7 +205,9 @@ private static TopologyCounts countBackendTopology(@Nonnull PhysicsRuntimeResour @Nonnull private static List collectRows(@Nonnull Store store, @Nullable UUID spaceUuid, - @Nullable UUID bodyUuid) { + @Nullable Ref spaceRef, + @Nullable UUID bodyUuid, + @Nullable Ref bodyRef) { ComponentType uuidType = UuidComponent.getComponentType(); ConcurrentLinkedQueue removals = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(uuidType, (index, chunk, _) -> { @@ -212,13 +218,13 @@ private static List collectRows(@Nonnull Store store, UUID rowUuid = uuid.getUuid(); Ref ref = chunk.getReferenceTo(index); JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); - if (matchesJoint(joint, spaceUuid, bodyUuid)) { + if (matchesJoint(joint, spaceUuid, spaceRef, bodyUuid, bodyRef)) { removals.add(new RowRemoval(ref, rowUuid, RowKind.JOINT, null)); return; } TerrainColliderComponent terrain = chunk.getComponent(index, TerrainColliderComponent.getComponentType()); - if (matchesTerrain(terrain, spaceUuid, bodyUuid)) { + if (matchesTerrain(terrain, spaceUuid, spaceRef, bodyUuid)) { removals.add(new RowRemoval(ref, rowUuid, RowKind.TERRAIN, @@ -226,7 +232,7 @@ private static List collectRows(@Nonnull Store store, return; } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); - if (matchesBody(body, rowUuid, spaceUuid, bodyUuid)) { + if (matchesBody(body, rowUuid, ref, spaceUuid, spaceRef, bodyUuid, bodyRef)) { removals.add(new RowRemoval(ref, rowUuid, RowKind.BODY, null)); } }); @@ -235,13 +241,17 @@ private static List collectRows(@Nonnull Store store, @Nonnull private static List collectTerrainRows(@Nonnull Store store, - @Nonnull UUID spaceUuid) { + @Nonnull UUID spaceUuid, + @Nullable Ref spaceRef) { ComponentType uuidType = UuidComponent.getComponentType(); ConcurrentLinkedQueue removals = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(uuidType, (index, chunk, _) -> { TerrainColliderComponent terrain = chunk.getComponent(index, TerrainColliderComponent.getComponentType()); - if (terrain == null || !spaceUuid.equals(terrain.getSpaceUuid())) { + if (terrain == null || !matchesSpace(terrain.getSpaceRef(), + terrain.getSpaceUuid(), + spaceRef, + spaceUuid)) { return; } UuidComponent uuid = chunk.getComponent(index, uuidType); @@ -258,39 +268,74 @@ private static List collectTerrainRows(@Nonnull Store private static boolean matchesJoint(@Nullable JointComponent joint, @Nullable UUID spaceUuid, - @Nullable UUID bodyUuid) { + @Nullable Ref spaceRef, + @Nullable UUID bodyUuid, + @Nullable Ref bodyRef) { if (joint == null) { return false; } - if (spaceUuid != null && !spaceUuid.equals(joint.getSpaceUuid())) { + if (!matchesSpace(joint.getSpaceRef(), joint.getSpaceUuid(), spaceRef, spaceUuid)) { return false; } return bodyUuid == null - || bodyUuid.equals(joint.getBodyAUuid()) - || bodyUuid.equals(joint.getBodyBUuid()); + || matchesEndpoint(joint.getBodyARef(), joint.getBodyAUuid(), bodyRef, bodyUuid) + || matchesEndpoint(joint.getBodyBRef(), joint.getBodyBUuid(), bodyRef, bodyUuid); } private static boolean matchesTerrain(@Nullable TerrainColliderComponent terrain, @Nullable UUID spaceUuid, + @Nullable Ref spaceRef, @Nullable UUID bodyUuid) { return bodyUuid == null && terrain != null - && (spaceUuid == null || spaceUuid.equals(terrain.getSpaceUuid())); + && matchesSpace(terrain.getSpaceRef(), terrain.getSpaceUuid(), spaceRef, spaceUuid); } private static boolean matchesBody(@Nullable BodyComponent body, @Nonnull UUID rowUuid, + @Nonnull Ref rowRef, @Nullable UUID spaceUuid, - @Nullable UUID bodyUuid) { + @Nullable Ref spaceRef, + @Nullable UUID bodyUuid, + @Nullable Ref bodyRef) { if (body == null) { return false; } - if (spaceUuid != null && !spaceUuid.equals(body.getSpaceUuid())) { + if (!matchesSpace(body.getSpaceRef(), body.getSpaceUuid(), spaceRef, spaceUuid)) { return false; } + if (bodyRef != null) { + return sameRef(rowRef, bodyRef); + } return bodyUuid == null || bodyUuid.equals(rowUuid); } + private static boolean matchesSpace(@Nullable Ref rowSpaceRef, + @Nonnull UUID rowSpaceUuid, + @Nullable Ref spaceRef, + @Nullable UUID spaceUuid) { + if (spaceRef != null && rowSpaceRef != null) { + return sameRef(rowSpaceRef, spaceRef); + } + return spaceUuid == null || spaceUuid.equals(rowSpaceUuid); + } + + private static boolean matchesEndpoint(@Nullable Ref endpointRef, + @Nonnull UUID endpointUuid, + @Nullable Ref bodyRef, + @Nonnull UUID bodyUuid) { + if (bodyRef != null && endpointRef != null) { + return sameRef(endpointRef, bodyRef); + } + return bodyUuid.equals(endpointUuid); + } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); + } + private static void removeRows(@Nonnull Store store, @Nonnull List removals) { PhysicsIdentityIndexResource identity = From a9cbb306e5f0a7e83d275da99f31c3c0e2a01799 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:39:33 +0200 Subject: [PATCH 187/534] refactor(visual): clear generated proxies by body ref Signed-off-by: Blovien --- .../PhysicsProjectionIndexResource.java | 16 ++++++++ .../PhysicsWorldRuntimeResource.java | 30 ++++++++++++++- .../visual/GeneratedProxyLifecycle.java | 14 +++++++ ...csDetachedVisualMaterializationSystem.java | 37 ++++++++++++++++--- 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index af6be4de..cb6026b0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -212,8 +212,16 @@ public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, } public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid) { + clearGeneratedVisualProxyForBodyRef(bodyUuid, null); + } + + public void clearGeneratedVisualProxyForBodyRef(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { synchronized (this) { generatedVisualProxies.remove(bodyUuid); + if (bodyRef != null) { + clearGeneratedVisualProxyRef(bodyRef); + } } } @@ -318,6 +326,14 @@ private void clearGeneratedVisualProxyRef(@Nonnull Ref bodyRef, } } + private void clearGeneratedVisualProxyRef(@Nonnull Ref bodyRef) { + int rowIndex = bodyRef.getIndex(); + GeneratedVisualProxyRef row = generatedVisualProxiesByRowIndex.get(rowIndex); + if (row != null && sameRef(row.bodyRef(), bodyRef)) { + generatedVisualProxiesByRowIndex.remove(rowIndex); + } + } + @Nonnull private Set> bodyAttachmentRefs(@Nonnull Ref bodyRef) { int rowIndex = bodyRef.getIndex(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 3bf9789a..7013d64a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1770,6 +1770,16 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey return bodyRegistry.getPublishedRegistrationView(bodyKey); } + @Nullable + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics body registration view") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(bodyRef); + } + return null; + } + @Nonnull public JointKey addJointOnOwner(@Nonnull JointKey jointKey, @Nonnull SpaceId spaceId, @@ -2136,13 +2146,25 @@ public void setGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref< public void clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { if (hasAttachedAuthoritativePhysicsStore()) { + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + "resolve generated visual proxy key"); authoritativeProjectionIndex("clear generated visual proxy") - .clearGeneratedVisualProxy(bodyKey.value()); + .clearGeneratedVisualProxyForBodyRef(bodyKey.value(), bodyRef); return; } visualRuntime.clearGeneratedVisualProxy(bodyKey); } + public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (hasAttachedAuthoritativePhysicsStore()) { + authoritativeProjectionIndex("clear generated visual proxy") + .clearGeneratedVisualProxyForBodyRef(bodyUuid, bodyRef); + return; + } + visualRuntime.clearGeneratedVisualProxy(RigidBodyKey.of(bodyUuid)); + } + public boolean clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref expectedProxy) { if (hasAttachedAuthoritativePhysicsStore()) { @@ -2188,6 +2210,12 @@ public boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, return visualRuntime.isGeneratedVisualProxy(bodyKey, proxy); } + public boolean isGeneratedVisualProxy(@Nonnull Ref bodyRef, + @Nonnull Ref proxy) { + return hasAttachedAuthoritativePhysicsStore() + && sameRef(getGeneratedVisualProxy(bodyRef), proxy); + } + public void setSyntheticVisualInterests(@Nonnull Collection interests) { visualRuntime.setSyntheticVisualInterests(interests); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index b2e23b67..1713998d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -8,6 +8,7 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; @@ -46,6 +47,19 @@ static void removeProxy(@Nonnull ComponentAccessor accessor, removeEntity(accessor, proxy); } + static void removeProxy(@Nonnull ComponentAccessor accessor, + @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nullable Ref proxy) { + if (proxy == null) { + resource.clearGeneratedVisualProxy(bodyUuid, bodyRef); + } else { + resource.clearGeneratedVisualProxy(bodyUuid, bodyRef, proxy); + } + removeEntity(accessor, proxy); + } + public static void clearMissingAttachment(@Nonnull Ref entityRef, @Nonnull BodyAttachmentComponent attachment, @Nonnull PhysicsWorldRuntimeResource resource, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 8e8908bb..ce7e1e26 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -20,6 +20,7 @@ import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; @@ -48,6 +49,7 @@ import java.util.Objects; import java.util.Queue; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.ToIntFunction; import java.util.WeakHashMap; @@ -623,30 +625,53 @@ private static void removeOrphanVisualFollowers(@Nonnull Store stor } var ref = archetypeChunk.getReferenceTo(index); - orphanProxies.add(new OrphanVisualProxy(RigidBodyKey.of(attachment.getBodyUuid()), + orphanProxies.add(new OrphanVisualProxy(attachment.getBodyUuid(), + attachment.getBodyRef(), + RigidBodyKey.of(attachment.getBodyUuid()), ref)); }); for (OrphanVisualProxy proxy : orphanProxies) { - if (!hasLiveVisualTarget(resource, proxy.bodyKey(), proxy.ref())) { - GeneratedProxyLifecycle.removeProxy(store, resource, proxy.bodyKey(), proxy.ref()); + if (!hasLiveVisualTarget(resource, + proxy.bodyRef(), + proxy.bodyKey(), + proxy.ref())) { + GeneratedProxyLifecycle.removeProxy(store, + resource, + proxy.bodyUuid(), + proxy.bodyRef(), + proxy.ref()); } } } private static boolean hasLiveVisualTarget(@Nonnull PhysicsWorldRuntimeResource resource, + @Nullable Ref bodyRef, @Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxyRef) { - PhysicsBodyRegistrationView registration = resource.getBodyRegistrationView(bodyKey); + PhysicsBodyRegistrationView registration = bodyRef != null + ? resource.getBodyRegistrationView(bodyRef) + : resource.getBodyRegistrationView(bodyKey); if (registration == null) { return resource.isBodyCreationPending(bodyKey) - && resource.isGeneratedVisualProxy(bodyKey, proxyRef); + && isGeneratedVisualProxy(resource, bodyRef, bodyKey, proxyRef); } return resource.getSpaceBinding(registration.spaceId()) != null - && resource.isGeneratedVisualProxy(bodyKey, proxyRef); + && isGeneratedVisualProxy(resource, bodyRef, bodyKey, proxyRef); + } + + private static boolean isGeneratedVisualProxy(@Nonnull PhysicsWorldRuntimeResource resource, + @Nullable Ref bodyRef, + @Nonnull RigidBodyKey bodyKey, + @Nonnull Ref proxyRef) { + return bodyRef != null + ? resource.isGeneratedVisualProxy(bodyRef, proxyRef) + : resource.isGeneratedVisualProxy(bodyKey, proxyRef); } private record OrphanVisualProxy( + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, @Nonnull RigidBodyKey bodyKey, @Nonnull Ref ref ) { From def9db0ff133cdcb79382e6980f9ce585d0cb0d1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:50:58 +0200 Subject: [PATCH 188/534] refactor(visual): carry physics body refs through proxies Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 76 +++++++++++++++++++ .../body/PhysicsBodySnapshotRefVisitor.java | 25 ++++++ ...csDetachedVisualMaterializationSystem.java | 66 ++++++++++++---- 3 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 7013d64a..9f6a17e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -35,6 +35,7 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; +import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotRefVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState; @@ -821,6 +822,42 @@ private static int forEachIndexedAuthoritativeBodySnapshotNear( return candidates; } + private static int forEachIndexedAuthoritativeBodySnapshotNearWithRefs( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3f center, + float radius, + @Nonnull PhysicsBodySnapshotRefVisitor visitor) { + UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); + if (spaceUuid == null || radius < 0.0f || Float.isNaN(radius)) { + return 0; + } + float radiusSquared = radius * radius; + int candidates = 0; + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); + for (PhysicsStoreBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { + if (!spaceUuid.equals(body.spaceUuid())) { + continue; + } + PhysicsBodySnapshotEntry entry = + authoritativeSnapshotEntry(store, registrations, body); + if (entry == null) { + continue; + } + candidates++; + if (withinRadius(entry.snapshot(), center, radiusSquared)) { + visitor.accept(entry.bodyKey(), + validSnapshotBodyRef(store, body), + entry.snapshot(), + entry.spaceId(), + entry.kind(), + entry.persistenceMode()); + } + } + return candidates; + } + @Nullable private static UUID authoritativeSpaceUuid(@Nonnull Store store, @Nonnull SpaceId spaceId) { @@ -851,6 +888,15 @@ private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( registration.persistenceMode()); } + @Nullable + private static Ref validSnapshotBodyRef(@Nonnull Store store, + @Nonnull PhysicsStoreBodySnapshot body) { + Ref bodyRef = body.bodyRef(); + return bodyRef != null && bodyRef.getStore() == store && bodyRef.isValid() + ? bodyRef + : null; + } + private static boolean withinRadius(@Nonnull PhysicsBodySnapshot snapshot, @Nonnull Vector3f center, float radiusSquared) { @@ -1436,6 +1482,25 @@ public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, return lifecycleState.forEachIndexedBodySnapshotNear(spaceId, center, radius, visitor); } + public int forEachIndexedBodySnapshotNearWithRefs(@Nonnull SpaceId spaceId, + @Nonnull Vector3f center, + float radius, + @Nonnull PhysicsBodySnapshotRefVisitor visitor) { + if (isAuthoritativePhysicsStoreActive()) { + return forEachIndexedAuthoritativeBodySnapshotNearWithRefs( + authoritativePhysicsStore("iterate nearby copied physics body snapshots"), + spaceId, + center, + radius, + visitor); + } + return lifecycleState.forEachIndexedBodySnapshotNear(spaceId, + center, + radius, + (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> + visitor.accept(bodyKey, null, snapshot, bodySpaceId, kind, persistenceMode)); + } + @Override public void removeSpace(@Nonnull SpaceId spaceId) { removeSpace(spaceId, ""); @@ -2144,6 +2209,17 @@ public void setGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref< visualRuntime.setGeneratedVisualProxy(bodyKey, proxy); } + public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref proxy) { + if (hasAttachedAuthoritativePhysicsStore()) { + authoritativeProjectionIndex("set generated visual proxy") + .setGeneratedVisualProxy(bodyUuid, bodyRef, proxy); + return; + } + visualRuntime.setGeneratedVisualProxy(RigidBodyKey.of(bodyUuid), proxy); + } + public void clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { if (hasAttachedAuthoritativePhysicsStore()) { Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java new file mode 100644 index 00000000..d8c7a6d8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java @@ -0,0 +1,25 @@ +package dev.hytalemodding.impulse.core.internal.resources.body; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Internal snapshot visitor for authoritative paths that can use live PhysicsStore row refs. + */ +@FunctionalInterface +public interface PhysicsBodySnapshotRefVisitor { + + void accept(@Nonnull RigidBodyKey bodyKey, + @Nullable Ref bodyRef, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode); +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index ce7e1e26..865d30c1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -359,13 +359,16 @@ private int spawnCachedMaterializationTargets(@Nonnull MaterializationState stat state.cachedMaterializationTargets.remove(index); Ref proxy = spawnProxy(store, candidate.bodyKey(), + candidate.bodyRef(), candidate.snapshot(), candidate.registration(), candidate.settings()); if (proxy == null) { continue; } - resource.setGeneratedVisualProxy(candidate.bodyKey(), proxy); + resource.setGeneratedVisualProxy(candidate.bodyKey().value(), + candidate.bodyRef(), + proxy); spawned++; materialized++; if (collector != null) { @@ -385,16 +388,18 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, @Nonnull GameplayAttachmentSnapshot gameplayAttachments, @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - if (resource.getGeneratedVisualProxy(target.bodyKey()) != null) { + if (generatedVisualProxy(resource, target.bodyRef(), target.bodyKey()) != null) { return null; } - PhysicsBodyRegistrationView registration = resource.getBodyRegistrationView(target.bodyKey()); + PhysicsBodyRegistrationView registration = bodyRegistration(resource, + target.bodyRef(), + target.bodyKey()); if (registration == null || registration.kind() != PhysicsBodyKind.BODY || !sameSpaceId(registration.spaceId(), target.spaceId()) || gameplayAttachments.hasKnownGameplayAttachment( - resource.hasBodyAttachments(registration.bodyKey()), + hasBodyAttachments(resource, target.bodyRef(), registration.bodyKey()), registration.bodyKey())) { return null; } @@ -429,12 +434,41 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( return null; } return new MaterializationCandidate(target.bodyKey(), + target.bodyRef(), snapshot, registration, settings, currentPolicy.priorityDistanceSquared()); } + @Nullable + private static Ref generatedVisualProxy( + @Nonnull PhysicsWorldRuntimeResource resource, + @Nullable Ref bodyRef, + @Nonnull RigidBodyKey bodyKey) { + return bodyRef != null + ? resource.getGeneratedVisualProxy(bodyRef) + : resource.getGeneratedVisualProxy(bodyKey); + } + + @Nullable + private static PhysicsBodyRegistrationView bodyRegistration( + @Nonnull PhysicsWorldRuntimeResource resource, + @Nullable Ref bodyRef, + @Nonnull RigidBodyKey bodyKey) { + return bodyRef != null + ? resource.getBodyRegistrationView(bodyRef) + : resource.getBodyRegistrationView(bodyKey); + } + + private static boolean hasBodyAttachments(@Nonnull PhysicsWorldRuntimeResource resource, + @Nullable Ref bodyRef, + @Nonnull RigidBodyKey bodyKey) { + return bodyRef != null + ? resource.hasBodyAttachments(bodyRef) + : resource.hasBodyAttachments(bodyKey); + } + private static int refreshCooldown(int intervalTicks) { return Math.max(0, intervalTicks - 1); } @@ -569,17 +603,18 @@ private static void collectMaterializationCandidates(@Nonnull Store if (collector != null) { collector.incrementNearQueries(); } - int nearCandidates = resource.forEachIndexedBodySnapshotNear(space.spaceId(), + int nearCandidates = resource.forEachIndexedBodySnapshotNearWithRefs(space.spaceId(), interest.position(), settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), - (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> { - if (!seenBodies.add(bodyKey) || resource.getGeneratedVisualProxy(bodyKey) != null) { + (bodyKey, bodyRef, snapshot, bodySpaceId, kind, persistenceMode) -> { + if (!seenBodies.add(bodyKey) + || generatedVisualProxy(resource, bodyRef, bodyKey) != null) { return; } if (kind != PhysicsBodyKind.BODY || !bodySpaceId.equals(space.spaceId()) || gameplayAttachments.hasKnownGameplayAttachment( - resource.hasBodyAttachments(bodyKey), + hasBodyAttachments(resource, bodyRef, bodyKey), bodyKey)) { return; } @@ -599,6 +634,7 @@ private static void collectMaterializationCandidates(@Nonnull Store collector); if (materializeInterest.shouldMaterialize()) { candidates.add(new CachedMaterializationTarget(bodyKey, + bodyRef, bodySpaceId, materializeInterest.priorityDistanceSquared())); } @@ -817,6 +853,7 @@ private static boolean sameRef(@Nullable Ref first, @Nullable private static Ref spawnProxy(@Nonnull Store store, @Nonnull RigidBodyKey bodyKey, + @Nullable Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull PhysicsBodyRegistrationView registration, @Nonnull PhysicsSpaceSettings settings) { @@ -845,15 +882,17 @@ private static Ref spawnProxy(@Nonnull Store store, holder.removeComponent(Velocity.getComponentType()); holder.addComponent(store.getRegistry().getNonSerializedComponentType(), NonSerialized.get()); holder.addComponent(GeneratedVisualProxyComponent.getComponentType(), new GeneratedVisualProxyComponent()); - holder.addComponent(BodyAttachmentComponent.getComponentType(), - BodyAttachmentComponent.generatedProxy(bodyKey.value(), - new Vector3f(), - new Quaternionf(), - Float.NaN)); + BodyAttachmentComponent attachment = BodyAttachmentComponent.generatedProxy(bodyKey.value(), + new Vector3f(), + new Quaternionf(), + Float.NaN); + attachment.setBodyRef(bodyRef); + holder.addComponent(BodyAttachmentComponent.getComponentType(), attachment); return store.addEntity(holder, AddReason.SPAWN); } private record MaterializationCandidate(@Nonnull RigidBodyKey bodyKey, + @Nullable Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull PhysicsBodyRegistrationView registration, @Nonnull PhysicsSpaceSettings settings, @@ -861,6 +900,7 @@ private record MaterializationCandidate(@Nonnull RigidBodyKey bodyKey, } private record CachedMaterializationTarget(@Nonnull RigidBodyKey bodyKey, + @Nullable Ref bodyRef, @Nonnull SpaceId spaceId, float distanceSquared) { } From 1f5493652c53c50edf85316cefe124268b6e936c Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 11:58:00 +0200 Subject: [PATCH 189/534] refactor(visual): index gameplay attachments by body ref Signed-off-by: Blovien --- .../visual/GameplayAttachmentSnapshot.java | 89 ++++++++++++++++--- ...csDetachedVisualMaterializationSystem.java | 9 +- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java index d1ca67ae..2ddf1fcf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java @@ -1,11 +1,14 @@ package dev.hytalemodding.impulse.core.internal.systems.visual; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Queue; import java.util.Set; @@ -16,50 +19,76 @@ final class GameplayAttachmentSnapshot { @Nonnull - private final BodyKeySource source; + private final AttachmentSource source; @Nullable - private Set bodyKeys; + private AttachmentBodies bodies; - private GameplayAttachmentSnapshot(@Nonnull BodyKeySource source) { + private GameplayAttachmentSnapshot(@Nonnull AttachmentSource source) { this.source = source; } @Nonnull static GameplayAttachmentSnapshot forStore(@Nonnull Store store) { - return fromSource(() -> collectGameplayAttachmentBodyKeys(store)); + return fromAttachmentSource(() -> collectGameplayAttachments(store)); } @Nonnull static GameplayAttachmentSnapshot fromSource(@Nonnull BodyKeySource source) { + return fromAttachmentSource(() -> new AttachmentBodies(source.bodyKeys(), + new Int2ObjectOpenHashMap<>())); + } + + @Nonnull + private static GameplayAttachmentSnapshot fromAttachmentSource(@Nonnull AttachmentSource source) { return new GameplayAttachmentSnapshot(source); } boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, + @Nonnull RigidBodyKey bodyKey) { + return hasKnownGameplayAttachment(runtimeIndexHasAttachment, null, bodyKey); + } + + boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, + @Nullable Ref bodyRef, @Nonnull RigidBodyKey bodyKey) { if (runtimeIndexHasAttachment) { return true; } - return hasGameplayAttachment(bodyKey); + return hasGameplayAttachment(bodyRef, bodyKey); } boolean hasGameplayAttachment(@Nonnull RigidBodyKey bodyKey) { - return bodyKeys().contains(bodyKey); + return hasGameplayAttachment(null, bodyKey); + } + + boolean hasGameplayAttachment(@Nullable Ref bodyRef, + @Nonnull RigidBodyKey bodyKey) { + return hasGameplayAttachment(bodyRef) || bodies().bodyKeys().contains(bodyKey); + } + + private boolean hasGameplayAttachment(@Nullable Ref bodyRef) { + if (bodyRef == null || !bodyRef.isValid()) { + return false; + } + Ref indexed = bodies().bodyRefsByRowIndex().get(bodyRef.getIndex()); + return sameRef(indexed, bodyRef); } @Nonnull - private Set bodyKeys() { - if (bodyKeys == null) { - bodyKeys = source.bodyKeys(); + private AttachmentBodies bodies() { + if (bodies == null) { + bodies = source.attachments(); } - return bodyKeys; + return bodies; } @Nonnull - private static Set collectGameplayAttachmentBodyKeys( + private static AttachmentBodies collectGameplayAttachments( @Nonnull Store store) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); Queue bodyKeys = new ConcurrentLinkedQueue<>(); + Queue> bodyRefs = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, _) -> { BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, @@ -67,11 +96,34 @@ private static Set collectGameplayAttachmentBodyKeys( if (attachment != null && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { bodyKeys.add(RigidBodyKey.of(attachment.getBodyUuid())); + Ref bodyRef = attachment.getBodyRef(); + if (bodyRef != null && bodyRef.isValid()) { + bodyRefs.add(bodyRef); + } } }); Set uniqueBodyKeys = new ObjectOpenHashSet<>(); uniqueBodyKeys.addAll(bodyKeys); - return uniqueBodyKeys; + Int2ObjectOpenHashMap> bodyRefsByRowIndex = + new Int2ObjectOpenHashMap<>(); + for (Ref bodyRef : bodyRefs) { + int rowIndex = bodyRef.getIndex(); + Ref existing = bodyRefsByRowIndex.get(rowIndex); + if (existing == null || sameRef(existing, bodyRef)) { + bodyRefsByRowIndex.put(rowIndex, bodyRef); + } + } + return new AttachmentBodies(uniqueBodyKeys, bodyRefsByRowIndex); + } + + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); } @FunctionalInterface @@ -80,4 +132,17 @@ interface BodyKeySource { @Nonnull Set bodyKeys(); } + + @FunctionalInterface + private interface AttachmentSource { + + @Nonnull + AttachmentBodies attachments(); + } + + private record AttachmentBodies( + @Nonnull Set bodyKeys, + @Nonnull Int2ObjectOpenHashMap> bodyRefsByRowIndex + ) { + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 865d30c1..357dfab1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -400,6 +400,7 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( || !sameSpaceId(registration.spaceId(), target.spaceId()) || gameplayAttachments.hasKnownGameplayAttachment( hasBodyAttachments(resource, target.bodyRef(), registration.bodyKey()), + target.bodyRef(), registration.bodyKey())) { return null; } @@ -615,6 +616,7 @@ private static void collectMaterializationCandidates(@Nonnull Store || !bodySpaceId.equals(space.spaceId()) || gameplayAttachments.hasKnownGameplayAttachment( hasBodyAttachments(resource, bodyRef, bodyKey), + bodyRef, bodyKey)) { return; } @@ -720,6 +722,7 @@ private static boolean hasGameplayAttachment(@Nonnull Store store, @Nonnull GameplayAttachmentSnapshot gameplayAttachments) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); + Ref bodyRef = null; for (Ref attachmentRef : resource.getBodyAttachments(bodyKey)) { if (sameRef(attachmentRef, proxy)) { continue; @@ -730,7 +733,11 @@ private static boolean hasGameplayAttachment(@Nonnull Store store, return true; } } - return gameplayAttachments.hasGameplayAttachment(bodyKey); + BodyAttachmentComponent proxyAttachment = store.getComponent(proxy, attachmentType); + if (proxyAttachment != null) { + bodyRef = proxyAttachment.getBodyRef(); + } + return gameplayAttachments.hasGameplayAttachment(bodyRef, bodyKey); } @Nullable From 799c1604972e6c8b75d50e37c2206afe92b5f398 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 12:06:09 +0200 Subject: [PATCH 190/534] refactor(visual): clean materialized proxies by body ref Signed-off-by: Blovien --- ...csDetachedVisualMaterializationSystem.java | 78 +++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 357dfab1..c4110387 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -42,6 +42,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -522,28 +523,38 @@ private static int processMaterializedProxies(@Nonnull Store store, if (collector != null) { collector.incrementVisibilityChecks(); } - PhysicsBodyRegistrationView registration = resource.getBodyRegistrationView(bodyKey); + Ref proxy = resource.getGeneratedVisualProxy(bodyKey); + BodyAttachmentComponent proxyAttachment = + expectedProxyAttachment(store, proxy, bodyKey); + Ref bodyRef = proxyAttachment != null + ? validBodyRef(proxyAttachment.getBodyRef()) + : null; + PhysicsBodyRegistrationView registration = bodyRegistration(resource, bodyRef, bodyKey); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { if (registration == null && resource.isBodyCreationPending(bodyKey)) { count++; continue; } - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey); + removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } continue; } - Ref proxy = resource.getGeneratedVisualProxy(bodyKey); - if (proxy == null || !isExpectedProxy(store, proxy, bodyKey)) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey); + if (proxy == null || proxyAttachment == null) { + removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } continue; } - if (hasGameplayAttachment(store, resource, bodyKey, proxy, gameplayAttachments)) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey); + if (hasGameplayAttachment(store, + resource, + bodyKey, + bodyRef, + proxy, + gameplayAttachments)) { + removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -552,7 +563,7 @@ private static int processMaterializedProxies(@Nonnull Store store, PhysicsSpaceSettings settings = resolveSettings(resource, registration); if (settings == null || !settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled()) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey); + removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -561,7 +572,7 @@ private static int processMaterializedProxies(@Nonnull Store store, PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyKey); if (snapshot == null) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey); + removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -569,7 +580,7 @@ private static int processMaterializedProxies(@Nonnull Store store, } if (!isBodyChunkLoaded(store, snapshot) || shouldDematerialize(snapshot, settings, interests)) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey); + removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -718,12 +729,15 @@ private record OrphanVisualProxy( private static boolean hasGameplayAttachment(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull RigidBodyKey bodyKey, + @Nullable Ref bodyRef, @Nonnull Ref proxy, @Nonnull GameplayAttachmentSnapshot gameplayAttachments) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); - Ref bodyRef = null; - for (Ref attachmentRef : resource.getBodyAttachments(bodyKey)) { + Collection> attachments = bodyRef != null + ? resource.getBodyAttachments(bodyRef) + : resource.getBodyAttachments(bodyKey); + for (Ref attachmentRef : attachments) { if (sameRef(attachmentRef, proxy)) { continue; } @@ -733,10 +747,6 @@ private static boolean hasGameplayAttachment(@Nonnull Store store, return true; } } - BodyAttachmentComponent proxyAttachment = store.getComponent(proxy, attachmentType); - if (proxyAttachment != null) { - bodyRef = proxyAttachment.getBodyRef(); - } return gameplayAttachments.hasGameplayAttachment(bodyRef, bodyKey); } @@ -830,17 +840,39 @@ private static boolean isBodyChunkLoaded(@Nonnull Store store, return worldChunk != null; } - private static boolean isExpectedProxy(@Nonnull Store store, - @Nonnull Ref proxy, + @Nullable + private static BodyAttachmentComponent expectedProxyAttachment( + @Nonnull Store store, + @Nullable Ref proxy, @Nonnull RigidBodyKey bodyKey) { - if (!proxy.isValid()) { - return false; + if (proxy == null || !proxy.isValid()) { + return null; } BodyAttachmentComponent attachment = store.getComponent(proxy, BodyAttachmentComponent.getComponentType()); - return attachment != null - && attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY - && attachment.getBodyUuid().equals(bodyKey.value()); + if (attachment == null + || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY + || !attachment.getBodyUuid().equals(bodyKey.value())) { + return null; + } + return attachment; + } + + @Nullable + private static Ref validBodyRef(@Nullable Ref bodyRef) { + return bodyRef != null && bodyRef.isValid() ? bodyRef : null; + } + + private static void removeGeneratedProxy(@Nonnull Store store, + @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull RigidBodyKey bodyKey, + @Nullable Ref bodyRef, + @Nullable Ref proxy) { + if (bodyRef != null) { + GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey.value(), bodyRef, proxy); + return; + } + GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey, proxy); } private static boolean sameSpaceId(@Nullable SpaceId first, @Nullable SpaceId second) { From 1013ff3ef596807ce2030b5582cc2a0bbd22d09f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 12:20:03 +0200 Subject: [PATCH 191/534] refactor(visual): expose generated proxy views by body ref Signed-off-by: Blovien --- .../resources/GeneratedVisualProxyView.java | 16 +++++++ .../PhysicsProjectionIndexResource.java | 48 +++++++++++++++++-- .../PhysicsWorldRuntimeResource.java | 16 +++++++ ...csDetachedVisualMaterializationSystem.java | 20 ++++---- 4 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java new file mode 100644 index 00000000..fe2a53a8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java @@ -0,0 +1,16 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Copied generated-proxy index row with durable body identity plus optional live body ref. + */ +public record GeneratedVisualProxyView(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref proxy) { +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index cb6026b0..7c14740e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -164,6 +164,45 @@ public Collection getGeneratedVisualProxyBodyKeys() { return bodyKeys; } + @Nonnull + public Collection getGeneratedVisualProxyViews() { + List views = new ArrayList<>(); + Map> bodyRefsByUuid = new Object2ObjectOpenHashMap<>(); + synchronized (this) { + List staleRowIndexes = new ArrayList<>(); + for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { + GeneratedVisualProxyRef row = entry.getValue(); + Ref proxy = row.proxy(); + Ref uuidProxy = generatedVisualProxies.get(row.bodyUuid()); + if (row.bodyRef() == null + || !row.bodyRef().isValid() + || proxy == null + || !proxy.isValid() + || !sameRef(proxy, uuidProxy)) { + staleRowIndexes.add(entry.getIntKey()); + continue; + } + bodyRefsByUuid.put(row.bodyUuid(), row.bodyRef()); + } + for (Integer rowIndex : staleRowIndexes) { + generatedVisualProxiesByRowIndex.remove(rowIndex.intValue()); + } + for (Iterator>> iterator = + generatedVisualProxies.entrySet().iterator(); iterator.hasNext();) { + Map.Entry> entry = iterator.next(); + Ref proxy = entry.getValue(); + if (proxy != null && proxy.isValid()) { + views.add(new GeneratedVisualProxyView(entry.getKey(), + bodyRefsByUuid.get(entry.getKey()), + proxy)); + } else { + iterator.remove(); + } + } + } + return views; + } + public int generatedVisualProxyCount() { int count = 0; synchronized (this) { @@ -206,7 +245,7 @@ public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, generatedVisualProxies.put(bodyUuid, proxy); if (bodyRef != null) { generatedVisualProxiesByRowIndex.put(bodyRef.getIndex(), - new GeneratedVisualProxyRef(bodyRef, proxy)); + new GeneratedVisualProxyRef(bodyUuid, bodyRef, proxy)); } } } @@ -264,7 +303,7 @@ public void updateAttachmentBodyRef(@Nonnull UUID bodyUuid, .add(attachment); if (generatedProxy) { generatedVisualProxiesByRowIndex.put(newBodyRef.getIndex(), - new GeneratedVisualProxyRef(newBodyRef, attachment)); + new GeneratedVisualProxyRef(bodyUuid, newBodyRef, attachment)); } } } @@ -288,7 +327,7 @@ public PhysicsProjectionIndexResource clone() { for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { GeneratedVisualProxyRef proxy = entry.getValue(); copy.generatedVisualProxiesByRowIndex.put(entry.getIntKey(), - new GeneratedVisualProxyRef(proxy.bodyRef(), proxy.proxy())); + new GeneratedVisualProxyRef(proxy.bodyUuid(), proxy.bodyRef(), proxy.proxy())); } } return copy; @@ -445,7 +484,8 @@ private record BodyAttachmentRefs(@Nonnull Ref bodyRef, @Nonnull Set> attachments) { } - private record GeneratedVisualProxyRef(@Nonnull Ref bodyRef, + private record GeneratedVisualProxyRef(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, @Nonnull Ref proxy) { } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 9f6a17e8..a1bea304 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2189,6 +2189,22 @@ public Collection getGeneratedVisualProxyBodyKeys() { return visualRuntime.getGeneratedVisualProxyBodyKeys(); } + @Nonnull + public Collection getGeneratedVisualProxyViews() { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativeProjectionIndex("list generated visual proxies") + .getGeneratedVisualProxyViews(); + } + List views = new ArrayList<>(); + for (RigidBodyKey bodyKey : visualRuntime.getGeneratedVisualProxyBodyKeys()) { + Ref proxy = visualRuntime.getGeneratedVisualProxy(bodyKey); + if (proxy != null) { + views.add(new GeneratedVisualProxyView(bodyKey.value(), null, proxy)); + } + } + return views; + } + public int getGeneratedVisualProxyCount() { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativeProjectionIndex("count generated visual proxies") diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index c4110387..cd6104c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -28,6 +28,7 @@ import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; +import dev.hytalemodding.impulse.core.internal.resources.GeneratedVisualProxyView; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; @@ -519,16 +520,19 @@ private static int processMaterializedProxies(@Nonnull Store store, @Nonnull GameplayAttachmentSnapshot gameplayAttachments, @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { int count = 0; - for (RigidBodyKey bodyKey : resource.getGeneratedVisualProxyBodyKeys()) { + for (GeneratedVisualProxyView generatedProxy : resource.getGeneratedVisualProxyViews()) { if (collector != null) { collector.incrementVisibilityChecks(); } - Ref proxy = resource.getGeneratedVisualProxy(bodyKey); + UUID bodyUuid = generatedProxy.bodyUuid(); + RigidBodyKey bodyKey = RigidBodyKey.of(bodyUuid); + Ref proxy = generatedProxy.proxy(); BodyAttachmentComponent proxyAttachment = - expectedProxyAttachment(store, proxy, bodyKey); - Ref bodyRef = proxyAttachment != null - ? validBodyRef(proxyAttachment.getBodyRef()) - : null; + expectedProxyAttachment(store, proxy, bodyUuid); + Ref bodyRef = validBodyRef(generatedProxy.bodyRef()); + if (bodyRef == null && proxyAttachment != null) { + bodyRef = validBodyRef(proxyAttachment.getBodyRef()); + } PhysicsBodyRegistrationView registration = bodyRegistration(resource, bodyRef, bodyKey); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { if (registration == null && resource.isBodyCreationPending(bodyKey)) { @@ -844,7 +848,7 @@ private static boolean isBodyChunkLoaded(@Nonnull Store store, private static BodyAttachmentComponent expectedProxyAttachment( @Nonnull Store store, @Nullable Ref proxy, - @Nonnull RigidBodyKey bodyKey) { + @Nonnull UUID bodyUuid) { if (proxy == null || !proxy.isValid()) { return null; } @@ -852,7 +856,7 @@ private static BodyAttachmentComponent expectedProxyAttachment( store.getComponent(proxy, BodyAttachmentComponent.getComponentType()); if (attachment == null || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY - || !attachment.getBodyUuid().equals(bodyKey.value())) { + || !attachment.getBodyUuid().equals(bodyUuid)) { return null; } return attachment; From 681e6844d9e80c4b66e8c709a04a61c8a9fbb7d8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 12:24:21 +0200 Subject: [PATCH 192/534] refactor(visual): remove body keys from orphan proxy tracking Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 28 +++++++++++++++++++ ...csDetachedVisualMaterializationSystem.java | 20 ++++++------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index a1bea304..e78d7cfb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1835,6 +1835,16 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey return bodyRegistry.getPublishedRegistrationView(bodyKey); } + @Nullable + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("read physics body registration view") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(bodyUuid); + } + return getBodyRegistrationView(RigidBodyKey.of(bodyUuid)); + } + @Nullable public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { @@ -1999,6 +2009,10 @@ public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { return bodyRuntime.isBodyCreationPending(bodyKey); } + public boolean isBodyCreationPending(@Nonnull UUID bodyUuid) { + return isBodyCreationPending(RigidBodyKey.of(bodyUuid)); + } + public boolean hasPublishedOrPendingBodyRegistration(@Nonnull RigidBodyKey bodyKey) { return getBodyRegistrationView(bodyKey) != null || isBodyCreationPending(bodyKey); @@ -2302,6 +2316,20 @@ public boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, return visualRuntime.isGeneratedVisualProxy(bodyKey, proxy); } + public boolean isGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref proxy) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("check generated visual proxy"); + Ref registered = bodyRef != null + ? projection.getGeneratedVisualProxy(bodyRef) + : projection.getGeneratedVisualProxy(bodyUuid); + return sameRef(registered, proxy); + } + return visualRuntime.isGeneratedVisualProxy(RigidBodyKey.of(bodyUuid), proxy); + } + public boolean isGeneratedVisualProxy(@Nonnull Ref bodyRef, @Nonnull Ref proxy) { return hasAttachedAuthoritativePhysicsStore() diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index cd6104c8..ab5c9797 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -680,14 +680,13 @@ private static void removeOrphanVisualFollowers(@Nonnull Store stor var ref = archetypeChunk.getReferenceTo(index); orphanProxies.add(new OrphanVisualProxy(attachment.getBodyUuid(), attachment.getBodyRef(), - RigidBodyKey.of(attachment.getBodyUuid()), ref)); }); for (OrphanVisualProxy proxy : orphanProxies) { if (!hasLiveVisualTarget(resource, + proxy.bodyUuid(), proxy.bodyRef(), - proxy.bodyKey(), proxy.ref())) { GeneratedProxyLifecycle.removeProxy(store, resource, @@ -699,33 +698,30 @@ private static void removeOrphanVisualFollowers(@Nonnull Store stor } private static boolean hasLiveVisualTarget(@Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxyRef) { PhysicsBodyRegistrationView registration = bodyRef != null ? resource.getBodyRegistrationView(bodyRef) - : resource.getBodyRegistrationView(bodyKey); + : resource.getBodyRegistrationView(bodyUuid); if (registration == null) { - return resource.isBodyCreationPending(bodyKey) - && isGeneratedVisualProxy(resource, bodyRef, bodyKey, proxyRef); + return resource.isBodyCreationPending(bodyUuid) + && isGeneratedVisualProxy(resource, bodyUuid, bodyRef, proxyRef); } return resource.getSpaceBinding(registration.spaceId()) != null - && isGeneratedVisualProxy(resource, bodyRef, bodyKey, proxyRef); + && isGeneratedVisualProxy(resource, bodyUuid, bodyRef, proxyRef); } private static boolean isGeneratedVisualProxy(@Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxyRef) { - return bodyRef != null - ? resource.isGeneratedVisualProxy(bodyRef, proxyRef) - : resource.isGeneratedVisualProxy(bodyKey, proxyRef); + return resource.isGeneratedVisualProxy(bodyUuid, bodyRef, proxyRef); } private record OrphanVisualProxy( @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey, @Nonnull Ref ref ) { } From 7760a3208e0295ba36252a4fa4a821375d5fcf0d Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 12:35:18 +0200 Subject: [PATCH 193/534] refactor(visual): cache materialization targets by uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 52 +++++++++ .../visual/GameplayAttachmentSnapshot.java | 41 +++++-- ...csDetachedVisualMaterializationSystem.java | 107 +++++++++--------- 3 files changed, 135 insertions(+), 65 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index e78d7cfb..8e93a2e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -662,6 +662,20 @@ public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull Ref bodyRef) { + if (isAuthoritativePhysicsStoreActive()) { + Store store = + authoritativePhysicsStore("read optional copied physics body snapshot"); + PhysicsStoreBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() + ? store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyRef) + : store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyUuid); + return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; + } + return getBodySnapshotIfRegistered(RigidBodyKey.of(bodyUuid)); + } + @Nonnull private PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull RigidBodyKey bodyKey) { PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); @@ -2106,6 +2120,19 @@ public Collection> getBodyAttachments(@Nonnull Ref> getBodyAttachments(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("read physics body attachments"); + return bodyRef != null && bodyRef.isValid() + ? projection.getAttachments(bodyRef) + : projection.getAttachments(bodyUuid); + } + return visualRuntime.getAttachments(RigidBodyKey.of(bodyUuid)); + } + @Override public boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey) { if (hasAttachedAuthoritativePhysicsStore()) { @@ -2127,6 +2154,18 @@ && authoritativeProjectionIndex("check physics body attachments") .hasAttachments(bodyRef); } + public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("check physics body attachments"); + return bodyRef != null && bodyRef.isValid() + ? projection.hasAttachments(bodyRef) + : projection.hasAttachments(bodyUuid); + } + return visualRuntime.hasAttachments(RigidBodyKey.of(bodyUuid)); + } + public void registerBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { if (hasAttachedAuthoritativePhysicsStore()) { Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), @@ -2194,6 +2233,19 @@ public Ref getGeneratedVisualProxy(@Nonnull Ref bodyR return null; } + @Nullable + public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (hasAttachedAuthoritativePhysicsStore()) { + PhysicsProjectionIndexResource projection = + authoritativeProjectionIndex("read generated visual proxy"); + return bodyRef != null && bodyRef.isValid() + ? projection.getGeneratedVisualProxy(bodyRef) + : projection.getGeneratedVisualProxy(bodyUuid); + } + return visualRuntime.getGeneratedVisualProxy(RigidBodyKey.of(bodyUuid)); + } + @Nonnull public Collection getGeneratedVisualProxyBodyKeys() { if (hasAttachedAuthoritativePhysicsStore()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java index 2ddf1fcf..ae6a861b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java @@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Queue; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -34,8 +35,13 @@ static GameplayAttachmentSnapshot forStore(@Nonnull Store store) { @Nonnull static GameplayAttachmentSnapshot fromSource(@Nonnull BodyKeySource source) { - return fromAttachmentSource(() -> new AttachmentBodies(source.bodyKeys(), - new Int2ObjectOpenHashMap<>())); + return fromAttachmentSource(() -> { + Set bodyUuids = new ObjectOpenHashSet<>(); + for (RigidBodyKey bodyKey : source.bodyKeys()) { + bodyUuids.add(bodyKey.value()); + } + return new AttachmentBodies(bodyUuids, new Int2ObjectOpenHashMap<>()); + }); } @Nonnull @@ -45,25 +51,36 @@ private static GameplayAttachmentSnapshot fromAttachmentSource(@Nonnull Attachme boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, @Nonnull RigidBodyKey bodyKey) { - return hasKnownGameplayAttachment(runtimeIndexHasAttachment, null, bodyKey); + return hasKnownGameplayAttachment(runtimeIndexHasAttachment, null, bodyKey.value()); } boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, @Nullable Ref bodyRef, @Nonnull RigidBodyKey bodyKey) { + return hasKnownGameplayAttachment(runtimeIndexHasAttachment, bodyRef, bodyKey.value()); + } + + boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, + @Nullable Ref bodyRef, + @Nonnull UUID bodyUuid) { if (runtimeIndexHasAttachment) { return true; } - return hasGameplayAttachment(bodyRef, bodyKey); + return hasGameplayAttachment(bodyRef, bodyUuid); } boolean hasGameplayAttachment(@Nonnull RigidBodyKey bodyKey) { - return hasGameplayAttachment(null, bodyKey); + return hasGameplayAttachment(null, bodyKey.value()); } boolean hasGameplayAttachment(@Nullable Ref bodyRef, @Nonnull RigidBodyKey bodyKey) { - return hasGameplayAttachment(bodyRef) || bodies().bodyKeys().contains(bodyKey); + return hasGameplayAttachment(bodyRef, bodyKey.value()); + } + + boolean hasGameplayAttachment(@Nullable Ref bodyRef, + @Nonnull UUID bodyUuid) { + return hasGameplayAttachment(bodyRef) || bodies().bodyUuids().contains(bodyUuid); } private boolean hasGameplayAttachment(@Nullable Ref bodyRef) { @@ -87,7 +104,7 @@ private static AttachmentBodies collectGameplayAttachments( @Nonnull Store store) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); - Queue bodyKeys = new ConcurrentLinkedQueue<>(); + Queue bodyUuids = new ConcurrentLinkedQueue<>(); Queue> bodyRefs = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, _) -> { @@ -95,15 +112,15 @@ private static AttachmentBodies collectGameplayAttachments( attachmentType); if (attachment != null && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { - bodyKeys.add(RigidBodyKey.of(attachment.getBodyUuid())); + bodyUuids.add(attachment.getBodyUuid()); Ref bodyRef = attachment.getBodyRef(); if (bodyRef != null && bodyRef.isValid()) { bodyRefs.add(bodyRef); } } }); - Set uniqueBodyKeys = new ObjectOpenHashSet<>(); - uniqueBodyKeys.addAll(bodyKeys); + Set uniqueBodyUuids = new ObjectOpenHashSet<>(); + uniqueBodyUuids.addAll(bodyUuids); Int2ObjectOpenHashMap> bodyRefsByRowIndex = new Int2ObjectOpenHashMap<>(); for (Ref bodyRef : bodyRefs) { @@ -113,7 +130,7 @@ private static AttachmentBodies collectGameplayAttachments( bodyRefsByRowIndex.put(rowIndex, bodyRef); } } - return new AttachmentBodies(uniqueBodyKeys, bodyRefsByRowIndex); + return new AttachmentBodies(uniqueBodyUuids, bodyRefsByRowIndex); } private static boolean sameRef(@Nullable Ref first, @@ -141,7 +158,7 @@ private interface AttachmentSource { } private record AttachmentBodies( - @Nonnull Set bodyKeys, + @Nonnull Set bodyUuids, @Nonnull Int2ObjectOpenHashMap> bodyRefsByRowIndex ) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index ab5c9797..23c4eb25 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -360,7 +360,7 @@ private int spawnCachedMaterializationTargets(@Nonnull MaterializationState stat } state.cachedMaterializationTargets.remove(index); Ref proxy = spawnProxy(store, - candidate.bodyKey(), + candidate.bodyUuid(), candidate.bodyRef(), candidate.snapshot(), candidate.registration(), @@ -368,7 +368,7 @@ private int spawnCachedMaterializationTargets(@Nonnull MaterializationState stat if (proxy == null) { continue; } - resource.setGeneratedVisualProxy(candidate.bodyKey().value(), + resource.setGeneratedVisualProxy(candidate.bodyUuid(), candidate.bodyRef(), proxy); spawned++; @@ -390,20 +390,20 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, @Nonnull GameplayAttachmentSnapshot gameplayAttachments, @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - if (generatedVisualProxy(resource, target.bodyRef(), target.bodyKey()) != null) { + if (generatedVisualProxy(resource, target.bodyUuid(), target.bodyRef()) != null) { return null; } PhysicsBodyRegistrationView registration = bodyRegistration(resource, - target.bodyRef(), - target.bodyKey()); + target.bodyUuid(), + target.bodyRef()); if (registration == null || registration.kind() != PhysicsBodyKind.BODY || !sameSpaceId(registration.spaceId(), target.spaceId()) || gameplayAttachments.hasKnownGameplayAttachment( - hasBodyAttachments(resource, target.bodyRef(), registration.bodyKey()), + hasBodyAttachments(resource, target.bodyUuid(), target.bodyRef()), target.bodyRef(), - registration.bodyKey())) { + target.bodyUuid())) { return null; } @@ -416,7 +416,7 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( return null; } - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(target.bodyKey()); + PhysicsBodySnapshot snapshot = bodySnapshot(resource, target.bodyUuid(), target.bodyRef()); if (snapshot == null) { return null; } @@ -425,7 +425,7 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( } DetachedVisualOcclusion.Result currentPolicy = resolveCurrentMaterializationPolicy(resource, - target.bodyKey(), + registration.bodyKey(), space, snapshot, settings, @@ -436,7 +436,7 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( if (!currentPolicy.shouldMaterialize()) { return null; } - return new MaterializationCandidate(target.bodyKey(), + return new MaterializationCandidate(target.bodyUuid(), target.bodyRef(), snapshot, registration, @@ -447,29 +447,32 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( @Nullable private static Ref generatedVisualProxy( @Nonnull PhysicsWorldRuntimeResource resource, - @Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey) { - return bodyRef != null - ? resource.getGeneratedVisualProxy(bodyRef) - : resource.getGeneratedVisualProxy(bodyKey); + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + return resource.getGeneratedVisualProxy(bodyUuid, bodyRef); } @Nullable private static PhysicsBodyRegistrationView bodyRegistration( @Nonnull PhysicsWorldRuntimeResource resource, - @Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey) { - return bodyRef != null + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + return bodyRef != null && bodyRef.isValid() ? resource.getBodyRegistrationView(bodyRef) - : resource.getBodyRegistrationView(bodyKey); + : resource.getBodyRegistrationView(bodyUuid); } private static boolean hasBodyAttachments(@Nonnull PhysicsWorldRuntimeResource resource, - @Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey) { - return bodyRef != null - ? resource.hasBodyAttachments(bodyRef) - : resource.hasBodyAttachments(bodyKey); + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + return resource.hasBodyAttachments(bodyUuid, bodyRef); + } + + @Nullable + private static PhysicsBodySnapshot bodySnapshot(@Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + return resource.getBodySnapshotIfRegistered(bodyUuid, bodyRef); } private static int refreshCooldown(int intervalTicks) { @@ -525,7 +528,6 @@ private static int processMaterializedProxies(@Nonnull Store store, collector.incrementVisibilityChecks(); } UUID bodyUuid = generatedProxy.bodyUuid(); - RigidBodyKey bodyKey = RigidBodyKey.of(bodyUuid); Ref proxy = generatedProxy.proxy(); BodyAttachmentComponent proxyAttachment = expectedProxyAttachment(store, proxy, bodyUuid); @@ -533,20 +535,20 @@ private static int processMaterializedProxies(@Nonnull Store store, if (bodyRef == null && proxyAttachment != null) { bodyRef = validBodyRef(proxyAttachment.getBodyRef()); } - PhysicsBodyRegistrationView registration = bodyRegistration(resource, bodyRef, bodyKey); + PhysicsBodyRegistrationView registration = bodyRegistration(resource, bodyUuid, bodyRef); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { - if (registration == null && resource.isBodyCreationPending(bodyKey)) { + if (registration == null && resource.isBodyCreationPending(bodyUuid)) { count++; continue; } - removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); + removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } continue; } if (proxy == null || proxyAttachment == null) { - removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); + removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -554,11 +556,11 @@ private static int processMaterializedProxies(@Nonnull Store store, } if (hasGameplayAttachment(store, resource, - bodyKey, + bodyUuid, bodyRef, proxy, gameplayAttachments)) { - removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); + removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -567,16 +569,16 @@ private static int processMaterializedProxies(@Nonnull Store store, PhysicsSpaceSettings settings = resolveSettings(resource, registration); if (settings == null || !settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled()) { - removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); + removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } continue; } - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyKey); + PhysicsBodySnapshot snapshot = bodySnapshot(resource, bodyUuid, bodyRef); if (snapshot == null) { - removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); + removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -584,7 +586,7 @@ private static int processMaterializedProxies(@Nonnull Store store, } if (!isBodyChunkLoaded(store, snapshot) || shouldDematerialize(snapshot, settings, interests)) { - removeGeneratedProxy(store, resource, bodyKey, bodyRef, proxy); + removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); if (collector != null) { collector.incrementDematerialized(); } @@ -607,7 +609,7 @@ private static void collectMaterializationCandidates(@Nonnull Store return; } - Set seenBodies = new ObjectOpenHashSet<>(); + Set seenBodyUuids = new ObjectOpenHashSet<>(); for (PhysicsSpaceBinding space : resource.iterateSpaceBindings()) { PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(space.spaceId()); if (!settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled()) { @@ -623,16 +625,17 @@ private static void collectMaterializationCandidates(@Nonnull Store interest.position(), settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), (bodyKey, bodyRef, snapshot, bodySpaceId, kind, persistenceMode) -> { - if (!seenBodies.add(bodyKey) - || generatedVisualProxy(resource, bodyRef, bodyKey) != null) { + UUID bodyUuid = bodyKey.value(); + if (!seenBodyUuids.add(bodyUuid) + || generatedVisualProxy(resource, bodyUuid, bodyRef) != null) { return; } if (kind != PhysicsBodyKind.BODY || !bodySpaceId.equals(space.spaceId()) || gameplayAttachments.hasKnownGameplayAttachment( - hasBodyAttachments(resource, bodyRef, bodyKey), + hasBodyAttachments(resource, bodyUuid, bodyRef), bodyRef, - bodyKey)) { + bodyUuid)) { return; } if (!isBodyChunkLoaded(store, snapshot)) { @@ -650,7 +653,7 @@ private static void collectMaterializationCandidates(@Nonnull Store raycastBudget, collector); if (materializeInterest.shouldMaterialize()) { - candidates.add(new CachedMaterializationTarget(bodyKey, + candidates.add(new CachedMaterializationTarget(bodyUuid, bodyRef, bodySpaceId, materializeInterest.priorityDistanceSquared())); @@ -728,15 +731,13 @@ private record OrphanVisualProxy( private static boolean hasGameplayAttachment(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref proxy, @Nonnull GameplayAttachmentSnapshot gameplayAttachments) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); - Collection> attachments = bodyRef != null - ? resource.getBodyAttachments(bodyRef) - : resource.getBodyAttachments(bodyKey); + Collection> attachments = resource.getBodyAttachments(bodyUuid, bodyRef); for (Ref attachmentRef : attachments) { if (sameRef(attachmentRef, proxy)) { continue; @@ -747,7 +748,7 @@ private static boolean hasGameplayAttachment(@Nonnull Store store, return true; } } - return gameplayAttachments.hasGameplayAttachment(bodyRef, bodyKey); + return gameplayAttachments.hasGameplayAttachment(bodyRef, bodyUuid); } @Nullable @@ -865,14 +866,14 @@ private static Ref validBodyRef(@Nullable Ref bodyRe private static void removeGeneratedProxy(@Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nullable Ref proxy) { if (bodyRef != null) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey.value(), bodyRef, proxy); + GeneratedProxyLifecycle.removeProxy(store, resource, bodyUuid, bodyRef, proxy); return; } - GeneratedProxyLifecycle.removeProxy(store, resource, bodyKey, proxy); + GeneratedProxyLifecycle.removeProxy(store, resource, RigidBodyKey.of(bodyUuid), proxy); } private static boolean sameSpaceId(@Nullable SpaceId first, @Nullable SpaceId second) { @@ -891,7 +892,7 @@ private static boolean sameRef(@Nullable Ref first, @Nullable private static Ref spawnProxy(@Nonnull Store store, - @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull PhysicsBodyRegistrationView registration, @@ -921,7 +922,7 @@ private static Ref spawnProxy(@Nonnull Store store, holder.removeComponent(Velocity.getComponentType()); holder.addComponent(store.getRegistry().getNonSerializedComponentType(), NonSerialized.get()); holder.addComponent(GeneratedVisualProxyComponent.getComponentType(), new GeneratedVisualProxyComponent()); - BodyAttachmentComponent attachment = BodyAttachmentComponent.generatedProxy(bodyKey.value(), + BodyAttachmentComponent attachment = BodyAttachmentComponent.generatedProxy(bodyUuid, new Vector3f(), new Quaternionf(), Float.NaN); @@ -930,7 +931,7 @@ private static Ref spawnProxy(@Nonnull Store store, return store.addEntity(holder, AddReason.SPAWN); } - private record MaterializationCandidate(@Nonnull RigidBodyKey bodyKey, + private record MaterializationCandidate(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull PhysicsBodyRegistrationView registration, @@ -938,7 +939,7 @@ private record MaterializationCandidate(@Nonnull RigidBodyKey bodyKey, float distanceSquared) { } - private record CachedMaterializationTarget(@Nonnull RigidBodyKey bodyKey, + private record CachedMaterializationTarget(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull SpaceId spaceId, float distanceSquared) { From d214bc784faf73ba9ba7fa33c7aaf8440f8bb7fa Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 12:48:18 +0200 Subject: [PATCH 194/534] refactor(visual): resolve occlusion by body ref Signed-off-by: Blovien --- .../resources/PhysicsVisualRuntime.java | 73 ++++++++++++++++++- .../PhysicsWorldRuntimeResource.java | 21 ++++++ .../visual/DetachedVisualOcclusion.java | 48 +++++++++++- ...csDetachedVisualMaterializationSystem.java | 53 ++++++++++---- 4 files changed, 174 insertions(+), 21 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index 064492a0..43aaf6e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -2,8 +2,10 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; @@ -13,6 +15,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; @@ -35,6 +38,8 @@ public final class PhysicsVisualRuntime { private final List syntheticVisualInterests = new ArrayList<>(); private final Map bodyVisualInterestStates = new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap bodyVisualInterestStatesByRowIndex = + new Int2ObjectOpenHashMap<>(); public PhysicsVisualRuntime(@Nonnull Consumer> syncStateCleaner) { this.syncStateCleaner = syncStateCleaner; @@ -238,12 +243,54 @@ public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( _ -> new BodyVisualInterestState()); } + @Nonnull + public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (bodyRef != null && bodyRef.isValid()) { + return getOrCreateBodyVisualInterestState(bodyRef); + } + return getOrCreateBodyVisualInterestState(RigidBodyKey.of(bodyUuid)); + } + @Nullable public synchronized BodyVisualInterestState getBodyVisualInterestState( @Nonnull RigidBodyKey bodyKey) { return bodyVisualInterestStates.get(bodyKey); } + @Nullable + public synchronized BodyVisualInterestState getBodyVisualInterestState( + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (bodyRef != null && bodyRef.isValid()) { + int rowIndex = bodyRef.getIndex(); + BodyVisualInterestRefState row = bodyVisualInterestStatesByRowIndex.get(rowIndex); + if (row == null) { + return null; + } + if (!isMatchingLiveRef(row, bodyRef)) { + bodyVisualInterestStatesByRowIndex.remove(rowIndex); + return null; + } + return row.state(); + } + return getBodyVisualInterestState(RigidBodyKey.of(bodyUuid)); + } + + public synchronized void clearBodyVisualInterestState(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + bodyVisualInterestStates.remove(RigidBodyKey.of(bodyUuid)); + if (bodyRef == null) { + return; + } + int rowIndex = bodyRef.getIndex(); + BodyVisualInterestRefState row = bodyVisualInterestStatesByRowIndex.get(rowIndex); + if (row != null && (!row.bodyRef().isValid() || sameRef(row.bodyRef(), bodyRef))) { + bodyVisualInterestStatesByRowIndex.remove(rowIndex); + } + } + public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { List> staleRefs = new ArrayList<>(); synchronized (this) { @@ -275,10 +322,28 @@ public void clear() { generatedVisualProxies.clear(); syntheticVisualInterests.clear(); bodyVisualInterestStates.clear(); + bodyVisualInterestStatesByRowIndex.clear(); } cleanSyncStates(staleRefs); } + @Nonnull + private BodyVisualInterestState getOrCreateBodyVisualInterestState( + @Nonnull Ref bodyRef) { + int rowIndex = bodyRef.getIndex(); + BodyVisualInterestRefState row = bodyVisualInterestStatesByRowIndex.get(rowIndex); + if (row == null || !isMatchingLiveRef(row, bodyRef)) { + row = new BodyVisualInterestRefState(bodyRef, new BodyVisualInterestState()); + bodyVisualInterestStatesByRowIndex.put(rowIndex, row); + } + return row.state(); + } + + private static boolean isMatchingLiveRef(@Nonnull BodyVisualInterestRefState row, + @Nonnull Ref bodyRef) { + return row.bodyRef().isValid() && sameRef(row.bodyRef(), bodyRef); + } + private void cleanSyncState(@Nullable Ref ref) { if (ref != null) { syncStateCleaner.accept(ref); @@ -291,8 +356,8 @@ private void cleanSyncStates(@Nonnull Collection> refs) { } } - private static boolean sameRef(@Nullable Ref first, - @Nullable Ref second) { + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { return first == second || (first != null && second != null @@ -301,6 +366,10 @@ private static boolean sameRef(@Nullable Ref first, && first.getIndex() == second.getIndex()); } + private record BodyVisualInterestRefState(@Nonnull Ref bodyRef, + @Nonnull BodyVisualInterestState state) { + } + /** * Per-body visual-interest cache produced by detached visual materialization. * diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 8e93a2e8..fd7f0380 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2458,6 +2458,15 @@ public BodyVisualInterestState getOrCreateBodyVisualInterestState(@Nonnull Rigid return state; } + @Nonnull + public BodyVisualInterestState getOrCreateBodyVisualInterestState(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + BodyVisualInterestState state = + visualRuntime.getOrCreateBodyVisualInterestState(bodyUuid, bodyRef); + state.advanceVisualInterestTick(visualInterestTick.get()); + return state; + } + @Nullable public BodyVisualInterestState getBodyVisualInterestState(@Nonnull RigidBodyKey bodyKey) { BodyVisualInterestState state = visualRuntime.getBodyVisualInterestState(bodyKey); @@ -2467,6 +2476,17 @@ public BodyVisualInterestState getBodyVisualInterestState(@Nonnull RigidBodyKey return state; } + @Nullable + public BodyVisualInterestState getBodyVisualInterestState(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + BodyVisualInterestState state = visualRuntime.getBodyVisualInterestState(bodyUuid, + bodyRef); + if (state != null) { + state.advanceVisualInterestTick(visualInterestTick.get()); + } + return state; + } + public long advanceVisualInterestTick() { return visualInterestTick.incrementAndGet(); } @@ -2670,6 +2690,7 @@ private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { chunkRuntime.clearBody(bodyRef); } bodyRuntime.clearBodyRuntimeState(bodyKey); + visualRuntime.clearBodyVisualInterestState(bodyKey.value(), bodyRef); } public void markContinuousCollisionForced(@Nonnull RigidBodyKey bodyKey) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java index 0b711cd8..0c505e38 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -40,9 +41,34 @@ static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, long visualInterestTick, @Nonnull RaycastBudget raycastBudget, @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { + return resolve(resource, + bodyKey.value(), + null, + space, + snapshot, + settings, + interests, + radius, + visualInterestTick, + raycastBudget, + collector); + } + + @Nonnull + static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nullable PhysicsSpaceBinding space, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull PhysicsSpaceSettings settings, + @Nonnull List interests, + float radius, + long visualInterestTick, + @Nonnull RaycastBudget raycastBudget, + @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { InterestProbe probe = probeNearestLikelyInterest(snapshot, settings, interests, radius); PhysicsVisualRuntime.BodyVisualInterestState state = - resource.getOrCreateBodyVisualInterestState(bodyKey); + resource.getOrCreateBodyVisualInterestState(bodyUuid, bodyRef); if (!probe.inRange()) { state.clearPendingRaycast(); state.recordInterest(Float.POSITIVE_INFINITY, false, false, false, visualInterestTick); @@ -69,7 +95,7 @@ static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, if (state.hasCompletedRaycast()) { Optional completedRaycast = state.pollCompletedRaycast(); raycastVisible = completedRaycast - .map(view -> raycastHitMatchesBody(bodyKey, view)) + .map(view -> raycastHitMatchesBody(bodyUuid, bodyRef, view)) .orElse(false); raycastDecisionKnown = true; raycastEvaluated = true; @@ -109,15 +135,29 @@ static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, return Result.visible(probe.distanceSquared(), priorityDistanceSquared); } - private static boolean raycastHitMatchesBody(@Nonnull RigidBodyKey bodyKey, + private static boolean raycastHitMatchesBody(@Nonnull UUID bodyUuid, + @Nullable Ref expectedBodyRef, @Nonnull RaycastHitView view) { Ref bodyRef = view.bodyRef(); if (bodyRef == null || !bodyRef.isValid()) { return false; } + if (expectedBodyRef != null && expectedBodyRef.isValid()) { + return sameRef(expectedBodyRef, bodyRef); + } UuidComponent uuid = bodyRef.getStore().getComponent(bodyRef, UuidComponent.getComponentType()); - return uuid != null && bodyKey.value().equals(uuid.getUuid()); + return uuid != null && bodyUuid.equals(uuid.getUuid()); + } + + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); } private static void submitRaycast(@Nonnull PhysicsWorldRuntimeResource resource, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 23c4eb25..e9e42366 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -425,7 +425,8 @@ private static MaterializationCandidate resolveCachedMaterializationCandidate( } DetachedVisualOcclusion.Result currentPolicy = resolveCurrentMaterializationPolicy(resource, - registration.bodyKey(), + target.bodyUuid(), + target.bodyRef(), space, snapshot, settings, @@ -643,15 +644,16 @@ private static void collectMaterializationCandidates(@Nonnull Store } DetachedVisualOcclusion.Result materializeInterest = DetachedVisualOcclusion.resolve(resource, - bodyKey, - space, - snapshot, - settings, - interests, - settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), - visualInterestTick, - raycastBudget, - collector); + bodyUuid, + bodyRef, + space, + snapshot, + settings, + interests, + settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), + visualInterestTick, + raycastBudget, + collector); if (materializeInterest.shouldMaterialize()) { candidates.add(new CachedMaterializationTarget(bodyUuid, bodyRef, @@ -801,6 +803,31 @@ static DetachedVisualOcclusion.Result resolveCurrentMaterializationPolicy( collector); } + @Nonnull + static DetachedVisualOcclusion.Result resolveCurrentMaterializationPolicy( + @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull PhysicsSpaceBinding space, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull PhysicsSpaceSettings settings, + @Nonnull List interests, + long visualInterestTick, + @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, + @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { + return DetachedVisualOcclusion.resolve(resource, + bodyUuid, + bodyRef, + space, + snapshot, + settings, + interests, + settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), + visualInterestTick, + raycastBudget, + collector); + } + private static boolean shouldDematerialize(@Nonnull PhysicsBodySnapshot snapshot, @Nonnull PhysicsSpaceSettings settings, @Nonnull List interests) { @@ -869,11 +896,7 @@ private static void removeGeneratedProxy(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nullable Ref proxy) { - if (bodyRef != null) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyUuid, bodyRef, proxy); - return; - } - GeneratedProxyLifecycle.removeProxy(store, resource, RigidBodyKey.of(bodyUuid), proxy); + GeneratedProxyLifecycle.removeProxy(store, resource, bodyUuid, bodyRef, proxy); } private static boolean sameSpaceId(@Nullable SpaceId first, @Nullable SpaceId second) { From 06d1f845897924c5b336fb2ff56a44370683e7c7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 12:53:00 +0200 Subject: [PATCH 195/534] refactor(visual): drop key proxy cleanup overloads Signed-off-by: Blovien --- .../visual/GeneratedProxyLifecycle.java | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index 1713998d..bd30dab9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -9,10 +9,9 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -28,25 +27,6 @@ public final class GeneratedProxyLifecycle { private GeneratedProxyLifecycle() { } - static void removeProxy(@Nonnull ComponentAccessor accessor, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyKey bodyKey) { - Ref proxy = resource.getGeneratedVisualProxy(bodyKey); - removeProxy(accessor, resource, bodyKey, proxy); - } - - static void removeProxy(@Nonnull ComponentAccessor accessor, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyKey bodyKey, - @Nullable Ref proxy) { - if (proxy == null) { - resource.clearGeneratedVisualProxy(bodyKey); - } else { - resource.clearGeneratedVisualProxy(bodyKey, proxy); - } - removeEntity(accessor, proxy); - } - static void removeProxy(@Nonnull ComponentAccessor accessor, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull UUID bodyUuid, From 52cdb9fb46f2f4494c31f839bbf4a8efe6a0933a Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 13:01:10 +0200 Subject: [PATCH 196/534] refactor(visual): remove key materialization adapters Signed-off-by: Blovien --- .../visual/DetachedVisualOcclusion.java | 25 ------------ .../visual/GameplayAttachmentSnapshot.java | 39 ------------------- ...csDetachedVisualMaterializationSystem.java | 24 ------------ 3 files changed, 88 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java index 0c505e38..228c9b37 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java @@ -8,7 +8,6 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -30,30 +29,6 @@ final class DetachedVisualOcclusion { private DetachedVisualOcclusion() { } - @Nonnull - static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyKey bodyKey, - @Nullable PhysicsSpaceBinding space, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - float radius, - long visualInterestTick, - @Nonnull RaycastBudget raycastBudget, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - return resolve(resource, - bodyKey.value(), - null, - space, - snapshot, - settings, - interests, - radius, - visualInterestTick, - raycastBudget, - collector); - } - @Nonnull static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, @Nonnull UUID bodyUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java index ae6a861b..9945ea0e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -33,33 +32,11 @@ static GameplayAttachmentSnapshot forStore(@Nonnull Store store) { return fromAttachmentSource(() -> collectGameplayAttachments(store)); } - @Nonnull - static GameplayAttachmentSnapshot fromSource(@Nonnull BodyKeySource source) { - return fromAttachmentSource(() -> { - Set bodyUuids = new ObjectOpenHashSet<>(); - for (RigidBodyKey bodyKey : source.bodyKeys()) { - bodyUuids.add(bodyKey.value()); - } - return new AttachmentBodies(bodyUuids, new Int2ObjectOpenHashMap<>()); - }); - } - @Nonnull private static GameplayAttachmentSnapshot fromAttachmentSource(@Nonnull AttachmentSource source) { return new GameplayAttachmentSnapshot(source); } - boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, - @Nonnull RigidBodyKey bodyKey) { - return hasKnownGameplayAttachment(runtimeIndexHasAttachment, null, bodyKey.value()); - } - - boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, - @Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey) { - return hasKnownGameplayAttachment(runtimeIndexHasAttachment, bodyRef, bodyKey.value()); - } - boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, @Nullable Ref bodyRef, @Nonnull UUID bodyUuid) { @@ -69,15 +46,6 @@ boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, return hasGameplayAttachment(bodyRef, bodyUuid); } - boolean hasGameplayAttachment(@Nonnull RigidBodyKey bodyKey) { - return hasGameplayAttachment(null, bodyKey.value()); - } - - boolean hasGameplayAttachment(@Nullable Ref bodyRef, - @Nonnull RigidBodyKey bodyKey) { - return hasGameplayAttachment(bodyRef, bodyKey.value()); - } - boolean hasGameplayAttachment(@Nullable Ref bodyRef, @Nonnull UUID bodyUuid) { return hasGameplayAttachment(bodyRef) || bodies().bodyUuids().contains(bodyUuid); @@ -143,13 +111,6 @@ private static boolean sameRef(@Nullable Ref first, && first.getIndex() == second.getIndex()); } - @FunctionalInterface - interface BodyKeySource { - - @Nonnull - Set bodyKeys(); - } - @FunctionalInterface private interface AttachmentSource { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index e9e42366..5ff00361 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -35,7 +35,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; @@ -780,29 +779,6 @@ private static boolean shouldMaterialize(@Nonnull PhysicsBodySnapshot snapshot, settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius()) != Float.POSITIVE_INFINITY; } - @Nonnull - static DetachedVisualOcclusion.Result resolveCurrentMaterializationPolicy( - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsSpaceBinding space, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - long visualInterestTick, - @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - return DetachedVisualOcclusion.resolve(resource, - bodyKey, - space, - snapshot, - settings, - interests, - settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), - visualInterestTick, - raycastBudget, - collector); - } - @Nonnull static DetachedVisualOcclusion.Result resolveCurrentMaterializationPolicy( @Nonnull PhysicsWorldRuntimeResource resource, From ab2d0292d19cf6f338e96aa0f76bbcf8610ca4be Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 13:24:08 +0200 Subject: [PATCH 197/534] refactor(visual): index runtime state by body ref Signed-off-by: Blovien --- .../resources/PhysicsVisualRuntime.java | 517 ++++++++++++++++-- .../PhysicsWorldRuntimeResource.java | 45 +- 2 files changed, 500 insertions(+), 62 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index 43aaf6e2..f164b5f4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -31,12 +31,16 @@ public final class PhysicsVisualRuntime { @Nonnull private final Consumer> syncStateCleaner; - private final Map>> bodyAttachments = + private final Map>> bodyAttachments = new Object2ObjectOpenHashMap<>(); - private final Map> generatedVisualProxies = + private final Int2ObjectOpenHashMap bodyAttachmentsByRowIndex = + new Int2ObjectOpenHashMap<>(); + private final Map> generatedVisualProxies = new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap generatedVisualProxiesByRowIndex = + new Int2ObjectOpenHashMap<>(); private final List syntheticVisualInterests = new ArrayList<>(); - private final Map bodyVisualInterestStates = + private final Map bodyVisualInterestStates = new Object2ObjectOpenHashMap<>(); private final Int2ObjectOpenHashMap bodyVisualInterestStatesByRowIndex = new Int2ObjectOpenHashMap<>(); @@ -46,29 +50,76 @@ public PhysicsVisualRuntime(@Nonnull Consumer> syncStateCleaner } public synchronized void registerAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { - bodyAttachments.computeIfAbsent(bodyKey, _ -> new ObjectOpenHashSet<>()) + registerAttachment(bodyKey.value(), null, attachment); + } + + public synchronized void registerAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref attachment) { + bodyAttachments.computeIfAbsent(bodyUuid, _ -> new ObjectOpenHashSet<>()) .add(attachment); + if (isValidRef(bodyRef)) { + bodyAttachmentRefs(bodyRef).add(attachment); + } } public synchronized void unregisterAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { - Set> attachments = bodyAttachments.get(bodyKey); + unregisterAttachment(bodyKey.value(), null, attachment); + } + + public synchronized void unregisterAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref attachment) { + Set> attachments = bodyAttachments.get(bodyUuid); if (attachments == null) { + if (isValidRef(bodyRef)) { + unregisterAttachmentRef(bodyRef, attachment); + } return; } - attachments.remove(attachment); if (attachments.isEmpty()) { - bodyAttachments.remove(bodyKey); + bodyAttachments.remove(bodyUuid); + } + if (isValidRef(bodyRef)) { + unregisterAttachmentRef(bodyRef, attachment); } } @Nonnull public Collection> getAttachments(@Nonnull RigidBodyKey bodyKey) { + return getAttachments(bodyKey.value(), null); + } + + @Nonnull + public Collection> getAttachments(@Nonnull Ref bodyRef) { + if (!bodyRef.isValid()) { + return List.of(); + } + return liveAttachments(bodyRef); + } + + @Nonnull + public Collection> getAttachments(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (isValidRef(bodyRef)) { + Collection> attachments = liveAttachments(bodyRef); + if (!attachments.isEmpty()) { + return attachments; + } + } + return liveAttachments(bodyAttachments, bodyUuid); + } + + @Nonnull + private Collection> liveAttachments( + @Nonnull Map>> attachmentsByKey, + @Nonnull K key) { List> liveAttachments = new ArrayList<>(); List> staleAttachments = new ArrayList<>(); synchronized (this) { - Set> attachments = bodyAttachments.get(bodyKey); + Set> attachments = attachmentsByKey.get(key); if (attachments == null || attachments.isEmpty()) { return List.of(); } @@ -82,7 +133,7 @@ public Collection> getAttachments(@Nonnull RigidBodyKey bodyKey } staleAttachments.forEach(attachments::remove); if (attachments.isEmpty()) { - bodyAttachments.remove(bodyKey); + attachmentsByKey.remove(key); } } cleanSyncStates(staleAttachments); @@ -90,10 +141,25 @@ public Collection> getAttachments(@Nonnull RigidBodyKey bodyKey } public boolean hasAttachments(@Nonnull RigidBodyKey bodyKey) { + return hasAttachments(bodyKey.value(), null); + } + + public boolean hasAttachments(@Nonnull Ref bodyRef) { + return bodyRef.isValid() && hasLiveAttachments(bodyRef); + } + + public boolean hasAttachments(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + return isValidRef(bodyRef) && hasLiveAttachments(bodyRef) + || hasLiveAttachments(bodyAttachments, bodyUuid); + } + + private boolean hasLiveAttachments(@Nonnull Map>> attachmentsByKey, + @Nonnull K key) { boolean hasLiveAttachment = false; List> staleAttachments = new ArrayList<>(); synchronized (this) { - Set> attachments = bodyAttachments.get(bodyKey); + Set> attachments = attachmentsByKey.get(key); if (attachments == null || attachments.isEmpty()) { return false; } @@ -110,7 +176,7 @@ public boolean hasAttachments(@Nonnull RigidBodyKey bodyKey) { } } if (attachments.isEmpty()) { - bodyAttachments.remove(bodyKey); + attachmentsByKey.remove(key); } } cleanSyncStates(staleAttachments); @@ -119,12 +185,39 @@ public boolean hasAttachments(@Nonnull RigidBodyKey bodyKey) { @Nullable public Ref getGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { + return getGeneratedVisualProxy(bodyKey.value(), null); + } + + @Nullable + public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { + if (!bodyRef.isValid()) { + return null; + } + return liveGeneratedVisualProxy(bodyRef); + } + + @Nullable + public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + if (isValidRef(bodyRef)) { + Ref proxy = liveGeneratedVisualProxy(bodyRef); + if (proxy != null) { + return proxy; + } + } + return liveGeneratedVisualProxy(generatedVisualProxies, bodyUuid); + } + + @Nullable + private Ref liveGeneratedVisualProxy( + @Nonnull Map> proxiesByKey, + @Nonnull K key) { Ref staleProxy = null; Ref proxy; synchronized (this) { - proxy = generatedVisualProxies.get(bodyKey); + proxy = proxiesByKey.get(key); if (proxy != null && !proxy.isValid()) { - generatedVisualProxies.remove(bodyKey); + proxiesByKey.remove(key); staleProxy = proxy; proxy = null; } @@ -136,36 +229,84 @@ public Ref getGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { @Nonnull public Collection getGeneratedVisualProxyBodyKeys() { List bodyKeys = new ArrayList<>(); - List staleBodyKeys = new ArrayList<>(); + List staleBodyUuids = new ArrayList<>(); List> staleProxies = new ArrayList<>(); synchronized (this) { - for (Map.Entry> entry : generatedVisualProxies.entrySet()) { + for (Map.Entry> entry : generatedVisualProxies.entrySet()) { Ref proxy = entry.getValue(); if (proxy != null && proxy.isValid()) { - bodyKeys.add(entry.getKey()); + bodyKeys.add(RigidBodyKey.of(entry.getKey())); } else { - staleBodyKeys.add(entry.getKey()); + staleBodyUuids.add(entry.getKey()); if (proxy != null) { staleProxies.add(proxy); } } } - for (RigidBodyKey bodyKey : staleBodyKeys) { - generatedVisualProxies.remove(bodyKey); + for (UUID bodyUuid : staleBodyUuids) { + generatedVisualProxies.remove(bodyUuid); } } cleanSyncStates(staleProxies); return bodyKeys; } + @Nonnull + public Collection getGeneratedVisualProxyViews() { + List views = new ArrayList<>(); + List staleRowIndexes = new ArrayList<>(); + List staleBodyUuids = new ArrayList<>(); + List> staleProxies = new ArrayList<>(); + Map> bodyRefsByUuid = new Object2ObjectOpenHashMap<>(); + synchronized (this) { + for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { + GeneratedVisualProxyRef row = entry.getValue(); + Ref proxy = row.proxy(); + Ref uuidProxy = generatedVisualProxies.get(row.bodyUuid()); + if (!row.bodyRef().isValid() + || proxy == null + || !proxy.isValid() + || !sameRef(proxy, uuidProxy)) { + staleRowIndexes.add(entry.getIntKey()); + if (proxy != null && !proxy.isValid()) { + staleProxies.add(proxy); + } + continue; + } + bodyRefsByUuid.put(row.bodyUuid(), row.bodyRef()); + } + for (Integer rowIndex : staleRowIndexes) { + generatedVisualProxiesByRowIndex.remove(rowIndex.intValue()); + } + for (Map.Entry> entry : generatedVisualProxies.entrySet()) { + Ref proxy = entry.getValue(); + if (proxy != null && proxy.isValid()) { + views.add(new GeneratedVisualProxyView(entry.getKey(), + bodyRefsByUuid.get(entry.getKey()), + proxy)); + } else { + staleBodyUuids.add(entry.getKey()); + if (proxy != null) { + staleProxies.add(proxy); + } + } + } + for (UUID bodyUuid : staleBodyUuids) { + generatedVisualProxies.remove(bodyUuid); + } + } + cleanSyncStates(staleProxies); + return views; + } + public int generatedVisualProxyCount() { int count = 0; List> staleProxies = new ArrayList<>(); synchronized (this) { - Iterator>> iterator = + Iterator>> iterator = generatedVisualProxies.entrySet().iterator(); while (iterator.hasNext()) { - Map.Entry> entry = iterator.next(); + Map.Entry> entry = iterator.next(); Ref proxy = entry.getValue(); if (proxy != null && proxy.isValid()) { count++; @@ -182,45 +323,112 @@ public int generatedVisualProxyCount() { } public void setGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, + @Nonnull Ref proxy) { + setGeneratedVisualProxy(bodyKey.value(), null, proxy); + } + + public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, @Nonnull Ref proxy) { Ref previousProxy; + Ref previousRefProxy = null; synchronized (this) { - previousProxy = generatedVisualProxies.put(bodyKey, proxy); + previousProxy = generatedVisualProxies.put(bodyUuid, proxy); + if (isValidRef(bodyRef)) { + GeneratedVisualProxyRef previous = generatedVisualProxiesByRowIndex.put(bodyRef.getIndex(), + new GeneratedVisualProxyRef(bodyUuid, bodyRef, proxy)); + if (previous != null) { + previousRefProxy = previous.proxy(); + } + } } if (!sameRef(previousProxy, proxy)) { cleanSyncState(previousProxy); } + if (!sameRef(previousRefProxy, proxy) && !sameRef(previousRefProxy, previousProxy)) { + cleanSyncState(previousRefProxy); + } } public void clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { + clearGeneratedVisualProxy(bodyKey.value(), null); + } + + public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { Ref proxy; + Ref refProxy = null; synchronized (this) { - proxy = generatedVisualProxies.remove(bodyKey); + proxy = generatedVisualProxies.remove(bodyUuid); + if (isValidRef(bodyRef)) { + GeneratedVisualProxyRef removed = removeGeneratedVisualProxyRef(bodyRef); + if (removed != null) { + refProxy = removed.proxy(); + } + } } cleanSyncState(proxy); + if (!sameRef(refProxy, proxy)) { + cleanSyncState(refProxy); + } } public boolean clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref expectedProxy) { - Ref proxy; + return clearGeneratedVisualProxy(bodyKey.value(), null, expectedProxy); + } + + public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref expectedProxy) { + Ref proxy = null; + Ref refProxy = null; + boolean matched = false; synchronized (this) { - proxy = generatedVisualProxies.get(bodyKey); - if (proxy == null || !sameRef(proxy, expectedProxy)) { + Ref uuidProxy = generatedVisualProxies.get(bodyUuid); + if (sameRef(uuidProxy, expectedProxy)) { + generatedVisualProxies.remove(bodyUuid); + proxy = uuidProxy; + matched = true; + } + if (isValidRef(bodyRef)) { + GeneratedVisualProxyRef removed = removeGeneratedVisualProxyRef(bodyRef, expectedProxy); + if (removed != null) { + refProxy = removed.proxy(); + matched = true; + } + } + if (!matched) { return false; } - - generatedVisualProxies.remove(bodyKey); } cleanSyncState(proxy); + if (!sameRef(refProxy, proxy)) { + cleanSyncState(refProxy); + } return true; } public synchronized boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxy) { - Ref registeredProxy = generatedVisualProxies.get(bodyKey); + return isGeneratedVisualProxy(bodyKey.value(), null, proxy); + } + + public synchronized boolean isGeneratedVisualProxy(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Ref proxy) { + if (isValidRef(bodyRef) && sameRef(liveGeneratedVisualProxy(bodyRef), proxy)) { + return true; + } + Ref registeredProxy = generatedVisualProxies.get(bodyUuid); return registeredProxy != null && sameRef(registeredProxy, proxy); } + public synchronized boolean isGeneratedVisualProxy(@Nonnull Ref bodyRef, + @Nonnull Ref proxy) { + return bodyRef.isValid() && sameRef(liveGeneratedVisualProxy(bodyRef), proxy); + } + public synchronized void setSyntheticVisualInterests( @Nonnull Collection interests) { syntheticVisualInterests.clear(); @@ -239,7 +447,7 @@ public synchronized void clearSyntheticVisualInterests() { @Nonnull public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( @Nonnull RigidBodyKey bodyKey) { - return bodyVisualInterestStates.computeIfAbsent(bodyKey, + return bodyVisualInterestStates.computeIfAbsent(bodyKey.value(), _ -> new BodyVisualInterestState()); } @@ -250,13 +458,14 @@ public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( if (bodyRef != null && bodyRef.isValid()) { return getOrCreateBodyVisualInterestState(bodyRef); } - return getOrCreateBodyVisualInterestState(RigidBodyKey.of(bodyUuid)); + return bodyVisualInterestStates.computeIfAbsent(bodyUuid, + _ -> new BodyVisualInterestState()); } @Nullable public synchronized BodyVisualInterestState getBodyVisualInterestState( @Nonnull RigidBodyKey bodyKey) { - return bodyVisualInterestStates.get(bodyKey); + return bodyVisualInterestStates.get(bodyKey.value()); } @Nullable @@ -275,13 +484,13 @@ public synchronized BodyVisualInterestState getBodyVisualInterestState( } return row.state(); } - return getBodyVisualInterestState(RigidBodyKey.of(bodyUuid)); + return bodyVisualInterestStates.get(bodyUuid); } public synchronized void clearBodyVisualInterestState(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { - bodyVisualInterestStates.remove(RigidBodyKey.of(bodyUuid)); - if (bodyRef == null) { + bodyVisualInterestStates.remove(bodyUuid); + if (!isValidRef(bodyRef)) { return; } int rowIndex = bodyRef.getIndex(); @@ -292,17 +501,41 @@ public synchronized void clearBodyVisualInterestState(@Nonnull UUID bodyUuid, } public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { + clearBodyRuntimeState(bodyKey.value(), null); + } + + public void clearBodyRuntimeState(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { List> staleRefs = new ArrayList<>(); synchronized (this) { - Set> attachments = bodyAttachments.remove(bodyKey); + Set> attachments = bodyAttachments.remove(bodyUuid); if (attachments != null) { staleRefs.addAll(attachments); } - Ref proxy = generatedVisualProxies.remove(bodyKey); + if (isValidRef(bodyRef)) { + BodyAttachmentRefs attachmentRefs = removeAttachmentRefs(bodyRef); + if (attachmentRefs != null) { + staleRefs.addAll(attachmentRefs.attachments()); + } + } + Ref proxy = generatedVisualProxies.remove(bodyUuid); if (proxy != null) { staleRefs.add(proxy); } - bodyVisualInterestStates.remove(bodyKey); + if (isValidRef(bodyRef)) { + GeneratedVisualProxyRef proxyRef = removeGeneratedVisualProxyRef(bodyRef); + if (proxyRef != null && !sameRef(proxyRef.proxy(), proxy)) { + staleRefs.add(proxyRef.proxy()); + } + } + bodyVisualInterestStates.remove(bodyUuid); + if (isValidRef(bodyRef)) { + int rowIndex = bodyRef.getIndex(); + BodyVisualInterestRefState row = bodyVisualInterestStatesByRowIndex.get(rowIndex); + if (row != null && (!row.bodyRef().isValid() || sameRef(row.bodyRef(), bodyRef))) { + bodyVisualInterestStatesByRowIndex.remove(rowIndex); + } + } } cleanSyncStates(staleRefs); } @@ -318,8 +551,19 @@ public void clear() { } } } + for (var entry : bodyAttachmentsByRowIndex.int2ObjectEntrySet()) { + staleRefs.addAll(entry.getValue().attachments()); + } + for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { + Ref proxy = entry.getValue().proxy(); + if (proxy != null) { + staleRefs.add(proxy); + } + } bodyAttachments.clear(); + bodyAttachmentsByRowIndex.clear(); generatedVisualProxies.clear(); + generatedVisualProxiesByRowIndex.clear(); syntheticVisualInterests.clear(); bodyVisualInterestStates.clear(); bodyVisualInterestStatesByRowIndex.clear(); @@ -344,6 +588,194 @@ private static boolean isMatchingLiveRef(@Nonnull BodyVisualInterestRefState row return row.bodyRef().isValid() && sameRef(row.bodyRef(), bodyRef); } + private static boolean isMatchingLiveRef(@Nonnull BodyAttachmentRefs row, + @Nonnull Ref bodyRef) { + return row.bodyRef().isValid() && sameRef(row.bodyRef(), bodyRef); + } + + private static boolean isMatchingLiveRef(@Nonnull GeneratedVisualProxyRef row, + @Nonnull Ref bodyRef) { + return row.bodyRef().isValid() && sameRef(row.bodyRef(), bodyRef); + } + + @Nonnull + private Set> bodyAttachmentRefs(@Nonnull Ref bodyRef) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null || !isMatchingLiveRef(row, bodyRef)) { + row = new BodyAttachmentRefs(bodyRef, new ObjectOpenHashSet<>()); + bodyAttachmentsByRowIndex.put(rowIndex, row); + } + return row.attachments(); + } + + private void unregisterAttachmentRef(@Nonnull Ref bodyRef, + @Nonnull Ref attachment) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null) { + return; + } + if (!isMatchingLiveRef(row, bodyRef)) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return; + } + Set> attachments = row.attachments(); + attachments.remove(attachment); + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + } + } + + @Nullable + private BodyAttachmentRefs removeAttachmentRefs(@Nonnull Ref bodyRef) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null || !isMatchingLiveRef(row, bodyRef)) { + if (row != null) { + bodyAttachmentsByRowIndex.remove(rowIndex); + } + return null; + } + bodyAttachmentsByRowIndex.remove(rowIndex); + return row; + } + + @Nonnull + private Collection> liveAttachments(@Nonnull Ref bodyRef) { + List> liveAttachments = new ArrayList<>(); + List> staleAttachments = new ArrayList<>(); + synchronized (this) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null) { + return List.of(); + } + if (!isMatchingLiveRef(row, bodyRef)) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return List.of(); + } + Set> attachments = row.attachments(); + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return List.of(); + } + for (Iterator> iterator = attachments.iterator(); iterator.hasNext();) { + Ref attachment = iterator.next(); + if (attachment != null && attachment.isValid()) { + liveAttachments.add(attachment); + } else { + iterator.remove(); + if (attachment != null) { + staleAttachments.add(attachment); + } + } + } + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + } + } + cleanSyncStates(staleAttachments); + return liveAttachments; + } + + private boolean hasLiveAttachments(@Nonnull Ref bodyRef) { + boolean hasLiveAttachment = false; + List> staleAttachments = new ArrayList<>(); + synchronized (this) { + int rowIndex = bodyRef.getIndex(); + BodyAttachmentRefs row = bodyAttachmentsByRowIndex.get(rowIndex); + if (row == null) { + return false; + } + if (!isMatchingLiveRef(row, bodyRef)) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return false; + } + Set> attachments = row.attachments(); + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + return false; + } + for (Iterator> iterator = attachments.iterator(); iterator.hasNext();) { + Ref attachment = iterator.next(); + if (attachment != null && attachment.isValid()) { + hasLiveAttachment = true; + } else { + iterator.remove(); + if (attachment != null) { + staleAttachments.add(attachment); + } + } + } + if (attachments.isEmpty()) { + bodyAttachmentsByRowIndex.remove(rowIndex); + } + } + cleanSyncStates(staleAttachments); + return hasLiveAttachment; + } + + @Nullable + private Ref liveGeneratedVisualProxy(@Nonnull Ref bodyRef) { + Ref staleProxy = null; + synchronized (this) { + int rowIndex = bodyRef.getIndex(); + GeneratedVisualProxyRef row = generatedVisualProxiesByRowIndex.get(rowIndex); + if (row == null) { + return null; + } + if (!isMatchingLiveRef(row, bodyRef)) { + generatedVisualProxiesByRowIndex.remove(rowIndex); + return null; + } + Ref proxy = row.proxy(); + if (proxy != null && proxy.isValid()) { + return proxy; + } + generatedVisualProxiesByRowIndex.remove(rowIndex); + staleProxy = proxy; + } + cleanSyncState(staleProxy); + return null; + } + + @Nullable + private GeneratedVisualProxyRef removeGeneratedVisualProxyRef(@Nonnull Ref bodyRef) { + int rowIndex = bodyRef.getIndex(); + GeneratedVisualProxyRef row = generatedVisualProxiesByRowIndex.get(rowIndex); + if (row == null || !isMatchingLiveRef(row, bodyRef)) { + if (row != null) { + generatedVisualProxiesByRowIndex.remove(rowIndex); + } + return null; + } + generatedVisualProxiesByRowIndex.remove(rowIndex); + return row; + } + + @Nullable + private GeneratedVisualProxyRef removeGeneratedVisualProxyRef(@Nonnull Ref bodyRef, + @Nonnull Ref expectedProxy) { + int rowIndex = bodyRef.getIndex(); + GeneratedVisualProxyRef row = generatedVisualProxiesByRowIndex.get(rowIndex); + if (row == null || !isMatchingLiveRef(row, bodyRef)) { + if (row != null) { + generatedVisualProxiesByRowIndex.remove(rowIndex); + } + return null; + } + if (!sameRef(row.proxy(), expectedProxy)) { + return null; + } + generatedVisualProxiesByRowIndex.remove(rowIndex); + return row; + } + + private static boolean isValidRef(@Nullable Ref ref) { + return ref != null && ref.isValid(); + } + private void cleanSyncState(@Nullable Ref ref) { if (ref != null) { syncStateCleaner.accept(ref); @@ -366,6 +798,15 @@ private static boolean sameRef(@Nullable Ref first, && first.getIndex() == second.getIndex()); } + private record BodyAttachmentRefs(@Nonnull Ref bodyRef, + @Nonnull Set> attachments) { + } + + private record GeneratedVisualProxyRef(@Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nonnull Ref proxy) { + } + private record BodyVisualInterestRefState(@Nonnull Ref bodyRef, @Nonnull BodyVisualInterestState state) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index fd7f0380..7a617525 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2117,7 +2117,7 @@ public Collection> getBodyAttachments(@Nonnull Ref> getBodyAttachments(@Nonnull UUID bodyUuid, ? projection.getAttachments(bodyRef) : projection.getAttachments(bodyUuid); } - return visualRuntime.getAttachments(RigidBodyKey.of(bodyUuid)); + return visualRuntime.getAttachments(bodyUuid, bodyRef); } @Override @@ -2149,9 +2149,11 @@ public boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey) { @Override public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { - return hasAttachedAuthoritativePhysicsStore() - && authoritativeProjectionIndex("check physics body attachments") + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativeProjectionIndex("check physics body attachments") .hasAttachments(bodyRef); + } + return visualRuntime.hasAttachments(bodyRef); } public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, @@ -2163,7 +2165,7 @@ public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, ? projection.hasAttachments(bodyRef) : projection.hasAttachments(bodyUuid); } - return visualRuntime.hasAttachments(RigidBodyKey.of(bodyUuid)); + return visualRuntime.hasAttachments(bodyUuid, bodyRef); } public void registerBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { @@ -2185,7 +2187,7 @@ public void registerBodyAttachment(@Nonnull UUID bodyUuid, .registerAttachment(bodyUuid, bodyRef, attachment); return; } - visualRuntime.registerAttachment(RigidBodyKey.of(bodyUuid), attachment); + visualRuntime.registerAttachment(bodyUuid, bodyRef, attachment); } public void unregisterBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { @@ -2207,7 +2209,7 @@ public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, .unregisterAttachment(bodyUuid, bodyRef, attachment); return; } - visualRuntime.unregisterAttachment(RigidBodyKey.of(bodyUuid), attachment); + visualRuntime.unregisterAttachment(bodyUuid, bodyRef, attachment); } @Nullable @@ -2230,7 +2232,7 @@ public Ref getGeneratedVisualProxy(@Nonnull Ref bodyR return authoritativeProjectionIndex("read generated visual proxy") .getGeneratedVisualProxy(bodyRef); } - return null; + return visualRuntime.getGeneratedVisualProxy(bodyRef); } @Nullable @@ -2243,7 +2245,7 @@ public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid, ? projection.getGeneratedVisualProxy(bodyRef) : projection.getGeneratedVisualProxy(bodyUuid); } - return visualRuntime.getGeneratedVisualProxy(RigidBodyKey.of(bodyUuid)); + return visualRuntime.getGeneratedVisualProxy(bodyUuid, bodyRef); } @Nonnull @@ -2261,14 +2263,7 @@ public Collection getGeneratedVisualProxyViews() { return authoritativeProjectionIndex("list generated visual proxies") .getGeneratedVisualProxyViews(); } - List views = new ArrayList<>(); - for (RigidBodyKey bodyKey : visualRuntime.getGeneratedVisualProxyBodyKeys()) { - Ref proxy = visualRuntime.getGeneratedVisualProxy(bodyKey); - if (proxy != null) { - views.add(new GeneratedVisualProxyView(bodyKey.value(), null, proxy)); - } - } - return views; + return visualRuntime.getGeneratedVisualProxyViews(); } public int getGeneratedVisualProxyCount() { @@ -2299,7 +2294,7 @@ public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, .setGeneratedVisualProxy(bodyUuid, bodyRef, proxy); return; } - visualRuntime.setGeneratedVisualProxy(RigidBodyKey.of(bodyUuid), proxy); + visualRuntime.setGeneratedVisualProxy(bodyUuid, bodyRef, proxy); } public void clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { @@ -2320,7 +2315,7 @@ public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, .clearGeneratedVisualProxyForBodyRef(bodyUuid, bodyRef); return; } - visualRuntime.clearGeneratedVisualProxy(RigidBodyKey.of(bodyUuid)); + visualRuntime.clearGeneratedVisualProxy(bodyUuid, bodyRef); } public boolean clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @@ -2357,7 +2352,7 @@ public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, projection.clearGeneratedVisualProxy(bodyUuid, bodyRef, expectedProxy); return true; } - return visualRuntime.clearGeneratedVisualProxy(RigidBodyKey.of(bodyUuid), expectedProxy); + return visualRuntime.clearGeneratedVisualProxy(bodyUuid, bodyRef, expectedProxy); } public boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @@ -2379,13 +2374,15 @@ public boolean isGeneratedVisualProxy(@Nonnull UUID bodyUuid, : projection.getGeneratedVisualProxy(bodyUuid); return sameRef(registered, proxy); } - return visualRuntime.isGeneratedVisualProxy(RigidBodyKey.of(bodyUuid), proxy); + return visualRuntime.isGeneratedVisualProxy(bodyUuid, bodyRef, proxy); } public boolean isGeneratedVisualProxy(@Nonnull Ref bodyRef, @Nonnull Ref proxy) { - return hasAttachedAuthoritativePhysicsStore() - && sameRef(getGeneratedVisualProxy(bodyRef), proxy); + if (hasAttachedAuthoritativePhysicsStore()) { + return sameRef(getGeneratedVisualProxy(bodyRef), proxy); + } + return visualRuntime.isGeneratedVisualProxy(bodyRef, proxy); } public void setSyntheticVisualInterests(@Nonnull Collection interests) { @@ -2690,7 +2687,7 @@ private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { chunkRuntime.clearBody(bodyRef); } bodyRuntime.clearBodyRuntimeState(bodyKey); - visualRuntime.clearBodyVisualInterestState(bodyKey.value(), bodyRef); + visualRuntime.clearBodyRuntimeState(bodyKey.value(), bodyRef); } public void markContinuousCollisionForced(@Nonnull RigidBodyKey bodyKey) { From 00c6f86059ac7925fd57dbd2487f7b014662aa95 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 13:34:56 +0200 Subject: [PATCH 198/534] refactor(core): remove legacy physics systems Signed-off-by: Blovien --- .../systems/PhysicsChunkBoundarySystem.java | 648 --------------- .../systems/PhysicsCollisionLodSystem.java | 748 ------------------ .../PhysicsWorldCollisionStreamingSystem.java | 549 ------------- .../PersistentPhysicsWorldSyncSystem.java | 325 -------- .../PhysicsRuntimeHolderSystem.java | 55 -- .../PhysicsSnapshotPublicationSystem.java | 79 -- .../systems/step/PhysicsStepRestoreGate.java | 14 - .../systems/step/PhysicsStepSystem.java | 369 --------- 8 files changed, 2787 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGate.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java deleted file mode 100644 index 83a78a97..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystem.java +++ /dev/null @@ -1,648 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.dependency.SystemGroupDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.math.util.MathUtil; -import com.hypixel.hytale.server.core.modules.entity.system.UpdateLocationSystems; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; -import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundarySafeState; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import java.util.Set; -import java.util.function.Consumer; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector2d; -import org.joml.Vector3f; - -/** - * Keeps registered dynamic physics bodies from drifting into unloaded chunks. - * - *

Authoritative PhysicsStore bodies are tracked by live row refs. Legacy backend bodies keep - * the key compatibility path. Entity views may be absent, stale, or generated later, so this - * system uses the body's last known safe pose instead of entity transforms.

- */ -public class PhysicsChunkBoundarySystem extends TickingSystem { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private final Set> dependencies = Set.of( - new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), - new SystemDependency<>(Order.AFTER, PhysicsSnapshotPublicationSystem.class), - new SystemDependency<>(Order.BEFORE, PhysicsWorldCollisionStreamingSystem.class), - new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class), - new SystemDependency<>(Order.BEFORE, UpdateLocationSystems.TickingSystem.class) - ); - - private static final int TICKING_CHUNK_REQUEST_FLAGS = 4; - private final LongSet requestedChunkIndices = new LongOpenHashSet(); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - if (!WorldCollisionLifecycle.isEnabled()) { - return; - } - requestedChunkIndices.clear(); - - World world = store.getExternalData().getWorld(); - ChunkStore chunkStore = world.getChunkStore(); - Store chunkComponentStore = chunkStore.getStore(); - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - if (isAuthoritativePhysicsStoreActive()) { - processAuthoritativeBodies(world, - resource, - store, - chunkStore, - chunkComponentStore); - return; - } - - for (PhysicsBodyRegistrationView registration - : resource.getBodyRegistrationViews(PhysicsBodyKind.BODY)) { - processBody(registration, resource, store, chunkStore, chunkComponentStore); - } - } - - private void processAuthoritativeBodies(@Nonnull World world, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull Store store, - @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { - Store physics = ((PhysicsStoreWorld) world).getPhysicsStore() - .getStore(); - PhysicsStoreThreading.requireWorldThread(physics, - "process chunk-boundary PhysicsStore bodies"); - PhysicsBodyRegistrationResource registrations = - physics.getResource(PhysicsBodyRegistrationResource.getResourceType()); - for (PhysicsStoreBodySnapshot body : physics.getResource(PhysicsSnapshotResource.getResourceType()) - .getLatestFrame() - .bodies()) { - Ref bodyRef = body.bodyRef(); - if (bodyRef == null || !bodyRef.isValid()) { - continue; - } - PhysicsBodyRegistrationView registration = - registrations.getBodyRegistrationView(bodyRef); - if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { - continue; - } - processAuthoritativeBody(bodyRef, - registration, - resource, - store, - chunkStore, - chunkComponentStore); - } - } - - private void processBody(@Nonnull PhysicsBodyRegistrationView registration, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull Store store, - @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { - if (resource.getSpaceBinding(registration.spaceId()) == null) { - return; - } - - RigidBodyKey bodyKey = registration.bodyKey(); - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyKey); - if (snapshot == null) { - return; - } - if (snapshot.isStatic()) { - return; - } - - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(registration.spaceId()); - EntityChunkBoundaryMode mode = settings.getWorldCollisionSettings().getEntityChunkBoundaryMode(); - PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState = - resource.getChunkBoundaryPauseState(bodyKey); - if (pauseState != null) { - handlePausedBody(bodyKey, - snapshot, - pauseState, - mode, - resource, - store, - chunkStore, - chunkComponentStore); - return; - } - - long[] targetChunkIndices = chunkIndices(snapshot); - if (areChunksTicking(targetChunkIndices, chunkStore, chunkComponentStore)) { - recordSafePose(bodyKey, snapshot, resource); - return; - } - - if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { - requestTickingChunks(chunkStore, targetChunkIndices); - return; - } - - PhysicsOwnerBridge.run(store, "pause chunk-boundary physics body", - () -> pauseBody(bodyKey, snapshot, targetChunkIndices, resource)); - } - - private void processAuthoritativeBody(@Nonnull Ref bodyRef, - @Nonnull PhysicsBodyRegistrationView registration, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull Store store, - @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyRef); - if (snapshot == null || snapshot.isStatic()) { - return; - } - - PhysicsSpaceSettings settings = resource.getSpaceSettings(registration.spaceId()); - EntityChunkBoundaryMode mode = settings.getWorldCollisionSettings().getEntityChunkBoundaryMode(); - PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState = - resource.getChunkBoundaryPauseState(bodyRef); - if (pauseState != null) { - handlePausedBody(bodyRef, - snapshot, - pauseState, - mode, - resource, - store, - chunkStore, - chunkComponentStore); - return; - } - - long[] targetChunkIndices = chunkIndices(snapshot); - if (areChunksTicking(targetChunkIndices, chunkStore, chunkComponentStore)) { - recordSafePose(bodyRef, snapshot, resource); - return; - } - - if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { - requestTickingChunks(chunkStore, targetChunkIndices); - return; - } - - pauseBodyAuthoritative(store, bodyRef, snapshot, targetChunkIndices, resource); - } - - private void handlePausedBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState, - @Nonnull EntityChunkBoundaryMode mode, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull Store entityStore, - @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { - long[] targetChunkIndices = pauseState.getTargetChunkIndices(); - if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { - requestTickingChunks(chunkStore, targetChunkIndices); - } - - if (!areChunksTicking(targetChunkIndices, chunkStore, chunkComponentStore)) { - return; - } - - PhysicsOwnerBridge.run(entityStore, "resume chunk-boundary physics body", () -> { - var registration = resource.getRegistration(bodyKey); - if (registration == null) { - return; - } - PhysicsSpaceBinding space = resource.getSpaceBinding(registration.spaceId()); - if (space == null) { - return; - } - space.runtime() - .setBodyType(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - BackendRuntimeCodes.bodyTypeCode( - pauseState.getOriginalBodyType())); - space.runtime().setBodyVelocity(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - pauseState.getLinearVelocity().x, - pauseState.getLinearVelocity().y, - pauseState.getLinearVelocity().z, - pauseState.getAngularVelocity().x, - pauseState.getAngularVelocity().y, - pauseState.getAngularVelocity().z); - space.runtime().activateBody(space.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - resource.clearChunkBoundaryPauseState(bodyKey); - recordSafePose(bodyKey, snapshot, resource); - }); - } - - private void handlePausedBody(@Nonnull Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState, - @Nonnull EntityChunkBoundaryMode mode, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull Store entityStore, - @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { - long[] targetChunkIndices = pauseState.getTargetChunkIndices(); - if (mode == EntityChunkBoundaryMode.LOAD_TICKING_CHUNK) { - requestTickingChunks(chunkStore, targetChunkIndices); - } - - if (!areChunksTicking(targetChunkIndices, chunkStore, chunkComponentStore)) { - return; - } - - resumeBodyAuthoritative(entityStore, bodyRef, snapshot, pauseState, resource); - } - - private static void pauseBodyAuthoritative(@Nonnull Store entityStore, - @Nonnull Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull long[] targetChunkIndices, - @Nonnull PhysicsWorldRuntimeResource resource) { - ChunkBoundarySafeState safeState = resource.getChunkBoundarySafeState(bodyRef); - Vector3f safePosition = safeState != null ? new Vector3f(safeState.getPosition()) : null; - Quaternionf safeRotation = safeState != null - ? new Quaternionf(safeState.getRotation()) - : null; - resource.pauseChunkBoundaryBody(bodyRef, - primaryChunkIndex(targetChunkIndices, snapshot), - targetChunkIndices, - snapshot); - scheduleAuthoritativeMutation(entityStore, - "pause chunk-boundary PhysicsStore body", - physics -> applyPauseBody(physics, - bodyRef, - snapshot.bodyType(), - safePosition, - safeRotation)); - } - - private static void resumeBodyAuthoritative(@Nonnull Store entityStore, - @Nonnull Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState, - @Nonnull PhysicsWorldRuntimeResource resource) { - Vector3f linearVelocity = new Vector3f(pauseState.getLinearVelocity()); - Vector3f angularVelocity = new Vector3f(pauseState.getAngularVelocity()); - PhysicsBodyType originalBodyType = pauseState.getOriginalBodyType(); - scheduleAuthoritativeMutation(entityStore, - "resume chunk-boundary PhysicsStore body", - physics -> applyResumeBody(physics, - bodyRef, - originalBodyType, - linearVelocity, - angularVelocity)); - resource.clearChunkBoundaryPauseState(bodyRef); - recordSafePose(bodyRef, snapshot, resource); - } - - private static void applyPauseBody(@Nonnull Store store, - @Nonnull Ref bodyRef, - @Nonnull PhysicsBodyType originalBodyType, - @Nullable Vector3f safePosition, - @Nullable Quaternionf safeRotation) { - if (!isValidBodyRef(store, bodyRef)) { - return; - } - if (originalBodyType != PhysicsBodyType.KINEMATIC) { - appendBodyCommand(store, - bodyRef, - BodyCommandComponent.setType(PhysicsBodyType.KINEMATIC, false)); - } - store.putComponent(bodyRef, - TargetComponent.getComponentType(), - parkedTarget(safePosition, safeRotation)); - } - - private static void applyResumeBody(@Nonnull Store store, - @Nonnull Ref bodyRef, - @Nonnull PhysicsBodyType originalBodyType, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - if (!isValidBodyRef(store, bodyRef)) { - return; - } - appendBodyCommand(store, bodyRef, BodyCommandComponent.setType(originalBodyType, true)); - appendBodyCommand(store, - bodyRef, - BodyCommandComponent.setVelocity(linearVelocity, angularVelocity, true)); - store.removeComponent(bodyRef, TargetComponent.getComponentType()); - } - - @Nonnull - private static TargetComponent parkedTarget(@Nullable Vector3f safePosition, - @Nullable Quaternionf safeRotation) { - TargetComponent target = new TargetComponent(); - target.setActive(true); - target.setTransformEnabled(safePosition != null && safeRotation != null); - if (safePosition != null && safeRotation != null) { - target.setPosition(safePosition); - target.setRotation(safeRotation); - } - target.setLinearVelocity(new Vector3f()); - target.setAngularVelocity(new Vector3f()); - target.setVelocityEnabled(true); - target.setActivate(false); - return target; - } - - private static void appendBodyCommand(@Nonnull Store store, - @Nonnull Ref bodyRef, - @Nonnull BodyCommandComponent command) { - BodyCommandComponent existing = store.getComponent(bodyRef, - BodyCommandComponent.getComponentType()); - BodyCommandComponent merged = existing != null ? existing.append(command) : command; - store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); - } - - private static boolean isValidBodyRef(@Nonnull Store store, - @Nonnull Ref bodyRef) { - if (bodyRef.getStore() != store || !bodyRef.isValid()) { - return false; - } - BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); - return body != null && body.getKind() == PhysicsBodyKind.BODY; - } - - private static void scheduleAuthoritativeMutation(@Nonnull Store entityStore, - @Nonnull String operation, - @Nonnull Consumer> mutation) { - World world = entityStore.getExternalData().getWorld(); - PhysicsStoreThreading.executeOnWorldThread(world, operation, mutation) - .whenComplete((ignored, failure) -> { - if (failure != null) { - LOGGER.at(Level.WARNING).log("PhysicsStore chunk-boundary mutation failed " - + "(%s): %s", operation, failure.getMessage()); - } - }); - } - - private static boolean isAuthoritativePhysicsStoreActive() { - return PhysicsStoreEarlyPluginProbe.isAvailable(); - } - - static void pauseBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - long targetChunkIndex, - @Nonnull PhysicsWorldRuntimeResource resource) { - pauseBody(bodyKey, snapshot, new long[] {targetChunkIndex}, resource); - } - - static void pauseBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull long[] targetChunkIndices, - @Nonnull PhysicsWorldRuntimeResource resource) { - var registration = resource.getRegistration(bodyKey); - if (registration == null) { - return; - } - PhysicsSpaceBinding space = resource.getSpaceBinding(registration.spaceId()); - if (space == null) { - return; - } - ChunkBoundarySafeState safeState = - resource.getChunkBoundarySafeState(bodyKey); - resource.pauseChunkBoundaryBody(bodyKey, - primaryChunkIndex(targetChunkIndices, snapshot), - targetChunkIndices, - snapshot); - - if (safeState != null) { - space.runtime().setBodyTransform(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - safeState.getPosition().x, - safeState.getPosition().y, - safeState.getPosition().z, - safeState.getRotation().x, - safeState.getRotation().y, - safeState.getRotation().z, - safeState.getRotation().w); - } - - if (snapshot.bodyType() != PhysicsBodyType.KINEMATIC) { - space.runtime().setBodyType(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.KINEMATIC)); - } - space.runtime().setBodyVelocity(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f); - } - - static void recordSafePose(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsWorldRuntimeResource resource) { - resource.updateChunkBoundarySafeState(bodyKey, snapshot); - } - - static void recordSafePose(@Nonnull Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsWorldRuntimeResource resource) { - resource.updateChunkBoundarySafeState(bodyRef, snapshot); - } - - private void requestTickingChunk(@Nonnull ChunkStore chunkStore, long chunkIndex) { - if (!requestedChunkIndices.add(chunkIndex)) { - return; - } - chunkStore.getChunkReferenceAsync(chunkIndex, TICKING_CHUNK_REQUEST_FLAGS); - } - - private void requestTickingChunks(@Nonnull ChunkStore chunkStore, - @Nonnull long[] chunkIndices) { - for (long chunkIndex : chunkIndices) { - requestTickingChunk(chunkStore, chunkIndex); - } - } - - private boolean areChunksTicking(@Nonnull long[] chunkIndices, - @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { - for (long chunkIndex : chunkIndices) { - if (!isChunkTicking(chunkIndex, chunkStore, chunkComponentStore)) { - return false; - } - } - return true; - } - - private boolean isChunkTicking(long chunkIndex, - @Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore) { - var chunkRef = chunkStore.getChunkReference(chunkIndex); - if (chunkRef == null || !chunkRef.isValid()) { - return false; - } - - WorldChunk worldChunk = chunkComponentStore.getComponentConcurrent(chunkRef, - WorldChunk.getComponentType()); - return worldChunk != null && worldChunk.is(ChunkFlag.TICKING); - } - - private static long primaryChunkIndex(@Nonnull long[] chunkIndices, - @Nonnull PhysicsBodySnapshot snapshot) { - return chunkIndices.length > 0 - ? chunkIndices[0] - : chunkIndex(snapshot.positionX(), snapshot.positionZ()); - } - - static long[] chunkIndices(@Nonnull PhysicsBodySnapshot snapshot) { - Vector2d extents = horizontalHalfExtents(snapshot); - int minChunkX = chunkCoordinate(snapshot.positionX() - extents.x); - int maxChunkX = chunkCoordinate(snapshot.positionX() + extents.x); - int minChunkZ = chunkCoordinate(snapshot.positionZ() - extents.y); - int maxChunkZ = chunkCoordinate(snapshot.positionZ() + extents.y); - int count = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); - long[] chunks = new long[count]; - int index = 0; - for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { - for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - chunks[index++] = ChunkUtil.indexChunk(chunkX, chunkZ); - } - } - return chunks; - } - - @Nonnull - private static Vector2d horizontalHalfExtents(@Nonnull PhysicsBodySnapshot snapshot) { - return switch (snapshot.shapeType()) { - case BOX -> boxHorizontalHalfExtents(snapshot); - case SPHERE -> roundHorizontalHalfExtents(snapshot.sphereRadius()); - case CAPSULE -> roundHeightHorizontalHalfExtents(snapshot, - finitePositive(snapshot.sphereRadius()), - finitePositive(snapshot.halfHeight()) + finitePositive(snapshot.sphereRadius())); - case CYLINDER, CONE -> roundHeightHorizontalHalfExtents(snapshot, - finitePositive(snapshot.sphereRadius()), - finitePositive(snapshot.halfHeight())); - case PLANE, VOXELS, UNKNOWN -> new Vector2d(); - }; - } - - @Nonnull - private static Vector2d boxHorizontalHalfExtents(@Nonnull PhysicsBodySnapshot snapshot) { - if (!snapshot.hasBoxHalfExtents()) { - return new Vector2d(); - } - return rotatedHorizontalHalfExtents(snapshot, - finitePositive(snapshot.boxHalfExtentX()), - finitePositive(snapshot.boxHalfExtentY()), - finitePositive(snapshot.boxHalfExtentZ())); - } - - @Nonnull - private static Vector2d roundHorizontalHalfExtents(float radius) { - double halfExtent = finitePositive(radius); - return new Vector2d(halfExtent, halfExtent); - } - - @Nonnull - private static Vector2d roundHeightHorizontalHalfExtents(@Nonnull PhysicsBodySnapshot snapshot, - double radius, - double axisHalfExtent) { - double halfX = radius; - double halfY = radius; - double halfZ = radius; - switch (snapshot.shapeAxis()) { - case X -> halfX = axisHalfExtent; - case Y -> halfY = axisHalfExtent; - case Z -> halfZ = axisHalfExtent; - } - return rotatedHorizontalHalfExtents(snapshot, halfX, halfY, halfZ); - } - - @Nonnull - private static Vector2d rotatedHorizontalHalfExtents(@Nonnull PhysicsBodySnapshot snapshot, - double halfX, - double halfY, - double halfZ) { - double x = snapshot.rotationX(); - double y = snapshot.rotationY(); - double z = snapshot.rotationZ(); - double w = snapshot.rotationW(); - double lengthSquared = x * x + y * y + z * z + w * w; - if (!Double.isFinite(lengthSquared) || lengthSquared <= 0.0) { - return new Vector2d(halfX, halfZ); - } - - double scale = 2.0 / lengthSquared; - double xs = x * scale; - double ys = y * scale; - double zs = z * scale; - double wx = w * xs; - double wy = w * ys; - double wz = w * zs; - double xx = x * xs; - double xy = x * ys; - double xz = x * zs; - double yy = y * ys; - double yz = y * zs; - double zz = z * zs; - double m00 = 1.0 - (yy + zz); - double m01 = xy - wz; - double m02 = xz + wy; - double m20 = xz - wy; - double m21 = yz + wx; - double m22 = 1.0 - (xx + yy); - return new Vector2d(Math.abs(m00) * halfX + Math.abs(m01) * halfY + Math.abs(m02) * halfZ, - Math.abs(m20) * halfX + Math.abs(m21) * halfY + Math.abs(m22) * halfZ); - } - - private static double finitePositive(float value) { - return Float.isFinite(value) && value > 0.0f ? value : 0.0; - } - - private static int chunkCoordinate(double coordinate) { - return MathUtil.floor(coordinate) >> ChunkUtil.BITS; - } - - private static long chunkIndex(double x, double z) { - int chunkX = MathUtil.floor(x) >> ChunkUtil.BITS; - int chunkZ = MathUtil.floor(z) >> ChunkUtil.BITS; - return ChunkUtil.indexChunk(chunkX, chunkZ); - } - - @Nonnull - @Override - public Set> getDependencies() { - return dependencies; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java deleted file mode 100644 index 9f1b5be2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystem.java +++ /dev/null @@ -1,748 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; -import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.UUID; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Applies an opt-in distance collision LOD for default Impulse dynamic-body filters. - */ -public class PhysicsCollisionLodSystem extends TickingSystem { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PhysicsSnapshotPublicationSystem.class) - ); - - @Nonnull - private final Map, CollisionLodState> statesByStore = - Collections.synchronizedMap(new WeakHashMap<>()); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - if (!WorldCollisionLifecycle.isEnabled()) { - return; - } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - CollisionLodState state = stateFor(store); - state.refreshPendingMutation(); - if (state.hasPendingMutation()) { - return; - } - - long tick = state.nextTick(); - List updates = collectUpdates(store, resource, state, tick); - if (updates.isEmpty()) { - return; - } - - PhysicsMutationHandle handle = isAuthoritativePhysicsStoreActive() - ? applyAuthoritativeUpdatesAsync(store, updates) - : PhysicsOwnerBridge.runAsync(store, - "apply collision LOD filters", - () -> applyUpdates(resource, updates)); - state.trackPendingMutation(handle, updates); - } - - @Nonnull - private List collectUpdates(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull CollisionLodState state, - long tick) { - List updates = new ArrayList<>(); - IntOpenHashSet activeSpaces = new IntOpenHashSet(); - List interests = - VisualInterestCollector.collectMaterializationInterests(store, resource); - if (isAuthoritativePhysicsStoreActive()) { - Store physics = - ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() - .getStore(); - PhysicsStoreThreading.requireWorldThread(physics, - "collect collision LOD PhysicsStore bodies"); - PhysicsBodyRegistrationResource registrations = - physics.getResource(PhysicsBodyRegistrationResource.getResourceType()); - for (SpaceId spaceId : resource.getSpaceIds()) { - activeSpaces.add(spaceId.value()); - PhysicsSpaceSettings settings = resource.getSpaceSettings(spaceId); - if (!settings.getCollisionLodSettings().isCollisionLodEnabled()) { - state.collectRestoreRefUpdates(spaceId, updates); - continue; - } - if (!state.shouldRefresh(spaceId, - settings.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks(), - tick)) { - continue; - } - collectAuthoritativeSpaceUpdates(physics, - registrations, - resource, - spaceId, - settings, - interests, - state, - updates); - } - state.pruneRemovedSpaces(activeSpaces); - return updates; - } - for (PhysicsSpaceBinding space : resource.getSpaceBindings()) { - SpaceId spaceId = space.spaceId(); - activeSpaces.add(spaceId.value()); - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(spaceId); - if (!settings.getCollisionLodSettings().isCollisionLodEnabled()) { - state.collectRestoreUpdates(spaceId, updates); - continue; - } - if (!state.shouldRefresh(spaceId, - settings.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks(), - tick)) { - continue; - } - collectSpaceUpdates(resource, spaceId, settings, interests, state, updates); - } - state.pruneRemovedSpaces(activeSpaces); - return updates; - } - - private static void collectSpaceUpdates(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - @Nonnull CollisionLodState state, - @Nonnull List updates) { - ObjectOpenHashSet seenBodies = new ObjectOpenHashSet<>(); - resource.forEachIndexedBodySnapshot(spaceId, (bodyKey, snapshot, _, kind, persistenceMode) -> { - seenBodies.add(bodyKey); - if (persistenceMode == PhysicsBodyPersistenceMode.PERSISTENT) { - state.recordRestore(spaceId, bodyKey, updates); - return; - } - if (!isCollisionLodCandidate(snapshot, kind, persistenceMode)) { - return; - } - - CollisionLodTier previousTier = state.tier(bodyKey); - CollisionLodTier tier = resource.isBodyControlled(bodyKey) - ? CollisionLodTier.NEAR_FULL - : resolveTier(settings, - previousTier, - snapshot.positionX(), - snapshot.positionY(), - snapshot.positionZ(), - interests); - state.recordTier(spaceId, - bodyKey, - tier, - settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled(), - updates); - }); - state.pruneMissingBodies(spaceId, seenBodies); - } - - private static void collectAuthoritativeSpaceUpdates(@Nonnull Store physics, - @Nonnull PhysicsBodyRegistrationResource registrations, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - @Nonnull CollisionLodState state, - @Nonnull List updates) { - IntOpenHashSet seenRows = new IntOpenHashSet(); - for (PhysicsStoreBodySnapshot body : physics.getResource(PhysicsSnapshotResource.getResourceType()) - .getLatestFrame() - .bodies()) { - Ref bodyRef = body.bodyRef(); - if (bodyRef == null || !bodyRef.isValid()) { - continue; - } - PhysicsBodyRegistrationView registration = registrations.getBodyRegistrationView(bodyRef); - if (registration == null || !registration.spaceId().equals(spaceId)) { - continue; - } - seenRows.add(bodyRef.getIndex()); - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyRef); - if (snapshot == null) { - continue; - } - if (registration.persistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) { - state.recordRestore(spaceId, bodyRef, updates); - continue; - } - if (!isCollisionLodCandidate(snapshot, - registration.kind(), - registration.persistenceMode())) { - continue; - } - - CollisionLodTier previousTier = state.tier(bodyRef); - CollisionLodTier tier = resource.isBodyControlled(bodyRef) - ? CollisionLodTier.NEAR_FULL - : resolveTier(settings, - previousTier, - snapshot.positionX(), - snapshot.positionY(), - snapshot.positionZ(), - interests); - state.recordTier(spaceId, - bodyRef, - tier, - settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled(), - updates); - } - state.pruneMissingBodyRefs(spaceId, seenRows); - } - - static CollisionLodTier resolveTier(@Nonnull PhysicsSpaceSettings settings, - @Nullable CollisionLodTier previousTier, - @Nonnull Vector3f position, - @Nonnull List interests) { - return resolveTier(settings, previousTier, position.x, position.y, position.z, interests); - } - - static CollisionLodTier resolveTier(@Nonnull PhysicsSpaceSettings settings, - @Nullable CollisionLodTier previousTier, - float positionX, - float positionY, - float positionZ, - @Nonnull List interests) { - float distanceSquared = nearestDistanceSquared(positionX, positionY, positionZ, interests); - if (distanceSquared == Float.POSITIVE_INFINITY) { - return CollisionLodTier.FAR_SLEEPING; - } - - int nearRadius = settings.getCollisionLodSettings().getCollisionLodNearRadius(); - int midRadius = settings.getCollisionLodSettings().getCollisionLodMidRadius(); - int hysteresis = settings.getCollisionLodSettings().getCollisionLodHysteresis(); - if (previousTier == CollisionLodTier.NEAR_FULL) { - return distanceSquared <= squared(nearRadius + hysteresis) - ? CollisionLodTier.NEAR_FULL - : resolveTierWithoutHysteresis(distanceSquared, nearRadius, midRadius); - } - if (previousTier == CollisionLodTier.MID_TERRAIN) { - if (distanceSquared <= squared(nearRadius)) { - return CollisionLodTier.NEAR_FULL; - } - return distanceSquared <= squared(midRadius + hysteresis) - ? CollisionLodTier.MID_TERRAIN - : CollisionLodTier.FAR_SLEEPING; - } - return resolveTierWithoutHysteresis(distanceSquared, nearRadius, midRadius); - } - - @Nonnull - private static CollisionLodTier resolveTierWithoutHysteresis(float distanceSquared, - int nearRadius, - int midRadius) { - if (distanceSquared <= squared(nearRadius)) { - return CollisionLodTier.NEAR_FULL; - } - if (distanceSquared <= squared(midRadius)) { - return CollisionLodTier.MID_TERRAIN; - } - return CollisionLodTier.FAR_SLEEPING; - } - - private static float nearestDistanceSquared(float positionX, - float positionY, - float positionZ, - @Nonnull List interests) { - float nearest = Float.POSITIVE_INFINITY; - for (PhysicsVisualRuntime.VisualInterest interest : interests) { - Vector3f interestPosition = interest.position(); - float dx = positionX - interestPosition.x; - float dy = positionY - interestPosition.y; - float dz = positionZ - interestPosition.z; - nearest = Math.min(nearest, dx * dx + dy * dy + dz * dz); - } - return nearest; - } - - private static int squared(int value) { - return value * value; - } - - static boolean isCollisionLodCandidate(@Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return persistenceMode != PhysicsBodyPersistenceMode.PERSISTENT - && kind == PhysicsBodyKind.BODY - && snapshot.isDynamic() - && !snapshot.sensor(); - } - - @Nonnull - private static PhysicsMutationHandle applyAuthoritativeUpdatesAsync( - @Nonnull Store store, - @Nonnull List updates) { - World world = store.getExternalData().getWorld(); - return PhysicsMutationHandle.fromCompletion("apply collision LOD filters", - null, - PhysicsStoreThreading.executeOnWorldThread(world, - "apply collision LOD filters", - physics -> applyAuthoritativeUpdates(physics, updates))); - } - - private static void applyAuthoritativeUpdates(@Nonnull Store store, - @Nonnull List updates) { - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - for (CollisionLodUpdate update : updates) { - UUID spaceUuid = compatibility.getSpaceUuid(update.spaceId()); - if (spaceUuid == null) { - continue; - } - Ref bodyRef = update.bodyRef(); - if (bodyRef == null - || bodyRef.getStore() != store - || !bodyRef.isValid()) { - continue; - } - BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); - if (body == null - || !spaceUuid.equals(body.getSpaceUuid()) - || body.getKind() != PhysicsBodyKind.BODY - || (update.trackTier() - && body.getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT)) { - continue; - } - appendBodyCommand(store, bodyRef, collisionFilterCommand(update)); - if (update.tier() == CollisionLodTier.FAR_SLEEPING && update.farSleepEnabled()) { - appendBodyCommand(store, bodyRef, BodyCommandComponent.sleep()); - } - } - } - - @Nonnull - private static BodyCommandComponent collisionFilterCommand(@Nonnull CollisionLodUpdate update) { - int terrainOnlyMask = PhysicsCollisionFilters.TERRAIN; - int fullDynamicMask = PhysicsCollisionFilters.TERRAIN - | PhysicsCollisionFilters.DYNAMIC_BODY; - int mask = update.tier() == CollisionLodTier.NEAR_FULL - ? fullDynamicMask - : terrainOnlyMask; - return BodyCommandComponent.setCollisionFilter(PhysicsCollisionFilters.DYNAMIC_BODY, - mask, - update.tier() != CollisionLodTier.FAR_SLEEPING); - } - - private static void appendBodyCommand(@Nonnull Store store, - @Nonnull Ref bodyRef, - @Nonnull BodyCommandComponent command) { - BodyCommandComponent existing = store.getComponent(bodyRef, - BodyCommandComponent.getComponentType()); - BodyCommandComponent merged = existing != null ? existing.append(command) : command; - store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); - } - - private static void applyUpdates(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull List updates) { - for (CollisionLodUpdate update : updates) { - PhysicsBodyRegistration registration = - resource.getRegistration(update.bodyKey()); - if (registration == null - || !registration.spaceId().equals(update.spaceId()) - || registration.kind() != PhysicsBodyKind.BODY - || (update.trackTier() - && registration.persistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT)) { - continue; - } - PhysicsSpaceBinding space = resource.getSpaceBinding(registration.spaceId()); - if (space == null) { - continue; - } - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, registration.backendBodyHandle().value()); - if (snapshot == null || !snapshot.isDynamic() || snapshot.sensor()) { - continue; - } - applyTier(space, - registration.backendBodyHandle().value(), - update.tier(), - update.farSleepEnabled()); - } - } - - private static boolean isAuthoritativePhysicsStoreActive() { - return PhysicsStoreEarlyPluginProbe.isAvailable(); - } - - private static void applyTier(@Nonnull PhysicsSpaceBinding space, - long backendBodyId, - @Nonnull CollisionLodTier tier, - boolean farSleepEnabled) { - int terrainOnlyMask = PhysicsCollisionFilters.TERRAIN; - int fullDynamicMask = PhysicsCollisionFilters.TERRAIN - | PhysicsCollisionFilters.DYNAMIC_BODY; - switch (tier) { - case NEAR_FULL -> { - space.runtime().setBodyCollisionFilter(space.backendSpaceHandle().value(), - backendBodyId, - PhysicsCollisionFilters.DYNAMIC_BODY, - fullDynamicMask); - space.runtime().activateBody(space.backendSpaceHandle().value(), backendBodyId); - } - case MID_TERRAIN -> { - space.runtime().setBodyCollisionFilter(space.backendSpaceHandle().value(), - backendBodyId, - PhysicsCollisionFilters.DYNAMIC_BODY, - terrainOnlyMask); - space.runtime().activateBody(space.backendSpaceHandle().value(), backendBodyId); - } - case FAR_SLEEPING -> { - space.runtime().setBodyCollisionFilter(space.backendSpaceHandle().value(), - backendBodyId, - PhysicsCollisionFilters.DYNAMIC_BODY, - terrainOnlyMask); - if (farSleepEnabled) { - space.runtime().sleepBody(space.backendSpaceHandle().value(), backendBodyId); - } - } - } - } - - @Nonnull - private CollisionLodState stateFor(@Nonnull Store store) { - synchronized (statesByStore) { - return statesByStore.computeIfAbsent(store, _ -> new CollisionLodState()); - } - } - - enum CollisionLodTier { - NEAR_FULL, - MID_TERRAIN, - FAR_SLEEPING - } - - record CollisionLodUpdate(@Nonnull SpaceId spaceId, - @Nullable RigidBodyKey bodyKey, - @Nullable Ref bodyRef, - @Nonnull CollisionLodTier tier, - boolean farSleepEnabled, - boolean trackTier) { - - CollisionLodUpdate(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey, - @Nonnull CollisionLodTier tier, - boolean farSleepEnabled, - boolean trackTier) { - this(spaceId, bodyKey, null, tier, farSleepEnabled, trackTier); - } - - CollisionLodUpdate(@Nonnull SpaceId spaceId, - @Nonnull Ref bodyRef, - @Nonnull CollisionLodTier tier, - boolean farSleepEnabled, - boolean trackTier) { - this(spaceId, null, bodyRef, tier, farSleepEnabled, trackTier); - } - - CollisionLodUpdate { - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(tier, "tier"); - if ((bodyKey == null) == (bodyRef == null)) { - throw new IllegalArgumentException("Exactly one body identity is required"); - } - } - } - - private record BodyTier(@Nonnull SpaceId spaceId, @Nonnull CollisionLodTier tier) { - } - - static final class CollisionLodState { - - @Nonnull - private final Object2ObjectMap tiers = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectOpenHashMap refTiersByRowIndex = - new Int2ObjectOpenHashMap<>(); - @Nonnull - private final Int2LongOpenHashMap nextRefreshTicks = new Int2LongOpenHashMap(); - @Nonnull - private final Queue pendingUpdates = new ArrayDeque<>(); - @Nullable - private PhysicsMutationHandle pendingHandle; - private long tick; - - CollisionLodState() { - nextRefreshTicks.defaultReturnValue(0L); - } - - private long nextTick() { - return ++tick; - } - - boolean hasPendingMutation() { - return pendingHandle != null; - } - - void refreshPendingMutation() { - if (pendingHandle == null || !pendingHandle.isDone()) { - return; - } - Throwable failure = pendingHandle.failure(); - if (failure != null) { - LOGGER.at(Level.WARNING) - .log("Collision LOD owner mutation failed: %s", failure.getMessage()); - clearPendingRefreshes(); - } else { - commitPendingUpdates(); - } - pendingUpdates.clear(); - pendingHandle = null; - } - - void trackPendingMutation(@Nonnull PhysicsMutationHandle handle, - @Nonnull List updates) { - pendingHandle = handle; - pendingUpdates.addAll(updates); - } - - boolean shouldRefresh(@Nonnull SpaceId spaceId, int interval, long tick) { - long nextRefreshTick = nextRefreshTicks.get(spaceId.value()); - if (tick < nextRefreshTick) { - return false; - } - nextRefreshTicks.put(spaceId.value(), tick + interval); - return true; - } - - @Nullable - CollisionLodTier tier(@Nonnull RigidBodyKey bodyKey) { - BodyTier bodyTier = tiers.get(bodyKey); - return bodyTier != null ? bodyTier.tier() : null; - } - - @Nullable - CollisionLodTier tier(@Nonnull Ref bodyRef) { - RefBodyTier bodyTier = refTiersByRowIndex.get(rowIndex(bodyRef)); - return bodyTier != null && sameRef(bodyTier.bodyRef(), bodyRef) - ? bodyTier.tier().tier() - : null; - } - - void recordTier(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey, - @Nonnull CollisionLodTier tier, - boolean farSleepEnabled, - @Nonnull List updates) { - BodyTier previous = tiers.get(bodyKey); - if (previous != null && previous.spaceId().equals(spaceId) && previous.tier() == tier) { - return; - } - updates.add(new CollisionLodUpdate(spaceId, bodyKey, tier, farSleepEnabled, true)); - } - - void recordTier(@Nonnull SpaceId spaceId, - @Nonnull Ref bodyRef, - @Nonnull CollisionLodTier tier, - boolean farSleepEnabled, - @Nonnull List updates) { - RefBodyTier previous = refTiersByRowIndex.get(rowIndex(bodyRef)); - if (previous != null - && sameRef(previous.bodyRef(), bodyRef) - && previous.tier().spaceId().equals(spaceId) - && previous.tier().tier() == tier) { - return; - } - updates.add(new CollisionLodUpdate(spaceId, bodyRef, tier, farSleepEnabled, true)); - } - - void recordRestore(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey, - @Nonnull List updates) { - BodyTier previous = tiers.get(bodyKey); - if (previous == null || !previous.spaceId().equals(spaceId)) { - return; - } - updates.add(new CollisionLodUpdate(spaceId, - bodyKey, - CollisionLodTier.NEAR_FULL, - false, - false)); - } - - void recordRestore(@Nonnull SpaceId spaceId, - @Nonnull Ref bodyRef, - @Nonnull List updates) { - RefBodyTier previous = refTiersByRowIndex.get(rowIndex(bodyRef)); - if (previous == null - || !sameRef(previous.bodyRef(), bodyRef) - || !previous.tier().spaceId().equals(spaceId)) { - return; - } - updates.add(new CollisionLodUpdate(spaceId, - bodyRef, - CollisionLodTier.NEAR_FULL, - false, - false)); - } - - private void collectRestoreUpdates(@Nonnull SpaceId spaceId, - @Nonnull List updates) { - for (Object2ObjectMap.Entry entry - : tiers.object2ObjectEntrySet()) { - if (!entry.getValue().spaceId().equals(spaceId)) { - continue; - } - updates.add(new CollisionLodUpdate(spaceId, - entry.getKey(), - CollisionLodTier.NEAR_FULL, - false, - false)); - } - nextRefreshTicks.remove(spaceId.value()); - } - - private void collectRestoreRefUpdates(@Nonnull SpaceId spaceId, - @Nonnull List updates) { - for (Int2ObjectMap.Entry entry - : refTiersByRowIndex.int2ObjectEntrySet()) { - if (!entry.getValue().tier().spaceId().equals(spaceId)) { - continue; - } - Ref bodyRef = entry.getValue().bodyRef(); - if (bodyRef == null || !bodyRef.isValid()) { - continue; - } - updates.add(new CollisionLodUpdate(spaceId, - bodyRef, - CollisionLodTier.NEAR_FULL, - false, - false)); - } - nextRefreshTicks.remove(spaceId.value()); - } - - private void commitPendingUpdates() { - for (CollisionLodUpdate update : pendingUpdates) { - if (update.bodyRef() != null) { - int rowIndex = rowIndex(update.bodyRef()); - if (update.trackTier()) { - refTiersByRowIndex.put(rowIndex, - new RefBodyTier(update.bodyRef(), - new BodyTier(update.spaceId(), update.tier()))); - } else { - RefBodyTier previous = refTiersByRowIndex.get(rowIndex); - if (previous != null && sameRef(previous.bodyRef(), update.bodyRef())) { - refTiersByRowIndex.remove(rowIndex); - } - } - } else if (update.trackTier()) { - tiers.put(update.bodyKey(), new BodyTier(update.spaceId(), update.tier())); - } else { - tiers.remove(update.bodyKey()); - } - } - } - - private void clearPendingRefreshes() { - for (CollisionLodUpdate update : pendingUpdates) { - nextRefreshTicks.remove(update.spaceId().value()); - } - } - - private void pruneMissingBodies(@Nonnull SpaceId spaceId, - @Nonnull ObjectOpenHashSet seenBodies) { - tiers.object2ObjectEntrySet() - .removeIf(entry -> entry.getValue().spaceId().equals(spaceId) - && !seenBodies.contains(entry.getKey())); - } - - private void pruneMissingBodyRefs(@Nonnull SpaceId spaceId, - @Nonnull IntOpenHashSet seenRows) { - refTiersByRowIndex.int2ObjectEntrySet() - .removeIf(entry -> entry.getValue().tier().spaceId().equals(spaceId) - && (!seenRows.contains(entry.getIntKey()) - || !entry.getValue().bodyRef().isValid())); - } - - private void pruneRemovedSpaces(@Nonnull IntOpenHashSet activeSpaces) { - tiers.object2ObjectEntrySet() - .removeIf(entry -> !activeSpaces.contains(entry.getValue().spaceId().value())); - refTiersByRowIndex.int2ObjectEntrySet() - .removeIf(entry -> !activeSpaces.contains(entry.getValue().tier().spaceId().value()) - || !entry.getValue().bodyRef().isValid()); - nextRefreshTicks.keySet().removeIf(spaceValue -> !activeSpaces.contains(spaceValue)); - } - - private static int rowIndex(@Nonnull Ref bodyRef) { - return Objects.requireNonNull(bodyRef, "bodyRef").getIndex(); - } - - private static boolean sameRef(@Nonnull Ref first, - @Nonnull Ref second) { - return first.getIndex() == second.getIndex() - && first.getStore() == second.getStore(); - } - } - - private record RefBodyTier(@Nonnull Ref bodyRef, - @Nonnull BodyTier tier) { - - private RefBodyTier { - Objects.requireNonNull(bodyRef, "bodyRef"); - Objects.requireNonNull(tier, "tier"); - } - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystem.java deleted file mode 100644 index 1a3cb0a4..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystem.java +++ /dev/null @@ -1,549 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; - -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.QuerySystem; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionStreamingBounds; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.SectionAccessCache; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.TargetRefreshDecision; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3d; - -/** - * Streams world voxel collision around online players and dynamic physics bodies - * once per entity-store tick. - * - *

Players stream at the configured world collision radius. - * Dynamic physics bodies stream at a smaller radius so they do not - * aggressively pull collision into unpopulated areas, but still - * have terrain to interact with after rolling away from players.

- */ -public class PhysicsWorldCollisionStreamingSystem extends TickingSystem - implements QuerySystem { - - @Nullable - private static volatile ComponentType playerType; - @Nullable - private static volatile ComponentType transformType; - @Nullable - private static volatile Query query; - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PhysicsSnapshotPublicationSystem.class) - ); - - /** - * Radius (in blocks) used for streaming around dynamic physics bodies. - * Smaller than the player radius because bodies should not pull collision - * as far as players do, but they still need terrain to land on. - */ - public static final int DEFAULT_BODY_STREAMING_RADIUS = 4; - - @Nonnull - private final Map, StreamingState> statesByStore = - Collections.synchronizedMap(new WeakHashMap<>()); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - if (!WorldCollisionLifecycle.isEnabled()) { - return; - } - WorldCollisionProfilingResource profiling = store.getResource( - WorldCollisionProfilingResource.getResourceType()); - Snapshot snapshot = null; - long tickStart = 0L; - try { - World world = store.getExternalData().getWorld(); - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - WorldVoxelCollisionCache cache = resource.worldCollisionCache(); - if (cache.isStreamingApplyPending()) { - recordSkippedTerrainApply(profiling); - return; - } - - snapshot = profiling.isEnabled() ? profiling.beginTick() : null; - tickStart = snapshot != null ? System.nanoTime() : 0L; - List playerPositions = new ArrayList<>(); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> collectPlayerPositions(chunk, playerPositions); - store.forEachChunk(systemIndex, collector); - List streamingPlayerPositions = List.copyOf(playerPositions); - - if (snapshot != null) { - snapshot.setPlayerStreamingTargets(streamingPlayerPositions.size()); - } - - long currentTick = stateFor(store).nextTick(); - List plans = collectStreamingPlans(resource, - cache, - streamingPlayerPositions, - currentTick, - snapshot); - if (plans.isEmpty()) { - return; - } - if (!cache.tryBeginStreamingApply()) { - if (snapshot != null) { - snapshot.incrementTerrainApplySkippedPending(); - } - return; - } - Snapshot applySnapshot = snapshot; - long queuedStart = tickStart; - if (tryQueueStreamingApply(cache, profiling, applySnapshot, queuedStart, applyFinished -> - resource.enqueueOwnerMutation("stream world collision terrain apply", () -> { - long applyStart = applySnapshot != null ? System.nanoTime() : 0L; - try { - SectionAccessCache sectionAccessCache = cache.newSectionAccessCache(); - for (SpaceStreamingPlan plan : plans) { - PhysicsSpaceBinding space = resource.getSpaceBinding(plan.spaceId()); - if (space == null) { - continue; - } - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(plan.spaceId()); - PhysicsWorldCollisionSettings collisionSettings = - settings.getWorldCollisionSettings(); - if (!WorldCollisionLifecycle.isEnabled() - || WorldCollisionLifecycle.generation() != plan.lifecycleGeneration() - || collisionSettings.getWorldCollisionMode() != WorldCollisionMode.STREAMING - || resource.worldCollisionStreamingRevision(plan.spaceId()) != plan.settingsRevision()) { - continue; - } - applySpaceCollision(world, - cache, - sectionAccessCache, - space, - plan, - collisionSettings, - currentTick, - applySnapshot); - } - } finally { - finishQueuedStreamingApply(cache, - profiling, - applySnapshot, - applyStart, - applyFinished); - } - }))) { - snapshot = null; - } - } finally { - if (snapshot != null) { - snapshot.setTickNanos(System.nanoTime() - tickStart); - profiling.finishTick(snapshot); - } - } - } - - static boolean tryQueueStreamingApply(@Nonnull WorldVoxelCollisionCache cache, - @Nonnull WorldCollisionProfilingResource profiling, - @Nullable Snapshot snapshot, - long queuedStartNanos, - @Nonnull StreamingApplySubmission submission) { - AtomicBoolean applyFinished = new AtomicBoolean(); - try { - PhysicsMutationHandle handle = submission.submit(applyFinished); - if (isRejectedExecutionFailure(handle.failure())) { - finishRejectedStreamingApply(cache, snapshot); - return false; - } - if (snapshot != null) { - snapshot.incrementTerrainApplyQueued(); - } - handle.completion().exceptionally(failure -> { - finishQueuedStreamingApply(cache, - profiling, - snapshot, - queuedStartNanos, - applyFinished); - return null; - }); - return true; - } catch (RejectedExecutionException exception) { - finishRejectedStreamingApply(cache, snapshot); - return false; - } - } - - static void finishQueuedStreamingApply(@Nonnull WorldVoxelCollisionCache cache, - @Nonnull WorldCollisionProfilingResource profiling, - @Nullable Snapshot snapshot, - long startNanos, - @Nonnull AtomicBoolean finished) { - if (!finished.compareAndSet(false, true)) { - return; - } - if (snapshot != null) { - snapshot.setTickNanos(System.nanoTime() - startNanos); - profiling.finishTick(snapshot); - } - cache.finishStreamingApply(); - } - - static void finishRejectedStreamingApply(@Nonnull WorldVoxelCollisionCache cache, - @Nullable Snapshot snapshot) { - cache.finishStreamingApply(); - if (snapshot != null) { - snapshot.incrementTerrainApplySkippedPending(); - } - } - - @FunctionalInterface - interface StreamingApplySubmission { - - @Nonnull - PhysicsMutationHandle submit(@Nonnull AtomicBoolean applyFinished); - } - - private static boolean isRejectedExecutionFailure(@Nullable Throwable failure) { - Throwable current = failure; - while (current != null) { - if (current instanceof RejectedExecutionException) { - return true; - } - current = current.getCause(); - } - return false; - } - - private static void recordSkippedTerrainApply( - @Nonnull WorldCollisionProfilingResource profiling) { - if (!profiling.isEnabled()) { - return; - } - Snapshot snapshot = profiling.beginTick(); - snapshot.incrementTerrainApplySkippedPending(); - profiling.finishTick(snapshot); - } - - @Nonnull - private List collectStreamingPlans( - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull List playerPositions, - long currentTick, - @Nullable Snapshot snapshot) { - List plans = new ArrayList<>(); - if (!WorldCollisionLifecycle.isEnabled()) { - return plans; - } - long lifecycleGeneration = WorldCollisionLifecycle.generation(); - for (SpaceId spaceId : resource.getSpaceIds()) { - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(spaceId); - if (settings.getWorldCollisionSettings().getWorldCollisionMode() != WorldCollisionMode.STREAMING) { - continue; - } - - if (snapshot != null) { - snapshot.incrementStreamingSpaces(); - } - - int bodyRadius = settings.getWorldCollisionSettings().getWorldCollisionBodyRadius(); - plans.add(new SpaceStreamingPlan(spaceId, - resource.worldCollisionStreamingRevision(spaceId), - lifecycleGeneration, - playerPositions, - collectDynamicBodyTargets(resource, - cache, - spaceId, - bodyRadius, - currentTick, - settings.getWorldCollisionSettings().getWorldCollisionTtlTicks(), - snapshot))); - } - return plans; - } - - private void applySpaceCollision(@Nonnull World world, - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull SectionAccessCache sectionAccessCache, - @Nonnull PhysicsSpaceBinding space, - @Nonnull SpaceStreamingPlan plan, - @Nonnull PhysicsWorldCollisionSettings settings, - long currentTick, - @Nullable Snapshot snapshot) { - LongSet visitedSections = new LongOpenHashSet(); - WorldCollisionBuildOptions buildOptions = WorldCollisionBuildOptions.fromSettings(settings); - for (Vector3d position : plan.playerPositions()) { - int sectionsBefore = visitedSections.size(); - cache.ensureAround(world, - space, - position, - settings.getWorldCollisionRadius(), - currentTick, - snapshot, - visitedSections, - snapshot != null ? StreamingTargetDiagnostic.player(position) : null, - sectionAccessCache, - buildOptions); - if (snapshot != null) { - snapshot.addPlayerSectionTargets(visitedSections.size() - sectionsBefore); - } - } - for (BodyStreamingTarget target : plan.bodyTargets()) { - int sectionsBefore = visitedSections.size(); - cache.ensureAround(world, - space, - target.position(), - settings.getWorldCollisionBodyRadius(), - currentTick, - snapshot, - visitedSections, - target.diagnostic(), - sectionAccessCache, - buildOptions); - for (BodyStreamingRefresh refresh : target.refreshes()) { - cache.recordBodyTargetRefresh(space.spaceId(), - refresh.bodyKey(), - target.bounds(), - refresh.sleeping(), - currentTick); - } - if (snapshot != null) { - snapshot.addBodySectionTargets(visitedSections.size() - sectionsBefore); - } - } - cache.pruneUnloaded(world, space.spaceId(), space, snapshot, sectionAccessCache); - cache.pruneUnused(space.spaceId(), - space, - currentTick, - settings.getWorldCollisionTtlTicks(), - snapshot); - } - - private void collectPlayerPositions(@Nonnull ArchetypeChunk chunk, - @Nonnull List positions) { - for (int index = 0; index < chunk.size(); index++) { - TransformComponent transform = chunk.getComponent(index, transformType()); - if (transform != null) { - positions.add(new Vector3d(transform.getPosition())); - } - } - } - - /** - * Collects unique dynamic-body streaming targets for one physics space. - * - *

Bodies are deduplicated by the exact section bounds that the configured - * radius would touch. This removes a large amount of repeated `ensureAround` - * work for big piles where many bodies share the same neighborhood.

- */ - @Nonnull - private List collectDynamicBodyTargets(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull WorldVoxelCollisionCache cache, - @Nonnull SpaceId spaceId, - int radius, - long currentTick, - int ttlTicks, - @Nullable Snapshot snapshot) { - Map uniqueTargets = - new Object2ObjectOpenHashMap<>(); - BodyStreamingCollectionStats stats = new BodyStreamingCollectionStats(); - resource.forEachIndexedBodySnapshot(spaceId, (bodyKey, bodySnapshot, bodySpaceId, kind, persistenceMode) -> { - stats.spatialIndexCandidateCount++; - if (!bodySnapshot.isDynamic()) { - return; - } - - stats.candidateCount++; - float positionX = bodySnapshot.positionX(); - float positionY = bodySnapshot.positionY(); - float positionZ = bodySnapshot.positionZ(); - WorldCollisionStreamingBounds bounds = WorldCollisionStreamingBounds.from(positionX, - positionY, - positionZ, - radius); - TargetRefreshDecision refreshDecision = cache.shouldRefreshBodyTarget(spaceId, - bodyKey, - bounds, - bodySnapshot.sleeping(), - currentTick, - ttlTicks, - snapshot); - if (!refreshDecision.refresh()) { - return; - } - BodyStreamingTarget previous = uniqueTargets.get(bounds); - if (previous == null) { - BodyStreamingTarget target = new BodyStreamingTarget( - new Vector3d(positionX, positionY, positionZ), - bounds, - new ArrayList<>(), - diagnosticFor(snapshot, bodyKey, positionX, positionY, positionZ)); - target.refreshes().add(new BodyStreamingRefresh(bodyKey, bodySnapshot.sleeping())); - uniqueTargets.put(bounds, target); - } - if (previous != null) { - previous.refreshes().add(new BodyStreamingRefresh(bodyKey, bodySnapshot.sleeping())); - if (snapshot != null) { - snapshot.incrementBodyTargetDedupeSkips(); - } - } - }); - cache.pruneBodyStreamingTargets(spaceId, currentTick, ttlTicks, snapshot); - if (snapshot != null) { - snapshot.addBodyStreamingCandidates(stats.candidateCount); - snapshot.addBodySpatialIndexCandidates(stats.spatialIndexCandidateCount); - snapshot.addBodyStreamingTargets(uniqueTargets.size()); - } - return new ArrayList<>(uniqueTargets.values()); - } - - private static final class BodyStreamingCollectionStats { - - private int spatialIndexCandidateCount; - private int candidateCount; - } - - @Nullable - private static StreamingTargetDiagnostic diagnosticFor(@Nullable Snapshot snapshot, - @Nonnull RigidBodyKey bodyKey, - float positionX, - float positionY, - float positionZ) { - if (snapshot == null) { - return null; - } - - return StreamingTargetDiagnostic.body(bodyKey, - positionX, - positionY, - positionZ, - positionX, - positionY, - positionZ); - } - - @Nonnull - private StreamingState stateFor(@Nonnull Store store) { - synchronized (statesByStore) { - return statesByStore.computeIfAbsent(store, _ -> new StreamingState()); - } - } - - private record BodyStreamingTarget(@Nonnull Vector3d position, - @Nonnull WorldCollisionStreamingBounds bounds, - @Nonnull List refreshes, - @Nullable StreamingTargetDiagnostic diagnostic) { - } - - private record BodyStreamingRefresh(@Nonnull RigidBodyKey bodyKey, - boolean sleeping) { - } - - private record SpaceStreamingPlan(@Nonnull SpaceId spaceId, - long settingsRevision, - long lifecycleGeneration, - @Nonnull List playerPositions, - @Nonnull List bodyTargets) { - } - - private static final class StreamingState { - - private long tick; - - private synchronized long nextTick() { - return ++tick; - } - } - - @Nonnull - @Override - public Query getQuery() { - return query(); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - - @Nonnull - private static Query query() { - Query resolved = query; - if (resolved != null) { - return resolved; - } - synchronized (PhysicsWorldCollisionStreamingSystem.class) { - resolved = query; - if (resolved == null) { - resolved = Query.and(playerType(), transformType()); - query = resolved; - } - } - return resolved; - } - - @Nonnull - private static ComponentType playerType() { - ComponentType resolved = playerType; - if (resolved != null) { - return resolved; - } - synchronized (PhysicsWorldCollisionStreamingSystem.class) { - resolved = playerType; - if (resolved == null) { - resolved = Player.getComponentType(); - playerType = resolved; - } - } - return resolved; - } - - @Nonnull - private static ComponentType transformType() { - ComponentType resolved = transformType; - if (resolved != null) { - return resolved; - } - synchronized (PhysicsWorldCollisionStreamingSystem.class) { - resolved = transformType; - if (resolved == null) { - resolved = TransformComponent.getComponentType(); - transformType = resolved; - } - } - return resolved; - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystem.java deleted file mode 100644 index a15832dd..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystem.java +++ /dev/null @@ -1,325 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRuntimeSnapshot; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsSnapshotPublicationSystem; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.RejectedExecutionException; -import javax.annotation.Nonnull; - -/** - * Syncs the persisted world resource from owner-lane runtime snapshots. - * - *

Mirrors the live spaces, their settings, persistent bodies, and persistent - * endpoint joints back into {@link PersistentPhysicsWorldResource} on a bounded - * cadence. Scalar world settings and cheap topology count changes request a full - * owner snapshot immediately; joint-only footprint checks are queued as light - * owner reads so the main tick does not inspect live backend joints.

- * - *

Runs after restore hydration to ensure both sides are settled before copying. - * Skipped while a restore is in progress, or after a hard restore failure, to avoid - * overwriting deserialized data before hydration finishes or before the failure is - * resolved.

- */ -public class PersistentPhysicsWorldSyncSystem extends TickingSystem { - - private static final int WORLD_SYNC_INTERVAL_TICKS = 20; - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistentPhysicsJointHydrationSystem.class), - new SystemDependency<>(Order.AFTER, PhysicsSnapshotPublicationSystem.class) - ); - - private final Map pendingRuntimeReads = - new WeakHashMap<>(); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PersistentPhysicsWorldResource persistent = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); - if (shouldSkipRuntimeSnapshotTick(persistent)) { - pendingRuntimeReads.remove(persistent); - return; - } - - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(store); - PendingRuntimeRead pending = pendingRuntimeReads.get(persistent); - if (pending != null) { - if (!pending.isDone()) { - return; - } - pendingRuntimeReads.remove(persistent); - if (pending.kind() == RuntimeReadKind.SNAPSHOT) { - syncRuntimeSnapshot(persistent, - pending.joinSnapshot(), - pending.restoreGeneration()); - return; - } - if (!pending.matchesGeneration(persistent)) { - return; - } - if (hasRuntimePersistenceFootprintChanged(persistent, pending.joinFootprint())) { - requestSnapshotRead(store, persistent, runtime); - } - return; - } - - if (!hasScalarWorldStateChanged(persistent, runtime) - && !hasCheapRuntimePersistenceFootprintChanged(persistent, runtime) - && !persistent.shouldSyncRuntimeSnapshot(WORLD_SYNC_INTERVAL_TICKS)) { - requestFootprintRead(store, persistent, runtime); - return; - } - - requestSnapshotRead(store, persistent, runtime); - } - - @Nonnull - public static SyncResult syncRuntimeSnapshot(@Nonnull Store store, - @Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PhysicsWorldResource runtime) { - if (persistent.isRuntimeRestorePending()) { - return SyncResult.skipped("restore pending"); - } - if (persistent.hasRuntimeRestoreFailed()) { - return SyncResult.skipped("restore failed"); - } - - PhysicsWorldRuntimeResource runtimeResource = PhysicsWorldRuntimeResource.require(runtime); - PersistentPhysicsRuntimeSnapshot snapshot = PhysicsOwnerBridge.call(store, - "capture persisted physics runtime snapshot", - () -> PersistentPhysicsRuntimeSnapshot.capture(runtimeResource)); - return syncRuntimeSnapshot(persistent, snapshot); - } - - @Nonnull - public static SyncResult syncRuntimeSnapshot(@Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PersistentPhysicsRuntimeSnapshot snapshot) { - return syncRuntimeSnapshot(persistent, - snapshot, - persistent.runtimeRestoreGeneration()); - } - - @Nonnull - static SyncResult syncRuntimeSnapshot(@Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PersistentPhysicsRuntimeSnapshot snapshot, - long restoreGeneration) { - if (persistent.runtimeRestoreGeneration() != restoreGeneration) { - return SyncResult.skipped("restore generation changed"); - } - if (persistent.isRuntimeRestorePending()) { - return SyncResult.skipped("restore pending"); - } - if (persistent.hasRuntimeRestoreFailed()) { - return SyncResult.skipped("restore failed"); - } - - persistent.setSchemaVersion(PersistentPhysicsWorldResource.CURRENT_SCHEMA_VERSION); - persistent.setWorldSettings(snapshot.getWorldSettings()); - persistent.setSpaces(snapshot.getSpaces()); - persistent.setBodies(snapshot.getBodies()); - persistent.setJoints(snapshot.getJoints()); - persistent.markRuntimeSnapshotSynced(); - - PersistentPhysicsRuntimeSnapshot.Footprint footprint = snapshot.getFootprint(); - return SyncResult.synced(footprint.spaces(), footprint.bodies(), footprint.joints()); - } - - static boolean shouldSkipRuntimeSnapshotTick( - @Nonnull PersistentPhysicsWorldResource persistent) { - return persistent.isRuntimeRestorePending() || persistent.hasRuntimeRestoreFailed(); - } - - private static boolean hasScalarWorldStateChanged(@Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PhysicsWorldRuntimeResource runtime) { - PhysicsWorldSettings runtimeSettings = runtime.getWorldSettings(); - PhysicsWorldSettings persistedSettings = persistent.getWorldSettings(); - return persistedSettings.getSimulationSteps() != runtimeSettings.getSimulationSteps() - || persistedSettings.getStepMode() != runtimeSettings.getStepMode() - || persistedSettings.getStepSchedulingMode() != runtimeSettings.getStepSchedulingMode() - || persistedSettings.getEventCollectionMode() != runtimeSettings.getEventCollectionMode() - || Float.compare(persistedSettings.getMaxStepDt(), runtimeSettings.getMaxStepDt()) != 0; - } - - private static boolean hasCheapRuntimePersistenceFootprintChanged( - @Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PhysicsWorldRuntimeResource runtime) { - return persistent.getSpaceCount() != runtime.getSpaceCount() - || persistent.getBodyCount() != runtime.getBodyRegistrationCount( - PhysicsBodyPersistenceMode.PERSISTENT); - } - - static boolean hasRuntimePersistenceFootprintChanged( - @Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PersistentPhysicsRuntimeSnapshot.Footprint footprint) { - return persistent.getSpaceCount() != footprint.spaces() - || persistent.getBodyCount() != footprint.bodies() - || persistent.getJointCount() != footprint.joints(); - } - - private void requestSnapshotRead(@Nonnull Store store, - @Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PhysicsWorldRuntimeResource runtime) { - if (runtime.canAccessLiveBackendDirectly()) { - syncRuntimeSnapshot(persistent, PersistentPhysicsRuntimeSnapshot.capture(runtime)); - return; - } - submitOwnerRead(store, - persistent, - PendingRuntimeRead.snapshot(persistent), - future -> future.complete(PersistentPhysicsRuntimeSnapshot.capture(runtime))); - } - - private void requestFootprintRead(@Nonnull Store store, - @Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PhysicsWorldRuntimeResource runtime) { - if (runtime.canAccessLiveBackendDirectly()) { - PersistentPhysicsRuntimeSnapshot.Footprint footprint = - PersistentPhysicsRuntimeSnapshot.captureFootprint(runtime); - if (hasRuntimePersistenceFootprintChanged(persistent, footprint)) { - requestSnapshotRead(store, persistent, runtime); - } - return; - } - submitOwnerRead(store, - persistent, - PendingRuntimeRead.footprint(persistent), - future -> future.complete(PersistentPhysicsRuntimeSnapshot.captureFootprint(runtime))); - } - - private void submitOwnerRead(@Nonnull Store store, - @Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PendingRuntimeRead pending, - @Nonnull OwnerReadCapture capture) { - PhysicsOwnerResource owner = store.getResource(PhysicsOwnerResource.getResourceType()); - if (owner.isClosed()) { - return; - } - - try { - owner.submitMutation(pending.operation(), () -> { - try { - capture.capture(pending.future()); - return PhysicsOwnerSnapshot.empty(); - } catch (RuntimeException | Error exception) { - pending.future().completeExceptionally(exception); - throw exception; - } - }); - pendingRuntimeReads.put(persistent, pending); - } catch (RejectedExecutionException exception) { - pending.future().completeExceptionally(exception); - } - } - - public record SyncResult(boolean synced, - int spaces, - int bodies, - int joints, - @Nonnull String skippedReason) { - - @Nonnull - private static SyncResult synced(int spaces, int bodies, int joints) { - return new SyncResult(true, spaces, bodies, joints, ""); - } - - @Nonnull - private static SyncResult skipped(@Nonnull String reason) { - return new SyncResult(false, 0, 0, 0, reason); - } - } - - private enum RuntimeReadKind { - SNAPSHOT, - FOOTPRINT - } - - private record PendingRuntimeRead(long restoreGeneration, - @Nonnull RuntimeReadKind kind, - @Nonnull CompletableFuture future) { - - @Nonnull - private static PendingRuntimeRead snapshot(@Nonnull PersistentPhysicsWorldResource persistent) { - return new PendingRuntimeRead(persistent.runtimeRestoreGeneration(), - RuntimeReadKind.SNAPSHOT, - new CompletableFuture<>()); - } - - @Nonnull - private static PendingRuntimeRead footprint(@Nonnull PersistentPhysicsWorldResource persistent) { - return new PendingRuntimeRead(persistent.runtimeRestoreGeneration(), - RuntimeReadKind.FOOTPRINT, - new CompletableFuture<>()); - } - - private boolean isDone() { - return future.isDone(); - } - - private boolean matchesGeneration(@Nonnull PersistentPhysicsWorldResource persistent) { - return restoreGeneration == persistent.runtimeRestoreGeneration(); - } - - @Nonnull - private String operation() { - return kind == RuntimeReadKind.SNAPSHOT - ? "capture persisted physics runtime snapshot" - : "capture persisted physics runtime footprint"; - } - - @Nonnull - private PersistentPhysicsRuntimeSnapshot joinSnapshot() { - return (PersistentPhysicsRuntimeSnapshot) joinFuture(); - } - - @Nonnull - private PersistentPhysicsRuntimeSnapshot.Footprint joinFootprint() { - return (PersistentPhysicsRuntimeSnapshot.Footprint) joinFuture(); - } - - @Nonnull - private Object joinFuture() { - try { - return future.join(); - } catch (CompletionException exception) { - Throwable cause = exception.getCause(); - if (cause instanceof RuntimeException runtimeException) { - throw runtimeException; - } - if (cause instanceof Error error) { - throw error; - } - throw exception; - } - } - } - - @FunctionalInterface - private interface OwnerReadCapture { - - void capture(@Nonnull CompletableFuture future); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java deleted file mode 100644 index aca5c06e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PhysicsRuntimeHolderSystem.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.HolderSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import javax.annotation.Nonnull; - -/** - * Strips runtime-only core physics components from holders when entities cross unload/load - * boundaries. - */ -public class PhysicsRuntimeHolderSystem extends HolderSystem { - - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private static final Query QUERY = ATTACHMENT_TYPE; - - @Override - public void onEntityAdd(@Nonnull Holder holder, - @Nonnull AddReason reason, - @Nonnull Store store) { - if (reason == AddReason.LOAD) { - cleanupHolder(holder, store); - } - } - - @Override - public void onEntityRemoved(@Nonnull Holder holder, - @Nonnull RemoveReason reason, - @Nonnull Store store) { - cleanupHolder(holder, store); - } - - private static void cleanupHolder(@Nonnull Holder holder, - @Nonnull Store store) { - BodyAttachmentComponent attachment = holder.getComponent(ATTACHMENT_TYPE); - if (attachment == null - || attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { - holder.tryRemoveComponent(ATTACHMENT_TYPE); - } - } - - @Nonnull - @Override - public Query getQuery() { - return QUERY; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystem.java deleted file mode 100644 index dc947ec4..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystem.java +++ /dev/null @@ -1,79 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.publication; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Entity-tick publication stage for completed physics owner-lane output. - * - *

The command-buffer visibility chain is: recorded, queued, owner completed, snapshot captured, - * reader-side applied, then ECS systems consume the reader view. This system owns the reader-side - * apply step and intentionally never waits for an in-flight owner-lane step.

- */ -public final class PhysicsSnapshotPublicationSystem extends TickingSystem { - - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.BEFORE, PhysicsDetachedVisualMaterializationSystem.class), - new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) - ); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsOwnerResource owner = store.getResource( - PhysicsOwnerResource.getResourceType()); - if (owner.isClosed()) { - return; - } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - PhysicsRuntimeProfilingResource profiling = store.getResource( - PhysicsRuntimeProfilingResource.getResourceType()); - // Mutation futures are drained for failures/completion only; snapshot inclusion is still - // determined by the next captured and applied PublishedPhysicsSnapshotFrame. - PhysicsPublicationPipeline.publishCompletedMutations(owner); - long publicationServerTick = Math.max(0L, store.getExternalData().getWorld().getTick()); - PhysicsEventFrame frame = - PhysicsPublicationPipeline.publishCompletedStep(owner, resource, profiling, publicationServerTick); - if (frame != null) { - store.invoke(new PhysicsEventFramePublishedEvent(frame)); - } - } - - static int publishCompletedMutations(@Nonnull PhysicsOwnerResource owner) { - return PhysicsPublicationPipeline.publishCompletedMutations(owner); - } - - @Nullable - static PhysicsEventFrame publishCompletedStep(@Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - @Nonnull PhysicsRuntimeProfilingResource profiling) { - return PhysicsPublicationPipeline.publishCompletedStep(owner, resource, profiling); - } - - @Nullable - static PhysicsEventFrame publishCompletedStep(@Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - @Nonnull PhysicsRuntimeProfilingResource profiling, - long publicationServerTick) { - return PhysicsPublicationPipeline.publishCompletedStep(owner, resource, profiling, publicationServerTick); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGate.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGate.java deleted file mode 100644 index cac27e3b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGate.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.step; - -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import javax.annotation.Nonnull; - -final class PhysicsStepRestoreGate { - - private PhysicsStepRestoreGate() { - } - - static boolean canSubmitStep(@Nonnull PersistentPhysicsWorldResource persistent) { - return !persistent.isRuntimeRestorePending() && !persistent.hasRuntimeRestoreFailed(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystem.java deleted file mode 100644 index 98c9d73c..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystem.java +++ /dev/null @@ -1,369 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.step; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCommand; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.RejectedExecutionException; -import java.util.function.BooleanSupplier; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Chunk-tick scheduler for physics steps. - * - *

This system only submits work to the per-world physics owner lane. It does not wait for step - * results or apply snapshots; {@code PhysicsSnapshotPublicationSystem} performs reader-side - * publication later on the entity-store tick.

- */ -public class PhysicsStepSystem extends TickingSystem implements AutoCloseable { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final float MAX_ACCUMULATED_STEP_DT = 0.25f; - - @Nonnull - private final Map, StepSchedulerState> statesByStore = - Collections.synchronizedMap(new WeakHashMap<>()); - private volatile boolean closed; - - public PhysicsStepSystem() { - } - - @Override - public void close() { - closed = true; - } - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - if (closed) { - return; - } - - var world = store.getExternalData().getWorld(); - var entityStore = world.getEntityStore().getStore(); - PhysicsWorldResource resource = entityStore.getResource( - PhysicsWorldResource.getResourceType()); - PhysicsRuntimeProfilingResource profiling = entityStore.getResource( - PhysicsRuntimeProfilingResource.getResourceType()); - PhysicsOwnerResource owner = entityStore.getResource( - PhysicsOwnerResource.getResourceType()); - if (owner.isClosed()) { - return; - } - PersistentPhysicsWorldResource persistent = entityStore.getResource( - PersistentPhysicsWorldResource.getResourceType()); - - submitStepIfRestoreReady(stateFor(store), - persistent, - owner, - resource, - dt, - profiling, - Math.max(0L, world.getTick())); - } - - boolean submitStepIfRestoreReady(@Nonnull StepSchedulerState state, - @Nonnull PersistentPhysicsWorldResource persistent, - @Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - float dt, - @Nonnull PhysicsRuntimeProfilingResource profiling, - long serverTick) { - if (!PhysicsStepRestoreGate.canSubmitStep(persistent)) { - /* - * Restore replaces runtime topology. Dropping catch-up dt avoids replaying stale - * backlog against newly restored spaces after restore completes. - */ - state.clearAccumulatedStepDt(); - return false; - } - submitStepIfIdle(state, owner, resource, dt, profiling, serverTick); - return true; - } - - void submitStepIfIdle(@Nonnull StepSchedulerState state, - @Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - float dt, - @Nonnull PhysicsRuntimeProfilingResource profiling, - long serverTick) { - PhysicsWorldSettings settings = resource.getWorldSettings(); - if (settings.getStepSchedulingMode() - != PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT) { - submitCurrentTickStepIfIdle(state, owner, resource, dt, profiling, serverTick); - return; - } - - submitAccumulatedStepIfIdle(state, owner, resource, dt, profiling, serverTick); - } - - private void submitCurrentTickStepIfIdle(@Nonnull StepSchedulerState state, - @Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - float dt, - @Nonnull PhysicsRuntimeProfilingResource profiling, - long serverTick) { - boolean profilingEnabled = profiling.isEnabled(); - state.clearAccumulatedStepDt(); - if (owner.hasPendingStep()) { - if (profilingEnabled) { - profiling.recordStepSkippedPending(owner.pendingStepAgeNanos()); - } - return; - } - - PhysicsOwnerStepCommand command = state.currentTickStepCommand(resource, - dt, - profilingEnabled, - serverTick); - try { - if (owner.submitStepIfIdle(command)) { - state.consumeStepSequence(); - return; - } - if (profilingEnabled) { - profiling.recordStepSkippedPending(owner.pendingStepAgeNanos()); - } - } catch (RejectedExecutionException exception) { - if (!owner.isClosed()) { - LOGGER.at(Level.WARNING).log( - "Skipping async physics step because the owner lane is unavailable: %s", - exception.getMessage()); - } - } - } - - private void submitAccumulatedStepIfIdle(@Nonnull StepSchedulerState state, - @Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - float dt, - @Nonnull PhysicsRuntimeProfilingResource profiling, - long serverTick) { - boolean profilingEnabled = profiling.isEnabled(); - float safeDt = safeStepDt(dt); - float maxAccumulatedDt = maxAccumulatedStepDt(resource); - AccumulatedStepPreparation preparation = state.prepareAccumulatedStepCommand(safeDt, - maxAccumulatedDt, - owner::hasPendingStep, - resource, - profilingEnabled, - serverTick); - StepSchedulerSample sample = preparation.sample(); - if (preparation.hasPendingStep()) { - recordSchedulerSample(profiling, - profilingEnabled, - safeDt, - 0.0f, - preparation.backlogDt(), - sample.droppedDt(), - sample.capHit()); - if (profilingEnabled) { - profiling.recordStepSkippedPending(owner.pendingStepAgeNanos()); - } - return; - } - PhysicsOwnerStepCommand command = preparation.commandOrThrow(); - try { - if (owner.submitStepIfIdle(command)) { - float submittedDt = state.consumeSubmittedStepDt(); - recordSchedulerSample(profiling, - profilingEnabled, - safeDt, - submittedDt, - 0.0f, - sample.droppedDt(), - sample.capHit()); - return; - } - recordSchedulerSample(profiling, - profilingEnabled, - safeDt, - 0.0f, - state.accumulatedStepDt(), - sample.droppedDt(), - sample.capHit()); - if (profilingEnabled) { - profiling.recordStepSkippedPending(owner.pendingStepAgeNanos()); - } - } catch (RejectedExecutionException exception) { - recordSchedulerSample(profiling, - profilingEnabled, - safeDt, - 0.0f, - state.accumulatedStepDt(), - sample.droppedDt(), - sample.capHit()); - if (!owner.isClosed()) { - LOGGER.at(Level.WARNING).log( - "Skipping async physics step because the owner lane is unavailable: %s", - exception.getMessage()); - } - } - } - - @Nonnull - private StepSchedulerState stateFor(@Nonnull Store store) { - synchronized (statesByStore) { - return statesByStore.computeIfAbsent(store, _ -> new StepSchedulerState()); - } - } - - private static void recordSchedulerSample(@Nonnull PhysicsRuntimeProfilingResource profiling, - boolean profilingEnabled, - float inputDt, - float submittedDt, - float backlogDt, - float droppedDt, - boolean capHit) { - if (profilingEnabled) { - profiling.recordStepScheduling(inputDt, submittedDt, backlogDt, droppedDt, capHit); - } - } - - private static float safeStepDt(float dt) { - return Float.isFinite(dt) ? Math.max(dt, 0.0f) : 0.0f; - } - - static float maxAccumulatedStepDt(@Nonnull PhysicsWorldResource resource) { - PhysicsWorldSettings settings = resource.getWorldSettings(); - float maxStepDt = settings.getMaxStepDt(); - float safeMaxStepDt = Float.isFinite(maxStepDt) && maxStepDt > 0.0f - ? maxStepDt - : PhysicsWorldSettings.DEFAULT_MAX_STEP_DT; - /* - * Fixed and CCD modes do not refine catch-up dt inside the owner lane: they - * use the configured substep count directly. Cap accumulated submissions - * to that exact budget and drop the rest through the scheduler profiling - * path. Adaptive/progressive modes keep the wider refinement window. - */ - int stepBudget = switch (settings.getStepMode()) { - case FIXED, CCD -> Math.clamp(settings.getSimulationSteps(), - PhysicsWorldSettings.MIN_SIMULATION_STEPS, - PhysicsWorldSettings.MAX_SIMULATION_STEPS); - case ADAPTIVE, PROGRESSIVE_REFINEMENT -> PhysicsWorldSettings.MAX_SIMULATION_STEPS; - }; - return Math.min(MAX_ACCUMULATED_STEP_DT, safeMaxStepDt * stepBudget); - } - - static final class StepSchedulerState { - - private final Object lock = new Object(); - - /** - * Impulse-local sequence for correlating owner step commands with published - * snapshot frames. This is not the Hytale world tick. - */ - private long nextStepSequence = 1L; - - /* - * Accumulated scheduler dt is intentionally local to this ChunkStore scheduler. It is - * submitted only when the owner lane has no in-flight step. - */ - private double accumulatedStepDt; - - @Nonnull - private AccumulatedStepPreparation prepareAccumulatedStepCommand(float dt, - float maxAccumulatedDt, - @Nonnull BooleanSupplier hasPendingStep, - @Nonnull PhysicsWorldResource resource, - boolean profilingEnabled, - long serverTick) { - synchronized (lock) { - StepSchedulerSample sample = accumulateLocked(dt, maxAccumulatedDt); - if (hasPendingStep.getAsBoolean()) { - return new AccumulatedStepPreparation(sample, - (float) accumulatedStepDt, - null); - } - PhysicsOwnerStepCommand command = new PhysicsOwnerStepCommand(resource, - (float) accumulatedStepDt, - profilingEnabled, - nextStepSequence, - serverTick); - return new AccumulatedStepPreparation(sample, 0.0f, command); - } - } - - @Nonnull - PhysicsOwnerStepCommand currentTickStepCommand(@Nonnull PhysicsWorldResource resource, - float dt, - boolean profilingEnabled, - long serverTick) { - synchronized (lock) { - return new PhysicsOwnerStepCommand(resource, - dt, - profilingEnabled, - nextStepSequence, - serverTick); - } - } - - @Nonnull - private StepSchedulerSample accumulateLocked(float dt, float maxAccumulatedDt) { - double candidate = accumulatedStepDt + Math.max(0.0f, dt); - double capped = Math.clamp(maxAccumulatedDt, 0.0f, candidate); - accumulatedStepDt = capped; - double dropped = Math.max(0.0, candidate - capped); - return new StepSchedulerSample((float) dropped, dropped > 0.0); - } - - private float consumeSubmittedStepDt() { - synchronized (lock) { - float submittedDt = (float) accumulatedStepDt; - accumulatedStepDt = 0.0; - nextStepSequence++; - return submittedDt; - } - } - - private void clearAccumulatedStepDt() { - synchronized (lock) { - accumulatedStepDt = 0.0; - } - } - - private void consumeStepSequence() { - synchronized (lock) { - nextStepSequence++; - } - } - - private float accumulatedStepDt() { - synchronized (lock) { - return (float) accumulatedStepDt; - } - } - } - - private record AccumulatedStepPreparation(@Nonnull StepSchedulerSample sample, - float backlogDt, - @Nullable PhysicsOwnerStepCommand command) { - - private boolean hasPendingStep() { - return command == null; - } - - @Nonnull - private PhysicsOwnerStepCommand commandOrThrow() { - if (command == null) { - throw new IllegalStateException("No accumulated step command was prepared"); - } - return command; - } - } - - private record StepSchedulerSample(float droppedDt, boolean capHit) { - } -} From dccb7af5a2e3e7e68123e4f96a69844057d2bb0d Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 13:44:35 +0200 Subject: [PATCH 199/534] refactor(core): remove legacy owner restore pipeline Signed-off-by: Blovien --- .../PersistentPhysicsRuntimeSnapshot.java | 184 ---------------- .../PersistentPhysicsRuntimeSupport.java | 143 ------------- .../PersistentPhysicsSpaceState.java | 5 +- .../owner/PhysicsOwnerLifecycleSystem.java | 157 -------------- .../PersistentPhysicsBodyHydrationSystem.java | 137 ------------ ...PersistentPhysicsJointHydrationSystem.java | 158 -------------- ...ersistentPhysicsRestoreTerrainPrewarm.java | 150 -------------- ...PersistentPhysicsSpaceBootstrapSystem.java | 196 ------------------ .../PhysicsPublicationPipeline.java | 110 ---------- 9 files changed, 1 insertion(+), 1239 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSnapshot.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSupport.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsBodyHydrationSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipeline.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSnapshot.java deleted file mode 100644 index 0a4888aa..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSnapshot.java +++ /dev/null @@ -1,184 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.annotation.Nonnull; - -/** - * Owner-owned copy of the live runtime state needed by world persistence. - * - *

{@link #capture(PhysicsWorldRuntimeResource)} and - * {@link #captureFootprint(PhysicsWorldRuntimeResource)} - * read live backend spaces, bodies, and joints. Callers must invoke them from the physics - * owner lane or through the physics owner bridge.

- */ -public final class PersistentPhysicsRuntimeSnapshot { - - @Nonnull - private final Footprint footprint; - @Nonnull - private final PhysicsWorldSettings worldSettings; - @Nonnull - private final PersistentPhysicsSpaceState[] spaces; - @Nonnull - private final PersistentPhysicsBodyState[] bodies; - @Nonnull - private final PersistentPhysicsJointState[] joints; - - private PersistentPhysicsRuntimeSnapshot(@Nonnull PhysicsWorldSettings worldSettings, - @Nonnull PersistentPhysicsSpaceState[] spaces, - @Nonnull PersistentPhysicsBodyState[] bodies, - @Nonnull PersistentPhysicsJointState[] joints) { - this.worldSettings = new PhysicsWorldSettings(worldSettings); - this.spaces = copySpaces(spaces); - this.bodies = copyBodies(bodies); - this.joints = copyJoints(joints); - footprint = new Footprint(spaces.length, bodies.length, joints.length); - } - - @Nonnull - public static PersistentPhysicsRuntimeSnapshot capture(@Nonnull PhysicsWorldRuntimeResource runtime) { - runtime.assertCanAccessLiveBackendDirectly("capture persistent physics runtime snapshot"); - - List spaces = new ArrayList<>(); - List bodies = new ArrayList<>(); - List joints = new ArrayList<>(); - for (PhysicsBodyRegistration registration : runtime.getBodyRegistrations()) { - if (registration.persistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) { - bodies.add(PersistentPhysicsBodyState.from(registration, - runtime.captureLiveBodySnapshot(registration))); - } - } - for (PhysicsSpaceBinding space : runtime.iterateSpaceBindings()) { - PhysicsSpaceSettings settings = runtime.getLiveSpaceSettings(space.spaceId()); - spaces.add(PersistentPhysicsSpaceState.from(space, settings)); - capturePersistentJoints(runtime, space, joints); - } - - return new PersistentPhysicsRuntimeSnapshot(runtime.getWorldSettings(), - spaces.toArray(PersistentPhysicsSpaceState[]::new), - bodies.toArray(PersistentPhysicsBodyState[]::new), - joints.toArray(PersistentPhysicsJointState[]::new)); - } - - @Nonnull - public static Footprint captureFootprint(@Nonnull PhysicsWorldRuntimeResource runtime) { - runtime.assertCanAccessLiveBackendDirectly("capture persistent physics runtime footprint"); - return new Footprint(runtime.getSpaceCount(), - runtime.getBodyRegistrationCount(PhysicsBodyPersistenceMode.PERSISTENT), - countPersistentJoints(runtime)); - } - - @Nonnull - public PhysicsWorldSettings getWorldSettings() { - return new PhysicsWorldSettings(worldSettings); - } - - @Nonnull - public Footprint getFootprint() { - return footprint; - } - - @Nonnull - public PersistentPhysicsSpaceState[] getSpaces() { - return copySpaces(spaces); - } - - @Nonnull - public PersistentPhysicsBodyState[] getBodies() { - return copyBodies(bodies); - } - - @Nonnull - public PersistentPhysicsJointState[] getJoints() { - return copyJoints(joints); - } - - private static int countPersistentJoints(@Nonnull PhysicsWorldRuntimeResource runtime) { - int count = 0; - for (PhysicsJointRegistration joint : runtime.getJointRegistrations()) { - PhysicsBodyRegistration bodyA = runtime.getRegistration(joint.bodyA()); - PhysicsBodyRegistration bodyB = runtime.getRegistration(joint.bodyB()); - if (bodyA == null - || bodyB == null - || bodyA.persistenceMode() != PhysicsBodyPersistenceMode.PERSISTENT - || bodyB.persistenceMode() != PhysicsBodyPersistenceMode.PERSISTENT) { - continue; - } - count++; - } - return count; - } - - private static void capturePersistentJoints(@Nonnull PhysicsWorldRuntimeResource runtime, - @Nonnull PhysicsSpaceBinding space, - @Nonnull List joints) { - for (PhysicsJointRegistration joint : runtime.getJointRegistrations()) { - if (!joint.spaceId().equals(space.spaceId())) { - continue; - } - RigidBodyKey bodyAKey = joint.bodyA(); - RigidBodyKey bodyBKey = joint.bodyB(); - PhysicsBodyRegistration bodyA = runtime.getRegistration(bodyAKey); - PhysicsBodyRegistration bodyB = runtime.getRegistration(bodyBKey); - if (bodyA == null - || bodyB == null - || bodyA.persistenceMode() != PhysicsBodyPersistenceMode.PERSISTENT - || bodyB.persistenceMode() != PhysicsBodyPersistenceMode.PERSISTENT) { - continue; - } - joints.add(PersistentPhysicsJointState.from(space.spaceId().value(), - bodyAKey, - bodyBKey, - joint)); - } - } - - @Nonnull - private static PersistentPhysicsSpaceState[] copySpaces( - @Nonnull PersistentPhysicsSpaceState[] source) { - PersistentPhysicsSpaceState[] copy = Arrays.copyOf(source, source.length); - for (int i = 0; i < copy.length; i++) { - copy[i] = copy[i].copy(); - } - return copy; - } - - @Nonnull - private static PersistentPhysicsBodyState[] copyBodies( - @Nonnull PersistentPhysicsBodyState[] source) { - PersistentPhysicsBodyState[] copy = Arrays.copyOf(source, source.length); - for (int i = 0; i < copy.length; i++) { - copy[i] = copy[i].copy(); - } - return copy; - } - - @Nonnull - private static PersistentPhysicsJointState[] copyJoints( - @Nonnull PersistentPhysicsJointState[] source) { - PersistentPhysicsJointState[] copy = Arrays.copyOf(source, source.length); - for (int i = 0; i < copy.length; i++) { - copy[i] = copy[i].copy(); - } - return copy; - } - - public record Footprint(int spaces, int bodies, int joints) { - - public Footprint { - spaces = Math.max(0, spaces); - bodies = Math.max(0, bodies); - joints = Math.max(0, joints); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSupport.java deleted file mode 100644 index 780957a9..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRuntimeSupport.java +++ /dev/null @@ -1,143 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Shared helpers for bridging persisted physics state to live runtime objects. - * - *

Used by the hydration and sync systems to construct joints from persisted - * body-key endpoint definitions and produce stable keys for deduplicating joints - * across hydration ticks.

- */ -public final class PersistentPhysicsRuntimeSupport { - - private PersistentPhysicsRuntimeSupport() { - } - - @Nonnull - public static String jointKey(int spaceId, - @Nonnull RigidBodyKey bodyAKey, - @Nonnull RigidBodyKey bodyBKey, - @Nonnull PhysicsJointRegistration joint) { - return PersistentPhysicsJointState.from(spaceId, bodyAKey, bodyBKey, joint).key(); - } - - @Nonnull - public static JointKey createJoint(@Nonnull PhysicsWorldRuntimeResource runtime, - @Nonnull PhysicsSpaceBinding space, - @Nonnull PersistentPhysicsJointState state, - @Nonnull RigidBodyKey bodyAKey, - @Nonnull PhysicsBodyRegistration bodyA, - @Nonnull RigidBodyKey bodyBKey, - @Nonnull PhysicsBodyRegistration bodyB) { - JointType type = toRuntimeJointType(state.getType()); - Vector3f anchorA = new Vector3f(state.getAnchorA()); - Vector3f anchorB = new Vector3f(state.getAnchorB()); - Vector3f axis = state.getType() == PhysicsJointType.HINGE || state.getType() == PhysicsJointType.SLIDER - ? requireAxis(state) - : new Vector3f(); - BackendJointHandle backendJointHandle = new BackendJointHandle(space.runtime().createJoint( - space.backendSpaceHandle().value(), - toRuntimeJointTypeCode(state.getType()), - bodyA.backendBodyHandle().value(), - bodyB.backendBodyHandle().value(), - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z, - state.getSpringRestLength(), - state.getSpringStiffness(), - state.getSpringDamping(), - state.getLowerLimit(), - state.getUpperLimit(), - state.isMotorEnabled(), - state.getMotorTargetVelocity(), - state.getMotorMaxForce())); - JointKey jointKey = JointKey.random(); - try { - runtime.addJointOnOwner(jointKey, - space.spaceId(), - backendJointHandle, - bodyAKey, - bodyBKey, - type, - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z, - state.getSpringRestLength(), - state.getSpringStiffness(), - state.getSpringDamping(), - state.getLowerLimit(), - state.getUpperLimit(), - state.isMotorEnabled(), - state.getMotorTargetVelocity(), - state.getMotorMaxForce()); - } catch (RuntimeException exception) { - removeBackendJoint(space, backendJointHandle, exception); - throw exception; - } - return jointKey; - } - - private static void removeBackendJoint(@Nonnull PhysicsSpaceBinding space, - @Nonnull BackendJointHandle backendJointHandle, - @Nonnull RuntimeException restoreFailure) { - try { - space.runtime().removeJoint(space.backendSpaceHandle().value(), backendJointHandle.value()); - } catch (RuntimeException cleanupFailure) { - restoreFailure.addSuppressed(cleanupFailure); - } - } - - @Nonnull - private static Vector3f requireAxis(@Nonnull PersistentPhysicsJointState state) { - Vector3f axis = state.getAxis(); - if (axis == null) { - throw new IllegalStateException("Persisted " + state.getType() + " joint requires an axis"); - } - return new Vector3f(axis); - } - - private static int toRuntimeJointTypeCode(@Nonnull PhysicsJointType type) { - return switch (type) { - case FIXED -> BackendRuntimeCodes.JOINT_FIXED; - case POINT -> BackendRuntimeCodes.JOINT_POINT; - case HINGE -> BackendRuntimeCodes.JOINT_HINGE; - case SLIDER -> BackendRuntimeCodes.JOINT_SLIDER; - case SPRING -> BackendRuntimeCodes.JOINT_SPRING; - }; - } - - @Nonnull - private static JointType toRuntimeJointType(@Nonnull PhysicsJointType type) { - return switch (type) { - case FIXED -> JointType.FIXED; - case POINT -> JointType.POINT; - case HINGE -> JointType.HINGE; - case SLIDER -> JointType.SLIDER; - case SPRING -> JointType.SPRING; - }; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java index 55e13878..26eaac99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java @@ -12,7 +12,6 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PersistentPhysicsSpaceBootstrapSystem; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; @@ -31,9 +30,7 @@ * Codec-backed definition of one physics space for the persistence layer. * *

Captures the space identity, backend choice, gravity, and world-collision - * settings so that {@link PersistentPhysicsSpaceBootstrapSystem} can recreate - * the runtime physics space after a - * world load or manual snapshot restore.

+ * settings for legacy world-resource persistence and migration.

*/ @Getter public class PersistentPhysicsSpaceState { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java deleted file mode 100644 index db8b8043..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystem.java +++ /dev/null @@ -1,157 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.owner; - -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.system.StoreSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Starts and stops the per-world physics owner lane with the EntityStore lifecycle. - */ -public final class PhysicsOwnerLifecycleSystem extends StoreSystem - implements AutoCloseable { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - - @Nonnull - private final ResourceType ownerResourceType; - @Nonnull - private final ResourceType physicsWorldResourceType; - @Nonnull - private final Set activeOwners = ConcurrentHashMap.newKeySet(); - - public PhysicsOwnerLifecycleSystem() { - this(PhysicsOwnerResource.getResourceType(), PhysicsWorldResource.getResourceType()); - } - - PhysicsOwnerLifecycleSystem( - @Nonnull ResourceType ownerResourceType, - @Nonnull ResourceType physicsWorldResourceType) { - this.ownerResourceType = ownerResourceType; - this.physicsWorldResourceType = physicsWorldResourceType; - } - - @Override - public void onSystemAddedToStore(@Nonnull Store store) { - PhysicsOwnerResource owner = store.getResource(ownerResourceType); - if (startOwner(owner, worldName(store))) { - PhysicsWorldRuntimeResource runtime = - PhysicsWorldRuntimeResource.require(store.getResource(physicsWorldResourceType)); - runtime.attachEntityStore(store); - runtime.attachOwnerExecutor(owner); - } - } - - @Override - public void onSystemRemovedFromStore(@Nonnull Store store) { - String worldName = worldName(store); - PhysicsOwnerResource owner = store.getResource(ownerResourceType); - PhysicsWorldResource physics = store.getResource(physicsWorldResourceType); - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(physics); - RuntimeException clearFailure = tryClearSpaces(store, physics, worldName); - boolean closedOwner = closeOwner(owner); - runtime.detachOwnerExecutor(owner); - runtime.detachEntityStore(store); - - // Retry if it failed before, this could happen. - if (clearFailure != null && closedOwner) { - clearFailure = tryClearSpaces(store, physics, worldName); - } - if (clearFailure != null) { - LOGGER.at(Level.WARNING).log("Failed to clear physics spaces for world %s: %s", - worldName, - clearFailure.getMessage()); - } - } - - @Override - public void close() { - for (PhysicsOwnerResource owner : new ArrayList<>(activeOwners)) { - closeOwner(owner); - } - } - - boolean startOwner(@Nonnull PhysicsOwnerResource owner, @Nonnull String worldName) { - try { - owner.start(worldName); - if (owner.isStarted()) { - activeOwners.add(owner); - return true; - } - } catch (RuntimeException exception) { - LOGGER.at(Level.WARNING).log("Physics owner lane could not be started for world %s: %s", - worldName, - exception.getMessage()); - } - return false; - } - - boolean closeOwner(@Nonnull PhysicsOwnerResource owner) { - activeOwners.remove(owner); - try { - owner.close(); - return true; - } catch (RuntimeException exception) { - LOGGER.at(Level.WARNING).log("Failed to close physics owner lane: %s", - exception.getMessage()); - return false; - } - } - - int activeOwnerCount() { - return activeOwners.size(); - } - - @Nonnull - private static String worldName(@Nonnull Store store) { - return store.getExternalData().getWorld().getName(); - } - - @Nullable - private static RuntimeException tryClearSpaces(@Nonnull Store store, - @Nonnull PhysicsWorldResource physics, - @Nonnull String worldName) { - try { - Store physicsStore = physicsStoreOrNull(store); - if (physicsStore != null) { - PhysicsStoreRuntimeCleaner.clearAll(physicsStore); - } else { - physics.clearAllSpaces(worldName); - } - return null; - } catch (RuntimeException exception) { - return exception; - } - } - - @Nullable - private static Store physicsStoreOrNull(@Nonnull Store store) { - World world = store.getExternalData().getWorld(); - try { - Method accessor = world.getClass().getMethod("getPhysicsStore"); - Object physicsStore = accessor.invoke(world); - return physicsStore instanceof PhysicsStore typedPhysicsStore - ? typedPhysicsStore.getStore() - : null; - } catch (NoSuchMethodException exception) { - return null; - } catch (ReflectiveOperationException exception) { - throw new IllegalStateException("Failed to access authoritative PhysicsStore for world " - + world.getName(), exception); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsBodyHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsBodyHydrationSystem.java deleted file mode 100644 index 125ec6a8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsBodyHydrationSystem.java +++ /dev/null @@ -1,137 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsBodyState; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import java.util.Set; -import javax.annotation.Nonnull; - -/** - * Restores world-level body states keyed by {@link RigidBodyKey}. - */ -public class PersistentPhysicsBodyHydrationSystem extends TickingSystem { - - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistentPhysicsSpaceBootstrapSystem.class) - ); - - @Nonnull - private final SystemGroup group = ImpulsePlugin.get().getPersistenceRestoreGroup(); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PersistentPhysicsWorldResource persistent = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); - if (!persistent.isRuntimeRestorePending() - || !persistent.isRuntimeSpaceBootstrapComplete() - || persistent.hasRuntimeRestoreFailed()) { - return; - } - - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(store); - for (PersistentPhysicsBodyState state : persistent.getBodies()) { - RigidBodyKey bodyKey = state.getBodyKey(); - if (bodyKey == null) { - persistent.recordRuntimeBodySkipped("missing body key"); - continue; - } - - String validationFailure = state.restoreValidationFailureReason(); - if (validationFailure != null) { - persistent.recordRuntimeBodySkipped(bodyKey, validationFailure); - continue; - } - - int resolvedSpaceId = state.resolveSpaceId(); - if (resolvedSpaceId <= 0) { - persistent.recordRuntimeBodySkipped(bodyKey, "no resolved space id"); - continue; - } - - try { - RestoreBodyResult result = PhysicsOwnerBridge.call(store, - "hydrate persisted physics body", - () -> restoreBodyOnOwner(runtime, state, bodyKey)); - if (result == RestoreBodyResult.RESTORED) { - persistent.recordRuntimeBodyRestored(); - } else if (result == RestoreBodyResult.MISSING_SPACE) { - persistent.recordRuntimeBodySkipped(bodyKey, "missing target space"); - } - } catch (RuntimeException exception) { - persistent.failRuntimeRestore("Failed to hydrate persisted body " + bodyKey - + ": " + exception.getClass().getSimpleName()); - return; - } - } - } - - static RestoreBodyResult restoreBodyOnOwner(@Nonnull PhysicsWorldRuntimeResource runtime, - @Nonnull PersistentPhysicsBodyState state, - @Nonnull RigidBodyKey bodyKey) { - if (runtime.getRegistration(bodyKey) != null) { - return RestoreBodyResult.ALREADY_REGISTERED; - } - - PhysicsSpaceBinding space = runtime.getSpaceBinding(new SpaceId(state.resolveSpaceId())); - if (space == null) { - return RestoreBodyResult.MISSING_SPACE; - } - - BackendBodyHandle backendBodyHandle = state.createBackendBody(space); - try { - state.applyToBody(space, backendBodyHandle); - runtime.addBodyOnOwner(bodyKey, - space.spaceId(), - backendBodyHandle, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - } catch (RuntimeException exception) { - removeBackendBody(space, backendBodyHandle, exception); - throw exception; - } - return RestoreBodyResult.RESTORED; - } - - private static void removeBackendBody(@Nonnull PhysicsSpaceBinding space, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull RuntimeException restoreFailure) { - try { - space.runtime().removeBody(space.backendSpaceHandle().value(), backendBodyHandle.value()); - } catch (RuntimeException cleanupFailure) { - restoreFailure.addSuppressed(cleanupFailure); - } - } - - enum RestoreBodyResult { - RESTORED, - ALREADY_REGISTERED, - MISSING_SPACE - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - - @Nonnull - @Override - public SystemGroup getGroup() { - return group; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java deleted file mode 100644 index c5a34f37..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystem.java +++ /dev/null @@ -1,158 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsJointState; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRuntimeSupport; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.Set; -import java.util.logging.Level; -import javax.annotation.Nonnull; - -/** - * Third stage of persistence restore: reconnects joints between hydrated bodies. - * - *

Runs when {@code runtimeRestorePending} is true. - * Scans the persisted joint definitions and creates each joint in the target space - * if both endpoint bodies have already been hydrated by body key.

- * - *

Uses deterministic key strings (from {@link PersistentPhysicsJointState#key()}) - * to avoid duplicating joints that already exist in the runtime space. Clears - * the {@code runtimeRestorePending} flag once all persisted joints have been - * restored or terminally skipped.

- * - *

Runs after space bootstrap and body hydration so that both spaces and bodies - * are available for joint creation.

- */ -public class PersistentPhysicsJointHydrationSystem extends TickingSystem { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistentPhysicsSpaceBootstrapSystem.class), - new SystemDependency<>(Order.AFTER, PersistentPhysicsBodyHydrationSystem.class) - ); - - @Nonnull - private final SystemGroup group = ImpulsePlugin.get().getPersistenceRestoreGroup(); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PersistentPhysicsWorldResource persistent = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); - if (!persistent.isRuntimeRestorePending()) { - return; - } - - PhysicsOwnerBridge.run(store, "hydrate persisted physics joints", () -> hydrateJoints(store, - persistent)); - - if (!shouldFinalizeRuntimeRestore(persistent)) { - if (persistent.hasRuntimeRestoreFailed()) { - LOGGER.at(Level.SEVERE).log(persistent.runtimeRestoreFailureSummary()); - } - return; - } - - World world = store.getExternalData().getWorld(); - PersistentPhysicsRestoreTerrainPrewarm.prewarmRestoredDynamicBodyTerrain(store, - world, - PhysicsWorldRuntimeResource.require(store), - persistent, - Math.max(0L, world.getTick())); - persistent.clearRuntimeRestorePending(); - if (persistent.hasRuntimeRestoreSkips()) { - LOGGER.at(Level.WARNING).log(persistent.runtimeRestoreSummary()); - } else { - LOGGER.at(Level.INFO).log(persistent.runtimeRestoreSummary()); - } - } - - static boolean shouldFinalizeRuntimeRestore(@Nonnull PersistentPhysicsWorldResource persistent) { - return persistent.isRuntimeRestorePending() && !persistent.hasRuntimeRestoreFailed(); - } - - private static void hydrateJoints(@Nonnull Store store, - @Nonnull PersistentPhysicsWorldResource persistent) { - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(store); - Set existing = new ObjectOpenHashSet<>(); - for (PhysicsJointRegistration joint : runtime.getJointRegistrations()) { - existing.add(PersistentPhysicsRuntimeSupport.jointKey(joint.spaceId().value(), - joint.bodyA(), - joint.bodyB(), - joint)); - } - - for (PersistentPhysicsJointState state : persistent.getJoints()) { - String key = state.key(); - RigidBodyKey bodyAKey = state.getBodyAKey(); - RigidBodyKey bodyBKey = state.getBodyBKey(); - if (bodyAKey == null || bodyBKey == null) { - persistent.recordRuntimeJointSkipped(key, "missing endpoint body key"); - continue; - } - - if (existing.contains(key)) { - continue; - } - - PhysicsSpaceBinding space = runtime.getSpaceBinding(new SpaceId(state.getSpaceId())); - PhysicsBodyRegistration bodyA = runtime.getRegistration(bodyAKey); - PhysicsBodyRegistration bodyB = runtime.getRegistration(bodyBKey); - if (space == null) { - persistent.recordRuntimeJointSkipped(key, "missing target space"); - continue; - } - if (bodyA == null || bodyB == null) { - if (bodyA == null && bodyB == null) { - persistent.recordRuntimeJointSkipped(key, "missing both endpoint bodies"); - } else if (bodyA == null) { - persistent.recordRuntimeJointSkipped(key, "missing body A"); - } else { - persistent.recordRuntimeJointSkipped(key, "missing body B"); - } - continue; - } - - try { - PersistentPhysicsRuntimeSupport.createJoint(runtime, space, state, bodyAKey, bodyA, bodyBKey, bodyB); - } catch (RuntimeException exception) { - persistent.failRuntimeRestore("Failed to hydrate persisted joint in space " - + state.getSpaceId() - + ": " - + exception.getClass().getSimpleName()); - return; - } - persistent.recordRuntimeJointRestored(); - existing.add(key); - } - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - - @Nonnull - @Override - public SystemGroup getGroup() { - return group; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java deleted file mode 100644 index d4daada4..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsRestoreTerrainPrewarm.java +++ /dev/null @@ -1,150 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsBodyState; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Vector3d; -import org.joml.Vector3f; - -final class PersistentPhysicsRestoreTerrainPrewarm { - - private PersistentPhysicsRestoreTerrainPrewarm() { - } - - static void prewarmRestoredDynamicBodyTerrain(@Nonnull Store store, - @Nonnull World world, - @Nonnull PhysicsWorldRuntimeResource runtime, - @Nonnull PersistentPhysicsWorldResource persistent, - long tick) { - if (!WorldCollisionLifecycle.isEnabled()) { - return; - } - for (Map.Entry> entry : dynamicBodiesBySpace( - persistent.getBodies()).entrySet()) { - SpaceId spaceId = new SpaceId(entry.getKey()); - if (runtime.getSpaceBinding(spaceId) == null) { - continue; - } - PhysicsWorldCollisionSettings settings = - runtime.getLiveSpaceSettings(spaceId).getWorldCollisionSettings(); - if (settings.getWorldCollisionMode() != WorldCollisionMode.STREAMING) { - continue; - } - List targets = dynamicPrewarmTargets(entry.getValue(), - settings.getWorldCollisionBodyRadius()); - if (targets.isEmpty()) { - continue; - } - UUID spaceUuid = physicsStore(world) - .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(spaceId); - if (spaceUuid == null) { - continue; - } - store.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()) - .ensureAround(world, - spaceUuid, - terrainMutationQueue(world), - targets, - settings.getWorldCollisionBodyRadius(), - tick, - null, - WorldCollisionBuildOptions.fromSettings(settings)); - } - } - - @Nonnull - private static Store physicsStore(@Nonnull World world) { - Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); - PhysicsStoreThreading.requireWorldThread(store, - "read PhysicsStore restore terrain prewarm state"); - return store; - } - - @Nonnull - private static PhysicsTerrainMutationQueueResource terrainMutationQueue(@Nonnull World world) { - return physicsStore(world).getResource(PhysicsTerrainMutationQueueResource.getResourceType()); - } - - @Nonnull - static Map> dynamicPrewarmTargetsBySpace( - @Nonnull PersistentPhysicsBodyState[] bodies, - int radius) { - Map> targetsBySpace = new LinkedHashMap<>(); - for (Map.Entry> entry : dynamicBodiesBySpace(bodies).entrySet()) { - targetsBySpace.put(entry.getKey(), dynamicPrewarmTargets(entry.getValue(), radius)); - } - return targetsBySpace; - } - - @Nonnull - private static Map> dynamicBodiesBySpace( - @Nonnull PersistentPhysicsBodyState[] bodies) { - Map> bodiesBySpace = new LinkedHashMap<>(); - for (PersistentPhysicsBodyState body : bodies) { - if (body.restoreValidationFailureReason() != null - || body.resolveSpaceId() <= 0 - || body.getBodyType() != PhysicsBodyType.DYNAMIC - || body.isSensor()) { - continue; - } - bodiesBySpace.computeIfAbsent(body.resolveSpaceId(), ignored -> new ArrayList<>()).add(body); - } - return bodiesBySpace; - } - - @Nonnull - private static List dynamicPrewarmTargets(@Nonnull List bodies, - int radius) { - List targets = new ArrayList<>(); - for (PersistentPhysicsBodyState body : bodies) { - addBodyTargets(targets, - body, - radius); - } - return targets; - } - - private static void addBodyTargets(@Nonnull List targets, - @Nonnull PersistentPhysicsBodyState body, - int radius) { - Vector3f position = body.getPosition(); - Vector3f velocity = body.getLinearVelocity(); - if (!Float.isFinite(velocity.y) || velocity.y >= 0.0f) { - targets.add(new Vector3d(position.x, position.y, position.z)); - return; - } - - double minCenterY = Math.min(position.y, Math.max(0, radius)); - double step = Math.max(1.0, Math.max(0, radius) * 2.0); - double lastY = Double.NaN; - for (double y = position.y; y >= minCenterY; y -= step) { - targets.add(new Vector3d(position.x, y, position.z)); - lastY = y; - } - if (Double.isNaN(lastY) || lastY > minCenterY) { - targets.add(new Vector3d(position.x, minCenterY, position.z)); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java deleted file mode 100644 index 0fcd7f75..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsSpaceBootstrapSystem.java +++ /dev/null @@ -1,196 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRestorePreflight; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsSpaceState; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import java.util.logging.Level; -import javax.annotation.Nonnull; - -/** - * First stage of persistence restore: recreates physics spaces from persisted world state. - * - *

Runs when {@code runtimeRestorePending} is true - * (set after Hytale deserializes the resource or after a manual snapshot load). For each - * persisted space definition, either creates a new runtime space or updates an existing - * one with the persisted gravity and settings.

- * - *

Persisted spaces bind to explicit backend ids. When a saved backend is not - * registered at restore time, the restore is treated as a hard failure and the - * persisted data is left untouched. That behavior keeps backend availability an - * explicit part of the persistence contract instead of silently changing physics - * behavior by falling back to a different backend.

- * - *

Runs before body and joint hydration so that target spaces exist when the - * downstream systems try to add bodies to them. Downstream systems declare - * {@code AFTER} this system in their dependency sets.

- */ -public class PersistentPhysicsSpaceBootstrapSystem extends TickingSystem { - - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private static final ComponentType GENERATED_PROXY_TYPE = - GeneratedVisualProxyComponent.getComponentType(); - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - - @Nonnull - private final SystemGroup group = ImpulsePlugin.get().getPersistenceRestoreGroup(); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PersistentPhysicsWorldResource persistent = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); - if (!persistent.isRuntimeRestorePending() - || persistent.isRuntimeSpaceBootstrapComplete() - || persistent.hasRuntimeRestoreFailed()) { - return; - } - - World world = store.getExternalData().getWorld(); - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(store); - if (physicsStoreOrNull(store) != null) { - stripRuntimePhysicsStateForRestore(store, runtime, world); - persistent.clearRuntimeRestorePending(); - LOGGER.at(Level.INFO).log("Skipped legacy PersistentPhysicsWorldResource restore in " - + "world %s because authoritative PhysicsStore persistence is active", - world.getName()); - return; - } - PersistentPhysicsSpaceState[] spaces = persistent.getSpaces(); - String validationFailure = PersistentPhysicsRestorePreflight.validate(persistent); - if (validationFailure != null) { - persistent.failRuntimeRestore(validationFailure); - LOGGER.at(Level.SEVERE).log(persistent.runtimeRestoreFailureSummary()); - return; - } - stripRuntimePhysicsStateForRestore(store, runtime, world); - try { - PhysicsOwnerBridge.run(store, "restore persisted physics runtime settings", - () -> runtime.setWorldSettings(persistent.getWorldSettings())); - } catch (RuntimeException exception) { - runtime.clearAllSpaces(world.getName()); - persistent.failRuntimeRestore("Invalid persisted physics runtime settings: " - + exception.getMessage()); - LOGGER.at(Level.SEVERE).log(persistent.runtimeRestoreFailureSummary()); - return; - } - - int restoredSpaceCount = 0; - for (PersistentPhysicsSpaceState state : spaces) { - SpaceId spaceId = state.toSpaceId(); - try { - PhysicsOwnerBridge.run(store, "bootstrap persisted physics space", () -> { - PhysicsSpaceBinding targetSpace = runtime.getSpaceBinding(spaceId); - if (targetSpace == null) { - runtime.createSpace(state.toBackendId(), - spaceId, - world.getName(), - state.toSettings()); - targetSpace = runtime.getSpaceBinding(spaceId); - } else { - runtime.setSpaceSettings(spaceId, state.toSettings()); - } - if (targetSpace == null) { - throw new IllegalStateException("Physics space id=" + spaceId - + " was not registered after creation"); - } - targetSpace.runtime().setGravity(targetSpace.backendSpaceHandle().value(), - state.getGravity().x, - state.getGravity().y, - state.getGravity().z); - }); - } catch (RuntimeException exception) { - runtime.clearAllSpaces(world.getName()); - persistent.failRuntimeRestore("Failed to bootstrap space id=" + state.getSpaceId() - + " backend=" + state.getBackendId() + ": " + exception.getMessage()); - LOGGER.at(Level.SEVERE).log( - "%s Cause: %s", - persistent.runtimeRestoreFailureSummary(), - exception.getMessage()); - return; - } - restoredSpaceCount++; - } - - persistent.markRuntimeSpaceBootstrapComplete(restoredSpaceCount); - } - - private static void stripRuntimePhysicsStateForRestore(@Nonnull Store store, - @Nonnull PhysicsWorldResource runtime, - @Nonnull World world) { - Store physicsStore = physicsStoreOrNull(store); - if (physicsStore != null) { - PhysicsStoreRuntimeCleaner.clearAll(physicsStore); - } else { - PhysicsOwnerBridge.run(store, "strip runtime physics state for restore", () -> { - runtime.clearAllSpaces(world.getName()); - runtime.clearBodies(); - }); - } - - store.forEachEntityParallel(ATTACHMENT_TYPE, - (index, archetypeChunk, commandBuffer) -> { - BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, - ATTACHMENT_TYPE); - if (attachment == null - || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { - return; - } - - var ref = archetypeChunk.getReferenceTo(index); - commandBuffer.removeEntity(ref, RemoveReason.REMOVE); - }); - - store.forEachEntityParallel(GENERATED_PROXY_TYPE, - (index, archetypeChunk, commandBuffer) -> { - if (archetypeChunk.getComponent(index, ATTACHMENT_TYPE) != null) { - return; - } - - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); - }); - - if (PhysicsControlSessionComponent.isComponentTypeRegistered()) { - ComponentType controlSessionType = - PhysicsControlSessionComponent.getComponentType(); - store.forEachEntityParallel(controlSessionType, - (index, archetypeChunk, commandBuffer) -> commandBuffer.removeComponent( - archetypeChunk.getReferenceTo(index), - controlSessionType)); - } - } - - private static Store physicsStoreOrNull(@Nonnull Store store) { - World world = store.getExternalData().getWorld(); - return world instanceof PhysicsStoreWorld physicsWorld - ? physicsWorld.getPhysicsStore().getStore() - : null; - } - - @Nonnull - @Override - public SystemGroup getGroup() { - return group; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipeline.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipeline.java deleted file mode 100644 index 588d3930..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipeline.java +++ /dev/null @@ -1,110 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.publication; - -import com.hypixel.hytale.logger.HytaleLogger; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerMutationCompletion; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResult; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCompletion; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.List; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Names the frame boundary between owner execution and reader-visible physics state. - * - *

The publication system uses this class to drain completed mutations, apply one completed - * step snapshot, and write profiling data without blocking the entity-store tick.

- */ -public final class PhysicsPublicationPipeline { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final int MAX_MUTATION_COMPLETIONS_PER_TICK = 64; - - private PhysicsPublicationPipeline() { - } - - public static int publishCompletedMutations(@Nonnull PhysicsOwnerResource owner) { - // Mutation completions report failures and clear handles; they do not publish snapshots. - List completions = - owner.pollCompletedMutations(MAX_MUTATION_COMPLETIONS_PER_TICK); - for (PhysicsOwnerMutationCompletion completion : completions) { - if (completion.executionFailure() != null) { - LOGGER.at(Level.SEVERE).log( - "Async physics owner-lane mutation failed while running %s: %s", - completion.operation(), - completion.executionFailure().getMessage()); - } - } - return completions.size(); - } - - @Nullable - public static PhysicsEventFrame publishCompletedStep(@Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - @Nonnull PhysicsRuntimeProfilingResource profiling) { - return publishCompletedStep(owner, resource, profiling, 0L); - } - - @Nullable - public static PhysicsEventFrame publishCompletedStep(@Nonnull PhysicsOwnerResource owner, - @Nonnull PhysicsWorldResource resource, - @Nonnull PhysicsRuntimeProfilingResource profiling, - long publicationServerTick) { - PhysicsOwnerStepCompletion completion = owner.pollCompletedStep(); - if (completion == null) { - return null; - } - - if (completion.executionFailure() != null) { - LOGGER.at(Level.SEVERE).log("Async physics owner-lane step failed: %s", - completion.executionFailure().getMessage()); - return null; - } - - boolean currentFrame = true; - PublishedPhysicsSnapshotFrame frame = completion.frame(); - if (frame != null) { - // Reader-side apply happens here; command handles may have completed earlier. - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require(resource); - runtime.applyPublishedSnapshotFrame(frame, publicationServerTick); - if (frame.worldEpoch() != runtime.worldEpoch()) { - currentFrame = false; - } - } - - PhysicsOwnerSnapshot snapshot = completion.snapshotOrEmpty(); - if (profiling.isEnabled()) { - PhysicsOwnerResult result = completion.result(); - profiling.recordStep(snapshot.spaces(), - snapshot.substeps(), - snapshot.stepNanos(), - snapshot.bodySnapshots(), - snapshot.spatialIndexCells(), - snapshot.snapshotNanos(), - result != null ? result.queuedNanos() : 0L, - result != null ? result.runNanos() : 0L, - result != null ? result.completedNanos() : 0L, - snapshot.nativePhaseStats(), - completion.preStepDrainedMutations(), - completion.preStepDrainRunNanos(), - completion.lateMutationBacklogAtStep()); - } - - RuntimeException stepFailure = completion.stepFailure(); - if (stepFailure != null) { - LOGGER.at(Level.SEVERE).log( - "Async physics owner-lane step failed after %s completed substeps; snapshots were published with status=%s: %s", - snapshot.substeps(), - frame != null ? frame.status() : "", - stepFailure.getMessage()); - } - return currentFrame ? completion.eventFrame() : null; - } -} From e359d6e76dddacef223c53c76fa41dda72bfd97f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 13:49:57 +0200 Subject: [PATCH 200/534] refactor(core): remove owner lane resource Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 72 -- .../PhysicsWorldRuntimeResource.java | 14 +- .../resources/owner/PhysicsOwnerBridge.java | 226 ----- .../resources/owner/PhysicsOwnerCommand.java | 13 - .../resources/owner/PhysicsOwnerExecutor.java | 44 - .../resources/owner/PhysicsOwnerGateway.java | 84 +- .../resources/owner/PhysicsOwnerHandle.java | 7 - .../owner/PhysicsOwnerLaneResource.java | 803 ------------------ .../owner/PhysicsOwnerLaneScheduler.java | 242 ------ .../owner/PhysicsOwnerMutationCompletion.java | 27 - .../resources/owner/PhysicsOwnerResource.java | 70 -- .../resources/owner/PhysicsOwnerResult.java | 31 - .../resources/owner/PhysicsOwnerSnapshot.java | 51 -- .../owner/PhysicsOwnerStepCommand.java | 506 ----------- .../owner/PhysicsOwnerStepCompletion.java | 43 - 15 files changed, 15 insertions(+), 2218 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerBridge.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCommand.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerExecutor.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerHandle.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneScheduler.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutationCompletion.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResult.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerSnapshot.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommand.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCompletion.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index db508cbe..aa2e79ca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -26,8 +26,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerLaneScheduler; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; @@ -53,7 +51,6 @@ public final class ImpulsePlugin extends JavaPlugin { private static ImpulsePlugin instance; private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - static final String OWNER_POOL_SIZE_PROPERTY = "impulse.ownerPool.size"; @Getter private ComponentType bodyAttachmentComponentType; @@ -73,9 +70,6 @@ public final class ImpulsePlugin extends JavaPlugin { @Getter private ResourceType physicsProjectionIndexResourceType; - @Getter - private ResourceType physicsOwnerResourceType; - @Getter private ResourceType persistentPhysicsWorldResourceType; @@ -88,8 +82,6 @@ public final class ImpulsePlugin extends JavaPlugin { @Nullable private BackendId defaultBackendId; - private PhysicsOwnerLaneScheduler physicsOwnerLaneScheduler; - public ImpulsePlugin(@Nonnull JavaPluginInit init) { super(init); instance = this; @@ -124,10 +116,6 @@ protected void start() { @Override protected void shutdown() { ImpulseCommandContributionRegistry.unregister(); - if (physicsOwnerLaneScheduler != null) { - physicsOwnerLaneScheduler.close(); - physicsOwnerLaneScheduler = null; - } } /** @@ -206,31 +194,6 @@ static BackendId selectDefaultRuntimeProviderId( return providers.iterator().next().getId(); } - static int configuredPositiveInt(@Nonnull String property, - int defaultValue) { - return configuredPositiveIntDetails(property, defaultValue).value(); - } - - @Nonnull - static ConfiguredPositiveInt configuredPositiveIntDetails(@Nonnull String property, - int defaultValue) { - if (defaultValue < 1) { - throw new IllegalArgumentException("defaultValue must be positive"); - } - String configured = System.getProperty(property); - if (configured == null || configured.isBlank()) { - return new ConfiguredPositiveInt(defaultValue, configured, false); - } - try { - int parsed = Integer.parseInt(configured.trim()); - return parsed > 0 - ? new ConfiguredPositiveInt(parsed, configured, false) - : new ConfiguredPositiveInt(defaultValue, configured, true); - } catch (NumberFormatException exception) { - return new ConfiguredPositiveInt(defaultValue, configured, true); - } - } - @Nonnull private String getAvailableBackendIds() { StringBuilder ids = new StringBuilder(); @@ -263,16 +226,6 @@ private void registerComponents() { physicsProjectionIndexResourceType = entityRegistry.registerResource( PhysicsProjectionIndexResource.class, PhysicsProjectionIndexResource::new); - ConfiguredPositiveInt ownerPoolSize = configuredPositiveIntDetails(OWNER_POOL_SIZE_PROPERTY, - PhysicsOwnerLaneScheduler.DEFAULT_POOL_SIZE); - logOwnerPoolSize(ownerPoolSize); - physicsOwnerLaneScheduler = new PhysicsOwnerLaneScheduler( - ownerPoolSize.value(), - PhysicsOwnerLaneScheduler.DEFAULT_QUEUE_CAPACITY, - PhysicsOwnerLaneScheduler.DEFAULT_CLOSE_TIMEOUT); - physicsOwnerResourceType = entityRegistry.registerResource( - PhysicsOwnerResource.class, - physicsOwnerLaneScheduler::createLane); persistentPhysicsWorldResourceType = entityRegistry.registerResource( PersistentPhysicsWorldResource.class, "PersistentPhysicsWorld", @@ -281,31 +234,6 @@ private void registerComponents() { entityRegistry.registerWorldEventType(PhysicsEventFramePublishedEvent.class); } - private static void logOwnerPoolSize(@Nonnull ConfiguredPositiveInt ownerPoolSize) { - String configured = ownerPoolSize.configuredValue(); - if (configured == null || configured.isBlank()) { - LOGGER.at(Level.INFO).log("Physics owner pool size %d (default)", - ownerPoolSize.value()); - return; - } - if (ownerPoolSize.usedFallback()) { - LOGGER.at(Level.WARNING).log("Invalid %s=%s; using physics owner pool size %d", - OWNER_POOL_SIZE_PROPERTY, - configured, - ownerPoolSize.value()); - return; - } - LOGGER.at(Level.INFO).log("Physics owner pool size %d from %s=%s", - ownerPoolSize.value(), - OWNER_POOL_SIZE_PROPERTY, - configured); - } - - record ConfiguredPositiveInt(int value, - @Nullable String configuredValue, - boolean usedFallback) { - } - private void registerSystems() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); persistenceRestoreGroup = entityRegistry.registerSystemGroup(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 7a617525..4920df17 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -45,7 +45,6 @@ import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerCallable; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerGateway; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerHandle; import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerMutation; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; @@ -163,21 +162,10 @@ public static PhysicsWorldRuntimeResource require(@Nonnull PhysicsWorldResource "Physics world resource is not the Impulse runtime implementation"); } - public void attachOwnerExecutor(@Nonnull PhysicsOwnerHandle ownerExecutor) { - ownerGateway.attachOwnerExecutor(ownerExecutor); - } - public void attachEntityStore(@Nonnull Store store) { owningStore = Objects.requireNonNull(store, "store"); } - public void detachOwnerExecutor(@Nonnull PhysicsOwnerHandle ownerExecutor) { - ownerGateway.detachOwnerExecutor(ownerExecutor); - if (!ownerGateway.hasOwnerExecutor()) { - lifecycleState.publishDetachedOwnerRegistrationViews(bodyRegistry); - } - } - public void detachEntityStore(@Nonnull Store store) { if (owningStore == store) { owningStore = null; @@ -2743,7 +2731,7 @@ private void clearRuntimeTopologyDirect(boolean clearCollision) { } private void markWorldChanged() { - lifecycleState.markWorldChanged(bodyRegistry, ownerGateway.hasOwnerExecutor()); + lifecycleState.markWorldChanged(bodyRegistry, false); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerBridge.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerBridge.java deleted file mode 100644 index b0cea2f3..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerBridge.java +++ /dev/null @@ -1,226 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Synchronous bridge for code paths that must mutate live physics state in the - * world physics owner context. - */ -public final class PhysicsOwnerBridge { - - private PhysicsOwnerBridge() { - } - - public static void run(@Nonnull Store store, - @Nonnull String operation, - @Nonnull OwnerMutation mutation) { - run(owner(store), operation, mutation); - } - - public static void run(@Nonnull PhysicsOwnerResource owner, - @Nonnull String operation, - @Nonnull OwnerMutation mutation) { - call(owner, operation, () -> { - mutation.run(); - return null; - }); - } - - @Nonnull - public static PhysicsMutationHandle runAsync(@Nonnull Store store, - @Nonnull String operation, - @Nonnull OwnerMutation mutation) { - return runAsync(owner(store), operation, mutation); - } - - @Nonnull - public static PhysicsMutationHandle runAsync(@Nonnull PhysicsOwnerResource owner, - @Nonnull String operation, - @Nonnull OwnerMutation mutation) { - return runAsync(owner, operation, null, mutation); - } - - @Nonnull - public static PhysicsMutationHandle runAsync(@Nonnull Store store, - @Nonnull String operation, - @Nullable T value, - @Nonnull OwnerMutation mutation) { - return runAsync(owner(store), operation, value, mutation); - } - - @Nonnull - public static PhysicsMutationHandle runAsync(@Nonnull PhysicsOwnerResource owner, - @Nonnull String operation, - @Nullable T value, - @Nonnull OwnerMutation mutation) { - Objects.requireNonNull(owner, "owner"); - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(mutation, "mutation"); - if (owner.isOwnerContext()) { - return runInlineAsync(operation, value, mutation); - } - - PhysicsOwnerCommand command = () -> { - mutation.run(); - return PhysicsOwnerSnapshot.empty(); - }; - try { - return owner.submitMutation(operation, value, command); - } catch (RejectedExecutionException exception) { - return PhysicsMutationHandle.failed(operation, value, exception); - } - } - - @Nonnull - public static T call(@Nonnull Store store, - @Nonnull String operation, - @Nonnull OwnerCallable callable) { - return call(owner(store), operation, callable); - } - - @Nonnull - public static T call(@Nonnull PhysicsOwnerResource owner, - @Nonnull String operation, - @Nonnull OwnerCallable callable) { - Objects.requireNonNull(owner, "owner"); - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(callable, "callable"); - if (owner.isOwnerContext()) { - return callInline(operation, callable); - } - - AtomicReference value = new AtomicReference<>(); - PhysicsOwnerCommand command = () -> { - value.set(callable.call()); - return PhysicsOwnerSnapshot.empty(); - }; - try { - owner.submitAndDrain(command); - return value.get(); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Interrupted while running physics owner lane operation " - + operation, exception); - } catch (ExecutionException exception) { - throw ownerFailure(operation, exception.getCause()); - } catch (RejectedExecutionException exception) { - throw ownerFailure(operation, exception); - } - } - - @Nonnull - public static CompletableFuture callAsync(@Nonnull PhysicsOwnerResource owner, - @Nonnull String operation, - @Nonnull OwnerCallable callable) { - Objects.requireNonNull(owner, "owner"); - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(callable, "callable"); - if (owner.isOwnerContext()) { - return callInlineAsync(callable); - } - - CompletableFuture completion = new CompletableFuture<>(); - AtomicReference value = new AtomicReference<>(); - PhysicsOwnerCommand command = () -> { - value.set(callable.call()); - return PhysicsOwnerSnapshot.empty(); - }; - try { - owner.submitMutationFuture(operation, command) - .whenComplete((ignored, failure) -> { - if (failure != null) { - completion.completeExceptionally(failure); - } else { - completion.complete(value.get()); - } - }); - } catch (RejectedExecutionException exception) { - completion.completeExceptionally(exception); - } - return completion; - } - - @Nonnull - private static PhysicsOwnerResource owner(@Nonnull Store store) { - return store.getResource(PhysicsOwnerResource.getResourceType()); - } - - @Nonnull - private static T callInline(@Nonnull String operation, - @Nonnull OwnerCallable callable) { - try { - return callable.call(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException(ownerFailureMessage(operation, exception), exception); - } - } - - @Nonnull - private static PhysicsMutationHandle runInlineAsync(@Nonnull String operation, - @Nullable T value, - @Nonnull OwnerMutation mutation) { - try { - mutation.run(); - return PhysicsMutationHandle.completed(operation, value); - } catch (Throwable throwable) { - return PhysicsMutationHandle.failed(operation, value, throwable); - } - } - - @Nonnull - private static CompletableFuture callInlineAsync(@Nonnull OwnerCallable callable) { - try { - return CompletableFuture.completedFuture(callable.call()); - } catch (Throwable throwable) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(throwable); - return completion; - } - } - - @Nonnull - private static RuntimeException ownerFailure(@Nonnull String operation, - @Nonnull Throwable cause) { - if (cause instanceof RuntimeException runtimeException) { - return runtimeException; - } - return new IllegalStateException(ownerFailureMessage(operation, cause), cause); - } - - @Nonnull - private static String ownerFailureMessage(@Nonnull String operation, - @Nonnull Throwable cause) { - StringBuilder message = new StringBuilder("Physics owner lane operation ") - .append(operation) - .append(" failed: ") - .append(cause.getClass().getSimpleName()); - String causeMessage = cause.getMessage(); - if (causeMessage != null && !causeMessage.isBlank()) { - message.append(": ").append(causeMessage); - } - return message.toString(); - } - - @FunctionalInterface - public interface OwnerCallable { - - T call() throws Exception; - } - - @FunctionalInterface - public interface OwnerMutation { - - void run() throws Exception; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCommand.java deleted file mode 100644 index aa8b322d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCommand.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import javax.annotation.Nonnull; - -/** - * Unit of work executed within a serialized physics owner context. - */ -@FunctionalInterface -public interface PhysicsOwnerCommand { - - @Nonnull - PhysicsOwnerSnapshot run() throws Exception; -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerExecutor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerExecutor.java deleted file mode 100644 index 6fc4b29e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerExecutor.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.util.concurrent.CompletableFuture; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Internal serialized owner lane for one world physics resource. - * - *

Callbacks routed through this executor may touch backend runtime state. Implementations define - * owner-context membership with their own lane token; callers must not assume a stable Java thread - * owns a world.

- */ -public interface PhysicsOwnerExecutor { - - /** - * Returns whether the current thread is already executing this exact owner lane. - */ - boolean isOwnerContext(); - - /** - * Returns whether the current thread is running a user-visible owner completion callback. - */ - default boolean isCompletionCallbackContext() { - return false; - } - - void run(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation); - - @Nonnull - PhysicsMutationHandle enqueue(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation); - - @Nonnull - CompletableFuture enqueueCall(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable); - - @Nonnull - T call(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java index f439760d..42408d05 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java @@ -3,71 +3,37 @@ import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * Internal owner-lane gateway for one world physics resource. + * Direct compatibility gateway for legacy world-resource operations. * - *

This class centralizes owner-context routing while {@code PhysicsWorldResource} remains the - * plugin-facing facade. Callbacks routed through this gateway may touch backend runtime state; - * ordinary world-thread reads should use published snapshots instead.

+ *

Authoritative PhysicsStore paths do not use this gateway. Remaining legacy + * {@code PhysicsWorldResource} methods run directly when the early PhysicsStore is not active.

*/ public final class PhysicsOwnerGateway { - private final AtomicReference ownerExecutor = new AtomicReference<>(); - - public void attachOwnerExecutor(@Nonnull PhysicsOwnerExecutor ownerExecutor) { - this.ownerExecutor.set(Objects.requireNonNull(ownerExecutor, "ownerExecutor")); - } - - public void detachOwnerExecutor(@Nonnull PhysicsOwnerExecutor ownerExecutor) { - this.ownerExecutor.compareAndSet(Objects.requireNonNull(ownerExecutor, "ownerExecutor"), null); - } - /** * Returns whether the current thread may touch live backend objects without routing. */ public boolean canAccessLiveBackendDirectly() { - PhysicsOwnerExecutor executor = ownerExecutor.get(); - return executor == null || executor.isOwnerContext(); - } - - public boolean hasOwnerExecutor() { - return ownerExecutor.get() != null; + return true; } public void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { Objects.requireNonNull(operation, "operation"); - if (!canAccessLiveBackendDirectly()) { - throw new IllegalStateException("Impulse live backend operation " + operation - + " must run in the physics owner lane. Use PhysicsStore row mutation, " - + "queued reads, or an internal owner-routed resource method."); - } } public void rejectSynchronousCompletionCallbackWait(@Nonnull String operation) { Objects.requireNonNull(operation, "operation"); - PhysicsOwnerExecutor executor = ownerExecutor.get(); - if (executor != null && executor.isCompletionCallbackContext()) { - throw new RejectedExecutionException( - "cannot synchronously wait for physics owner operation " + operation - + " from a completion callback"); - } } public void run(@Nonnull String operation, @Nonnull PhysicsOwnerMutation mutation) { Objects.requireNonNull(operation, "operation"); Objects.requireNonNull(mutation, "mutation"); - PhysicsOwnerExecutor executor = ownerExecutor.get(); - if (executor == null || executor.isOwnerContext()) { - runDirect(operation, mutation); - return; - } - executor.run(operation, mutation); + runDirect(operation, mutation); } @Nonnull @@ -82,15 +48,7 @@ public PhysicsMutationHandle enqueue(@Nonnull String operation, @Nonnull PhysicsOwnerMutation mutation) { Objects.requireNonNull(operation, "operation"); Objects.requireNonNull(mutation, "mutation"); - PhysicsOwnerExecutor executor = ownerExecutor.get(); - if (executor == null || executor.isOwnerContext()) { - return runDirectAsync(operation, value, mutation); - } - try { - return executor.enqueue(operation, value, mutation); - } catch (RejectedExecutionException exception) { - return PhysicsMutationHandle.failed(operation, value, exception); - } + return runDirectAsync(operation, value, mutation); } @Nonnull @@ -98,17 +56,7 @@ public CompletableFuture enqueueCall(@Nonnull String operation, @Nonnull PhysicsOwnerCallable callable) { Objects.requireNonNull(operation, "operation"); Objects.requireNonNull(callable, "callable"); - PhysicsOwnerExecutor executor = ownerExecutor.get(); - if (executor == null || executor.isOwnerContext()) { - return callDirectAsync(callable); - } - try { - return executor.enqueueCall(operation, callable); - } catch (RejectedExecutionException exception) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(exception); - return completion; - } + return callDirectAsync(callable); } @Nonnull @@ -116,18 +64,14 @@ public T call(@Nonnull String operation, @Nonnull PhysicsOwnerCallable callable) { Objects.requireNonNull(operation, "operation"); Objects.requireNonNull(callable, "callable"); - PhysicsOwnerExecutor executor = ownerExecutor.get(); - if (executor == null || executor.isOwnerContext()) { - try { - return callable.call(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("Physics operation " + operation + " failed", - exception); - } + try { + return callable.call(); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IllegalStateException("Physics operation " + operation + " failed", + exception); } - return executor.call(operation, callable); } private static void runDirect(@Nonnull String operation, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerHandle.java deleted file mode 100644 index 5c2a0a09..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerHandle.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -/** - * Internal opaque owner lane for live physics backend state. - */ -public interface PhysicsOwnerHandle extends PhysicsOwnerExecutor { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneResource.java deleted file mode 100644 index 2444835b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneResource.java +++ /dev/null @@ -1,803 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerCommand; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerMutationCompletion; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResult; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCommand; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCompletion; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.time.Duration; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Per-world owner lane backed by a shared {@link PhysicsOwnerLaneScheduler}. - */ -public final class PhysicsOwnerLaneResource implements PhysicsOwnerResource { - - private final Object lock = new Object(); - private final PhysicsOwnerLaneScheduler scheduler; - private final int queueCapacity; - @Nonnull - private final Duration closeTimeout; - private final ArrayDeque mutationQueue = new ArrayDeque<>(); - private final ArrayDeque stepQueue = new ArrayDeque<>(); - private final ArrayDeque pendingMutations = new ArrayDeque<>(); - private long nextSequence = 1L; - private boolean started; - private boolean accepting; - private boolean closing; - private boolean closed; - private boolean active; - private boolean activePreStepDrain; - private int activeCompletions; - @Nullable - private QueuedCommand activeCommand; - @Nullable - private PendingStep pendingStep; - - PhysicsOwnerLaneResource(@Nonnull PhysicsOwnerLaneScheduler scheduler, - int queueCapacity, - @Nonnull Duration closeTimeout) { - if (queueCapacity < 1) { - throw new IllegalArgumentException("queueCapacity must be positive"); - } - this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); - this.queueCapacity = queueCapacity; - this.closeTimeout = Objects.requireNonNull(closeTimeout, "closeTimeout"); - } - - @Override - public void start(@Nonnull String worldName) { - Objects.requireNonNull(worldName, "worldName"); - synchronized (lock) { - if (closed || started) { - return; - } - started = true; - accepting = true; - } - } - - @Override - public boolean isStarted() { - synchronized (lock) { - return started && accepting && !closed; - } - } - - @Override - public boolean isClosed() { - synchronized (lock) { - return closed; - } - } - - @Nonnull - @Override - public PhysicsOwnerResult submitAndDrain(@Nonnull PhysicsOwnerCommand command) - throws InterruptedException, ExecutionException { - Objects.requireNonNull(command, "command"); - if (isOwnerContext()) { - return runInline(command); - } - rejectSynchronousCompletionCallbackWait(); - rejectSynchronousCrossLaneWait(); - - CompletableFuture future; - synchronized (lock) { - future = enqueueLocked(CommandKind.MUTATION, command).future(); - dispatchIfIdleLocked(); - } - return future.get(); - } - - @Override - public boolean submitStepIfIdle(@Nonnull PhysicsOwnerCommand command) { - Objects.requireNonNull(command, "command"); - if (!(command instanceof PhysicsOwnerStepCommand stepCommand)) { - throw new IllegalArgumentException("Physics owner step command must capture step metadata"); - } - synchronized (lock) { - requireAcceptingLocked(); - if (pendingStep != null) { - return false; - } - QueuedCommand queuedCommand = enqueueLocked(CommandKind.STEP, stepCommand); - pendingStep = new PendingStep(stepCommand, - queuedCommand.future(), - queuedCommand.submittedNanos()); - dispatchIfIdleLocked(); - return true; - } - } - - @Nonnull - @Override - public PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nonnull PhysicsOwnerCommand command) { - return submitMutation(operation, null, command); - } - - @Nonnull - @Override - public PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerCommand command) { - CompletableFuture future = submitMutationFuture(operation, command); - return PhysicsMutationHandle.fromCompletion(operation, value, future); - } - - @Nonnull - @Override - public CompletableFuture submitMutationFuture(@Nonnull String operation, - @Nonnull PhysicsOwnerCommand command) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(command, "command"); - synchronized (lock) { - if (pendingMutations.size() >= queueCapacity) { - throw new RejectedExecutionException("physics owner lane mutation completion backlog is full"); - } - CompletableFuture future = - enqueueLocked(CommandKind.MUTATION, command).future(); - pendingMutations.add(new PendingMutation(operation, future)); - dispatchIfIdleLocked(); - return future; - } - } - - @Nonnull - @Override - public List pollCompletedMutations(int maxCompletions) { - int limit = Math.max(0, maxCompletions); - if (limit == 0) { - return Collections.emptyList(); - } - synchronized (lock) { - PendingMutation first = pendingMutations.peek(); - if (first == null || !first.future().isDone()) { - return Collections.emptyList(); - } - List completions = new ArrayList<>(limit); - while (completions.size() < limit) { - PendingMutation current = pendingMutations.peek(); - if (current == null || !current.future().isDone()) { - break; - } - pendingMutations.poll(); - completions.add(toMutationCompletion(current)); - } - return completions; - } - } - - @Nullable - @Override - public PhysicsOwnerStepCompletion pollCompletedStep() { - PendingStep completed; - synchronized (lock) { - PendingStep current = pendingStep; - if (current == null || !current.future().isDone()) { - return null; - } - pendingStep = null; - completed = current; - } - - try { - PhysicsOwnerResult result = completed.future().get(); - return new PhysicsOwnerStepCompletion(result, - completed.command().publishedFrame(), - completed.command().eventFrame(), - completed.command().failure(), - completed.preStepDrainedMutations(), - completed.preStepDrainRunNanos(), - completed.lateMutationBacklogAtStep(), - null); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - return new PhysicsOwnerStepCompletion(null, - null, - null, - null, - completed.preStepDrainedMutations(), - completed.preStepDrainRunNanos(), - completed.lateMutationBacklogAtStep(), - exception); - } catch (ExecutionException exception) { - return new PhysicsOwnerStepCompletion(null, - null, - null, - null, - completed.preStepDrainedMutations(), - completed.preStepDrainRunNanos(), - completed.lateMutationBacklogAtStep(), - exception.getCause()); - } - } - - @Override - public boolean hasPendingStep() { - synchronized (lock) { - return pendingStep != null; - } - } - - @Override - public long pendingStepAgeNanos() { - synchronized (lock) { - PendingStep current = pendingStep; - return current == null ? 0L : Math.max(0L, System.nanoTime() - current.submittedNanos()); - } - } - - @Override - public int pendingMutations() { - synchronized (lock) { - return pendingMutations.size(); - } - } - - @Override - public int pendingCommands() { - synchronized (lock) { - return queuedCommandCountLocked() + (activeCommandPendingLocked() ? 1 : 0); - } - } - - @Override - public boolean isOwnerContext() { - return scheduler.isCurrentLane(this); - } - - @Override - public boolean isCompletionCallbackContext() { - return scheduler.isCompletingAnyLane(); - } - - @Override - public void run(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(mutation, "mutation"); - if (isOwnerContext()) { - runDirect(operation, mutation); - return; - } - rejectSynchronousCompletionCallbackWait(); - rejectSynchronousCrossLaneWait(); - try { - submitAndDrain(() -> { - mutation.run(); - return PhysicsOwnerSnapshot.empty(); - }); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Interrupted while running physics owner lane operation " - + operation, exception); - } catch (ExecutionException exception) { - throw ownerFailure(operation, exception.getCause()); - } - } - - @Nonnull - @Override - public PhysicsMutationHandle enqueue(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(mutation, "mutation"); - if (isOwnerContext()) { - return runDirectAsync(operation, value, mutation); - } - return submitMutation(operation, value, () -> { - mutation.run(); - return PhysicsOwnerSnapshot.empty(); - }); - } - - @Nonnull - @Override - public CompletableFuture enqueueCall(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(callable, "callable"); - if (isOwnerContext()) { - return callDirectAsync(callable); - } - - CompletableFuture completion = new CompletableFuture<>(); - AtomicReference value = new AtomicReference<>(); - try { - submitMutationFuture(operation, () -> { - value.set(callable.call()); - return PhysicsOwnerSnapshot.empty(); - }).whenComplete((ignored, failure) -> { - if (failure != null) { - completion.completeExceptionally(failure); - } else { - completion.complete(value.get()); - } - }); - } catch (RejectedExecutionException exception) { - completion.completeExceptionally(exception); - } - return completion; - } - - @Nonnull - @Override - public T call(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(callable, "callable"); - if (isOwnerContext()) { - return callDirect(operation, callable); - } - rejectSynchronousCompletionCallbackWait(); - rejectSynchronousCrossLaneWait(); - - AtomicReference value = new AtomicReference<>(); - try { - submitAndDrain(() -> { - value.set(callable.call()); - return PhysicsOwnerSnapshot.empty(); - }); - return value.get(); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Interrupted while running physics owner lane operation " - + operation, exception); - } catch (ExecutionException exception) { - throw ownerFailure(operation, exception.getCause()); - } - } - - @Override - public void close() { - if (!isClosed()) { - rejectSynchronousCompletionCallbackWait(); - rejectSynchronousCrossLaneWait(); - rejectSynchronousOwnerContextWait(); - } - - synchronized (lock) { - if (closed) { - return; - } - closing = true; - accepting = false; - dispatchIfIdleLocked(); - } - - RuntimeException timeout = null; - long timeoutNanos = Math.max(0L, closeTimeout.toNanos()); - long deadline = timeoutNanos == 0L - ? Long.MAX_VALUE - : System.nanoTime() + timeoutNanos; - boolean interrupted = false; - synchronized (lock) { - while (!closed && (active - || activeCompletions > 0 - || !mutationQueue.isEmpty() - || !stepQueue.isEmpty())) { - long remainingNanos = deadline - System.nanoTime(); - if (remainingNanos <= 0L) { - timeout = new IllegalStateException( - "Physics owner lane did not stop within " + closeTimeout); - break; - } - try { - long millis = TimeUnit.NANOSECONDS.toMillis(remainingNanos); - int nanos = (int) (remainingNanos - TimeUnit.MILLISECONDS.toNanos(millis)); - lock.wait(millis, nanos); - } catch (InterruptedException exception) { - interrupted = true; - } - } - if (timeout == null && !closed) { - closeIfReadyLocked(); - if (!closed) { - closed = true; - scheduler.unregister(this); - lock.notifyAll(); - } - } - } - if (interrupted) { - Thread.currentThread().interrupt(); - } - if (timeout != null) { - throw timeout; - } - } - - @Nonnull - @Override - public PhysicsOwnerLaneResource clone() { - return scheduler.createLane(); - } - - private void runQueuedCommand(@Nonnull QueuedCommand command) { - long startNanos = System.nanoTime(); - PhysicsOwnerResult result = null; - Throwable failure = null; - long commandRunNanos; - try { - PhysicsOwnerSnapshot snapshot = command.command().run(); - long completedNanos = System.nanoTime(); - result = new PhysicsOwnerResult(command.sequence(), - snapshot, - startNanos - command.submittedNanos(), - completedNanos - startNanos, - completedNanos); - commandRunNanos = result.runNanos(); - } catch (Throwable throwable) { - failure = throwable; - commandRunNanos = Math.max(0L, System.nanoTime() - startNanos); - } - - completeCommandFuture(command, result, failure); - - synchronized (lock) { - if (activePreStepDrain && pendingStep != null) { - pendingStep.recordPreStepDrain(commandRunNanos); - } - active = false; - activePreStepDrain = false; - activeCommand = null; - closeIfReadyLocked(); - dispatchIfIdleLocked(); - lock.notifyAll(); - } - } - - private void completeCommandFuture(@Nonnull QueuedCommand command, - @Nullable PhysicsOwnerResult result, - @Nullable Throwable failure) { - recordCompletionScheduled(); - scheduler.completeOutsideOwnerContext(this, () -> { - try { - if (failure == null) { - command.future().complete(result); - } else { - command.future().completeExceptionally(failure); - } - } finally { - recordCompletionFinished(); - } - }); - } - - @Nonnull - private PhysicsOwnerResult runInline(@Nonnull PhysicsOwnerCommand command) - throws ExecutionException { - long sequence; - synchronized (lock) { - sequence = nextSequence++; - } - long startNanos = System.nanoTime(); - try { - PhysicsOwnerSnapshot snapshot = command.run(); - long completedNanos = System.nanoTime(); - return new PhysicsOwnerResult(sequence, - snapshot, - 0L, - completedNanos - startNanos, - completedNanos); - } catch (Throwable throwable) { - throw new ExecutionException(throwable); - } - } - - @Nonnull - private QueuedCommand enqueueLocked( - @Nonnull CommandKind kind, - @Nonnull PhysicsOwnerCommand command) { - requireAcceptingLocked(); - if (queuedCommandCountLocked() >= queueCapacity) { - throw new RejectedExecutionException("physics owner lane command queue is full"); - } - CompletableFuture future = new CompletableFuture<>(); - QueuedCommand queuedCommand = new QueuedCommand(nextSequence++, - command, - System.nanoTime(), - future); - if (kind == CommandKind.STEP) { - stepQueue.addLast(new QueuedStep(queuedCommand, queuedCommand.sequence() - 1L)); - } else { - mutationQueue.addLast(queuedCommand); - } - return queuedCommand; - } - - @Nullable - private QueuedCommand nextCommandLocked() { - activePreStepDrain = false; - QueuedStep queuedStep = stepQueue.peekFirst(); - if (queuedStep == null) { - return mutationQueue.pollFirst(); - } - QueuedCommand mutation = mutationQueue.peekFirst(); - if (mutation != null && mutation.sequence() <= queuedStep.drainBeforeSequence()) { - activePreStepDrain = true; - return mutationQueue.pollFirst(); - } - PendingStep currentStep = pendingStep; - if (currentStep != null) { - currentStep.recordLateMutationBacklog(mutationQueue.size()); - } - return stepQueue.pollFirst().command(); - } - - private void dispatchIfIdleLocked() { - if (!started || closed || active) { - return; - } - QueuedCommand next = nextCommandLocked(); - if (next == null) { - if (closing) { - closeIfReadyLocked(); - lock.notifyAll(); - } - return; - } - active = true; - activeCommand = next; - try { - scheduler.execute(this, () -> runQueuedCommand(next)); - } catch (RejectedExecutionException exception) { - active = false; - activePreStepDrain = false; - activeCommand = null; - completeCommandFuture(next, null, exception); - closeIfReadyLocked(); - if (closed) { - lock.notifyAll(); - return; - } - dispatchIfIdleLocked(); - } - } - - private void recordCompletionScheduled() { - synchronized (lock) { - activeCompletions++; - } - } - - private void recordCompletionFinished() { - synchronized (lock) { - if (activeCompletions <= 0) { - throw new IllegalStateException("physics owner lane completion count underflow"); - } - activeCompletions--; - closeIfReadyLocked(); - lock.notifyAll(); - } - } - - private void closeIfReadyLocked() { - if (!closing || closed || active || activeCompletions > 0 - || !mutationQueue.isEmpty() || !stepQueue.isEmpty()) { - return; - } - closed = true; - scheduler.unregister(this); - } - - private int queuedCommandCountLocked() { - return mutationQueue.size() + stepQueue.size(); - } - - private boolean activeCommandPendingLocked() { - if (!active) { - return false; - } - return activeCommand == null || !activeCommand.future().isDone(); - } - - private void requireAcceptingLocked() { - if (!started) { - throw new RejectedExecutionException("physics owner lane is not started"); - } - if (!accepting || closing || closed) { - throw new RejectedExecutionException("physics owner lane is closed"); - } - } - - private void rejectSynchronousCrossLaneWait() { - PhysicsOwnerLaneResource currentLane = scheduler.currentLane(); - if (currentLane != null && currentLane != this) { - throw new RejectedExecutionException( - "cannot synchronously wait for a different physics owner lane"); - } - } - - private void rejectSynchronousCompletionCallbackWait() { - if (scheduler.isCompletingAnyLane()) { - throw new RejectedExecutionException( - "cannot synchronously wait for a physics owner lane from a completion callback"); - } - } - - private void rejectSynchronousOwnerContextWait() { - if (isOwnerContext()) { - throw new RejectedExecutionException( - "cannot synchronously wait for a physics owner lane from its owner context"); - } - } - - @Nonnull - private static PhysicsOwnerMutationCompletion toMutationCompletion( - @Nonnull PendingMutation mutation) { - try { - return new PhysicsOwnerMutationCompletion(mutation.operation(), - mutation.future().get(), - null); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - return new PhysicsOwnerMutationCompletion(mutation.operation(), null, exception); - } catch (ExecutionException exception) { - return new PhysicsOwnerMutationCompletion(mutation.operation(), - null, - exception.getCause()); - } - } - - private static void runDirect(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - try { - mutation.run(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("Physics operation " + operation + " failed", - exception); - } - } - - @Nonnull - private static PhysicsMutationHandle runDirectAsync(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - try { - mutation.run(); - return PhysicsMutationHandle.completed(operation, value); - } catch (Throwable throwable) { - return PhysicsMutationHandle.failed(operation, value, throwable); - } - } - - @Nonnull - private static CompletableFuture callDirectAsync(@Nonnull PhysicsOwnerCallable callable) { - try { - return CompletableFuture.completedFuture(callable.call()); - } catch (Throwable throwable) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(throwable); - return completion; - } - } - - @Nonnull - private static T callDirect(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - try { - return callable.call(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("Physics operation " + operation + " failed", - exception); - } - } - - @Nonnull - private static RuntimeException ownerFailure(@Nonnull String operation, - @Nonnull Throwable cause) { - if (cause instanceof RuntimeException runtimeException) { - return runtimeException; - } - return new IllegalStateException(ownerFailureMessage(operation, cause), cause); - } - - @Nonnull - private static String ownerFailureMessage(@Nonnull String operation, - @Nonnull Throwable cause) { - StringBuilder message = new StringBuilder("Physics owner lane operation ") - .append(operation) - .append(" failed: ") - .append(cause.getClass().getSimpleName()); - String causeMessage = cause.getMessage(); - if (causeMessage != null && !causeMessage.isBlank()) { - message.append(": ").append(causeMessage); - } - return message.toString(); - } - - private enum CommandKind { - STEP, - MUTATION - } - - private record QueuedCommand(long sequence, - @Nonnull PhysicsOwnerCommand command, - long submittedNanos, - @Nonnull CompletableFuture future) { - } - - private record QueuedStep(@Nonnull QueuedCommand command, - long drainBeforeSequence) { - } - - private static final class PendingStep { - - @Nonnull - private final PhysicsOwnerStepCommand command; - @Nonnull - private final CompletableFuture future; - private final long submittedNanos; - private int preStepDrainedMutations; - private long preStepDrainRunNanos; - private int lateMutationBacklogAtStep; - - private PendingStep(@Nonnull PhysicsOwnerStepCommand command, - @Nonnull CompletableFuture future, - long submittedNanos) { - this.command = command; - this.future = future; - this.submittedNanos = submittedNanos; - } - - @Nonnull - private PhysicsOwnerStepCommand command() { - return command; - } - - @Nonnull - private CompletableFuture future() { - return future; - } - - private long submittedNanos() { - return submittedNanos; - } - - private int preStepDrainedMutations() { - return preStepDrainedMutations; - } - - private long preStepDrainRunNanos() { - return preStepDrainRunNanos; - } - - private int lateMutationBacklogAtStep() { - return lateMutationBacklogAtStep; - } - - private void recordPreStepDrain(long runNanos) { - preStepDrainedMutations++; - preStepDrainRunNanos += Math.max(0L, runNanos); - } - - private void recordLateMutationBacklog(int backlog) { - lateMutationBacklogAtStep = Math.max(0, backlog); - } - } - - private record PendingMutation(@Nonnull String operation, - @Nonnull CompletableFuture future) { - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneScheduler.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneScheduler.java deleted file mode 100644 index 4b4a5d87..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneScheduler.java +++ /dev/null @@ -1,242 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Shared execution pool for serialized per-world physics owner lanes. - */ -public final class PhysicsOwnerLaneScheduler implements AutoCloseable { - - public static final int DEFAULT_POOL_SIZE = 1; - public static final int DEFAULT_QUEUE_CAPACITY = 128; - public static final Duration DEFAULT_CLOSE_TIMEOUT = Duration.ofSeconds(5L); - - private static final ThreadLocal CURRENT_LANE = - new ThreadLocal<>(); - private static final ThreadLocal COMPLETING_LANE = - new ThreadLocal<>(); - - private final Object lifecycleLock = new Object(); - private final Set lanes = ConcurrentHashMap.newKeySet(); - private final ExecutorService executor; - private final ExecutorService completionExecutor; - private final int queueCapacity; - @Nonnull - private final Duration closeTimeout; - private boolean closing; - private boolean closed; - - public PhysicsOwnerLaneScheduler(int poolSize, - int queueCapacity, - @Nonnull Duration closeTimeout) { - if (poolSize < 1) { - throw new IllegalArgumentException("poolSize must be positive"); - } - if (queueCapacity < 1) { - throw new IllegalArgumentException("queueCapacity must be positive"); - } - this.queueCapacity = queueCapacity; - this.closeTimeout = Objects.requireNonNull(closeTimeout, "closeTimeout"); - executor = Executors.newFixedThreadPool(poolSize, new OwnerLaneThreadFactory()); - completionExecutor = Executors.newThreadPerTaskExecutor( - Thread.ofVirtual().name("Impulse physics owner completion ", 1).factory()); - } - - @Nonnull - public PhysicsOwnerLaneResource createLane() { - synchronized (lifecycleLock) { - if (closing || closed) { - throw new RejectedExecutionException("physics owner lane scheduler is closed"); - } - PhysicsOwnerLaneResource lane = new PhysicsOwnerLaneResource(this, - queueCapacity, - closeTimeout); - lanes.add(lane); - return lane; - } - } - - boolean isCurrentLane(@Nonnull PhysicsOwnerLaneResource lane) { - return CURRENT_LANE.get() == lane; - } - - boolean isCompletingAnyLane() { - return COMPLETING_LANE.get() != null; - } - - @Nullable - PhysicsOwnerLaneResource currentLane() { - return CURRENT_LANE.get(); - } - - void execute(@Nonnull PhysicsOwnerLaneResource lane, - @Nonnull Runnable command) { - Objects.requireNonNull(lane, "lane"); - Objects.requireNonNull(command, "command"); - synchronized (lifecycleLock) { - if (closed || !lanes.contains(lane)) { - throw new RejectedExecutionException("physics owner lane is not registered"); - } - executor.execute(() -> runInLane(lane, command)); - } - } - - void completeOutsideOwnerContext(@Nonnull PhysicsOwnerLaneResource lane, - @Nonnull Runnable completion) { - Objects.requireNonNull(lane, "lane"); - Objects.requireNonNull(completion, "completion"); - try { - completionExecutor.execute(() -> runCompletion(lane, completion)); - } catch (RejectedExecutionException exception) { - try { - runCompletion(lane, completion); - } catch (RuntimeException | Error completionFailure) { - exception.addSuppressed(completionFailure); - } - } - } - - void unregister(@Nonnull PhysicsOwnerLaneResource lane) { - lanes.remove(Objects.requireNonNull(lane, "lane")); - } - - @Override - public void close() { - if (CURRENT_LANE.get() != null || COMPLETING_LANE.get() != null) { - throw new RejectedExecutionException( - "cannot synchronously close the physics owner lane scheduler from owner context"); - } - - ArrayList lanesToClose; - synchronized (lifecycleLock) { - if (closed) { - return; - } - closing = true; - lanesToClose = new ArrayList<>(lanes); - } - - RuntimeException closeFailure = null; - for (PhysicsOwnerLaneResource lane : lanesToClose) { - try { - lane.close(); - } catch (RuntimeException exception) { - if (closeFailure == null) { - closeFailure = exception; - } else { - closeFailure.addSuppressed(exception); - } - } - } - - executor.shutdown(); - boolean ownerStopped = awaitExecutorStop(executor); - completionExecutor.shutdown(); - boolean completionStopped = awaitExecutorStop(completionExecutor); - synchronized (lifecycleLock) { - closed = true; - } - if (!ownerStopped || !completionStopped) { - IllegalStateException timeout = new IllegalStateException( - "Physics owner lane scheduler did not stop within " + closeTimeout); - if (closeFailure != null) { - timeout.addSuppressed(closeFailure); - } - throw timeout; - } - if (closeFailure != null) { - throw closeFailure; - } - } - - private void runInLane(@Nonnull PhysicsOwnerLaneResource lane, - @Nonnull Runnable command) { - PhysicsOwnerLaneResource previous = CURRENT_LANE.get(); - CURRENT_LANE.set(lane); - try { - command.run(); - } finally { - if (previous == null) { - CURRENT_LANE.remove(); - } else { - CURRENT_LANE.set(previous); - } - } - } - - private void runCompletion(@Nonnull PhysicsOwnerLaneResource lane, - @Nonnull Runnable completion) { - PhysicsOwnerLaneResource previousCurrent = CURRENT_LANE.get(); - PhysicsOwnerLaneResource previousCompleting = COMPLETING_LANE.get(); - CURRENT_LANE.remove(); - COMPLETING_LANE.set(lane); - try { - completion.run(); - } finally { - if (previousCompleting == null) { - COMPLETING_LANE.remove(); - } else { - COMPLETING_LANE.set(previousCompleting); - } - if (previousCurrent == null) { - CURRENT_LANE.remove(); - } else { - CURRENT_LANE.set(previousCurrent); - } - } - } - - private boolean awaitExecutorStop(@Nonnull ExecutorService target) { - long timeoutNanos = Math.max(0L, closeTimeout.toNanos()); - long deadline = timeoutNanos == 0L - ? Long.MAX_VALUE - : System.nanoTime() + timeoutNanos; - boolean interrupted = false; - try { - while (!target.isTerminated()) { - long remainingNanos = deadline - System.nanoTime(); - if (remainingNanos <= 0L) { - return false; - } - try { - if (target.awaitTermination(remainingNanos, TimeUnit.NANOSECONDS)) { - return true; - } - } catch (InterruptedException exception) { - interrupted = true; - } - } - return true; - } finally { - if (interrupted) { - Thread.currentThread().interrupt(); - } - } - } - - private static final class OwnerLaneThreadFactory implements ThreadFactory { - - private final AtomicInteger nextThread = new AtomicInteger(1); - - @Override - public Thread newThread(@Nonnull Runnable runnable) { - Thread thread = new Thread(runnable, - "Impulse physics owner lane executor " + nextThread.getAndIncrement()); - thread.setDaemon(true); - return thread; - } - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutationCompletion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutationCompletion.java deleted file mode 100644 index dc37c574..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutationCompletion.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Completed non-step owner mutation consumed by the main tick after the future - * is already complete. - */ -public record PhysicsOwnerMutationCompletion(@Nonnull String operation, - @Nullable PhysicsOwnerResult result, - @Nullable Throwable executionFailure) { - - public PhysicsOwnerMutationCompletion { - Objects.requireNonNull(operation, "operation"); - } - - public boolean completedSuccessfully() { - return result != null && executionFailure == null; - } - - @Nonnull - public PhysicsOwnerSnapshot snapshotOrEmpty() { - return result != null ? result.snapshot() : PhysicsOwnerSnapshot.empty(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResource.java deleted file mode 100644 index 163d16da..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResource.java +++ /dev/null @@ -1,70 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Runtime ECS resource that owns serialized live-backend execution for one world. - */ -public interface PhysicsOwnerResource - extends Resource, AutoCloseable, PhysicsOwnerHandle { - - void start(@Nonnull String worldName); - - boolean isStarted(); - - boolean isClosed(); - - @Override - void close(); - - @Nonnull - PhysicsOwnerResult submitAndDrain(@Nonnull PhysicsOwnerCommand command) - throws InterruptedException, ExecutionException; - - boolean submitStepIfIdle(@Nonnull PhysicsOwnerCommand command); - - @Nonnull - PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nonnull PhysicsOwnerCommand command); - - @Nonnull - PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerCommand command); - - @Nonnull - CompletableFuture submitMutationFuture(@Nonnull String operation, - @Nonnull PhysicsOwnerCommand command); - - @Nonnull - List pollCompletedMutations(int maxCompletions); - - @Nullable - PhysicsOwnerStepCompletion pollCompletedStep(); - - boolean hasPendingStep(); - - long pendingStepAgeNanos(); - - int pendingMutations(); - - int pendingCommands(); - - @Nonnull - @Override - PhysicsOwnerResource clone(); - - @Nonnull - static ResourceType getResourceType() { - return ImpulsePlugin.get().getPhysicsOwnerResourceType(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResult.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResult.java deleted file mode 100644 index f1b8bfdc..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerResult.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Completed owner command metadata plus its published step snapshot. - */ -public record PhysicsOwnerResult(long sequence, - @Nonnull PhysicsOwnerSnapshot snapshot, - long queuedNanos, - long runNanos, - long completedNanos) { - - public PhysicsOwnerResult { - if (sequence < 1L) { - throw new IllegalArgumentException("sequence must be positive"); - } - Objects.requireNonNull(snapshot, "snapshot"); - queuedNanos = Math.max(0L, queuedNanos); - runNanos = Math.max(0L, runNanos); - completedNanos = Math.max(0L, completedNanos); - } - - public PhysicsOwnerResult(long sequence, - @Nonnull PhysicsOwnerSnapshot snapshot, - long queuedNanos, - long runNanos) { - this(sequence, snapshot, queuedNanos, runNanos, 0L); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerSnapshot.java deleted file mode 100644 index 57145e39..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerSnapshot.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import javax.annotation.Nonnull; - -/** - * Step output published by a physics owner command. - */ -public record PhysicsOwnerSnapshot(int spaces, - int substeps, - int bodySnapshots, - int spatialIndexCells, - long stepNanos, - long snapshotNanos, - @Nonnull PhysicsStepPhaseStats nativePhaseStats) { - - public PhysicsOwnerSnapshot { - spaces = Math.max(0, spaces); - substeps = Math.max(0, substeps); - bodySnapshots = Math.max(0, bodySnapshots); - spatialIndexCells = Math.max(0, spatialIndexCells); - stepNanos = Math.max(0L, stepNanos); - snapshotNanos = Math.max(0L, snapshotNanos); - } - - public PhysicsOwnerSnapshot(int spaces, - int substeps, - int bodySnapshots, - int spatialIndexCells, - long stepNanos, - long snapshotNanos) { - this(spaces, - substeps, - bodySnapshots, - spatialIndexCells, - stepNanos, - snapshotNanos, - PhysicsStepPhaseStats.unavailable()); - } - - @Nonnull - public static PhysicsOwnerSnapshot empty() { - return new PhysicsOwnerSnapshot(0, - 0, - 0, - 0, - 0L, - 0L, - PhysicsStepPhaseStats.unavailable()); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommand.java deleted file mode 100644 index d4326481..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommand.java +++ /dev/null @@ -1,506 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.ArrayList; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Owner-lane execution of the world physics step. - * - *

Runtime mutations are owner-lane operations. Callers must - * only submit this command when the lane is the exclusive owner of backend - * spaces and the resource until the result has been consumed.

- * - *

{@code stepSequence} and {@code serverTick} are copied into the published - * snapshot frame for diagnostics and external correlation. They do not control - * publication freshness; that remains guarded by the resource's frame and world - * epochs.

- */ -public final class PhysicsOwnerStepCommand implements PhysicsOwnerCommand { - - private static final float DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP = 0.45f; - private static final float MIN_LINEAR_TRAVEL_PER_SUBSTEP = 0.125f; - private static final float SHAPE_TRAVEL_FRACTION = 0.75f; - private static final float MAX_ANGULAR_RADIANS_PER_SUBSTEP = (float) Math.toRadians(30.0); - - @Nonnull - private final PhysicsWorldRuntimeResource resource; - private final float dt; - private final boolean profilingEnabled; - private final long stepSequence; - private final long serverTick; - @Nullable - private RuntimeException failure; - @Nullable - private PublishedPhysicsSnapshotFrame publishedFrame; - @Nullable - private PhysicsEventFrame eventFrame; - - public PhysicsOwnerStepCommand(@Nonnull PhysicsWorldResource resource, - float dt, - boolean profilingEnabled) { - this(resource, dt, profilingEnabled, 0L, 0L); - } - - /** - * Creates an owner step command with snapshot correlation metadata. - * - * @param stepSequence monotonic Impulse step-scheduler sequence; not a - * Hytale world tick and not guaranteed contiguous in published frames - * @param serverTick Hytale world tick observed when the step command was - * scheduled; not a physics step counter - */ - public PhysicsOwnerStepCommand(@Nonnull PhysicsWorldResource resource, - float dt, - boolean profilingEnabled, - long stepSequence, - long serverTick) { - this.resource = PhysicsWorldRuntimeResource.require( - Objects.requireNonNull(resource, "resource")); - this.dt = dt; - this.profilingEnabled = profilingEnabled; - this.stepSequence = Math.max(0L, stepSequence); - this.serverTick = Math.max(0L, serverTick); - } - - @Nonnull - @Override - public PhysicsOwnerSnapshot run() { - StepExecution result = runStepCycle(resource, - dt, - profilingEnabled, - stepSequence, - serverTick); - failure = result.failure(); - publishedFrame = result.frame(); - eventFrame = result.eventFrame(); - return result.snapshot(); - } - - @Nullable - public RuntimeException failure() { - return failure; - } - - @Nullable - public PublishedPhysicsSnapshotFrame publishedFrame() { - return publishedFrame; - } - - @Nullable - public PhysicsEventFrame eventFrame() { - return eventFrame; - } - - @Nonnull - static PhysicsOwnerSnapshot runStep(@Nonnull PhysicsWorldResource resource, - float dt, - boolean profilingEnabled) { - return runStepCycle(resource, dt, profilingEnabled, 0L, 0L).snapshot(); - } - - @Nonnull - static StepExecution runStepCycle(@Nonnull PhysicsWorldResource resource, - float dt, - boolean profilingEnabled, - long stepSequence, - long serverTick) { - PhysicsWorldRuntimeResource runtime = PhysicsWorldRuntimeResource.require( - Objects.requireNonNull(resource, "resource")); - float safeDt = Float.isFinite(dt) ? Math.max(dt, 0.0f) : 0.0f; - PhysicsWorldSettings settings = runtime.getWorldSettings(); - PhysicsStepMode stepMode = settings.getStepMode(); - PhysicsEventCollectionMode eventCollectionMode = settings.getEventCollectionMode(); - int simulationSteps = settings.getSimulationSteps(); - float configuredMaxStepDt = settings.getMaxStepDt(); - float maxStepDt = configuredMaxStepDt > 0f - ? configuredMaxStepDt - : PhysicsWorldSettings.DEFAULT_MAX_STEP_DT; - int steps = stepMode == PhysicsStepMode.ADAPTIVE - ? resolveAdaptiveStepCount(safeDt, simulationSteps, maxStepDt, runtime) - : PhysicsStepCountPolicy.resolveStepCount(safeDt, - simulationSteps, - maxStepDt, - stepMode); - - long startNanos = profilingEnabled ? System.nanoTime() : 0L; - StepCounters counters = new StepCounters(); - StepBackendEvents backendEvents = new StepBackendEvents(); - RuntimeException stepFailure = null; - try { - if (profilingEnabled) { - resetStepPhaseStats(runtime); - } - if (stepMode != PhysicsStepMode.CCD) { - restoreForcedContinuousCollision(runtime); - } - executeSteps(runtime, - safeDt, - stepMode, - steps, - counters, - backendEvents, - eventCollectionMode.collectsBackendEvents()); - } catch (RuntimeException exception) { - stepFailure = exception; - } - long stepNanos = profilingEnabled ? System.nanoTime() - startNanos : 0L; - - PublishedPhysicsSnapshotFrame frame; - try { - frame = runtime.capturePublishedSnapshotFrame(stepSequence, - serverTick, - stepFailure == null - ? PublishedPhysicsSnapshotFrame.Status.COMPLETE - : PublishedPhysicsSnapshotFrame.Status.PARTIAL, - stepNanos, - profilingEnabled, - backendEvents.physicsEvents, - backendEvents.droppedBackendEventCount); - } catch (RuntimeException exception) { - if (stepFailure != null) { - stepFailure.addSuppressed(exception); - throw stepFailure; - } - throw exception; - } - PhysicsEventFrame eventFrame = runtime.getLatestEventFrame(); - - PhysicsStepPhaseStats nativePhaseStats = profilingEnabled - ? collectStepPhaseStats(runtime) - : PhysicsStepPhaseStats.unavailable(); - return new StepExecution(new PhysicsOwnerSnapshot(counters.spaceCount(), - counters.substeps(), - frame.bodyCount(), - frame.spatialIndexCellCount(), - stepNanos, - frame.snapshotNanos(), - nativePhaseStats), stepFailure, frame, eventFrame); - } - - private static void resetStepPhaseStats(@Nonnull PhysicsWorldRuntimeResource resource) { - for (PhysicsSpaceBinding space : resource.iterateSpaceBindings()) { - space.runtime().resetStepPhaseStats(space.backendSpaceHandle().value()); - } - } - - @Nonnull - private static PhysicsStepPhaseStats collectStepPhaseStats( - @Nonnull PhysicsWorldRuntimeResource resource) { - PhysicsStepPhaseStats stats = PhysicsStepPhaseStats.unavailable(); - for (PhysicsSpaceBinding space : resource.iterateSpaceBindings()) { - StepPhaseStatsCapture spaceStats = new StepPhaseStatsCapture(); - space.runtime().stepPhaseStats(space.backendSpaceHandle().value(), - spaceStats); - stats = stats.add(spaceStats.value()); - } - return stats; - } - - private static final class StepPhaseStatsCapture implements BackendStepPhaseStatsSink { - - @Nonnull - private PhysicsStepPhaseStats value = PhysicsStepPhaseStats.unavailable(); - - @Override - public void accept(long stepNanos, - long broadPhaseNanos, - long narrowPhaseNanos, - long solverNanos, - long continuousCollisionNanos, - long snapshotNanos, - boolean available) { - value = available - ? PhysicsStepPhaseStats.available(stepNanos, - broadPhaseNanos, - narrowPhaseNanos, - solverNanos, - continuousCollisionNanos, - snapshotNanos) - : PhysicsStepPhaseStats.unavailable(); - } - - @Nonnull - private PhysicsStepPhaseStats value() { - return value; - } - } - - private static int resolveAdaptiveStepCount(float dt, - int simulationSteps, - float maxStepDt, - @Nonnull PhysicsWorldRuntimeResource resource) { - float sampledDt = Math.max(dt, 0.0f); - if (sampledDt <= 0.0f) { - return simulationSteps; - } - - int minimumSteps = PhysicsStepCountPolicy.resolveMaxStepCount(sampledDt, - simulationSteps, - maxStepDt); - StepRisk risk = new StepRisk(sampledDt, minimumSteps); - for (PhysicsBodyRegistration registration : resource.getBodyRegistrations()) { - PhysicsSpaceBinding space = resource.getSpaceBinding(registration.spaceId()); - if (space == null) { - continue; - } - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, registration.backendBodyHandle().value()); - if (snapshot != null) { - risk.inspect(snapshot); - } - } - return risk.steps(); - } - - private static void executeSteps(@Nonnull PhysicsWorldRuntimeResource resource, - float safeDt, - @Nonnull PhysicsStepMode stepMode, - int steps, - @Nonnull StepCounters counters, - @Nonnull StepBackendEvents backendEvents, - boolean collectBackendEvents) { - float stepDt = safeDt / steps; - for (PhysicsSpaceBinding space : resource.iterateSpaceBindings()) { - counters.spaceCount++; - if (stepMode == PhysicsStepMode.CCD && supportsContinuousCollision(space)) { - forceContinuousCollision(resource, space); - } - for (int step = 0; step < steps; step++) { - space.runtime().step(space.backendSpaceHandle().value(), stepDt); - counters.substeps++; - if (collectBackendEvents) { - collectContactEvents(resource, space, backendEvents); - } - } - } - } - - private static void collectContactEvents(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsSpaceBinding space, - @Nonnull StepBackendEvents backendEvents) { - space.runtime().contacts(space.backendSpaceHandle().value(), (bodyAId, - bodyBId, - pointAX, - pointAY, - pointAZ, - pointBX, - pointBY, - pointBZ, - normalBX, - normalBY, - normalBZ, - distance, - impulse) -> { - RigidBodyKey bodyAKey = resource.getBodyKey(space.spaceId(), bodyAId); - RigidBodyKey bodyBKey = resource.getBodyKey(space.spaceId(), bodyBId); - if (bodyAKey == null || bodyBKey == null) { - backendEvents.droppedBackendEventCount++; - return; - } - backendEvents.physicsEvents.add(new PhysicsContactEvent(space.spaceId(), - PhysicsContactPhase.OBSERVED, - bodyAKey, - bodyBKey, - new Vector3f(pointAX, pointAY, pointAZ), - new Vector3f(pointBX, pointBY, pointBZ), - new Vector3f(normalBX, normalBY, normalBZ), - distance, - impulse)); - }); - } - - private static boolean supportsContinuousCollision(@Nonnull PhysicsSpaceBinding space) { - return space.runtime().supportsContinuousCollision(space.backendSpaceHandle().value()); - } - - private static int requiredSteps(float travel, float safeTravel) { - if (travel <= safeTravel) { - return 1; - } - return (int) Math.ceil(travel / safeTravel); - } - - private static float safeLinearTravel(@Nonnull PhysicsBodySnapshot body) { - return Math.clamp( - approximateMinimumExtent(body) * SHAPE_TRAVEL_FRACTION, - MIN_LINEAR_TRAVEL_PER_SUBSTEP, - DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP); - } - - private static float safeAngularTravel(@Nonnull PhysicsBodySnapshot body) { - return Math.clamp( - approximateShapeRadius(body) * MAX_ANGULAR_RADIANS_PER_SUBSTEP, - MIN_LINEAR_TRAVEL_PER_SUBSTEP, - DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP); - } - - private static float approximateMinimumExtent(@Nonnull PhysicsBodySnapshot body) { - ShapeType shapeType = body.shapeType(); - if (shapeType == ShapeType.BOX) { - Vector3f halfExtents = body.boxHalfExtents(); - if (halfExtents != null) { - return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, - Math.min(halfExtents.x, Math.min(halfExtents.y, halfExtents.z))); - } - } - if (shapeType == ShapeType.SPHERE) { - return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, body.sphereRadius()); - } - if (shapeType == ShapeType.CAPSULE - || shapeType == ShapeType.CYLINDER - || shapeType == ShapeType.CONE) { - return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, body.sphereRadius()); - } - return DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP; - } - - private static float approximateShapeRadius(@Nonnull PhysicsBodySnapshot body) { - ShapeType shapeType = body.shapeType(); - if (shapeType == ShapeType.BOX) { - Vector3f halfExtents = body.boxHalfExtents(); - if (halfExtents != null) { - return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, - (float) Math.sqrt(halfExtents.x * halfExtents.x - + halfExtents.y * halfExtents.y - + halfExtents.z * halfExtents.z)); - } - } - if (shapeType == ShapeType.SPHERE) { - return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, body.sphereRadius()); - } - if (shapeType == ShapeType.CAPSULE - || shapeType == ShapeType.CYLINDER - || shapeType == ShapeType.CONE) { - return Math.max(MIN_LINEAR_TRAVEL_PER_SUBSTEP, - body.sphereRadius() + body.halfHeight()); - } - return DEFAULT_LINEAR_TRAVEL_PER_SUBSTEP; - } - - private static void forceContinuousCollision(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsSpaceBinding space) { - for (PhysicsBodyRegistration registration : resource.getBodyRegistrations()) { - if (!registration.spaceId().equals(space.spaceId())) { - continue; - } - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, registration.backendBodyHandle().value()); - if (snapshot == null - || !snapshot.isDynamic() - || space.runtime().isBodyContinuousCollisionEnabled(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value())) { - continue; - } - space.runtime().setBodyContinuousCollision(space.backendSpaceHandle().value(), registration.backendBodyHandle().value(), true); - resource.markContinuousCollisionForced(registration.bodyKey()); - } - } - - private static void restoreForcedContinuousCollision(@Nonnull PhysicsWorldRuntimeResource resource) { - if (!resource.hasForcedContinuousCollisionBodies()) { - return; - } - - resource.forEachForcedContinuousCollisionBody(bodyKey -> { - PhysicsBodyRegistration registration = resource.getRegistration(bodyKey); - if (registration != null) { - PhysicsSpaceBinding space = resource.getSpaceBinding(registration.spaceId()); - if (space != null) { - space.runtime() - .setBodyContinuousCollision(space.backendSpaceHandle().value(), registration.backendBodyHandle().value(), false); - } - } - }); - resource.clearForcedContinuousCollisionBodies(); - } - - private static final class StepRisk { - - private final float dt; - private int steps; - @Nonnull - private final Vector3f linearVelocity = new Vector3f(); - @Nonnull - private final Vector3f angularVelocity = new Vector3f(); - - private StepRisk(float dt, int minimumSteps) { - this.dt = dt; - steps = minimumSteps; - } - - private void inspect(@Nonnull PhysicsBodySnapshot body) { - if (steps >= PhysicsWorldSettings.MAX_SIMULATION_STEPS - || body.isStatic() - || body.sleeping() - || body.sensor() - || (!body.isDynamic() && !body.isKinematic())) { - return; - } - - body.copyLinearVelocityTo(linearVelocity); - body.copyAngularVelocityTo(angularVelocity); - float linearTravel = linearVelocity.length() * dt; - float angularSurfaceTravel = angularVelocity.length() - * approximateShapeRadius(body) - * dt; - float safeLinearTravel = safeLinearTravel(body); - int requiredSteps = Math.max( - requiredSteps(linearTravel, safeLinearTravel), - requiredSteps(angularSurfaceTravel, safeAngularTravel(body))); - steps = Math.clamp(steps, - Math.min(requiredSteps, PhysicsWorldSettings.MAX_SIMULATION_STEPS), - PhysicsWorldSettings.MAX_SIMULATION_STEPS); - } - - private int steps() { - return steps; - } - } - - record StepExecution(@Nonnull PhysicsOwnerSnapshot snapshot, - @Nullable RuntimeException failure, - @Nonnull PublishedPhysicsSnapshotFrame frame, - @Nonnull PhysicsEventFrame eventFrame) { - } - - private static final class StepBackendEvents { - - @Nonnull - private final ArrayList physicsEvents = new ArrayList<>(); - private int droppedBackendEventCount; - } - - private static final class StepCounters { - - private int spaceCount; - private int substeps; - - private int spaceCount() { - return spaceCount; - } - - private int substeps() { - return substeps; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCompletion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCompletion.java deleted file mode 100644 index 4c10aa24..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCompletion.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Non-blocking step completion consumed by the main tick once the owner future - * has already completed. - */ -public record PhysicsOwnerStepCompletion(@Nullable PhysicsOwnerResult result, - @Nullable PublishedPhysicsSnapshotFrame frame, - @Nullable PhysicsEventFrame eventFrame, - @Nullable RuntimeException stepFailure, - int preStepDrainedMutations, - long preStepDrainRunNanos, - int lateMutationBacklogAtStep, - @Nullable Throwable executionFailure) { - - public PhysicsOwnerStepCompletion(@Nullable PhysicsOwnerResult result, - @Nullable PublishedPhysicsSnapshotFrame frame, - @Nullable PhysicsEventFrame eventFrame, - @Nullable RuntimeException stepFailure, - @Nullable Throwable executionFailure) { - this(result, frame, eventFrame, stepFailure, 0, 0L, 0, executionFailure); - } - - public PhysicsOwnerStepCompletion { - preStepDrainedMutations = Math.max(0, preStepDrainedMutations); - preStepDrainRunNanos = Math.max(0L, preStepDrainRunNanos); - lateMutationBacklogAtStep = Math.max(0, lateMutationBacklogAtStep); - } - - public boolean completedSuccessfully() { - return result != null && executionFailure == null; - } - - @Nonnull - public PhysicsOwnerSnapshot snapshotOrEmpty() { - return result != null ? result.snapshot() : PhysicsOwnerSnapshot.empty(); - } -} From a2545413416891171084714ff7ee884a7f53c622 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:04:16 +0200 Subject: [PATCH 201/534] refactor(core): remove legacy world persistence resource Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 9 - .../PersistentPhysicsBinaryPayloadCodec.java | 124 --- .../PersistentPhysicsBodyState.java | 522 ---------- ...ersistentPhysicsExtensionSettingState.java | 93 -- .../PersistentPhysicsJointState.java | 357 ------- .../PersistentPhysicsRestorePreflight.java | 173 ---- .../PersistentPhysicsSpaceState.java | 637 ------------ .../PersistentPhysicsStateBlock.java | 367 ------- .../PersistentPhysicsValidation.java | 187 ---- .../PersistentPhysicsWorldResource.java | 565 ----------- ...csDetachedVisualMaterializationSystem.java | 911 +----------------- .../persistence/PhysicsPersistence.java | 55 +- .../PhysicsPersistenceResource.java | 55 -- .../PhysicsPersistenceSyncResult.java | 10 - 14 files changed, 22 insertions(+), 4043 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBinaryPayloadCodec.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBodyState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsExtensionSettingState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsJointState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflight.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStateBlock.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsValidation.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceSyncResult.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index aa2e79ca..a9ec419c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -22,7 +22,6 @@ import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; @@ -35,7 +34,6 @@ import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; -import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.nio.file.Path; @@ -70,9 +68,6 @@ public final class ImpulsePlugin extends JavaPlugin { @Getter private ResourceType physicsProjectionIndexResourceType; - @Getter - private ResourceType persistentPhysicsWorldResourceType; - @Getter private WorldEventType physicsEventFramePublishedEventType; @@ -226,10 +221,6 @@ private void registerComponents() { physicsProjectionIndexResourceType = entityRegistry.registerResource( PhysicsProjectionIndexResource.class, PhysicsProjectionIndexResource::new); - persistentPhysicsWorldResourceType = entityRegistry.registerResource( - PersistentPhysicsWorldResource.class, - "PersistentPhysicsWorld", - PersistentPhysicsWorldResource.CODEC); physicsEventFramePublishedEventType = entityRegistry.registerWorldEventType(PhysicsEventFramePublishedEvent.class); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBinaryPayloadCodec.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBinaryPayloadCodec.java deleted file mode 100644 index 056effd1..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBinaryPayloadCodec.java +++ /dev/null @@ -1,124 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.codec.exception.CodecException; -import com.hypixel.hytale.codec.schema.SchemaContext; -import com.hypixel.hytale.codec.schema.config.Schema; -import com.hypixel.hytale.codec.schema.config.StringSchema; -import com.hypixel.hytale.codec.util.RawJsonReader; -import java.io.IOException; -import java.util.Arrays; -import java.util.Base64; -import javax.annotation.Nonnull; -import org.bson.BsonBinary; -import org.bson.BsonValue; - -/** - * Binary payload codec for compressed physics state blocks. - */ -final class PersistentPhysicsBinaryPayloadCodec implements Codec { - - private static final byte[] EMPTY_PAYLOAD = new byte[0]; - - @Nonnull - @Override - public byte[] decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { - return copyPayload(bsonValue.asBinary().getData()); - } - - @Nonnull - @Override - public BsonValue encode(@Nonnull byte[] bytes, ExtraInfo extraInfo) { - return new BsonBinary(bytes); - } - - @Nonnull - @Override - public byte[] decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { - reader.consumeWhiteSpace(); - if (reader.peekFor('"')) { - return Base64.getDecoder().decode(reader.readString()); - } - return decodeExtendedBinaryObject(reader); - } - - @Nonnull - @Override - public Schema toSchema(@Nonnull SchemaContext context) { - StringSchema base64 = new StringSchema(); - base64.setPattern(Codec.BASE64_PATTERN); - base64.setTitle("Binary payload"); - return base64; - } - - @Nonnull - private static byte[] decodeExtendedBinaryObject(@Nonnull RawJsonReader reader) throws IOException { - reader.expect('{'); - reader.consumeWhiteSpace(); - - byte[] payload = null; - while (true) { - String key = reader.readString(); - reader.consumeWhiteSpace(); - reader.expect(':'); - reader.consumeWhiteSpace(); - - if ("$binary".equals(key)) { - payload = decodeBinaryValue(reader); - } else { - reader.skipValue(); - } - - reader.consumeWhiteSpace(); - if (reader.tryConsumeOrExpect('}', ',')) { - if (payload == null) { - throw new CodecException("Expected '$binary' field"); - } - return payload; - } - reader.consumeWhiteSpace(); - } - } - - @Nonnull - private static byte[] decodeBinaryValue(@Nonnull RawJsonReader reader) throws IOException { - if (reader.peekFor('"')) { - return Base64.getDecoder().decode(reader.readString()); - } - - reader.expect('{'); - reader.consumeWhiteSpace(); - - String base64 = null; - while (true) { - String key = reader.readString(); - reader.consumeWhiteSpace(); - reader.expect(':'); - reader.consumeWhiteSpace(); - - if ("base64".equals(key)) { - base64 = reader.readString(); - } else { - reader.skipValue(); - } - - reader.consumeWhiteSpace(); - if (reader.tryConsumeOrExpect('}', ',')) { - if (base64 == null) { - throw new CodecException("Expected '$binary.base64' field"); - } - return Base64.getDecoder().decode(base64); - } - reader.consumeWhiteSpace(); - } - } - - @Nonnull - private static byte[] copyPayload(byte[] payload) { - if (payload == null || payload.length == 0) { - return EMPTY_PAYLOAD; - } - return Arrays.copyOf(payload, payload.length); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBodyState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBodyState.java deleted file mode 100644 index 95ca5dbc..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsBodyState.java +++ /dev/null @@ -1,522 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import lombok.Getter; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * World-level persistent state for one physics body. - */ -public class PersistentPhysicsBodyState { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PersistentPhysicsBodyState.class, - PersistentPhysicsBodyState::new) - .append(new KeyedCodec<>("BodyId", Codec.UUID_BINARY), - (state, value) -> state.bodyId = value, - PersistentPhysicsBodyState::getBodyIdValue) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("SpaceId", Codec.INTEGER), - (state, value) -> state.spaceId = value, - PersistentPhysicsBodyState::getSpaceId) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, Integer.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("ShapeType", new EnumCodec<>(ShapeType.class)), - (state, value) -> state.shapeType = value, - PersistentPhysicsBodyState::getShapeType) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.persistentShapeType()) - .add() - .append(new KeyedCodec<>("ShapeAxis", new EnumCodec<>(PhysicsAxis.class)), - (state, value) -> state.shapeAxis = value, - PersistentPhysicsBodyState::getShapeAxis) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BoxHalfExtents", Vector3fUtil.CODEC), - (state, value) -> state.boxHalfExtents.set(value), - PersistentPhysicsBodyState::getBoxHalfExtents) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted body box half extents must be finite")) - .add() - .append(new KeyedCodec<>("SphereRadius", Codec.FLOAT), - (state, value) -> state.sphereRadius = value, - PersistentPhysicsBodyState::getSphereRadius) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted body sphere radius must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("HalfHeight", Codec.FLOAT), - (state, value) -> state.halfHeight = value, - PersistentPhysicsBodyState::getHalfHeight) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted body half height must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("BodyType", new EnumCodec<>(PhysicsBodyType.class)), - (state, value) -> state.bodyType = value, - PersistentPhysicsBodyState::getBodyType) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Mass", Codec.FLOAT), - (state, value) -> state.mass = value, - PersistentPhysicsBodyState::getMass) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted body mass must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("Position", Vector3fUtil.CODEC), - (state, value) -> state.position.set(value), - PersistentPhysicsBodyState::getPosition) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted body position must be finite")) - .add() - .append(new KeyedCodec<>("Rotation", ImpulseCodecs.QUATERNIONF), - (state, value) -> { - if (value != null) { - copyNormalizedQuaternionIfValid(state.rotation, value); - } - }, - PersistentPhysicsBodyState::getRotation) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("LinearVelocity", Vector3fUtil.CODEC), - (state, value) -> state.linearVelocity.set(value), - PersistentPhysicsBodyState::getLinearVelocity) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted body linear velocity must be finite")) - .add() - .append(new KeyedCodec<>("AngularVelocity", Vector3fUtil.CODEC), - (state, value) -> state.angularVelocity.set(value), - PersistentPhysicsBodyState::getAngularVelocity) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted body angular velocity must be finite")) - .add() - .append(new KeyedCodec<>("Friction", Codec.FLOAT), - (state, value) -> state.friction = value, - PersistentPhysicsBodyState::getFriction) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted body friction must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("Restitution", Codec.FLOAT), - (state, value) -> state.restitution = value, - PersistentPhysicsBodyState::getRestitution) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted body restitution must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("LinearDamping", Codec.FLOAT), - (state, value) -> state.linearDamping = value, - PersistentPhysicsBodyState::getLinearDamping) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted body linear damping must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("AngularDamping", Codec.FLOAT), - (state, value) -> state.angularDamping = value, - PersistentPhysicsBodyState::getAngularDamping) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted body angular damping must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("Sensor", Codec.BOOLEAN), - (state, value) -> state.sensor = value, - PersistentPhysicsBodyState::isSensor) - .add() - .append(new KeyedCodec<>("CollisionGroup", Codec.INTEGER), - (state, value) -> state.collisionGroup = value, - PersistentPhysicsBodyState::getCollisionGroup) - .add() - .append(new KeyedCodec<>("CollisionMask", Codec.INTEGER), - (state, value) -> state.collisionMask = value, - PersistentPhysicsBodyState::getCollisionMask) - .add() - .append(new KeyedCodec<>("ContinuousCollision", Codec.BOOLEAN), - (state, value) -> state.continuousCollisionEnabled = value, - PersistentPhysicsBodyState::isContinuousCollisionEnabled) - .add() - .append(new KeyedCodec<>("Sleeping", Codec.BOOLEAN), - (state, value) -> state.sleeping = value, - PersistentPhysicsBodyState::isSleeping) - .add() - .build(); - - @Nullable - private UUID bodyId; - @Getter - private int spaceId; - @Nonnull - private ShapeType shapeType = ShapeType.UNKNOWN; - @Nonnull - private PhysicsAxis shapeAxis = PhysicsAxis.Y; - @Nonnull - private final Vector3f boxHalfExtents = new Vector3f(); - @Getter - private float sphereRadius; - @Getter - private float halfHeight; - @Nonnull - private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; - @Getter - private float mass = 1.0f; - @Nonnull - private final Vector3f position = new Vector3f(); - @Nonnull - private final Quaternionf rotation = new Quaternionf(); - @Nonnull - private final Vector3f linearVelocity = new Vector3f(); - @Nonnull - private final Vector3f angularVelocity = new Vector3f(); - @Getter - private float friction; - @Getter - private float restitution; - @Getter - private float linearDamping; - @Getter - private float angularDamping; - @Getter - private boolean sensor; - @Getter - private int collisionGroup; - @Getter - private int collisionMask; - @Getter - private boolean continuousCollisionEnabled; - @Getter - private boolean sleeping; - - public PersistentPhysicsBodyState() { - } - - @Nonnull - public static PersistentPhysicsBodyState from(@Nonnull PhysicsBodyRegistration registration, - @Nonnull PhysicsBodySnapshot snapshot) { - PersistentPhysicsBodyState state = new PersistentPhysicsBodyState(); - state.bodyId = registration.bodyKey().value(); - state.updateFromSnapshot(snapshot, registration.spaceId()); - return state; - } - - @Nullable - public RigidBodyKey getBodyKey() { - return bodyId != null ? RigidBodyKey.of(bodyId) : null; - } - - @Nullable - public UUID getBodyIdValue() { - return bodyId; - } - - @Nonnull - public ShapeType getShapeType() { - return shapeType; - } - - @Nonnull - public PhysicsAxis getShapeAxis() { - return shapeAxis; - } - - @Nonnull - public Vector3f getBoxHalfExtents() { - return boxHalfExtents; - } - - @Nonnull - public PhysicsBodyType getBodyType() { - return bodyType; - } - - @Nonnull - public Vector3f getPosition() { - return position; - } - - @Nonnull - public Quaternionf getRotation() { - return new Quaternionf(rotation); - } - - @Nonnull - public Vector3f getLinearVelocity() { - return linearVelocity; - } - - @Nonnull - public Vector3f getAngularVelocity() { - return angularVelocity; - } - - public int resolveSpaceId() { - return spaceId; - } - - @Nullable - public String restoreValidationFailureReason() { - if (bodyId == null) { - return "missing body key"; - } - if (shapeType == ShapeType.UNKNOWN || shapeType == ShapeType.VOXELS) { - return "unsupported body shape"; - } - if (!isFiniteVector(position)) { - return "invalid position"; - } - if (!ImpulseCodecs.isFiniteAndNonZero(rotation)) { - return "invalid rotation"; - } - if (!isFiniteVector(linearVelocity)) { - return "invalid linear velocity"; - } - if (!isFiniteVector(angularVelocity)) { - return "invalid angular velocity"; - } - if (!isNonNegativeFiniteForRestore(mass)) { - return "invalid mass"; - } - if (bodyType == PhysicsBodyType.DYNAMIC && mass <= 0.0f) { - return "invalid dynamic mass"; - } - if (!isNonNegativeFiniteForRestore(friction)) { - return "invalid friction"; - } - if (!isNonNegativeFiniteForRestore(restitution)) { - return "invalid restitution"; - } - if (!isNonNegativeFiniteForRestore(linearDamping)) { - return "invalid linear damping"; - } - if (!isNonNegativeFiniteForRestore(angularDamping)) { - return "invalid angular damping"; - } - if (shapeType == ShapeType.BOX && !isPositiveFiniteVector(boxHalfExtents)) { - return "invalid box half extents"; - } - if (shapeType == ShapeType.SPHERE && !isPositiveFiniteForRestore(sphereRadius)) { - return "invalid sphere radius"; - } - if (usesRadiusAndHalfHeight(shapeType) - && (!isPositiveFiniteForRestore(sphereRadius) || !isPositiveFiniteForRestore(halfHeight))) { - return "invalid swept shape dimensions"; - } - return null; - } - - public void updateFromSnapshot(@Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - if (spaceId.value() <= 0) { - throw new IllegalArgumentException( - "Persistent body state requires a positive explicit space id"); - } - this.spaceId = spaceId.value(); - shapeType = snapshot.shapeType(); - shapeAxis = snapshot.shapeAxis(); - Vector3f halfExtents = snapshot.boxHalfExtents(); - if (isFiniteVector(halfExtents)) { - boxHalfExtents.set(halfExtents); - } else { - boxHalfExtents.zero(); - } - sphereRadius = positiveFiniteOrZeroForRestore(snapshot.sphereRadius()); - halfHeight = positiveFiniteOrZeroForRestore(snapshot.halfHeight()); - bodyType = snapshot.bodyType(); - mass = nonNegativeFiniteOrDefaultForSnapshot(snapshot.mass(), bodyType == PhysicsBodyType.DYNAMIC ? 1.0f : 0.0f); - copyFiniteVectorOrZero(position, snapshot.position()); - copyNormalizedQuaternionIfValid(rotation, snapshot.rotation()); - copyFiniteVectorOrZero(linearVelocity, snapshot.linearVelocity()); - copyFiniteVectorOrZero(angularVelocity, snapshot.angularVelocity()); - friction = nonNegativeFiniteOrZeroForRestore(snapshot.friction()); - restitution = nonNegativeFiniteOrZeroForRestore(snapshot.restitution()); - linearDamping = nonNegativeFiniteOrZeroForRestore(snapshot.linearDamping()); - angularDamping = nonNegativeFiniteOrZeroForRestore(snapshot.angularDamping()); - sensor = snapshot.sensor(); - collisionGroup = snapshot.collisionGroup(); - collisionMask = snapshot.collisionMask(); - continuousCollisionEnabled = snapshot.continuousCollisionEnabled(); - sleeping = snapshot.sleeping(); - } - - @Nonnull - public BackendBodyHandle createBackendBody(@Nonnull PhysicsSpaceBinding space) { - float dynamicMass = bodyType == PhysicsBodyType.DYNAMIC ? mass : 0.0f; - Quaternionf restoreRotation = normalizedRotationForRestore(); - long backendBodyId = switch (shapeType) { - case BOX, SPHERE, CAPSULE, CYLINDER, CONE, PLANE -> space.runtime().createBody( - space.backendSpaceHandle().value(), - BackendRuntimeCodes.shapeTypeCode(shapeType), - boxHalfExtents.x, - boxHalfExtents.y, - boxHalfExtents.z, - sphereRadius, - halfHeight, - BackendRuntimeCodes.axisCode(shapeAxis), - finiteOrZero(position.y), - dynamicMass, - BackendRuntimeCodes.bodyTypeCode(bodyType), - position.x, - position.y, - position.z, - restoreRotation.x, - restoreRotation.y, - restoreRotation.z, - restoreRotation.w); - case VOXELS -> throw new IllegalStateException( - "PersistentPhysicsBodyState cannot rebuild streamed voxel terrain bodies"); - case UNKNOWN -> throw new IllegalStateException("Persistent body shape is unknown"); - }; - return new BackendBodyHandle(backendBodyId); - } - - public void applyToBody(@Nonnull PhysicsSpaceBinding space, @Nonnull BackendBodyHandle backendBodyHandle) { - long backendBodyId = backendBodyHandle.value(); - Quaternionf restoreRotation = normalizedRotationForRestore(); - space.runtime().setBodyType(space.backendSpaceHandle().value(), - backendBodyId, - BackendRuntimeCodes.bodyTypeCode(bodyType)); - space.runtime().setBodyTransform(space.backendSpaceHandle().value(), - backendBodyId, - position.x, - position.y, - position.z, - restoreRotation.x, - restoreRotation.y, - restoreRotation.z, - restoreRotation.w); - space.runtime().setBodyVelocity(space.backendSpaceHandle().value(), - backendBodyId, - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z); - space.runtime().setBodyFriction(space.backendSpaceHandle().value(), backendBodyId, friction); - space.runtime().setBodyRestitution(space.backendSpaceHandle().value(), backendBodyId, restitution); - space.runtime().setBodyDamping(space.backendSpaceHandle().value(), backendBodyId, linearDamping, angularDamping); - space.runtime().setBodySensor(space.backendSpaceHandle().value(), backendBodyId, sensor); - space.runtime().setBodyCollisionFilter(space.backendSpaceHandle().value(), backendBodyId, collisionGroup, collisionMask); - space.runtime() - .setBodyContinuousCollision(space.backendSpaceHandle().value(), backendBodyId, continuousCollisionEnabled); - if (sleeping && bodyType == PhysicsBodyType.DYNAMIC) { - space.runtime().sleepBody(space.backendSpaceHandle().value(), backendBodyId); - } else { - space.runtime().activateBody(space.backendSpaceHandle().value(), backendBodyId); - } - } - - @Nonnull - public PersistentPhysicsBodyState copy() { - PersistentPhysicsBodyState copy = new PersistentPhysicsBodyState(); - copy.bodyId = bodyId; - copy.spaceId = spaceId; - copy.shapeType = shapeType; - copy.shapeAxis = shapeAxis; - copy.boxHalfExtents.set(boxHalfExtents); - copy.sphereRadius = sphereRadius; - copy.halfHeight = halfHeight; - copy.bodyType = bodyType; - copy.mass = mass; - copy.position.set(position); - copy.rotation.set(rotation); - copy.linearVelocity.set(linearVelocity); - copy.angularVelocity.set(angularVelocity); - copy.friction = friction; - copy.restitution = restitution; - copy.linearDamping = linearDamping; - copy.angularDamping = angularDamping; - copy.sensor = sensor; - copy.collisionGroup = collisionGroup; - copy.collisionMask = collisionMask; - copy.continuousCollisionEnabled = continuousCollisionEnabled; - copy.sleeping = sleeping; - return copy; - } - - private static void copyFiniteVectorOrZero(@Nonnull Vector3f target, @Nullable Vector3f value) { - if (isFiniteVector(value)) { - target.set(value); - } else { - target.zero(); - } - } - - private static void copyNormalizedQuaternionIfValid(@Nonnull Quaternionf target, - @Nonnull Quaternionf value) { - target.set(value); - if (ImpulseCodecs.isFiniteAndNonZero(target)) { - target.normalize(); - } - } - - @Nonnull - private Quaternionf normalizedRotationForRestore() { - Quaternionf restoreRotation = new Quaternionf(rotation); - if (ImpulseCodecs.isFiniteAndNonZero(restoreRotation)) { - restoreRotation.normalize(); - } - return restoreRotation; - } - - private static boolean isFiniteVector(@Nullable Vector3f value) { - return value != null - && Float.isFinite(value.x) - && Float.isFinite(value.y) - && Float.isFinite(value.z); - } - - private static boolean isPositiveFiniteVector(@Nonnull Vector3f value) { - return isPositiveFiniteForRestore(value.x) - && isPositiveFiniteForRestore(value.y) - && isPositiveFiniteForRestore(value.z); - } - - private static float positiveFiniteOrZeroForRestore(float value) { - return isPositiveFiniteForRestore(value) ? value : 0.0f; - } - - private static boolean isPositiveFiniteForRestore(float value) { - return Float.isFinite(value) && value > 0.0f; - } - - private static float nonNegativeFiniteOrZeroForRestore(float value) { - return isNonNegativeFiniteForRestore(value) ? value : 0.0f; - } - - private static float nonNegativeFiniteOrDefaultForSnapshot(float value, float defaultValue) { - return isNonNegativeFiniteForRestore(value) ? value : defaultValue; - } - - private static boolean isNonNegativeFiniteForRestore(float value) { - return Float.isFinite(value) && value >= 0.0f; - } - - private static boolean usesRadiusAndHalfHeight(@Nullable ShapeType type) { - return type == ShapeType.CAPSULE || type == ShapeType.CYLINDER || type == ShapeType.CONE; - } - - private static float finiteOrZero(float value) { - return Float.isFinite(value) ? value : 0.0f; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsExtensionSettingState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsExtensionSettingState.java deleted file mode 100644 index 97de6edb..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsExtensionSettingState.java +++ /dev/null @@ -1,93 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.validation.Validators; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettingValue; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; - -/** - * Persisted capability-keyed extension setting. - */ -@Getter -final class PersistentPhysicsExtensionSettingState { - - @Nonnull - static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentPhysicsExtensionSettingState.class, - PersistentPhysicsExtensionSettingState::new) - .append(new KeyedCodec<>("CapabilityId", Codec.STRING), - (state, value) -> state.capabilityId = value, - PersistentPhysicsExtensionSettingState::getCapabilityId) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.nonBlankString( - "Persisted extension capability id cannot be blank")) - .add() - .append(new KeyedCodec<>("Key", Codec.STRING), - (state, value) -> state.key = value, - PersistentPhysicsExtensionSettingState::getKey) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.nonBlankString( - "Persisted extension setting key cannot be blank")) - .add() - .append(new KeyedCodec<>("Kind", new EnumCodec<>(PhysicsExtensionSettingValue.Kind.class)), - (state, value) -> state.kind = value, - PersistentPhysicsExtensionSettingState::getKind) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Value", Codec.STRING), - (state, value) -> state.value = value, - PersistentPhysicsExtensionSettingState::getValue) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.nonBlankString( - "Persisted extension setting value cannot be blank")) - .add() - .build(); - - @Nonnull - @Setter - private String capabilityId = ""; - @Nonnull - @Setter - private String key = ""; - @Nonnull - @Setter - private PhysicsExtensionSettingValue.Kind kind = PhysicsExtensionSettingValue.Kind.STRING; - @Nonnull - @Setter - private String value = ""; - - @Nonnull - static PersistentPhysicsExtensionSettingState from(@Nonnull PhysicsBackendExtensionId extensionId, - @Nonnull String key, - @Nonnull PhysicsExtensionSettingValue value) { - PersistentPhysicsExtensionSettingState state = new PersistentPhysicsExtensionSettingState(); - state.capabilityId = extensionId.value(); - state.key = key; - state.kind = value.kind(); - state.value = value.value(); - return state; - } - - void applyTo(@Nonnull PhysicsExtensionSettings settings) { - settings.set(new PhysicsBackendExtensionId(capabilityId), - key, - new PhysicsExtensionSettingValue(kind, value)); - } - - @Nonnull - PersistentPhysicsExtensionSettingState copy() { - PersistentPhysicsExtensionSettingState copy = new PersistentPhysicsExtensionSettingState(); - copy.capabilityId = capabilityId; - copy.key = key; - copy.kind = kind; - copy.value = value; - return copy; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsJointState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsJointState.java deleted file mode 100644 index 3b651ec2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsJointState.java +++ /dev/null @@ -1,357 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.validation.ValidationResults; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import lombok.Getter; -import lombok.Setter; -import org.joml.Vector3f; - -/** - * Codec-backed definition of one physics joint for the persistence layer. - *

- * This class uses stable physics body keys to identify the two endpoint bodies. - * - *

The {@link #key()} method produces a deterministic string from all fields - * (using {@link Float#floatToIntBits(float)} to avoid floating-point drift). - * The joint hydration system uses this to detect whether a given joint already - * exists in the runtime space and avoid duplicating it on repeated ticks.

- */ -public class PersistentPhysicsJointState { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PersistentPhysicsJointState.class, - PersistentPhysicsJointState::new) - .append(new KeyedCodec<>("SpaceId", Codec.INTEGER), - (state, value) -> state.spaceId = value, - PersistentPhysicsJointState::getSpaceId) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, Integer.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("BodyAId", Codec.UUID_BINARY), - (state, value) -> state.bodyAId = value, - PersistentPhysicsJointState::getBodyAIdValue) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BodyBId", Codec.UUID_BINARY), - (state, value) -> state.bodyBId = value, - PersistentPhysicsJointState::getBodyBIdValue) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Type", new EnumCodec<>(PhysicsJointType.class)), - (state, value) -> state.type = value, - PersistentPhysicsJointState::getType) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("AnchorA", Vector3fUtil.CODEC), - (state, value) -> state.anchorA.set(value), - PersistentPhysicsJointState::getAnchorA) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted joint anchor A must be finite")) - .add() - .append(new KeyedCodec<>("AnchorB", Vector3fUtil.CODEC), - (state, value) -> state.anchorB.set(value), - PersistentPhysicsJointState::getAnchorB) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted joint anchor B must be finite")) - .add() - .append(new KeyedCodec<>("Axis", Vector3fUtil.CODEC, true), - (state, value) -> state.axis = value != null ? new Vector3f(value) : null, - PersistentPhysicsJointState::getAxis) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted joint axis must be finite")) - .add() - .append(new KeyedCodec<>("LowerLimit", Codec.FLOAT), - (state, value) -> state.lowerLimit = value, - PersistentPhysicsJointState::getLowerLimit) - .addValidator(PersistentPhysicsValidation.finiteFloat( - "Persisted joint lower limit must be finite")) - .add() - .append(new KeyedCodec<>("UpperLimit", Codec.FLOAT), - (state, value) -> state.upperLimit = value, - PersistentPhysicsJointState::getUpperLimit) - .addValidator(PersistentPhysicsValidation.finiteFloat( - "Persisted joint upper limit must be finite")) - .add() - .append(new KeyedCodec<>("Enabled", Codec.BOOLEAN), - (state, value) -> state.enabled = value, - PersistentPhysicsJointState::isEnabled) - .add() - .append(new KeyedCodec<>("MotorEnabled", Codec.BOOLEAN), - (state, value) -> state.motorEnabled = value, - PersistentPhysicsJointState::isMotorEnabled) - .add() - .append(new KeyedCodec<>("MotorTargetVelocity", Codec.FLOAT), - (state, value) -> state.motorTargetVelocity = value, - PersistentPhysicsJointState::getMotorTargetVelocity) - .addValidator(PersistentPhysicsValidation.finiteFloat( - "Persisted joint motor target velocity must be finite")) - .add() - .append(new KeyedCodec<>("MotorMaxForce", Codec.FLOAT), - (state, value) -> state.motorMaxForce = value, - PersistentPhysicsJointState::getMotorMaxForce) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted joint motor max force must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("SpringRestLength", Codec.FLOAT, false), - (state, value) -> state.springRestLength = value, - PersistentPhysicsJointState::getSpringRestLength) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted joint spring rest length must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("SpringStiffness", Codec.FLOAT, false), - (state, value) -> state.springStiffness = value, - PersistentPhysicsJointState::getSpringStiffness) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted joint spring stiffness must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("SpringDamping", Codec.FLOAT, false), - (state, value) -> state.springDamping = value, - PersistentPhysicsJointState::getSpringDamping) - .addValidator(PersistentPhysicsValidation.nonNegativeFiniteFloat( - "Persisted joint spring damping must be finite and >= 0")) - .add() - .afterDecode(PersistentPhysicsJointState::validateAfterDecode) - .build(); - - @Getter - @Setter - private int spaceId; - @Nullable - private UUID bodyAId; - @Nullable - private UUID bodyBId; - @Nonnull - @Setter - private PhysicsJointType type = PhysicsJointType.FIXED; - @Nonnull - private final Vector3f anchorA = new Vector3f(); - @Nonnull - private final Vector3f anchorB = new Vector3f(); - @Nullable - private Vector3f axis; - @Getter - @Setter - private float lowerLimit; - @Getter - @Setter - private float upperLimit; - @Getter - @Setter - private boolean enabled = true; - @Getter - @Setter - private boolean motorEnabled; - @Getter - @Setter - private float motorTargetVelocity; - @Getter - @Setter - private float motorMaxForce; - @Getter - @Setter - private float springRestLength = 0f; - @Getter - @Setter - private float springStiffness = 0f; - @Getter - @Setter - private float springDamping = 0f; - - public PersistentPhysicsJointState() { - } - - @Nonnull - public PhysicsJointType getType() { - return type; - } - - @Nonnull - public Vector3f getAnchorA() { - return anchorA; - } - - @Nonnull - public Vector3f getAnchorB() { - return anchorB; - } - - @Nullable - public Vector3f getAxis() { - return axis; - } - - @Nonnull - public static PersistentPhysicsJointState from(int spaceId, - @Nonnull RigidBodyKey bodyAKey, - @Nonnull RigidBodyKey bodyBKey, - @Nonnull PhysicsJointRegistration joint) { - PersistentPhysicsJointState state = new PersistentPhysicsJointState(); - state.spaceId = spaceId; - state.bodyAId = bodyAKey.value(); - state.bodyBId = bodyBKey.value(); - state.type = toPersistentJointType(joint.type()); - state.anchorA.set(joint.anchorAX(), joint.anchorAY(), joint.anchorAZ()); - state.anchorB.set(joint.anchorBX(), joint.anchorBY(), joint.anchorBZ()); - Vector3f jointAxis = new Vector3f(joint.axisX(), joint.axisY(), joint.axisZ()); - state.axis = jointAxis.lengthSquared() > 0.0f ? jointAxis : null; - state.lowerLimit = joint.lowerLimit(); - state.upperLimit = joint.upperLimit(); - state.enabled = true; - state.motorEnabled = joint.motorEnabled(); - state.motorTargetVelocity = joint.motorTargetVelocity(); - state.motorMaxForce = joint.motorMaxForce(); - state.springRestLength = nanToZero(joint.restLength()); - state.springStiffness = nanToZero(joint.stiffness()); - state.springDamping = nanToZero(joint.damping()); - return state; - } - - @Nullable - public RigidBodyKey getBodyAKey() { - return bodyAId != null ? RigidBodyKey.of(bodyAId) : null; - } - - @Nullable - public UUID getBodyAIdValue() { - return bodyAId; - } - - public void setBodyAKey(@Nullable RigidBodyKey bodyAKey) { - this.bodyAId = bodyAKey != null ? bodyAKey.value() : null; - } - - @Nullable - public RigidBodyKey getBodyBKey() { - return bodyBId != null ? RigidBodyKey.of(bodyBId) : null; - } - - @Nullable - public UUID getBodyBIdValue() { - return bodyBId; - } - - public void setBodyBKey(@Nullable RigidBodyKey bodyBKey) { - this.bodyBId = bodyBKey != null ? bodyBKey.value() : null; - } - - public void setAxis(@Nullable Vector3f axis) { - this.axis = axis != null ? new Vector3f(axis) : null; - } - - @Nonnull - public String key() { - return String.valueOf(spaceId) - + '|' - + type.name() - + '|' - + bodyAId - + '|' - + bodyBId - + '|' - + bits(anchorA.x) - + '|' - + bits(anchorA.y) - + '|' - + bits(anchorA.z) - + '|' - + bits(anchorB.x) - + '|' - + bits(anchorB.y) - + '|' - + bits(anchorB.z) - + '|' - + (axis != null ? bits(axis.x) : "na") - + '|' - + (axis != null ? bits(axis.y) : "na") - + '|' - + (axis != null ? bits(axis.z) : "na") - + '|' - + bits(lowerLimit) - + '|' - + bits(upperLimit) - + '|' - + enabled - + '|' - + motorEnabled - + '|' - + bits(motorTargetVelocity) - + '|' - + bits(motorMaxForce) - + '|' - + bits(springRestLength) - + '|' - + bits(springStiffness) - + '|' - + bits(springDamping); - } - - private static int bits(float value) { - return Float.floatToIntBits(value); - } - - private static float nanToZero(float value) { - return Float.isNaN(value) ? 0f : value; - } - - @Nonnull - private static PhysicsJointType toPersistentJointType(@Nonnull JointType type) { - return switch (type) { - case FIXED -> PhysicsJointType.FIXED; - case POINT -> PhysicsJointType.POINT; - case HINGE -> PhysicsJointType.HINGE; - case SLIDER -> PhysicsJointType.SLIDER; - case SPRING -> PhysicsJointType.SPRING; - }; - } - - @Nonnull - public PersistentPhysicsJointState copy() { - PersistentPhysicsJointState copy = new PersistentPhysicsJointState(); - copy.spaceId = spaceId; - copy.bodyAId = bodyAId; - copy.bodyBId = bodyBId; - copy.type = type; - copy.anchorA.set(anchorA); - copy.anchorB.set(anchorB); - copy.axis = axis != null ? new Vector3f(axis) : null; - copy.lowerLimit = lowerLimit; - copy.upperLimit = upperLimit; - copy.enabled = enabled; - copy.motorEnabled = motorEnabled; - copy.motorTargetVelocity = motorTargetVelocity; - copy.motorMaxForce = motorMaxForce; - copy.springRestLength = springRestLength; - copy.springStiffness = springStiffness; - copy.springDamping = springDamping; - return copy; - } - - private static void validateAfterDecode(@Nonnull PersistentPhysicsJointState state, - @Nonnull ExtraInfo extraInfo) { - ValidationResults results = extraInfo.getValidationResults(); - if ((state.type == PhysicsJointType.HINGE || state.type == PhysicsJointType.SLIDER) - && state.axis == null) { - results.fail("Persisted " + state.type + " joint requires an axis"); - } - if (state.lowerLimit > state.upperLimit) { - results.fail("Persisted joint lower limit cannot exceed upper limit"); - } - results._processValidationResults(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflight.java deleted file mode 100644 index fe3a522f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflight.java +++ /dev/null @@ -1,173 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Validates hard restore invariants before live runtime topology is stripped. - */ -public final class PersistentPhysicsRestorePreflight { - - private PersistentPhysicsRestorePreflight() { - } - - @Nullable - public static String validate(@Nonnull PersistentPhysicsWorldResource persistent) { - PhysicsWorldSettings worldSettings = persistent.getWorldSettings(); - String worldSettingsFailure = validateWorldSettings(worldSettings); - if (worldSettingsFailure != null) { - return worldSettingsFailure; - } - - String spaceFailure = validateSpaces(persistent.getSpaces(), worldSettings.getStepMode()); - if (spaceFailure != null) { - return spaceFailure; - } - - String bodyFailure = validateBodyKeys(persistent.getBodies()); - if (bodyFailure != null) { - return bodyFailure; - } - - return validateJointKeys(persistent.getJoints()); - } - - @Nullable - private static String validateWorldSettings(@Nonnull PhysicsWorldSettings settings) { - try { - PhysicsWorldSettings validated = PhysicsWorldSettings.defaults(); - validated.setSimulationSteps(settings.getSimulationSteps()); - validated.setStepMode(settings.getStepMode()); - validated.setStepSchedulingMode(settings.getStepSchedulingMode()); - validated.setEventCollectionMode(settings.getEventCollectionMode()); - validated.setMaxStepDt(settings.getMaxStepDt()); - return null; - } catch (RuntimeException exception) { - return "Invalid persisted physics runtime settings: " + exception.getMessage(); - } - } - - @Nullable - private static String validateSpaces(@Nonnull PersistentPhysicsSpaceState[] spaces, - @Nonnull PhysicsStepMode stepMode) { - IntOpenHashSet spaceIds = new IntOpenHashSet(); - for (PersistentPhysicsSpaceState state : spaces) { - int spaceId = state.getSpaceId(); - if (spaceId <= 0) { - return "Persisted space id must be positive, found " + spaceId; - } - if (!spaceIds.add(spaceId)) { - return "Duplicate persisted space id " + spaceId; - } - if (!isFiniteVector(state.getGravity())) { - return "Persisted space gravity must be finite for space id=" + spaceId; - } - - String settingsFailure = validateSpaceBackendAndSettings(state, spaceId, stepMode); - if (settingsFailure != null) { - return settingsFailure; - } - } - return null; - } - - @Nullable - private static String validateSpaceBackendAndSettings(@Nonnull PersistentPhysicsSpaceState state, - int spaceId, - @Nonnull PhysicsStepMode stepMode) { - BackendId backendId; - try { - /* - * BackendId construction validates the serialized id. Keep that check inside - * preflight so bootstrap records a restore failure instead of throwing mid-tick. - */ - backendId = state.toBackendId(); - } catch (RuntimeException exception) { - return "Invalid persisted physics space backend id for space id=" + spaceId - + ": " + failureMessage(exception); - } - - try { - Impulse.getRuntimeProvider(backendId); - } catch (IllegalStateException exception) { - return "Saved backend " + backendId + " is not available for restore"; - } - - try { - state.toSettings(); - validateBackendStepModeCompatibility(backendId, stepMode); - } catch (RuntimeException exception) { - return "Invalid persisted physics space settings for space id=" + spaceId - + " backend=" + backendId + ": " + failureMessage(exception); - } - return null; - } - - private static void validateBackendStepModeCompatibility(@Nonnull BackendId backendId, - @Nonnull PhysicsStepMode stepMode) { - if (stepMode != PhysicsStepMode.CCD) { - return; - } - - PhysicsBackendRuntime probe = Impulse.createRuntime(backendId); - SpaceId probeSpaceId = SpaceId.next(); - int backendSpaceId = probe.createSpace(probeSpaceId); - try { - if (!probe.supportsContinuousCollision(backendSpaceId)) { - throw new IllegalArgumentException("CCD mode is not available for backend " - + backendId); - } - } finally { - probe.destroySpace(backendSpaceId); - } - } - - @Nullable - private static String validateBodyKeys(@Nonnull PersistentPhysicsBodyState[] bodies) { - ObjectOpenHashSet bodyKeys = new ObjectOpenHashSet<>(); - for (PersistentPhysicsBodyState state : bodies) { - RigidBodyKey bodyKey = state.getBodyKey(); - if (bodyKey != null && !bodyKeys.add(bodyKey)) { - return "Duplicate persisted body key " + bodyKey; - } - } - return null; - } - - @Nullable - private static String validateJointKeys(@Nonnull PersistentPhysicsJointState[] joints) { - ObjectOpenHashSet jointKeys = new ObjectOpenHashSet<>(); - for (PersistentPhysicsJointState state : joints) { - String jointKey = state.key(); - if (!jointKeys.add(jointKey)) { - return "Duplicate persisted joint key " + jointKey; - } - } - return null; - } - - private static boolean isFiniteVector(@Nullable Vector3f value) { - return value != null - && Float.isFinite(value.x) - && Float.isFinite(value.y) - && Float.isFinite(value.z); - } - - @Nonnull - private static String failureMessage(@Nonnull RuntimeException exception) { - String message = exception.getMessage(); - return message != null && !message.isBlank() - ? message - : exception.getClass().getSimpleName(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java deleted file mode 100644 index 26eaac99..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsSpaceState.java +++ /dev/null @@ -1,637 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.codec.validation.ValidationResults; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; -import org.joml.Vector3f; - -/** - * Codec-backed definition of one physics space for the persistence layer. - * - *

Captures the space identity, backend choice, gravity, and world-collision - * settings for legacy world-resource persistence and migration.

- */ -@Getter -public class PersistentPhysicsSpaceState { - - private static final PersistentPhysicsExtensionSettingState[] EMPTY_EXTENSION_SETTINGS = - new PersistentPhysicsExtensionSettingState[0]; - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PersistentPhysicsSpaceState.class, - PersistentPhysicsSpaceState::new) - .append(new KeyedCodec<>("SpaceId", Codec.INTEGER), - (state, value) -> state.spaceId = value, - PersistentPhysicsSpaceState::getSpaceId) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BackendId", Codec.STRING), - (state, value) -> state.backendId = value, - PersistentPhysicsSpaceState::getBackendId) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Gravity", Vector3fUtil.CODEC), - (state, value) -> state.gravity.set(value), - PersistentPhysicsSpaceState::getGravity) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.finiteVector( - "Persisted space gravity must be finite")) - .add() - .append(new KeyedCodec<>("WorldCollisionMode", new EnumCodec<>(WorldCollisionMode.class), false), - (state, value) -> state.worldCollisionMode = value, - PersistentPhysicsSpaceState::getWorldCollisionMode) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("EntityChunkBoundaryMode", - new EnumCodec<>(EntityChunkBoundaryMode.class), false), - (state, value) -> state.entityChunkBoundaryMode = value, - PersistentPhysicsSpaceState::getEntityChunkBoundaryMode) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("WorldCollisionRadius", Codec.INTEGER, false), - (state, value) -> state.worldCollisionRadius = value, - PersistentPhysicsSpaceState::getWorldCollisionRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS)) - .add() - .append(new KeyedCodec<>("WorldCollisionBodyRadius", Codec.INTEGER, false), - (state, value) -> state.worldCollisionBodyRadius = value, - PersistentPhysicsSpaceState::getWorldCollisionBodyRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS)) - .add() - .append(new KeyedCodec<>("WorldCollisionTtlTicks", Codec.INTEGER, false), - (state, value) -> state.worldCollisionTtlTicks = value, - PersistentPhysicsSpaceState::getWorldCollisionTtlTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS)) - .add() - .append(new KeyedCodec<>("NativeVoxelTerrainEnabled", Codec.BOOLEAN, false), - (state, value) -> state.nativeVoxelTerrainEnabled = value, - PersistentPhysicsSpaceState::isNativeVoxelTerrainEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), - (state, value) -> state.terrainFriction = value, - PersistentPhysicsSpaceState::getTerrainFriction) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0.0f, Float.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), - (state, value) -> state.terrainRestitution = value, - PersistentPhysicsSpaceState::getTerrainRestitution) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0.0f, Float.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("VisualFullSyncRadius", Codec.INTEGER, false), - (state, value) -> state.visualFullSyncRadius = value, - PersistentPhysicsSpaceState::getVisualFullSyncRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualSyncSettings.MAX_VISUAL_FULL_SYNC_RADIUS)) - .add() - .append(new KeyedCodec<>("VisualMaxSyncRadius", Codec.INTEGER, false), - (state, value) -> state.visualMaxSyncRadius = value, - PersistentPhysicsSpaceState::getVisualMaxSyncRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualSyncSettings.MAX_VISUAL_MAX_SYNC_RADIUS)) - .add() - .append(new KeyedCodec<>("VisualFarSyncCutoffEnabled", Codec.BOOLEAN, false), - (state, value) -> state.visualFarSyncCutoffEnabled = value, - PersistentPhysicsSpaceState::isVisualFarSyncCutoffEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("VisualMidSyncIntervalTicks", Codec.INTEGER, false), - (state, value) -> state.visualMidSyncIntervalTicks = value, - PersistentPhysicsSpaceState::getVisualMidSyncIntervalTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualSyncSettings.MAX_VISUAL_MID_SYNC_INTERVAL_TICKS)) - .add() - .append(new KeyedCodec<>("VisualFarSyncIntervalTicks", Codec.INTEGER, false), - (state, value) -> state.visualFarSyncIntervalTicks = value, - PersistentPhysicsSpaceState::getVisualFarSyncIntervalTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualSyncSettings.MAX_VISUAL_FAR_SYNC_INTERVAL_TICKS)) - .add() - .append(new KeyedCodec<>("VisualOcclusionMode", new EnumCodec<>(VisualOcclusionMode.class), false), - (state, value) -> state.visualOcclusionMode = value, - PersistentPhysicsSpaceState::getVisualOcclusionMode) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("VisualOcclusionRaycastsPerTick", Codec.INTEGER, false), - (state, value) -> state.visualOcclusionRaycastsPerTick = value, - PersistentPhysicsSpaceState::getVisualOcclusionRaycastsPerTick) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualSyncSettings.MAX_VISUAL_OCCLUSION_RAYCASTS_PER_TICK)) - .add() - .append(new KeyedCodec<>("VisualOcclusionCacheTicks", Codec.INTEGER, false), - (state, value) -> state.visualOcclusionCacheTicks = value, - PersistentPhysicsSpaceState::getVisualOcclusionCacheTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualSyncSettings.MAX_VISUAL_OCCLUSION_CACHE_TICKS)) - .add() - .append(new KeyedCodec<>("VisualSnapshotPredictionEnabled", Codec.BOOLEAN, false), - (state, value) -> state.visualSnapshotPredictionEnabled = value, - PersistentPhysicsSpaceState::isVisualSnapshotPredictionEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("VisualSnapshotPredictionMaxSeconds", Codec.FLOAT, false), - (state, value) -> state.visualSnapshotPredictionMaxSeconds = value, - PersistentPhysicsSpaceState::getVisualSnapshotPredictionMaxSeconds) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0.0f, PhysicsVisualSyncSettings.MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS)) - .add() - .append(new KeyedCodec<>("VisualSnapshotSmoothingEnabled", Codec.BOOLEAN, false), - (state, value) -> state.visualSnapshotSmoothingEnabled = value, - PersistentPhysicsSpaceState::isVisualSnapshotSmoothingEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("VisualSnapshotSmoothingRate", Codec.FLOAT, false), - (state, value) -> state.visualSnapshotSmoothingRate = value, - PersistentPhysicsSpaceState::getVisualSnapshotSmoothingRate) - .addValidator(Validators.nonNull()) - .addValidator(Validators.greaterThan(0.0f)) - .addValidator(Validators.max(PhysicsVisualSyncSettings.MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE)) - .add() - .append(new KeyedCodec<>("SolverIterations", Codec.INTEGER, false), - (state, value) -> state.solverIterations = value, - PersistentPhysicsSpaceState::getSolverIterations) - .addValidator(Validators.nonNull()) - .addValidator(Validators.min(1)) - .add() - .append(new KeyedCodec<>("StabilizationIterations", Codec.INTEGER, false), - (state, value) -> state.stabilizationIterations = value, - PersistentPhysicsSpaceState::getStabilizationIterations) - .addValidator(Validators.nonNull()) - .addValidator(Validators.min(0)) - .add() - .append(new KeyedCodec<>("DynamicSleepLinearThreshold", Codec.FLOAT, false), - (state, value) -> state.dynamicSleepLinearThreshold = value, - PersistentPhysicsSpaceState::getDynamicSleepLinearThreshold) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0.0f, Float.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("DynamicSleepAngularThreshold", Codec.FLOAT, false), - (state, value) -> state.dynamicSleepAngularThreshold = value, - PersistentPhysicsSpaceState::getDynamicSleepAngularThreshold) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0.0f, Float.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("DynamicSleepTimeUntilSleep", Codec.FLOAT, false), - (state, value) -> state.dynamicSleepTimeUntilSleep = value, - PersistentPhysicsSpaceState::getDynamicSleepTimeUntilSleep) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0.0f, Float.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("ExtensionSettings", - new ArrayCodec<>(PersistentPhysicsExtensionSettingState.CODEC, - PersistentPhysicsExtensionSettingState[]::new), - false), - (state, value) -> state.extensionSettings = copyExtensionSettings(value), - PersistentPhysicsSpaceState::getExtensionSettings) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("EntityVisualSyncCullingEnabled", Codec.BOOLEAN, false), - (state, value) -> state.entityVisualSyncCullingEnabled = value, - PersistentPhysicsSpaceState::isEntityVisualSyncCullingEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("VisualVisibilityCullingEnabled", Codec.BOOLEAN, false), - (state, value) -> state.visualVisibilityCullingEnabled = value, - PersistentPhysicsSpaceState::isVisualVisibilityCullingEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("DetachedVisualMaterializationEnabled", Codec.BOOLEAN, false), - (state, value) -> state.detachedVisualMaterializationEnabled = value, - PersistentPhysicsSpaceState::isDetachedVisualMaterializationEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("DetachedVisualMaterializationRadius", Codec.INTEGER, false), - (state, value) -> state.detachedVisualMaterializationRadius = value, - PersistentPhysicsSpaceState::getDetachedVisualMaterializationRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_MATERIALIZATION_RADIUS)) - .add() - .append(new KeyedCodec<>("DetachedVisualDematerializationRadius", Codec.INTEGER, false), - (state, value) -> state.detachedVisualDematerializationRadius = value, - PersistentPhysicsSpaceState::getDetachedVisualDematerializationRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS)) - .add() - .append(new KeyedCodec<>("DetachedVisualMaxSpawnsPerTick", Codec.INTEGER, false), - (state, value) -> state.detachedVisualMaxSpawnsPerTick = value, - PersistentPhysicsSpaceState::getDetachedVisualMaxSpawnsPerTick) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK)) - .add() - .append(new KeyedCodec<>("DetachedVisualMaxMaterialized", Codec.INTEGER, false), - (state, value) -> state.detachedVisualMaxMaterialized = value, - PersistentPhysicsSpaceState::getDetachedVisualMaxMaterialized) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_MAX_MATERIALIZED)) - .add() - .append(new KeyedCodec<>("DetachedVisualInterestRefreshIntervalTicks", Codec.INTEGER, false), - (state, value) -> state.detachedVisualInterestRefreshIntervalTicks = value, - PersistentPhysicsSpaceState::getDetachedVisualInterestRefreshIntervalTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS)) - .add() - .append(new KeyedCodec<>("DetachedVisualCandidateRefreshIntervalTicks", Codec.INTEGER, false), - (state, value) -> state.detachedVisualCandidateRefreshIntervalTicks = value, - PersistentPhysicsSpaceState::getDetachedVisualCandidateRefreshIntervalTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS)) - .add() - .append(new KeyedCodec<>("DetachedVisualVisibilityCheckIntervalTicks", Codec.INTEGER, false), - (state, value) -> state.detachedVisualVisibilityCheckIntervalTicks = value, - PersistentPhysicsSpaceState::getDetachedVisualVisibilityCheckIntervalTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS)) - .add() - .append(new KeyedCodec<>("CollisionLodEnabled", Codec.BOOLEAN, false), - (state, value) -> state.collisionLodEnabled = value, - PersistentPhysicsSpaceState::isCollisionLodEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("CollisionLodNearRadius", Codec.INTEGER, false), - (state, value) -> state.collisionLodNearRadius = value, - PersistentPhysicsSpaceState::getCollisionLodNearRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsCollisionLodSettings.MAX_COLLISION_LOD_RADIUS)) - .add() - .append(new KeyedCodec<>("CollisionLodMidRadius", Codec.INTEGER, false), - (state, value) -> state.collisionLodMidRadius = value, - PersistentPhysicsSpaceState::getCollisionLodMidRadius) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsCollisionLodSettings.MAX_COLLISION_LOD_RADIUS)) - .add() - .append(new KeyedCodec<>("CollisionLodHysteresis", Codec.INTEGER, false), - (state, value) -> state.collisionLodHysteresis = value, - PersistentPhysicsSpaceState::getCollisionLodHysteresis) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0, PhysicsCollisionLodSettings.MAX_COLLISION_LOD_HYSTERESIS)) - .add() - .append(new KeyedCodec<>("CollisionLodRefreshIntervalTicks", Codec.INTEGER, false), - (state, value) -> state.collisionLodRefreshIntervalTicks = value, - PersistentPhysicsSpaceState::getCollisionLodRefreshIntervalTicks) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, PhysicsCollisionLodSettings.MAX_COLLISION_LOD_REFRESH_INTERVAL_TICKS)) - .add() - .append(new KeyedCodec<>("CollisionLodFarSleepEnabled", Codec.BOOLEAN, false), - (state, value) -> state.collisionLodFarSleepEnabled = value, - PersistentPhysicsSpaceState::isCollisionLodFarSleepEnabled) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("DetachedVisualBlockType", Codec.STRING, false), - (state, value) -> state.detachedVisualBlockType = value, - PersistentPhysicsSpaceState::getDetachedVisualBlockType) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.nonBlankString( - "Persisted detached visual block type cannot be blank")) - .add() - .afterDecode(PersistentPhysicsSpaceState::validateAfterDecode) - .build(); - - @Setter - private int spaceId; - @Nonnull - @Setter - private String backendId = ""; - @Nonnull - private final Vector3f gravity = new Vector3f(0.0f, -32f, 0.0f);// new Vector3f(0.0f, -9.81f, 0.0f); - @Nonnull - @Setter - private WorldCollisionMode worldCollisionMode = WorldCollisionMode.NONE; - @Nonnull - @Setter - private EntityChunkBoundaryMode entityChunkBoundaryMode = - PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - private int worldCollisionRadius = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS; - private int worldCollisionBodyRadius = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS; - private int worldCollisionTtlTicks = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS; - @Setter - private boolean nativeVoxelTerrainEnabled = - PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; - private float terrainFriction = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION; - private int visualFullSyncRadius = PhysicsVisualSyncSettings.DEFAULT_VISUAL_FULL_SYNC_RADIUS; - private int visualMaxSyncRadius = PhysicsVisualSyncSettings.DEFAULT_VISUAL_MAX_SYNC_RADIUS; - @Setter - private boolean visualFarSyncCutoffEnabled = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED; - private int visualMidSyncIntervalTicks = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS; - private int visualFarSyncIntervalTicks = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS; - @Nonnull - @Setter - private VisualOcclusionMode visualOcclusionMode = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_MODE; - private int visualOcclusionRaycastsPerTick = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK; - private int visualOcclusionCacheTicks = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS; - @Setter - private boolean visualSnapshotPredictionEnabled = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED; - private float visualSnapshotPredictionMaxSeconds = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS; - @Setter - private boolean visualSnapshotSmoothingEnabled = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED; - private float visualSnapshotSmoothingRate = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE; - @Setter - private int solverIterations = PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS; - @Setter - private int stabilizationIterations = PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS; - @Setter - private float dynamicSleepLinearThreshold = - PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_LINEAR_THRESHOLD; - @Setter - private float dynamicSleepAngularThreshold = - PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_ANGULAR_THRESHOLD; - @Setter - private float dynamicSleepTimeUntilSleep = - PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_TIME_UNTIL_SLEEP; - @Nonnull - private PersistentPhysicsExtensionSettingState[] extensionSettings = EMPTY_EXTENSION_SETTINGS; - @Setter - private boolean entityVisualSyncCullingEnabled = - PhysicsVisualSyncSettings.DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED; - @Setter - private boolean visualVisibilityCullingEnabled = - PhysicsVisualSyncSettings.DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED; - @Setter - private boolean detachedVisualMaterializationEnabled = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED; - private int detachedVisualMaterializationRadius = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS; - private int detachedVisualDematerializationRadius = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS; - private int detachedVisualMaxSpawnsPerTick = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK; - private int detachedVisualMaxMaterialized = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED; - private int detachedVisualInterestRefreshIntervalTicks = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS; - private int detachedVisualCandidateRefreshIntervalTicks = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS; - private int detachedVisualVisibilityCheckIntervalTicks = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS; - @Setter - private boolean collisionLodEnabled = - PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_ENABLED; - private int collisionLodNearRadius = - PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_NEAR_RADIUS; - private int collisionLodMidRadius = - PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_MID_RADIUS; - private int collisionLodHysteresis = - PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_HYSTERESIS; - private int collisionLodRefreshIntervalTicks = - PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS; - @Setter - private boolean collisionLodFarSleepEnabled = - PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED; - @Nonnull - @Setter - private String detachedVisualBlockType = - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; - - public PersistentPhysicsSpaceState() { - } - - @Nonnull - public static PersistentPhysicsSpaceState from(@Nonnull PhysicsSpaceBinding space, - @Nonnull PhysicsSpaceSettings settings) { - PersistentPhysicsSpaceState state = new PersistentPhysicsSpaceState(); - state.spaceId = space.spaceId().value(); - state.backendId = space.backendId().value(); - space.runtime().getGravity(space.backendSpaceHandle().value(), state.gravity::set); - state.worldCollisionMode = settings.getWorldCollisionSettings().getWorldCollisionMode(); - state.entityChunkBoundaryMode = settings.getWorldCollisionSettings().getEntityChunkBoundaryMode(); - state.worldCollisionRadius = settings.getWorldCollisionSettings().getWorldCollisionRadius(); - state.worldCollisionBodyRadius = settings.getWorldCollisionSettings().getWorldCollisionBodyRadius(); - state.worldCollisionTtlTicks = settings.getWorldCollisionSettings().getWorldCollisionTtlTicks(); - state.nativeVoxelTerrainEnabled = settings.getWorldCollisionSettings().isNativeVoxelTerrainEnabled(); - state.terrainFriction = settings.getWorldCollisionSettings().getTerrainFriction(); - state.terrainRestitution = settings.getWorldCollisionSettings().getTerrainRestitution(); - state.visualFullSyncRadius = settings.getVisualSyncSettings().getVisualFullSyncRadius(); - state.visualMaxSyncRadius = settings.getVisualSyncSettings().getVisualMaxSyncRadius(); - state.visualFarSyncCutoffEnabled = settings.getVisualSyncSettings().isVisualFarSyncCutoffEnabled(); - state.visualMidSyncIntervalTicks = settings.getVisualSyncSettings().getVisualMidSyncIntervalTicks(); - state.visualFarSyncIntervalTicks = settings.getVisualSyncSettings().getVisualFarSyncIntervalTicks(); - state.visualOcclusionMode = settings.getVisualSyncSettings().getVisualOcclusionMode(); - state.visualOcclusionRaycastsPerTick = settings.getVisualSyncSettings().getVisualOcclusionRaycastsPerTick(); - state.visualOcclusionCacheTicks = settings.getVisualSyncSettings().getVisualOcclusionCacheTicks(); - state.visualSnapshotPredictionEnabled = settings.getVisualSyncSettings().isVisualSnapshotPredictionEnabled(); - state.visualSnapshotPredictionMaxSeconds = settings.getVisualSyncSettings().getVisualSnapshotPredictionMaxSeconds(); - state.visualSnapshotSmoothingEnabled = settings.getVisualSyncSettings().isVisualSnapshotSmoothingEnabled(); - state.visualSnapshotSmoothingRate = settings.getVisualSyncSettings().getVisualSnapshotSmoothingRate(); - state.solverIterations = settings.getSolverSettings().getSolverIterations(); - state.stabilizationIterations = settings.getSolverSettings().getStabilizationIterations(); - state.dynamicSleepLinearThreshold = settings.getSolverSettings().getDynamicSleepLinearThreshold(); - state.dynamicSleepAngularThreshold = settings.getSolverSettings().getDynamicSleepAngularThreshold(); - state.dynamicSleepTimeUntilSleep = settings.getSolverSettings().getDynamicSleepTimeUntilSleep(); - state.extensionSettings = extensionSettingsFrom(settings); - state.entityVisualSyncCullingEnabled = settings.getVisualSyncSettings().isEntityVisualSyncCullingEnabled(); - state.visualVisibilityCullingEnabled = settings.getVisualSyncSettings().isVisualVisibilityCullingEnabled(); - state.detachedVisualMaterializationEnabled = settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled(); - state.detachedVisualMaterializationRadius = settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(); - state.detachedVisualDematerializationRadius = settings.getVisualMaterializationSettings().getDetachedVisualDematerializationRadius(); - state.detachedVisualMaxSpawnsPerTick = settings.getVisualMaterializationSettings().getDetachedVisualMaxSpawnsPerTick(); - state.detachedVisualMaxMaterialized = settings.getVisualMaterializationSettings().getDetachedVisualMaxMaterialized(); - state.detachedVisualInterestRefreshIntervalTicks = - settings.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks(); - state.detachedVisualCandidateRefreshIntervalTicks = - settings.getVisualMaterializationSettings().getDetachedVisualCandidateRefreshIntervalTicks(); - state.detachedVisualVisibilityCheckIntervalTicks = - settings.getVisualMaterializationSettings().getDetachedVisualVisibilityCheckIntervalTicks(); - state.collisionLodEnabled = settings.getCollisionLodSettings().isCollisionLodEnabled(); - state.collisionLodNearRadius = settings.getCollisionLodSettings().getCollisionLodNearRadius(); - state.collisionLodMidRadius = settings.getCollisionLodSettings().getCollisionLodMidRadius(); - state.collisionLodHysteresis = settings.getCollisionLodSettings().getCollisionLodHysteresis(); - state.collisionLodRefreshIntervalTicks = settings.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks(); - state.collisionLodFarSleepEnabled = settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled(); - state.detachedVisualBlockType = settings.getVisualMaterializationSettings().getDetachedVisualBlockType(); - return state; - } - - @Nonnull - public SpaceId toSpaceId() { - return new SpaceId(spaceId); - } - - @Nonnull - public BackendId toBackendId() { - return new BackendId(backendId); - } - - @Nonnull - public PhysicsSpaceSettings toSettings() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getWorldCollisionSettings().setWorldCollisionMode(worldCollisionMode); - settings.getWorldCollisionSettings().setEntityChunkBoundaryMode(entityChunkBoundaryMode); - settings.getWorldCollisionSettings().setWorldCollisionRadius(worldCollisionRadius); - settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(worldCollisionBodyRadius); - settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(worldCollisionTtlTicks); - settings.getWorldCollisionSettings().setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); - settings.getWorldCollisionSettings().setTerrainMaterial(terrainFriction, terrainRestitution); - settings.getVisualSyncSettings().setVisualSyncRadii(visualFullSyncRadius, visualMaxSyncRadius); - settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(visualFarSyncCutoffEnabled); - settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(visualMidSyncIntervalTicks); - settings.getVisualSyncSettings().setVisualFarSyncIntervalTicks(visualFarSyncIntervalTicks); - settings.getVisualSyncSettings().setVisualOcclusionMode(visualOcclusionMode); - settings.getVisualSyncSettings().setVisualOcclusionRaycastsPerTick(visualOcclusionRaycastsPerTick); - settings.getVisualSyncSettings().setVisualOcclusionCacheTicks(visualOcclusionCacheTicks); - settings.getVisualSyncSettings().setVisualSnapshotPredictionEnabled(visualSnapshotPredictionEnabled); - settings.getVisualSyncSettings().setVisualSnapshotPredictionMaxSeconds(visualSnapshotPredictionMaxSeconds); - settings.getVisualSyncSettings().setVisualSnapshotSmoothingEnabled(visualSnapshotSmoothingEnabled); - settings.getVisualSyncSettings().setVisualSnapshotSmoothingRate(visualSnapshotSmoothingRate); - settings.getSolverSettings().setSolverIterations(solverIterations); - settings.getSolverSettings().setStabilizationIterations(stabilizationIterations); - settings.getSolverSettings().setDynamicSleepTuning(dynamicSleepLinearThreshold, - dynamicSleepAngularThreshold, - dynamicSleepTimeUntilSleep); - for (PersistentPhysicsExtensionSettingState extensionSetting : extensionSettings) { - extensionSetting.applyTo(settings.getExtensionSettings()); - } - settings.getVisualSyncSettings().setEntityVisualSyncCullingEnabled(entityVisualSyncCullingEnabled); - settings.getVisualSyncSettings().setVisualVisibilityCullingEnabled(visualVisibilityCullingEnabled); - settings.getVisualMaterializationSettings().setDetachedVisualMaterializationEnabled(detachedVisualMaterializationEnabled); - settings.getVisualMaterializationSettings().setDetachedVisualRadii( - detachedVisualMaterializationRadius, - detachedVisualDematerializationRadius); - settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(detachedVisualMaxSpawnsPerTick); - settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(detachedVisualMaxMaterialized); - settings.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks( - detachedVisualInterestRefreshIntervalTicks); - settings.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks( - detachedVisualCandidateRefreshIntervalTicks); - settings.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks( - detachedVisualVisibilityCheckIntervalTicks); - settings.getCollisionLodSettings().setCollisionLodEnabled(collisionLodEnabled); - settings.getCollisionLodSettings().setCollisionLodRadii(collisionLodNearRadius, collisionLodMidRadius); - settings.getCollisionLodSettings().setCollisionLodHysteresis(collisionLodHysteresis); - settings.getCollisionLodSettings().setCollisionLodRefreshIntervalTicks(collisionLodRefreshIntervalTicks); - settings.getCollisionLodSettings().setCollisionLodFarSleepEnabled(collisionLodFarSleepEnabled); - settings.getVisualMaterializationSettings().setDetachedVisualBlockType(detachedVisualBlockType); - return settings; - } - - private static void validate(@Nonnull PersistentPhysicsSpaceState state, - @Nonnull ValidationResults results) { - try { - state.toSettings(); - } catch (RuntimeException exception) { - results.fail("Invalid persisted space settings: " + exception.getMessage()); - } - } - - private static void validateAfterDecode(@Nonnull PersistentPhysicsSpaceState state, - @Nonnull ExtraInfo extraInfo) { - validate(state, extraInfo.getValidationResults()); - } - - @Nonnull - public PersistentPhysicsSpaceState copy() { - PersistentPhysicsSpaceState copy = new PersistentPhysicsSpaceState(); - copy.spaceId = spaceId; - copy.backendId = backendId; - copy.gravity.set(gravity); - copy.worldCollisionMode = worldCollisionMode; - copy.entityChunkBoundaryMode = entityChunkBoundaryMode; - copy.worldCollisionRadius = worldCollisionRadius; - copy.worldCollisionBodyRadius = worldCollisionBodyRadius; - copy.worldCollisionTtlTicks = worldCollisionTtlTicks; - copy.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; - copy.terrainFriction = terrainFriction; - copy.terrainRestitution = terrainRestitution; - copy.visualFullSyncRadius = visualFullSyncRadius; - copy.visualMaxSyncRadius = visualMaxSyncRadius; - copy.visualFarSyncCutoffEnabled = visualFarSyncCutoffEnabled; - copy.visualMidSyncIntervalTicks = visualMidSyncIntervalTicks; - copy.visualFarSyncIntervalTicks = visualFarSyncIntervalTicks; - copy.visualOcclusionMode = visualOcclusionMode; - copy.visualOcclusionRaycastsPerTick = visualOcclusionRaycastsPerTick; - copy.visualOcclusionCacheTicks = visualOcclusionCacheTicks; - copy.visualSnapshotPredictionEnabled = visualSnapshotPredictionEnabled; - copy.visualSnapshotPredictionMaxSeconds = visualSnapshotPredictionMaxSeconds; - copy.visualSnapshotSmoothingEnabled = visualSnapshotSmoothingEnabled; - copy.visualSnapshotSmoothingRate = visualSnapshotSmoothingRate; - copy.solverIterations = solverIterations; - copy.stabilizationIterations = stabilizationIterations; - copy.dynamicSleepLinearThreshold = dynamicSleepLinearThreshold; - copy.dynamicSleepAngularThreshold = dynamicSleepAngularThreshold; - copy.dynamicSleepTimeUntilSleep = dynamicSleepTimeUntilSleep; - copy.extensionSettings = copyExtensionSettings(extensionSettings); - copy.entityVisualSyncCullingEnabled = entityVisualSyncCullingEnabled; - copy.visualVisibilityCullingEnabled = visualVisibilityCullingEnabled; - copy.detachedVisualMaterializationEnabled = detachedVisualMaterializationEnabled; - copy.detachedVisualMaterializationRadius = detachedVisualMaterializationRadius; - copy.detachedVisualDematerializationRadius = detachedVisualDematerializationRadius; - copy.detachedVisualMaxSpawnsPerTick = detachedVisualMaxSpawnsPerTick; - copy.detachedVisualMaxMaterialized = detachedVisualMaxMaterialized; - copy.detachedVisualInterestRefreshIntervalTicks = - detachedVisualInterestRefreshIntervalTicks; - copy.detachedVisualCandidateRefreshIntervalTicks = - detachedVisualCandidateRefreshIntervalTicks; - copy.detachedVisualVisibilityCheckIntervalTicks = - detachedVisualVisibilityCheckIntervalTicks; - copy.collisionLodEnabled = collisionLodEnabled; - copy.collisionLodNearRadius = collisionLodNearRadius; - copy.collisionLodMidRadius = collisionLodMidRadius; - copy.collisionLodHysteresis = collisionLodHysteresis; - copy.collisionLodRefreshIntervalTicks = collisionLodRefreshIntervalTicks; - copy.collisionLodFarSleepEnabled = collisionLodFarSleepEnabled; - copy.detachedVisualBlockType = detachedVisualBlockType; - return copy; - } - - @Nonnull - private static PersistentPhysicsExtensionSettingState[] extensionSettingsFrom( - @Nonnull PhysicsSpaceSettings settings) { - return settings.getExtensionSettings().asMap().entrySet().stream() - .flatMap(entry -> entry.getValue().entrySet().stream() - .map(setting -> PersistentPhysicsExtensionSettingState.from(entry.getKey(), - setting.getKey(), - setting.getValue()))) - .toArray(PersistentPhysicsExtensionSettingState[]::new); - } - - @Nonnull - private static PersistentPhysicsExtensionSettingState[] copyExtensionSettings( - @Nonnull PersistentPhysicsExtensionSettingState[] settings) { - if (settings.length == 0) { - return EMPTY_EXTENSION_SETTINGS; - } - PersistentPhysicsExtensionSettingState[] copy = - new PersistentPhysicsExtensionSettingState[settings.length]; - for (int i = 0; i < settings.length; i++) { - copy[i] = settings[i].copy(); - } - return copy; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStateBlock.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStateBlock.java deleted file mode 100644 index ce5147b8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStateBlock.java +++ /dev/null @@ -1,367 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.github.luben.zstd.Zstd; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.codec.validation.Validators; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.zip.CRC32; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.bson.BsonBinaryReader; -import org.bson.BsonBinaryWriter; -import org.bson.BsonDocument; -import org.bson.BsonValue; -import org.bson.codecs.BsonDocumentCodec; -import org.bson.codecs.DecoderContext; -import org.bson.codecs.EncoderContext; -import org.bson.io.BasicOutputBuffer; - -/** - * Compressed block for high-cardinality persistent physics state. - */ -public class PersistentPhysicsStateBlock { - - static final String KIND_BODIES = "bodies"; - static final String KIND_JOINTS = "joints"; - static final String CODEC_BSON_ARRAY_V1 = "bson-array-v1"; - static final String COMPRESSION_ZSTD = "zstd"; - private static final String PAYLOAD_ITEMS_KEY = "Items"; - private static final Set SUPPORTED_KINDS = Set.of(KIND_BODIES, KIND_JOINTS); - private static final int ZSTD_COMPRESSION_LEVEL = 3; - private static final int MAX_UNCOMPRESSED_BYTES = 64 * 1024 * 1024; - private static final String UNCOMPRESSED_BYTES_LIMIT_MESSAGE = - "Persistent physics state block uncompressed size exceeds limit"; - private static final byte[] EMPTY_PAYLOAD = new byte[0]; - private static final Codec BINARY_PAYLOAD_CODEC = new PersistentPhysicsBinaryPayloadCodec(); - private static final BsonDocumentCodec BSON_DOCUMENT_CODEC = new BsonDocumentCodec(); - private static final ArrayCodec BODY_ARRAY_CODEC = - new ArrayCodec<>(PersistentPhysicsBodyState.CODEC, PersistentPhysicsBodyState[]::new); - private static final ArrayCodec JOINT_ARRAY_CODEC = - new ArrayCodec<>(PersistentPhysicsJointState.CODEC, PersistentPhysicsJointState[]::new); - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PersistentPhysicsStateBlock.class, - PersistentPhysicsStateBlock::new) - .append(new KeyedCodec<>("Kind", Codec.STRING, false), - (block, value) -> block.kind = value, - PersistentPhysicsStateBlock::getKind) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.stringIn(SUPPORTED_KINDS, - "Persistent physics state block kind is unsupported")) - .add() - .append(new KeyedCodec<>("Codec", Codec.STRING, false), - (block, value) -> block.codec = value, - PersistentPhysicsStateBlock::getCodec) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.stringEquals(CODEC_BSON_ARRAY_V1, - "Persistent physics state block codec is unsupported")) - .add() - .append(new KeyedCodec<>("Compression", Codec.STRING, false), - (block, value) -> block.compression = value, - PersistentPhysicsStateBlock::getCompression) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.stringEquals(COMPRESSION_ZSTD, - "Persistent physics state block compression is unsupported")) - .add() - .append(new KeyedCodec<>("SchemaVersion", Codec.INTEGER, false), - (block, value) -> block.schemaVersion = value, - PersistentPhysicsStateBlock::getSchemaVersion) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(PersistentPhysicsWorldResource.CURRENT_SCHEMA_VERSION, - PersistentPhysicsWorldResource.CURRENT_SCHEMA_VERSION)) - .add() - .append(new KeyedCodec<>("BlockIndex", Codec.INTEGER, false), - (block, value) -> block.blockIndex = value, - PersistentPhysicsStateBlock::getBlockIndex) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0, Integer.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("SpaceId", Codec.INTEGER, false), - (block, value) -> block.spaceId = value, - PersistentPhysicsStateBlock::getSpaceId) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, Integer.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("ItemCount", Codec.INTEGER, false), - (block, value) -> block.itemCount = value, - PersistentPhysicsStateBlock::getItemCount) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0, Integer.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("UncompressedBytes", Codec.INTEGER, false), - (block, value) -> block.uncompressedBytes = value, - PersistentPhysicsStateBlock::getUncompressedBytes) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, Integer.MAX_VALUE)) - .addValidator(PersistentPhysicsValidation.intAtMost(MAX_UNCOMPRESSED_BYTES, - UNCOMPRESSED_BYTES_LIMIT_MESSAGE)) - .add() - .append(new KeyedCodec<>("CompressedBytes", Codec.INTEGER, false), - (block, value) -> block.compressedBytes = value, - PersistentPhysicsStateBlock::getCompressedBytes) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(1, Integer.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("Crc32", Codec.LONG, false), - (block, value) -> block.crc32 = value, - PersistentPhysicsStateBlock::getCrc32) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(0L, 0xffff_ffffL)) - .add() - .append(new KeyedCodec<>("Payload", BINARY_PAYLOAD_CODEC, false), - (block, value) -> block.payload = copyPayload(value), - PersistentPhysicsStateBlock::getPayload) - .addValidator(Validators.nonNull()) - .addValidator(PersistentPhysicsValidation.nonEmptyBytes( - "Persistent physics state block payload cannot be empty")) - .add() - .build(); - - @Nonnull - private String kind = ""; - @Nonnull - private String codec = ""; - @Nonnull - private String compression = ""; - @Getter - private int schemaVersion; - @Getter - private int blockIndex; - @Getter - private int spaceId; - @Getter - private int itemCount; - @Getter - private int uncompressedBytes; - @Getter - private int compressedBytes; - @Getter - private long crc32; - @Nonnull - private byte[] payload = EMPTY_PAYLOAD; - - public PersistentPhysicsStateBlock() { - } - - @Nonnull - static PersistentPhysicsStateBlock[] bodyBlocks(@Nonnull PersistentPhysicsBodyState[] bodies) { - Map> bySpace = new LinkedHashMap<>(); - for (PersistentPhysicsBodyState body : bodies) { - bySpace.computeIfAbsent(body.getSpaceId(), ignored -> new ArrayList<>()).add(body.copy()); - } - - List blocks = new ArrayList<>(bySpace.size()); - int blockIndex = 0; - for (Map.Entry> entry : bySpace.entrySet()) { - PersistentPhysicsBodyState[] states = entry.getValue().toArray(PersistentPhysicsBodyState[]::new); - BsonDocument payloadDocument = new BsonDocument(); - payloadDocument.put(PAYLOAD_ITEMS_KEY, BODY_ARRAY_CODEC.encode(states, new ExtraInfo())); - blocks.add(compressed(KIND_BODIES, blockIndex++, entry.getKey(), states.length, payloadDocument)); - } - return blocks.toArray(PersistentPhysicsStateBlock[]::new); - } - - @Nonnull - static PersistentPhysicsStateBlock[] jointBlocks(@Nonnull PersistentPhysicsJointState[] joints) { - Map> bySpace = new LinkedHashMap<>(); - for (PersistentPhysicsJointState joint : joints) { - bySpace.computeIfAbsent(joint.getSpaceId(), ignored -> new ArrayList<>()).add(joint.copy()); - } - - List blocks = new ArrayList<>(bySpace.size()); - int blockIndex = 0; - for (Map.Entry> entry : bySpace.entrySet()) { - PersistentPhysicsJointState[] states = entry.getValue().toArray(PersistentPhysicsJointState[]::new); - BsonDocument payloadDocument = new BsonDocument(); - payloadDocument.put(PAYLOAD_ITEMS_KEY, JOINT_ARRAY_CODEC.encode(states, new ExtraInfo())); - blocks.add(compressed(KIND_JOINTS, blockIndex++, entry.getKey(), states.length, payloadDocument)); - } - return blocks.toArray(PersistentPhysicsStateBlock[]::new); - } - - @Nonnull - static PersistentPhysicsBodyState[] decodeBodyBlocks(@Nonnull PersistentPhysicsStateBlock[] blocks) { - List states = new ArrayList<>(); - for (PersistentPhysicsStateBlock block : blocks) { - BsonValue items = block.inflate(KIND_BODIES).get(PAYLOAD_ITEMS_KEY); - PersistentPhysicsBodyState[] decoded = BODY_ARRAY_CODEC.decode(items, new ExtraInfo()); - assert decoded != null; - block.requireItemCount(decoded.length); - for (PersistentPhysicsBodyState state : decoded) { - states.add(state.copy()); - } - } - return states.toArray(PersistentPhysicsBodyState[]::new); - } - - @Nonnull - static PersistentPhysicsJointState[] decodeJointBlocks(@Nonnull PersistentPhysicsStateBlock[] blocks) { - List states = new ArrayList<>(); - for (PersistentPhysicsStateBlock block : blocks) { - BsonValue items = block.inflate(KIND_JOINTS).get(PAYLOAD_ITEMS_KEY); - PersistentPhysicsJointState[] decoded = JOINT_ARRAY_CODEC.decode(items, new ExtraInfo()); - assert decoded != null; - block.requireItemCount(decoded.length); - for (PersistentPhysicsJointState state : decoded) { - states.add(state.copy()); - } - } - return states.toArray(PersistentPhysicsJointState[]::new); - } - - @Nonnull - PersistentPhysicsStateBlock copy() { - PersistentPhysicsStateBlock copy = new PersistentPhysicsStateBlock(); - copy.kind = kind; - copy.codec = codec; - copy.compression = compression; - copy.schemaVersion = schemaVersion; - copy.blockIndex = blockIndex; - copy.spaceId = spaceId; - copy.itemCount = itemCount; - copy.uncompressedBytes = uncompressedBytes; - copy.compressedBytes = compressedBytes; - copy.crc32 = crc32; - copy.payload = copyPayload(payload); - return copy; - } - - @Nonnull - public String getKind() { - return kind; - } - - @Nonnull - public String getCodec() { - return codec; - } - - @Nonnull - public String getCompression() { - return compression; - } - - @Nonnull - public byte[] getPayload() { - return copyPayload(payload); - } - - @Nonnull - private static PersistentPhysicsStateBlock compressed(@Nonnull String kind, - int blockIndex, - int spaceId, - int itemCount, - @Nonnull BsonDocument payloadDocument) { - byte[] uncompressed = writeBson(payloadDocument); - requireUncompressedByteLimit(uncompressed.length); - byte[] compressed = Zstd.compress(uncompressed, ZSTD_COMPRESSION_LEVEL); - - PersistentPhysicsStateBlock block = new PersistentPhysicsStateBlock(); - block.kind = kind; - block.codec = CODEC_BSON_ARRAY_V1; - block.compression = COMPRESSION_ZSTD; - block.schemaVersion = PersistentPhysicsWorldResource.CURRENT_SCHEMA_VERSION; - block.blockIndex = blockIndex; - block.spaceId = spaceId; - block.itemCount = itemCount; - block.uncompressedBytes = uncompressed.length; - block.compressedBytes = compressed.length; - block.crc32 = crc32(uncompressed); - block.payload = compressed; - return block; - } - - @Nonnull - private BsonDocument inflate(@Nonnull String expectedKind) { - validateEnvelope(expectedKind); - byte[] uncompressed = Zstd.decompress(payload, uncompressedBytes); - if (uncompressed.length != uncompressedBytes) { - throw new IllegalStateException("Persistent physics state block decompressed to " - + uncompressed.length + " bytes, expected " + uncompressedBytes); - } - long actualCrc32 = crc32(uncompressed); - if (actualCrc32 != crc32) { - throw new IllegalStateException("Persistent physics state block checksum mismatch for " - + kind + " block " + blockIndex); - } - return readBson(uncompressed); - } - - private void validateEnvelope(@Nonnull String expectedKind) { - if (!expectedKind.equals(kind)) { - throw new IllegalStateException("Persistent physics state block kind mismatch: expected " - + expectedKind + ", found " + kind); - } - if (!CODEC_BSON_ARRAY_V1.equals(codec)) { - throw new IllegalStateException("Persistent physics state block codec is unsupported: " + codec); - } - if (!COMPRESSION_ZSTD.equals(compression)) { - throw new IllegalStateException("Persistent physics state block compression is unsupported: " + compression); - } - if (schemaVersion != PersistentPhysicsWorldResource.CURRENT_SCHEMA_VERSION) { - throw new IllegalStateException("Persistent physics state block schema is unsupported: " + schemaVersion); - } - requireUncompressedByteLimit(uncompressedBytes); - if (payload.length != compressedBytes) { - throw new IllegalStateException("Persistent physics state block compressed size mismatch for " - + kind + " block " + blockIndex); - } - } - - private static void requireUncompressedByteLimit(int byteCount) { - if (byteCount > MAX_UNCOMPRESSED_BYTES) { - throw new IllegalStateException(UNCOMPRESSED_BYTES_LIMIT_MESSAGE - + ": " + byteCount + " > " + MAX_UNCOMPRESSED_BYTES); - } - } - - private void requireItemCount(int decodedCount) { - if (decodedCount != itemCount) { - throw new IllegalStateException("Persistent physics state block item count mismatch for " - + kind + " block " + blockIndex + ": expected " + itemCount + ", decoded " + decodedCount); - } - } - - @Nonnull - private static byte[] writeBson(@Nonnull BsonDocument document) { - try (BasicOutputBuffer output = new BasicOutputBuffer()) { - try (BsonBinaryWriter writer = new BsonBinaryWriter(output)) { - BSON_DOCUMENT_CODEC.encode(writer, document, EncoderContext.builder().build()); - } - return output.toByteArray(); - } - } - - @Nonnull - private static BsonDocument readBson(@Nonnull byte[] bytes) { - try (BsonBinaryReader reader = new BsonBinaryReader(ByteBuffer.wrap(bytes))) { - return BSON_DOCUMENT_CODEC.decode(reader, DecoderContext.builder().build()); - } - } - - private static long crc32(@Nonnull byte[] bytes) { - CRC32 crc32 = new CRC32(); - crc32.update(bytes); - return crc32.getValue(); - } - - @Nonnull - private static byte[] copyPayload(byte[] payload) { - if (payload == null || payload.length == 0) { - return EMPTY_PAYLOAD; - } - return Arrays.copyOf(payload, payload.length); - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsValidation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsValidation.java deleted file mode 100644 index 174a48a7..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsValidation.java +++ /dev/null @@ -1,187 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.schema.SchemaContext; -import com.hypixel.hytale.codec.schema.config.Schema; -import com.hypixel.hytale.codec.validation.Validator; -import com.hypixel.hytale.codec.validation.ValidationResults; -import dev.hytalemodding.impulse.api.ShapeType; -import java.util.Set; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -final class PersistentPhysicsValidation { - - private PersistentPhysicsValidation() { - } - - @Nonnull - static Validator finiteFloat(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(Float value, ValidationResults results) { - if (value == null) { - return; - } - if (!Float.isFinite(value)) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator nonNegativeFiniteFloat(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(Float value, ValidationResults results) { - if (value == null) { - return; - } - if (!Float.isFinite(value) || value < 0.0f) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator intAtMost(int max, @Nonnull String message) { - return new Validator<>() { - @Override - public void accept(Integer value, ValidationResults results) { - if (value == null) { - return; - } - if (value > max) { - results.fail(message + ": " + value + " > " + max); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator finiteVector(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(Vector3f value, ValidationResults results) { - if (value == null) { - return; - } - if (!Float.isFinite(value.x) || !Float.isFinite(value.y) || !Float.isFinite(value.z)) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator persistentShapeType() { - return new Validator<>() { - @Override - public void accept(ShapeType value, ValidationResults results) { - if (value == null) { - return; - } - if (value == ShapeType.UNKNOWN || value == ShapeType.VOXELS) { - results.fail("Persisted body shape is unsupported: " + value); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator nonBlankString(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(String value, ValidationResults results) { - if (value == null) { - return; - } - if (value.isBlank()) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator stringEquals(@Nonnull String expected, @Nonnull String message) { - return new Validator<>() { - @Override - public void accept(String value, ValidationResults results) { - if (value == null) { - return; - } - if (!expected.equals(value)) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator stringIn(@Nonnull Set expected, @Nonnull String message) { - return new Validator<>() { - @Override - public void accept(String value, ValidationResults results) { - if (value == null) { - return; - } - if (!expected.contains(value)) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator nonEmptyBytes(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(byte[] value, ValidationResults results) { - if (value == null) { - return; - } - if (value.length == 0) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java deleted file mode 100644 index cbf4a4e1..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsWorldResource.java +++ /dev/null @@ -1,565 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.codec.schema.SchemaContext; -import com.hypixel.hytale.codec.schema.config.Schema; -import com.hypixel.hytale.codec.validation.Validator; -import com.hypixel.hytale.codec.validation.ValidationResults; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceResource; -import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistenceSyncResult; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.Arrays; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Codec-backed world-level physics resource for the persistence layer. - * - *

This resource stores the world-level state that does not belong - * on individual entities: the space definitions (id, backend, gravity, world-collision - * settings), the body states (keyed by stable physics body keys), the joint - * definitions (keyed by endpoint body keys), and world simulation settings.

- * - *

The {@code runtimeRestorePending} flag is set by {@code afterDecode} whenever - * Hytale deserializes this resource. It signals the hydration systems that they - * need to recreate live spaces, bodies, and joints from the persisted data. The - * flag is cleared once restore finishes.

- * - *

Restore semantics are intentionally asymmetric. Missing saved backends are a - * hard failure because the world-level space definitions cannot be recreated at - * all. Missing entities, missing runtime bodies, and unresolved joints are softer - * failures: those entries are skipped, counted, and reported so restore can finish - * instead of hanging forever.

- */ -public class PersistentPhysicsWorldResource extends PhysicsPersistenceResource { - - public static final int CURRENT_SCHEMA_VERSION = PhysicsPersistenceResource.CURRENT_SCHEMA_VERSION; - private static final PersistentPhysicsSpaceState[] EMPTY_SPACES = new PersistentPhysicsSpaceState[0]; - private static final PersistentPhysicsBodyState[] EMPTY_BODIES = new PersistentPhysicsBodyState[0]; - private static final PersistentPhysicsJointState[] EMPTY_JOINTS = new PersistentPhysicsJointState[0]; - private static final PersistentPhysicsStateBlock[] EMPTY_STATE_BLOCKS = new PersistentPhysicsStateBlock[0]; - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PersistentPhysicsWorldResource.class, - PersistentPhysicsWorldResource::new) - .append(new KeyedCodec<>("SchemaVersion", Codec.INTEGER, false), - PersistentPhysicsWorldResource::setSchemaVersion, - PersistentPhysicsWorldResource::getSchemaVersion) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(CURRENT_SCHEMA_VERSION, CURRENT_SCHEMA_VERSION)) - .add() - .append(new KeyedCodec<>("SimulationSteps", Codec.INTEGER), - (resource, value) -> resource.worldSettings.setSimulationSteps(value), - resource -> resource.worldSettings.getSimulationSteps()) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range( - PhysicsWorldSettings.MIN_SIMULATION_STEPS, - PhysicsWorldSettings.MAX_SIMULATION_STEPS)) - .add() - .append(new KeyedCodec<>("StepMode", Codec.STRING), - (resource, value) -> resource.worldSettings.setStepMode(PhysicsStepMode.parse(value)), - resource -> resource.worldSettings.getStepMode().getSerializedName()) - .addValidator(Validators.nonNull()) - .addValidator(stepModeName()) - .add() - .append(new KeyedCodec<>("StepSchedulingMode", Codec.STRING), - (resource, value) -> resource.worldSettings.setStepSchedulingMode( - PhysicsStepSchedulingMode.parse(value)), - resource -> resource.worldSettings.getStepSchedulingMode().getSerializedName()) - .addValidator(Validators.nonNull()) - .addValidator(stepSchedulingModeName()) - .add() - .append(new KeyedCodec<>("EventCollectionMode", Codec.STRING, false), - (resource, value) -> resource.worldSettings.setEventCollectionMode( - PhysicsEventCollectionMode.parse(value)), - resource -> resource.worldSettings.getEventCollectionMode().getSerializedName()) - .addValidator(Validators.nonNull()) - .addValidator(eventCollectionModeName()) - .add() - .append(new KeyedCodec<>("MaxStepDt", Codec.FLOAT), - (resource, value) -> resource.worldSettings.setMaxStepDt(value), - resource -> resource.worldSettings.getMaxStepDt()) - .addValidator(Validators.nonNull()) - .addValidator(Validators.greaterThan(0.0f)) - .addValidator(Validators.max(Float.MAX_VALUE)) - .add() - .append(new KeyedCodec<>("Spaces", - new ArrayCodec<>(PersistentPhysicsSpaceState.CODEC, PersistentPhysicsSpaceState[]::new)), - (resource, value) -> resource.spaces = copySpaces(value), - PersistentPhysicsWorldResource::getSpaces) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("BodyBlocks", - new ArrayCodec<>(PersistentPhysicsStateBlock.CODEC, PersistentPhysicsStateBlock[]::new)), - (resource, value) -> resource.bodyBlocks = copyStateBlocks(value), - PersistentPhysicsWorldResource::getBodyBlocksForCodec) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("JointBlocks", - new ArrayCodec<>(PersistentPhysicsStateBlock.CODEC, PersistentPhysicsStateBlock[]::new)), - (resource, value) -> resource.jointBlocks = copyStateBlocks(value), - PersistentPhysicsWorldResource::getJointBlocksForCodec) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .afterDecode(PersistentPhysicsWorldResource::afterCodecDecode) - .build(); - - private int schemaVersion = CURRENT_SCHEMA_VERSION; - @Nonnull - private final PhysicsWorldSettings worldSettings = new PhysicsWorldSettings(); - @Nonnull - private PersistentPhysicsSpaceState[] spaces = EMPTY_SPACES; - @Nonnull - private PersistentPhysicsBodyState[] bodies = EMPTY_BODIES; - @Nonnull - private PersistentPhysicsJointState[] joints = EMPTY_JOINTS; - @Nonnull - private PersistentPhysicsStateBlock[] bodyBlocks = EMPTY_STATE_BLOCKS; - @Nonnull - private PersistentPhysicsStateBlock[] jointBlocks = EMPTY_STATE_BLOCKS; - private transient boolean runtimeRestorePending; - private transient boolean runtimeSpaceBootstrapComplete; - private transient boolean runtimeRestoreFailed; - @Nonnull - private transient String runtimeRestoreFailureMessage = ""; - private transient int runtimeRestoredSpaceCount; - private transient int runtimeRestoredBodyCount; - private transient int runtimeRestoredJointCount; - private transient long runtimeRestoreGeneration; - @Nonnull - private final transient Object2IntMap runtimeSkippedBodiesByReason = new Object2IntLinkedOpenHashMap<>(); - @Nonnull - private final transient Set runtimeSkippedBodyKeys = new ObjectOpenHashSet<>(); - @Nonnull - private final transient Object2IntMap runtimeSkippedJointsByReason = new Object2IntLinkedOpenHashMap<>(); - @Nonnull - private final transient Set runtimeSkippedJointKeys = new ObjectOpenHashSet<>(); - private transient boolean runtimeSnapshotSynced; - private transient int runtimeSnapshotSyncSkipTicks; - - public PersistentPhysicsWorldResource() { - } - - @Nonnull - @SuppressWarnings("unchecked") - public static ResourceType getResourceType() { - return (ResourceType) - ImpulsePlugin.get().getPersistentPhysicsWorldResourceType(); - } - - @Override - public int getSchemaVersion() { - return schemaVersion; - } - - public void setSchemaVersion(int schemaVersion) { - if (schemaVersion != CURRENT_SCHEMA_VERSION) { - throw new IllegalArgumentException("Schema version must be " - + CURRENT_SCHEMA_VERSION); - } - this.schemaVersion = schemaVersion; - } - - @Nonnull - @Override - public PhysicsPersistenceSyncResult saveRuntimeSnapshot(@Nonnull Store store, - @Nonnull PhysicsWorldResource runtime) { - return new PhysicsPersistenceSyncResult(false, - getSpaceCount(), - getBodyCount(), - getJointCount(), - "legacy-persistence-import-only"); - } - - @Nonnull - public PhysicsWorldSettings getWorldSettings() { - return new PhysicsWorldSettings(worldSettings); - } - - public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { - worldSettings.copyFrom(settings); - } - - @Nonnull - public PersistentPhysicsSpaceState[] getSpaces() { - return copySpaces(spaces); - } - - @Override - public int getSpaceCount() { - return spaces.length; - } - - public void setSpaces(@Nonnull PersistentPhysicsSpaceState[] spaces) { - this.spaces = copySpaces(spaces); - } - - @Nonnull - public PersistentPhysicsBodyState[] getBodies() { - return copyBodies(bodies); - } - - @Override - public int getBodyCount() { - return bodies.length; - } - - public void setBodies(@Nonnull PersistentPhysicsBodyState[] bodies) { - this.bodies = copyBodies(bodies); - } - - @Nonnull - public PersistentPhysicsJointState[] getJoints() { - return copyJoints(joints); - } - - @Override - public int getJointCount() { - return joints.length; - } - - public void setJoints(@Nonnull PersistentPhysicsJointState[] joints) { - this.joints = copyJoints(joints); - } - - @Override - public boolean isRuntimeRestorePending() { - return runtimeRestorePending; - } - - @Override - public void markRuntimeRestorePending() { - runtimeRestoreGeneration++; - runtimeRestorePending = true; - runtimeSpaceBootstrapComplete = false; - runtimeRestoreFailed = false; - runtimeRestoreFailureMessage = ""; - runtimeRestoredSpaceCount = 0; - runtimeRestoredBodyCount = 0; - runtimeRestoredJointCount = 0; - runtimeSnapshotSynced = false; - runtimeSnapshotSyncSkipTicks = 0; - runtimeSkippedBodiesByReason.clear(); - runtimeSkippedBodyKeys.clear(); - runtimeSkippedJointsByReason.clear(); - runtimeSkippedJointKeys.clear(); - } - - @Override - public boolean isRuntimeSpaceBootstrapComplete() { - return runtimeSpaceBootstrapComplete; - } - - public void clearRuntimeRestorePending() { - runtimeRestoreGeneration++; - runtimeRestorePending = false; - runtimeSnapshotSynced = false; - runtimeSnapshotSyncSkipTicks = 0; - } - - public long runtimeRestoreGeneration() { - return runtimeRestoreGeneration; - } - - public boolean shouldSyncRuntimeSnapshot(int intervalTicks) { - if (!runtimeSnapshotSynced || intervalTicks <= 0) { - return true; - } - if (runtimeSnapshotSyncSkipTicks < intervalTicks) { - runtimeSnapshotSyncSkipTicks++; - return false; - } - return true; - } - - public void markRuntimeSnapshotSynced() { - runtimeSnapshotSynced = true; - runtimeSnapshotSyncSkipTicks = 0; - } - - public void markRuntimeSpaceBootstrapComplete(int restoredSpaceCount) { - runtimeSpaceBootstrapComplete = true; - runtimeRestoredSpaceCount = restoredSpaceCount; - } - - @Override - public boolean hasRuntimeRestoreFailed() { - return runtimeRestoreFailed; - } - - public void failRuntimeRestore(@Nonnull String message) { - runtimeRestorePending = false; - runtimeRestoreFailed = true; - runtimeRestoreFailureMessage = message; - } - - public void recordRuntimeBodyRestored() { - runtimeRestoredBodyCount++; - } - - public void recordRuntimeBodySkipped(@Nonnull String reason) { - runtimeSkippedBodiesByReason.put(reason, runtimeSkippedBodiesByReason.getInt(reason) + 1); - } - - public void recordRuntimeBodySkipped(@Nonnull RigidBodyKey bodyKey, @Nonnull String reason) { - runtimeSkippedBodyKeys.add(bodyKey); - recordRuntimeBodySkipped(reason); - } - - public boolean isRuntimeBodySkipped(@Nonnull RigidBodyKey bodyKey) { - return runtimeSkippedBodyKeys.contains(bodyKey); - } - - public void recordRuntimeJointRestored() { - runtimeRestoredJointCount++; - } - - public void recordRuntimeJointSkipped(@Nonnull String key, @Nonnull String reason) { - if (!runtimeSkippedJointKeys.add(key)) { - return; - } - runtimeSkippedJointsByReason.put(reason, runtimeSkippedJointsByReason.getInt(reason) + 1); - } - - @Override - public boolean hasRuntimeRestoreSkips() { - return !runtimeSkippedBodiesByReason.isEmpty() || !runtimeSkippedJointsByReason.isEmpty(); - } - - @Nonnull - @Override - public String runtimeRestoreSummary() { - return "Impulse persistence restore completed: " - + runtimeRestoredSpaceCount + " spaces, " - + runtimeRestoredBodyCount + " bodies restored, " - + countReasons(runtimeSkippedBodiesByReason) + " bodies skipped (" - + formatReasons(runtimeSkippedBodiesByReason) + "), " - + runtimeRestoredJointCount + " joints restored, " - + countReasons(runtimeSkippedJointsByReason) + " joints skipped (" - + formatReasons(runtimeSkippedJointsByReason) + ")."; - } - - @Nonnull - @Override - public String runtimeRestoreFailureSummary() { - return "Impulse persistence restore failed: " + runtimeRestoreFailureMessage + " " - + "Partial progress before failure: " - + runtimeRestoredSpaceCount + " spaces, " - + runtimeRestoredBodyCount + " bodies restored, " - + countReasons(runtimeSkippedBodiesByReason) + " bodies skipped, " - + runtimeRestoredJointCount + " joints restored, " - + countReasons(runtimeSkippedJointsByReason) + " joints skipped."; - } - - public void copyFrom(@Nonnull PersistentPhysicsWorldResource other) { - if (this == other) { - return; - } - setSchemaVersion(other.schemaVersion); - worldSettings.copyFrom(other.worldSettings); - spaces = copySpaces(other.spaces); - bodies = copyBodies(other.bodies); - joints = copyJoints(other.joints); - bodyBlocks = EMPTY_STATE_BLOCKS; - jointBlocks = EMPTY_STATE_BLOCKS; - runtimeRestorePending = false; - runtimeSpaceBootstrapComplete = false; - runtimeRestoreFailed = false; - runtimeRestoreFailureMessage = ""; - runtimeRestoredSpaceCount = 0; - runtimeRestoredBodyCount = 0; - runtimeRestoredJointCount = 0; - runtimeRestoreGeneration = 0L; - runtimeSnapshotSynced = false; - runtimeSnapshotSyncSkipTicks = 0; - runtimeSkippedBodiesByReason.clear(); - runtimeSkippedBodyKeys.clear(); - runtimeSkippedJointsByReason.clear(); - runtimeSkippedJointKeys.clear(); - } - - private void afterCodecDecode() { - if (bodyBlocks.length > 0) { - bodies = PersistentPhysicsStateBlock.decodeBodyBlocks(bodyBlocks); - } - if (jointBlocks.length > 0) { - joints = PersistentPhysicsStateBlock.decodeJointBlocks(jointBlocks); - } - bodyBlocks = EMPTY_STATE_BLOCKS; - jointBlocks = EMPTY_STATE_BLOCKS; - markRuntimeRestorePending(); - } - - @Nonnull - private PersistentPhysicsStateBlock[] getBodyBlocksForCodec() { - if (bodies.length == 0) { - return EMPTY_STATE_BLOCKS; - } - return PersistentPhysicsStateBlock.bodyBlocks(bodies); - } - - @Nonnull - private PersistentPhysicsStateBlock[] getJointBlocksForCodec() { - if (joints.length == 0) { - return EMPTY_STATE_BLOCKS; - } - return PersistentPhysicsStateBlock.jointBlocks(joints); - } - - @Nonnull - private static PersistentPhysicsBodyState[] copyBodies(@Nonnull PersistentPhysicsBodyState[] source) { - PersistentPhysicsBodyState[] copy = Arrays.copyOf(source, source.length); - for (int i = 0; i < copy.length; i++) { - copy[i] = copy[i].copy(); - } - return copy; - } - - @Nonnull - @Override - public PersistentPhysicsWorldResource clone() { - PersistentPhysicsWorldResource copy = new PersistentPhysicsWorldResource(); - copy.copyFrom(this); - return copy; - } - - @Nonnull - private static PersistentPhysicsSpaceState[] copySpaces(@Nonnull PersistentPhysicsSpaceState[] source) { - PersistentPhysicsSpaceState[] copy = Arrays.copyOf(source, source.length); - for (int i = 0; i < copy.length; i++) { - copy[i] = copy[i].copy(); - } - return copy; - } - - @Nonnull - private static PersistentPhysicsJointState[] copyJoints(@Nonnull PersistentPhysicsJointState[] source) { - PersistentPhysicsJointState[] copy = Arrays.copyOf(source, source.length); - for (int i = 0; i < copy.length; i++) { - copy[i] = copy[i].copy(); - } - return copy; - } - - @Nonnull - private static PersistentPhysicsStateBlock[] copyStateBlocks(@Nullable PersistentPhysicsStateBlock[] source) { - if (source == null || source.length == 0) { - return EMPTY_STATE_BLOCKS; - } - PersistentPhysicsStateBlock[] copy = Arrays.copyOf(source, source.length); - for (int i = 0; i < copy.length; i++) { - copy[i] = copy[i].copy(); - } - return copy; - } - - @Nonnull - private static Validator stepModeName() { - return new Validator<>() { - @Override - public void accept(String value, ValidationResults results) { - if (value == null) { - return; - } - try { - PhysicsStepMode.parse(value); - } catch (IllegalArgumentException exception) { - results.fail("Persistent physics step mode is unknown: " + value); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - private static Validator stepSchedulingModeName() { - return new Validator<>() { - @Override - public void accept(String value, ValidationResults results) { - if (value == null) { - return; - } - try { - PhysicsStepSchedulingMode.parse(value); - } catch (IllegalArgumentException exception) { - results.fail("Persistent physics step scheduling mode is unknown: " + value); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - private static Validator eventCollectionModeName() { - return new Validator<>() { - @Override - public void accept(String value, ValidationResults results) { - if (value == null) { - return; - } - try { - PhysicsEventCollectionMode.parse(value); - } catch (IllegalArgumentException exception) { - results.fail("Persistent physics event collection mode is unknown: " + value); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - private static int countReasons(@Nonnull Object2IntMap reasons) { - int total = 0; - for (int count : reasons.values()) { - total += count; - } - return total; - } - - @Nonnull - private static String formatReasons(@Nonnull Object2IntMap reasons) { - if (reasons.isEmpty()) { - return "none"; - } - - StringBuilder builder = new StringBuilder(); - boolean first = true; - for (Object2IntMap.Entry entry : reasons.object2IntEntrySet()) { - if (!first) { - builder.append(", "); - } - builder.append(entry.getKey()).append('=').append(entry.getIntValue()); - first = false; - } - return builder.toString(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java index 5ff00361..78991d8a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java @@ -1,178 +1,57 @@ package dev.hytalemodding.impulse.core.internal.systems.visual; -import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.NonSerialized; -import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemGroupDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.server.core.entity.entities.BlockEntity; -import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.modules.physics.component.Velocity; -import com.hypixel.hytale.server.core.modules.time.TimeResource; -import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; -import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.GeneratedVisualProxyView; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Queue; import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.function.ToIntFunction; import java.util.WeakHashMap; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3d; -import org.joml.Vector3f; /** - * Materializes disposable Hytale visual followers for detached physics bodies near players. - * - *

Detached bodies stay physics-authoritative and are not persisted through these visual - * proxies. Proxies are ordinary Hytale block entities with a generated - * {@link BodyAttachmentComponent}, so removing a proxy never removes the backend body.

+ * Removes serialized generated visual proxies left by the pre-PhysicsStore runtime model. */ public class PhysicsDetachedVisualMaterializationSystem extends TickingSystem { + private static final int CLEANUP_INTERVAL_TICKS = 40; + private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()) ); - - /** - * Bounds generated-proxy orphan scans. Regular materialized-proxy - * visibility checks are cached separately below. - */ - private static final int ORPHAN_VISUAL_CLEANUP_INTERVAL_TICKS = 40; @Nonnull - private final Map, MaterializationState> statesByStore = + private final Map, Integer> cleanupCooldowns = Collections.synchronizedMap(new WeakHashMap<>()); @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsRuntimeProfilingResource profiling = store.getResource( - PhysicsRuntimeProfilingResource.getResourceType()); - PhysicsRuntimeProfilingResource.VisualCollector collector = profiling.isEnabled() - ? profiling.beginVisualSample() - : null; - long tickStart = collector != null ? System.nanoTime() : 0L; - try { - tickMaterialization(store, collector); - } finally { - if (collector != null) { - profiling.finishVisualSample(collector, System.nanoTime() - tickStart); - } - } - } - - private void tickMaterialization(@Nonnull Store store, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - if (hasAuthoritativePhysicsStore(store)) { - MaterializationState state = stateFor(store); - clearCachedMaterializationState(state); - removeLegacyGeneratedVisualProxies(store); + if (shouldSkipCleanup(store)) { return; } - MaterializationState state = stateFor(store); - PersistentPhysicsWorldResource persistent = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); - if (shouldPauseForRestore(state, persistent)) { - return; - } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - if (state.orphanVisualCleanupCooldown <= 0) { - removeOrphanVisualFollowers(store, resource); - state.orphanVisualCleanupCooldown = ORPHAN_VISUAL_CLEANUP_INTERVAL_TICKS; - } else { - state.orphanVisualCleanupCooldown--; - } - long visualInterestTick = resource.advanceVisualInterestTick(); - GameplayAttachmentSnapshot gameplayAttachments = GameplayAttachmentSnapshot.forStore(store); - List interests = currentVisualInterests(state, - store, - resource, - collector); - DetachedVisualOcclusion.RaycastBudget raycastBudget = - new DetachedVisualOcclusion.RaycastBudget(); - int materialized = currentMaterializedProxyCount(state, - store, - resource, - interests, - gameplayAttachments, - collector); - refreshCachedMaterializationTargets(state, - store, - resource, - interests, - visualInterestTick, - raycastBudget, - gameplayAttachments, - collector); - materialized += spawnCachedMaterializationTargets(state, - store, - resource, - materialized, - interests, - visualInterestTick, - raycastBudget, - gameplayAttachments, - collector); - if (collector != null) { - collector.setMaterialized(materialized); - } + removeLegacyGeneratedVisualProxies(store); } - private static boolean hasAuthoritativePhysicsStore(@Nonnull Store store) { - try { - ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); - return true; - } catch (ClassCastException | IllegalStateException exception) { + private boolean shouldSkipCleanup(@Nonnull Store store) { + synchronized (cleanupCooldowns) { + int cooldown = cleanupCooldowns.getOrDefault(store, 0); + if (cooldown > 0) { + cleanupCooldowns.put(store, cooldown - 1); + return true; + } + cleanupCooldowns.put(store, CLEANUP_INTERVAL_TICKS); return false; } } - private static void clearCachedMaterializationState(@Nonnull MaterializationState state) { - state.cachedInterests = List.of(); - state.cachedMaterializationTargets.clear(); - state.visualInterestRefreshCooldown = 0; - state.materializationCandidateRefreshCooldown = 0; - state.materializedVisibilityCheckCooldown = 0; - } - private static void removeLegacyGeneratedVisualProxies(@Nonnull Store store) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); @@ -186,6 +65,7 @@ private static void removeLegacyGeneratedVisualProxies(@Nonnull Store generatedProxyType = GeneratedVisualProxyComponent.getComponentType(); store.forEachEntityParallel(generatedProxyType, @@ -197,767 +77,6 @@ private static void removeLegacyGeneratedVisualProxies(@Nonnull Store store) { - synchronized (statesByStore) { - return statesByStore.computeIfAbsent(store, ignored -> new MaterializationState()); - } - } - - @Nonnull - private List currentVisualInterests( - @Nonnull MaterializationState state, - @Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - int refreshInterval = resolveVisualInterestRefreshInterval(resource); - state.visualInterestRefreshCooldown = Math.min(state.visualInterestRefreshCooldown, - refreshCooldown(refreshInterval)); - if (state.visualInterestRefreshCooldown <= 0) { - state.cachedInterests = VisualInterestCollector.collectMaterializationInterests(store, resource); - state.visualInterestRefreshCooldown = refreshCooldown(refreshInterval); - state.materializationCandidateRefreshCooldown = 0; - } else { - state.visualInterestRefreshCooldown--; - } - if (collector != null) { - collector.setInterests(state.cachedInterests.size()); - } - return state.cachedInterests; - } - - private int currentMaterializedProxyCount(@Nonnull MaterializationState state, - @Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull List interests, - @Nonnull GameplayAttachmentSnapshot gameplayAttachments, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - int refreshInterval = resolveMaterializedVisibilityCheckInterval(resource); - state.materializedVisibilityCheckCooldown = Math.min(state.materializedVisibilityCheckCooldown, - refreshCooldown(refreshInterval)); - if (state.materializedVisibilityCheckCooldown <= 0) { - state.materializedVisibilityCheckCooldown = refreshCooldown(refreshInterval); - return processMaterializedProxies(store, resource, interests, gameplayAttachments, collector); - } - - state.materializedVisibilityCheckCooldown--; - int materialized = resource.getGeneratedVisualProxyCount(); - if (collector != null) { - collector.addVisibilityCheckSkips(materialized); - } - return materialized; - } - - private void refreshCachedMaterializationTargets(@Nonnull MaterializationState state, - @Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull List interests, - long visualInterestTick, - @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, - @Nonnull GameplayAttachmentSnapshot gameplayAttachments, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - if (interests.isEmpty()) { - state.cachedMaterializationTargets.clear(); - state.materializationCandidateRefreshCooldown = 0; - if (collector != null) { - collector.setCandidates(0); - } - return; - } - - int refreshInterval = resolveMaterializationCandidateRefreshInterval(resource); - state.materializationCandidateRefreshCooldown = Math.min( - state.materializationCandidateRefreshCooldown, - refreshCooldown(refreshInterval)); - if (state.materializationCandidateRefreshCooldown <= 0) { - List refreshedTargets = new ArrayList<>(); - if (collector != null) { - collector.incrementCandidateRefreshes(); - } - collectMaterializationCandidates(store, - resource, - interests, - visualInterestTick, - raycastBudget, - gameplayAttachments, - refreshedTargets, - collector); - refreshedTargets.removeIf(Objects::isNull); - refreshedTargets.sort(Comparator.comparingDouble( - CachedMaterializationTarget::distanceSquared)); - state.cachedMaterializationTargets.clear(); - state.cachedMaterializationTargets.addAll(refreshedTargets); - state.materializationCandidateRefreshCooldown = refreshCooldown(refreshInterval); - } else { - state.materializationCandidateRefreshCooldown--; - if (collector != null) { - collector.incrementCandidateCacheUses(); - } - } - - if (collector != null) { - collector.setCandidates(state.cachedMaterializationTargets.size()); - } - } - - private int spawnCachedMaterializationTargets(@Nonnull MaterializationState state, - @Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - int materialized, - @Nonnull List interests, - long visualInterestTick, - @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, - @Nonnull GameplayAttachmentSnapshot gameplayAttachments, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - int spawned = 0; - int index = 0; - while (index < state.cachedMaterializationTargets.size()) { - CachedMaterializationTarget target = state.cachedMaterializationTargets.get(index); - if (target == null) { - state.cachedMaterializationTargets.remove(index); - continue; - } - MaterializationCandidate candidate = resolveCachedMaterializationCandidate(store, - resource, - target, - interests, - visualInterestTick, - raycastBudget, - gameplayAttachments, - collector); - if (candidate == null) { - state.cachedMaterializationTargets.remove(index); - continue; - } - if (spawned >= candidate.settings().getVisualMaterializationSettings().getDetachedVisualMaxSpawnsPerTick() - || materialized >= candidate.settings().getVisualMaterializationSettings().getDetachedVisualMaxMaterialized()) { - index++; - continue; - } - state.cachedMaterializationTargets.remove(index); - Ref proxy = spawnProxy(store, - candidate.bodyUuid(), - candidate.bodyRef(), - candidate.snapshot(), - candidate.registration(), - candidate.settings()); - if (proxy == null) { - continue; - } - resource.setGeneratedVisualProxy(candidate.bodyUuid(), - candidate.bodyRef(), - proxy); - spawned++; - materialized++; - if (collector != null) { - collector.incrementSpawned(); - } - } - return spawned; - } - - @Nullable - private static MaterializationCandidate resolveCachedMaterializationCandidate( - @Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull CachedMaterializationTarget target, - @Nonnull List interests, - long visualInterestTick, - @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, - @Nonnull GameplayAttachmentSnapshot gameplayAttachments, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - if (generatedVisualProxy(resource, target.bodyUuid(), target.bodyRef()) != null) { - return null; - } - - PhysicsBodyRegistrationView registration = bodyRegistration(resource, - target.bodyUuid(), - target.bodyRef()); - if (registration == null - || registration.kind() != PhysicsBodyKind.BODY - || !sameSpaceId(registration.spaceId(), target.spaceId()) - || gameplayAttachments.hasKnownGameplayAttachment( - hasBodyAttachments(resource, target.bodyUuid(), target.bodyRef()), - target.bodyRef(), - target.bodyUuid())) { - return null; - } - - PhysicsSpaceSettings settings = resolveSettings(resource, registration); - if (settings == null || !settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled()) { - return null; - } - PhysicsSpaceBinding space = resolveSpace(resource, registration); - if (space == null) { - return null; - } - - PhysicsBodySnapshot snapshot = bodySnapshot(resource, target.bodyUuid(), target.bodyRef()); - if (snapshot == null) { - return null; - } - if (!isBodyChunkLoaded(store, snapshot)) { - return null; - } - DetachedVisualOcclusion.Result currentPolicy = - resolveCurrentMaterializationPolicy(resource, - target.bodyUuid(), - target.bodyRef(), - space, - snapshot, - settings, - interests, - visualInterestTick, - raycastBudget, - collector); - if (!currentPolicy.shouldMaterialize()) { - return null; - } - return new MaterializationCandidate(target.bodyUuid(), - target.bodyRef(), - snapshot, - registration, - settings, - currentPolicy.priorityDistanceSquared()); - } - - @Nullable - private static Ref generatedVisualProxy( - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - return resource.getGeneratedVisualProxy(bodyUuid, bodyRef); - } - - @Nullable - private static PhysicsBodyRegistrationView bodyRegistration( - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - return bodyRef != null && bodyRef.isValid() - ? resource.getBodyRegistrationView(bodyRef) - : resource.getBodyRegistrationView(bodyUuid); - } - - private static boolean hasBodyAttachments(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - return resource.hasBodyAttachments(bodyUuid, bodyRef); - } - - @Nullable - private static PhysicsBodySnapshot bodySnapshot(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - return resource.getBodySnapshotIfRegistered(bodyUuid, bodyRef); - } - - private static int refreshCooldown(int intervalTicks) { - return Math.max(0, intervalTicks - 1); - } - - private static int resolveVisualInterestRefreshInterval( - @Nonnull PhysicsWorldRuntimeResource resource) { - return resolveMinimumDetachedVisualInterval(resource, - settings -> settings.getVisualMaterializationSettings() - .getDetachedVisualInterestRefreshIntervalTicks(), - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS); - } - - private static int resolveMaterializationCandidateRefreshInterval( - @Nonnull PhysicsWorldRuntimeResource resource) { - return resolveMinimumDetachedVisualInterval(resource, - settings -> settings.getVisualMaterializationSettings() - .getDetachedVisualCandidateRefreshIntervalTicks(), - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS); - } - - private static int resolveMaterializedVisibilityCheckInterval( - @Nonnull PhysicsWorldRuntimeResource resource) { - return resolveMinimumDetachedVisualInterval(resource, - settings -> settings.getVisualMaterializationSettings() - .getDetachedVisualVisibilityCheckIntervalTicks(), - PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS); - } - - private static int resolveMinimumDetachedVisualInterval( - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull ToIntFunction intervalGetter, - int defaultInterval) { - int interval = Integer.MAX_VALUE; - for (PhysicsSpaceBinding space : resource.iterateSpaceBindings()) { - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(space.spaceId()); - if (settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled()) { - interval = Math.min(interval, intervalGetter.applyAsInt(settings)); - } - } - return interval == Integer.MAX_VALUE ? defaultInterval : interval; - } - - private static int processMaterializedProxies(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull List interests, - @Nonnull GameplayAttachmentSnapshot gameplayAttachments, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - int count = 0; - for (GeneratedVisualProxyView generatedProxy : resource.getGeneratedVisualProxyViews()) { - if (collector != null) { - collector.incrementVisibilityChecks(); - } - UUID bodyUuid = generatedProxy.bodyUuid(); - Ref proxy = generatedProxy.proxy(); - BodyAttachmentComponent proxyAttachment = - expectedProxyAttachment(store, proxy, bodyUuid); - Ref bodyRef = validBodyRef(generatedProxy.bodyRef()); - if (bodyRef == null && proxyAttachment != null) { - bodyRef = validBodyRef(proxyAttachment.getBodyRef()); - } - PhysicsBodyRegistrationView registration = bodyRegistration(resource, bodyUuid, bodyRef); - if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { - if (registration == null && resource.isBodyCreationPending(bodyUuid)) { - count++; - continue; - } - removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); - if (collector != null) { - collector.incrementDematerialized(); - } - continue; - } - if (proxy == null || proxyAttachment == null) { - removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); - if (collector != null) { - collector.incrementDematerialized(); - } - continue; - } - if (hasGameplayAttachment(store, - resource, - bodyUuid, - bodyRef, - proxy, - gameplayAttachments)) { - removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); - if (collector != null) { - collector.incrementDematerialized(); - } - continue; - } - - PhysicsSpaceSettings settings = resolveSettings(resource, registration); - if (settings == null || !settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled()) { - removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); - if (collector != null) { - collector.incrementDematerialized(); - } - continue; - } - - PhysicsBodySnapshot snapshot = bodySnapshot(resource, bodyUuid, bodyRef); - if (snapshot == null) { - removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); - if (collector != null) { - collector.incrementDematerialized(); - } - continue; - } - if (!isBodyChunkLoaded(store, snapshot) - || shouldDematerialize(snapshot, settings, interests)) { - removeGeneratedProxy(store, resource, bodyUuid, bodyRef, proxy); - if (collector != null) { - collector.incrementDematerialized(); - } - continue; - } - count++; - } - return count; - } - - private static void collectMaterializationCandidates(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull List interests, - long visualInterestTick, - @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, - @Nonnull GameplayAttachmentSnapshot gameplayAttachments, - @Nonnull List candidates, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - if (interests.isEmpty()) { - return; - } - - Set seenBodyUuids = new ObjectOpenHashSet<>(); - for (PhysicsSpaceBinding space : resource.iterateSpaceBindings()) { - PhysicsSpaceSettings settings = resource.getLiveSpaceSettings(space.spaceId()); - if (!settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled()) { - continue; - } - - for (int interestIndex = 0; interestIndex < interests.size(); interestIndex++) { - VisualInterest interest = interests.get(interestIndex); - if (collector != null) { - collector.incrementNearQueries(); - } - int nearCandidates = resource.forEachIndexedBodySnapshotNearWithRefs(space.spaceId(), - interest.position(), - settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), - (bodyKey, bodyRef, snapshot, bodySpaceId, kind, persistenceMode) -> { - UUID bodyUuid = bodyKey.value(); - if (!seenBodyUuids.add(bodyUuid) - || generatedVisualProxy(resource, bodyUuid, bodyRef) != null) { - return; - } - if (kind != PhysicsBodyKind.BODY - || !bodySpaceId.equals(space.spaceId()) - || gameplayAttachments.hasKnownGameplayAttachment( - hasBodyAttachments(resource, bodyUuid, bodyRef), - bodyRef, - bodyUuid)) { - return; - } - if (!isBodyChunkLoaded(store, snapshot)) { - return; - } - DetachedVisualOcclusion.Result materializeInterest = - DetachedVisualOcclusion.resolve(resource, - bodyUuid, - bodyRef, - space, - snapshot, - settings, - interests, - settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), - visualInterestTick, - raycastBudget, - collector); - if (materializeInterest.shouldMaterialize()) { - candidates.add(new CachedMaterializationTarget(bodyUuid, - bodyRef, - bodySpaceId, - materializeInterest.priorityDistanceSquared())); - } - }); - if (collector != null) { - collector.addNearQueryCandidates(nearCandidates); - } - } - } - } - - private static void removeOrphanVisualFollowers(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource) { - Queue orphanProxies = new ConcurrentLinkedQueue<>(); - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, commandBuffer) -> { - BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, - attachmentType); - if (attachment == null - || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { - return; - } - - var ref = archetypeChunk.getReferenceTo(index); - orphanProxies.add(new OrphanVisualProxy(attachment.getBodyUuid(), - attachment.getBodyRef(), - ref)); - }); - - for (OrphanVisualProxy proxy : orphanProxies) { - if (!hasLiveVisualTarget(resource, - proxy.bodyUuid(), - proxy.bodyRef(), - proxy.ref())) { - GeneratedProxyLifecycle.removeProxy(store, - resource, - proxy.bodyUuid(), - proxy.bodyRef(), - proxy.ref()); - } - } - } - - private static boolean hasLiveVisualTarget(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref proxyRef) { - PhysicsBodyRegistrationView registration = bodyRef != null - ? resource.getBodyRegistrationView(bodyRef) - : resource.getBodyRegistrationView(bodyUuid); - if (registration == null) { - return resource.isBodyCreationPending(bodyUuid) - && isGeneratedVisualProxy(resource, bodyUuid, bodyRef, proxyRef); - } - return resource.getSpaceBinding(registration.spaceId()) != null - && isGeneratedVisualProxy(resource, bodyUuid, bodyRef, proxyRef); - } - - private static boolean isGeneratedVisualProxy(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref proxyRef) { - return resource.isGeneratedVisualProxy(bodyUuid, bodyRef, proxyRef); - } - - private record OrphanVisualProxy( - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref ref - ) { - } - - private static boolean hasGameplayAttachment(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref proxy, - @Nonnull GameplayAttachmentSnapshot gameplayAttachments) { - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - Collection> attachments = resource.getBodyAttachments(bodyUuid, bodyRef); - for (Ref attachmentRef : attachments) { - if (sameRef(attachmentRef, proxy)) { - continue; - } - BodyAttachmentComponent attachment = store.getComponent(attachmentRef, - attachmentType); - if (attachment != null && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { - return true; - } - } - return gameplayAttachments.hasGameplayAttachment(bodyRef, bodyUuid); - } - - @Nullable - private static PhysicsSpaceSettings resolveSettings(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsBodyRegistrationView registration) { - if (resource.getSpaceBinding(registration.spaceId()) != null) { - return resource.getLiveSpaceSettings(registration.spaceId()); - } - return null; - } - - @Nullable - private static PhysicsSpaceBinding resolveSpace(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsBodyRegistrationView registration) { - return resource.getSpaceBinding(registration.spaceId()); - } - - private static boolean shouldMaterialize(@Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests) { - if (interests.isEmpty()) { - return false; - } - return visibleDistanceSquared(snapshot, - settings, - interests, - settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius()) != Float.POSITIVE_INFINITY; - } - - @Nonnull - static DetachedVisualOcclusion.Result resolveCurrentMaterializationPolicy( - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull PhysicsSpaceBinding space, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - long visualInterestTick, - @Nonnull DetachedVisualOcclusion.RaycastBudget raycastBudget, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - return DetachedVisualOcclusion.resolve(resource, - bodyUuid, - bodyRef, - space, - snapshot, - settings, - interests, - settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(), - visualInterestTick, - raycastBudget, - collector); - } - - private static boolean shouldDematerialize(@Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests) { - if (interests.isEmpty()) { - return true; - } - return visibleDistanceSquared(snapshot, - settings, - interests, - settings.getVisualMaterializationSettings().getDetachedVisualDematerializationRadius()) == Float.POSITIVE_INFINITY; - } - - private static float visibleDistanceSquared(@Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - float radius) { - return DetachedVisualGeometry.visibleDistanceSquared(snapshot.positionX(), - snapshot.positionY(), - snapshot.positionZ(), - settings, - interests, - radius); - } - - private static boolean isBodyChunkLoaded(@Nonnull Store store, - @Nonnull PhysicsBodySnapshot snapshot) { - ChunkStore chunkStore = store.getExternalData().getWorld().getChunkStore(); - Store chunkComponentStore = chunkStore.getStore(); - Ref chunkRef = chunkStore.getChunkReference(ChunkUtil.indexChunk( - ChunkUtil.chunkCoordinate(snapshot.positionX()), - ChunkUtil.chunkCoordinate(snapshot.positionZ()))); - if (chunkRef == null || !chunkRef.isValid()) { - return false; - } - - WorldChunk worldChunk = chunkComponentStore.getComponentConcurrent(chunkRef, - WorldChunk.getComponentType()); - return worldChunk != null; - } - - @Nullable - private static BodyAttachmentComponent expectedProxyAttachment( - @Nonnull Store store, - @Nullable Ref proxy, - @Nonnull UUID bodyUuid) { - if (proxy == null || !proxy.isValid()) { - return null; - } - BodyAttachmentComponent attachment = - store.getComponent(proxy, BodyAttachmentComponent.getComponentType()); - if (attachment == null - || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY - || !attachment.getBodyUuid().equals(bodyUuid)) { - return null; - } - return attachment; - } - - @Nullable - private static Ref validBodyRef(@Nullable Ref bodyRef) { - return bodyRef != null && bodyRef.isValid() ? bodyRef : null; - } - - private static void removeGeneratedProxy(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nullable Ref proxy) { - GeneratedProxyLifecycle.removeProxy(store, resource, bodyUuid, bodyRef, proxy); - } - - private static boolean sameSpaceId(@Nullable SpaceId first, @Nullable SpaceId second) { - return Objects.equals(first, second); - } - - private static boolean sameRef(@Nullable Ref first, - @Nullable Ref second) { - return first == second - || (first != null - && second != null - && first.getStore() != null - && first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); - } - - @Nullable - private static Ref spawnProxy(@Nonnull Store store, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsBodyRegistrationView registration, - @Nonnull PhysicsSpaceSettings settings) { - TimeResource time = store.getResource(TimeResource.getResourceType()); - Quaternionf rotation = new Quaternionf(snapshot.rotationX(), - snapshot.rotationY(), - snapshot.rotationZ(), - snapshot.rotationW()); - Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(snapshot.positionX(), - snapshot.positionY(), - snapshot.positionZ()), - rotation, - snapshot.centerOfMassOffsetY(), - new Vector3f(), - new Vector3f()); - Holder holder = BlockEntity.assembleDefaultBlockEntity( - time, - settings.getVisualMaterializationSettings().getDetachedVisualBlockType(), - new Vector3d(visualPosition.x, visualPosition.y, visualPosition.z)); - TransformComponent transform = holder.getComponent(TransformComponent.getComponentType()); - if (transform != null) { - Vector3f euler = rotation.getEulerAnglesYXZ(new Vector3f()); - transform.getRotation().set(euler.x, euler.y, euler.z); - } - holder.removeComponent(DespawnComponent.getComponentType()); - holder.removeComponent(Velocity.getComponentType()); - holder.addComponent(store.getRegistry().getNonSerializedComponentType(), NonSerialized.get()); - holder.addComponent(GeneratedVisualProxyComponent.getComponentType(), new GeneratedVisualProxyComponent()); - BodyAttachmentComponent attachment = BodyAttachmentComponent.generatedProxy(bodyUuid, - new Vector3f(), - new Quaternionf(), - Float.NaN); - attachment.setBodyRef(bodyRef); - holder.addComponent(BodyAttachmentComponent.getComponentType(), attachment); - return store.addEntity(holder, AddReason.SPAWN); - } - - private record MaterializationCandidate(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsBodyRegistrationView registration, - @Nonnull PhysicsSpaceSettings settings, - float distanceSquared) { - } - - private record CachedMaterializationTarget(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull SpaceId spaceId, - float distanceSquared) { - } - - private static final class MaterializationState { - - private int orphanVisualCleanupCooldown; - private int visualInterestRefreshCooldown; - private int materializationCandidateRefreshCooldown; - private int materializedVisibilityCheckCooldown; - private long observedRestoreGeneration; - @Nonnull - private List cachedInterests = List.of(); - @Nonnull - private final List cachedMaterializationTargets = - new ArrayList<>(); - } - @Nonnull @Override public Set> getDependencies() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 70426d69..5a45d70f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -65,14 +64,14 @@ public static CompletionStage requestRuntimeRestoreAsync( @Nonnull public static Status status(@Nonnull Store store) { Store physicsStore = physicsStore(store); - return copiedStatus(physicsStore, legacyStatus(store)); + return copiedStatus(physicsStore); } @Nonnull public static CompletionStage statusAsync(@Nonnull Store store) { return PhysicsStoreThreading.enqueueReadOnWorldThread(store.getExternalData().getWorld(), "queue PhysicsStore persistence status read", - physics -> liveStatus(physics, legacyStatus(store))); + PhysicsPersistence::liveStatus); } @Nonnull @@ -82,8 +81,7 @@ private static Store physicsStore(@Nonnull Store stor } @Nonnull - private static Status liveStatus(@Nonnull Store physicsStore, - @Nonnull LegacyStatus legacy) { + private static Status liveStatus(@Nonnull Store physicsStore) { PersistentPhysicsStoreResource persistent = physicsStore.getResource( PersistentPhysicsStoreResource.getResourceType()); PhysicsRestoreStatusResource restore = physicsStore.getResource( @@ -103,12 +101,11 @@ private static Status liveStatus(@Nonnull Store physicsStore, persistent.getBodies().length, persistent.getJoints().length, restoreState(restore), - restoreMessage(restore, persistent, legacy)); + restoreMessage(restore)); } @Nonnull - private static Status copiedStatus(@Nonnull Store physicsStore, - @Nonnull LegacyStatus legacy) { + private static Status copiedStatus(@Nonnull Store physicsStore) { PhysicsStoreThreading.requireWorldThread(physicsStore, "read copied PhysicsStore persistence status"); PersistentPhysicsStoreResource persistent = physicsStore.getResource( @@ -130,7 +127,7 @@ private static Status copiedStatus(@Nonnull Store physicsStore, persistent.getBodies().length, persistent.getJoints().length, restoreState(restore), - restoreMessage(restore, persistent, legacy)); + restoreMessage(restore)); } @Nonnull @@ -145,48 +142,16 @@ private static RestoreState restoreState(@Nonnull PhysicsRestoreStatusResource r } @Nonnull - private static String restoreMessage(@Nonnull PhysicsRestoreStatusResource restore, - @Nonnull PersistentPhysicsStoreResource persistent, - @Nonnull LegacyStatus legacy) { + private static String restoreMessage(@Nonnull PhysicsRestoreStatusResource restore) { if (restore.isFailed()) { return restore.getFailureMessage(); } if (!restore.getSoftSkipsByReason().isEmpty()) { return "PhysicsStore restore soft skips: " + restore.getSoftSkipsByReason(); } - if (hasLegacyData(legacy)) { - String legacyCounts = "legacy PersistentPhysicsWorld spaces=" + legacy.spaceCount() - + ", bodies=" + legacy.bodyCount() - + ", joints=" + legacy.jointCount(); - if (hasAuthoritativeData(persistent)) { - return legacyCounts - + " ignored because PersistentPhysicsStore contains authoritative state."; - } - return legacyCounts - + " present, but legacy import into PersistentPhysicsStore is deferred."; - } return ""; } - private static boolean hasAuthoritativeData(@Nonnull PersistentPhysicsStoreResource persistent) { - return persistent.getSpaces().length > 0 - || persistent.getBodies().length > 0 - || persistent.getJoints().length > 0; - } - - @Nonnull - private static LegacyStatus legacyStatus(@Nonnull Store store) { - PersistentPhysicsWorldResource legacy = store.getResource( - PersistentPhysicsWorldResource.getResourceType()); - return new LegacyStatus(legacy.getSpaceCount(), - legacy.getBodyCount(), - legacy.getJointCount()); - } - - private static boolean hasLegacyData(@Nonnull LegacyStatus legacy) { - return legacy.hasData(); - } - public enum RestoreState { IDLE("idle"), PENDING_SPACES("pending-spaces"), @@ -235,10 +200,4 @@ public boolean hasRestoreMessage() { } } - private record LegacyStatus(int spaceCount, int bodyCount, int jointCount) { - - private boolean hasData() { - return spaceCount > 0 || bodyCount > 0 || jointCount > 0; - } - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceResource.java deleted file mode 100644 index 87b307c6..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceResource.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.persistence; - -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import javax.annotation.Nonnull; - -/** - * Plugin-facing contract for Impulse's world-level persistence resource. - */ -public abstract class PhysicsPersistenceResource implements Resource { - - public static final int CURRENT_SCHEMA_VERSION = 6; - - @Nonnull - public static ResourceType getResourceType() { - return ImpulsePlugin.get().getPersistentPhysicsWorldResourceType(); - } - - public abstract int getSchemaVersion(); - - public abstract int getSpaceCount(); - - public abstract int getBodyCount(); - - public abstract int getJointCount(); - - public abstract boolean isRuntimeRestorePending(); - - public abstract void markRuntimeRestorePending(); - - public abstract boolean isRuntimeSpaceBootstrapComplete(); - - public abstract boolean hasRuntimeRestoreFailed(); - - public abstract boolean hasRuntimeRestoreSkips(); - - @Nonnull - public abstract String runtimeRestoreFailureSummary(); - - @Nonnull - public abstract String runtimeRestoreSummary(); - - @Nonnull - public abstract PhysicsPersistenceSyncResult saveRuntimeSnapshot( - @Nonnull Store store, - @Nonnull PhysicsWorldResource runtime); - - @Nonnull - @Override - public abstract PhysicsPersistenceResource clone(); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceSyncResult.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceSyncResult.java deleted file mode 100644 index 92f598a8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistenceSyncResult.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.persistence; - -import javax.annotation.Nonnull; - -public record PhysicsPersistenceSyncResult(boolean synced, - int spaces, - int bodies, - int joints, - @Nonnull String skippedReason) { -} From ebe4c4627811317986b5456c1fd3792859da64ef Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:12:57 +0200 Subject: [PATCH 202/534] refactor(core): inline legacy runtime mutations Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 131 +++++++++++------- .../resources/owner/PhysicsOwnerCallable.java | 12 -- .../resources/owner/PhysicsOwnerGateway.java | 111 --------------- .../resources/owner/PhysicsOwnerMutation.java | 10 -- 4 files changed, 82 insertions(+), 182 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCallable.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutation.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 4920df17..c8d22914 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -43,9 +43,6 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerCallable; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerGateway; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerMutation; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; @@ -139,7 +136,6 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { this::markWorldChanged); private final AtomicLong visualInterestTick = new AtomicLong(); - private final PhysicsOwnerGateway ownerGateway = new PhysicsOwnerGateway(); @Nullable private Store owningStore; @@ -178,11 +174,11 @@ public World requireAuthoritativeWorldForPhysicsStore(@Nonnull String operation) } public boolean canAccessLiveBackendDirectly() { - return ownerGateway.canAccessLiveBackendDirectly(); + return true; } public void rejectSynchronousCompletionCallbackWait(@Nonnull String operation) { - ownerGateway.rejectSynchronousCompletionCallbackWait(operation); + Objects.requireNonNull(operation, "operation"); } public long worldEpoch() { @@ -201,7 +197,7 @@ public PhysicsEventFrame getLatestEventFrame() { } public void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { - ownerGateway.assertCanAccessLiveBackendDirectly(operation); + Objects.requireNonNull(operation, "operation"); } private void requireLegacyMutationAllowed(@Nonnull String operation) { @@ -357,30 +353,55 @@ private PhysicsMutationHandle enqueueAuthoritativePhysicsStoreMutation( PhysicsStoreThreading.executeOnWorldThread(world, operation, mutation)); } - public void runOwnerMutation(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - ownerGateway.run(operation, mutation); + private void runDirectRuntimeMutation(@Nonnull String operation, + @Nonnull DirectRuntimeMutation mutation) { + Objects.requireNonNull(operation, "operation"); + Objects.requireNonNull(mutation, "mutation"); + try { + mutation.run(); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IllegalStateException("Physics operation " + operation + " failed", + exception); + } } @Nonnull - public PhysicsMutationHandle enqueueOwnerMutation(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - return enqueueOwnerMutation(operation, null, mutation); + private PhysicsMutationHandle enqueueDirectRuntimeMutation(@Nonnull String operation, + @Nonnull DirectRuntimeMutation mutation) { + return enqueueDirectRuntimeMutation(operation, null, mutation); } @Nonnull - public PhysicsMutationHandle enqueueOwnerMutation(@Nonnull String operation, + private PhysicsMutationHandle enqueueDirectRuntimeMutation(@Nonnull String operation, @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - return ownerGateway.enqueue(operation, value, mutation); + @Nonnull DirectRuntimeMutation mutation) { + Objects.requireNonNull(operation, "operation"); + Objects.requireNonNull(mutation, "mutation"); + try { + mutation.run(); + return PhysicsMutationHandle.completed(operation, value); + } catch (Throwable throwable) { + return PhysicsMutationHandle.failed(operation, value, throwable); + } } @Nonnull - public T callOwner(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - return ownerGateway.call(operation, callable); + private T callDirectRuntime(@Nonnull String operation, + @Nonnull DirectRuntimeCallable callable) { + Objects.requireNonNull(operation, "operation"); + Objects.requireNonNull(callable, "callable"); + try { + return callable.call(); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IllegalStateException("Physics operation " + operation + " failed", + exception); + } } @@ -409,7 +430,7 @@ public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { return; } requireLegacyMutationAllowed("set physics world settings"); - runOwnerMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); + runDirectRuntimeMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); } @Nonnull @@ -427,7 +448,7 @@ public PhysicsMutationHandle setWorldSettingsAsync( return PhysicsMutationHandle.completed("set physics world settings", null); } requireLegacyMutationAllowed("set physics world settings"); - return enqueueOwnerMutation("set physics world settings", + return enqueueDirectRuntimeMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); } @@ -479,7 +500,7 @@ public SpaceId createSpace(@Nonnull BackendId backendId, return spaceId; } requireLegacyMutationAllowed("create physics space"); - callOwner("create physics space", + callDirectRuntime("create physics space", () -> createSpaceDirect(backendId, spaceId, worldName, settings)); return spaceId; } @@ -511,7 +532,7 @@ public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backen requested)); } requireLegacyMutationAllowed("create physics space"); - return enqueueOwnerMutation("create physics space", + return enqueueDirectRuntimeMutation("create physics space", spaceId, () -> createSpaceDirect(backendId, spaceId, worldName, settings)); } @@ -595,7 +616,7 @@ public int refreshBodySnapshots() { .bodies() .size(); } - return callOwner("refresh physics body snapshots", () -> { + return callDirectRuntime("refresh physics body snapshots", () -> { PublishedPhysicsSnapshotFrame frame = capturePublishedSnapshotFrameDirect(0L, 0L, PublishedPhysicsSnapshotFrame.Status.COMPLETE, @@ -622,7 +643,7 @@ public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { if (snapshot != null) { return snapshot; } - return callOwner("refresh missing physics body snapshot", + return callDirectRuntime("refresh missing physics body snapshot", () -> getBodySnapshotDirect(bodyKey)); } @@ -637,7 +658,7 @@ public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull RigidBodyKey bod if (snapshot != null) { return snapshot; } - return callOwner("refresh optional physics body snapshot", + return callDirectRuntime("refresh optional physics body snapshot", () -> getBodySnapshotIfRegisteredDirect(bodyKey)); } @@ -1025,7 +1046,7 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame(long stepSequ boolean profilingEnabled, @Nonnull List physicsEvents, int droppedBackendEventCount) { - return callOwner("capture published physics snapshot frame", + return callDirectRuntime("capture published physics snapshot frame", () -> capturePublishedSnapshotFrameDirect(stepSequence, serverTick, status, @@ -1151,7 +1172,7 @@ public WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world } requireLegacyMutationAllowed("rebuild world collision"); requireWorldCollisionLifecycleEnabled(); - return callOwner("rebuild world collision", () -> { + return callDirectRuntime("rebuild world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); requireWorldCollisionSpaceEnabled(spaceId); WorldCollisionBuildOptions buildOptions = @@ -1187,7 +1208,7 @@ public WorldCollisionBuildStats refreshWorldCollisionAround(@Nonnull World world } requireLegacyMutationAllowed("refresh world collision"); requireWorldCollisionLifecycleEnabled(); - return callOwner("refresh world collision", () -> { + return callDirectRuntime("refresh world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); requireWorldCollisionSpaceEnabled(spaceId); WorldCollisionBuildOptions buildOptions = @@ -1226,7 +1247,7 @@ public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World worl requireLegacyMutationAllowed("ensure world collision"); Objects.requireNonNull(centers, "centers"); requireWorldCollisionLifecycleEnabled(); - return callOwner("ensure world collision", () -> { + return callDirectRuntime("ensure world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); requireWorldCollisionSpaceEnabled(spaceId); WorldCollisionBuildOptions buildOptions = @@ -1249,7 +1270,7 @@ public int clearWorldCollision(@Nonnull SpaceId spaceId) { return clearAuthoritativeWorldCollisionSpace(store, spaceUuid); } requireLegacyMutationAllowed("clear world collision"); - return callOwner("clear world collision", () -> { + return callDirectRuntime("clear world collision", () -> { PhysicsSpaceBinding space = requireSpaceBinding(spaceId); return collisionRuntime.clear(space); }); @@ -1267,7 +1288,7 @@ public WorldCollisionStats getWorldCollisionStats() { ? authoritativeWorldCollisionStreaming().stats() : new WorldCollisionStats(0, 0, 0, 0); } - return callOwner("read world collision stats", collisionRuntime::getStats); + return callDirectRuntime("read world collision stats", collisionRuntime::getStats); } @Nonnull @@ -1352,7 +1373,7 @@ public void disableWorldCollisionLifecycle() { return; } try { - runOwnerMutation("disable world collision lifecycle", this::disableWorldCollisionLifecycleDirect); + runDirectRuntimeMutation("disable world collision lifecycle", this::disableWorldCollisionLifecycleDirect); } catch (RejectedExecutionException ignored) { // The server can unload the subplugin after a world owner lane has already closed. } catch (RuntimeException exception) { @@ -1518,7 +1539,7 @@ public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { return; } requireLegacyMutationAllowed("remove physics space"); - runOwnerMutation("remove physics space", () -> removeSpaceDirect(spaceId, worldName)); + runDirectRuntimeMutation("remove physics space", () -> removeSpaceDirect(spaceId, worldName)); } @Nonnull @@ -1535,7 +1556,7 @@ public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, }); } requireLegacyMutationAllowed("remove physics space"); - return enqueueOwnerMutation("remove physics space", + return enqueueDirectRuntimeMutation("remove physics space", spaceId, () -> removeSpaceDirect(spaceId, worldName)); } @@ -1575,7 +1596,7 @@ public void clearAllSpaces(@Nonnull String worldName) { return; } requireLegacyMutationAllowed("clear physics spaces"); - runOwnerMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); + runDirectRuntimeMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); } @Nonnull @@ -1590,7 +1611,7 @@ public PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName }); } requireLegacyMutationAllowed("clear physics spaces"); - return enqueueOwnerMutation("clear physics spaces", + return enqueueDirectRuntimeMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); } @@ -1630,7 +1651,7 @@ public PhysicsRuntimeResetResult resetRuntimeStateKeepingSpaces(@Nonnull String return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); } requireLegacyMutationAllowed("reset physics runtime state"); - return callOwner("reset physics runtime state", + return callDirectRuntime("reset physics runtime state", () -> resetRuntimeStateKeepingSpacesDirect(worldName)); } @@ -1676,7 +1697,7 @@ public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSett } requireLegacyMutationAllowed("set physics space settings"); PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); - runOwnerMutation("set physics space settings", () -> setSpaceSettingsDirect(spaceId, requested)); + runDirectRuntimeMutation("set physics space settings", () -> setSpaceSettingsDirect(spaceId, requested)); } @Nonnull @@ -1691,7 +1712,7 @@ public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spa } requireLegacyMutationAllowed("set physics space settings"); PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); - return enqueueOwnerMutation("set physics space settings", + return enqueueDirectRuntimeMutation("set physics space settings", spaceId, () -> setSpaceSettingsDirect(spaceId, requested)); } @@ -1787,7 +1808,7 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) return; } requireLegacyMutationAllowed("destroy physics body"); - runOwnerMutation("destroy physics body", () -> destroyBodyDirect(bodyKey, removeFromSpace)); + runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyKey, removeFromSpace)); } @Nonnull @@ -1799,7 +1820,7 @@ public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKe store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyKey)); } requireLegacyMutationAllowed("destroy physics body"); - return enqueueOwnerMutation("destroy physics body", + return enqueueDirectRuntimeMutation("destroy physics body", bodyKey, () -> destroyBodyDirect(bodyKey, removeFromSpace)); } @@ -1963,7 +1984,7 @@ private JointKey addJointDirect(@Nonnull JointKey jointKey, public boolean removeJoint(@Nonnull JointKey jointKey) { requireLegacyMutationAllowed("remove physics joint"); - return callOwner("remove physics joint", () -> removeJointDirect(jointKey)); + return callDirectRuntime("remove physics joint", () -> removeJointDirect(jointKey)); } private boolean removeJointDirect(@Nonnull JointKey jointKey) { @@ -2395,7 +2416,7 @@ public void clearBodies() { return; } requireLegacyMutationAllowed("clear physics bodies"); - runOwnerMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); + runDirectRuntimeMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); } @Nonnull @@ -2410,7 +2431,7 @@ public PhysicsMutationHandle clearBodiesAsync() { }); } requireLegacyMutationAllowed("clear physics bodies"); - return enqueueOwnerMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); + return enqueueDirectRuntimeMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); } private void destroyRegisteredBodiesDirect() { @@ -2655,14 +2676,14 @@ public void clearChunkBoundaryPauseState(@Nonnull Ref bodyRef) { public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { requireLegacyMutationAllowed("clear physics body runtime state"); - runOwnerMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyKey)); + runDirectRuntimeMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyKey)); } @Nonnull public PhysicsMutationHandle clearBodyRuntimeStateAsync( @Nonnull RigidBodyKey bodyKey) { requireLegacyMutationAllowed("clear physics body runtime state"); - return enqueueOwnerMutation("clear physics body runtime state", + return enqueueDirectRuntimeMutation("clear physics body runtime state", bodyKey, () -> clearBodyRuntimeStateDirect(bodyKey)); } @@ -2704,12 +2725,12 @@ public void clearForcedContinuousCollisionBodies() { } public void copyFrom(@Nonnull PhysicsWorldResource other) { - runOwnerMutation("copy physics world resource", () -> copyFromDirect(other)); + runDirectRuntimeMutation("copy physics world resource", () -> copyFromDirect(other)); } @Nonnull public PhysicsMutationHandle copyFromAsync(@Nonnull PhysicsWorldResource other) { - return enqueueOwnerMutation("copy physics world resource", () -> copyFromDirect(other)); + return enqueueDirectRuntimeMutation("copy physics world resource", () -> copyFromDirect(other)); } private void copyFromDirect(@Nonnull PhysicsWorldResource other) { @@ -2734,6 +2755,18 @@ private void markWorldChanged() { lifecycleState.markWorldChanged(bodyRegistry, false); } + @FunctionalInterface + private interface DirectRuntimeMutation { + + void run() throws Exception; + } + + @FunctionalInterface + private interface DirectRuntimeCallable { + + T call() throws Exception; + } + @Nonnull @Override public PhysicsWorldResource clone() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCallable.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCallable.java deleted file mode 100644 index 8ae5129c..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerCallable.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -/** - * Internal operation that runs in the current live-backend owner context and returns a value. - * - * @param value returned to the caller - */ -@FunctionalInterface -public interface PhysicsOwnerCallable { - - T call() throws Exception; -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java deleted file mode 100644 index 42408d05..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGateway.java +++ /dev/null @@ -1,111 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Direct compatibility gateway for legacy world-resource operations. - * - *

Authoritative PhysicsStore paths do not use this gateway. Remaining legacy - * {@code PhysicsWorldResource} methods run directly when the early PhysicsStore is not active.

- */ -public final class PhysicsOwnerGateway { - - /** - * Returns whether the current thread may touch live backend objects without routing. - */ - public boolean canAccessLiveBackendDirectly() { - return true; - } - - public void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { - Objects.requireNonNull(operation, "operation"); - } - - public void rejectSynchronousCompletionCallbackWait(@Nonnull String operation) { - Objects.requireNonNull(operation, "operation"); - } - - public void run(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(mutation, "mutation"); - runDirect(operation, mutation); - } - - @Nonnull - public PhysicsMutationHandle enqueue(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - return enqueue(operation, null, mutation); - } - - @Nonnull - public PhysicsMutationHandle enqueue(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(mutation, "mutation"); - return runDirectAsync(operation, value, mutation); - } - - @Nonnull - public CompletableFuture enqueueCall(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(callable, "callable"); - return callDirectAsync(callable); - } - - @Nonnull - public T call(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(callable, "callable"); - try { - return callable.call(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("Physics operation " + operation + " failed", - exception); - } - } - - private static void runDirect(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - try { - mutation.run(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("Physics operation " + operation + " failed", - exception); - } - } - - @Nonnull - private static PhysicsMutationHandle runDirectAsync(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - try { - mutation.run(); - return PhysicsMutationHandle.completed(operation, value); - } catch (Throwable throwable) { - return PhysicsMutationHandle.failed(operation, value, throwable); - } - } - - @Nonnull - private static CompletableFuture callDirectAsync(@Nonnull PhysicsOwnerCallable callable) { - try { - return CompletableFuture.completedFuture(callable.call()); - } catch (Throwable throwable) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(throwable); - return completion; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutation.java deleted file mode 100644 index b4653a44..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerMutation.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -/** - * Internal mutation that runs in the current live-backend owner context. - */ -@FunctionalInterface -public interface PhysicsOwnerMutation { - - void run() throws Exception; -} From bc897d817ffb941afe29130721d10d94a2861666 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:16:46 +0200 Subject: [PATCH 203/534] refactor(visual): remove detached materialization leftovers Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 4 +- .../PhysicsStoreEventPublicationSystem.java | 4 +- .../systems/sync/PhysicsSyncSystem.java | 4 +- .../visual/DetachedVisualGeometry.java | 62 ----- .../visual/DetachedVisualOcclusion.java | 235 ------------------ .../visual/GameplayAttachmentSnapshot.java | 126 ---------- ...> PhysicsGeneratedProxyCleanupSystem.java} | 2 +- 7 files changed, 7 insertions(+), 430 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualGeometry.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/{PhysicsDetachedVisualMaterializationSystem.java => PhysicsGeneratedProxyCleanupSystem.java} (97%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index a9ec419c..9912f406 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -32,7 +32,7 @@ import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -230,7 +230,7 @@ private void registerSystems() { persistenceRestoreGroup = entityRegistry.registerSystemGroup(); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); - entityRegistry.registerSystem(new PhysicsDetachedVisualMaterializationSystem()); + entityRegistry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); entityRegistry.registerSystem(new PhysicsSyncSystem()); entityRegistry.registerSystem(new PhysicsDebugSystem()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index 69048912..12943f5f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource.StepSample; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; @@ -30,7 +30,7 @@ public final class PhysicsStoreEventPublicationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.BEFORE, PhysicsDetachedVisualMaterializationSystem.class), + new SystemDependency<>(Order.BEFORE, PhysicsGeneratedProxyCleanupSystem.class), new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index cd075f08..6b2283fb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -27,7 +27,7 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.visual.GeneratedProxyLifecycle; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; @@ -63,7 +63,7 @@ public class PhysicsSyncSystem extends EntityTickingSystem { private static final Query QUERY = Query.and(ATTACHMENT_TYPE, TRANSFORM_TYPE); private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), - new SystemDependency<>(Order.AFTER, PhysicsDetachedVisualMaterializationSystem.class), + new SystemDependency<>(Order.AFTER, PhysicsGeneratedProxyCleanupSystem.class), new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class), new SystemDependency<>(Order.BEFORE, UpdateLocationSystems.TickingSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualGeometry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualGeometry.java deleted file mode 100644 index 2b03171e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualGeometry.java +++ /dev/null @@ -1,62 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; - -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.util.List; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -final class DetachedVisualGeometry { - - /* - * Approximate visibility policy for optional visual culling. Close bodies - * bypass the cone so players do not lose nearby proxies while turning. - */ - private static final float VIEW_CONE_DOT = 0.35f; - private static final float VIEW_CONE_NEAR_RADIUS_SQUARED = 8.0f * 8.0f; - - private DetachedVisualGeometry() { - } - - static float visibleDistanceSquared(float positionX, - float positionY, - float positionZ, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - float radius) { - float radiusSquared = radius * radius; - float nearestDistanceSquared = Float.POSITIVE_INFINITY; - for (VisualInterest interest : interests) { - float dx = positionX - interest.position().x; - float dy = positionY - interest.position().y; - float dz = positionZ - interest.position().z; - float distanceSquared = dx * dx + dy * dy + dz * dz; - if (distanceSquared <= radiusSquared - && isInsideViewCone(settings, interest, dx, dy, dz, distanceSquared)) { - nearestDistanceSquared = Math.min(nearestDistanceSquared, distanceSquared); - } - } - return nearestDistanceSquared; - } - - static boolean isInsideViewCone(@Nonnull PhysicsSpaceSettings settings, - @Nonnull VisualInterest interest, - float dx, - float dy, - float dz, - float distanceSquared) { - if (!settings.getVisualSyncSettings().isVisualVisibilityCullingEnabled() - || interest.direction() == null - || distanceSquared <= VIEW_CONE_NEAR_RADIUS_SQUARED) { - return true; - } - - float length = (float) Math.sqrt(distanceSquared); - if (length <= 0.0f) { - return true; - } - Vector3f direction = interest.direction(); - float dot = (dx * direction.x + dy * direction.y + dz * direction.z) / length; - return dot >= VIEW_CONE_DOT; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java deleted file mode 100644 index 228c9b37..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/DetachedVisualOcclusion.java +++ /dev/null @@ -1,235 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -final class DetachedVisualOcclusion { - - private static final ThreadLocal RAYCAST_TARGET = - ThreadLocal.withInitial(Vector3f::new); - - private DetachedVisualOcclusion() { - } - - @Nonnull - static Result resolve(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nullable PhysicsSpaceBinding space, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - float radius, - long visualInterestTick, - @Nonnull RaycastBudget raycastBudget, - @Nullable PhysicsRuntimeProfilingResource.VisualCollector collector) { - InterestProbe probe = probeNearestLikelyInterest(snapshot, settings, interests, radius); - PhysicsVisualRuntime.BodyVisualInterestState state = - resource.getOrCreateBodyVisualInterestState(bodyUuid, bodyRef); - if (!probe.inRange()) { - state.clearPendingRaycast(); - state.recordInterest(Float.POSITIVE_INFINITY, false, false, false, visualInterestTick); - return Result.notVisible(); - } - - VisualOcclusionMode occlusionMode = settings.getVisualSyncSettings().getVisualOcclusionMode(); - if (occlusionMode == VisualOcclusionMode.OFF || space == null) { - state.clearPendingRaycast(); - state.recordInterest(probe.distanceSquared(), true, true, false, visualInterestTick); - return Result.visible(probe.distanceSquared(), probe.distanceSquared()); - } - - boolean raycastFresh = state.hasFreshRaycast(settings.getVisualSyncSettings() - .getVisualOcclusionCacheTicks(), - visualInterestTick); - boolean raycastDecisionKnown = raycastFresh; - boolean raycastVisible = raycastFresh && state.isRaycastVisible(); - boolean raycastEvaluated = false; - if (raycastFresh && collector != null) { - collector.incrementRaycastCacheHits(); - } - - if (state.hasCompletedRaycast()) { - Optional completedRaycast = state.pollCompletedRaycast(); - raycastVisible = completedRaycast - .map(view -> raycastHitMatchesBody(bodyUuid, bodyRef, view)) - .orElse(false); - raycastDecisionKnown = true; - raycastEvaluated = true; - } else if (!raycastFresh) { - if (state.hasRaycastResult()) { - raycastVisible = state.isRaycastVisible(); - raycastDecisionKnown = true; - } - if (!state.hasPendingRaycast() && raycastBudget.tryUse(settings)) { - submitRaycast(resource, space, state, probe, snapshot); - if (collector != null) { - collector.incrementRaycasts(); - } - } - } - - state.recordInterest(probe.distanceSquared(), - true, - raycastVisible, - raycastEvaluated, - visualInterestTick); - if (occlusionMode == VisualOcclusionMode.CULL && raycastDecisionKnown && !raycastVisible) { - return Result.notVisible(); - } - - /* - * PRIORITY only biases spawn order: visible bodies move forward in the - * queue and occluded bodies move back. CULL above is the mode that skips - * occluded candidates entirely. - */ - float priorityDistanceSquared = probe.distanceSquared(); - if (occlusionMode == VisualOcclusionMode.PRIORITY && raycastDecisionKnown) { - priorityDistanceSquared = raycastVisible - ? probe.distanceSquared() * 0.25f - : probe.distanceSquared() + radius * radius; - } - return Result.visible(probe.distanceSquared(), priorityDistanceSquared); - } - - private static boolean raycastHitMatchesBody(@Nonnull UUID bodyUuid, - @Nullable Ref expectedBodyRef, - @Nonnull RaycastHitView view) { - Ref bodyRef = view.bodyRef(); - if (bodyRef == null || !bodyRef.isValid()) { - return false; - } - if (expectedBodyRef != null && expectedBodyRef.isValid()) { - return sameRef(expectedBodyRef, bodyRef); - } - UuidComponent uuid = bodyRef.getStore().getComponent(bodyRef, - UuidComponent.getComponentType()); - return uuid != null && bodyUuid.equals(uuid.getUuid()); - } - - private static boolean sameRef(@Nullable Ref first, - @Nullable Ref second) { - return first == second - || (first != null - && second != null - && first.getStore() != null - && first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); - } - - private static void submitRaycast(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsSpaceBinding space, - @Nonnull PhysicsVisualRuntime.BodyVisualInterestState state, - @Nonnull InterestProbe probe, - @Nonnull PhysicsBodySnapshot snapshot) { - VisualInterest interest = Objects.requireNonNull(probe.interest(), "interest"); - Vector3f target = RAYCAST_TARGET.get() - .set(snapshot.positionX(), snapshot.positionY(), snapshot.positionZ()); - state.startPendingRaycast(PhysicsStoreRaycasts.closestAsync( - resource.requireAuthoritativeWorldForPhysicsStore("submit visual occlusion raycast"), - space.spaceId(), - interest.position(), - target)); - } - - @Nonnull - private static InterestProbe probeNearestLikelyInterest(@Nonnull PhysicsBodySnapshot snapshot, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - float radius) { - return probeNearestLikelyInterest(snapshot.positionX(), - snapshot.positionY(), - snapshot.positionZ(), - settings, - interests, - radius); - } - - @Nonnull - private static InterestProbe probeNearestLikelyInterest(float positionX, - float positionY, - float positionZ, - @Nonnull PhysicsSpaceSettings settings, - @Nonnull List interests, - float radius) { - float radiusSquared = radius * radius; - float nearestDistanceSquared = Float.POSITIVE_INFINITY; - VisualInterest nearestInterest = null; - for (VisualInterest interest : interests) { - float dx = positionX - interest.position().x; - float dy = positionY - interest.position().y; - float dz = positionZ - interest.position().z; - float distanceSquared = dx * dx + dy * dy + dz * dz; - if (distanceSquared <= radiusSquared - && distanceSquared < nearestDistanceSquared - && DetachedVisualGeometry.isInsideViewCone(settings, - interest, - dx, - dy, - dz, - distanceSquared)) { - nearestDistanceSquared = distanceSquared; - nearestInterest = interest; - } - } - return nearestInterest == null - ? InterestProbe.notVisible() - : new InterestProbe(nearestInterest, nearestDistanceSquared); - } - - record Result(boolean shouldMaterialize, - float distanceSquared, - float priorityDistanceSquared) { - - static Result notVisible() { - return new Result(false, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - } - - static Result visible(float distanceSquared, float priorityDistanceSquared) { - return new Result(true, distanceSquared, priorityDistanceSquared); - } - } - - static final class RaycastBudget { - - private int used; - - boolean tryUse(@Nonnull PhysicsSpaceSettings settings) { - if (used >= settings.getVisualSyncSettings().getVisualOcclusionRaycastsPerTick()) { - return false; - } - used++; - return true; - } - } - - private record InterestProbe(@Nullable VisualInterest interest, - float distanceSquared) { - - static InterestProbe notVisible() { - return new InterestProbe(null, Float.POSITIVE_INFINITY); - } - - boolean inRange() { - return interest != null; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java deleted file mode 100644 index 9945ea0e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GameplayAttachmentSnapshot.java +++ /dev/null @@ -1,126 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; - -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.Queue; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -final class GameplayAttachmentSnapshot { - - @Nonnull - private final AttachmentSource source; - @Nullable - private AttachmentBodies bodies; - - private GameplayAttachmentSnapshot(@Nonnull AttachmentSource source) { - this.source = source; - } - - @Nonnull - static GameplayAttachmentSnapshot forStore(@Nonnull Store store) { - return fromAttachmentSource(() -> collectGameplayAttachments(store)); - } - - @Nonnull - private static GameplayAttachmentSnapshot fromAttachmentSource(@Nonnull AttachmentSource source) { - return new GameplayAttachmentSnapshot(source); - } - - boolean hasKnownGameplayAttachment(boolean runtimeIndexHasAttachment, - @Nullable Ref bodyRef, - @Nonnull UUID bodyUuid) { - if (runtimeIndexHasAttachment) { - return true; - } - return hasGameplayAttachment(bodyRef, bodyUuid); - } - - boolean hasGameplayAttachment(@Nullable Ref bodyRef, - @Nonnull UUID bodyUuid) { - return hasGameplayAttachment(bodyRef) || bodies().bodyUuids().contains(bodyUuid); - } - - private boolean hasGameplayAttachment(@Nullable Ref bodyRef) { - if (bodyRef == null || !bodyRef.isValid()) { - return false; - } - Ref indexed = bodies().bodyRefsByRowIndex().get(bodyRef.getIndex()); - return sameRef(indexed, bodyRef); - } - - @Nonnull - private AttachmentBodies bodies() { - if (bodies == null) { - bodies = source.attachments(); - } - return bodies; - } - - @Nonnull - private static AttachmentBodies collectGameplayAttachments( - @Nonnull Store store) { - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - Queue bodyUuids = new ConcurrentLinkedQueue<>(); - Queue> bodyRefs = new ConcurrentLinkedQueue<>(); - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, _) -> { - BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, - attachmentType); - if (attachment != null - && attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { - bodyUuids.add(attachment.getBodyUuid()); - Ref bodyRef = attachment.getBodyRef(); - if (bodyRef != null && bodyRef.isValid()) { - bodyRefs.add(bodyRef); - } - } - }); - Set uniqueBodyUuids = new ObjectOpenHashSet<>(); - uniqueBodyUuids.addAll(bodyUuids); - Int2ObjectOpenHashMap> bodyRefsByRowIndex = - new Int2ObjectOpenHashMap<>(); - for (Ref bodyRef : bodyRefs) { - int rowIndex = bodyRef.getIndex(); - Ref existing = bodyRefsByRowIndex.get(rowIndex); - if (existing == null || sameRef(existing, bodyRef)) { - bodyRefsByRowIndex.put(rowIndex, bodyRef); - } - } - return new AttachmentBodies(uniqueBodyUuids, bodyRefsByRowIndex); - } - - private static boolean sameRef(@Nullable Ref first, - @Nullable Ref second) { - return first == second - || (first != null - && second != null - && first.getStore() != null - && first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); - } - - @FunctionalInterface - private interface AttachmentSource { - - @Nonnull - AttachmentBodies attachments(); - } - - private record AttachmentBodies( - @Nonnull Set bodyUuids, - @Nonnull Int2ObjectOpenHashMap> bodyRefsByRowIndex - ) { - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java index 78991d8a..0f83d716 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java @@ -21,7 +21,7 @@ /** * Removes serialized generated visual proxies left by the pre-PhysicsStore runtime model. */ -public class PhysicsDetachedVisualMaterializationSystem extends TickingSystem { +public class PhysicsGeneratedProxyCleanupSystem extends TickingSystem { private static final int CLEANUP_INTERVAL_TICKS = 40; From 2bb56ddbe8c1d89f79cf3e8fc4880a3573aae0a0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:22:39 +0200 Subject: [PATCH 204/534] refactor(visual): remove generated proxy view APIs Signed-off-by: Blovien --- .../resources/GeneratedVisualProxyView.java | 16 ---- .../PhysicsProjectionIndexResource.java | 58 --------------- .../resources/PhysicsVisualRuntime.java | 73 ------------------- .../PhysicsWorldRuntimeResource.java | 18 ----- 4 files changed, 165 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java deleted file mode 100644 index fe2a53a8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/GeneratedVisualProxyView.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Copied generated-proxy index row with durable body identity plus optional live body ref. - */ -public record GeneratedVisualProxyView(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref proxy) { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index 7c14740e..2b7eac78 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -146,63 +145,6 @@ public Ref getGeneratedVisualProxy(@Nonnull Ref bodyR return liveGeneratedVisualProxy(bodyRef); } - @Nonnull - public Collection getGeneratedVisualProxyBodyKeys() { - List bodyKeys = new ArrayList<>(); - synchronized (this) { - for (Iterator>> iterator = - generatedVisualProxies.entrySet().iterator(); iterator.hasNext();) { - Map.Entry> entry = iterator.next(); - Ref proxy = entry.getValue(); - if (proxy != null && proxy.isValid()) { - bodyKeys.add(RigidBodyKey.of(entry.getKey())); - } else { - iterator.remove(); - } - } - } - return bodyKeys; - } - - @Nonnull - public Collection getGeneratedVisualProxyViews() { - List views = new ArrayList<>(); - Map> bodyRefsByUuid = new Object2ObjectOpenHashMap<>(); - synchronized (this) { - List staleRowIndexes = new ArrayList<>(); - for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { - GeneratedVisualProxyRef row = entry.getValue(); - Ref proxy = row.proxy(); - Ref uuidProxy = generatedVisualProxies.get(row.bodyUuid()); - if (row.bodyRef() == null - || !row.bodyRef().isValid() - || proxy == null - || !proxy.isValid() - || !sameRef(proxy, uuidProxy)) { - staleRowIndexes.add(entry.getIntKey()); - continue; - } - bodyRefsByUuid.put(row.bodyUuid(), row.bodyRef()); - } - for (Integer rowIndex : staleRowIndexes) { - generatedVisualProxiesByRowIndex.remove(rowIndex.intValue()); - } - for (Iterator>> iterator = - generatedVisualProxies.entrySet().iterator(); iterator.hasNext();) { - Map.Entry> entry = iterator.next(); - Ref proxy = entry.getValue(); - if (proxy != null && proxy.isValid()) { - views.add(new GeneratedVisualProxyView(entry.getKey(), - bodyRefsByUuid.get(entry.getKey()), - proxy)); - } else { - iterator.remove(); - } - } - } - return views; - } - public int generatedVisualProxyCount() { int count = 0; synchronized (this) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index f164b5f4..6e0bc2af 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -226,79 +226,6 @@ private Ref liveGeneratedVisualProxy( return proxy; } - @Nonnull - public Collection getGeneratedVisualProxyBodyKeys() { - List bodyKeys = new ArrayList<>(); - List staleBodyUuids = new ArrayList<>(); - List> staleProxies = new ArrayList<>(); - synchronized (this) { - for (Map.Entry> entry : generatedVisualProxies.entrySet()) { - Ref proxy = entry.getValue(); - if (proxy != null && proxy.isValid()) { - bodyKeys.add(RigidBodyKey.of(entry.getKey())); - } else { - staleBodyUuids.add(entry.getKey()); - if (proxy != null) { - staleProxies.add(proxy); - } - } - } - for (UUID bodyUuid : staleBodyUuids) { - generatedVisualProxies.remove(bodyUuid); - } - } - cleanSyncStates(staleProxies); - return bodyKeys; - } - - @Nonnull - public Collection getGeneratedVisualProxyViews() { - List views = new ArrayList<>(); - List staleRowIndexes = new ArrayList<>(); - List staleBodyUuids = new ArrayList<>(); - List> staleProxies = new ArrayList<>(); - Map> bodyRefsByUuid = new Object2ObjectOpenHashMap<>(); - synchronized (this) { - for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { - GeneratedVisualProxyRef row = entry.getValue(); - Ref proxy = row.proxy(); - Ref uuidProxy = generatedVisualProxies.get(row.bodyUuid()); - if (!row.bodyRef().isValid() - || proxy == null - || !proxy.isValid() - || !sameRef(proxy, uuidProxy)) { - staleRowIndexes.add(entry.getIntKey()); - if (proxy != null && !proxy.isValid()) { - staleProxies.add(proxy); - } - continue; - } - bodyRefsByUuid.put(row.bodyUuid(), row.bodyRef()); - } - for (Integer rowIndex : staleRowIndexes) { - generatedVisualProxiesByRowIndex.remove(rowIndex.intValue()); - } - for (Map.Entry> entry : generatedVisualProxies.entrySet()) { - Ref proxy = entry.getValue(); - if (proxy != null && proxy.isValid()) { - views.add(new GeneratedVisualProxyView(entry.getKey(), - bodyRefsByUuid.get(entry.getKey()), - proxy)); - } else { - staleBodyUuids.add(entry.getKey()); - if (proxy != null) { - staleProxies.add(proxy); - } - } - } - for (UUID bodyUuid : staleBodyUuids) { - generatedVisualProxies.remove(bodyUuid); - } - } - cleanSyncStates(staleProxies); - return views; - } - public int generatedVisualProxyCount() { int count = 0; List> staleProxies = new ArrayList<>(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index c8d22914..ed71adfa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2257,24 +2257,6 @@ public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid, return visualRuntime.getGeneratedVisualProxy(bodyUuid, bodyRef); } - @Nonnull - public Collection getGeneratedVisualProxyBodyKeys() { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativeProjectionIndex("list generated visual proxies") - .getGeneratedVisualProxyBodyKeys(); - } - return visualRuntime.getGeneratedVisualProxyBodyKeys(); - } - - @Nonnull - public Collection getGeneratedVisualProxyViews() { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativeProjectionIndex("list generated visual proxies") - .getGeneratedVisualProxyViews(); - } - return visualRuntime.getGeneratedVisualProxyViews(); - } - public int getGeneratedVisualProxyCount() { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativeProjectionIndex("count generated visual proxies") From c61ea22f9f271010cb5c6e2297e41a81bf8409f6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:27:37 +0200 Subject: [PATCH 205/534] refactor(visual): remove key-based proxy overloads Signed-off-by: Blovien --- .../resources/PhysicsVisualRuntime.java | 24 ------- .../PhysicsWorldRuntimeResource.java | 64 ------------------- 2 files changed, 88 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index 6e0bc2af..98dc8f8b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -183,11 +183,6 @@ private boolean hasLiveAttachments(@Nonnull Map>> at return hasLiveAttachment; } - @Nullable - public Ref getGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { - return getGeneratedVisualProxy(bodyKey.value(), null); - } - @Nullable public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { if (!bodyRef.isValid()) { @@ -249,11 +244,6 @@ public int generatedVisualProxyCount() { return count; } - public void setGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, - @Nonnull Ref proxy) { - setGeneratedVisualProxy(bodyKey.value(), null, proxy); - } - public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref proxy) { @@ -277,10 +267,6 @@ public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, } } - public void clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { - clearGeneratedVisualProxy(bodyKey.value(), null); - } - public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { Ref proxy; @@ -300,11 +286,6 @@ public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, } } - public boolean clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, - @Nonnull Ref expectedProxy) { - return clearGeneratedVisualProxy(bodyKey.value(), null, expectedProxy); - } - public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref expectedProxy) { @@ -336,11 +317,6 @@ public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, return true; } - public synchronized boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, - @Nonnull Ref proxy) { - return isGeneratedVisualProxy(bodyKey.value(), null, proxy); - } - public synchronized boolean isGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref proxy) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index ed71adfa..28971772 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2221,20 +2221,6 @@ public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, visualRuntime.unregisterAttachment(bodyUuid, bodyRef, attachment); } - @Nullable - public Ref getGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("read generated visual proxy"); - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve generated visual proxy key"); - return bodyRef != null - ? projection.getGeneratedVisualProxy(bodyRef) - : projection.getGeneratedVisualProxy(bodyKey.value()); - } - return visualRuntime.getGeneratedVisualProxy(bodyKey); - } - @Nullable public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { @@ -2265,18 +2251,6 @@ public int getGeneratedVisualProxyCount() { return visualRuntime.generatedVisualProxyCount(); } - public void setGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref proxy) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("set generated visual proxy"); - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve generated visual proxy key"); - projection.setGeneratedVisualProxy(bodyKey.value(), bodyRef, proxy); - return; - } - visualRuntime.setGeneratedVisualProxy(bodyKey, proxy); - } - public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref proxy) { @@ -2288,17 +2262,6 @@ public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, visualRuntime.setGeneratedVisualProxy(bodyUuid, bodyRef, proxy); } - public void clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey) { - if (hasAttachedAuthoritativePhysicsStore()) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve generated visual proxy key"); - authoritativeProjectionIndex("clear generated visual proxy") - .clearGeneratedVisualProxyForBodyRef(bodyKey.value(), bodyRef); - return; - } - visualRuntime.clearGeneratedVisualProxy(bodyKey); - } - public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { @@ -2309,25 +2272,6 @@ public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, visualRuntime.clearGeneratedVisualProxy(bodyUuid, bodyRef); } - public boolean clearGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, - @Nonnull Ref expectedProxy) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("clear generated visual proxy"); - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve generated visual proxy key"); - Ref registered = bodyRef != null - ? projection.getGeneratedVisualProxy(bodyRef) - : projection.getGeneratedVisualProxy(bodyKey.value()); - if (!sameRef(registered, expectedProxy)) { - return false; - } - projection.clearGeneratedVisualProxy(bodyKey.value(), bodyRef, expectedProxy); - return true; - } - return visualRuntime.clearGeneratedVisualProxy(bodyKey, expectedProxy); - } - public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref expectedProxy) { @@ -2346,14 +2290,6 @@ public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, return visualRuntime.clearGeneratedVisualProxy(bodyUuid, bodyRef, expectedProxy); } - public boolean isGeneratedVisualProxy(@Nonnull RigidBodyKey bodyKey, - @Nonnull Ref proxy) { - if (hasAttachedAuthoritativePhysicsStore()) { - return sameRef(getGeneratedVisualProxy(bodyKey), proxy); - } - return visualRuntime.isGeneratedVisualProxy(bodyKey, proxy); - } - public boolean isGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref proxy) { From 4c05eae198d8044f6d7029ff10298f6134e32406 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:35:22 +0200 Subject: [PATCH 206/534] docs(core): clarify physics store threading terms Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreTopologyMutations.java | 2 +- .../resources/PhysicsStoreReadQueueResource.java | 2 +- .../physicsstore/systems/PhysicsStoreQueuedReadSystem.java | 2 +- .../core/plugin/physicsstore/PhysicsStoreDiagnostics.java | 4 ++-- .../core/plugin/physicsstore/PhysicsStoreRaycasts.java | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 7a300cd3..d89e26b5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -35,7 +35,7 @@ import javax.annotation.Nullable; /** - * Owner-lane topology mutations for public compatibility cleanup paths. + * World-thread topology mutations for public compatibility cleanup paths. */ public final class PhysicsStoreTopologyMutations { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java index 042693fd..5dbeaf57 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -18,7 +18,7 @@ import javax.annotation.Nonnull; /** - * Owner-lane backend read queue drained by PhysicsStore systems. + * World-thread backend read queue drained by PhysicsStore systems. * *

Reads execute during PhysicsStore ticking. Callers must pass value-copied inputs and return * immutable values rather than live stores, refs, runtime resources, or backend handles. This is a diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java index 16711ad0..13ae057d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java @@ -13,7 +13,7 @@ import javax.annotation.Nonnull; /** - * Resolves queued live backend reads on the PhysicsStore owner lane. + * Resolves queued live backend reads on the owning PhysicsStore world thread. */ public final class PhysicsStoreQueuedReadSystem extends TickingSystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index d3aa4ae4..4a411185 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -19,8 +19,8 @@ * Diagnostics for live PhysicsStore backend state. * *

The synchronous methods read mutable runtime/backend state and must only run from the - * PhysicsStore tick lane or explicitly scheduled PhysicsStore owner work. Off-lane callers should - * use the {@code *Async} methods, which enqueue copied reads on the PhysicsStore owner thread.

+ * owning PhysicsStore world thread. Off-thread callers should use the {@code *Async} methods, + * which enqueue copied reads on that world thread.

*/ public final class PhysicsStoreDiagnostics { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java index d56d9aff..da994816 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java @@ -22,9 +22,9 @@ * PhysicsStore live backend raycasts. * *

The synchronous methods read live backend state through {@link PhysicsRuntimeResource} and - * must only be called from the PhysicsStore tick lane or explicitly scheduled PhysicsStore owner - * work. Off-lane callers should use the {@code *Async} methods, which copy inputs, enqueue the - * read on the PhysicsStore owner thread, and complete with copied hit views.

+ * must only be called from the owning PhysicsStore world thread. Off-thread callers should use the + * {@code *Async} methods, which copy inputs, enqueue the read on that world thread, and complete + * with copied hit views.

*/ public final class PhysicsStoreRaycasts { From 0ea456132a5b7c319d091d4372550eedd46d52ed Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:39:21 +0200 Subject: [PATCH 207/534] refactor(core): remove generated proxy attachment factory Signed-off-by: Blovien --- .../projection/BodyAttachmentComponent.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java index 4e6f021c..17d8e454 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java @@ -178,19 +178,6 @@ public static BodyAttachmentComponent impulseOwnedVisual(@Nonnull UUID bodyUuid, visualOriginOffsetY); } - @Nonnull - public static BodyAttachmentComponent generatedProxy(@Nonnull UUID bodyUuid, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - float visualOriginOffsetY) { - return new BodyAttachmentComponent(bodyUuid, - TransformAuthority.BODY, - AttachmentLifecycle.GENERATED_PROXY, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY); - } - @Nonnull public UUID getBodyUuid() { return bodyUuid; From a0a998505198905c29e31ead0a4777320cca88f5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:45:21 +0200 Subject: [PATCH 208/534] refactor(core): index body registrations by uuid Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 2 +- .../PhysicsBodyRegistrationResource.java | 35 ++++++++----------- .../systems/StaleBodyRemovalSystem.java | 3 +- .../body/PhysicsBodyRegistrationView.java | 6 ++++ 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index d89e26b5..41cab045 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -357,7 +357,7 @@ private static void removeRows(@Nonnull Store store, } if (removal.kind() == RowKind.BODY) { snapshots.removeBody(removal.rowUuid()); - registrations.removeBody(RigidBodyKey.of(removal.rowUuid())); + registrations.removeBody(removal.rowUuid()); } store.removeEntity(removal.ref(), store.getRegistry().newHolder(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index ae243e46..ea23efa7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -33,7 +33,7 @@ public PhysicsBodyRegistrationResource() { @Nullable public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey bodyKey) { - return registrations.viewsByKey().get(Objects.requireNonNull(bodyKey, "bodyKey")); + return getBodyRegistrationView(Objects.requireNonNull(bodyKey, "bodyKey").value()); } @Nullable @@ -84,50 +84,47 @@ public Collection getBodyRegistrationViews( } public void publish(@Nonnull Collection publications) { - Object2ObjectLinkedOpenHashMap publicationsByKey = + Object2ObjectLinkedOpenHashMap publicationsByUuid = new Object2ObjectLinkedOpenHashMap<>(); for (BodyRegistrationPublication publication : publications) { BodyRegistrationPublication checkedPublication = Objects.requireNonNull(publication, "publication"); - publicationsByKey.put(checkedPublication.view().bodyKey(), checkedPublication); + publicationsByUuid.put(checkedPublication.view().bodyUuid(), checkedPublication); } - Object2ObjectLinkedOpenHashMap viewsByKey = - new Object2ObjectLinkedOpenHashMap<>(); Object2ObjectLinkedOpenHashMap viewsByUuid = new Object2ObjectLinkedOpenHashMap<>(); Int2ObjectOpenHashMap viewsByRowIndex = new Int2ObjectOpenHashMap<>(); - for (BodyRegistrationPublication publication : publicationsByKey.values()) { + for (BodyRegistrationPublication publication : publicationsByUuid.values()) { PhysicsBodyRegistrationView registration = publication.view(); - viewsByKey.put(registration.bodyKey(), registration); - viewsByUuid.put(registration.bodyKey().value(), registration); + viewsByUuid.put(registration.bodyUuid(), registration); viewsByRowIndex.put(publication.bodyRef().getIndex(), new RegistrationByRef(publication.bodyRef(), registration)); } - registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), - Map.copyOf(viewsByKey), + registrations = new PublishedRegistrations(List.copyOf(viewsByUuid.values()), Map.copyOf(viewsByUuid), viewsByRowIndex); } public void removeBody(@Nonnull RigidBodyKey bodyKey) { + removeBody(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public void removeBody(@Nonnull UUID bodyUuid) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); PublishedRegistrations current = registrations; - if (!current.viewsByKey().containsKey(bodyKey)) { + if (!current.viewsByUuid().containsKey(bodyUuid)) { return; } - Object2ObjectLinkedOpenHashMap viewsByKey = - new Object2ObjectLinkedOpenHashMap<>(current.viewsByKey()); - viewsByKey.remove(bodyKey); Object2ObjectLinkedOpenHashMap viewsByUuid = new Object2ObjectLinkedOpenHashMap<>(current.viewsByUuid()); - viewsByUuid.remove(bodyKey.value()); + viewsByUuid.remove(bodyUuid); Int2ObjectOpenHashMap viewsByRowIndex = new Int2ObjectOpenHashMap<>(current.viewsByRowIndex()); viewsByRowIndex.int2ObjectEntrySet() - .removeIf(entry -> entry.getValue().view().bodyKey().equals(bodyKey)); - registrations = new PublishedRegistrations(List.copyOf(viewsByKey.values()), - Map.copyOf(viewsByKey), + .removeIf(entry -> entry.getValue().view().bodyUuid().equals(bodyUuid)); + registrations = new PublishedRegistrations(List.copyOf(viewsByUuid.values()), Map.copyOf(viewsByUuid), viewsByRowIndex); } @@ -170,13 +167,11 @@ private record RegistrationByRef(@Nonnull Ref bodyRef, private record PublishedRegistrations( @Nonnull List views, - @Nonnull Map viewsByKey, @Nonnull Map viewsByUuid, @Nonnull Int2ObjectOpenHashMap viewsByRowIndex) { private static final PublishedRegistrations EMPTY = new PublishedRegistrations(List.of(), - Map.of(), Map.of(), new Int2ObjectOpenHashMap<>()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 923dccc8..46bf935a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -18,7 +18,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; @@ -88,7 +87,7 @@ private static void removeStaleBodies(@Nonnull Store store, identity.removeBodyHandle(body.bodyHandle()); identity.removeUuid(body.bodyUuid(), body.bodyRef()); snapshots.removeBody(body.bodyUuid()); - registrations.removeBody(RigidBodyKey.of(body.bodyUuid())); + registrations.removeBody(body.bodyUuid()); runtime.removeBodyHandle(body.bodyUuid(), body.bodyRef()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java index 8b661d9e..f45c6135 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.body; import dev.hytalemodding.impulse.api.SpaceId; +import java.util.UUID; import javax.annotation.Nonnull; /** @@ -10,4 +11,9 @@ public record PhysicsBodyRegistrationView(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + + @Nonnull + public UUID bodyUuid() { + return bodyKey.value(); + } } From c7541aabc975421460319db9e47a6f27aefc1aa3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:48:11 +0200 Subject: [PATCH 209/534] refactor(core): publish body registrations from uuid Signed-off-by: Blovien --- .../systems/CompletedStepPublicationSystem.java | 4 ++-- .../core/plugin/body/PhysicsBodyRegistrationView.java | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 85d46eb9..b1d6e4d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -214,7 +214,7 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run SpaceId spaceId = compatibility.getSpaceId(body.getSpaceUuid()); if (spaceId != null) { registrations.add(new BodyRegistrationPublication(rowRef, - new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), + new PhysicsBodyRegistrationView(rowUuid, spaceId, body.getKind(), body.getPersistenceMode()))); @@ -226,7 +226,7 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run SpaceId spaceId = compatibility.getSpaceId(terrain.getSpaceUuid()); if (spaceId != null) { registrations.add(new BodyRegistrationPublication(rowRef, - new PhysicsBodyRegistrationView(RigidBodyKey.of(rowUuid), + new PhysicsBodyRegistrationView(rowUuid, spaceId, PhysicsBodyKind.WORLD_COLLISION, PhysicsBodyPersistenceMode.RUNTIME_ONLY))); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java index f45c6135..5759a8b4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java @@ -12,6 +12,13 @@ public record PhysicsBodyRegistrationView(@Nonnull RigidBodyKey bodyKey, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + public PhysicsBodyRegistrationView(@Nonnull UUID bodyUuid, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + this(RigidBodyKey.of(bodyUuid), spaceId, kind, persistenceMode); + } + @Nonnull public UUID bodyUuid() { return bodyKey.value(); From 2dd872aead53fe9f23b5c25997525fb0308927db Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 14:54:49 +0200 Subject: [PATCH 210/534] refactor(examples): inline physics store access Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 9bef27c1..5d0c816a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -56,16 +56,12 @@ public final class ExamplePhysicsUtils { private ExamplePhysicsUtils() { } - @Nonnull - public static Store physicsStore(@Nonnull World world) { - return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() - .getStore(); - } - @Nullable public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world, @Nonnull SpaceId spaceId) { - Store store = physicsStore(world); + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space ref"); UUID spaceUuid = store .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) @@ -81,14 +77,19 @@ public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyRowDescriptor row) { - return addPhysicsStoreBody(physicsStore(world), row); + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); + return addPhysicsStoreBody(store, row); } @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyRowDescriptor row, @Nonnull BodyCommandComponent command) { - Store store = physicsStore(world); + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); Ref bodyRef = addPhysicsStoreBody(store, row); appendPhysicsStoreBodyCommand(store, bodyRef, command); return bodyRef; @@ -99,13 +100,18 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyRowDescriptor row, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { - return addPhysicsStoreBody(physicsStore(world), row, dynamics, target); + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); + return addPhysicsStoreBody(store, row, dynamics, target); } public static void addPhysicsStoreBodies(@Nonnull World world, @Nonnull Iterable rows) { Objects.requireNonNull(rows, "rows"); - Store store = physicsStore(world); + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); PhysicsStoreThreading.requireWorldThread(store, "add PhysicsStore body rows"); for (BodyRowDescriptor row : rows) { addPhysicsStoreBodyUnchecked(store, row, row.dynamics(), row.target()); @@ -150,7 +156,9 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store addPhysicsStoreJoint(@Nonnull World world, @Nonnull UUID jointUuid, @Nonnull JointComponent joint) { - Store store = physicsStore(world); + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore joint row"); return store.addEntity(PhysicsStoreEntities.jointHolder(store, Objects.requireNonNull(jointUuid, "jointUuid"), @@ -174,7 +182,9 @@ public static void appendPhysicsStoreBodyCommand(@Nonnull Store st public static SpaceId spaceId(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull OptionalArg spaceArg) { - Store store = physicsStore(world); + Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + .getPhysicsStore() + .getStore(); PhysicsStoreThreading.requireWorldThread(store, "select a PhysicsStore space"); PhysicsSpaceCompatibilityIndexResource compatibility = store .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()); From 5ca73a6d5e25db235ed96a1b313bcd1823008974 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:01:01 +0200 Subject: [PATCH 211/534] refactor(core): add ref based physics raycasts Signed-off-by: Blovien --- .../PhysicsStoreBackendAccess.java | 13 +++ .../physicsstore/PhysicsStoreRaycasts.java | 107 ++++++++++++++++++ .../examples/commands/GrabCommand.java | 9 +- .../examples/commands/RaycastCommand.java | 10 +- .../commands/stress/StressRaycastCommand.java | 10 +- 5 files changed, 146 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java index 8d8d2865..4a473819 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java @@ -15,6 +15,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -42,6 +43,18 @@ static SpaceContext space(@Nonnull Store store, @Nonnull UUID spac return spaceRef != null && spaceRef.isValid() ? space(runtime, spaceRef) : null; } + @Nullable + static SpaceContext space(@Nonnull Store store, + @Nonnull Ref spaceRef) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + Objects.requireNonNull(spaceRef, "spaceRef"); + if (spaceRef.getStore() != store || !spaceRef.isValid()) { + return null; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + return space(runtime, spaceRef); + } + @Nullable static SpaceContext space(@Nonnull PhysicsRuntimeResource runtime, @Nonnull Ref spaceRef) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java index da994816..65d90c17 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -51,6 +52,16 @@ public static Optional closest(@Nonnull Store stor return space != null ? closest(store, space, from, to) : Optional.empty(); } + @Nonnull + public static Optional closest(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + return space != null ? closest(store, space, from, to) : Optional.empty(); + } + @Nonnull public static List all(@Nonnull Store store, @Nonnull SpaceId spaceId, @@ -71,6 +82,16 @@ public static List all(@Nonnull Store store, return space != null ? all(store, space, from, to) : List.of(); } + @Nonnull + public static List all(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + return space != null ? all(store, space, from, to) : List.of(); + } + @Nonnull public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, @Nonnull SpaceId spaceId, @@ -89,6 +110,15 @@ public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull List rays) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + return closestBatch(store, space, rays); + } + @Nonnull public static CompletionStage> closestAsync(@Nonnull World world, @Nonnull SpaceId spaceId, @@ -141,6 +171,33 @@ public static CompletionStage> closestAsync( physics -> closest(physics, spaceUuid, copiedFrom, copiedTo)); } + @Nonnull + public static CompletionStage> closestAsync(@Nonnull World world, + @Nonnull Ref spaceRef, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore closest raycast read", + physics -> closest(physics, spaceRef, copiedFrom, copiedTo)); + } + + @Nonnull + public static CompletionStage> closestAsync( + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore closest raycast read", + physics -> closest(physics, spaceRef, copiedFrom, copiedTo)); + } + @Nonnull public static CompletionStage> allAsync(@Nonnull World world, @Nonnull SpaceId spaceId, @@ -192,6 +249,32 @@ public static CompletionStage> allAsync( physics -> all(physics, spaceUuid, copiedFrom, copiedTo)); } + @Nonnull + public static CompletionStage> allAsync(@Nonnull World world, + @Nonnull Ref spaceRef, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore all raycast read", + physics -> all(physics, spaceRef, copiedFrom, copiedTo)); + } + + @Nonnull + public static CompletionStage> allAsync(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Vector3f from, + @Nonnull Vector3f to) { + Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); + Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore all raycast read", + physics -> all(physics, spaceRef, copiedFrom, copiedTo)); + } + @Nonnull public static CompletionStage closestBatchAsync( @Nonnull World world, @@ -238,6 +321,30 @@ public static CompletionStage closestBatchAsync( physics -> closestBatch(physics, spaceUuid, copied)); } + @Nonnull + public static CompletionStage closestBatchAsync( + @Nonnull World world, + @Nonnull Ref spaceRef, + @Nonnull List rays) { + List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore batch raycast read", + physics -> closestBatch(physics, spaceRef, copied)); + } + + @Nonnull + public static CompletionStage closestBatchAsync( + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull List rays) { + List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore batch raycast read", + physics -> closestBatch(physics, spaceRef, copied)); + } + @Nonnull private static Optional closest(@Nonnull Store store, @Nonnull PhysicsStoreBackendAccess.SpaceContext space, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index db9d3482..c9faed6e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -88,6 +88,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (targetSpaceId == null) { return CompletableFuture.completedFuture(null); } + Ref targetSpaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + targetSpaceId); + if (targetSpaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + targetSpaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); Transform look = TargetUtil.getLook(ref, store); @@ -97,7 +104,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return PhysicsStoreAsync.acceptOnWorldThread(world, PhysicsStoreRaycasts.allAsync(world, - targetSpaceId, + targetSpaceRef, ExamplePhysicsUtils.toVector3f(start), ExamplePhysicsUtils.toVector3f(end)), hits -> finishGrab(ctx, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index ee8191cf..a67ae430 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -12,6 +12,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; @@ -44,6 +45,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); @@ -54,7 +62,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, DebugUtils.FLAG_FADE); return PhysicsStoreAsync.acceptOnWorldThread(world, PhysicsStoreRaycasts.closestAsync(world, - spaceId, + spaceRef, ExamplePhysicsUtils.toVector3f(start), ExamplePhysicsUtils.toVector3f(end)), hit -> handleHit(ctx, world, hit.map(RaycastCommand::toResult).orElse(null))); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index af52a61c..1439e406 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -10,6 +10,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; @@ -56,13 +57,20 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } int side = (int) Math.ceil(Math.sqrt(rays)); List segments = getRaycastSegments(side, rays, playerPos); long startNanos = System.nanoTime(); return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreRaycasts.closestBatchAsync(world, spaceId, segments), + PhysicsStoreRaycasts.closestBatchAsync(world, spaceRef, segments), result -> { long elapsedNanos = System.nanoTime() - startNanos; ctx.sender().sendMessage(Message.raw("Ran " + rays + " raycasts: " From 5b889f3dbc994dbd801259d1b8c68ee3a43f20e6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:07:19 +0200 Subject: [PATCH 212/534] refactor(examples): remove grab body key flow Signed-off-by: Blovien --- .../examples/commands/GrabCommand.java | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index c9faed6e..26485875 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -25,7 +25,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; @@ -40,9 +39,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RigidBodyStateView; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -169,7 +166,7 @@ private static void finishGrab(@Nonnull CommandContext ctx, private static GrabPhysicsState createGrabControl(@Nonnull World world, @Nonnull SpaceId selectedSpaceId, @Nonnull HitSelection selection) { - RigidBodyStateView selectedState = bodyState(world, selection.bodyRef(), selection.bodyKey()); + PhysicsStoreBodySnapshot selectedState = bodyState(world, selection.bodyRef()); if (selectedState == null) { return null; } @@ -184,8 +181,8 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, } Vector3f hitPoint = new Vector3f(selection.point()); - Vector3f bodyLocalHit = new Vector3f(hitPoint).sub(selectedState.pose().position()); - Quaternionf inverseBodyRotation = selectedState.pose().rotation(); + Vector3f bodyLocalHit = new Vector3f(hitPoint).sub(selectedState.position()); + Quaternionf inverseBodyRotation = selectedState.rotation(); inverseBodyRotation.invert().transform(bodyLocalHit); UUID anchorBodyUuid = UUID.randomUUID(); @@ -265,7 +262,6 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource continue; } candidates.add(new HitCandidate(hit.bodyRef(), - registration.bodyKey(), registration.spaceId(), hit.point(), hit.fraction(), @@ -281,7 +277,6 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource if (best == null || candidate.fraction() < best.fraction()) { best = new HitSelection(candidate.bodyRef(), - candidate.bodyKey(), attachments.controllableAttachment(), candidate.spaceId(), candidate.point(), @@ -293,20 +288,14 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource } @Nullable - private static RigidBodyStateView bodyState(@Nonnull World world, - @Nonnull Ref bodyRef, - @Nonnull RigidBodyKey bodyKey) { + private static PhysicsStoreBodySnapshot bodyState(@Nonnull World world, + @Nonnull Ref bodyRef) { Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); PhysicsStoreThreading.requireWorldThread(store, "read copied PhysicsStore grab body snapshot"); - PhysicsStoreBodySnapshot body = store + return store .getResource(PhysicsSnapshotResource.getResourceType()) .getBody(bodyRef); - return body != null - ? new RigidBodyStateView(bodyKey, - body.bodyType(), - RigidBodyPose.of(body.position(), body.rotation())) - : null; } @Nonnull @@ -332,7 +321,6 @@ private static AttachmentSelection inspectGameplayAttachments(@Nonnull PhysicsWo } private record HitSelection(@Nonnull Ref bodyRef, - @Nonnull RigidBodyKey bodyKey, @Nullable Ref attachment, @Nullable SpaceId spaceId, @Nonnull Vector3f point, @@ -341,7 +329,6 @@ private record HitSelection(@Nonnull Ref bodyRef, } private record HitCandidate(@Nonnull Ref bodyRef, - @Nonnull RigidBodyKey bodyKey, @Nullable SpaceId spaceId, @Nonnull Vector3f point, float fraction, From d41154dceabd2468bb4dacd55e790ac19c3c024a Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:11:13 +0200 Subject: [PATCH 213/534] refactor(core): expose uuid physics body views Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 3 ++ .../resources/PhysicsWorldResource.java | 31 +++++++++++++++++++ .../systems/ExplosiveFuseContactSystem.java | 24 +++++++------- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 28971772..7a20cf99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1859,6 +1859,7 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey } @Nullable + @Override public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativePhysicsStore("read physics body registration view") @@ -1869,6 +1870,7 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUui } @Nullable + @Override public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativePhysicsStore("read physics body registration view") @@ -2130,6 +2132,7 @@ public Collection> getBodyAttachments(@Nonnull Ref> getBodyAttachments(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index ceade79e..81eb7e35 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -23,6 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import java.util.Collection; import java.util.List; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -322,6 +323,25 @@ public abstract PhysicsMutationHandle destroyBodyAsync( public abstract PhysicsBodyRegistrationView getBodyRegistrationView( @Nonnull RigidBodyKey bodyKey); + /** + * Returns immutable registration metadata for a body UUID. + * + *

Prefer this overload when the caller is crossing a durable identity boundary. The key + * overload remains for compatibility with legacy event/facade APIs.

+ */ + @Nullable + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { + return getBodyRegistrationView(RigidBodyKey.of(bodyUuid)); + } + + /** + * Returns immutable registration metadata for a live PhysicsStore body ref. + */ + @Nullable + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { + return null; + } + /** * Returns immutable registration metadata for every registered body. */ @@ -352,6 +372,17 @@ public abstract Collection getBodyRegistrationViews @Nonnull public abstract Collection> getBodyAttachments(@Nonnull RigidBodyKey bodyKey); + /** + * Returns ECS attachments associated with a durable body UUID and optional live body ref. + */ + @Nonnull + public Collection> getBodyAttachments(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + return bodyRef != null && bodyRef.isValid() + ? getBodyAttachments(bodyRef) + : getBodyAttachments(RigidBodyKey.of(bodyUuid)); + } + /** * Returns ECS attachments associated with a live PhysicsStore body ref. * diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index dc97bcb4..f5eb46c8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -9,7 +9,6 @@ import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; @@ -18,6 +17,7 @@ import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; +import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Vector3d; import org.joml.Vector3f; @@ -48,14 +48,14 @@ public void handle(@Nonnull Store store, armIfExplosiveTouchesWorld(commandBuffer, resource, tick, - contact.bodyAKey(), - contact.bodyBKey(), + contact.bodyAKey().value(), + contact.bodyBKey().value(), contactCenter(contact.pointOnB())); armIfExplosiveTouchesWorld(commandBuffer, resource, tick, - contact.bodyBKey(), - contact.bodyAKey(), + contact.bodyBKey().value(), + contact.bodyAKey().value(), contactCenter(contact.pointOnA())); } } @@ -64,20 +64,20 @@ public void handle(@Nonnull Store store, private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer commandBuffer, @Nonnull PhysicsWorldResource resource, long tick, - @Nonnull RigidBodyKey explosiveBodyKey, - @Nonnull RigidBodyKey otherBodyKey, + @Nonnull UUID explosiveBodyUuid, + @Nonnull UUID otherBodyUuid, @Nonnull Vector3d explosionCenter) { - if (!isWorldCollision(resource, otherBodyKey)) { + if (!isWorldCollision(resource, otherBodyUuid)) { return; } - for (Ref ref : resource.getBodyAttachments(explosiveBodyKey)) { + for (Ref ref : resource.getBodyAttachments(explosiveBodyUuid, null)) { BodyAttachmentComponent attachment = commandBuffer.getComponent(ref, ATTACHMENT_TYPE); ExplosiveBlockComponent explosive = commandBuffer.getComponent(ref, EXPLOSIVE_TYPE); ExplosiveFuseComponent fuse = commandBuffer.getComponent(ref, FUSE_TYPE); if (attachment == null || explosive == null || fuse == null - || !explosiveBodyKey.value().equals(attachment.getBodyUuid())) { + || !explosiveBodyUuid.equals(attachment.getBodyUuid())) { continue; } ExplosiveFuseComponent updated = fuse.clone(); @@ -88,8 +88,8 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer Date: Tue, 16 Jun 2026 15:15:25 +0200 Subject: [PATCH 214/534] refactor(examples): raycast with physics store refs Signed-off-by: Blovien --- .../commands/PhysicsStoreExampleCommands.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 53e3e3e4..cf92e2d9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -92,8 +92,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } return PhysicsStoreAsync.acceptOnWorldThread(world, - raycastAsync(store, ref, spaceId), + raycastAsync(store, ref, spaceRef), hit -> applyImpulse(ctx, store, ref, world, hit)); } @@ -207,8 +214,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } return PhysicsStoreAsync.acceptOnWorldThread(world, - raycastAsync(store, ref, spaceId), + raycastAsync(store, ref, spaceRef), hit -> attachView(ctx, store, hit)); } @@ -423,13 +437,13 @@ private static float optionalFloat(@Nonnull CommandContext ctx, @Nonnull private static CompletionStage raycastAsync(@Nonnull Store store, @Nonnull Ref ref, - @Nonnull SpaceId spaceId) { + @Nonnull Ref spaceRef) { Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); Vector3d end = new Vector3d(start) .add(new Vector3d(look.getDirection()).mul(RAY_LENGTH)); return PhysicsStoreRaycasts.closestAsync(store.getExternalData().getWorld(), - spaceId, + spaceRef, vector(start), vector(end)) .thenApply(hit -> hit.orElse(null)); From e798e5ee09398af3b89325c93abe9199223e8f3a Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:18:10 +0200 Subject: [PATCH 215/534] refactor(core): store registration views by uuid Signed-off-by: Blovien --- .../body/PhysicsBodyRegistrationView.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java index 5759a8b4..d0dad4a9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java @@ -1,26 +1,34 @@ package dev.hytalemodding.impulse.core.plugin.body; import dev.hytalemodding.impulse.api.SpaceId; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; /** * Immutable body registration metadata safe for public and off-owner callers. */ -public record PhysicsBodyRegistrationView(@Nonnull RigidBodyKey bodyKey, +public record PhysicsBodyRegistrationView(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - public PhysicsBodyRegistrationView(@Nonnull UUID bodyUuid, + public PhysicsBodyRegistrationView { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(persistenceMode, "persistenceMode"); + } + + public PhysicsBodyRegistrationView(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - this(RigidBodyKey.of(bodyUuid), spaceId, kind, persistenceMode); + this(Objects.requireNonNull(bodyKey, "bodyKey").value(), spaceId, kind, persistenceMode); } @Nonnull - public UUID bodyUuid() { - return bodyKey.value(); + public RigidBodyKey bodyKey() { + return RigidBodyKey.of(bodyUuid); } } From 0226f4b7236ed6a39ae15a4a1cc4d2ee4e9988cd Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:20:58 +0200 Subject: [PATCH 216/534] refactor(core): prefer uuid body registration lookups Signed-off-by: Blovien --- .../resources/PhysicsWorldRuntimeResource.java | 6 +++--- .../plugin/resources/PhysicsWorldResource.java | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 7a20cf99..fbe9fd91 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -899,11 +899,11 @@ private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( @Nonnull Store store, @Nonnull PhysicsBodyRegistrationResource registrations, @Nonnull PhysicsStoreBodySnapshot body) { - RigidBodyKey bodyKey = RigidBodyKey.of(body.bodyUuid()); - PhysicsBodyRegistrationView registration = registrations.getBodyRegistrationView(bodyKey); + PhysicsBodyRegistrationView registration = registrations.getBodyRegistrationView(body.bodyUuid()); if (registration == null) { return null; } + RigidBodyKey bodyKey = RigidBodyKey.of(body.bodyUuid()); return new PhysicsBodySnapshotEntry(bodyKey, toPublicBodySnapshot(store, body), registration.spaceId(), @@ -1866,7 +1866,7 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUui .getResource(PhysicsBodyRegistrationResource.getResourceType()) .getBodyRegistrationView(bodyUuid); } - return getBodyRegistrationView(RigidBodyKey.of(bodyUuid)); + return bodyRegistry.getPublishedRegistrationView(RigidBodyKey.of(bodyUuid)); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 81eb7e35..86ccb6fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -317,21 +317,21 @@ public abstract PhysicsMutationHandle destroyBodyAsync( @Nonnull RigidBodyKey bodyKey); /** - * Returns immutable registration metadata for a body key. + * Returns immutable registration metadata for a body UUID. + * + *

Prefer this overload when the caller is crossing a durable identity boundary.

*/ @Nullable - public abstract PhysicsBodyRegistrationView getBodyRegistrationView( - @Nonnull RigidBodyKey bodyKey); + public abstract PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid); /** - * Returns immutable registration metadata for a body UUID. + * Returns immutable registration metadata for a body key. * - *

Prefer this overload when the caller is crossing a durable identity boundary. The key - * overload remains for compatibility with legacy event/facade APIs.

+ *

This overload is retained for compatibility with legacy event/facade APIs.

*/ @Nullable - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { - return getBodyRegistrationView(RigidBodyKey.of(bodyUuid)); + public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey bodyKey) { + return getBodyRegistrationView(bodyKey.value()); } /** From 391fa5d4964ceab88b7a9e81690f54e4f42be0a5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:23:26 +0200 Subject: [PATCH 217/534] refactor(core): debug bodies by uuid registrations Signed-off-by: Blovien --- .../core/internal/systems/debug/PhysicsDebugSystem.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 7ef1a233..6671549e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -221,11 +221,13 @@ private static int renderEntityBodies(@Nonnull Collection viewers, } double maxDistanceSquared = viewRadius * viewRadius; for (PhysicsBodyRegistrationView registration : resource.getBodyRegistrationViews(PhysicsBodyKind.BODY)) { - if (!resource.hasBodyAttachments(registration.bodyKey())) { + Collection> attachments = resource.getBodyAttachments(registration.bodyUuid(), + null); + if (attachments.isEmpty()) { continue; } - for (Ref attachmentRef : resource.getBodyAttachments(registration.bodyKey())) { + for (Ref attachmentRef : attachments) { if (!attachmentRef.isValid()) { continue; } @@ -238,7 +240,8 @@ private static int renderEntityBodies(@Nonnull Collection viewers, continue; } - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(registration.bodyKey()); + PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(registration.bodyUuid(), + null); if (snapshot == null) { continue; } From b33e56828689d1a96b988e8a47bb4051e85e1267 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:25:35 +0200 Subject: [PATCH 218/534] refactor(core): remove key registration resource overloads Signed-off-by: Blovien --- .../resources/PhysicsBodyRegistrationResource.java | 10 ---------- .../resources/PhysicsWorldRuntimeResource.java | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index ea23efa7..049314ad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -7,7 +7,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; @@ -31,11 +30,6 @@ public final class PhysicsBodyRegistrationResource implements Resource publication viewsByRowIndex); } - public void removeBody(@Nonnull RigidBodyKey bodyKey) { - removeBody(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - public void removeBody(@Nonnull UUID bodyUuid) { Objects.requireNonNull(bodyUuid, "bodyUuid"); PublishedRegistrations current = registrations; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index fbe9fd91..6ce99140 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1853,7 +1853,7 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey if (hasAttachedAuthoritativePhysicsStore()) { return authoritativePhysicsStore("read physics body registration view") .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(bodyKey); + .getBodyRegistrationView(bodyKey.value()); } return bodyRegistry.getPublishedRegistrationView(bodyKey); } From 4a8cce8ec6befb832b79647c2770bd94dbfc4ec4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:28:41 +0200 Subject: [PATCH 219/534] refactor(core): inherit key registration compatibility Signed-off-by: Blovien --- .../resources/PhysicsWorldRuntimeResource.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 6ce99140..5e4b5766 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1847,17 +1847,6 @@ public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { return bodyRegistry.getRegistration(bodyKey); } - @Nullable - @Override - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey bodyKey) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics body registration view") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(bodyKey.value()); - } - return bodyRegistry.getPublishedRegistrationView(bodyKey); - } - @Nullable @Override public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { From c30784aa53f74c293c00078e2e7ad56bfd7854a1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:30:42 +0200 Subject: [PATCH 220/534] refactor(core): prefer uuid body attachments Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 30 +------------------ .../resources/PhysicsWorldResource.java | 26 +++++++++++----- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 5e4b5766..e834e9f5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2095,21 +2095,6 @@ public Collection getBodyRegistrationViews(@Nonnull return bodyRegistry.getPublishedRegistrationViews(kind); } - @Nonnull - @Override - public Collection> getBodyAttachments(@Nonnull RigidBodyKey bodyKey) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("read physics body attachments"); - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve body attachment key"); - return bodyRef != null - ? projection.getAttachments(bodyRef) - : projection.getAttachments(bodyKey.value()); - } - return visualRuntime.getAttachments(bodyKey); - } - @Nonnull @Override public Collection> getBodyAttachments(@Nonnull Ref bodyRef) { @@ -2134,20 +2119,6 @@ public Collection> getBodyAttachments(@Nonnull UUID bodyUuid, return visualRuntime.getAttachments(bodyUuid, bodyRef); } - @Override - public boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("check physics body attachments"); - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve body attachment key"); - return bodyRef != null - ? projection.hasAttachments(bodyRef) - : projection.hasAttachments(bodyKey.value()); - } - return visualRuntime.hasAttachments(bodyKey); - } - @Override public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { @@ -2157,6 +2128,7 @@ public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { return visualRuntime.hasAttachments(bodyRef); } + @Override public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { if (hasAttachedAuthoritativePhysicsStore()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 86ccb6fa..0866d8c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -368,20 +368,20 @@ public abstract Collection getBodyRegistrationViews /** * Returns ECS attachments associated with a registered body key. + * + *

This overload is retained for compatibility with legacy event/facade APIs.

*/ @Nonnull - public abstract Collection> getBodyAttachments(@Nonnull RigidBodyKey bodyKey); + public Collection> getBodyAttachments(@Nonnull RigidBodyKey bodyKey) { + return getBodyAttachments(bodyKey.value(), null); + } /** * Returns ECS attachments associated with a durable body UUID and optional live body ref. */ @Nonnull - public Collection> getBodyAttachments(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - return bodyRef != null && bodyRef.isValid() - ? getBodyAttachments(bodyRef) - : getBodyAttachments(RigidBodyKey.of(bodyUuid)); - } + public abstract Collection> getBodyAttachments(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef); /** * Returns ECS attachments associated with a live PhysicsStore body ref. @@ -397,8 +397,18 @@ public Collection> getBodyAttachments(@Nonnull RefThis overload is retained for compatibility with legacy event/facade APIs.

+ */ + public boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey) { + return hasBodyAttachments(bodyKey.value(), null); + } + + /** + * Returns whether a durable body UUID and optional live body ref have one or more ECS attachments. */ - public abstract boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey); + public abstract boolean hasBodyAttachments(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef); /** * Returns whether a live PhysicsStore body ref has one or more ECS attachments without From f999b282bd59d5d07d3cd65aa4017cf2f7b404d8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:32:37 +0200 Subject: [PATCH 221/534] refactor(core): remove key visual attachment helpers Signed-off-by: Blovien --- .../resources/PhysicsVisualRuntime.java | 18 ------------------ .../resources/PhysicsWorldRuntimeResource.java | 4 ++-- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index 98dc8f8b..db576474 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -49,10 +49,6 @@ public PhysicsVisualRuntime(@Nonnull Consumer> syncStateCleaner this.syncStateCleaner = syncStateCleaner; } - public synchronized void registerAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { - registerAttachment(bodyKey.value(), null, attachment); - } - public synchronized void registerAttachment(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref attachment) { @@ -63,11 +59,6 @@ public synchronized void registerAttachment(@Nonnull UUID bodyUuid, } } - public synchronized void unregisterAttachment(@Nonnull RigidBodyKey bodyKey, - @Nonnull Ref attachment) { - unregisterAttachment(bodyKey.value(), null, attachment); - } - public synchronized void unregisterAttachment(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref attachment) { @@ -87,11 +78,6 @@ public synchronized void unregisterAttachment(@Nonnull UUID bodyUuid, } } - @Nonnull - public Collection> getAttachments(@Nonnull RigidBodyKey bodyKey) { - return getAttachments(bodyKey.value(), null); - } - @Nonnull public Collection> getAttachments(@Nonnull Ref bodyRef) { if (!bodyRef.isValid()) { @@ -140,10 +126,6 @@ private Collection> liveAttachments( return liveAttachments; } - public boolean hasAttachments(@Nonnull RigidBodyKey bodyKey) { - return hasAttachments(bodyKey.value(), null); - } - public boolean hasAttachments(@Nonnull Ref bodyRef) { return bodyRef.isValid() && hasLiveAttachments(bodyRef); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index e834e9f5..6837a729 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2149,7 +2149,7 @@ public void registerBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref Date: Tue, 16 Jun 2026 15:34:54 +0200 Subject: [PATCH 222/534] refactor(core): remove key visual interest helpers Signed-off-by: Blovien --- .../resources/PhysicsVisualRuntime.java | 18 ------------------ .../resources/PhysicsWorldRuntimeResource.java | 16 ---------------- .../resources/body/PhysicsBodyRuntime.java | 2 +- 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index db576474..d4895dc6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -329,13 +328,6 @@ public synchronized void clearSyntheticVisualInterests() { syntheticVisualInterests.clear(); } - @Nonnull - public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( - @Nonnull RigidBodyKey bodyKey) { - return bodyVisualInterestStates.computeIfAbsent(bodyKey.value(), - _ -> new BodyVisualInterestState()); - } - @Nonnull public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( @Nonnull UUID bodyUuid, @@ -347,12 +339,6 @@ public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( _ -> new BodyVisualInterestState()); } - @Nullable - public synchronized BodyVisualInterestState getBodyVisualInterestState( - @Nonnull RigidBodyKey bodyKey) { - return bodyVisualInterestStates.get(bodyKey.value()); - } - @Nullable public synchronized BodyVisualInterestState getBodyVisualInterestState( @Nonnull UUID bodyUuid, @@ -385,10 +371,6 @@ public synchronized void clearBodyVisualInterestState(@Nonnull UUID bodyUuid, } } - public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { - clearBodyRuntimeState(bodyKey.value(), null); - } - public void clearBodyRuntimeState(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { List> staleRefs = new ArrayList<>(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 6837a729..fcd25d2b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2339,13 +2339,6 @@ public void clearBodySyncState(@Nonnull Ref entityRef) { runtimeState.clearBodySyncState(entityRef); } - @Nonnull - public BodyVisualInterestState getOrCreateBodyVisualInterestState(@Nonnull RigidBodyKey bodyKey) { - BodyVisualInterestState state = visualRuntime.getOrCreateBodyVisualInterestState(bodyKey); - state.advanceVisualInterestTick(visualInterestTick.get()); - return state; - } - @Nonnull public BodyVisualInterestState getOrCreateBodyVisualInterestState(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { @@ -2355,15 +2348,6 @@ public BodyVisualInterestState getOrCreateBodyVisualInterestState(@Nonnull UUID return state; } - @Nullable - public BodyVisualInterestState getBodyVisualInterestState(@Nonnull RigidBodyKey bodyKey) { - BodyVisualInterestState state = visualRuntime.getBodyVisualInterestState(bodyKey); - if (state != null) { - state.advanceVisualInterestTick(visualInterestTick.get()); - } - return state; - } - @Nullable public BodyVisualInterestState getBodyVisualInterestState(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 5969aee2..277f55d8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -151,7 +151,7 @@ public void clearBodyStateWithoutMarkingWorldChanged() { } public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { - visualRuntime.clearBodyRuntimeState(bodyKey); + visualRuntime.clearBodyRuntimeState(bodyKey.value(), null); chunkRuntime.clearBody(bodyKey); lifecycleState.removeBodySnapshot(bodyKey); } From 4ea93998d2823c12d3e99d4d2473ac53ad47fd52 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:37:04 +0200 Subject: [PATCH 223/534] refactor(core): clean bodies by uuid membership Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 1ee456fe..6d3b9ab4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -141,7 +141,7 @@ private void cleanWithinRadius(@Nonnull CommandContext context, PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); resource.refreshBodySnapshots(); - Set selectedBodyKeys = selectBodyKeysNear(resource, center, radius); + SelectedBodies selectedBodies = selectBodiesNear(resource, center, radius); double radiusSquared = (double) radius * radius; ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); @@ -154,7 +154,7 @@ private void cleanWithinRadius(@Nonnull CommandContext context, BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); assert attachment != null; - if (!selectedBodyKeys.contains(RigidBodyKey.of(attachment.getBodyUuid()))) { + if (!selectedBodies.bodyUuids().contains(attachment.getBodyUuid())) { return; } @@ -185,7 +185,7 @@ private void cleanWithinRadius(@Nonnull CommandContext context, archetypeChunk, index, session, - selectedBodyKeys, + selectedBodies.bodyUuids(), center, radiusSquared)) { return; @@ -199,7 +199,7 @@ private void cleanWithinRadius(@Nonnull CommandContext context, } int removedBodies = 0; - for (RigidBodyKey bodyKey : selectedBodyKeys) { + for (RigidBodyKey bodyKey : selectedBodies.bodyKeys()) { resource.destroyBody(bodyKey); removedBodies++; } @@ -223,18 +223,22 @@ private static Vector3d playerPosition(@Nonnull CommandContext context, } @Nonnull - private static Set selectBodyKeysNear(@Nonnull PhysicsWorldRuntimeResource resource, + private static SelectedBodies selectBodiesNear(@Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d center, float radius) { Set bodyKeys = new ObjectOpenHashSet<>(); + Set bodyUuids = new ObjectOpenHashSet<>(); Vector3f centerF = new Vector3f((float) center.x, (float) center.y, (float) center.z); for (SpaceId spaceId : resource.getSpaceIds()) { resource.forEachIndexedBodySnapshotNear(spaceId, centerF, radius, - (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> bodyKeys.add(bodyKey)); + (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> { + bodyKeys.add(bodyKey); + bodyUuids.add(bodyKey.value()); + }); } - return bodyKeys; + return new SelectedBodies(bodyKeys, bodyUuids); } private static boolean controlSessionSelected( @@ -242,11 +246,11 @@ private static boolean controlSessionSelected( @Nonnull ArchetypeChunk archetypeChunk, int index, @Nonnull PhysicsControlSessionComponent session, - @Nonnull Set selectedBodyKeys, + @Nonnull Set selectedBodyUuids, @Nonnull Vector3d center, double radiusSquared) { - if (containsBody(selectedBodyKeys, session.getBodyRef()) - || containsBody(selectedBodyKeys, session.getAnchorBodyRef()) + if (containsBody(selectedBodyUuids, session.getBodyRef()) + || containsBody(selectedBodyUuids, session.getAnchorBodyRef()) || entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { return true; } @@ -270,10 +274,14 @@ private static ComponentType contro : null; } - private static boolean containsBody(@Nonnull Set bodyKeys, + private static boolean containsBody(@Nonnull Set bodyUuids, @Nullable Ref bodyRef) { UUID bodyUuid = rowUuid(bodyRef); - return bodyUuid != null && bodyKeys.contains(RigidBodyKey.of(bodyUuid)); + return bodyUuid != null && bodyUuids.contains(bodyUuid); + } + + private record SelectedBodies(@Nonnull Set bodyKeys, + @Nonnull Set bodyUuids) { } @Nullable From 0cf103e21a48628920815fda49f2cf7384157631 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:42:07 +0200 Subject: [PATCH 224/534] refactor(core): track pending bodies by uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 2 +- .../body/PhysicsBodyCreationTracker.java | 32 +++++++++++++------ .../resources/body/PhysicsBodyRuntime.java | 5 +++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index fcd25d2b..299ff08c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2024,7 +2024,7 @@ public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { } public boolean isBodyCreationPending(@Nonnull UUID bodyUuid) { - return isBodyCreationPending(RigidBodyKey.of(bodyUuid)); + return bodyRuntime.isBodyCreationPending(bodyUuid); } public boolean hasPublishedOrPendingBodyRegistration(@Nonnull RigidBodyKey bodyKey) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java index 69a24a31..4241de56 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java @@ -2,31 +2,45 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; /** - * Tracks body keys reserved by async body registrations until publication catches up. + * Tracks body UUIDs reserved by async body registrations until publication catches up. */ public final class PhysicsBodyCreationTracker { - private final Object2IntOpenHashMap pendingBodyCreations = + private final Object2IntOpenHashMap pendingBodyCreations = new Object2IntOpenHashMap<>(); public void markPending(@Nonnull RigidBodyKey bodyKey) { + markPending(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public void markPending(@Nonnull UUID bodyUuid) { synchronized (pendingBodyCreations) { - pendingBodyCreations.addTo(bodyKey, 1); + pendingBodyCreations.addTo(Objects.requireNonNull(bodyUuid, "bodyUuid"), 1); } } public void clearPending(@Nonnull RigidBodyKey bodyKey) { + clearPending(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public void clearPending(@Nonnull UUID bodyUuid) { synchronized (pendingBodyCreations) { - clearPendingDirect(bodyKey); + clearPendingDirect(Objects.requireNonNull(bodyUuid, "bodyUuid")); } } public boolean isPending(@Nonnull RigidBodyKey bodyKey) { + return isPending(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public boolean isPending(@Nonnull UUID bodyUuid) { synchronized (pendingBodyCreations) { - return pendingBodyCreations.containsKey(bodyKey); + return pendingBodyCreations.containsKey(Objects.requireNonNull(bodyUuid, "bodyUuid")); } } @@ -36,12 +50,12 @@ public void clear() { } } - private void clearPendingDirect(RigidBodyKey bodyKey) { - int count = pendingBodyCreations.getInt(bodyKey); + private void clearPendingDirect(UUID bodyUuid) { + int count = pendingBodyCreations.getInt(bodyUuid); if (count <= 1) { - pendingBodyCreations.removeInt(bodyKey); + pendingBodyCreations.removeInt(bodyUuid); } else { - pendingBodyCreations.put(bodyKey, count - 1); + pendingBodyCreations.put(bodyUuid, count - 1); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 277f55d8..40cdd99e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -15,6 +15,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.ArrayList; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -76,6 +77,10 @@ public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { return creationTracker.isPending(bodyKey); } + public boolean isBodyCreationPending(@Nonnull UUID bodyUuid) { + return creationTracker.isPending(bodyUuid); + } + @Nonnull public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, From 81d4061cdbee75ca2e7b7f6bf830a0e4d0142231 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:45:30 +0200 Subject: [PATCH 225/534] refactor(examples): resolve material space once Signed-off-by: Blovien --- .../examples/commands/MaterialsCommand.java | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 1d033b01..3aede0d8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -11,9 +11,12 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -43,15 +46,22 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-3.0, 5.0, 4.0); - spawnSphere(store, time, spaceId, new Vector3d(origin), 0.05f, 0.9f, 3.0f); - spawnSphere(store, time, spaceId, new Vector3d(origin).add(2.0, 0.0, 0.0), + spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin), 0.05f, 0.9f, 3.0f); + spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin).add(2.0, 0.0, 0.0), 0.95f, 0.9f, 3.0f); - spawnSphere(store, time, spaceId, new Vector3d(origin).add(4.0, 0.0, 0.0), + spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin).add(4.0, 0.0, 0.0), 0.5f, 0.0f, 2.0f); - spawnSphere(store, time, spaceId, new Vector3d(origin).add(6.0, 0.0, 0.0), + spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin).add(6.0, 0.0, 0.0), 0.5f, 0.95f, 2.0f); ctx.sender().sendMessage(Message.raw( @@ -61,19 +71,31 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawnSphere(@Nonnull Store store, @Nonnull TimeResource time, + @Nonnull World world, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float restitution, float friction, float speed) { - ExamplePhysicsUtils.spawnBlockBody(store, + UUID bodyUuid = UUID.randomUUID(); + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, + ExamplePhysicsUtils.bodyRow(spaceRef, + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), + PhysicsShapeSpec.sphere(0.5f), + 1.0f, + RigidBodySpawnSettings.material(friction, restitution), + new Vector3f(speed, 0.0f, 0.0f))); + ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, - spaceId, - position, - ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, - PhysicsShapeSpec.sphere(0.5f), - 1.0f, - RigidBodySpawnSettings.material(friction, restitution), - new Vector3f(speed, 0.0f, 0.0f)); + new CreatedBlockBody(bodyUuid, + bodyRef, + spaceId, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + (float) position.x, + (float) position.y, + (float) position.z, + true)); } } From 2171ca50fba1d29e898601dab6dc6bd4b1091132 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:48:54 +0200 Subject: [PATCH 226/534] refactor(examples): spawn shapes with resolved space refs Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 88 ++++++++++++++++--- .../examples/commands/MaterialsCommand.java | 38 +++----- .../examples/commands/ShapesCommand.java | 20 +++-- .../commands/stress/StressShapesCommand.java | 20 +++-- 4 files changed, 121 insertions(+), 45 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 5d0c816a..3c97f238 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -239,6 +239,30 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, + "bound in PhysicsStore: " + spaceId.value()); } + @Nonnull + public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, + @Nonnull TimeResource time, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d visualPosition, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity) { + return attachPhysicsStoreBlockBody(store, + time, + createPhysicsStoreBlockBody(store.getExternalData().getWorld(), + spaceRef, + spaceId, + visualPosition, + blockType, + shape, + mass, + settings, + linearVelocity)); + } + @Nullable private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store store, @Nonnull SpaceId spaceId, @@ -266,20 +290,64 @@ private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store bodyRef; try { - bodyRef = addPhysicsStoreBody(world, - bodyRow(spaceRef, - bodyUuid, - bodyCenter, - shape, - mass, - settings, - linearVelocity)); + return createPhysicsStoreBlockBody(world, + spaceRef, + spaceId, + visualPosition, + blockType, + shape, + mass, + settings, + linearVelocity, + bodyUuid); } catch (IllegalStateException exception) { return null; } + } + + @Nonnull + private static CreatedBlockBody createPhysicsStoreBlockBody(@Nonnull World world, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d visualPosition, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity) { + return createPhysicsStoreBlockBody(world, + spaceRef, + spaceId, + visualPosition, + blockType, + shape, + mass, + settings, + linearVelocity, + UUID.randomUUID()); + } + + @Nonnull + private static CreatedBlockBody createPhysicsStoreBlockBody(@Nonnull World world, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d visualPosition, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nullable Vector3f linearVelocity, + @Nonnull UUID bodyUuid) { + Vector3f bodyCenter = toVector3f(visualPosition); + Ref bodyRef = addPhysicsStoreBody(world, + bodyRow(spaceRef, + bodyUuid, + bodyCenter, + shape, + mass, + settings, + linearVelocity)); return new CreatedBlockBody(bodyUuid, bodyRef, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 3aede0d8..8ecfd741 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -15,8 +15,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -56,12 +54,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-3.0, 5.0, 4.0); - spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin), 0.05f, 0.9f, 3.0f); - spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin).add(2.0, 0.0, 0.0), + spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin), 0.05f, 0.9f, 3.0f); + spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin).add(2.0, 0.0, 0.0), 0.95f, 0.9f, 3.0f); - spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin).add(4.0, 0.0, 0.0), + spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin).add(4.0, 0.0, 0.0), 0.5f, 0.0f, 2.0f); - spawnSphere(store, time, world, spaceRef, spaceId, new Vector3d(origin).add(6.0, 0.0, 0.0), + spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin).add(6.0, 0.0, 0.0), 0.5f, 0.95f, 2.0f); ctx.sender().sendMessage(Message.raw( @@ -71,31 +69,21 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawnSphere(@Nonnull Store store, @Nonnull TimeResource time, - @Nonnull World world, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float restitution, float friction, float speed) { - UUID bodyUuid = UUID.randomUUID(); - Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceRef, - bodyUuid, - ExamplePhysicsUtils.toVector3f(position), - PhysicsShapeSpec.sphere(0.5f), - 1.0f, - RigidBodySpawnSettings.material(friction, restitution), - new Vector3f(speed, 0.0f, 0.0f))); - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, + ExamplePhysicsUtils.spawnBlockBody(store, time, - new CreatedBlockBody(bodyUuid, - bodyRef, - spaceId, - ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, - (float) position.x, - (float) position.y, - (float) position.z, - true)); + spaceRef, + spaceId, + position, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + PhysicsShapeSpec.sphere(0.5f), + 1.0f, + RigidBodySpawnSettings.material(friction, restitution), + new Vector3f(speed, 0.0f, 0.0f)); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index 8ec87138..e21ab37d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -43,15 +44,22 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-4.0, 3.0, 3.0); - spawn(store, time, spaceId, ShapeType.BOX, PhysicsAxis.Y, + spawn(store, time, spaceRef, spaceId, ShapeType.BOX, PhysicsAxis.Y, origin, 0); - spawn(store, time, spaceId, ShapeType.SPHERE, PhysicsAxis.Y, origin, 2); - spawn(store, time, spaceId, ShapeType.CAPSULE, PhysicsAxis.Y, origin, 4); - spawn(store, time, spaceId, ShapeType.CYLINDER, PhysicsAxis.Y, origin, 6); - spawn(store, time, spaceId, ShapeType.CONE, PhysicsAxis.Y, origin, 8); + spawn(store, time, spaceRef, spaceId, ShapeType.SPHERE, PhysicsAxis.Y, origin, 2); + spawn(store, time, spaceRef, spaceId, ShapeType.CAPSULE, PhysicsAxis.Y, origin, 4); + spawn(store, time, spaceRef, spaceId, ShapeType.CYLINDER, PhysicsAxis.Y, origin, 6); + spawn(store, time, spaceRef, spaceId, ShapeType.CONE, PhysicsAxis.Y, origin, 8); ctx.sender().sendMessage(Message.raw("Spawned shape demo.")); return CompletableFuture.completedFuture(null); @@ -59,6 +67,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawn(@Nonnull Store store, @Nonnull TimeResource time, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull ShapeType type, @Nonnull PhysicsAxis axis, @@ -66,6 +75,7 @@ private static void spawn(@Nonnull Store store, int xOffset) { ExamplePhysicsUtils.spawnBlockBody(store, time, + spaceRef, spaceId, new Vector3d(origin).add(xOffset, 0.0, 0.0), ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index 424a73ef..38925e4d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -57,6 +58,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-12.0, 5.0, 5.0); @@ -66,15 +74,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int col = set % 4; Vector3d base = new Vector3d(origin).add(col * 7.0, row * 2.2, row * 1.5); - spawn(store, time, spaceId, ShapeType.BOX, axis, + spawn(store, time, spaceRef, spaceId, ShapeType.BOX, axis, base, 0.0); - spawn(store, time, spaceId, ShapeType.SPHERE, axis, + spawn(store, time, spaceRef, spaceId, ShapeType.SPHERE, axis, base, 1.2); - spawn(store, time, spaceId, ShapeType.CAPSULE, axis, + spawn(store, time, spaceRef, spaceId, ShapeType.CAPSULE, axis, base, 2.4); - spawn(store, time, spaceId, ShapeType.CYLINDER, axis, + spawn(store, time, spaceRef, spaceId, ShapeType.CYLINDER, axis, base, 3.6); - spawn(store, time, spaceId, ShapeType.CONE, axis, + spawn(store, time, spaceRef, spaceId, ShapeType.CONE, axis, base, 4.8); } @@ -85,6 +93,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawn(@Nonnull Store store, @Nonnull TimeResource time, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull ShapeType type, @Nonnull PhysicsAxis axis, @@ -92,6 +101,7 @@ private static void spawn(@Nonnull Store store, double xOffset) { ExamplePhysicsUtils.spawnBlockBody(store, time, + spaceRef, spaceId, new Vector3d(base).add(xOffset, 0.0, 0.0), ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, From 872b5f480c4f34d87653189ca26c5153efed8faa Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:52:57 +0200 Subject: [PATCH 227/534] refactor(examples): resolve drop space once Signed-off-by: Blovien --- .../impulse/examples/commands/DropCommand.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index 376d06bc..43d4a34e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -52,10 +53,18 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound yet.")); + return CompletableFuture.completedFuture(null); + } TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.spawnBlockBody(store, time, + spaceRef, spaceId, new Vector3d(spawnX, spawnY, spawnZ), blockType(ctx), From e9d74ab671ea75698345c3a72c185c3e20333bd5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 15:59:14 +0200 Subject: [PATCH 228/534] refactor(examples): spawn stress bodies with space refs Signed-off-by: Blovien --- .../commands/ExamplePhysicsUtils.java | 222 ++++++++++++++++++ .../stress/StressBenchmarkCommand.java | 22 +- .../commands/stress/StressBodiesCommand.java | 9 + .../stress/StressRawBodiesCommand.java | 8 + 4 files changed, 254 insertions(+), 7 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index 3c97f238..dd6aaab9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -430,6 +430,38 @@ public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World worl rowApplyNanos); } + @Nonnull + public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Consumer builder) { + DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(spaceRef, + spaceId, + expectedBodies, + shape, + mass, + settings, + kind, + persistenceMode, + builder); + if (plan.isEmpty()) { + return new BodyRowBatchTiming(0, plan.setupWallNanos(), 0L); + } + + long applyStartNanos = System.nanoTime(); + addPhysicsStoreBodies(world, plan.bodies()); + long rowApplyNanos = System.nanoTime() - applyStartNanos; + return new BodyRowBatchTiming(plan.count(), + plan.setupWallNanos(), + rowApplyNanos); + } + @Nonnull private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, @Nonnull SpaceId spaceId, @@ -461,6 +493,70 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, + "bound in PhysicsStore: " + spaceId.value()); } + return dynamicBodyBatchPlan(spaceRef, + spaceId, + shape, + mass, + settings, + kind, + persistenceMode, + batch, + setupStartNanos); + } + + @Nonnull + private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Consumer builder) { + Objects.requireNonNull(spaceRef, "spaceRef"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(shape, "shape"); + Objects.requireNonNull(settings, "settings"); + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(persistenceMode, "persistenceMode"); + + PhysicsStoreThreading.requireWorldThread(spaceRef.getStore(), + "add dynamic PhysicsStore body rows"); + if (!spaceRef.isValid()) { + throw new IllegalStateException("Cannot add dynamic body rows because the target " + + "PhysicsStore space row is no longer valid: " + spaceId.value()); + } + + long setupStartNanos = System.nanoTime(); + BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); + Objects.requireNonNull(builder, "builder").accept(batch); + batch.seal(); + if (batch.isEmpty()) { + return new DynamicBodyBatchPlan(List.of(), 0L); + } + + return dynamicBodyBatchPlan(spaceRef, + spaceId, + shape, + mass, + settings, + kind, + persistenceMode, + batch, + setupStartNanos); + } + + @Nonnull + private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull BlockBodyBatchBuilder batch, + long setupStartNanos) { List bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); @@ -523,6 +619,32 @@ public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store st true).collectedBodies(); } + @Nonnull + public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store store, + @Nonnull TimeResource time, + long serverTick, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull Consumer builder) { + return spawnBlockBodiesInternal(store, + time, + serverTick, + spaceRef, + spaceId, + expectedBodies, + blockType, + shape, + mass, + settings, + builder, + true).collectedBodies(); + } + @Nonnull public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, @Nonnull TimeResource time, @@ -547,6 +669,32 @@ public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, + @Nonnull TimeResource time, + long serverTick, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull Consumer builder) { + return spawnBlockBodiesInternal(store, + time, + serverTick, + spaceRef, + spaceId, + expectedBodies, + blockType, + shape, + mass, + settings, + builder, + false).timing(); + } + @Nonnull private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store store, @Nonnull TimeResource time, @@ -576,6 +724,80 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store store, + @Nonnull TimeResource time, + long serverTick, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull Consumer builder, + boolean collectBodies) { + Objects.requireNonNull(spaceRef, "spaceRef"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(shape, "shape"); + Objects.requireNonNull(settings, "settings"); + + PhysicsStoreThreading.requireWorldThread(spaceRef.getStore(), + "spawn PhysicsStore block body rows"); + if (!spaceRef.isValid()) { + throw new IllegalStateException("Cannot spawn block body batch because the target " + + "PhysicsStore space row is no longer valid: " + spaceId.value()); + } + + BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); + Objects.requireNonNull(builder, "builder").accept(batch); + batch.seal(); + if (batch.isEmpty()) { + return new BlockBodyBatchResult(collectBodies ? new SpawnedBlockBody[0] : null, + 0, + 0L, + 0L); + } + + return spawnBlockBodiesInternal(store, + time, + serverTick, + spaceRef, + spaceId, + blockType, + shape, + mass, + settings, + batch, + collectBodies); + } + + @Nonnull + private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store store, + @Nonnull TimeResource time, + long serverTick, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull BlockBodyBatchBuilder batch, + boolean collectBodies) { + World world = store.getExternalData().getWorld(); List rows = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 1bcdbc92..4d46e0e3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -11,12 +11,12 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; @@ -76,14 +76,19 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound.")); + return CompletableFuture.completedFuture(null); + } BenchmarkLayout layout = BenchmarkLayout.around(playerPos, request.count()); return PhysicsStoreAsync.acceptOnWorldThread(world, PhysicsStoreDiagnostics.bodyCountAsync(world, spaceId), beforeBodies -> spawnBenchmark(ctx, store, world, - resource, + spaceRef, spaceId, request, layout, @@ -93,16 +98,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static void spawnBenchmark(@Nonnull CommandContext ctx, @Nonnull Store store, @Nonnull World world, - @Nonnull PhysicsWorldResource resource, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull BenchmarkRequest request, @Nonnull BenchmarkLayout layout, int beforeBodies) { long serverTick = Math.max(0L, world.getTick()); BenchmarkSpawnTiming timing = switch (request.mode()) { - case RAW -> spawnRaw(world, spaceId, layout, request.count()); + case RAW -> spawnRaw(world, spaceRef, spaceId, layout, request.count()); case ENTITY -> spawnEntities(store, - resource, + spaceRef, spaceId, layout, request.count(), @@ -154,6 +159,7 @@ private BenchmarkRequest parseRequest(@Nonnull CommandContext ctx) { @Nonnull private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull BenchmarkLayout layout, int count) { @@ -161,6 +167,7 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); ExamplePhysicsUtils.BodyRowBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, + spaceRef, spaceId, count, box, @@ -183,7 +190,7 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, @Nonnull private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store store, - @Nonnull PhysicsWorldResource resource, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull BenchmarkLayout layout, int count, @@ -195,6 +202,7 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st ExamplePhysicsUtils.BlockBodyBatchTiming timing = ExamplePhysicsUtils.spawnBlockBodiesMeasured(store, time, serverTick, + spaceRef, spaceId, count, blockType, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index f1dac95b..652bc017 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -146,6 +147,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound.")); + return CompletableFuture.completedFuture(null); + } PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); PhysicsSpaceSettings settings = configureStressRuntime(resource, spaceId, @@ -174,6 +181,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ExamplePhysicsUtils.BlockBodyBatchTiming batchTiming = ExamplePhysicsUtils.spawnBlockBodiesMeasured(store, time, serverTick, + spaceRef, spaceId, count, visualSettings.blockType(), @@ -195,6 +203,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, RigidBodySpawnSettings spawnSettings = detachedSpawnSettings(collisionPolicy); ExamplePhysicsUtils.BodyRowBatchTiming batchTiming = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, + spaceRef, spaceId, count, box, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index 6cab7462..1bdcca52 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -10,6 +10,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -60,6 +61,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } + Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound.")); + return CompletableFuture.completedFuture(null); + } int side = (int) Math.ceil(Math.cbrt(count)); double half = side * SPACING * 0.5; @@ -71,6 +78,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); long totalStartNanos = System.nanoTime(); BodyRowBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, + spaceRef, spaceId, count, box, From 8fe406b1f1e19d836f778c628e9ef799beeea88e Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:00:44 +0200 Subject: [PATCH 229/534] refactor(core): count physics bodies by space ref Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreDiagnostics.java | 26 +++++++++++++++++++ .../stress/StressBenchmarkCommand.java | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index 4a411185..ae883aa6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -39,6 +40,13 @@ public static int bodyCount(@Nonnull Store store, @Nonnull UUID sp return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; } + public static int bodyCount(@Nonnull Store store, + @Nonnull Ref spaceRef) { + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; + } + @Nonnull public static CompletionStage bodyCountAsync(@Nonnull World world, @Nonnull SpaceId spaceId) { @@ -75,6 +83,24 @@ public static CompletionStage bodyCountAsync(@Nonnull Store bodyCount(physics, spaceUuid)); } + @Nonnull + public static CompletionStage bodyCountAsync(@Nonnull World world, + @Nonnull Ref spaceRef) { + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore body count read", + physics -> bodyCount(physics, spaceRef)); + } + + @Nonnull + public static CompletionStage bodyCountAsync(@Nonnull Store store, + @Nonnull Ref spaceRef) { + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore body count read", + physics -> bodyCount(physics, spaceRef)); + } + public static int runtimeJointCount(@Nonnull Store store) { PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 4d46e0e3..d0389113 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -84,7 +84,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } BenchmarkLayout layout = BenchmarkLayout.around(playerPos, request.count()); return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.bodyCountAsync(world, spaceId), + PhysicsStoreDiagnostics.bodyCountAsync(world, spaceRef), beforeBodies -> spawnBenchmark(ctx, store, world, From 52b7b81311574507fb9772d0d477c71e71e89279 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:06:41 +0200 Subject: [PATCH 230/534] refactor(core): resolve solver diagnostics by space ref Signed-off-by: Blovien --- .../internal/commands/SpaceSelection.java | 53 ++++++++++++++- .../settings/SolverSettingsCommand.java | 9 ++- .../PhysicsStoreBackendAccess.java | 11 +++ .../physicsstore/PhysicsStoreDiagnostics.java | 68 +++++++++++++++++++ 4 files changed, 136 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java index 9ef24bcf..63cad2d4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.commands; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; @@ -8,10 +9,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import java.util.Comparator; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -24,7 +27,38 @@ private SpaceSelection() { public static SpaceId resolve(@Nonnull CommandContext context, @Nonnull World world, @Nonnull OptionalArg spaceArg) { - PhysicsSpaceCompatibilityIndexResource compatibility = compatibility(world); + PhysicsSpaceCompatibilityIndexResource compatibility = compatibility(store(world)); + return resolveSpaceId(context, world, spaceArg, compatibility); + } + + @Nullable + public static SelectedSpace resolveStoreSpace(@Nonnull CommandContext context, + @Nonnull World world, + @Nonnull OptionalArg spaceArg) { + Store store = store(world); + PhysicsSpaceCompatibilityIndexResource compatibility = compatibility(store); + SpaceId spaceId = resolveSpaceId(context, world, spaceArg, compatibility); + if (spaceId == null) { + return null; + } + + UUID spaceUuid = compatibility.getSpaceUuid(spaceId); + Ref spaceRef = spaceUuid != null + ? store.getResource(PhysicsIdentityIndexResource.getResourceType()).getByUuid(spaceUuid) + : null; + if (spaceRef == null || spaceRef.getStore() != store || !spaceRef.isValid()) { + context.sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " is not bound in world " + world.getName() + ".")); + return null; + } + return new SelectedSpace(spaceId, spaceRef); + } + + @Nullable + private static SpaceId resolveSpaceId(@Nonnull CommandContext context, + @Nonnull World world, + @Nonnull OptionalArg spaceArg, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility) { if (spaceArg.provided(context)) { int rawSpaceId = spaceArg.get(context); if (rawSpaceId <= 0) { @@ -68,11 +102,26 @@ static SpaceId firstRegisteredSpaceId( } @Nonnull - private static PhysicsSpaceCompatibilityIndexResource compatibility(@Nonnull World world) { + private static Store store(@Nonnull World world) { Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); PhysicsStoreThreading.requireWorldThread(store, "select a PhysicsStore space"); + return store; + } + + @Nonnull + private static PhysicsSpaceCompatibilityIndexResource compatibility( + @Nonnull Store store) { return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()); } + + public record SelectedSpace(@Nonnull SpaceId spaceId, + @Nonnull Ref spaceRef) { + + public SelectedSpace { + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(spaceRef, "spaceRef"); + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 8bc4a318..9baf7a57 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -55,12 +55,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Store store = world.getEntityStore().getStore(); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); - if (spaceId == null) { + SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, + world, + spaceArg); + if (selectedSpace == null) { return CompletableFuture.completedFuture(null); } + SpaceId spaceId = selectedSpace.spaceId(); return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.solverCapabilityAsync(world, spaceId), + PhysicsStoreDiagnostics.solverCapabilityAsync(world, selectedSpace.spaceRef()), summary -> applySettings(ctx, resource, spaceId, summary)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java index 4a473819..5a05ea73 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java @@ -88,6 +88,17 @@ static SpaceContext requireSpace(@Nonnull Store store, @Nonnull UU return space; } + @Nonnull + static SpaceContext requireSpace(@Nonnull Store store, + @Nonnull Ref spaceRef) { + SpaceContext space = space(store, spaceRef); + if (space == null) { + throw new IllegalArgumentException("Physics space ref=" + spaceRef + + " is not registered"); + } + return space; + } + @Nonnull static SpaceSummary summary(@Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull SpaceContext space) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index ae883aa6..0e768bf8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -158,6 +158,22 @@ public static SolverCapabilitySummary solverCapability(@Nonnull Store store, + @Nonnull Ref spaceRef) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.requireSpace(store, Objects.requireNonNull(spaceRef, "spaceRef")); + SpaceId spaceId = compatibility.getSpaceId(space.spaceUuid()); + if (spaceId == null) { + throw new IllegalArgumentException("Physics space ref=" + spaceRef + + " has no compatibility SpaceId"); + } + return solverCapability(spaceId, space); + } + @Nonnull public static CompletionStage solverCapabilityAsync( @Nonnull World world, @@ -188,6 +204,26 @@ public static CompletionStage solverCapabilityAsync( physics -> solverCapability(physics, spaceUuid)); } + @Nonnull + public static CompletionStage solverCapabilityAsync( + @Nonnull World world, + @Nonnull Ref spaceRef) { + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore solver capability read", + physics -> solverCapability(physics, spaceRef)); + } + + @Nonnull + public static CompletionStage solverCapabilityAsync( + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore solver capability read", + physics -> solverCapability(physics, spaceRef)); + } + @Nonnull public static CompletionStage solverCapabilityAsync( @Nonnull Store store, @@ -257,6 +293,19 @@ public static List spaceSummaries(@Nonnull Store sto : List.of(); } + @Nonnull + public static List spaceSummaries(@Nonnull Store store, + @Nonnull Ref spaceRef) { + PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + PhysicsStoreBackendAccess.SpaceContext space = + PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + return space != null && compatibility.getSpaceId(space.spaceUuid()) != null + ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) + : List.of(); + } + @Nonnull public static CompletionStage> spaceSummariesAsync(@Nonnull World world, @Nonnull SpaceId spaceId) { @@ -266,6 +315,25 @@ public static CompletionStage> spaceSummariesAsync(@Nonnull W physics -> spaceSummaries(physics, spaceId)); } + @Nonnull + public static CompletionStage> spaceSummariesAsync(@Nonnull World world, + @Nonnull Ref spaceRef) { + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore space summary read", + physics -> spaceSummaries(physics, spaceRef)); + } + + @Nonnull + public static CompletionStage> spaceSummariesAsync( + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Objects.requireNonNull(spaceRef, "spaceRef"); + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore space summary read", + physics -> spaceSummaries(physics, spaceRef)); + } + @Nonnull public static CompletionStage> spaceSummariesAsync( @Nonnull Store store, From 8d7d220cfe630a212a4d85b71969109860e51a5d Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:07:59 +0200 Subject: [PATCH 231/534] refactor(core): query deleted space by ref Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index ae621fd3..e22e5ef9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -163,20 +163,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, return CompletableFuture.completedFuture(null); } - int rawSpaceId = spaceArg.get(context); - if (rawSpaceId <= 0) { - context.sendMessage(Message.raw("Space id must be a positive integer.")); - return CompletableFuture.completedFuture(null); - } - Store store = world.getEntityStore().getStore(); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = new SpaceId(rawSpaceId); - if (!resource.hasSpace(spaceId)) { - context.sendMessage(Message.raw("No physics space id=" + rawSpaceId - + " exists in world " + world.getName() + ".")); + SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(context, + world, + spaceArg); + if (selectedSpace == null) { return CompletableFuture.completedFuture(null); } + SpaceId spaceId = selectedSpace.spaceId(); /* * Backend-only bodies can be generated by systems such as streaming world collision. @@ -186,12 +181,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, */ int registeredBodies = countRegisteredBodies(resource, spaceId); return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.spaceSummariesAsync(world), + PhysicsStoreDiagnostics.spaceSummariesAsync(world, selectedSpace.spaceRef()), summaries -> deleteIfEmpty(context, world, resource, spaceId, - rawSpaceId, + spaceId.value(), registeredBodies, summaries)); } From c42019c25047395372cc1ab700db7fa02f6ded21 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:15:54 +0200 Subject: [PATCH 232/534] refactor(core): update space settings by ref Signed-off-by: Blovien --- .../settings/SolverSettingsCommand.java | 10 +++-- .../VisualMaterializationSettingsCommand.java | 11 +++-- .../settings/VisualSyncSettingsCommand.java | 11 +++-- .../commands/CollisionLodSettingsCommand.java | 12 ++++-- .../WorldCollisionSettingsCommand.java | 12 ++++-- .../PhysicsStoreSpaceMutations.java | 26 ++++++++++++ .../PhysicsWorldRuntimeResource.java | 42 +++++++++++++++++++ .../resources/PhysicsWorldResource.java | 18 ++++++++ 8 files changed, 123 insertions(+), 19 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 9baf7a57..bcac7a3a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.commands.settings; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; @@ -8,6 +9,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; @@ -64,14 +66,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, SpaceId spaceId = selectedSpace.spaceId(); return PhysicsStoreAsync.acceptOnWorldThread(world, PhysicsStoreDiagnostics.solverCapabilityAsync(world, selectedSpace.spaceRef()), - summary -> applySettings(ctx, resource, spaceId, summary)); + summary -> applySettings(ctx, resource, selectedSpace.spaceRef(), spaceId, summary)); } private void applySettings(@Nonnull CommandContext ctx, @Nonnull PhysicsWorldResource resource, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull SolverCapabilitySummary summary) { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); + PhysicsSpaceSettings settings = new PhysicsSpaceSettings( + resource.getSpaceSettings(spaceRef)); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, summary, settings); return; @@ -109,7 +113,7 @@ private void applySettings(@Nonnull CommandContext ctx, settings.getSolverSettings().setSolverIterations(solverIterations); settings.getSolverSettings().setStabilizationIterations(stabilizationIterations); settings.getSolverSettings().setDynamicSleepTuning(sleepLinearThreshold, sleepAngularThreshold, sleepTime); - resource.setSpaceSettings(spaceId, settings); + resource.setSpaceSettings(spaceRef, settings); sendSummary(ctx, spaceId, summary, settings); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java index 9c4d6725..849f4833 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java @@ -88,13 +88,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); - if (spaceId == null) { + SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, + world, + spaceArg); + if (selectedSpace == null) { return CompletableFuture.completedFuture(null); } + SpaceId spaceId = selectedSpace.spaceId(); PhysicsSpaceSettings settings = new PhysicsSpaceSettings( - resource.getSpaceSettings(spaceId)); + resource.getSpaceSettings(selectedSpace.spaceRef())); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -143,7 +146,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - resource.setSpaceSettings(spaceId, settings); + resource.setSpaceSettings(selectedSpace.spaceRef(), settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java index 40f39e74..c7934960 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java @@ -111,13 +111,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); - if (spaceId == null) { + SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, + world, + spaceArg); + if (selectedSpace == null) { return CompletableFuture.completedFuture(null); } + SpaceId spaceId = selectedSpace.spaceId(); PhysicsSpaceSettings settings = new PhysicsSpaceSettings( - resource.getSpaceSettings(spaceId)); + resource.getSpaceSettings(selectedSpace.spaceRef())); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -256,7 +259,7 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), if (smoothingRateArg.provided(ctx)) { settings.getVisualSyncSettings().setVisualSnapshotSmoothingRate(smoothingRateArg.get(ctx)); } - resource.setSpaceSettings(spaceId, settings); + resource.setSpaceSettings(selectedSpace.spaceRef(), settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java index 8069ea86..ebafc426 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java @@ -71,12 +71,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); - if (spaceId == null) { + SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, + world, + spaceArg); + if (selectedSpace == null) { return CompletableFuture.completedFuture(null); } + SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); + PhysicsSpaceSettings settings = new PhysicsSpaceSettings( + resource.getSpaceSettings(selectedSpace.spaceRef())); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -134,7 +138,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getCollisionLodSettings().setCollisionLodHysteresis(hysteresis); settings.getCollisionLodSettings().setCollisionLodRefreshIntervalTicks(interval); settings.getCollisionLodSettings().setCollisionLodFarSleepEnabled(farSleep); - resource.setSpaceSettings(spaceId, settings); + resource.setSpaceSettings(selectedSpace.spaceRef(), settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java index 63667cd4..d00344cf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java @@ -71,12 +71,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = SpaceSelection.resolve(ctx, world, spaceArg); - if (spaceId == null) { + SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, + world, + spaceArg); + if (selectedSpace == null) { return CompletableFuture.completedFuture(null); } + SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); + PhysicsSpaceSettings settings = new PhysicsSpaceSettings( + resource.getSpaceSettings(selectedSpace.spaceRef())); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -136,7 +140,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getWorldCollisionSettings().setWorldCollisionRadius(playerRadius); settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(bodyRadius); settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(ttl); - resource.setSpaceSettings(spaceId, settings); + resource.setSpaceSettings(selectedSpace.spaceRef(), settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index a123f608..d36e8562 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; @@ -89,6 +90,13 @@ public static void putSpaceSettings(@Nonnull Store store, putSpaceSettings(store, ref, spaceUuid, settings); } + public static void putSpaceSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsSpaceSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, ref); + putSpaceSettings(store, ref, spaceUuid, settings); + } + public static void putSpaceGravity(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3f gravity) { @@ -209,4 +217,22 @@ private static Ref requireSpaceRef(@Nonnull Store st } return ref; } + + @Nonnull + private static UUID requireSpaceUuid(@Nonnull Store store, + @Nonnull Ref ref) { + Objects.requireNonNull(ref, "ref"); + PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); + if (ref.getStore() != store || !ref.isValid()) { + throw new IllegalArgumentException("PhysicsStore space row is not valid: " + ref); + } + if (store.getComponent(ref, SpaceComponent.getComponentType()) == null) { + throw new IllegalArgumentException("PhysicsStore row is not a space row: " + ref); + } + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + if (uuid == null) { + throw new IllegalArgumentException("PhysicsStore space row has no durable UUID: " + ref); + } + return uuid.getUuid(); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 299ff08c..d040629b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -283,6 +283,18 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( if (ref == null || !ref.isValid()) { return null; } + return getPhysicsStoreSpaceSettings(store, ref); + } + + @Nullable + private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( + @Nonnull Store store, + @Nonnull Ref ref) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(ref, "ref"); + if (ref.getStore() != store || !ref.isValid()) { + return null; + } SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); if (space == null) { return null; @@ -1681,6 +1693,23 @@ public PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { return spaceRuntime.getSpaceSettings(spaceId); } + @Nonnull + @Override + public PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef) { + if (!isAuthoritativePhysicsStoreActive()) { + throw new IllegalStateException("Cannot read PhysicsStore space settings by row ref " + + "when authoritative PhysicsStore mode is unavailable"); + } + PhysicsSpaceSettings settings = getPhysicsStoreSpaceSettings( + authoritativePhysicsStore("read physics space settings"), + spaceRef); + if (settings == null) { + throw new IllegalArgumentException("PhysicsStore space ref=" + spaceRef + + " is not registered"); + } + return settings; + } + @Nonnull public PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { return spaceRuntime.getLiveSpaceSettings(spaceId); @@ -1700,6 +1729,19 @@ public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSett runDirectRuntimeMutation("set physics space settings", () -> setSpaceSettingsDirect(spaceId, requested)); } + @Override + public void setSpaceSettings(@Nonnull Ref spaceRef, + @Nonnull PhysicsSpaceSettings settings) { + if (!isAuthoritativePhysicsStoreActive()) { + throw new IllegalStateException("Cannot set PhysicsStore space settings by row ref " + + "when authoritative PhysicsStore mode is unavailable"); + } + PhysicsStoreSpaceMutations.putSpaceSettings( + authoritativePhysicsStore("set physics space settings"), + spaceRef, + settings); + } + @Nonnull @Override public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 0866d8c9..58454c18 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -291,12 +291,30 @@ public abstract PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId @Nonnull public abstract PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId); + /** + * Returns the current settings for a live PhysicsStore space row. + * + *

Prefer this overload when command or gameplay code already resolved the target + * space row.

+ */ + @Nonnull + public abstract PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef); + /** * Applies settings to a registered physics space on the physics owner lane. */ public abstract void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings); + /** + * Applies settings to a live PhysicsStore space row. + * + *

Prefer this overload when command or gameplay code already resolved the target + * space row.

+ */ + public abstract void setSpaceSettings(@Nonnull Ref spaceRef, + @Nonnull PhysicsSpaceSettings settings); + /** * Queues settings replacement for a registered physics space. */ From ffdbd6a9024593e276880a8975f4cc9c2f7dd56f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:22:04 +0200 Subject: [PATCH 233/534] docs(core): rename owner lane wording Signed-off-by: Blovien --- .../hytalemodding/impulse/api/Impulse.java | 4 ++-- .../impulse/api/PhysicsBackend.java | 6 ++--- .../impulse/api/PhysicsBodySnapshot.java | 2 +- .../impulse/api/PhysicsSpace.java | 16 ++++++------- .../api/runtime/PhysicsBackendRuntime.java | 2 +- .../WorldCollisionPerfReportCommand.java | 4 ++-- .../PhysicsWorldRuntimeResource.java | 4 ++-- .../resources/PhysicsWorldSnapshotState.java | 4 ++-- .../body/PhysicsBodyRegistration.java | 2 +- .../joint/PhysicsJointRegistration.java | 2 +- .../view/BenchmarkSpaceStatsView.java | 2 +- .../systems/step/PhysicsStepCountPolicy.java | 2 +- .../core/plugin/events/PhysicsEventFrame.java | 4 ++-- .../core/plugin/events/PhysicsStepEvent.java | 2 +- .../impulse/core/plugin/joint/JointKey.java | 2 +- .../resources/PhysicsWorldResource.java | 24 +++++++++---------- .../settings/PhysicsEventCollectionMode.java | 2 +- .../settings/PhysicsStepSchedulingMode.java | 8 +++---- .../core/plugin/simulation/SpaceSummary.java | 2 +- .../simulation/view/RigidBodyStateView.java | 2 +- .../PublishedPhysicsBodySnapshot.java | 2 +- .../PublishedPhysicsSnapshotFrame.java | 4 ++-- 22 files changed, 51 insertions(+), 51 deletions(-) diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java index 3a03635c..898f1fe1 100644 --- a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java +++ b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java @@ -111,7 +111,7 @@ public static PhysicsBackendRuntime createRuntime(@Nonnull BackendId backendId) * Create a space for the given backend id. * *

This method is safe to call concurrently after the backend is registered. The returned - * space is live backend state and must still be owned by one serialized physics owner lane.

+ * space is live backend state and must still be owned by one serialized backend lane.

*/ @Nonnull @Deprecated(forRemoval = true) @@ -123,7 +123,7 @@ public static PhysicsSpace createSpace(@Nonnull BackendId backendId) { * Create a space for the given backend id and logical space id. * *

This method is safe to call concurrently after the backend is registered. The returned - * space is live backend state and must still be owned by one serialized physics owner lane.

+ * space is live backend state and must still be owned by one serialized backend lane.

*/ @Nonnull @Deprecated(forRemoval = true) diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java index 12ac4acc..caef2ae2 100644 --- a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java +++ b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java @@ -13,10 +13,10 @@ *
  • Backends are expected to be used through {@link Impulse}, which provides * thread-safe one-time initialization.
  • *
  • {@link #createSpace()} and {@link #createSpace(SpaceId)} may be called from multiple - * owner lanes after initialization. Implementations with mutable factory state must + * backend lanes after initialization. Implementations with mutable factory state must * synchronize internally.
  • - *
  • Different spaces may run concurrently on different owner lanes. Each individual - * {@link PhysicsSpace} remains serialized by its own owner lane.
  • + *
  • Different spaces may run concurrently on different backend lanes. Each individual + * {@link PhysicsSpace} remains serialized by its own backend lane.
  • * */ @Deprecated(forRemoval = true) diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java index c7ea3ee5..c1e4afbb 100644 --- a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java +++ b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java @@ -7,7 +7,7 @@ import org.joml.Vector3f; /** - * Immutable copy of body state captured on the physics owner. + * Immutable copy of body state captured from live backend state. * *

    Snapshots deliberately contain shape metadata instead of a live {@link PhysicsBody} handle so * they can be published to world-thread readers and debug systems without escaping backend diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java index 1f1365d0..67de2412 100644 --- a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java +++ b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java @@ -22,11 +22,11 @@ * between implementations. *

      *
    • This type must not be assumed to be thread-safe.
    • - *
    • All mutations, stepping, and live snapshots must happen from a single serialized owner + *
    • All mutations, stepping, and live snapshots must happen from a single serialized backend * lane.
    • - *
    • The owner lane is a logical execution context, not a public Java thread identity. It may + *
    • The backend lane is a logical execution context, not a public Java thread identity. It may * be backed by pooled executor lanes, but it must not execute the same space concurrently.
    • - *
    • If other threads need to interact, queue commands onto the owner lane.
    • + *
    • If other threads need to interact, queue work onto the backend lane.
    • *
    */ @Deprecated(forRemoval = true) @@ -89,7 +89,7 @@ default boolean containsBody(@Nonnull PhysicsBody body) { } /** - * Publishes owner-lane body snapshots for systems that must not repeatedly + * Publishes backend-lane body snapshots for systems that must not repeatedly * read mutable backend bodies. */ default void snapshotBodies(@Nonnull Consumer consumer) { @@ -97,7 +97,7 @@ default void snapshotBodies(@Nonnull Consumer consumer) { } /** - * Publishes owner-lane body snapshots, allowing callers to provide the last + * Publishes backend-lane body snapshots, allowing callers to provide the last * published snapshot so backends can avoid stable sleeping-body refreshes. */ default void snapshotBodies(@Nonnull Function previousSnapshots, @@ -106,7 +106,7 @@ default void snapshotBodies(@Nonnull Function } /** - * Publishes owner-lane body snapshots with the live body available only during the callback. + * Publishes backend-lane body snapshots with the live body available only during the callback. */ default void snapshotBodies(@Nonnull Function previousSnapshots, @Nonnull BiConsumer consumer) { @@ -114,7 +114,7 @@ default void snapshotBodies(@Nonnull Function } /** - * Publishes owner-lane snapshots for a caller-selected subset of bodies. + * Publishes backend-lane snapshots for a caller-selected subset of bodies. * *

    This lets backends batch-read only bodies that higher-level systems actually * need to publish. Callers should pass bodies that currently belong to this space.

    @@ -126,7 +126,7 @@ default void snapshotBodies(@Nonnull Iterable selectedBod } /** - * Publishes owner-lane snapshots for a caller-selected subset of bodies with the live body + * Publishes backend-lane snapshots for a caller-selected subset of bodies with the live body * available only during the callback. */ default void snapshotBodies(@Nonnull Iterable selectedBodies, diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java index a3892883..e341f6b7 100644 --- a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java +++ b/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java @@ -7,7 +7,7 @@ import javax.annotation.Nonnull; /** - * Owner-lane backend runtime port using backend-local numeric ids and primitive payloads. + * Store tick backend runtime port using backend-local numeric ids and primitive payloads. */ public interface PhysicsBackendRuntime { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index 64a55140..19499176 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -91,13 +91,13 @@ private static void sendReport(@Nonnull CommandContext ctx, + " indexCells=" + formatAverage(cumulativeStep.getSpatialIndexCells(), cumulativeStep.getTickSamples()))); ctx.sender().sendMessage(Message.raw("Physics snapshot avg ms/completedStep=" + formatAverageMillis(cumulativeStep.getSnapshotNanos(), cumulativeStep.getTickSamples()))); - ctx.sender().sendMessage(Message.raw("Physics owner step avg queued/run/latency ms=" + ctx.sender().sendMessage(Message.raw("Physics store tick avg queued/run/latency ms=" + formatAverageMillis(cumulativeStep.getOwnerQueuedNanos(), cumulativeStep.getTickSamples()) + "/" + formatAverageMillis(cumulativeStep.getOwnerRunNanos(), cumulativeStep.getTickSamples()) + "/" + formatAverageMillis( cumulativeStep.getOwnerQueuedNanos() + cumulativeStep.getOwnerRunNanos(), cumulativeStep.getTickSamples()) - + " ownerTPS latest/avg=" + formatHertz(latestStep.getOwnerStepIntervalNanos()) + + " storeTPS latest/avg=" + formatHertz(latestStep.getOwnerStepIntervalNanos()) + "/" + formatAverageHertz(cumulativeStep.getOwnerStepIntervalNanos(), cumulativeStep.getOwnerStepRateSamples()) + " maxGapMs=" + formatMillis(cumulativeStep.getMaxOwnerStepIntervalNanos()) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index d040629b..e324368e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1029,7 +1029,7 @@ public PhysicsBodySnapshot captureLiveBodySnapshot(@Nonnull PhysicsBodyRegistrat } /** - * Captures an immutable snapshot frame on the physics owner lane. + * Captures an immutable snapshot frame on the store tick lane. * *

    The generated {@code frameEpoch} and current {@code worldEpoch} govern * publication ordering and stale-frame rejection. {@code stepSequence} and @@ -1387,7 +1387,7 @@ public void disableWorldCollisionLifecycle() { try { runDirectRuntimeMutation("disable world collision lifecycle", this::disableWorldCollisionLifecycleDirect); } catch (RejectedExecutionException ignored) { - // The server can unload the subplugin after a world owner lane has already closed. + // The server can unload the subplugin after the store tick lane has already closed. } catch (RuntimeException exception) { LOGGER.at(Level.WARNING).log("Failed to disable world collision lifecycle: %s", exception.getMessage()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index e328a91e..ccbd13a1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -24,9 +24,9 @@ /** * Snapshot and epoch state for a world physics resource. * - *

    The owner-side store is used while capturing immutable frames on the physics owner. The + *

    The capture-side store is used while capturing immutable frames on the store tick lane. The * reader-side store is only updated when a frame is applied for the current world epoch. Keeping - * both stores and the epoch counters together prevents stale owner frames from repopulating + * both stores and the epoch counters together prevents stale capture frames from repopulating * world-thread snapshots after topology changes.

    */ public final class PhysicsWorldSnapshotState { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java index 576e1a68..532ff7a1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java @@ -8,7 +8,7 @@ import javax.annotation.Nonnull; /** - * Owner-lane registration for a stable body key and backend-local body handle. + * Store tick registration for a stable body key and backend-local body handle. */ public record PhysicsBodyRegistration(@Nonnull RigidBodyKey bodyKey, @Nonnull BackendBodyHandle backendBodyHandle, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java index e0606c26..43aad714 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java @@ -8,7 +8,7 @@ import javax.annotation.Nonnull; /** - * Owner-lane registration for a stable joint key and backend-local joint handle. + * Store tick registration for a stable joint key and backend-local joint handle. */ public record PhysicsJointRegistration(@Nonnull JointKey jointKey, @Nonnull BackendJointHandle backendJointHandle, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/BenchmarkSpaceStatsView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/BenchmarkSpaceStatsView.java index a2f6ab3d..fb711d11 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/BenchmarkSpaceStatsView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/BenchmarkSpaceStatsView.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.internal.simulation.view; /** - * Copied owner-lane counters used by stress and fall-envelope diagnostics. + * Copied store tick lane counters used by stress and fall-envelope diagnostics. */ public record BenchmarkSpaceStatsView(int bodies, int dynamicBodies, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepCountPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepCountPolicy.java index 61db52be..57d59cff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepCountPolicy.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepCountPolicy.java @@ -9,7 +9,7 @@ * *

    Fixed-style modes use the configured count directly. Adaptive-style modes * treat it as a minimum and raise the count when the tick dt exceeds the - * configured max substep dt. The owner step command layers body-risk + * configured max substep dt. The store tick step pipeline layers body-risk * refinement on top for {@link PhysicsStepMode#ADAPTIVE}.

    */ public final class PhysicsStepCountPolicy { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java index c172a679..686fb131 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java @@ -6,9 +6,9 @@ import javax.annotation.Nullable; /** - * Immutable value frame of physics-owner events. + * Immutable value frame of PhysicsStore events. * - *

    Event frames describe owner-lane outcomes. They are distinct from command + *

    Event frames describe store tick lane outcomes. They are distinct from command * contexts, backend handles, and published body snapshots.

    * *

    The runtime keeps only the latest frame. This is useful for diagnostics, latency diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java index e3682329..d4b48fd3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java @@ -5,7 +5,7 @@ import javax.annotation.Nonnull; /** - * Value-only event for a physics owner step that captured a snapshot frame. + * Value-only event for a store tick step that captured a snapshot frame. * * @param stepSequence Impulse step-scheduler sequence copied into the captured snapshot * @param serverTick Hytale server tick copied into the captured snapshot diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java index 35042956..2a15f92a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java @@ -9,7 +9,7 @@ /** * Stable Impulse-side identity for a physics joint. * - *

    Backend {@code PhysicsJoint} handles are live owner-lane objects. This id is the handle + *

    Backend {@code PhysicsJoint} handles are live backend objects. This id is the identity * component state and plugin-facing lifecycle code should retain.

    */ public final class JointKey { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 58454c18..ae0fb06a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -43,7 +43,7 @@ * *

    This facade does not directly return live backend spaces or bodies. Gameplay code should use * PhysicsStore rows for authoring, copied snapshots for body state, and explicit PhysicsStore - * diagnostics/raycast helpers for owner-lane backend reads.

    + * diagnostics/raycast helpers for store tick lane backend reads.

    */ public abstract class PhysicsWorldResource implements Resource { @@ -51,9 +51,9 @@ protected PhysicsWorldResource() { } /** - * Returns the latest value-only physics owner event frame. + * Returns the latest value-only physics store event frame. * - *

    Event frames describe owner-lane outcomes. They do not expose live + *

    Event frames describe store tick lane outcomes. They do not expose live * backend handles and do not imply that command completion has been * included in a captured or reader-applied body snapshot.

    */ @@ -71,7 +71,7 @@ protected PhysicsWorldResource() { public abstract PhysicsWorldSettings getWorldSettings(); /** - * Applies world-level simulation settings on the physics owner lane. + * Applies world-level simulation settings on the store tick lane. */ public abstract void setWorldSettings(@Nonnull PhysicsWorldSettings settings); @@ -85,7 +85,7 @@ public abstract PhysicsMutationHandle setWorldSettingsAsync( /** * Creates a physics space using default settings and returns its id. * - *

    Creation is serialized through this world's logical physics owner lane. Callers must not + *

    Creation is serialized through this world's logical store tick lane. Callers must not * infer a stable Java thread identity from the synchronous return path.

    */ @Nonnull @@ -104,8 +104,8 @@ public abstract SpaceId createSpace(@Nonnull BackendId backendId, /** * Creates a physics space with generated logical id and supplied settings. * - *

    The live backend space is created inside the serialized owner lane. Use the async variant - * when the caller should not block on owner-lane execution.

    + *

    The live backend space is created inside the serialized store tick lane. Use the async + * variant when the caller should not block on store tick execution.

    */ @Nonnull public abstract SpaceId createSpace(@Nonnull BackendId backendId, @@ -116,7 +116,7 @@ public abstract SpaceId createSpace(@Nonnull BackendId backendId, * Creates a physics space with an explicit logical id and supplied settings. * *

    The explicit id is reserved by the caller, but live backend creation still runs inside the - * serialized owner lane.

    + * serialized store tick lane.

    */ @Nonnull public abstract SpaceId createSpace(@Nonnull BackendId backendId, @@ -127,7 +127,7 @@ public abstract SpaceId createSpace(@Nonnull BackendId backendId, /** * Queues physics-space creation and returns the reserved generated space id. * - *

    The returned mutation handle completes when the owner lane creates the live backend + *

    The returned mutation handle completes when the store tick lane creates the live backend * space, not when a later snapshot or ECS reader has consumed any resulting state.

    */ @Nonnull @@ -140,7 +140,7 @@ public abstract PhysicsMutationHandle createSpaceAsync( * Queues physics-space creation and returns the requested explicit space id. * *

    Different worlds may queue work concurrently, but this world's spaces remain serialized by - * its owner lane.

    + * its store tick lane.

    */ @Nonnull public abstract PhysicsMutationHandle createSpaceAsync( @@ -177,7 +177,7 @@ public abstract PhysicsMutationHandle createSpaceAsync( /** * Returns the latest published snapshot for a body. * - *

    The legacy runtime may capture a copied live snapshot on the physics owner when the body + *

    The legacy runtime may capture a copied live snapshot from live backend state when the body * is registered but missing from the published frame. Authoritative PhysicsStore mode reads * only the copied {@code PhysicsSnapshotResource} frame and does not synchronously touch the * live backend.

    @@ -301,7 +301,7 @@ public abstract PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId public abstract PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef); /** - * Applies settings to a registered physics space on the physics owner lane. + * Applies settings to a registered physics space on the store tick lane. */ public abstract void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsEventCollectionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsEventCollectionMode.java index eaef2a16..609d7ee0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsEventCollectionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsEventCollectionMode.java @@ -4,7 +4,7 @@ import javax.annotation.Nonnull; /** - * Controls which backend physics events are collected during owner-lane steps. + * Controls which backend physics events are collected during store tick steps. */ public enum PhysicsEventCollectionMode { /** diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java index 275d20ac..7267ca3a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java @@ -5,16 +5,16 @@ /** * Controls how the world-level scheduler handles elapsed {@code dt} while a - * previous owner step is still unpublished. + * previous store tick step is still unpublished. */ public enum PhysicsStepSchedulingMode { /** - * Pending owner-lane steps do not add their {@code dt} to the next accepted step. + * Pending store tick steps do not add their {@code dt} to the next accepted step. */ DROP_PENDING_DT("drop_pending_dt"), /** - * Pending owner-lane steps accumulate elapsed {@code dt}; the next accepted step + * Pending store tick steps accumulate elapsed {@code dt}; the next accepted step * catches up once, bounded by the scheduler's hard cap. */ ACCUMULATE_PENDING_DT("accumulate_pending_dt"); @@ -34,7 +34,7 @@ public String getSerializedName() { @Nonnull public String describePendingStepBehavior() { return switch (this) { - case DROP_PENDING_DT -> "drop dt while an owner step is pending"; + case DROP_PENDING_DT -> "drop dt while a store tick step is pending"; case ACCUMULATE_PENDING_DT -> "accumulate pending dt for one capped catch-up step"; }; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java index 2f83f17f..df67b75a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java @@ -6,7 +6,7 @@ import javax.annotation.Nonnull; /** - * Copied owner-lane space diagnostics. + * Copied store tick lane space diagnostics. */ public record SpaceSummary(@Nonnull SpaceId spaceId, @Nonnull BackendId backendId, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java index 64f94cd3..6b82418c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java @@ -8,7 +8,7 @@ import javax.annotation.Nonnull; /** - * Copied rigid body state returned by owner-lane queries. + * Copied rigid body state returned by store tick lane queries. * *

    This value is a live-state query result, not a published snapshot frame entry. Use snapshot * APIs when reader-side systems need frame-coherent body data.

    diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java index 1b9bf6bd..542ef182 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java @@ -18,7 +18,7 @@ * Immutable body state published as part of an async snapshot frame. * *

    This type deliberately carries an Impulse body key instead of backend - * body handles so published frames can be read away from the owner lane.

    + * body handles so published frames can be read away from the store tick lane.

    */ public final class PublishedPhysicsBodySnapshot implements PublishedPhysicsBodySnapshotCursor { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java index 1c3ace9a..faeb1174 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java @@ -32,13 +32,13 @@ public enum Status { private final long worldEpoch; /** - * Impulse step-scheduler sequence assigned to the owner step + * Impulse step-scheduler sequence assigned to the store tick step * not a Hytale tick and not guaranteed contiguous in published frames */ private final long stepSequence; /** - * Hytale world tick observed when the owner step was scheduled. + * Hytale world tick observed when the store tick step was scheduled. * This is not a physics step counter and may diverge from {@code stepSequence} under paused * worlds, backpressure, or future multi-rate scheduling */ From 7233cea12fa79a66917ca9f33cb87785ff25c37e Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:25:26 +0200 Subject: [PATCH 234/534] refactor(core): remove pending body creation tracker Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 13 ---- .../body/PhysicsBodyCreationTracker.java | 61 ------------------- .../resources/body/PhysicsBodyRuntime.java | 20 ------ 3 files changed, 94 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index e324368e..4c9dfa6c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2061,19 +2061,6 @@ public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, return jointRegistry.findJointBetween(spaceId, bodyA, bodyB); } - public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - return bodyRuntime.isBodyCreationPending(bodyKey); - } - - public boolean isBodyCreationPending(@Nonnull UUID bodyUuid) { - return bodyRuntime.isBodyCreationPending(bodyUuid); - } - - public boolean hasPublishedOrPendingBodyRegistration(@Nonnull RigidBodyKey bodyKey) { - return getBodyRegistrationView(bodyKey) != null - || isBodyCreationPending(bodyKey); - } - @Nonnull public PhysicsBodyRegistration requireBodyRegistration(@Nonnull RigidBodyKey bodyKey) { PhysicsBodyRegistration registration = getRegistration(bodyKey); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java deleted file mode 100644 index 4241de56..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyCreationTracker.java +++ /dev/null @@ -1,61 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Tracks body UUIDs reserved by async body registrations until publication catches up. - */ -public final class PhysicsBodyCreationTracker { - - private final Object2IntOpenHashMap pendingBodyCreations = - new Object2IntOpenHashMap<>(); - - public void markPending(@Nonnull RigidBodyKey bodyKey) { - markPending(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - - public void markPending(@Nonnull UUID bodyUuid) { - synchronized (pendingBodyCreations) { - pendingBodyCreations.addTo(Objects.requireNonNull(bodyUuid, "bodyUuid"), 1); - } - } - - public void clearPending(@Nonnull RigidBodyKey bodyKey) { - clearPending(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - - public void clearPending(@Nonnull UUID bodyUuid) { - synchronized (pendingBodyCreations) { - clearPendingDirect(Objects.requireNonNull(bodyUuid, "bodyUuid")); - } - } - - public boolean isPending(@Nonnull RigidBodyKey bodyKey) { - return isPending(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - - public boolean isPending(@Nonnull UUID bodyUuid) { - synchronized (pendingBodyCreations) { - return pendingBodyCreations.containsKey(Objects.requireNonNull(bodyUuid, "bodyUuid")); - } - } - - public void clear() { - synchronized (pendingBodyCreations) { - pendingBodyCreations.clear(); - } - } - - private void clearPendingDirect(UUID bodyUuid) { - int count = pendingBodyCreations.getInt(bodyUuid); - if (count <= 1) { - pendingBodyCreations.removeInt(bodyUuid); - } else { - pendingBodyCreations.put(bodyUuid, count - 1); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 40cdd99e..b6d08113 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.ArrayList; -import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -42,8 +41,6 @@ public final class PhysicsBodyRuntime { private final PhysicsWorldLifecycleState lifecycleState; @Nonnull private final Runnable worldChangedMarker; - @Nonnull - private final PhysicsBodyCreationTracker creationTracker = new PhysicsBodyCreationTracker(); public PhysicsBodyRuntime(@Nonnull PhysicsSpaceRuntime spaceRuntime, @Nonnull PhysicsBodyRegistry bodyRegistry, @@ -65,22 +62,6 @@ public PhysicsBodyRuntime(@Nonnull PhysicsSpaceRuntime spaceRuntime, this.worldChangedMarker = worldChangedMarker; } - public void markBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - creationTracker.markPending(bodyKey); - } - - public void clearBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - creationTracker.clearPending(bodyKey); - } - - public boolean isBodyCreationPending(@Nonnull RigidBodyKey bodyKey) { - return creationTracker.isPending(bodyKey); - } - - public boolean isBodyCreationPending(@Nonnull UUID bodyUuid) { - return creationTracker.isPending(bodyUuid); - } - @Nonnull public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @@ -152,7 +133,6 @@ public void clearBodyStateWithoutMarkingWorldChanged() { chunkRuntime.clear(); visualRuntime.clear(); lifecycleState.clearBodySnapshots(); - creationTracker.clear(); } public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { From 78f1afa32ffedcb46fb80f007f1ff3c4a1644b9e Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:29:51 +0200 Subject: [PATCH 235/534] refactor(core): destroy clean bodies by uuid Signed-off-by: Blovien --- .../impulse/core/internal/commands/CleanCommand.java | 12 ++++-------- .../resources/PhysicsWorldRuntimeResource.java | 12 ++++++++++++ .../core/plugin/resources/PhysicsWorldResource.java | 9 +++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 6d3b9ab4..10dc45fe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -21,7 +21,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -199,8 +198,8 @@ private void cleanWithinRadius(@Nonnull CommandContext context, } int removedBodies = 0; - for (RigidBodyKey bodyKey : selectedBodies.bodyKeys()) { - resource.destroyBody(bodyKey); + for (UUID bodyUuid : selectedBodies.bodyUuids()) { + resource.destroyBody(bodyUuid); removedBodies++; } @@ -226,7 +225,6 @@ private static Vector3d playerPosition(@Nonnull CommandContext context, private static SelectedBodies selectBodiesNear(@Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d center, float radius) { - Set bodyKeys = new ObjectOpenHashSet<>(); Set bodyUuids = new ObjectOpenHashSet<>(); Vector3f centerF = new Vector3f((float) center.x, (float) center.y, (float) center.z); for (SpaceId spaceId : resource.getSpaceIds()) { @@ -234,11 +232,10 @@ private static SelectedBodies selectBodiesNear(@Nonnull PhysicsWorldRuntimeResou centerF, radius, (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> { - bodyKeys.add(bodyKey); bodyUuids.add(bodyKey.value()); }); } - return new SelectedBodies(bodyKeys, bodyUuids); + return new SelectedBodies(bodyUuids); } private static boolean controlSessionSelected( @@ -280,8 +277,7 @@ private static boolean containsBody(@Nonnull Set bodyUuids, return bodyUuid != null && bodyUuids.contains(bodyUuid); } - private record SelectedBodies(@Nonnull Set bodyKeys, - @Nonnull Set bodyUuids) { + private record SelectedBodies(@Nonnull Set bodyUuids) { } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 4c9dfa6c..7d14d2d8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1830,6 +1830,18 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey) { destroyBody(bodyKey, true); } + @Override + public void destroyBody(@Nonnull UUID bodyUuid) { + if (isAuthoritativePhysicsStoreActive()) { + PhysicsStoreTopologyMutations.destroyBody( + authoritativePhysicsStore("destroy physics body"), + bodyUuid); + return; + } + requireLegacyMutationAllowed("destroy physics body"); + destroyBody(RigidBodyKey.of(bodyUuid), true); + } + @Nonnull @Override public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index ae0fb06a..3b15fd19 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -327,6 +327,15 @@ public abstract PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull Sp */ public abstract void destroyBody(@Nonnull RigidBodyKey bodyKey); + /** + * Destroys a registered body by durable body UUID. + * + *

    Prefer this overload when the caller is crossing a durable identity boundary.

    + */ + public void destroyBody(@Nonnull UUID bodyUuid) { + destroyBody(RigidBodyKey.of(bodyUuid)); + } + /** * Queues destruction of a registered body by stable key. */ From a7814126dc7bdef5e53685ee8928566783e90707 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:33:06 +0200 Subject: [PATCH 236/534] refactor(core): remove forced ccd key cache Signed-off-by: Blovien --- .../PhysicsChunkBoundaryRuntime.java | 59 ------------------- .../PhysicsWorldRuntimeResource.java | 25 -------- 2 files changed, 84 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java index 1987a9c2..97e293c3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java @@ -8,13 +8,10 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -27,13 +24,10 @@ */ public final class PhysicsChunkBoundaryRuntime { - private final Set forcedContinuousCollisionBodyKeys = new ObjectOpenHashSet<>(); private final Map chunkBoundarySafeStates = new Object2ObjectOpenHashMap<>(); private final Map chunkBoundaryPauseStates = new Object2ObjectOpenHashMap<>(); - private final Int2ObjectOpenHashMap> forcedContinuousCollisionBodyRefsByRowIndex = - new Int2ObjectOpenHashMap<>(); private final Int2ObjectOpenHashMap> chunkBoundarySafeStatesByRowIndex = new Int2ObjectOpenHashMap<>(); private final Int2ObjectOpenHashMap> chunkBoundaryPauseStatesByRowIndex = @@ -170,72 +164,19 @@ public Collection> getChunkBoundaryPausedBodyRefs() { return liveRefs(chunkBoundaryPauseStatesByRowIndex); } - public void markContinuousCollisionForced(@Nonnull RigidBodyKey bodyKey) { - forcedContinuousCollisionBodyKeys.add(bodyKey); - } - - public void markContinuousCollisionForced(@Nonnull Ref bodyRef) { - forcedContinuousCollisionBodyRefsByRowIndex.put(rowIndex(bodyRef), bodyRef); - } - - @Nonnull - public Collection getForcedContinuousCollisionBodyKeys() { - return new ArrayList<>(forcedContinuousCollisionBodyKeys); - } - - @Nonnull - public Collection> getForcedContinuousCollisionBodyRefs() { - ArrayList> refs = new ArrayList<>(); - forcedContinuousCollisionBodyRefsByRowIndex.values() - .removeIf(ref -> ref == null || !ref.isValid()); - refs.addAll(forcedContinuousCollisionBodyRefsByRowIndex.values()); - return refs; - } - - public boolean hasForcedContinuousCollisionBodies() { - forcedContinuousCollisionBodyRefsByRowIndex.values() - .removeIf(ref -> ref == null || !ref.isValid()); - return !forcedContinuousCollisionBodyKeys.isEmpty() - || !forcedContinuousCollisionBodyRefsByRowIndex.isEmpty(); - } - - public void forEachForcedContinuousCollisionBody(@Nonnull Consumer consumer) { - forcedContinuousCollisionBodyKeys.forEach(consumer); - } - - public void forEachForcedContinuousCollisionBodyRef( - @Nonnull Consumer> consumer) { - for (Ref ref : getForcedContinuousCollisionBodyRefs()) { - consumer.accept(ref); - } - } - - public void clearForcedContinuousCollisionBodies() { - forcedContinuousCollisionBodyKeys.clear(); - forcedContinuousCollisionBodyRefsByRowIndex.clear(); - } - public void clearBody(@Nonnull RigidBodyKey bodyKey) { - forcedContinuousCollisionBodyKeys.remove(bodyKey); chunkBoundarySafeStates.remove(bodyKey); chunkBoundaryPauseStates.remove(bodyKey); } public void clearBody(@Nonnull Ref bodyRef) { - Ref forcedRef = - forcedContinuousCollisionBodyRefsByRowIndex.get(rowIndex(bodyRef)); - if (forcedRef != null && sameRef(forcedRef, bodyRef)) { - forcedContinuousCollisionBodyRefsByRowIndex.remove(bodyRef.getIndex()); - } removeRowState(chunkBoundarySafeStatesByRowIndex, bodyRef); removeRowState(chunkBoundaryPauseStatesByRowIndex, bodyRef); } public void clear() { - forcedContinuousCollisionBodyKeys.clear(); chunkBoundarySafeStates.clear(); chunkBoundaryPauseStates.clear(); - forcedContinuousCollisionBodyRefsByRowIndex.clear(); chunkBoundarySafeStatesByRowIndex.clear(); chunkBoundaryPauseStatesByRowIndex.clear(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 7d14d2d8..26553a09 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2606,31 +2606,6 @@ private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { visualRuntime.clearBodyRuntimeState(bodyKey.value(), bodyRef); } - public void markContinuousCollisionForced(@Nonnull RigidBodyKey bodyKey) { - chunkRuntime.markContinuousCollisionForced(bodyKey); - } - - public void markContinuousCollisionForced(@Nonnull Ref bodyRef) { - chunkRuntime.markContinuousCollisionForced(bodyRef); - } - - @Nonnull - public Collection getForcedContinuousCollisionBodyKeys() { - return chunkRuntime.getForcedContinuousCollisionBodyKeys(); - } - - public boolean hasForcedContinuousCollisionBodies() { - return chunkRuntime.hasForcedContinuousCollisionBodies(); - } - - public void forEachForcedContinuousCollisionBody(@Nonnull Consumer consumer) { - chunkRuntime.forEachForcedContinuousCollisionBody(consumer); - } - - public void clearForcedContinuousCollisionBodies() { - chunkRuntime.clearForcedContinuousCollisionBodies(); - } - public void copyFrom(@Nonnull PhysicsWorldResource other) { runDirectRuntimeMutation("copy physics world resource", () -> copyFromDirect(other)); } From bea867de87218dfb3ba06e0bec81f9c53adb534d Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:38:33 +0200 Subject: [PATCH 237/534] refactor(core): remove chunk boundary runtime state Signed-off-by: Blovien --- .../PhysicsChunkBoundaryRuntime.java | 371 ------------------ .../PhysicsWorldRuntimeResource.java | 139 ------- .../resources/body/PhysicsBodyRuntime.java | 7 - 3 files changed, 517 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java deleted file mode 100644 index 97e293c3..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsChunkBoundaryRuntime.java +++ /dev/null @@ -1,371 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.function.Supplier; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import lombok.Getter; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Chunk-boundary and forced-CCD runtime state for registered bodies. - */ -public final class PhysicsChunkBoundaryRuntime { - - private final Map chunkBoundarySafeStates = - new Object2ObjectOpenHashMap<>(); - private final Map chunkBoundaryPauseStates = - new Object2ObjectOpenHashMap<>(); - private final Int2ObjectOpenHashMap> chunkBoundarySafeStatesByRowIndex = - new Int2ObjectOpenHashMap<>(); - private final Int2ObjectOpenHashMap> chunkBoundaryPauseStatesByRowIndex = - new Int2ObjectOpenHashMap<>(); - - public void updateChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - ChunkBoundarySafeState state = chunkBoundarySafeStates.computeIfAbsent(bodyKey, - ignored -> new ChunkBoundarySafeState()); - state.set(position, rotation); - } - - public void updateChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot) { - ChunkBoundarySafeState state = chunkBoundarySafeStates.computeIfAbsent(bodyKey, - ignored -> new ChunkBoundarySafeState()); - state.set(snapshot); - } - - public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - ChunkBoundarySafeState state = rowState(chunkBoundarySafeStatesByRowIndex, - bodyRef, - ChunkBoundarySafeState::new); - state.set(position, rotation); - } - - public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot) { - ChunkBoundarySafeState state = rowState(chunkBoundarySafeStatesByRowIndex, - bodyRef, - ChunkBoundarySafeState::new); - state.set(snapshot); - } - - @Nullable - public ChunkBoundarySafeState getChunkBoundarySafeState( - @Nonnull RigidBodyKey bodyKey) { - return chunkBoundarySafeStates.get(bodyKey); - } - - @Nullable - public ChunkBoundarySafeState getChunkBoundarySafeState( - @Nonnull Ref bodyRef) { - return getRowState(chunkBoundarySafeStatesByRowIndex, bodyRef); - } - - public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, - long targetChunkIndex, - @Nonnull PhysicsBodyType originalBodyType, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - ChunkBoundaryPauseState state = chunkBoundaryPauseStates.computeIfAbsent(bodyKey, - ignored -> new ChunkBoundaryPauseState()); - state.set(targetChunkIndex, originalBodyType, linearVelocity, angularVelocity); - } - - public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, - long targetChunkIndex, - @Nonnull long[] targetChunkIndices, - @Nonnull PhysicsBodySnapshot snapshot) { - ChunkBoundaryPauseState state = chunkBoundaryPauseStates.computeIfAbsent(bodyKey, - ignored -> new ChunkBoundaryPauseState()); - state.set(targetChunkIndex, targetChunkIndices, snapshot); - } - - public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, - long targetChunkIndex, - @Nonnull PhysicsBodySnapshot snapshot) { - ChunkBoundaryPauseState state = chunkBoundaryPauseStates.computeIfAbsent(bodyKey, - ignored -> new ChunkBoundaryPauseState()); - state.set(targetChunkIndex, snapshot); - } - - public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, - long targetChunkIndex, - @Nonnull PhysicsBodyType originalBodyType, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - ChunkBoundaryPauseState state = rowState(chunkBoundaryPauseStatesByRowIndex, - bodyRef, - ChunkBoundaryPauseState::new); - state.set(targetChunkIndex, originalBodyType, linearVelocity, angularVelocity); - } - - public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, - long targetChunkIndex, - @Nonnull long[] targetChunkIndices, - @Nonnull PhysicsBodySnapshot snapshot) { - ChunkBoundaryPauseState state = rowState(chunkBoundaryPauseStatesByRowIndex, - bodyRef, - ChunkBoundaryPauseState::new); - state.set(targetChunkIndex, targetChunkIndices, snapshot); - } - - public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, - long targetChunkIndex, - @Nonnull PhysicsBodySnapshot snapshot) { - ChunkBoundaryPauseState state = rowState(chunkBoundaryPauseStatesByRowIndex, - bodyRef, - ChunkBoundaryPauseState::new); - state.set(targetChunkIndex, snapshot); - } - - @Nullable - public ChunkBoundaryPauseState getChunkBoundaryPauseState( - @Nonnull RigidBodyKey bodyKey) { - return chunkBoundaryPauseStates.get(bodyKey); - } - - @Nullable - public ChunkBoundaryPauseState getChunkBoundaryPauseState( - @Nonnull Ref bodyRef) { - return getRowState(chunkBoundaryPauseStatesByRowIndex, bodyRef); - } - - public void clearChunkBoundaryPauseState(@Nonnull RigidBodyKey bodyKey) { - chunkBoundaryPauseStates.remove(bodyKey); - } - - public void clearChunkBoundaryPauseState(@Nonnull Ref bodyRef) { - removeRowState(chunkBoundaryPauseStatesByRowIndex, bodyRef); - } - - @Nonnull - public Collection getChunkBoundaryPausedBodyKeys() { - return new ArrayList<>(chunkBoundaryPauseStates.keySet()); - } - - @Nonnull - public Collection> getChunkBoundaryPausedBodyRefs() { - return liveRefs(chunkBoundaryPauseStatesByRowIndex); - } - - public void clearBody(@Nonnull RigidBodyKey bodyKey) { - chunkBoundarySafeStates.remove(bodyKey); - chunkBoundaryPauseStates.remove(bodyKey); - } - - public void clearBody(@Nonnull Ref bodyRef) { - removeRowState(chunkBoundarySafeStatesByRowIndex, bodyRef); - removeRowState(chunkBoundaryPauseStatesByRowIndex, bodyRef); - } - - public void clear() { - chunkBoundarySafeStates.clear(); - chunkBoundaryPauseStates.clear(); - chunkBoundarySafeStatesByRowIndex.clear(); - chunkBoundaryPauseStatesByRowIndex.clear(); - } - - public void clearChunkBoundaryStates() { - chunkBoundarySafeStates.clear(); - chunkBoundaryPauseStates.clear(); - chunkBoundarySafeStatesByRowIndex.clear(); - chunkBoundaryPauseStatesByRowIndex.clear(); - } - - @Nonnull - private static T rowState(@Nonnull Int2ObjectOpenHashMap> states, - @Nonnull Ref bodyRef, - @Nonnull Supplier factory) { - int rowIndex = rowIndex(bodyRef); - RowState row = states.get(rowIndex); - if (row == null || !sameRef(row.bodyRef(), bodyRef)) { - T state = factory.get(); - states.put(rowIndex, new RowState<>(bodyRef, state)); - return state; - } - return row.state(); - } - - @Nullable - private static T getRowState(@Nonnull Int2ObjectOpenHashMap> states, - @Nonnull Ref bodyRef) { - RowState row = states.get(rowIndex(bodyRef)); - return row != null && sameRef(row.bodyRef(), bodyRef) ? row.state() : null; - } - - private static void removeRowState(@Nonnull Int2ObjectOpenHashMap> states, - @Nonnull Ref bodyRef) { - RowState row = states.get(rowIndex(bodyRef)); - if (row != null && sameRef(row.bodyRef(), bodyRef)) { - states.remove(bodyRef.getIndex()); - } - } - - @Nonnull - private static Collection> liveRefs( - @Nonnull Int2ObjectOpenHashMap> states) { - ArrayList> refs = new ArrayList<>(); - ArrayList staleRows = new ArrayList<>(); - for (Int2ObjectMap.Entry> entry : states.int2ObjectEntrySet()) { - Ref ref = entry.getValue().bodyRef(); - if (ref != null && ref.isValid()) { - refs.add(ref); - } else { - staleRows.add(entry.getIntKey()); - } - } - for (int row : staleRows) { - states.remove(row); - } - return refs; - } - - private static int rowIndex(@Nonnull Ref bodyRef) { - return Objects.requireNonNull(bodyRef, "bodyRef").getIndex(); - } - - private static boolean sameRef(@Nonnull Ref first, - @Nonnull Ref second) { - return first.getIndex() == second.getIndex() - && first.getStore() == second.getStore(); - } - - private record RowState(@Nonnull Ref bodyRef, - @Nonnull T state) { - - private RowState { - Objects.requireNonNull(bodyRef, "bodyRef"); - Objects.requireNonNull(state, "state"); - } - } - - public static final class ChunkBoundaryPauseState { - - @Getter - private long targetChunkIndex; - @Nonnull - private long[] targetChunkIndices = new long[0]; - @Nonnull - private PhysicsBodyType originalBodyType = PhysicsBodyType.DYNAMIC; - @Nonnull - private final Vector3f linearVelocity = new Vector3f(); - @Nonnull - private final Vector3f angularVelocity = new Vector3f(); - - public void set(long targetChunkIndex, - @Nonnull PhysicsBodyType originalBodyType, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - this.targetChunkIndex = targetChunkIndex; - this.targetChunkIndices = new long[] {targetChunkIndex}; - this.originalBodyType = originalBodyType; - this.linearVelocity.set(linearVelocity); - this.angularVelocity.set(angularVelocity); - } - - public void set(long targetChunkIndex, - @Nonnull long[] targetChunkIndices, - @Nonnull PhysicsBodySnapshot snapshot) { - this.targetChunkIndex = targetChunkIndex; - this.targetChunkIndices = copyTargetChunkIndices(targetChunkIndex, targetChunkIndices); - this.originalBodyType = snapshot.bodyType(); - snapshot.copyLinearVelocityTo(this.linearVelocity); - snapshot.copyAngularVelocityTo(this.angularVelocity); - } - - public void set(long targetChunkIndex, @Nonnull PhysicsBodySnapshot snapshot) { - this.targetChunkIndex = targetChunkIndex; - this.targetChunkIndices = new long[] {targetChunkIndex}; - this.originalBodyType = snapshot.bodyType(); - snapshot.copyLinearVelocityTo(this.linearVelocity); - snapshot.copyAngularVelocityTo(this.angularVelocity); - } - - @Nonnull - public PhysicsBodyType getOriginalBodyType() { - return originalBodyType; - } - - @Nonnull - public Vector3f getLinearVelocity() { - return linearVelocity; - } - - @Nonnull - public Vector3f getAngularVelocity() { - return angularVelocity; - } - - @Nonnull - public long[] getTargetChunkIndices() { - return targetChunkIndices.clone(); - } - - @Nonnull - private static long[] copyTargetChunkIndices(long targetChunkIndex, - @Nonnull long[] targetChunkIndices) { - if (targetChunkIndices.length == 0) { - return new long[] {targetChunkIndex}; - } - long[] copy = targetChunkIndices.clone(); - if (copy[0] != targetChunkIndex) { - int targetIndex = -1; - for (int index = 1; index < copy.length; index++) { - if (copy[index] == targetChunkIndex) { - targetIndex = index; - break; - } - } - if (targetIndex >= 0) { - copy[targetIndex] = copy[0]; - } - copy[0] = targetChunkIndex; - } - return copy; - } - } - - public static final class ChunkBoundarySafeState { - - @Nonnull - private final Vector3f position = new Vector3f(); - @Nonnull - private final Quaternionf rotation = new Quaternionf(); - - public void set(@Nonnull Vector3f position, @Nonnull Quaternionf rotation) { - this.position.set(position); - this.rotation.set(rotation); - } - - public void set(@Nonnull PhysicsBodySnapshot snapshot) { - snapshot.copyPositionTo(this.position); - snapshot.copyRotationTo(this.rotation); - } - - @Nonnull - public Vector3f getPosition() { - return position; - } - - @Nonnull - public Quaternionf getRotation() { - return rotation; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 26553a09..18d33721 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -37,9 +37,6 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotRefVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundarySafeState; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; @@ -122,7 +119,6 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { private final PhysicsBodyRuntimeState runtimeState = new PhysicsBodyRuntimeState(); private final PhysicsControlRuntimeState controlRuntime = new PhysicsControlRuntimeState(); private final PhysicsJointRegistry jointRegistry = new PhysicsJointRegistry(); - private final PhysicsChunkBoundaryRuntime chunkRuntime = new PhysicsChunkBoundaryRuntime(); private final PhysicsVisualRuntime visualRuntime = new PhysicsVisualRuntime(this::clearBodySyncState); private final PhysicsWorldLifecycleState lifecycleState = new PhysicsWorldLifecycleState(); private final PhysicsBodyRuntime bodyRuntime = new PhysicsBodyRuntime(spaceRuntime, @@ -130,7 +126,6 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { runtimeState, controlRuntime, jointRegistry, - chunkRuntime, visualRuntime, lifecycleState, this::markWorldChanged); @@ -1397,8 +1392,6 @@ public void disableWorldCollisionLifecycle() { private void disableWorldCollisionLifecycleDirect() { collisionRuntime.clearRetainedTerrain(spaceRuntime.getBindings()); restoreCollisionLodFiltersDirect(); - restoreChunkBoundaryPausedBodiesDirect(); - chunkRuntime.clearChunkBoundaryStates(); } private void restoreCollisionLodFiltersDirect() { @@ -1418,36 +1411,6 @@ private void restoreCollisionLodFiltersDirect() { } } - private void restoreChunkBoundaryPausedBodiesDirect() { - for (RigidBodyKey bodyKey : chunkRuntime.getChunkBoundaryPausedBodyKeys()) { - ChunkBoundaryPauseState pauseState = chunkRuntime.getChunkBoundaryPauseState(bodyKey); - PhysicsBodyRegistration registration = getRegistration(bodyKey); - if (pauseState == null || registration == null) { - chunkRuntime.clearChunkBoundaryPauseState(bodyKey); - continue; - } - PhysicsSpaceBinding space = getSpaceBinding(registration.spaceId()); - if (space == null) { - chunkRuntime.clearChunkBoundaryPauseState(bodyKey); - continue; - } - space.runtime().setBodyType(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - BackendRuntimeCodes.bodyTypeCode(pauseState.getOriginalBodyType())); - space.runtime().setBodyVelocity(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - pauseState.getLinearVelocity().x, - pauseState.getLinearVelocity().y, - pauseState.getLinearVelocity().z, - pauseState.getAngularVelocity().x, - pauseState.getAngularVelocity().y, - pauseState.getAngularVelocity().z); - space.runtime().activateBody(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value()); - chunkRuntime.clearChunkBoundaryPauseState(bodyKey); - } - } - private static void requireWorldCollisionLifecycleEnabled() { if (!WorldCollisionLifecycle.isEnabled()) { throw new IllegalStateException("Impulse world collision subplugin is disabled"); @@ -2480,107 +2443,6 @@ public void disableControlLifecycle() { controlRuntime.clear(); } - public void updateChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - chunkRuntime.updateChunkBoundarySafeState(bodyKey, position, rotation); - } - - public void updateChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot) { - chunkRuntime.updateChunkBoundarySafeState(bodyKey, snapshot); - } - - public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - chunkRuntime.updateChunkBoundarySafeState(bodyRef, position, rotation); - } - - public void updateChunkBoundarySafeState(@Nonnull Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot) { - chunkRuntime.updateChunkBoundarySafeState(bodyRef, snapshot); - } - - @Nullable - public ChunkBoundarySafeState getChunkBoundarySafeState(@Nonnull RigidBodyKey bodyKey) { - return chunkRuntime.getChunkBoundarySafeState(bodyKey); - } - - @Nullable - public ChunkBoundarySafeState getChunkBoundarySafeState(@Nonnull Ref bodyRef) { - return chunkRuntime.getChunkBoundarySafeState(bodyRef); - } - - public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, - long targetChunkIndex, - @Nonnull PhysicsBodyType originalBodyType, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - chunkRuntime.pauseChunkBoundaryBody(bodyKey, - targetChunkIndex, - originalBodyType, - linearVelocity, - angularVelocity); - } - - public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, - long targetChunkIndex, - @Nonnull PhysicsBodySnapshot snapshot) { - chunkRuntime.pauseChunkBoundaryBody(bodyKey, targetChunkIndex, snapshot); - } - - public void pauseChunkBoundaryBody(@Nonnull RigidBodyKey bodyKey, - long targetChunkIndex, - @Nonnull long[] targetChunkIndices, - @Nonnull PhysicsBodySnapshot snapshot) { - chunkRuntime.pauseChunkBoundaryBody(bodyKey, targetChunkIndex, targetChunkIndices, snapshot); - } - - public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, - long targetChunkIndex, - @Nonnull PhysicsBodyType originalBodyType, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity) { - chunkRuntime.pauseChunkBoundaryBody(bodyRef, - targetChunkIndex, - originalBodyType, - linearVelocity, - angularVelocity); - } - - public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, - long targetChunkIndex, - @Nonnull PhysicsBodySnapshot snapshot) { - chunkRuntime.pauseChunkBoundaryBody(bodyRef, targetChunkIndex, snapshot); - } - - public void pauseChunkBoundaryBody(@Nonnull Ref bodyRef, - long targetChunkIndex, - @Nonnull long[] targetChunkIndices, - @Nonnull PhysicsBodySnapshot snapshot) { - chunkRuntime.pauseChunkBoundaryBody(bodyRef, targetChunkIndex, targetChunkIndices, snapshot); - } - - @Nullable - public ChunkBoundaryPauseState getChunkBoundaryPauseState(@Nonnull RigidBodyKey bodyKey) { - return chunkRuntime.getChunkBoundaryPauseState(bodyKey); - } - - @Nullable - public ChunkBoundaryPauseState getChunkBoundaryPauseState( - @Nonnull Ref bodyRef) { - return chunkRuntime.getChunkBoundaryPauseState(bodyRef); - } - - public void clearChunkBoundaryPauseState(@Nonnull RigidBodyKey bodyKey) { - chunkRuntime.clearChunkBoundaryPauseState(bodyKey); - } - - public void clearChunkBoundaryPauseState(@Nonnull Ref bodyRef) { - chunkRuntime.clearChunkBoundaryPauseState(bodyRef); - } - public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { requireLegacyMutationAllowed("clear physics body runtime state"); runDirectRuntimeMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyKey)); @@ -2600,7 +2462,6 @@ private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { "resolve cleared body runtime key"); if (bodyRef != null) { controlRuntime.clearBody(bodyRef); - chunkRuntime.clearBody(bodyRef); } bodyRuntime.clearBodyRuntimeState(bodyKey); visualRuntime.clearBodyRuntimeState(bodyKey.value(), bodyRef); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index b6d08113..2a94f88b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -4,7 +4,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; @@ -34,8 +33,6 @@ public final class PhysicsBodyRuntime { @Nonnull private final PhysicsJointRegistry jointRegistry; @Nonnull - private final PhysicsChunkBoundaryRuntime chunkRuntime; - @Nonnull private final PhysicsVisualRuntime visualRuntime; @Nonnull private final PhysicsWorldLifecycleState lifecycleState; @@ -47,7 +44,6 @@ public PhysicsBodyRuntime(@Nonnull PhysicsSpaceRuntime spaceRuntime, @Nonnull PhysicsBodyRuntimeState runtimeState, @Nonnull PhysicsControlRuntimeState controlRuntime, @Nonnull PhysicsJointRegistry jointRegistry, - @Nonnull PhysicsChunkBoundaryRuntime chunkRuntime, @Nonnull PhysicsVisualRuntime visualRuntime, @Nonnull PhysicsWorldLifecycleState lifecycleState, @Nonnull Runnable worldChangedMarker) { @@ -56,7 +52,6 @@ public PhysicsBodyRuntime(@Nonnull PhysicsSpaceRuntime spaceRuntime, this.runtimeState = runtimeState; this.controlRuntime = controlRuntime; this.jointRegistry = jointRegistry; - this.chunkRuntime = chunkRuntime; this.visualRuntime = visualRuntime; this.lifecycleState = lifecycleState; this.worldChangedMarker = worldChangedMarker; @@ -130,14 +125,12 @@ public void clearBodyStateWithoutMarkingWorldChanged() { runtimeState.clear(); controlRuntime.clear(); jointRegistry.clear(); - chunkRuntime.clear(); visualRuntime.clear(); lifecycleState.clearBodySnapshots(); } public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { visualRuntime.clearBodyRuntimeState(bodyKey.value(), null); - chunkRuntime.clearBody(bodyKey); lifecycleState.removeBodySnapshot(bodyKey); } From 114ea1a3ec86de7bb1bd99b6c2633c58b2ca1865 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:44:07 +0200 Subject: [PATCH 238/534] docs(core): align store tick terminology Signed-off-by: Blovien --- README.md | 59 +++++++++---------- impulse-core/README.md | 4 +- .../resources/PhysicsWorldEventState.java | 4 +- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 6b4c1076..1613e6ad 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,12 @@ flowchart TB subgraph Examples["impulse-examples / plugin usage"] direction TB - Intents["Split ECS body components\nidentity / shape / dynamics / material / collision"] - ECSSystem["Body reconciliation systems"] - Direct["Direct command calls"] - Commands["command recipes / copied queries"] + Rows["PhysicsStore rows\nspaces / bodies / joints / terrain"] + Commands["row-local body commands + targets"] + Reads["copied snapshots / diagnostics / raycasts"] - Intents --> ECSSystem --> Commands - Direct --> Commands + Rows --> Commands + Commands --> Reads end subgraph CoreWorld["impulse-core: per-world runtime"] @@ -43,32 +42,28 @@ flowchart TB Modules["Internal modules\n- Hytale modules substitution (WIP)\n- World collision module\n- Control session module"] - Requests["physics command batches + query requests"] - Ordering["mutations + step request ordering"] - LaneBuild["owner-lane work units\ncomputed per world"] + StoreSystems["PhysicsStore systems + resources"] + Ordering["row mutation + backend step ordering"] + StoreTick["store tick lane\nper world"] - Plugin --> Requests - Modules --> Requests - Requests --> Ordering - Ordering --> LaneBuild + Plugin --> StoreSystems + Modules --> StoreSystems + StoreSystems --> Ordering + Ordering --> StoreTick end - subgraph OwnerQueues["owner-lane queues"] + subgraph StoreTicks["PhysicsStore ticks"] direction LR - QueueA["World A\nowner-lane queue"] - QueueB["World B\nowner-lane queue"] - QueueN["World N\nowner-lane queue"] + TickA["World A\nstore tick"] + TickB["World B\nstore tick"] + TickN["World N\nstore tick"] end - subgraph SharedCore["impulse-core: shared execution layer"] + subgraph SharedCore["impulse-core: backend dispatch"] direction TB - Scheduler["thread pool scheduler\nselects ready owner lanes"] - Workers["worker threads\nexecute owner lanes"] - Dispatch["backend dispatch"] - - Scheduler --> Workers --> Dispatch + Dispatch["serialized backend calls"] end subgraph API["impulse-api"] @@ -100,18 +95,18 @@ flowchart TB Router["snapshot + event publication"] end - Worlds --> Intents - Worlds --> Direct + Worlds --> Rows + Worlds --> Reads - Commands ----> Plugin + Reads ----> Plugin - LaneBuild --> QueueA - LaneBuild --> QueueB - LaneBuild --> QueueN + StoreTick --> TickA + StoreTick --> TickB + StoreTick --> TickN - QueueA ----> Scheduler - QueueB ----> Scheduler - QueueN ----> Scheduler + TickA ----> Dispatch + TickB ----> Dispatch + TickN ----> Dispatch Dispatch ----> Current Dispatch -.-> WIP diff --git a/impulse-core/README.md b/impulse-core/README.md index 45318060..e38de04e 100644 --- a/impulse-core/README.md +++ b/impulse-core/README.md @@ -37,10 +37,10 @@ world and `/impulse settings simulation events disabled` to return to the defaul ## Profiling Spark plugin is advised to profile threaded physics benchmarks. By using the following command, -the exported profile includes both Hytale world threads and Impulse's physics owner-lane executor threads: +the exported profile includes Hytale world/store tick threads and PhysicsStore completion work: ```bash -/spark profiler start --timeout 60 --save-to-file --regex --not-combined --ignore-sleeping --thread WorldThread.* --thread Impulse.*physics.*owner.* --thread ChunkLighting.* --thread WorldMap.* +/spark profiler start --timeout 60 --save-to-file --regex --not-combined --ignore-sleeping --thread WorldThread.* --thread Impulse.*PhysicsStore.* --thread ChunkLighting.* --thread WorldMap.* ``` Avoid contact debug rendering during benchmark captures; it calls backend contact enumeration and diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java index b18505ce..8f96438f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java @@ -12,10 +12,10 @@ import javax.annotation.Nonnull; /** - * Latest value-only event frame for physics-owner outcomes. + * Latest value-only event frame for store-tick outcomes. * *

    This state intentionally replaces the previous frame instead of queueing history. The public - * event frame is a low-overhead diagnostic snapshot of the newest owner outcome and latest + * event frame is a low-overhead diagnostic snapshot of the newest store-tick outcome and latest * captured snapshot inclusion state.

    */ public final class PhysicsWorldEventState { From c0347be36d1cf971470f13f9d04aecd329fe4beb Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:47:45 +0200 Subject: [PATCH 239/534] refactor(core): publish physics events by uuid Signed-off-by: Blovien --- .../CompletedStepPublicationSystem.java | 5 +-- .../events/PhysicsBodyActivationEvent.java | 16 +++++++- .../plugin/events/PhysicsContactEvent.java | 39 +++++++++++++++++-- .../plugin/events/PhysicsJointBreakEvent.java | 34 ++++++++++++++-- .../examples/events/PhysicsEventSummary.java | 4 +- .../systems/ExplosiveFuseContactSystem.java | 8 ++-- 6 files changed, 87 insertions(+), 19 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index b1d6e4d9..2aa45181 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -26,7 +26,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; @@ -324,8 +323,8 @@ private static void collectContactEvent(@Nonnull PhysicsRuntimeResource runtime, } backendEvents.physicsEvents.add(new PhysicsContactEvent(spaceId, PhysicsContactPhase.OBSERVED, - RigidBodyKey.of(bodyAUuid), - RigidBodyKey.of(bodyBUuid), + bodyAUuid, + bodyBUuid, new Vector3f(pointAX, pointAY, pointAZ), new Vector3f(pointBX, pointBY, pointBZ), new Vector3f(normalBX, normalBY, normalBZ), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java index a42f0b3c..6474eb94 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java @@ -4,6 +4,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; /** @@ -11,12 +12,23 @@ */ public record PhysicsBodyActivationEvent(@Nonnull SpaceId spaceId, @Nonnull PhysicsBodyActivationPhase phase, - @Nonnull RigidBodyKey bodyKey) implements PhysicsFrameEvent { + @Nonnull UUID bodyUuid) implements PhysicsFrameEvent { public PhysicsBodyActivationEvent { Objects.requireNonNull(spaceId, "spaceId"); Objects.requireNonNull(phase, "phase"); - Objects.requireNonNull(bodyKey, "bodyKey"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + } + + public PhysicsBodyActivationEvent(@Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyActivationPhase phase, + @Nonnull RigidBodyKey bodyKey) { + this(spaceId, phase, Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + @Nonnull + public RigidBodyKey bodyKey() { + return RigidBodyKey.of(bodyUuid); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java index bedd1899..a36dcc4c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java @@ -4,6 +4,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Vector3f; @@ -12,8 +13,8 @@ */ public record PhysicsContactEvent(@Nonnull SpaceId spaceId, @Nonnull PhysicsContactPhase phase, - @Nonnull RigidBodyKey bodyAKey, - @Nonnull RigidBodyKey bodyBKey, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid, @Nonnull Vector3f pointOnA, @Nonnull Vector3f pointOnB, @Nonnull Vector3f normalOnB, @@ -23,13 +24,43 @@ public record PhysicsContactEvent(@Nonnull SpaceId spaceId, public PhysicsContactEvent { Objects.requireNonNull(spaceId, "spaceId"); Objects.requireNonNull(phase, "phase"); - Objects.requireNonNull(bodyAKey, "bodyAKey"); - Objects.requireNonNull(bodyBKey, "bodyBKey"); + Objects.requireNonNull(bodyAUuid, "bodyAUuid"); + Objects.requireNonNull(bodyBUuid, "bodyBUuid"); pointOnA = new Vector3f(Objects.requireNonNull(pointOnA, "pointOnA")); pointOnB = new Vector3f(Objects.requireNonNull(pointOnB, "pointOnB")); normalOnB = new Vector3f(Objects.requireNonNull(normalOnB, "normalOnB")); } + public PhysicsContactEvent(@Nonnull SpaceId spaceId, + @Nonnull PhysicsContactPhase phase, + @Nonnull RigidBodyKey bodyAKey, + @Nonnull RigidBodyKey bodyBKey, + @Nonnull Vector3f pointOnA, + @Nonnull Vector3f pointOnB, + @Nonnull Vector3f normalOnB, + float distance, + float impulse) { + this(spaceId, + phase, + Objects.requireNonNull(bodyAKey, "bodyAKey").value(), + Objects.requireNonNull(bodyBKey, "bodyBKey").value(), + pointOnA, + pointOnB, + normalOnB, + distance, + impulse); + } + + @Nonnull + public RigidBodyKey bodyAKey() { + return RigidBodyKey.of(bodyAUuid); + } + + @Nonnull + public RigidBodyKey bodyBKey() { + return RigidBodyKey.of(bodyBUuid); + } + @Nonnull @Override public PhysicsFrameEventKind kind() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java index 6c7aab1d..9ade341c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java @@ -4,6 +4,7 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -11,13 +12,38 @@ * Stable joint-break event copied from a backend event batch. */ public record PhysicsJointBreakEvent(@Nonnull SpaceId spaceId, - @Nonnull JointKey jointKey, - @Nullable RigidBodyKey bodyAKey, - @Nullable RigidBodyKey bodyBKey) implements PhysicsFrameEvent { + @Nonnull UUID jointUuid, + @Nullable UUID bodyAUuid, + @Nullable UUID bodyBUuid) implements PhysicsFrameEvent { public PhysicsJointBreakEvent { Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(jointKey, "jointKey"); + Objects.requireNonNull(jointUuid, "jointUuid"); + } + + public PhysicsJointBreakEvent(@Nonnull SpaceId spaceId, + @Nonnull JointKey jointKey, + @Nullable RigidBodyKey bodyAKey, + @Nullable RigidBodyKey bodyBKey) { + this(spaceId, + Objects.requireNonNull(jointKey, "jointKey").value(), + bodyAKey != null ? bodyAKey.value() : null, + bodyBKey != null ? bodyBKey.value() : null); + } + + @Nonnull + public JointKey jointKey() { + return JointKey.of(jointUuid); + } + + @Nullable + public RigidBodyKey bodyAKey() { + return bodyAUuid != null ? RigidBodyKey.of(bodyAUuid) : null; + } + + @Nullable + public RigidBodyKey bodyBKey() { + return bodyBUuid != null ? RigidBodyKey.of(bodyBUuid) : null; } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/events/PhysicsEventSummary.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/events/PhysicsEventSummary.java index 31acc6a4..e14bc952 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/events/PhysicsEventSummary.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/events/PhysicsEventSummary.java @@ -36,9 +36,9 @@ public static String format(@Nonnull PhysicsEventFrame frame) { .append(" space=") .append(firstContact.spaceId().value()) .append(" bodyA=") - .append(firstContact.bodyAKey()) + .append(firstContact.bodyAUuid()) .append(" bodyB=") - .append(firstContact.bodyBKey()) + .append(firstContact.bodyBUuid()) .append(" distance=") .append(formatFloat(firstContact.distance())) .append(" impulse=") diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index f5eb46c8..9f3699f2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -48,14 +48,14 @@ public void handle(@Nonnull Store store, armIfExplosiveTouchesWorld(commandBuffer, resource, tick, - contact.bodyAKey().value(), - contact.bodyBKey().value(), + contact.bodyAUuid(), + contact.bodyBUuid(), contactCenter(contact.pointOnB())); armIfExplosiveTouchesWorld(commandBuffer, resource, tick, - contact.bodyBKey().value(), - contact.bodyAKey().value(), + contact.bodyBUuid(), + contact.bodyAUuid(), contactCenter(contact.pointOnA())); } } From fbf164858e4763f25b38a8a923182d24f901fb69 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 16:50:31 +0200 Subject: [PATCH 240/534] refactor(core): expose snapshot entries by uuid Signed-off-by: Blovien --- .../snapshot/PhysicsBodySnapshotEntry.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java index ee30419f..a0af9d2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java @@ -5,15 +5,41 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; /** - * Snapshot query result carrying a body's stable key, latest snapshot, and registration metadata. + * Snapshot query result carrying a body's durable UUID, latest snapshot, and registration metadata. */ -public record PhysicsBodySnapshotEntry(@Nonnull RigidBodyKey bodyKey, +public record PhysicsBodySnapshotEntry(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + public PhysicsBodySnapshotEntry { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(snapshot, "snapshot"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(persistenceMode, "persistenceMode"); + } + + public PhysicsBodySnapshotEntry(@Nonnull RigidBodyKey bodyKey, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + this(Objects.requireNonNull(bodyKey, "bodyKey").value(), + snapshot, + spaceId, + kind, + persistenceMode); + } + + @Nonnull + public RigidBodyKey bodyKey() { + return RigidBodyKey.of(bodyUuid); + } } From d9a345503e09413de7fe0e3274ba00351b188af4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:00:34 +0200 Subject: [PATCH 241/534] refactor(core): publish snapshot bodies by uuid Signed-off-by: Blovien --- .../PublishedPhysicsBodyFrameStorage.java | 92 +++++++-- .../PublishedPhysicsBodySnapshot.java | 186 ++++++++++++++++-- .../PublishedPhysicsBodySnapshotCursor.java | 8 +- .../PublishedPhysicsSnapshotFrame.java | 19 ++ 4 files changed, 278 insertions(+), 27 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java index dd7c6399..a1a53409 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.Objects; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; @@ -51,7 +52,8 @@ final class PublishedPhysicsBodyFrameStorage { private final long[] spaceEpochs; private final int[] spaceBodyStarts; private final int[] spaceBodyCounts; - private final RigidBodyKey[] bodyKeys; + private final long[] bodyUuidMostSignificantBits; + private final long[] bodyUuidLeastSignificantBits; private final SpaceId[] bodySpaceIds; private final long[] bodySpaceEpochs; private final long[] registrationGenerations; @@ -71,7 +73,8 @@ private PublishedPhysicsBodyFrameStorage(long frameEpoch, long[] spaceEpochs, int[] spaceBodyStarts, int[] spaceBodyCounts, - RigidBodyKey[] bodyKeys, + long[] bodyUuidMostSignificantBits, + long[] bodyUuidLeastSignificantBits, SpaceId[] bodySpaceIds, long[] bodySpaceEpochs, long[] registrationGenerations, @@ -90,7 +93,8 @@ private PublishedPhysicsBodyFrameStorage(long frameEpoch, this.spaceEpochs = spaceEpochs; this.spaceBodyStarts = spaceBodyStarts; this.spaceBodyCounts = spaceBodyCounts; - this.bodyKeys = bodyKeys; + this.bodyUuidMostSignificantBits = bodyUuidMostSignificantBits; + this.bodyUuidLeastSignificantBits = bodyUuidLeastSignificantBits; this.bodySpaceIds = bodySpaceIds; this.bodySpaceEpochs = bodySpaceEpochs; this.registrationGenerations = registrationGenerations; @@ -114,7 +118,7 @@ int spaceCount() { } int bodyCount() { - return bodyKeys.length; + return bodyUuidMostSignificantBits.length; } SpaceId spaceId(int spaceIndex) { @@ -134,7 +138,8 @@ int spaceBodyCount(int spaceIndex) { } PublishedPhysicsBodySnapshot bodySnapshot(int bodyIndex) { - return new PublishedPhysicsBodySnapshot(bodyKey(bodyIndex), + return new PublishedPhysicsBodySnapshot(bodyUuidMostSignificantBits(bodyIndex), + bodyUuidLeastSignificantBits(bodyIndex), bodySpaceId(bodyIndex), frameEpoch, worldEpoch, @@ -180,14 +185,23 @@ PublishedPhysicsBodySnapshot bodySnapshot(int bodyIndex) { void forEachBodyCursor(@Nonnull Consumer consumer) { Objects.requireNonNull(consumer, "consumer"); FrameBodyCursor cursor = new FrameBodyCursor(); - for (int bodyIndex = 0; bodyIndex < bodyKeys.length; bodyIndex++) { + for (int bodyIndex = 0; bodyIndex < bodyUuidMostSignificantBits.length; bodyIndex++) { cursor.index = bodyIndex; consumer.accept(cursor); } } - private RigidBodyKey bodyKey(int bodyIndex) { - return bodyKeys[bodyIndex]; + private UUID bodyUuid(int bodyIndex) { + return new UUID(bodyUuidMostSignificantBits(bodyIndex), + bodyUuidLeastSignificantBits(bodyIndex)); + } + + private long bodyUuidMostSignificantBits(int bodyIndex) { + return bodyUuidMostSignificantBits[bodyIndex]; + } + + private long bodyUuidLeastSignificantBits(int bodyIndex) { + return bodyUuidLeastSignificantBits[bodyIndex]; } private SpaceId bodySpaceId(int bodyIndex) { @@ -354,7 +368,8 @@ static final class Builder { private final long[] spaceEpochs; private final int[] spaceBodyStarts; private final int[] spaceBodyCounts; - private final RigidBodyKey[] bodyKeys; + private final long[] bodyUuidMostSignificantBits; + private final long[] bodyUuidLeastSignificantBits; private final SpaceId[] bodySpaceIds; private final long[] bodySpaceEpochs; private final long[] registrationGenerations; @@ -385,7 +400,8 @@ private Builder(long frameEpoch, long worldEpoch, int expectedSpaces, int expect this.spaceEpochs = new long[expectedSpaces]; this.spaceBodyStarts = new int[expectedSpaces]; this.spaceBodyCounts = new int[expectedSpaces]; - this.bodyKeys = new RigidBodyKey[expectedBodies]; + this.bodyUuidMostSignificantBits = new long[expectedBodies]; + this.bodyUuidLeastSignificantBits = new long[expectedBodies]; this.bodySpaceIds = new SpaceId[expectedBodies]; this.bodySpaceEpochs = new long[expectedBodies]; this.registrationGenerations = new long[expectedBodies]; @@ -418,6 +434,43 @@ void addSpace(@Nonnull SpaceId spaceId, long spaceEpoch, int bodyCount) { } void addBody(@Nonnull RigidBodyKey bodyKey, + @Nonnull SpaceId spaceId, + long spaceEpoch, + long registrationGeneration, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull PhysicsBodySnapshot snapshot) { + Objects.requireNonNull(bodyKey, "bodyKey"); + addBody(bodyKey.mostSignificantBits(), + bodyKey.leastSignificantBits(), + spaceId, + spaceEpoch, + registrationGeneration, + kind, + persistenceMode, + snapshot); + } + + void addBody(@Nonnull UUID bodyUuid, + @Nonnull SpaceId spaceId, + long spaceEpoch, + long registrationGeneration, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull PhysicsBodySnapshot snapshot) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + addBody(bodyUuid.getMostSignificantBits(), + bodyUuid.getLeastSignificantBits(), + spaceId, + spaceEpoch, + registrationGeneration, + kind, + persistenceMode, + snapshot); + } + + private void addBody(long bodyUuidMostSignificantBits, + long bodyUuidLeastSignificantBits, @Nonnull SpaceId spaceId, long spaceEpoch, long registrationGeneration, @@ -434,10 +487,11 @@ void addBody(@Nonnull RigidBodyKey bodyKey, if (currentSpaceBodyCount >= spaceBodyCounts[currentSpace]) { throw new IllegalStateException("too many bodies added to current published space frame"); } - if (nextBody >= bodyKeys.length) { + if (nextBody >= this.bodyUuidMostSignificantBits.length) { throw new IllegalStateException("too many bodies added to published frame"); } - bodyKeys[nextBody] = Objects.requireNonNull(bodyKey, "bodyKey"); + this.bodyUuidMostSignificantBits[nextBody] = bodyUuidMostSignificantBits; + this.bodyUuidLeastSignificantBits[nextBody] = bodyUuidLeastSignificantBits; bodySpaceIds[nextBody] = Objects.requireNonNull(spaceId, "spaceId"); bodySpaceEpochs[nextBody] = spaceEpoch; registrationGenerations[nextBody] = registrationGeneration; @@ -495,7 +549,7 @@ PublishedPhysicsBodyFrameStorage build() { if (nextSpace != spaceIds.length) { throw new IllegalStateException("published frame space count mismatch"); } - if (nextBody != bodyKeys.length) { + if (nextBody != bodyUuidMostSignificantBits.length) { throw new IllegalStateException("published frame body count mismatch"); } return new PublishedPhysicsBodyFrameStorage(frameEpoch, @@ -504,7 +558,8 @@ PublishedPhysicsBodyFrameStorage build() { spaceEpochs, spaceBodyStarts, spaceBodyCounts, - bodyKeys, + bodyUuidMostSignificantBits, + bodyUuidLeastSignificantBits, bodySpaceIds, bodySpaceEpochs, registrationGenerations, @@ -534,10 +589,17 @@ private final class FrameBodyCursor implements PublishedPhysicsBodySnapshotCurso private int index; + @Nonnull + @Override + public UUID bodyUuid() { + return PublishedPhysicsBodyFrameStorage.this.bodyUuid(index); + } + @Nonnull @Override public RigidBodyKey bodyKey() { - return PublishedPhysicsBodyFrameStorage.this.bodyKey(index); + return RigidBodyKey.of(bodyUuidMostSignificantBits(index), + bodyUuidLeastSignificantBits(index)); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java index 542ef182..4f89f7ee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; @@ -17,13 +18,13 @@ /** * Immutable body state published as part of an async snapshot frame. * - *

    This type deliberately carries an Impulse body key instead of backend + *

    This type deliberately carries a durable body UUID instead of backend * body handles so published frames can be read away from the store tick lane.

    */ public final class PublishedPhysicsBodySnapshot implements PublishedPhysicsBodySnapshotCursor { - @Nonnull - private final RigidBodyKey bodyKey; + private final long bodyUuidMostSignificantBits; + private final long bodyUuidLeastSignificantBits; @Nonnull private final SpaceId spaceId; private final long frameEpoch; @@ -92,7 +93,99 @@ public PublishedPhysicsBodySnapshot(@Nonnull RigidBodyKey bodyKey, float sphereRadius, float halfHeight, @Nonnull PhysicsAxis shapeAxis) { - this.bodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); + this(bodyKeyMostSignificantBits(bodyKey), + bodyKeyLeastSignificantBits(bodyKey), + spaceId, + frameEpoch, + worldEpoch, + spaceEpoch, + registrationGeneration, + kind, + persistenceMode, + position, + rotation, + linearVelocity, + angularVelocity, + bodyType, + sleeping, + sensor, + centerOfMassOffsetY, + shapeType, + boxHalfExtents, + sphereRadius, + halfHeight, + shapeAxis); + } + + public PublishedPhysicsBodySnapshot(@Nonnull UUID bodyUuid, + @Nonnull SpaceId spaceId, + long frameEpoch, + long worldEpoch, + long spaceEpoch, + long registrationGeneration, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + @Nonnull PhysicsBodyType bodyType, + boolean sleeping, + boolean sensor, + float centerOfMassOffsetY, + @Nonnull ShapeType shapeType, + @Nullable Vector3f boxHalfExtents, + float sphereRadius, + float halfHeight, + @Nonnull PhysicsAxis shapeAxis) { + this(uuidMostSignificantBits(bodyUuid), + uuidLeastSignificantBits(bodyUuid), + spaceId, + frameEpoch, + worldEpoch, + spaceEpoch, + registrationGeneration, + kind, + persistenceMode, + position, + rotation, + linearVelocity, + angularVelocity, + bodyType, + sleeping, + sensor, + centerOfMassOffsetY, + shapeType, + boxHalfExtents, + sphereRadius, + halfHeight, + shapeAxis); + } + + private PublishedPhysicsBodySnapshot(long bodyUuidMostSignificantBits, + long bodyUuidLeastSignificantBits, + @Nonnull SpaceId spaceId, + long frameEpoch, + long worldEpoch, + long spaceEpoch, + long registrationGeneration, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + @Nonnull PhysicsBodyType bodyType, + boolean sleeping, + boolean sensor, + float centerOfMassOffsetY, + @Nonnull ShapeType shapeType, + @Nullable Vector3f boxHalfExtents, + float sphereRadius, + float halfHeight, + @Nonnull PhysicsAxis shapeAxis) { + this.bodyUuidMostSignificantBits = bodyUuidMostSignificantBits; + this.bodyUuidLeastSignificantBits = bodyUuidLeastSignificantBits; this.spaceId = Objects.requireNonNull(spaceId, "spaceId"); requireNonNegativeEpoch(frameEpoch, "frameEpoch"); requireNonNegativeEpoch(worldEpoch, "worldEpoch"); @@ -152,6 +245,50 @@ public PublishedPhysicsBodySnapshot(@Nonnull RigidBodyKey bodyKey, @Nonnull public static PublishedPhysicsBodySnapshot from(@Nonnull RigidBodyKey bodyKey, + @Nonnull SpaceId spaceId, + long frameEpoch, + long worldEpoch, + long spaceEpoch, + long registrationGeneration, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull PhysicsBodySnapshot snapshot) { + return fromBits(bodyKeyMostSignificantBits(bodyKey), + bodyKeyLeastSignificantBits(bodyKey), + spaceId, + frameEpoch, + worldEpoch, + spaceEpoch, + registrationGeneration, + kind, + persistenceMode, + snapshot); + } + + @Nonnull + public static PublishedPhysicsBodySnapshot from(@Nonnull UUID bodyUuid, + @Nonnull SpaceId spaceId, + long frameEpoch, + long worldEpoch, + long spaceEpoch, + long registrationGeneration, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull PhysicsBodySnapshot snapshot) { + return fromBits(uuidMostSignificantBits(bodyUuid), + uuidLeastSignificantBits(bodyUuid), + spaceId, + frameEpoch, + worldEpoch, + spaceEpoch, + registrationGeneration, + kind, + persistenceMode, + snapshot); + } + + private static PublishedPhysicsBodySnapshot fromBits(long bodyUuidMostSignificantBits, + long bodyUuidLeastSignificantBits, @Nonnull SpaceId spaceId, long frameEpoch, long worldEpoch, @@ -161,7 +298,8 @@ public static PublishedPhysicsBodySnapshot from(@Nonnull RigidBodyKey bodyKey, @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull PhysicsBodySnapshot snapshot) { Objects.requireNonNull(snapshot, "snapshot"); - return new PublishedPhysicsBodySnapshot(bodyKey, + return new PublishedPhysicsBodySnapshot(bodyUuidMostSignificantBits, + bodyUuidLeastSignificantBits, spaceId, frameEpoch, worldEpoch, @@ -204,7 +342,24 @@ public static PublishedPhysicsBodySnapshot from(@Nonnull RigidBodyKey bodyKey, snapshot.shapeAxis()); } - PublishedPhysicsBodySnapshot(@Nonnull RigidBodyKey bodyKey, + private static long bodyKeyMostSignificantBits(@Nonnull RigidBodyKey bodyKey) { + return Objects.requireNonNull(bodyKey, "bodyKey").mostSignificantBits(); + } + + private static long bodyKeyLeastSignificantBits(@Nonnull RigidBodyKey bodyKey) { + return Objects.requireNonNull(bodyKey, "bodyKey").leastSignificantBits(); + } + + private static long uuidMostSignificantBits(@Nonnull UUID bodyUuid) { + return Objects.requireNonNull(bodyUuid, "bodyUuid").getMostSignificantBits(); + } + + private static long uuidLeastSignificantBits(@Nonnull UUID bodyUuid) { + return Objects.requireNonNull(bodyUuid, "bodyUuid").getLeastSignificantBits(); + } + + PublishedPhysicsBodySnapshot(long bodyUuidMostSignificantBits, + long bodyUuidLeastSignificantBits, @Nonnull SpaceId spaceId, long frameEpoch, long worldEpoch, @@ -245,7 +400,8 @@ public static PublishedPhysicsBodySnapshot from(@Nonnull RigidBodyKey bodyKey, float sphereRadius, float halfHeight, @Nonnull PhysicsAxis shapeAxis) { - this.bodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); + this.bodyUuidMostSignificantBits = bodyUuidMostSignificantBits; + this.bodyUuidLeastSignificantBits = bodyUuidLeastSignificantBits; this.spaceId = Objects.requireNonNull(spaceId, "spaceId"); requireNonNegativeEpoch(frameEpoch, "frameEpoch"); requireNonNegativeEpoch(worldEpoch, "worldEpoch"); @@ -377,10 +533,16 @@ public boolean matchesSnapshot(@Nonnull PhysicsBodySnapshot snapshot) { && Float.compare(boxHalfExtentZ, snapshot.boxHalfExtentZ()) == 0; } + @Nonnull + @Override + public UUID bodyUuid() { + return new UUID(bodyUuidMostSignificantBits, bodyUuidLeastSignificantBits); + } + @Nonnull @Override public RigidBodyKey bodyKey() { - return bodyKey; + return RigidBodyKey.of(bodyUuidMostSignificantBits, bodyUuidLeastSignificantBits); } @Nonnull @@ -704,7 +866,8 @@ public boolean equals(@Nullable Object other) { && Float.compare(boxHalfExtentZ, that.boxHalfExtentZ) == 0 && Float.compare(sphereRadius, that.sphereRadius) == 0 && Float.compare(halfHeight, that.halfHeight) == 0 - && bodyKey.equals(that.bodyKey) + && bodyUuidMostSignificantBits == that.bodyUuidMostSignificantBits + && bodyUuidLeastSignificantBits == that.bodyUuidLeastSignificantBits && spaceId.equals(that.spaceId) && kind == that.kind && persistenceMode == that.persistenceMode @@ -715,7 +878,8 @@ public boolean equals(@Nullable Object other) { @Override public int hashCode() { - int result = bodyKey.hashCode(); + int result = Long.hashCode(bodyUuidMostSignificantBits); + result = 31 * result + Long.hashCode(bodyUuidLeastSignificantBits); result = 31 * result + spaceId.hashCode(); result = 31 * result + Long.hashCode(frameEpoch); result = 31 * result + Long.hashCode(worldEpoch); @@ -763,7 +927,7 @@ public int hashCode() { @Override public String toString() { return "PublishedPhysicsBodySnapshot[" - + "bodyKey=" + bodyKey + + "bodyUuid=" + bodyUuid() + ", spaceId=" + spaceId + ", frameEpoch=" + frameEpoch + ", worldEpoch=" + worldEpoch diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java index 3fbc169f..9a355e91 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java @@ -8,6 +8,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -22,7 +23,12 @@ public interface PublishedPhysicsBodySnapshotCursor { @Nonnull - RigidBodyKey bodyKey(); + UUID bodyUuid(); + + @Nonnull + default RigidBodyKey bodyKey() { + return RigidBodyKey.of(bodyUuid()); + } @Nonnull SpaceId spaceId(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java index faeb1174..70920f84 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -464,6 +465,24 @@ public Builder addBody(@Nonnull RigidBodyKey bodyKey, return this; } + @Nonnull + public Builder addBody(@Nonnull UUID bodyUuid, + @Nonnull SpaceId spaceId, + long spaceEpoch, + long registrationGeneration, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull PhysicsBodySnapshot snapshot) { + bodyStorage.addBody(bodyUuid, + spaceId, + spaceEpoch, + registrationGeneration, + kind, + persistenceMode, + snapshot); + return this; + } + @Nonnull public PublishedPhysicsSnapshotFrame build() { return new PublishedPhysicsSnapshotFrame(frameEpoch, From 2ad906e239955b07d01488e2f8943e5cdb3d0cb8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:06:37 +0200 Subject: [PATCH 242/534] refactor(core): key copied snapshots by uuid Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 4 +- .../PhysicsWorldRuntimeResource.java | 13 ++-- .../resources/PhysicsWorldSnapshotState.java | 2 +- .../body/PhysicsBodySnapshotRefVisitor.java | 4 +- .../body/PhysicsBodySnapshotStore.java | 77 ++++++++++++------- .../body/PhysicsBodySnapshotVisitor.java | 4 +- .../body/PhysicsBodySpatialIndex.java | 31 ++++---- .../systems/debug/PhysicsDebugSystem.java | 6 +- 8 files changed, 81 insertions(+), 60 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 10dc45fe..9693a83b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -231,8 +231,8 @@ private static SelectedBodies selectBodiesNear(@Nonnull PhysicsWorldRuntimeResou resource.forEachIndexedBodySnapshotNear(spaceId, centerF, radius, - (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> { - bodyUuids.add(bodyKey.value()); + (bodyUuid, snapshot, bodySpaceId, kind, persistenceMode) -> { + bodyUuids.add(bodyUuid); }); } return new SelectedBodies(bodyUuids); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 18d33721..a9987685 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -777,7 +777,7 @@ private static void forEachIndexedAuthoritativeBodySnapshot( PhysicsBodySnapshotEntry entry = authoritativeSnapshotEntry(store, registrations, body); if (entry != null) { - visitor.accept(entry.bodyKey(), + visitor.accept(entry.bodyUuid(), entry.snapshot(), entry.spaceId(), entry.kind(), @@ -842,7 +842,7 @@ private static int forEachIndexedAuthoritativeBodySnapshotNear( } candidates++; if (withinRadius(entry.snapshot(), center, radiusSquared)) { - visitor.accept(entry.bodyKey(), + visitor.accept(entry.bodyUuid(), entry.snapshot(), entry.spaceId(), entry.kind(), @@ -877,7 +877,7 @@ private static int forEachIndexedAuthoritativeBodySnapshotNearWithRefs( } candidates++; if (withinRadius(entry.snapshot(), center, radiusSquared)) { - visitor.accept(entry.bodyKey(), + visitor.accept(entry.bodyUuid(), validSnapshotBodyRef(store, body), entry.snapshot(), entry.spaceId(), @@ -910,8 +910,7 @@ private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( if (registration == null) { return null; } - RigidBodyKey bodyKey = RigidBodyKey.of(body.bodyUuid()); - return new PhysicsBodySnapshotEntry(bodyKey, + return new PhysicsBodySnapshotEntry(body.bodyUuid(), toPublicBodySnapshot(store, body), registration.spaceId(), registration.kind(), @@ -1495,8 +1494,8 @@ public int forEachIndexedBodySnapshotNearWithRefs(@Nonnull SpaceId spaceId, return lifecycleState.forEachIndexedBodySnapshotNear(spaceId, center, radius, - (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> - visitor.accept(bodyKey, null, snapshot, bodySpaceId, kind, persistenceMode)); + (bodyUuid, snapshot, bodySpaceId, kind, persistenceMode) -> + visitor.accept(bodyUuid, null, snapshot, bodySpaceId, kind, persistenceMode)); } @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index ccbd13a1..eda47987 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -105,7 +105,7 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( int spaceBodyCount = ownerBodySnapshots.bodyCount(spaceId); frameBuilder.addSpace(spaceId, frameWorldEpoch, spaceBodyCount); ownerBodySnapshots.forEachIndexed(spaceId, - (bodyKey, snapshot, bodySpaceId, kind, persistenceMode) -> frameBuilder.addBody(bodyKey, + (bodyUuid, snapshot, bodySpaceId, kind, persistenceMode) -> frameBuilder.addBody(bodyUuid, bodySpaceId, frameWorldEpoch, frameWorldEpoch, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java index d8c7a6d8..f142ac6e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java @@ -6,7 +6,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -16,7 +16,7 @@ @FunctionalInterface public interface PhysicsBodySnapshotRefVisitor { - void accept(@Nonnull RigidBodyKey bodyKey, + void accept(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java index b46bfb0d..da659cc5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java @@ -13,6 +13,8 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Iterator; import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -23,9 +25,9 @@ */ public final class PhysicsBodySnapshotStore { - private final Map snapshots = + private final Map snapshots = new Object2ObjectOpenHashMap<>(); - private final Object2LongOpenHashMap livenessMarks = + private final Object2LongOpenHashMap livenessMarks = new Object2LongOpenHashMap<>(); private final PhysicsBodySpatialIndex spatialIndex = new PhysicsBodySpatialIndex(); private long livenessGeneration; @@ -49,13 +51,13 @@ public int refresh(@Nonnull Iterable spaces, @Nonnull Physi if (snapshot == null) { continue; } - RigidBodyKey bodyKey = registration.bodyKey(); - markLive(bodyKey, generation, liveBodies); - PhysicsBodySnapshot previous = snapshots.get(bodyKey); + UUID bodyUuid = registration.bodyKey().value(); + markLive(bodyUuid, generation, liveBodies); + PhysicsBodySnapshot previous = snapshots.get(bodyUuid); if (snapshot != previous) { - snapshots.put(bodyKey, snapshot); + snapshots.put(bodyUuid, snapshot); } - spatialIndex.update(bodyKey, + spatialIndex.update(bodyUuid, snapshot, spaceId, registration.kind(), @@ -79,20 +81,41 @@ public void put(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - snapshots.put(bodyKey, snapshot); - livenessMarks.put(bodyKey, livenessGeneration); - spatialIndex.update(bodyKey, snapshot, spaceId, kind, persistenceMode); + put(Objects.requireNonNull(bodyKey, "bodyKey").value(), + snapshot, + spaceId, + kind, + persistenceMode); + } + + public void put(@Nonnull UUID bodyUuid, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + snapshots.put(Objects.requireNonNull(bodyUuid, "bodyUuid"), snapshot); + livenessMarks.put(bodyUuid, livenessGeneration); + spatialIndex.update(bodyUuid, snapshot, spaceId, kind, persistenceMode); } @Nullable public PhysicsBodySnapshot get(@Nonnull RigidBodyKey bodyKey) { - return snapshots.get(bodyKey); + return get(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + @Nullable + public PhysicsBodySnapshot get(@Nonnull UUID bodyUuid) { + return snapshots.get(bodyUuid); } public void remove(@Nonnull RigidBodyKey bodyKey) { - snapshots.remove(bodyKey); - livenessMarks.removeLong(bodyKey); - spatialIndex.remove(bodyKey); + remove(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public void remove(@Nonnull UUID bodyUuid) { + snapshots.remove(bodyUuid); + livenessMarks.removeLong(bodyUuid); + spatialIndex.remove(bodyUuid); } public void clear() { @@ -146,23 +169,23 @@ private long nextLivenessGeneration() { return livenessGeneration; } - private void markLive(@Nonnull RigidBodyKey bodyKey, + private void markLive(@Nonnull UUID bodyUuid, long generation, @Nonnull MutableInt liveBodies) { - if (livenessMarks.put(bodyKey, generation) != generation) { + if (livenessMarks.put(bodyUuid, generation) != generation) { liveBodies.increment(); } } private int retainMarked(long generation) { int removed = 0; - Iterator iterator = snapshots.keySet().iterator(); + Iterator iterator = snapshots.keySet().iterator(); while (iterator.hasNext()) { - RigidBodyKey bodyKey = iterator.next(); - if (livenessMarks.getLong(bodyKey) != generation) { + UUID bodyUuid = iterator.next(); + if (livenessMarks.getLong(bodyUuid) != generation) { iterator.remove(); - livenessMarks.removeLong(bodyKey); - spatialIndex.remove(bodyKey); + livenessMarks.removeLong(bodyUuid); + spatialIndex.remove(bodyUuid); removed++; } } @@ -182,21 +205,21 @@ private PublishedFrameApplier(long generation) { @Override public void accept(@Nonnull PublishedPhysicsBodySnapshotCursor bodyFrame) { - RigidBodyKey bodyKey = bodyFrame.bodyKey(); - markLive(bodyKey, generation, liveBodies); - PhysicsBodySnapshot snapshot = snapshots.get(bodyKey); + UUID bodyUuid = bodyFrame.bodyUuid(); + markLive(bodyUuid, generation, liveBodies); + PhysicsBodySnapshot snapshot = snapshots.get(bodyUuid); if (snapshot == null) { inserted++; snapshot = bodyFrame.toBodySnapshot(); - snapshots.put(bodyKey, snapshot); + snapshots.put(bodyUuid, snapshot); } else if (!bodyFrame.matchesSnapshot(snapshot)) { snapshot = bodyFrame.toBodySnapshot(); - snapshots.put(bodyKey, snapshot); + snapshots.put(bodyUuid, snapshot); } else { applied++; return; } - spatialIndex.update(bodyKey, + spatialIndex.update(bodyUuid, snapshot, bodyFrame.spaceId(), bodyFrame.kind(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java index e6ebdab5..c77cd973 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java @@ -4,7 +4,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; +import java.util.UUID; import javax.annotation.Nonnull; /** @@ -13,7 +13,7 @@ @FunctionalInterface public interface PhysicsBodySnapshotVisitor { - void accept(@Nonnull RigidBodyKey bodyKey, + void accept(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java index 2ace2343..b56ff858 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java @@ -2,10 +2,8 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -14,6 +12,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import org.joml.Vector3f; @@ -21,8 +20,8 @@ /** * Snapshot-side spatial hash for detached physics bodies. * - *

    Stores the latest published {@link PhysicsBodySnapshot} for each - * {@link RigidBodyKey} and groups those snapshots into fixed-size world cells. + *

    Stores the latest published {@link PhysicsBodySnapshot} for each body UUID + * and groups those snapshots into fixed-size world cells. * Cell membership is updated whenever a body publishes a snapshot in a new * position.

    * @@ -36,20 +35,20 @@ final class PhysicsBodySpatialIndex { private static final float CELL_SIZE = 16.0f; private static final int AXIS_MASK = 0x1F_FFFF; - private final Map entries = new Object2ObjectOpenHashMap<>(); + private final Map entries = new Object2ObjectOpenHashMap<>(); private final Long2ObjectMap> cells = new Long2ObjectOpenHashMap<>(); private final Int2IntOpenHashMap spaceBodyCounts = new Int2IntOpenHashMap(); - void update(@Nonnull RigidBodyKey bodyKey, + void update(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { long cellKey = cellKey(snapshot.positionX(), snapshot.positionY(), snapshot.positionZ()); - IndexedBody indexed = entries.get(bodyKey); + IndexedBody indexed = entries.get(bodyUuid); if (indexed == null) { - indexed = new IndexedBody(bodyKey, snapshot, spaceId, kind, persistenceMode, cellKey); - entries.put(bodyKey, indexed); + indexed = new IndexedBody(bodyUuid, snapshot, spaceId, kind, persistenceMode, cellKey); + entries.put(bodyUuid, indexed); addToCell(indexed, cellKey); spaceBodyCounts.addTo(spaceId.value(), 1); return; @@ -69,8 +68,8 @@ void update(@Nonnull RigidBodyKey bodyKey, indexed.persistenceMode = persistenceMode; } - void remove(@Nonnull RigidBodyKey bodyKey) { - IndexedBody indexed = entries.remove(bodyKey); + void remove(@Nonnull UUID bodyUuid) { + IndexedBody indexed = entries.remove(bodyUuid); if (indexed != null) { removeFromCell(indexed); spaceBodyCounts.addTo(indexed.spaceId.value(), -1); @@ -234,7 +233,7 @@ private static long packCell(int x, int y, int z) { private static final class IndexedBody { @Nonnull - private final RigidBodyKey bodyKey; + private final UUID bodyUuid; @Nonnull private PhysicsBodySnapshot snapshot; @Nonnull @@ -246,13 +245,13 @@ private static final class IndexedBody { private long cellKey; private int cellIndex = -1; - private IndexedBody(@Nonnull RigidBodyKey bodyKey, + private IndexedBody(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode, long cellKey) { - this.bodyKey = bodyKey; + this.bodyUuid = bodyUuid; this.snapshot = snapshot; this.spaceId = spaceId; this.kind = kind; @@ -262,7 +261,7 @@ private IndexedBody(@Nonnull RigidBodyKey bodyKey, @Nonnull private PhysicsBodySnapshotEntry entry() { - return new PhysicsBodySnapshotEntry(bodyKey, + return new PhysicsBodySnapshotEntry(bodyUuid, snapshot, spaceId, kind, @@ -270,7 +269,7 @@ private PhysicsBodySnapshotEntry entry() { } private void visit(@Nonnull PhysicsBodySnapshotVisitor visitor) { - visitor.accept(bodyKey, + visitor.accept(bodyUuid, snapshot, spaceId, kind, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 6671549e..95cdd613 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -286,13 +286,13 @@ private static int renderDetachedBodies(@Nonnull Collection viewers, RenderedBodyCount rendered = new RenderedBodyCount(); double maxDistanceSquared = viewRadius * viewRadius; for (PhysicsSpaceBinding space : resource.getSpaceBindings()) { - resource.forEachIndexedBodySnapshot(space.spaceId(), (bodyKey, snapshot, spaceId, kind, persistenceMode) -> { + resource.forEachIndexedBodySnapshot(space.spaceId(), (bodyUuid, snapshot, spaceId, kind, persistenceMode) -> { if (rendered.hasReached(maxBodies)) { return; } if (kind != PhysicsBodyKind.BODY - || resource.hasBodyAttachments(bodyKey)) { + || resource.hasBodyAttachments(bodyUuid, null)) { return; } @@ -343,7 +343,7 @@ private static void renderSpaceOnlyShapes(@Nonnull Collection viewers @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull PhysicsSpaceBinding space, float time) { - resource.forEachIndexedBodySnapshot(space.spaceId(), (bodyKey, snapshot, spaceId, kind, persistenceMode) -> { + resource.forEachIndexedBodySnapshot(space.spaceId(), (bodyUuid, snapshot, spaceId, kind, persistenceMode) -> { if (snapshot.shapeType() != ShapeType.PLANE) { return; } From 237b21e2363ad24109c7abe40cd192fc9434d4be Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:10:20 +0200 Subject: [PATCH 243/534] refactor(core): key registration views by uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 2 +- .../resources/PhysicsWorldSnapshotState.java | 2 +- .../body/PhysicsBodyRegistration.java | 6 ++ .../resources/body/PhysicsBodyRegistry.java | 66 +++++++++++-------- .../body/PhysicsBodySnapshotStore.java | 2 +- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index a9987685..2d4c1fd4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1871,7 +1871,7 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUui .getResource(PhysicsBodyRegistrationResource.getResourceType()) .getBodyRegistrationView(bodyUuid); } - return bodyRegistry.getPublishedRegistrationView(RigidBodyKey.of(bodyUuid)); + return bodyRegistry.getPublishedRegistrationView(bodyUuid); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index eda47987..71ad65e7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -49,7 +49,7 @@ public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { @Nonnull public PhysicsBodySnapshot captureBodySnapshot( @Nonnull PhysicsBodyRegistration registration) { - PhysicsBodySnapshot snapshot = bodySnapshots.get(registration.bodyKey()); + PhysicsBodySnapshot snapshot = bodySnapshots.get(registration.bodyUuid()); if (snapshot == null) { throw new IllegalStateException("No physics body snapshot is available for " + registration.bodyKey()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java index 532ff7a1..cc9f6ae7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java @@ -5,6 +5,7 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import java.util.UUID; import javax.annotation.Nonnull; /** @@ -15,4 +16,9 @@ public record PhysicsBodyRegistration(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + + @Nonnull + public UUID bodyUuid() { + return bodyKey.value(); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java index ccd1a6bd..649c31c3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -32,11 +33,11 @@ public final class PhysicsBodyRegistry { private final Map registrationsByKey = new Object2ObjectLinkedOpenHashMap<>(); - private final Map registrationViewsByKey = + private final Map registrationViewsByUuid = new Object2ObjectOpenHashMap<>(); - private final Map publishedRegistrationViewsByKey = + private final Map publishedRegistrationViewsByUuid = new Object2ObjectLinkedOpenHashMap<>(); - private final Object2LongOpenHashMap publishedLivenessMarks = + private final Object2LongOpenHashMap publishedLivenessMarks = new Object2LongOpenHashMap<>(); private final Int2ObjectOpenHashMap> bodyKeysByRawBackendId = new Int2ObjectOpenHashMap<>(); @@ -59,8 +60,8 @@ public PhysicsBodyRegistration registerBody(@Nonnull RigidBodyKey bodyKey, PhysicsBodyRegistration registration = new PhysicsBodyRegistration(bodyKey, backendBodyHandle, spaceId, kind, persistenceMode); registrationsByKey.put(bodyKey, registration); - registrationViewsByKey.put(bodyKey, - new PhysicsBodyRegistrationView(bodyKey, spaceId, kind, persistenceMode)); + registrationViewsByUuid.put(registration.bodyUuid(), + new PhysicsBodyRegistrationView(registration.bodyUuid(), spaceId, kind, persistenceMode)); bodyKeysByRawBackendId .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) .put(backendBodyHandle.value(), bodyKey); @@ -93,7 +94,7 @@ public PhysicsBodyRegistration unregisterBody(@Nonnull RigidBodyKey bodyKey) { return null; } - registrationViewsByKey.remove(bodyKey); + registrationViewsByUuid.remove(registration.bodyUuid()); removeBackendIndex(registration); removeFromSpace(registration); return registration; @@ -112,32 +113,42 @@ public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { @Nullable public PhysicsBodyRegistrationView getRegistrationView(@Nonnull RigidBodyKey bodyKey) { - return registrationViewsByKey.get(bodyKey); + return getRegistrationView(bodyKey.value()); + } + + @Nullable + public PhysicsBodyRegistrationView getRegistrationView(@Nonnull UUID bodyUuid) { + return registrationViewsByUuid.get(bodyUuid); } @Nullable public PhysicsBodyRegistrationView getPublishedRegistrationView(@Nonnull RigidBodyKey bodyKey) { - return publishedRegistrationViewsByKey.get(bodyKey); + return getPublishedRegistrationView(bodyKey.value()); + } + + @Nullable + public PhysicsBodyRegistrationView getPublishedRegistrationView(@Nonnull UUID bodyUuid) { + return publishedRegistrationViewsByUuid.get(bodyUuid); } @Nonnull public Collection getRegistrationViews() { List views = new ArrayList<>(); for (PhysicsBodyRegistration registration : registrationsByKey.values()) { - views.add(registrationViewsByKey.get(registration.bodyKey())); + views.add(registrationViewsByUuid.get(registration.bodyUuid())); } return views; } @Nonnull public Collection getPublishedRegistrationViews() { - return new ArrayList<>(publishedRegistrationViewsByKey.values()); + return new ArrayList<>(publishedRegistrationViewsByUuid.values()); } @Nonnull public Collection getPublishedRegistrationViews(@Nonnull PhysicsBodyKind kind) { List views = new ArrayList<>(); - for (PhysicsBodyRegistrationView view : publishedRegistrationViewsByKey.values()) { + for (PhysicsBodyRegistrationView view : publishedRegistrationViewsByUuid.values()) { if (view.kind() == kind) { views.add(view); } @@ -150,7 +161,7 @@ public Collection getRegistrationViews(@Nonnull Phy List views = new ArrayList<>(); for (PhysicsBodyRegistration registration : registrationsByKey.values()) { if (registration.kind() == kind) { - views.add(registrationViewsByKey.get(registration.bodyKey())); + views.add(registrationViewsByUuid.get(registration.bodyUuid())); } } return views; @@ -184,7 +195,7 @@ public int getRegistrationCount() { } public int getPublishedRegistrationCount() { - return publishedRegistrationViewsByKey.size(); + return publishedRegistrationViewsByUuid.size(); } public void forEachRegistration(@Nonnull Consumer consumer) { @@ -245,7 +256,7 @@ public int getRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceM public int getPublishedRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { int count = 0; - for (PhysicsBodyRegistrationView view : publishedRegistrationViewsByKey.values()) { + for (PhysicsBodyRegistrationView view : publishedRegistrationViewsByUuid.values()) { if (view.persistenceMode() == persistenceMode) { count++; } @@ -266,8 +277,8 @@ public Collection getRegistrations(@Nonnull PhysicsBody public void clear() { registrationsByKey.clear(); - registrationViewsByKey.clear(); - publishedRegistrationViewsByKey.clear(); + registrationViewsByUuid.clear(); + publishedRegistrationViewsByUuid.clear(); publishedLivenessMarks.clear(); bodyKeysByRawBackendId.clear(); registrationsBySpace.clear(); @@ -276,7 +287,7 @@ public void clear() { public void publishLiveRegistrationViews() { long generation = nextPublishedLivenessGeneration(); for (PhysicsBodyRegistration registration : registrationsByKey.values()) { - publishRegistrationView(registration.bodyKey(), + publishRegistrationView(registration.bodyUuid(), registration.spaceId(), registration.kind(), registration.persistenceMode(), @@ -323,28 +334,27 @@ private void removeBackendIndex(@Nonnull PhysicsBodyRegistration registration) { private void publishRegistrationView(@Nonnull PublishedPhysicsBodySnapshotCursor body, long generation) { - RigidBodyKey bodyKey = body.bodyKey(); - publishRegistrationView(bodyKey, + publishRegistrationView(body.bodyUuid(), body.spaceId(), body.kind(), body.persistenceMode(), generation); } - private void publishRegistrationView(@Nonnull RigidBodyKey bodyKey, + private void publishRegistrationView(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode, long generation) { - PhysicsBodyRegistrationView existing = publishedRegistrationViewsByKey.get(bodyKey); + PhysicsBodyRegistrationView existing = publishedRegistrationViewsByUuid.get(bodyUuid); if (existing == null || !existing.spaceId().equals(spaceId) || existing.kind() != kind || existing.persistenceMode() != persistenceMode) { - publishedRegistrationViewsByKey.put(bodyKey, - new PhysicsBodyRegistrationView(bodyKey, spaceId, kind, persistenceMode)); + publishedRegistrationViewsByUuid.put(bodyUuid, + new PhysicsBodyRegistrationView(bodyUuid, spaceId, kind, persistenceMode)); } - publishedLivenessMarks.put(bodyKey, generation); + publishedLivenessMarks.put(bodyUuid, generation); } private long nextPublishedLivenessGeneration() { @@ -357,12 +367,12 @@ private long nextPublishedLivenessGeneration() { } private void retainPublishedRegistrationViews(long generation) { - Iterator iterator = publishedRegistrationViewsByKey.keySet().iterator(); + Iterator iterator = publishedRegistrationViewsByUuid.keySet().iterator(); while (iterator.hasNext()) { - RigidBodyKey bodyKey = iterator.next(); - if (publishedLivenessMarks.getLong(bodyKey) != generation) { + UUID bodyUuid = iterator.next(); + if (publishedLivenessMarks.getLong(bodyUuid) != generation) { iterator.remove(); - publishedLivenessMarks.removeLong(bodyKey); + publishedLivenessMarks.removeLong(bodyUuid); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java index da659cc5..67e89fba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java @@ -51,7 +51,7 @@ public int refresh(@Nonnull Iterable spaces, @Nonnull Physi if (snapshot == null) { continue; } - UUID bodyUuid = registration.bodyKey().value(); + UUID bodyUuid = registration.bodyUuid(); markLive(bodyUuid, generation, liveBodies); PhysicsBodySnapshot previous = snapshots.get(bodyUuid); if (snapshot != previous) { From 0ce3a83de01f0937f6b93cb08e1d88a3928a9853 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:14:48 +0200 Subject: [PATCH 244/534] refactor(core): route body cleanup by uuid Signed-off-by: Blovien --- .../resources/PhysicsWorldLifecycleState.java | 5 ++ .../PhysicsWorldRuntimeResource.java | 51 ++++++++----------- .../resources/PhysicsWorldSnapshotState.java | 9 +++- .../resources/body/PhysicsBodyRuntime.java | 15 ++++-- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index c003121c..af633460 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -15,6 +15,7 @@ import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.util.Collection; import java.util.List; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -163,6 +164,10 @@ public void removeBodySnapshot(@Nonnull RigidBodyKey bodyKey) { snapshotState.removeBodySnapshot(bodyKey); } + public void removeBodySnapshot(@Nonnull UUID bodyUuid) { + snapshotState.removeBodySnapshot(bodyUuid); + } + public void clearBodySnapshots() { snapshotState.clearBodySnapshots(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 2d4c1fd4..fc9cad1f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1783,9 +1783,7 @@ private RigidBodyKey addBodyDirect(@Nonnull RigidBodyKey bodyKey, @Override public void destroyBody(@Nonnull RigidBodyKey bodyKey) { if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreTopologyMutations.destroyBody( - authoritativePhysicsStore("destroy physics body"), - bodyKey); + destroyBody(bodyKey.value()); return; } requireLegacyMutationAllowed("destroy physics body"); @@ -1808,9 +1806,10 @@ public void destroyBody(@Nonnull UUID bodyUuid) { @Override public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey) { if (isAuthoritativePhysicsStoreActive()) { + UUID bodyUuid = bodyKey.value(); return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", bodyKey, - store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyKey)); + store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyUuid)); } requireLegacyMutationAllowed("destroy physics body"); return destroyBodyAsync(bodyKey, true); @@ -1818,9 +1817,7 @@ public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKe public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreTopologyMutations.destroyBody( - authoritativePhysicsStore("destroy physics body"), - bodyKey); + destroyBody(bodyKey.value()); return; } requireLegacyMutationAllowed("destroy physics body"); @@ -1831,9 +1828,10 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { if (isAuthoritativePhysicsStoreActive()) { + UUID bodyUuid = bodyKey.value(); return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", bodyKey, - store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyKey)); + store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyUuid)); } requireLegacyMutationAllowed("destroy physics body"); return enqueueDirectRuntimeMutation("destroy physics body", @@ -2145,14 +2143,15 @@ public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, } public void registerBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { + UUID bodyUuid = bodyKey.value(); if (hasAttachedAuthoritativePhysicsStore()) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, "resolve body attachment key"); authoritativeProjectionIndex("register physics body attachment") - .registerAttachment(bodyKey.value(), bodyRef, attachment); + .registerAttachment(bodyUuid, bodyRef, attachment); return; } - visualRuntime.registerAttachment(bodyKey.value(), null, attachment); + visualRuntime.registerAttachment(bodyUuid, null, attachment); } public void registerBodyAttachment(@Nonnull UUID bodyUuid, @@ -2167,14 +2166,15 @@ public void registerBodyAttachment(@Nonnull UUID bodyUuid, } public void unregisterBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { + UUID bodyUuid = bodyKey.value(); if (hasAttachedAuthoritativePhysicsStore()) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, "resolve body attachment key"); authoritativeProjectionIndex("unregister physics body attachment") - .unregisterAttachment(bodyKey.value(), bodyRef, attachment); + .unregisterAttachment(bodyUuid, bodyRef, attachment); return; } - visualRuntime.unregisterAttachment(bodyKey.value(), null, attachment); + visualRuntime.unregisterAttachment(bodyUuid, null, attachment); } public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, @@ -2379,11 +2379,7 @@ public void markBodyControlled(@Nonnull UUID bodyUuid) { } public void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve controlled body key"); - if (bodyRef != null) { - controlRuntime.markBodyControlled(bodyRef); - } + markBodyControlled(bodyKey.value()); } public void clearControlledBody(@Nonnull Ref bodyRef) { @@ -2399,11 +2395,7 @@ public void clearControlledBody(@Nonnull UUID bodyUuid) { } public void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve controlled body key"); - if (bodyRef != null) { - controlRuntime.clearControlledBody(bodyRef); - } + clearControlledBody(bodyKey.value()); } public boolean isBodyControlled(@Nonnull Ref bodyRef) { @@ -2417,9 +2409,7 @@ public boolean isBodyControlled(@Nonnull UUID bodyUuid) { } public boolean isBodyControlled(@Nonnull RigidBodyKey bodyKey) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), - "resolve controlled body key"); - return bodyRef != null && controlRuntime.isBodyControlled(bodyRef); + return isBodyControlled(bodyKey.value()); } @Nullable @@ -2457,13 +2447,14 @@ public PhysicsMutationHandle clearBodyRuntimeStateAsync( } private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyKey.value(), + UUID bodyUuid = bodyKey.value(); + Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, "resolve cleared body runtime key"); if (bodyRef != null) { controlRuntime.clearBody(bodyRef); } - bodyRuntime.clearBodyRuntimeState(bodyKey); - visualRuntime.clearBodyRuntimeState(bodyKey.value(), bodyRef); + bodyRuntime.clearBodyRuntimeState(bodyUuid); + visualRuntime.clearBodyRuntimeState(bodyUuid, bodyRef); } public void copyFrom(@Nonnull PhysicsWorldResource other) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index 71ad65e7..81d8a4f5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.util.Collection; import java.util.Objects; +import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -180,8 +181,12 @@ public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, } public void removeBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - bodySnapshots.remove(bodyKey); - ownerBodySnapshots.remove(bodyKey); + removeBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public void removeBodySnapshot(@Nonnull UUID bodyUuid) { + bodySnapshots.remove(bodyUuid); + ownerBodySnapshots.remove(bodyUuid); } public void clearBodySnapshots() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 2a94f88b..5f82f067 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -14,6 +14,8 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.ArrayList; +import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -87,13 +89,14 @@ public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyKey); if (registration != null) { + UUID bodyUuid = registration.bodyUuid(); if (removeFromSpace) { removeBodyFromSpace(registration); } bodyRegistry.unregisterBody(bodyKey); - clearBodyRuntimeState(bodyKey); + clearBodyRuntimeState(bodyUuid); } else { - clearBodyRuntimeState(bodyKey); + clearBodyRuntimeState(bodyKey.value()); } worldChangedMarker.run(); } @@ -130,8 +133,12 @@ public void clearBodyStateWithoutMarkingWorldChanged() { } public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { - visualRuntime.clearBodyRuntimeState(bodyKey.value(), null); - lifecycleState.removeBodySnapshot(bodyKey); + clearBodyRuntimeState(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public void clearBodyRuntimeState(@Nonnull UUID bodyUuid) { + visualRuntime.clearBodyRuntimeState(bodyUuid, null); + lifecycleState.removeBodySnapshot(bodyUuid); } private void removeBodyFromSpace(@Nonnull PhysicsBodyRegistration registration) { From 9cc94f5e3b008999824e8173be03aa42a3a827aa Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:19:43 +0200 Subject: [PATCH 245/534] refactor(core): index live body registrations by uuid Signed-off-by: Blovien --- .../resources/PhysicsWorldLifecycleState.java | 5 +++ .../PhysicsWorldRuntimeResource.java | 38 +++++++++++++++- .../resources/PhysicsWorldSnapshotState.java | 7 ++- .../resources/body/PhysicsBodyRegistry.java | 44 +++++++++++++------ .../resources/body/PhysicsBodyRuntime.java | 13 +++--- .../resources/PhysicsWorldResource.java | 11 +++-- 6 files changed, 92 insertions(+), 26 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index af633460..ea5d78de 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -43,6 +43,11 @@ public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { return snapshotState.getBodySnapshot(bodyKey); } + @Nullable + public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { + return snapshotState.getBodySnapshot(bodyUuid); + } + @Nonnull public PhysicsBodySnapshot captureBodySnapshot(@Nonnull PhysicsBodyRegistration registration) { return snapshotState.captureBodySnapshot(registration); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index fc9cad1f..b7d7969b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -689,7 +689,12 @@ public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid, : store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyUuid); return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; } - return getBodySnapshotIfRegistered(RigidBodyKey.of(bodyUuid)); + PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); + if (snapshot != null) { + return snapshot; + } + return callDirectRuntime("refresh optional physics body snapshot", + () -> getBodySnapshotIfRegisteredDirect(bodyUuid)); } @Nonnull @@ -712,6 +717,16 @@ private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull RigidBody return registration != null ? captureLiveBodySnapshot(registration) : null; } + @Nullable + private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull UUID bodyUuid) { + PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); + if (snapshot != null) { + return snapshot; + } + PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyUuid); + return registration != null ? captureLiveBodySnapshot(registration) : null; + } + @Nullable private static PhysicsBodySnapshot getAuthoritativeBodySnapshot( @Nonnull Store store, @@ -1799,7 +1814,7 @@ public void destroyBody(@Nonnull UUID bodyUuid) { return; } requireLegacyMutationAllowed("destroy physics body"); - destroyBody(RigidBodyKey.of(bodyUuid), true); + destroyBody(bodyUuid, true); } @Nonnull @@ -1824,6 +1839,15 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyKey, removeFromSpace)); } + public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { + if (isAuthoritativePhysicsStoreActive()) { + destroyBody(bodyUuid); + return; + } + requireLegacyMutationAllowed("destroy physics body"); + runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyUuid, removeFromSpace)); + } + @Nonnull public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { @@ -1843,6 +1867,10 @@ private void destroyBodyDirect(@Nonnull RigidBodyKey bodyKey, boolean removeFrom bodyRuntime.destroyBody(bodyKey, removeFromSpace); } + private void destroyBodyDirect(@Nonnull UUID bodyUuid, boolean removeFromSpace) { + bodyRuntime.destroyBody(bodyUuid, removeFromSpace); + } + @Nullable public RigidBodyKey getBodyKey(@Nonnull SpaceId spaceId, long backendBodyId) { return bodyRegistry.getBodyKey(spaceId, backendBodyId); @@ -1861,6 +1889,12 @@ public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { return bodyRegistry.getRegistration(bodyKey); } + @Nullable + public PhysicsBodyRegistration getRegistration(@Nonnull UUID bodyUuid) { + assertCanAccessLiveBackendDirectly("resolve physics body registration"); + return bodyRegistry.getRegistration(bodyUuid); + } + @Nullable @Override public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index 81d8a4f5..df0d57d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -44,7 +44,12 @@ public final class PhysicsWorldSnapshotState { @Nullable public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - return bodySnapshots.get(bodyKey); + return getBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + @Nullable + public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { + return bodySnapshots.get(bodyUuid); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java index 649c31c3..bf65f107 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java @@ -33,6 +33,8 @@ public final class PhysicsBodyRegistry { private final Map registrationsByKey = new Object2ObjectLinkedOpenHashMap<>(); + private final Map registrationsByUuid = + new Object2ObjectLinkedOpenHashMap<>(); private final Map registrationViewsByUuid = new Object2ObjectOpenHashMap<>(); private final Map publishedRegistrationViewsByUuid = @@ -52,7 +54,8 @@ public PhysicsBodyRegistration registerBody(@Nonnull RigidBodyKey bodyKey, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { validateRegisterable(bodyKey, backendBodyHandle, spaceId); - PhysicsBodyRegistration existingRegistration = registrationsByKey.get(bodyKey); + UUID bodyUuid = bodyKey.value(); + PhysicsBodyRegistration existingRegistration = registrationsByUuid.get(bodyUuid); if (existingRegistration != null) { removeFromSpace(existingRegistration); removeBackendIndex(existingRegistration); @@ -60,8 +63,9 @@ public PhysicsBodyRegistration registerBody(@Nonnull RigidBodyKey bodyKey, PhysicsBodyRegistration registration = new PhysicsBodyRegistration(bodyKey, backendBodyHandle, spaceId, kind, persistenceMode); registrationsByKey.put(bodyKey, registration); - registrationViewsByUuid.put(registration.bodyUuid(), - new PhysicsBodyRegistrationView(registration.bodyUuid(), spaceId, kind, persistenceMode)); + registrationsByUuid.put(bodyUuid, registration); + registrationViewsByUuid.put(bodyUuid, + new PhysicsBodyRegistrationView(bodyUuid, spaceId, kind, persistenceMode)); bodyKeysByRawBackendId .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) .put(backendBodyHandle.value(), bodyKey); @@ -78,7 +82,7 @@ public void validateRegisterable(@Nonnull RigidBodyKey bodyKey, if (existingKey != null && !existingKey.equals(bodyKey)) { throw new IllegalArgumentException("Physics body is already registered as " + existingKey); } - PhysicsBodyRegistration existingRegistration = registrationsByKey.get(bodyKey); + PhysicsBodyRegistration existingRegistration = registrationsByUuid.get(bodyKey.value()); if (existingRegistration != null && (!existingRegistration.backendBodyHandle().equals(backendBodyHandle) || !existingRegistration.spaceId().equals(spaceId))) { @@ -89,11 +93,17 @@ public void validateRegisterable(@Nonnull RigidBodyKey bodyKey, @Nullable public PhysicsBodyRegistration unregisterBody(@Nonnull RigidBodyKey bodyKey) { - PhysicsBodyRegistration registration = registrationsByKey.remove(bodyKey); + return unregisterBody(bodyKey.value()); + } + + @Nullable + public PhysicsBodyRegistration unregisterBody(@Nonnull UUID bodyUuid) { + PhysicsBodyRegistration registration = registrationsByUuid.remove(bodyUuid); if (registration == null) { return null; } + registrationsByKey.remove(registration.bodyKey()); registrationViewsByUuid.remove(registration.bodyUuid()); removeBackendIndex(registration); removeFromSpace(registration); @@ -108,7 +118,12 @@ public PhysicsBodyRegistration unregisterBody(@Nonnull SpaceId spaceId, long bac @Nullable public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { - return registrationsByKey.get(bodyKey); + return getRegistration(bodyKey.value()); + } + + @Nullable + public PhysicsBodyRegistration getRegistration(@Nonnull UUID bodyUuid) { + return registrationsByUuid.get(bodyUuid); } @Nullable @@ -134,7 +149,7 @@ public PhysicsBodyRegistrationView getPublishedRegistrationView(@Nonnull UUID bo @Nonnull public Collection getRegistrationViews() { List views = new ArrayList<>(); - for (PhysicsBodyRegistration registration : registrationsByKey.values()) { + for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { views.add(registrationViewsByUuid.get(registration.bodyUuid())); } return views; @@ -159,7 +174,7 @@ public Collection getPublishedRegistrationViews(@No @Nonnull public Collection getRegistrationViews(@Nonnull PhysicsBodyKind kind) { List views = new ArrayList<>(); - for (PhysicsBodyRegistration registration : registrationsByKey.values()) { + for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { if (registration.kind() == kind) { views.add(registrationViewsByUuid.get(registration.bodyUuid())); } @@ -187,11 +202,11 @@ public Collection getBodyKeys() { @Nonnull public Collection getRegistrations() { - return new ArrayList<>(registrationsByKey.values()); + return new ArrayList<>(registrationsByUuid.values()); } public int getRegistrationCount() { - return registrationsByKey.size(); + return registrationsByUuid.size(); } public int getPublishedRegistrationCount() { @@ -199,7 +214,7 @@ public int getPublishedRegistrationCount() { } public void forEachRegistration(@Nonnull Consumer consumer) { - registrationsByKey.values().forEach(consumer); + registrationsByUuid.values().forEach(consumer); } public void forEachRegistration(@Nonnull SpaceId spaceId, @@ -246,7 +261,7 @@ public int getRegistrationCount(@Nonnull SpaceId spaceId) { public int getRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { int count = 0; - for (PhysicsBodyRegistration registration : registrationsByKey.values()) { + for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { if (registration.persistenceMode() == persistenceMode) { count++; } @@ -267,7 +282,7 @@ public int getPublishedRegistrationCount(@Nonnull PhysicsBodyPersistenceMode per @Nonnull public Collection getRegistrations(@Nonnull PhysicsBodyKind kind) { List registrations = new ArrayList<>(); - for (PhysicsBodyRegistration registration : registrationsByKey.values()) { + for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { if (registration.kind() == kind) { registrations.add(registration); } @@ -277,6 +292,7 @@ public Collection getRegistrations(@Nonnull PhysicsBody public void clear() { registrationsByKey.clear(); + registrationsByUuid.clear(); registrationViewsByUuid.clear(); publishedRegistrationViewsByUuid.clear(); publishedLivenessMarks.clear(); @@ -286,7 +302,7 @@ public void clear() { public void publishLiveRegistrationViews() { long generation = nextPublishedLivenessGeneration(); - for (PhysicsBodyRegistration registration : registrationsByKey.values()) { + for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { publishRegistrationView(registration.bodyUuid(), registration.spaceId(), registration.kind(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 5f82f067..073511a1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -87,16 +87,19 @@ public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, } public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { - PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyKey); + destroyBody(Objects.requireNonNull(bodyKey, "bodyKey").value(), removeFromSpace); + } + + public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { + PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyUuid); if (registration != null) { - UUID bodyUuid = registration.bodyUuid(); if (removeFromSpace) { removeBodyFromSpace(registration); } - bodyRegistry.unregisterBody(bodyKey); + bodyRegistry.unregisterBody(bodyUuid); clearBodyRuntimeState(bodyUuid); } else { - clearBodyRuntimeState(bodyKey.value()); + clearBodyRuntimeState(bodyUuid); } worldChangedMarker.run(); } @@ -106,7 +109,7 @@ public void destroyRegisteredBodies() { boolean bodyFailure = false; for (PhysicsBodyRegistration registration : new ArrayList<>(bodyRegistry.getRegistrations())) { try { - destroyBody(registration.bodyKey(), true); + destroyBody(registration.bodyUuid(), true); } catch (RuntimeException exception) { bodyFailure = true; failure = collectFailure(failure, exception); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 3b15fd19..e3fed847 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -23,6 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; @@ -324,17 +325,19 @@ public abstract PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull Sp /** * Destroys a registered body by stable key and removes it from its physics space. + * + *

    This overload is retained for compatibility with legacy event/facade APIs.

    */ - public abstract void destroyBody(@Nonnull RigidBodyKey bodyKey); + public void destroyBody(@Nonnull RigidBodyKey bodyKey) { + destroyBody(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } /** * Destroys a registered body by durable body UUID. * *

    Prefer this overload when the caller is crossing a durable identity boundary.

    */ - public void destroyBody(@Nonnull UUID bodyUuid) { - destroyBody(RigidBodyKey.of(bodyUuid)); - } + public abstract void destroyBody(@Nonnull UUID bodyUuid); /** * Queues destruction of a registered body by stable key. From fce69d26e6c56be47a791860d5b5f19531440972 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:23:15 +0200 Subject: [PATCH 246/534] refactor(core): map backend body ids to uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 4 +- .../resources/body/PhysicsBodyRegistry.java | 49 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index b7d7969b..44503926 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1879,8 +1879,8 @@ public RigidBodyKey getBodyKey(@Nonnull SpaceId spaceId, long backendBodyId) { @Nullable public PhysicsBodyRegistration getBodyRegistration(@Nonnull SpaceId spaceId, long backendBodyId) { assertCanAccessLiveBackendDirectly("resolve physics body registration"); - RigidBodyKey bodyKey = bodyRegistry.getBodyKey(spaceId, backendBodyId); - return bodyKey != null ? bodyRegistry.getRegistration(bodyKey) : null; + UUID bodyUuid = bodyRegistry.getBodyUuid(spaceId, backendBodyId); + return bodyUuid != null ? bodyRegistry.getRegistration(bodyUuid) : null; } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java index bf65f107..e988295b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java @@ -41,7 +41,7 @@ public final class PhysicsBodyRegistry { new Object2ObjectLinkedOpenHashMap<>(); private final Object2LongOpenHashMap publishedLivenessMarks = new Object2LongOpenHashMap<>(); - private final Int2ObjectOpenHashMap> bodyKeysByRawBackendId = + private final Int2ObjectOpenHashMap> bodyUuidsByRawBackendId = new Int2ObjectOpenHashMap<>(); private final Int2ObjectOpenHashMap> registrationsBySpace = new Int2ObjectOpenHashMap<>(); @@ -66,9 +66,9 @@ public PhysicsBodyRegistration registerBody(@Nonnull RigidBodyKey bodyKey, registrationsByUuid.put(bodyUuid, registration); registrationViewsByUuid.put(bodyUuid, new PhysicsBodyRegistrationView(bodyUuid, spaceId, kind, persistenceMode)); - bodyKeysByRawBackendId + bodyUuidsByRawBackendId .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) - .put(backendBodyHandle.value(), bodyKey); + .put(backendBodyHandle.value(), bodyUuid); addToSpace(registration); return registration; } @@ -76,13 +76,14 @@ public PhysicsBodyRegistration registerBody(@Nonnull RigidBodyKey bodyKey, public void validateRegisterable(@Nonnull RigidBodyKey bodyKey, @Nonnull BackendBodyHandle backendBodyHandle, @Nonnull SpaceId spaceId) { - Long2ObjectOpenHashMap bodyKeys = - bodyKeysByRawBackendId.get(spaceId.value()); - RigidBodyKey existingKey = bodyKeys != null ? bodyKeys.get(backendBodyHandle.value()) : null; - if (existingKey != null && !existingKey.equals(bodyKey)) { - throw new IllegalArgumentException("Physics body is already registered as " + existingKey); + Long2ObjectOpenHashMap bodyUuids = + bodyUuidsByRawBackendId.get(spaceId.value()); + UUID bodyUuid = bodyKey.value(); + UUID existingUuid = bodyUuids != null ? bodyUuids.get(backendBodyHandle.value()) : null; + if (existingUuid != null && !existingUuid.equals(bodyUuid)) { + throw new IllegalArgumentException("Physics body is already registered as " + existingUuid); } - PhysicsBodyRegistration existingRegistration = registrationsByUuid.get(bodyKey.value()); + PhysicsBodyRegistration existingRegistration = registrationsByUuid.get(bodyUuid); if (existingRegistration != null && (!existingRegistration.backendBodyHandle().equals(backendBodyHandle) || !existingRegistration.spaceId().equals(spaceId))) { @@ -112,8 +113,8 @@ public PhysicsBodyRegistration unregisterBody(@Nonnull UUID bodyUuid) { @Nullable public PhysicsBodyRegistration unregisterBody(@Nonnull SpaceId spaceId, long backendBodyId) { - RigidBodyKey bodyKey = getBodyKey(spaceId, backendBodyId); - return bodyKey != null ? unregisterBody(bodyKey) : null; + UUID bodyUuid = getBodyUuid(spaceId, backendBodyId); + return bodyUuid != null ? unregisterBody(bodyUuid) : null; } @Nullable @@ -184,9 +185,15 @@ public Collection getRegistrationViews(@Nonnull Phy @Nullable public RigidBodyKey getBodyKey(@Nonnull SpaceId spaceId, long backendBodyId) { - Long2ObjectOpenHashMap bodyKeys = - bodyKeysByRawBackendId.get(spaceId.value()); - return bodyKeys != null ? bodyKeys.get(backendBodyId) : null; + UUID bodyUuid = getBodyUuid(spaceId, backendBodyId); + return bodyUuid != null ? RigidBodyKey.of(bodyUuid) : null; + } + + @Nullable + public UUID getBodyUuid(@Nonnull SpaceId spaceId, long backendBodyId) { + Long2ObjectOpenHashMap bodyUuids = + bodyUuidsByRawBackendId.get(spaceId.value()); + return bodyUuids != null ? bodyUuids.get(backendBodyId) : null; } @Nullable @@ -296,7 +303,7 @@ public void clear() { registrationViewsByUuid.clear(); publishedRegistrationViewsByUuid.clear(); publishedLivenessMarks.clear(); - bodyKeysByRawBackendId.clear(); + bodyUuidsByRawBackendId.clear(); registrationsBySpace.clear(); } @@ -337,14 +344,14 @@ private void removeFromSpace(@Nonnull PhysicsBodyRegistration registration) { } private void removeBackendIndex(@Nonnull PhysicsBodyRegistration registration) { - Long2ObjectOpenHashMap bodyKeys = - bodyKeysByRawBackendId.get(registration.spaceId().value()); - if (bodyKeys == null) { + Long2ObjectOpenHashMap bodyUuids = + bodyUuidsByRawBackendId.get(registration.spaceId().value()); + if (bodyUuids == null) { return; } - bodyKeys.remove(registration.backendBodyHandle().value()); - if (bodyKeys.isEmpty()) { - bodyKeysByRawBackendId.remove(registration.spaceId().value()); + bodyUuids.remove(registration.backendBodyHandle().value()); + if (bodyUuids.isEmpty()) { + bodyUuidsByRawBackendId.remove(registration.spaceId().value()); } } From 200939af8de45b6b544896bc8ec8a1b9074aa07b Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:29:39 +0200 Subject: [PATCH 247/534] refactor(core): make body registrations uuid primary Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 4 +-- .../resources/PhysicsWorldSnapshotState.java | 2 +- .../body/PhysicsBodyRegistration.java | 29 ++++++++++++++++--- .../resources/body/PhysicsBodyRegistry.java | 6 ++-- .../resources/body/PhysicsBodyRuntime.java | 2 +- .../resources/joint/PhysicsJointRegistry.java | 8 ++++- 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 44503926..1a678a4d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1032,7 +1032,7 @@ public PhysicsBodySnapshot captureLiveBodySnapshot(@Nonnull PhysicsBodyRegistrat registration.backendBodyHandle().value()); if (snapshot == null) { throw new IllegalStateException( - "No live physics body snapshot is available for " + registration.bodyKey()); + "No live physics body snapshot is available for " + registration.bodyUuid()); } return snapshot; } @@ -1562,7 +1562,7 @@ private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldNa jointRegistry.unregisterSpace(spaceId); for (PhysicsBodyRegistration registration : new ArrayList<>(bodyRegistry.getRegistrations())) { if (registration.spaceId().equals(spaceId)) { - destroyBody(registration.bodyKey(), false); + destroyBody(registration.bodyUuid(), false); } } LOGGER.at(Level.FINE).log( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index df0d57d9..5733c524 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -57,7 +57,7 @@ public PhysicsBodySnapshot captureBodySnapshot( @Nonnull PhysicsBodyRegistration registration) { PhysicsBodySnapshot snapshot = bodySnapshots.get(registration.bodyUuid()); if (snapshot == null) { - throw new IllegalStateException("No physics body snapshot is available for " + registration.bodyKey()); + throw new IllegalStateException("No physics body snapshot is available for " + registration.bodyUuid()); } return snapshot; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java index cc9f6ae7..7d151f24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java @@ -5,20 +5,41 @@ import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; /** - * Store tick registration for a stable body key and backend-local body handle. + * Store tick registration for a stable body UUID and backend-local body handle. */ -public record PhysicsBodyRegistration(@Nonnull RigidBodyKey bodyKey, +public record PhysicsBodyRegistration(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle backendBodyHandle, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + public PhysicsBodyRegistration { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(backendBodyHandle, "backendBodyHandle"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(persistenceMode, "persistenceMode"); + } + + public PhysicsBodyRegistration(@Nonnull RigidBodyKey bodyKey, + @Nonnull BackendBodyHandle backendBodyHandle, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + this(Objects.requireNonNull(bodyKey, "bodyKey").value(), + backendBodyHandle, + spaceId, + kind, + persistenceMode); + } + @Nonnull - public UUID bodyUuid() { - return bodyKey.value(); + public RigidBodyKey bodyKey() { + return RigidBodyKey.of(bodyUuid); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java index e988295b..29ddd0ec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java @@ -61,7 +61,7 @@ public PhysicsBodyRegistration registerBody(@Nonnull RigidBodyKey bodyKey, removeBackendIndex(existingRegistration); } PhysicsBodyRegistration registration = - new PhysicsBodyRegistration(bodyKey, backendBodyHandle, spaceId, kind, persistenceMode); + new PhysicsBodyRegistration(bodyUuid, backendBodyHandle, spaceId, kind, persistenceMode); registrationsByKey.put(bodyKey, registration); registrationsByUuid.put(bodyUuid, registration); registrationViewsByUuid.put(bodyUuid, @@ -104,8 +104,8 @@ public PhysicsBodyRegistration unregisterBody(@Nonnull UUID bodyUuid) { return null; } - registrationsByKey.remove(registration.bodyKey()); - registrationViewsByUuid.remove(registration.bodyUuid()); + registrationsByKey.remove(RigidBodyKey.of(bodyUuid)); + registrationViewsByUuid.remove(bodyUuid); removeBackendIndex(registration); removeFromSpace(registration); return registration; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 073511a1..b67163d6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -145,7 +145,7 @@ public void clearBodyRuntimeState(@Nonnull UUID bodyUuid) { } private void removeBodyFromSpace(@Nonnull PhysicsBodyRegistration registration) { - for (PhysicsJointRegistration joint : jointRegistry.unregisterJointsForBody(registration.bodyKey())) { + for (PhysicsJointRegistration joint : jointRegistry.unregisterJointsForBody(registration.bodyUuid())) { PhysicsSpaceBinding jointSpace = spaceRuntime.getBinding(joint.spaceId()); if (jointSpace != null) { jointSpace.runtime().removeJoint(jointSpace.backendSpaceHandle().value(), joint.backendJointHandle().value()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java index dbe2d75d..dab2758a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -108,9 +109,14 @@ public PhysicsJointRegistration unregisterJoint(@Nonnull SpaceId spaceId, long b @Nonnull public Collection unregisterJointsForBody(@Nonnull RigidBodyKey bodyKey) { + return unregisterJointsForBody(bodyKey.value()); + } + + @Nonnull + public Collection unregisterJointsForBody(@Nonnull UUID bodyUuid) { ArrayList removed = new ArrayList<>(); for (PhysicsJointRegistration registration : registrationsByKey.values()) { - if (registration.bodyA().equals(bodyKey) || registration.bodyB().equals(bodyKey)) { + if (registration.bodyA().value().equals(bodyUuid) || registration.bodyB().value().equals(bodyUuid)) { removed.add(registration.jointKey()); } } From b5a0a06a22b71ddae781d14eadf22a92326b6eed Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:35:22 +0200 Subject: [PATCH 248/534] refactor(core): make joint registrations uuid primary Signed-off-by: Blovien --- .../joint/PhysicsJointRegistration.java | 82 +++++++++++- .../resources/joint/PhysicsJointRegistry.java | 122 ++++++++++++------ 2 files changed, 158 insertions(+), 46 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java index 43aad714..959f195b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java @@ -5,16 +5,18 @@ import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; /** - * Store tick registration for a stable joint key and backend-local joint handle. + * Store tick registration for a stable joint UUID and backend-local joint handle. */ -public record PhysicsJointRegistration(@Nonnull JointKey jointKey, +public record PhysicsJointRegistration(@Nonnull UUID jointUuid, @Nonnull BackendJointHandle backendJointHandle, @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid, @Nonnull JointType type, float anchorAX, float anchorAY, @@ -33,4 +35,76 @@ public record PhysicsJointRegistration(@Nonnull JointKey jointKey, boolean motorEnabled, float motorTargetVelocity, float motorMaxForce) { + + public PhysicsJointRegistration { + Objects.requireNonNull(jointUuid, "jointUuid"); + Objects.requireNonNull(backendJointHandle, "backendJointHandle"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(bodyAUuid, "bodyAUuid"); + Objects.requireNonNull(bodyBUuid, "bodyBUuid"); + Objects.requireNonNull(type, "type"); + } + + public PhysicsJointRegistration(@Nonnull JointKey jointKey, + @Nonnull BackendJointHandle backendJointHandle, + @Nonnull SpaceId spaceId, + @Nonnull RigidBodyKey bodyA, + @Nonnull RigidBodyKey bodyB, + @Nonnull JointType type, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisX, + float axisY, + float axisZ, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + boolean motorEnabled, + float motorTargetVelocity, + float motorMaxForce) { + this(Objects.requireNonNull(jointKey, "jointKey").value(), + backendJointHandle, + spaceId, + Objects.requireNonNull(bodyA, "bodyA").value(), + Objects.requireNonNull(bodyB, "bodyB").value(), + type, + anchorAX, + anchorAY, + anchorAZ, + anchorBX, + anchorBY, + anchorBZ, + axisX, + axisY, + axisZ, + restLength, + stiffness, + damping, + lowerLimit, + upperLimit, + motorEnabled, + motorTargetVelocity, + motorMaxForce); + } + + @Nonnull + public JointKey jointKey() { + return JointKey.of(jointUuid); + } + + @Nonnull + public RigidBodyKey bodyA() { + return RigidBodyKey.of(bodyAUuid); + } + + @Nonnull + public RigidBodyKey bodyB() { + return RigidBodyKey.of(bodyBUuid); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java index dab2758a..7e12cbba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java @@ -22,7 +22,9 @@ public final class PhysicsJointRegistry { private final Map registrationsByKey = new Object2ObjectLinkedOpenHashMap<>(); - private final Int2ObjectOpenHashMap> jointKeysByRawBackendId = + private final Map registrationsByUuid = + new Object2ObjectLinkedOpenHashMap<>(); + private final Int2ObjectOpenHashMap> jointUuidsByRawBackendId = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -49,22 +51,26 @@ public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, boolean motorEnabled, float motorTargetVelocity, float motorMaxForce) { - JointKey existingKey = getJointKey(spaceId, backendJointHandle); - if (existingKey != null && !existingKey.equals(jointKey)) { - throw new IllegalArgumentException("Physics joint is already registered as " + existingKey); + UUID jointUuid = jointKey.value(); + UUID existingUuid = getJointUuid(spaceId, backendJointHandle); + if (existingUuid != null && !existingUuid.equals(jointUuid)) { + throw new IllegalArgumentException("Physics joint is already registered as " + existingUuid); } - PhysicsJointRegistration existingRegistration = registrationsByKey.get(jointKey); + PhysicsJointRegistration existingRegistration = registrationsByUuid.get(jointUuid); if (existingRegistration != null && (!existingRegistration.backendJointHandle().equals(backendJointHandle) || !existingRegistration.spaceId().equals(spaceId))) { throw new IllegalArgumentException("Physics joint key=" + jointKey + " is already registered to another backend joint"); } - PhysicsJointRegistration registration = new PhysicsJointRegistration(jointKey, + if (existingRegistration != null) { + removeBackendIndex(existingRegistration); + } + PhysicsJointRegistration registration = new PhysicsJointRegistration(jointUuid, backendJointHandle, spaceId, - bodyA, - bodyB, + bodyA.value(), + bodyB.value(), type, anchorAX, anchorAY, @@ -84,27 +90,34 @@ public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, motorTargetVelocity, motorMaxForce); registrationsByKey.put(jointKey, registration); - jointKeysByRawBackendId + registrationsByUuid.put(jointUuid, registration); + jointUuidsByRawBackendId .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) - .put(backendJointHandle.value(), jointKey); + .put(backendJointHandle.value(), jointUuid); return registration; } @Nullable public PhysicsJointRegistration unregisterJoint(@Nonnull JointKey jointKey) { - PhysicsJointRegistration registration = registrationsByKey.remove(jointKey); + return unregisterJoint(jointKey.value()); + } + + @Nullable + public PhysicsJointRegistration unregisterJoint(@Nonnull UUID jointUuid) { + PhysicsJointRegistration registration = registrationsByUuid.remove(jointUuid); if (registration == null) { return null; } + registrationsByKey.remove(JointKey.of(jointUuid)); removeBackendIndex(registration); return registration; } @Nullable public PhysicsJointRegistration unregisterJoint(@Nonnull SpaceId spaceId, long backendJointId) { - JointKey jointKey = getJointKey(spaceId, backendJointId); - return jointKey != null ? unregisterJoint(jointKey) : null; + UUID jointUuid = getJointUuid(spaceId, backendJointId); + return jointUuid != null ? unregisterJoint(jointUuid) : null; } @Nonnull @@ -114,15 +127,15 @@ public Collection unregisterJointsForBody(@Nonnull Rig @Nonnull public Collection unregisterJointsForBody(@Nonnull UUID bodyUuid) { - ArrayList removed = new ArrayList<>(); - for (PhysicsJointRegistration registration : registrationsByKey.values()) { - if (registration.bodyA().value().equals(bodyUuid) || registration.bodyB().value().equals(bodyUuid)) { - removed.add(registration.jointKey()); + ArrayList removed = new ArrayList<>(); + for (PhysicsJointRegistration registration : registrationsByUuid.values()) { + if (registration.bodyAUuid().equals(bodyUuid) || registration.bodyBUuid().equals(bodyUuid)) { + removed.add(registration.jointUuid()); } } ArrayList registrations = new ArrayList<>(removed.size()); - for (JointKey jointKey : removed) { - PhysicsJointRegistration registration = unregisterJoint(jointKey); + for (UUID jointUuid : removed) { + PhysicsJointRegistration registration = unregisterJoint(jointUuid); if (registration != null) { registrations.add(registration); } @@ -131,27 +144,38 @@ public Collection unregisterJointsForBody(@Nonnull UUI } public void unregisterSpace(@Nonnull SpaceId spaceId) { - ArrayList removed = new ArrayList<>(); - for (PhysicsJointRegistration registration : registrationsByKey.values()) { + ArrayList removed = new ArrayList<>(); + for (PhysicsJointRegistration registration : registrationsByUuid.values()) { if (registration.spaceId().equals(spaceId)) { - removed.add(registration.jointKey()); + removed.add(registration.jointUuid()); } } - for (JointKey jointKey : removed) { - unregisterJoint(jointKey); + for (UUID jointUuid : removed) { + unregisterJoint(jointUuid); } } @Nullable public PhysicsJointRegistration getRegistration(@Nonnull JointKey jointKey) { - return registrationsByKey.get(jointKey); + return getRegistration(jointKey.value()); + } + + @Nullable + public PhysicsJointRegistration getRegistration(@Nonnull UUID jointUuid) { + return registrationsByUuid.get(jointUuid); } @Nullable public JointKey getJointKey(@Nonnull SpaceId spaceId, long backendJointId) { - Long2ObjectOpenHashMap jointKeys = - jointKeysByRawBackendId.get(spaceId.value()); - return jointKeys != null ? jointKeys.get(backendJointId) : null; + UUID jointUuid = getJointUuid(spaceId, backendJointId); + return jointUuid != null ? JointKey.of(jointUuid) : null; + } + + @Nullable + public UUID getJointUuid(@Nonnull SpaceId spaceId, long backendJointId) { + Long2ObjectOpenHashMap jointUuids = + jointUuidsByRawBackendId.get(spaceId.value()); + return jointUuids != null ? jointUuids.get(backendJointId) : null; } @Nullable @@ -160,15 +184,28 @@ public JointKey getJointKey(@Nonnull SpaceId spaceId, return getJointKey(spaceId, backendJointHandle.value()); } + @Nullable + public UUID getJointUuid(@Nonnull SpaceId spaceId, + @Nonnull BackendJointHandle backendJointHandle) { + return getJointUuid(spaceId, backendJointHandle.value()); + } + @Nullable public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, @Nonnull RigidBodyKey bodyA, @Nonnull RigidBodyKey bodyB) { - for (PhysicsJointRegistration registration : registrationsByKey.values()) { + return findJointBetween(spaceId, bodyA.value(), bodyB.value()); + } + + @Nullable + public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid) { + for (PhysicsJointRegistration registration : registrationsByUuid.values()) { if (!registration.spaceId().equals(spaceId)) { continue; } - if (connects(bodyA, bodyB, registration.bodyA(), registration.bodyB())) { + if (connects(bodyAUuid, bodyBUuid, registration.bodyAUuid(), registration.bodyBUuid())) { return registration; } } @@ -177,30 +214,31 @@ public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, @Nonnull public Collection getRegistrations() { - return new ArrayList<>(registrationsByKey.values()); + return new ArrayList<>(registrationsByUuid.values()); } public void clear() { registrationsByKey.clear(); - jointKeysByRawBackendId.clear(); + registrationsByUuid.clear(); + jointUuidsByRawBackendId.clear(); } private void removeBackendIndex(@Nonnull PhysicsJointRegistration registration) { - Long2ObjectOpenHashMap jointKeys = - jointKeysByRawBackendId.get(registration.spaceId().value()); - if (jointKeys == null) { + Long2ObjectOpenHashMap jointUuids = + jointUuidsByRawBackendId.get(registration.spaceId().value()); + if (jointUuids == null) { return; } - jointKeys.remove(registration.backendJointHandle().value()); - if (jointKeys.isEmpty()) { - jointKeysByRawBackendId.remove(registration.spaceId().value()); + jointUuids.remove(registration.backendJointHandle().value()); + if (jointUuids.isEmpty()) { + jointUuidsByRawBackendId.remove(registration.spaceId().value()); } } - private static boolean connects(@Nonnull RigidBodyKey expectedA, - @Nonnull RigidBodyKey expectedB, - @Nonnull RigidBodyKey actualA, - @Nonnull RigidBodyKey actualB) { + private static boolean connects(@Nonnull UUID expectedA, + @Nonnull UUID expectedB, + @Nonnull UUID actualA, + @Nonnull UUID actualB) { return (expectedA.equals(actualA) && expectedB.equals(actualB)) || (expectedA.equals(actualB) && expectedB.equals(actualA)); } From 7a3aba5c115a1fc219e5a362ef96610ae24cc8fc Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:38:25 +0200 Subject: [PATCH 249/534] refactor(core): write body snapshots by uuid Signed-off-by: Blovien --- .../resources/PhysicsWorldLifecycleState.java | 8 ++++++++ .../resources/PhysicsWorldSnapshotState.java | 16 ++++++++++++++-- .../resources/body/PhysicsBodyRuntime.java | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index ea5d78de..bfb64aca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -61,6 +61,14 @@ public void putBodySnapshot(@Nonnull RigidBodyKey bodyKey, snapshotState.putBodySnapshot(bodyKey, snapshot, spaceId, kind, persistenceMode); } + public void putBodySnapshot(@Nonnull UUID bodyUuid, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + snapshotState.putBodySnapshot(bodyUuid, snapshot, spaceId, kind, persistenceMode); + } + @Nonnull public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( @Nonnull Collection spaces, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index 5733c524..f4f51dad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -67,8 +67,20 @@ public void putBodySnapshot(@Nonnull RigidBodyKey bodyKey, @Nonnull SpaceId spaceId, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - bodySnapshots.put(bodyKey, snapshot, spaceId, kind, persistenceMode); - ownerBodySnapshots.put(bodyKey, snapshot, spaceId, kind, persistenceMode); + putBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value(), + snapshot, + spaceId, + kind, + persistenceMode); + } + + public void putBodySnapshot(@Nonnull UUID bodyUuid, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + bodySnapshots.put(bodyUuid, snapshot, spaceId, kind, persistenceMode); + ownerBodySnapshots.put(bodyUuid, snapshot, spaceId, kind, persistenceMode); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index b67163d6..b3a958ec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -76,7 +76,7 @@ public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, bodyRegistry.registerBody(bodyKey, backendBodyHandle, spaceId, kind, persistenceMode); PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(binding, backendBodyId); if (snapshot != null) { - lifecycleState.putBodySnapshot(bodyKey, + lifecycleState.putBodySnapshot(registration.bodyUuid(), snapshot, spaceId, registration.kind(), From 3533cf4f5b5ef9b2d88a21e670a179e925caa097 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:40:47 +0200 Subject: [PATCH 250/534] refactor(core): expose body snapshots by uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 67 ++++++++++--------- .../resources/PhysicsWorldResource.java | 10 ++- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 1a678a4d..5ee73585 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -635,53 +635,47 @@ public int refreshBodySnapshots() { @Nonnull @Override - public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { + public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("read copied physics body snapshot"); - PhysicsBodySnapshot snapshot = getAuthoritativeBodySnapshot(store, bodyKey); + PhysicsBodySnapshot snapshot = getAuthoritativeBodySnapshot(store, bodyUuid); if (snapshot == null) { throw new IllegalStateException("No copied PhysicsStore body snapshot is available for " - + bodyKey); + + bodyUuid); } return snapshot; } - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); + PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); if (snapshot != null) { return snapshot; } return callDirectRuntime("refresh missing physics body snapshot", - () -> getBodySnapshotDirect(bodyKey)); + () -> getBodySnapshotDirect(bodyUuid)); + } + + @Nonnull + @Override + public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { + return getBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); } @Nullable public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull RigidBodyKey bodyKey) { - if (isAuthoritativePhysicsStoreActive()) { - return getAuthoritativeBodySnapshot( - authoritativePhysicsStore("read optional copied physics body snapshot"), - bodyKey); - } - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); - if (snapshot != null) { - return snapshot; - } - return callDirectRuntime("refresh optional physics body snapshot", - () -> getBodySnapshotIfRegisteredDirect(bodyKey)); + return getBodySnapshotIfRegistered(Objects.requireNonNull(bodyKey, "bodyKey").value(), null); } @Nullable - public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull Ref bodyRef) { - Store store = Objects.requireNonNull(bodyRef, "bodyRef").getStore(); - PhysicsStoreThreading.requireWorldThread(store, "read optional copied physics body snapshot"); - PhysicsStoreBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(bodyRef); - return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; + public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid) { + return getBodySnapshotIfRegistered(bodyUuid, null); } @Nullable public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { if (isAuthoritativePhysicsStoreActive()) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); Store store = authoritativePhysicsStore("read optional copied physics body snapshot"); PhysicsStoreBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() @@ -697,24 +691,31 @@ public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid, () -> getBodySnapshotIfRegisteredDirect(bodyUuid)); } + @Nullable + public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull Ref bodyRef) { + Store store = Objects.requireNonNull(bodyRef, "bodyRef").getStore(); + PhysicsStoreThreading.requireWorldThread(store, "read optional copied physics body snapshot"); + PhysicsStoreBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(bodyRef); + return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; + } + @Nonnull - private PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull RigidBodyKey bodyKey) { - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); + private PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull UUID bodyUuid) { + PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); if (snapshot != null) { return snapshot; } - PhysicsBodyRegistration registration = requireBodyRegistration(bodyKey); + PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyUuid); + if (registration == null) { + throw new IllegalArgumentException("Physics body uuid=" + bodyUuid + " is not registered"); + } return captureLiveBodySnapshot(registration); } @Nullable private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull RigidBodyKey bodyKey) { - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyKey); - if (snapshot != null) { - return snapshot; - } - PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyKey); - return registration != null ? captureLiveBodySnapshot(registration) : null; + return getBodySnapshotIfRegisteredDirect(Objects.requireNonNull(bodyKey, "bodyKey").value()); } @Nullable @@ -730,9 +731,9 @@ private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull UUID body @Nullable private static PhysicsBodySnapshot getAuthoritativeBodySnapshot( @Nonnull Store store, - @Nonnull RigidBodyKey bodyKey) { + @Nonnull UUID bodyUuid) { PhysicsStoreBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(Objects.requireNonNull(bodyKey, "bodyKey").value()); + .getBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index e3fed847..b390a406 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -184,7 +184,15 @@ public abstract PhysicsMutationHandle createSpaceAsync( * live backend.

    */ @Nonnull - public abstract PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey); + public abstract PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid); + + /** + * Compatibility adapter for callers that still carry a legacy body key. + */ + @Nonnull + public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { + return getBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } /** * Returns the number of body snapshots in the latest published frame. From 0abf70cca55fde4d6a53402c917213a3a2e75e50 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:44:16 +0200 Subject: [PATCH 251/534] refactor(core): queue body destruction by uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 40 +++++++++---------- .../resources/PhysicsWorldResource.java | 13 +++++- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 5ee73585..f81c9579 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1820,24 +1820,19 @@ public void destroyBody(@Nonnull UUID bodyUuid) { @Nonnull @Override - public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey) { - if (isAuthoritativePhysicsStoreActive()) { - UUID bodyUuid = bodyKey.value(); - return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", - bodyKey, - store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyUuid)); - } - requireLegacyMutationAllowed("destroy physics body"); - return destroyBodyAsync(bodyKey, true); + public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid) { + return destroyBodyAsync(bodyUuid, true); } public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { + RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); if (isAuthoritativePhysicsStoreActive()) { - destroyBody(bodyKey.value()); + destroyBody(checkedBodyKey.value()); return; } requireLegacyMutationAllowed("destroy physics body"); - runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyKey, removeFromSpace)); + runDirectRuntimeMutation("destroy physics body", + () -> destroyBodyDirect(checkedBodyKey.value(), removeFromSpace)); } public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { @@ -1852,20 +1847,25 @@ public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { @Nonnull public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { + RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); + return PhysicsMutationHandle.fromCompletion("destroy physics body", + checkedBodyKey, + destroyBodyAsync(checkedBodyKey.value(), removeFromSpace).completion()); + } + + @Nonnull + public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid, + boolean removeFromSpace) { + UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { - UUID bodyUuid = bodyKey.value(); return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", - bodyKey, - store -> PhysicsStoreTopologyMutations.destroyBody(store, bodyUuid)); + checkedBodyUuid, + store -> PhysicsStoreTopologyMutations.destroyBody(store, checkedBodyUuid)); } requireLegacyMutationAllowed("destroy physics body"); return enqueueDirectRuntimeMutation("destroy physics body", - bodyKey, - () -> destroyBodyDirect(bodyKey, removeFromSpace)); - } - - private void destroyBodyDirect(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { - bodyRuntime.destroyBody(bodyKey, removeFromSpace); + checkedBodyUuid, + () -> destroyBodyDirect(checkedBodyUuid, removeFromSpace)); } private void destroyBodyDirect(@Nonnull UUID bodyUuid, boolean removeFromSpace) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index b390a406..8412457b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -347,12 +347,21 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey) { */ public abstract void destroyBody(@Nonnull UUID bodyUuid); + @Nonnull + public abstract PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid); + /** * Queues destruction of a registered body by stable key. + * + *

    This overload is retained for compatibility with legacy event/facade APIs.

    */ @Nonnull - public abstract PhysicsMutationHandle destroyBodyAsync( - @Nonnull RigidBodyKey bodyKey); + public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey) { + RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); + return PhysicsMutationHandle.fromCompletion("destroy physics body", + checkedBodyKey, + destroyBodyAsync(checkedBodyKey.value()).completion()); + } /** * Returns immutable registration metadata for a body UUID. From b97faf63fe74f5f750f95bea8fa25321b92f472f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:47:07 +0200 Subject: [PATCH 252/534] refactor(core): clear body runtime state by uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index f81c9579..4e2cdf54 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2468,21 +2468,34 @@ public void disableControlLifecycle() { } public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { + clearBodyRuntimeState(Objects.requireNonNull(bodyKey, "bodyKey").value()); + } + + public void clearBodyRuntimeState(@Nonnull UUID bodyUuid) { requireLegacyMutationAllowed("clear physics body runtime state"); - runDirectRuntimeMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyKey)); + runDirectRuntimeMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyUuid)); } @Nonnull public PhysicsMutationHandle clearBodyRuntimeStateAsync( @Nonnull RigidBodyKey bodyKey) { + RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); + return PhysicsMutationHandle.fromCompletion("clear physics body runtime state", + checkedBodyKey, + clearBodyRuntimeStateAsync(checkedBodyKey.value()).completion()); + } + + @Nonnull + public PhysicsMutationHandle clearBodyRuntimeStateAsync( + @Nonnull UUID bodyUuid) { + UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); requireLegacyMutationAllowed("clear physics body runtime state"); return enqueueDirectRuntimeMutation("clear physics body runtime state", - bodyKey, - () -> clearBodyRuntimeStateDirect(bodyKey)); + checkedBodyUuid, + () -> clearBodyRuntimeStateDirect(checkedBodyUuid)); } - private void clearBodyRuntimeStateDirect(@Nonnull RigidBodyKey bodyKey) { - UUID bodyUuid = bodyKey.value(); + private void clearBodyRuntimeStateDirect(@Nonnull UUID bodyUuid) { Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, "resolve cleared body runtime key"); if (bodyRef != null) { From b3a451e109ee38d3c4a1beb6781df35bc969bc26 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:51:37 +0200 Subject: [PATCH 253/534] refactor(core): remove key wrappers from snapshot stores Signed-off-by: Blovien --- .../resources/PhysicsWorldLifecycleState.java | 18 --------------- .../resources/PhysicsWorldSnapshotState.java | 22 ------------------- .../resources/body/PhysicsBodyRuntime.java | 9 -------- .../body/PhysicsBodySnapshotStore.java | 22 ------------------- 4 files changed, 71 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index bfb64aca..3c917443 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -8,7 +8,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSnapshotState.ApplyResult; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; @@ -38,11 +37,6 @@ public PhysicsEventFrame latestEventFrame() { return eventState.getLatestFrame(); } - @Nullable - public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - return snapshotState.getBodySnapshot(bodyKey); - } - @Nullable public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { return snapshotState.getBodySnapshot(bodyUuid); @@ -53,14 +47,6 @@ public PhysicsBodySnapshot captureBodySnapshot(@Nonnull PhysicsBodyRegistration return snapshotState.captureBodySnapshot(registration); } - public void putBodySnapshot(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - snapshotState.putBodySnapshot(bodyKey, snapshot, spaceId, kind, persistenceMode); - } - public void putBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, @@ -173,10 +159,6 @@ public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, return snapshotState.forEachIndexedBodySnapshotNear(spaceId, center, radius, visitor); } - public void removeBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - snapshotState.removeBodySnapshot(bodyKey); - } - public void removeBodySnapshot(@Nonnull UUID bodyUuid) { snapshotState.removeBodySnapshot(bodyUuid); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index f4f51dad..8ad5c510 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -5,7 +5,6 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotStore; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; @@ -42,11 +41,6 @@ public final class PhysicsWorldSnapshotState { @Getter private volatile long latestSnapshotAppliedNanos; - @Nullable - public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - return getBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - @Nullable public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { return bodySnapshots.get(bodyUuid); @@ -62,18 +56,6 @@ public PhysicsBodySnapshot captureBodySnapshot( return snapshot; } - public void putBodySnapshot(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - putBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value(), - snapshot, - spaceId, - kind, - persistenceMode); - } - public void putBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, @@ -197,10 +179,6 @@ public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, return bodySnapshots.forEachIndexedNear(spaceId, center, radius, visitor); } - public void removeBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - removeBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - public void removeBodySnapshot(@Nonnull UUID bodyUuid) { bodySnapshots.remove(bodyUuid); ownerBodySnapshots.remove(bodyUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index b3a958ec..a8292361 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -14,7 +14,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.ArrayList; -import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -86,10 +85,6 @@ public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, return bodyKey; } - public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { - destroyBody(Objects.requireNonNull(bodyKey, "bodyKey").value(), removeFromSpace); - } - public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyUuid); if (registration != null) { @@ -135,10 +130,6 @@ public void clearBodyStateWithoutMarkingWorldChanged() { lifecycleState.clearBodySnapshots(); } - public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { - clearBodyRuntimeState(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - public void clearBodyRuntimeState(@Nonnull UUID bodyUuid) { visualRuntime.clearBodyRuntimeState(bodyUuid, null); lifecycleState.removeBodySnapshot(bodyUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java index 67e89fba..39345121 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java @@ -3,7 +3,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; @@ -76,18 +75,6 @@ public ApplyStats applyPublishedFrame(@Nonnull PublishedPhysicsSnapshotFrame fra return new ApplyStats(applier.applied(), applier.inserted(), retainMarked(applier.generation())); } - public void put(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - put(Objects.requireNonNull(bodyKey, "bodyKey").value(), - snapshot, - spaceId, - kind, - persistenceMode); - } - public void put(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, @@ -98,20 +85,11 @@ public void put(@Nonnull UUID bodyUuid, spatialIndex.update(bodyUuid, snapshot, spaceId, kind, persistenceMode); } - @Nullable - public PhysicsBodySnapshot get(@Nonnull RigidBodyKey bodyKey) { - return get(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - @Nullable public PhysicsBodySnapshot get(@Nonnull UUID bodyUuid) { return snapshots.get(bodyUuid); } - public void remove(@Nonnull RigidBodyKey bodyKey) { - remove(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - public void remove(@Nonnull UUID bodyUuid) { snapshots.remove(bodyUuid); livenessMarks.removeLong(bodyUuid); From d4c99cb686a406be2c24664cee813c006113f3cf Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:53:45 +0200 Subject: [PATCH 254/534] refactor(core): drop body registry key map Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 2 +- .../resources/body/PhysicsBodyRegistry.java | 30 ------------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 4e2cdf54..411cbcd6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1887,7 +1887,7 @@ public PhysicsBodyRegistration getBodyRegistration(@Nonnull SpaceId spaceId, lon @Nullable public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { assertCanAccessLiveBackendDirectly("resolve physics body registration"); - return bodyRegistry.getRegistration(bodyKey); + return bodyRegistry.getRegistration(Objects.requireNonNull(bodyKey, "bodyKey").value()); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java index 29ddd0ec..6f15f6eb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java @@ -31,8 +31,6 @@ */ public final class PhysicsBodyRegistry { - private final Map registrationsByKey = - new Object2ObjectLinkedOpenHashMap<>(); private final Map registrationsByUuid = new Object2ObjectLinkedOpenHashMap<>(); private final Map registrationViewsByUuid = @@ -62,7 +60,6 @@ public PhysicsBodyRegistration registerBody(@Nonnull RigidBodyKey bodyKey, } PhysicsBodyRegistration registration = new PhysicsBodyRegistration(bodyUuid, backendBodyHandle, spaceId, kind, persistenceMode); - registrationsByKey.put(bodyKey, registration); registrationsByUuid.put(bodyUuid, registration); registrationViewsByUuid.put(bodyUuid, new PhysicsBodyRegistrationView(bodyUuid, spaceId, kind, persistenceMode)); @@ -92,11 +89,6 @@ public void validateRegisterable(@Nonnull RigidBodyKey bodyKey, } } - @Nullable - public PhysicsBodyRegistration unregisterBody(@Nonnull RigidBodyKey bodyKey) { - return unregisterBody(bodyKey.value()); - } - @Nullable public PhysicsBodyRegistration unregisterBody(@Nonnull UUID bodyUuid) { PhysicsBodyRegistration registration = registrationsByUuid.remove(bodyUuid); @@ -104,7 +96,6 @@ public PhysicsBodyRegistration unregisterBody(@Nonnull UUID bodyUuid) { return null; } - registrationsByKey.remove(RigidBodyKey.of(bodyUuid)); registrationViewsByUuid.remove(bodyUuid); removeBackendIndex(registration); removeFromSpace(registration); @@ -117,31 +108,16 @@ public PhysicsBodyRegistration unregisterBody(@Nonnull SpaceId spaceId, long bac return bodyUuid != null ? unregisterBody(bodyUuid) : null; } - @Nullable - public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { - return getRegistration(bodyKey.value()); - } - @Nullable public PhysicsBodyRegistration getRegistration(@Nonnull UUID bodyUuid) { return registrationsByUuid.get(bodyUuid); } - @Nullable - public PhysicsBodyRegistrationView getRegistrationView(@Nonnull RigidBodyKey bodyKey) { - return getRegistrationView(bodyKey.value()); - } - @Nullable public PhysicsBodyRegistrationView getRegistrationView(@Nonnull UUID bodyUuid) { return registrationViewsByUuid.get(bodyUuid); } - @Nullable - public PhysicsBodyRegistrationView getPublishedRegistrationView(@Nonnull RigidBodyKey bodyKey) { - return getPublishedRegistrationView(bodyKey.value()); - } - @Nullable public PhysicsBodyRegistrationView getPublishedRegistrationView(@Nonnull UUID bodyUuid) { return publishedRegistrationViewsByUuid.get(bodyUuid); @@ -202,11 +178,6 @@ public RigidBodyKey getBodyKey(@Nonnull SpaceId spaceId, return getBodyKey(spaceId, backendBodyHandle.value()); } - @Nonnull - public Collection getBodyKeys() { - return new ArrayList<>(registrationsByKey.keySet()); - } - @Nonnull public Collection getRegistrations() { return new ArrayList<>(registrationsByUuid.values()); @@ -298,7 +269,6 @@ public Collection getRegistrations(@Nonnull PhysicsBody } public void clear() { - registrationsByKey.clear(); registrationsByUuid.clear(); registrationViewsByUuid.clear(); publishedRegistrationViewsByUuid.clear(); From 14fa3963125925fdb2a6f6071b485912457bdcb9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:55:34 +0200 Subject: [PATCH 255/534] refactor(core): drop joint registry key map Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 11 +++++--- .../resources/joint/PhysicsJointRegistry.java | 27 ------------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 411cbcd6..57094d09 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2028,7 +2028,8 @@ public boolean removeJoint(@Nonnull JointKey jointKey) { } private boolean removeJointDirect(@Nonnull JointKey jointKey) { - PhysicsJointRegistration registration = jointRegistry.getRegistration(jointKey); + UUID jointUuid = Objects.requireNonNull(jointKey, "jointKey").value(); + PhysicsJointRegistration registration = jointRegistry.getRegistration(jointUuid); if (registration == null) { return false; } @@ -2037,7 +2038,7 @@ private boolean removeJointDirect(@Nonnull JointKey jointKey) { if (binding != null) { binding.runtime().removeJoint(binding.backendSpaceHandle().value(), registration.backendJointHandle().value()); } - jointRegistry.unregisterJoint(jointKey); + jointRegistry.unregisterJoint(jointUuid); markWorldChanged(); return true; } @@ -2051,7 +2052,7 @@ public JointKey getJointKey(@Nonnull SpaceId spaceId, long backendJointId) { @Nullable public PhysicsJointRegistration getJointRegistration(@Nonnull JointKey jointKey) { assertCanAccessLiveBackendDirectly("resolve physics joint registration"); - return jointRegistry.getRegistration(jointKey); + return jointRegistry.getRegistration(Objects.requireNonNull(jointKey, "jointKey").value()); } @Nonnull @@ -2065,7 +2066,9 @@ public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, @Nonnull RigidBodyKey bodyA, @Nonnull RigidBodyKey bodyB) { assertCanAccessLiveBackendDirectly("resolve physics joint registration"); - return jointRegistry.findJointBetween(spaceId, bodyA, bodyB); + return jointRegistry.findJointBetween(spaceId, + Objects.requireNonNull(bodyA, "bodyA").value(), + Objects.requireNonNull(bodyB, "bodyB").value()); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java index 7e12cbba..98895a6c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java @@ -20,8 +20,6 @@ */ public final class PhysicsJointRegistry { - private final Map registrationsByKey = - new Object2ObjectLinkedOpenHashMap<>(); private final Map registrationsByUuid = new Object2ObjectLinkedOpenHashMap<>(); private final Int2ObjectOpenHashMap> jointUuidsByRawBackendId = @@ -89,7 +87,6 @@ public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, motorEnabled, motorTargetVelocity, motorMaxForce); - registrationsByKey.put(jointKey, registration); registrationsByUuid.put(jointUuid, registration); jointUuidsByRawBackendId .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) @@ -97,11 +94,6 @@ public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, return registration; } - @Nullable - public PhysicsJointRegistration unregisterJoint(@Nonnull JointKey jointKey) { - return unregisterJoint(jointKey.value()); - } - @Nullable public PhysicsJointRegistration unregisterJoint(@Nonnull UUID jointUuid) { PhysicsJointRegistration registration = registrationsByUuid.remove(jointUuid); @@ -109,7 +101,6 @@ public PhysicsJointRegistration unregisterJoint(@Nonnull UUID jointUuid) { return null; } - registrationsByKey.remove(JointKey.of(jointUuid)); removeBackendIndex(registration); return registration; } @@ -120,11 +111,6 @@ public PhysicsJointRegistration unregisterJoint(@Nonnull SpaceId spaceId, long b return jointUuid != null ? unregisterJoint(jointUuid) : null; } - @Nonnull - public Collection unregisterJointsForBody(@Nonnull RigidBodyKey bodyKey) { - return unregisterJointsForBody(bodyKey.value()); - } - @Nonnull public Collection unregisterJointsForBody(@Nonnull UUID bodyUuid) { ArrayList removed = new ArrayList<>(); @@ -155,11 +141,6 @@ public void unregisterSpace(@Nonnull SpaceId spaceId) { } } - @Nullable - public PhysicsJointRegistration getRegistration(@Nonnull JointKey jointKey) { - return getRegistration(jointKey.value()); - } - @Nullable public PhysicsJointRegistration getRegistration(@Nonnull UUID jointUuid) { return registrationsByUuid.get(jointUuid); @@ -190,13 +171,6 @@ public UUID getJointUuid(@Nonnull SpaceId spaceId, return getJointUuid(spaceId, backendJointHandle.value()); } - @Nullable - public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB) { - return findJointBetween(spaceId, bodyA.value(), bodyB.value()); - } - @Nullable public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, @Nonnull UUID bodyAUuid, @@ -218,7 +192,6 @@ public Collection getRegistrations() { } public void clear() { - registrationsByKey.clear(); registrationsByUuid.clear(); jointUuidsByRawBackendId.clear(); } From 0f5115d0865812970204353c1993c14e3679c142 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 17:58:40 +0200 Subject: [PATCH 256/534] refactor(core): key world collision targets by uuid Signed-off-by: Blovien --- .../WorldVoxelCollisionCache.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCache.java index c155776f..33aa8d2f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCache.java @@ -16,7 +16,6 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2LongMap; @@ -29,6 +28,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import javax.annotation.Nonnull; @@ -90,21 +90,21 @@ public SectionAccessCache newSectionAccessCache() { /** * Returns whether a body target needs terrain work. Call - * {@link #recordBodyTargetRefresh(SpaceId, RigidBodyKey, WorldCollisionStreamingBounds, boolean, long)} + * {@link #recordBodyTargetRefresh(SpaceId, UUID, WorldCollisionStreamingBounds, boolean, long)} * only after the terrain apply path has actually attempted that work. */ @Nonnull public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID bodyUuid, @Nonnull WorldCollisionStreamingBounds bounds, boolean sleeping, long currentTick, int ttlTicks, @Nullable Snapshot profiling) { SpaceCollisionCache cache = spaces.computeIfAbsent(spaceId.value(), ignored -> new SpaceCollisionCache()); - CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyKey); + CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyUuid); if (target == null) { - cache.bodyTargets.put(bodyKey, new CachedBodyStreamingTarget(bounds, + cache.bodyTargets.put(bodyUuid, new CachedBodyStreamingTarget(bounds, sleeping, currentTick, BODY_TARGET_REFRESH_PENDING)); @@ -160,14 +160,14 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull Space * Records that a body target's terrain refresh was attempted by the apply loop. */ public synchronized void recordBodyTargetRefresh(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey, + @Nonnull UUID bodyUuid, @Nonnull WorldCollisionStreamingBounds bounds, boolean sleeping, long currentTick) { SpaceCollisionCache cache = spaces.computeIfAbsent(spaceId.value(), ignored -> new SpaceCollisionCache()); - CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyKey); + CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyUuid); if (target == null) { - cache.bodyTargets.put(bodyKey, new CachedBodyStreamingTarget(bounds, + cache.bodyTargets.put(bodyUuid, new CachedBodyStreamingTarget(bounds, sleeping, currentTick, currentTick)); @@ -190,7 +190,7 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull SpaceId spaceId, long maxAge = Math.max(1L, ttlTicks) * 2L; int removed = 0; - Iterator> iterator = + Iterator> iterator = cache.bodyTargets.object2ObjectEntrySet().iterator(); while (iterator.hasNext()) { CachedBodyStreamingTarget target = iterator.next().getValue(); @@ -1214,7 +1214,7 @@ private static int signExtend26(int value) { private static final class SpaceCollisionCache { private final Long2ObjectMap sections = new Long2ObjectOpenHashMap<>(); - private final Object2ObjectMap bodyTargets = + private final Object2ObjectMap bodyTargets = new Object2ObjectOpenHashMap<>(); private final Long2LongMap missingBlockChunkBackoffs = new Long2LongOpenHashMap(); private final Long2LongMap missingBlockSectionBackoffs = new Long2LongOpenHashMap(); @@ -1226,7 +1226,7 @@ private SpaceCollisionCache(@Nonnull SpaceCollisionCache other) { for (Long2ObjectMap.Entry entry : other.sections.long2ObjectEntrySet()) { sections.put(entry.getLongKey(), new CachedSection(entry.getValue())); } - for (Object2ObjectMap.Entry entry + for (Object2ObjectMap.Entry entry : other.bodyTargets.object2ObjectEntrySet()) { bodyTargets.put(entry.getKey(), new CachedBodyStreamingTarget(entry.getValue())); } From 4da46649fbb013c6ef224e39880878e75b56c0d3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:01:08 +0200 Subject: [PATCH 257/534] refactor(core): report world collision bodies by uuid Signed-off-by: Blovien --- .../commands/WorldCollisionPerfReportCommand.java | 4 ++-- .../profiling/WorldCollisionProfilingResource.java | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index 19499176..40dddc7c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -376,8 +376,8 @@ private static String formatMissingSectionSamples(@Nonnull Snapshot snapshot) { .append(sample.retainedEnvelopeStatus().name().toLowerCase(Locale.ROOT)) .append(" target=") .append(sample.target().targetType().name().toLowerCase(Locale.ROOT)); - if (sample.target().bodyKey() != null) { - builder.append(" body=").append(sample.target().bodyKey()); + if (sample.target().bodyUuid() != null) { + builder.append(" body=").append(sample.target().bodyUuid()); } if (sample.target().snapshotPosition() != null) { builder.append(" snapshot=(") diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResource.java index cb1d519c..3db2f360 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResource.java @@ -4,13 +4,13 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.AccessLevel; @@ -634,7 +634,7 @@ public String compact() { } public record StreamingTargetDiagnostic(@Nonnull StreamingTargetType targetType, - @Nullable RigidBodyKey bodyKey, + @Nullable UUID bodyUuid, @Nullable DiagnosticPosition snapshotPosition, @Nullable DiagnosticPosition livePosition) { @@ -648,17 +648,17 @@ public static StreamingTargetDiagnostic player(@Nonnull Vector3d position) { } @Nonnull - public static StreamingTargetDiagnostic body(@Nonnull RigidBodyKey bodyKey, + public static StreamingTargetDiagnostic body(@Nonnull UUID bodyUuid, @Nonnull Vector3f snapshotPosition, @Nonnull Vector3f livePosition) { return new StreamingTargetDiagnostic(StreamingTargetType.BODY, - bodyKey, + bodyUuid, DiagnosticPosition.from(snapshotPosition), DiagnosticPosition.from(livePosition)); } @Nonnull - public static StreamingTargetDiagnostic body(@Nonnull RigidBodyKey bodyKey, + public static StreamingTargetDiagnostic body(@Nonnull UUID bodyUuid, float snapshotX, float snapshotY, float snapshotZ, @@ -666,7 +666,7 @@ public static StreamingTargetDiagnostic body(@Nonnull RigidBodyKey bodyKey, float liveY, float liveZ) { return new StreamingTargetDiagnostic(StreamingTargetType.BODY, - bodyKey, + bodyUuid, DiagnosticPosition.from(snapshotX, snapshotY, snapshotZ), DiagnosticPosition.from(liveX, liveY, liveZ)); } From f41dda0d9d15388a2e56a4d160a7c205585c90ab Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:04:31 +0200 Subject: [PATCH 258/534] refactor(core): remove key control session overloads Signed-off-by: Blovien --- .../control/PhysicsControlSessions.java | 84 +------------------ 1 file changed, 1 insertion(+), 83 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 2895d78a..f9e794e7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -13,8 +13,6 @@ import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import java.util.UUID; import javax.annotation.Nonnull; @@ -51,91 +49,11 @@ public static boolean hasSession(@Nonnull Store store, return session != null && session.isActive(); } - /** - * Compatibility adapter for legacy body keys. Prefer the PhysicsStore ref overload when the - * caller already has live rows. - */ - @Deprecated(forRemoval = true) - public static void startSession(@Nonnull Store store, - @Nonnull Ref controllerRef, - @Nonnull RigidBodyKey bodyKey, - @Nonnull RigidBodyKey anchorBodyKey, - @Nullable Ref targetRef, - @Nonnull PhysicsBodyType originalBodyType, - float grabDistance, - @Nonnull Vector3f viewOffset, - @Nonnull Vector3f previousTarget) { - startSessionFromUuids(store, - controllerRef, - bodyKey.value(), - anchorBodyKey.value(), - null, - targetRef, - originalBodyType, - grabDistance, - viewOffset, - previousTarget); - } - - /** - * Compatibility adapter for legacy body and joint keys. Prefer the PhysicsStore ref overload - * when the caller already has live rows. - */ - @Deprecated(forRemoval = true) - public static void startSession(@Nonnull Store store, - @Nonnull Ref controllerRef, - @Nonnull RigidBodyKey bodyKey, - @Nonnull RigidBodyKey anchorBodyKey, - @Nullable JointKey controlJointKey, - @Nullable Ref targetRef, - @Nonnull PhysicsBodyType originalBodyType, - float grabDistance, - @Nonnull Vector3f viewOffset, - @Nonnull Vector3f previousTarget) { - startSessionFromUuids(store, - controllerRef, - bodyKey.value(), - anchorBodyKey.value(), - controlJointKey != null ? controlJointKey.value() : null, - targetRef, - originalBodyType, - grabDistance, - viewOffset, - previousTarget); - } - - /** - * Compatibility adapter for durable body UUIDs. Prefer the PhysicsStore ref overload when the - * caller already has live rows. - */ - @Deprecated(forRemoval = true) - public static void startSession(@Nonnull Store store, - @Nonnull Ref controllerRef, - @Nonnull UUID bodyUuid, - @Nonnull UUID anchorBodyUuid, - @Nullable JointKey controlJointKey, - @Nullable Ref targetRef, - @Nonnull PhysicsBodyType originalBodyType, - float grabDistance, - @Nonnull Vector3f viewOffset, - @Nonnull Vector3f previousTarget) { - startSessionFromUuids(store, - controllerRef, - bodyUuid, - anchorBodyUuid, - controlJointKey != null ? controlJointKey.value() : null, - targetRef, - originalBodyType, - grabDistance, - viewOffset, - previousTarget); - } - /** * Starts or replaces the controller entity's Impulse control session from durable row UUIDs. * Prefer the ref overload when the caller already has live PhysicsStore row refs. */ - private static void startSessionFromUuids(@Nonnull Store store, + public static void startSession(@Nonnull Store store, @Nonnull Ref controllerRef, @Nonnull UUID bodyUuid, @Nonnull UUID anchorBodyUuid, From c05c368af536fecfd9a9f4de8f505f0b1f1c7bbf Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:08:40 +0200 Subject: [PATCH 259/534] refactor(core): register legacy runtime bodies by uuid Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 34 +++++++++---------- .../resources/body/PhysicsBodyRegistry.java | 10 +++--- .../resources/body/PhysicsBodyRuntime.java | 9 +++-- .../resources/joint/PhysicsJointRegistry.java | 14 ++++---- 4 files changed, 31 insertions(+), 36 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 57094d09..a3f983d4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1778,22 +1778,22 @@ private void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { } @Nonnull - public RigidBodyKey addBodyOnOwner(@Nonnull RigidBodyKey bodyKey, + public UUID addBodyOnOwner(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @Nonnull BackendBodyHandle backendBodyHandle, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { assertCanAccessLiveBackendDirectly("add physics body"); - return addBodyDirect(bodyKey, spaceId, backendBodyHandle, kind, persistenceMode); + return addBodyDirect(bodyUuid, spaceId, backendBodyHandle, kind, persistenceMode); } @Nonnull - private RigidBodyKey addBodyDirect(@Nonnull RigidBodyKey bodyKey, + private UUID addBodyDirect(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @Nonnull BackendBodyHandle backendBodyHandle, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return bodyRuntime.addBody(bodyKey, spaceId, backendBodyHandle, kind, persistenceMode); + return bodyRuntime.addBody(bodyUuid, spaceId, backendBodyHandle, kind, persistenceMode); } @Override @@ -1919,11 +1919,11 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyUuids = bodyUuidsByRawBackendId.get(spaceId.value()); - UUID bodyUuid = bodyKey.value(); UUID existingUuid = bodyUuids != null ? bodyUuids.get(backendBodyHandle.value()) : null; if (existingUuid != null && !existingUuid.equals(bodyUuid)) { throw new IllegalArgumentException("Physics body is already registered as " + existingUuid); @@ -84,7 +82,7 @@ public void validateRegisterable(@Nonnull RigidBodyKey bodyKey, if (existingRegistration != null && (!existingRegistration.backendBodyHandle().equals(backendBodyHandle) || !existingRegistration.spaceId().equals(spaceId))) { - throw new IllegalArgumentException("Physics body key=" + bodyKey + throw new IllegalArgumentException("Physics body uuid=" + bodyUuid + " is already registered to another backend body"); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index a8292361..250cd303 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -12,7 +12,6 @@ import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.ArrayList; import java.util.UUID; import javax.annotation.Nonnull; @@ -59,7 +58,7 @@ public PhysicsBodyRuntime(@Nonnull PhysicsSpaceRuntime spaceRuntime, } @Nonnull - public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, + public UUID addBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @Nonnull BackendBodyHandle backendBodyHandle, @Nonnull PhysicsBodyKind kind, @@ -70,9 +69,9 @@ public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, throw new IllegalArgumentException("Physics backend body id=" + backendBodyId + " is not registered in space " + spaceId); } - bodyRegistry.validateRegisterable(bodyKey, backendBodyHandle, spaceId); + bodyRegistry.validateRegisterable(bodyUuid, backendBodyHandle, spaceId); PhysicsBodyRegistration registration = - bodyRegistry.registerBody(bodyKey, backendBodyHandle, spaceId, kind, persistenceMode); + bodyRegistry.registerBody(bodyUuid, backendBodyHandle, spaceId, kind, persistenceMode); PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(binding, backendBodyId); if (snapshot != null) { lifecycleState.putBodySnapshot(registration.bodyUuid(), @@ -82,7 +81,7 @@ public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, registration.persistenceMode()); } worldChangedMarker.run(); - return bodyKey; + return bodyUuid; } public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java index 98895a6c..61caf4e4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -26,11 +25,11 @@ public final class PhysicsJointRegistry { new Int2ObjectOpenHashMap<>(); @Nonnull - public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, + public PhysicsJointRegistration registerJoint(@Nonnull UUID jointUuid, @Nonnull SpaceId spaceId, @Nonnull BackendJointHandle backendJointHandle, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid, @Nonnull JointType type, float anchorAX, float anchorAY, @@ -49,7 +48,6 @@ public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, boolean motorEnabled, float motorTargetVelocity, float motorMaxForce) { - UUID jointUuid = jointKey.value(); UUID existingUuid = getJointUuid(spaceId, backendJointHandle); if (existingUuid != null && !existingUuid.equals(jointUuid)) { throw new IllegalArgumentException("Physics joint is already registered as " + existingUuid); @@ -58,7 +56,7 @@ public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, if (existingRegistration != null && (!existingRegistration.backendJointHandle().equals(backendJointHandle) || !existingRegistration.spaceId().equals(spaceId))) { - throw new IllegalArgumentException("Physics joint key=" + jointKey + throw new IllegalArgumentException("Physics joint uuid=" + jointUuid + " is already registered to another backend joint"); } if (existingRegistration != null) { @@ -67,8 +65,8 @@ public PhysicsJointRegistration registerJoint(@Nonnull JointKey jointKey, PhysicsJointRegistration registration = new PhysicsJointRegistration(jointUuid, backendJointHandle, spaceId, - bodyA.value(), - bodyB.value(), + bodyAUuid, + bodyBUuid, type, anchorAX, anchorAY, From 4c5dba627edc627fcdf8090fdcebab8dcef0ad79 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:10:44 +0200 Subject: [PATCH 260/534] refactor(core): remove dead rigid body state view Signed-off-by: Blovien --- .../simulation/view/RigidBodyStateView.java | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java deleted file mode 100644 index 6b82418c..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RigidBodyStateView.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.view; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodyPose; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Copied rigid body state returned by store tick lane queries. - * - *

    This value is a live-state query result, not a published snapshot frame entry. Use snapshot - * APIs when reader-side systems need frame-coherent body data.

    - */ -public record RigidBodyStateView(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodyType bodyType, - @Nonnull RigidBodyPose pose) { - - public RigidBodyStateView { - Objects.requireNonNull(bodyKey, "bodyKey"); - Objects.requireNonNull(bodyType, "bodyType"); - Objects.requireNonNull(pose, "pose"); - } -} From 5e8df815c577ad452ecee862f3d73a35424b856f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:16:49 +0200 Subject: [PATCH 261/534] refactor(core): remove internal key registration adapters Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 6 - .../PhysicsWorldRuntimeResource.java | 131 ------------------ .../body/PhysicsBodyRegistration.java | 18 --- .../joint/PhysicsJointRegistration.java | 65 --------- .../resources/joint/PhysicsJointRegistry.java | 22 --- 5 files changed, 242 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 41cab045..7025182f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -19,7 +19,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; @@ -42,11 +41,6 @@ public final class PhysicsStoreTopologyMutations { private PhysicsStoreTopologyMutations() { } - public static void destroyBody(@Nonnull Store store, - @Nonnull RigidBodyKey bodyKey) { - destroyBody(store, Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - public static void destroyBody(@Nonnull Store store, @Nonnull UUID bodyUuid) { PhysicsStoreThreading.requireWorldThread(store, "destroy a PhysicsStore body row"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index a3f983d4..96a64cdd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1796,16 +1796,6 @@ private UUID addBodyDirect(@Nonnull UUID bodyUuid, return bodyRuntime.addBody(bodyUuid, spaceId, backendBodyHandle, kind, persistenceMode); } - @Override - public void destroyBody(@Nonnull RigidBodyKey bodyKey) { - if (isAuthoritativePhysicsStoreActive()) { - destroyBody(bodyKey.value()); - return; - } - requireLegacyMutationAllowed("destroy physics body"); - destroyBody(bodyKey, true); - } - @Override public void destroyBody(@Nonnull UUID bodyUuid) { if (isAuthoritativePhysicsStoreActive()) { @@ -1824,17 +1814,6 @@ public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid) { return destroyBodyAsync(bodyUuid, true); } - public void destroyBody(@Nonnull RigidBodyKey bodyKey, boolean removeFromSpace) { - RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - if (isAuthoritativePhysicsStoreActive()) { - destroyBody(checkedBodyKey.value()); - return; - } - requireLegacyMutationAllowed("destroy physics body"); - runDirectRuntimeMutation("destroy physics body", - () -> destroyBodyDirect(checkedBodyKey.value(), removeFromSpace)); - } - public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { if (isAuthoritativePhysicsStoreActive()) { destroyBody(bodyUuid); @@ -1844,15 +1823,6 @@ public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyUuid, removeFromSpace)); } - @Nonnull - public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey, - boolean removeFromSpace) { - RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - return PhysicsMutationHandle.fromCompletion("destroy physics body", - checkedBodyKey, - destroyBodyAsync(checkedBodyKey.value(), removeFromSpace).completion()); - } - @Nonnull public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid, boolean removeFromSpace) { @@ -1884,12 +1854,6 @@ public PhysicsBodyRegistration getBodyRegistration(@Nonnull SpaceId spaceId, lon return bodyUuid != null ? bodyRegistry.getRegistration(bodyUuid) : null; } - @Nullable - public PhysicsBodyRegistration getRegistration(@Nonnull RigidBodyKey bodyKey) { - assertCanAccessLiveBackendDirectly("resolve physics body registration"); - return bodyRegistry.getRegistration(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - @Nullable public PhysicsBodyRegistration getRegistration(@Nonnull UUID bodyUuid) { assertCanAccessLiveBackendDirectly("resolve physics body registration"); @@ -2022,64 +1986,18 @@ private UUID addJointDirect(@Nonnull UUID jointUuid, return jointUuid; } - public boolean removeJoint(@Nonnull JointKey jointKey) { - requireLegacyMutationAllowed("remove physics joint"); - return callDirectRuntime("remove physics joint", () -> removeJointDirect(jointKey)); - } - - private boolean removeJointDirect(@Nonnull JointKey jointKey) { - UUID jointUuid = Objects.requireNonNull(jointKey, "jointKey").value(); - PhysicsJointRegistration registration = jointRegistry.getRegistration(jointUuid); - if (registration == null) { - return false; - } - - PhysicsSpaceBinding binding = spaceRuntime.getBinding(registration.spaceId()); - if (binding != null) { - binding.runtime().removeJoint(binding.backendSpaceHandle().value(), registration.backendJointHandle().value()); - } - jointRegistry.unregisterJoint(jointUuid); - markWorldChanged(); - return true; - } - @Nullable public JointKey getJointKey(@Nonnull SpaceId spaceId, long backendJointId) { assertCanAccessLiveBackendDirectly("resolve physics joint key"); return jointRegistry.getJointKey(spaceId, backendJointId); } - @Nullable - public PhysicsJointRegistration getJointRegistration(@Nonnull JointKey jointKey) { - assertCanAccessLiveBackendDirectly("resolve physics joint registration"); - return jointRegistry.getRegistration(Objects.requireNonNull(jointKey, "jointKey").value()); - } - @Nonnull public Collection getJointRegistrations() { assertCanAccessLiveBackendDirectly("list physics joint registrations"); return jointRegistry.getRegistrations(); } - @Nullable - public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB) { - assertCanAccessLiveBackendDirectly("resolve physics joint registration"); - return jointRegistry.findJointBetween(spaceId, - Objects.requireNonNull(bodyA, "bodyA").value(), - Objects.requireNonNull(bodyB, "bodyB").value()); - } - - @Nonnull - public PhysicsBodyRegistration requireBodyRegistration(@Nonnull RigidBodyKey bodyKey) { - PhysicsBodyRegistration registration = getRegistration(bodyKey); - if (registration == null) { - throw new IllegalArgumentException("Physics body key=" + bodyKey + " is not registered"); - } - return registration; - } - @Nonnull public Collection getBodyRegistrations() { assertCanAccessLiveBackendDirectly("list physics body registrations"); @@ -2180,18 +2098,6 @@ public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, return visualRuntime.hasAttachments(bodyUuid, bodyRef); } - public void registerBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { - UUID bodyUuid = bodyKey.value(); - if (hasAttachedAuthoritativePhysicsStore()) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, - "resolve body attachment key"); - authoritativeProjectionIndex("register physics body attachment") - .registerAttachment(bodyUuid, bodyRef, attachment); - return; - } - visualRuntime.registerAttachment(bodyUuid, null, attachment); - } - public void registerBodyAttachment(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref attachment) { @@ -2203,18 +2109,6 @@ public void registerBodyAttachment(@Nonnull UUID bodyUuid, visualRuntime.registerAttachment(bodyUuid, bodyRef, attachment); } - public void unregisterBodyAttachment(@Nonnull RigidBodyKey bodyKey, @Nonnull Ref attachment) { - UUID bodyUuid = bodyKey.value(); - if (hasAttachedAuthoritativePhysicsStore()) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, - "resolve body attachment key"); - authoritativeProjectionIndex("unregister physics body attachment") - .unregisterAttachment(bodyUuid, bodyRef, attachment); - return; - } - visualRuntime.unregisterAttachment(bodyUuid, null, attachment); - } - public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref attachment) { @@ -2416,10 +2310,6 @@ public void markBodyControlled(@Nonnull UUID bodyUuid) { } } - public void markBodyControlled(@Nonnull RigidBodyKey bodyKey) { - markBodyControlled(bodyKey.value()); - } - public void clearControlledBody(@Nonnull Ref bodyRef) { controlRuntime.clearControlledBody(bodyRef); } @@ -2432,10 +2322,6 @@ public void clearControlledBody(@Nonnull UUID bodyUuid) { } } - public void clearControlledBody(@Nonnull RigidBodyKey bodyKey) { - clearControlledBody(bodyKey.value()); - } - public boolean isBodyControlled(@Nonnull Ref bodyRef) { return controlRuntime.isBodyControlled(bodyRef); } @@ -2446,10 +2332,6 @@ public boolean isBodyControlled(@Nonnull UUID bodyUuid) { return bodyRef != null && controlRuntime.isBodyControlled(bodyRef); } - public boolean isBodyControlled(@Nonnull RigidBodyKey bodyKey) { - return isBodyControlled(bodyKey.value()); - } - @Nullable private Ref resolvePhysicsStoreBodyRef(@Nonnull UUID bodyUuid, @Nonnull String operation) { @@ -2470,24 +2352,11 @@ public void disableControlLifecycle() { controlRuntime.clear(); } - public void clearBodyRuntimeState(@Nonnull RigidBodyKey bodyKey) { - clearBodyRuntimeState(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - public void clearBodyRuntimeState(@Nonnull UUID bodyUuid) { requireLegacyMutationAllowed("clear physics body runtime state"); runDirectRuntimeMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyUuid)); } - @Nonnull - public PhysicsMutationHandle clearBodyRuntimeStateAsync( - @Nonnull RigidBodyKey bodyKey) { - RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - return PhysicsMutationHandle.fromCompletion("clear physics body runtime state", - checkedBodyKey, - clearBodyRuntimeStateAsync(checkedBodyKey.value()).completion()); - } - @Nonnull public PhysicsMutationHandle clearBodyRuntimeStateAsync( @Nonnull UUID bodyUuid) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java index 7d151f24..77565912 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; @@ -25,21 +24,4 @@ public record PhysicsBodyRegistration(@Nonnull UUID bodyUuid, Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(persistenceMode, "persistenceMode"); } - - public PhysicsBodyRegistration(@Nonnull RigidBodyKey bodyKey, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - this(Objects.requireNonNull(bodyKey, "bodyKey").value(), - backendBodyHandle, - spaceId, - kind, - persistenceMode); - } - - @Nonnull - public RigidBodyKey bodyKey() { - return RigidBodyKey.of(bodyUuid); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java index 959f195b..3cf26cc2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java @@ -2,8 +2,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import java.util.Objects; import java.util.UUID; @@ -44,67 +42,4 @@ public record PhysicsJointRegistration(@Nonnull UUID jointUuid, Objects.requireNonNull(bodyBUuid, "bodyBUuid"); Objects.requireNonNull(type, "type"); } - - public PhysicsJointRegistration(@Nonnull JointKey jointKey, - @Nonnull BackendJointHandle backendJointHandle, - @Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - this(Objects.requireNonNull(jointKey, "jointKey").value(), - backendJointHandle, - spaceId, - Objects.requireNonNull(bodyA, "bodyA").value(), - Objects.requireNonNull(bodyB, "bodyB").value(), - type, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce); - } - - @Nonnull - public JointKey jointKey() { - return JointKey.of(jointUuid); - } - - @Nonnull - public RigidBodyKey bodyA() { - return RigidBodyKey.of(bodyAUuid); - } - - @Nonnull - public RigidBodyKey bodyB() { - return RigidBodyKey.of(bodyBUuid); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java index 61caf4e4..363c6834 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java @@ -169,21 +169,6 @@ public UUID getJointUuid(@Nonnull SpaceId spaceId, return getJointUuid(spaceId, backendJointHandle.value()); } - @Nullable - public PhysicsJointRegistration findJointBetween(@Nonnull SpaceId spaceId, - @Nonnull UUID bodyAUuid, - @Nonnull UUID bodyBUuid) { - for (PhysicsJointRegistration registration : registrationsByUuid.values()) { - if (!registration.spaceId().equals(spaceId)) { - continue; - } - if (connects(bodyAUuid, bodyBUuid, registration.bodyAUuid(), registration.bodyBUuid())) { - return registration; - } - } - return null; - } - @Nonnull public Collection getRegistrations() { return new ArrayList<>(registrationsByUuid.values()); @@ -206,11 +191,4 @@ private void removeBackendIndex(@Nonnull PhysicsJointRegistration registration) } } - private static boolean connects(@Nonnull UUID expectedA, - @Nonnull UUID expectedB, - @Nonnull UUID actualA, - @Nonnull UUID actualB) { - return (expectedA.equals(actualA) && expectedB.equals(actualB)) - || (expectedA.equals(actualB) && expectedB.equals(actualA)); - } } From 8e6d2c8bb22673171f67901ed962f648db20ece9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:19:58 +0200 Subject: [PATCH 262/534] refactor(core): remove stale owner runtime helpers Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 124 ------------------ 1 file changed, 124 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 96a64cdd..8a56ed29 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -80,7 +80,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.util.ArrayList; @@ -1777,25 +1776,6 @@ private void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { spaceRuntime.validateStepModeSupported(stepMode); } - @Nonnull - public UUID addBodyOnOwner(@Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - assertCanAccessLiveBackendDirectly("add physics body"); - return addBodyDirect(bodyUuid, spaceId, backendBodyHandle, kind, persistenceMode); - } - - @Nonnull - private UUID addBodyDirect(@Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return bodyRuntime.addBody(bodyUuid, spaceId, backendBodyHandle, kind, persistenceMode); - } - @Override public void destroyBody(@Nonnull UUID bodyUuid) { if (isAuthoritativePhysicsStoreActive()) { @@ -1882,110 +1862,6 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref Date: Tue, 16 Jun 2026 18:23:38 +0200 Subject: [PATCH 263/534] refactor(core): remove backend key lookup adapters Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 29 ------------------- .../resources/body/PhysicsBodyRegistry.java | 13 --------- .../resources/joint/PhysicsJointRegistry.java | 13 --------- 3 files changed, 55 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 8a56ed29..891aad36 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -47,7 +47,6 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; @@ -58,7 +57,6 @@ import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; @@ -654,17 +652,6 @@ public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { () -> getBodySnapshotDirect(bodyUuid)); } - @Nonnull - @Override - public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - return getBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - - @Nullable - public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull RigidBodyKey bodyKey) { - return getBodySnapshotIfRegistered(Objects.requireNonNull(bodyKey, "bodyKey").value(), null); - } - @Nullable public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid) { return getBodySnapshotIfRegistered(bodyUuid, null); @@ -712,11 +699,6 @@ private PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull UUID bodyUuid) { return captureLiveBodySnapshot(registration); } - @Nullable - private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull RigidBodyKey bodyKey) { - return getBodySnapshotIfRegisteredDirect(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - @Nullable private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull UUID bodyUuid) { PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); @@ -1822,11 +1804,6 @@ private void destroyBodyDirect(@Nonnull UUID bodyUuid, boolean removeFromSpace) bodyRuntime.destroyBody(bodyUuid, removeFromSpace); } - @Nullable - public RigidBodyKey getBodyKey(@Nonnull SpaceId spaceId, long backendBodyId) { - return bodyRegistry.getBodyKey(spaceId, backendBodyId); - } - @Nullable public PhysicsBodyRegistration getBodyRegistration(@Nonnull SpaceId spaceId, long backendBodyId) { assertCanAccessLiveBackendDirectly("resolve physics body registration"); @@ -1862,12 +1839,6 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref getJointRegistrations() { assertCanAccessLiveBackendDirectly("list physics joint registrations"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java index d3aeefd2..701d6627 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -157,12 +156,6 @@ public Collection getRegistrationViews(@Nonnull Phy return views; } - @Nullable - public RigidBodyKey getBodyKey(@Nonnull SpaceId spaceId, long backendBodyId) { - UUID bodyUuid = getBodyUuid(spaceId, backendBodyId); - return bodyUuid != null ? RigidBodyKey.of(bodyUuid) : null; - } - @Nullable public UUID getBodyUuid(@Nonnull SpaceId spaceId, long backendBodyId) { Long2ObjectOpenHashMap bodyUuids = @@ -170,12 +163,6 @@ public UUID getBodyUuid(@Nonnull SpaceId spaceId, long backendBodyId) { return bodyUuids != null ? bodyUuids.get(backendBodyId) : null; } - @Nullable - public RigidBodyKey getBodyKey(@Nonnull SpaceId spaceId, - @Nonnull BackendBodyHandle backendBodyHandle) { - return getBodyKey(spaceId, backendBodyHandle.value()); - } - @Nonnull public Collection getRegistrations() { return new ArrayList<>(registrationsByUuid.values()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java index 363c6834..8d53eee1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -144,12 +143,6 @@ public PhysicsJointRegistration getRegistration(@Nonnull UUID jointUuid) { return registrationsByUuid.get(jointUuid); } - @Nullable - public JointKey getJointKey(@Nonnull SpaceId spaceId, long backendJointId) { - UUID jointUuid = getJointUuid(spaceId, backendJointId); - return jointUuid != null ? JointKey.of(jointUuid) : null; - } - @Nullable public UUID getJointUuid(@Nonnull SpaceId spaceId, long backendJointId) { Long2ObjectOpenHashMap jointUuids = @@ -157,12 +150,6 @@ public UUID getJointUuid(@Nonnull SpaceId spaceId, long backendJointId) { return jointUuids != null ? jointUuids.get(backendJointId) : null; } - @Nullable - public JointKey getJointKey(@Nonnull SpaceId spaceId, - @Nonnull BackendJointHandle backendJointHandle) { - return getJointKey(spaceId, backendJointHandle.value()); - } - @Nullable public UUID getJointUuid(@Nonnull SpaceId spaceId, @Nonnull BackendJointHandle backendJointHandle) { From 75d829dc5c13ab9f11df6e1cb1bb9fb813ed06af Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:27:06 +0200 Subject: [PATCH 264/534] refactor(core): use uuid live crucible bodies Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 5 +++-- .../crucible/ImpulseLiveCrucibleTests.java | 22 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index e22e5ef9..ddb745e4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -176,8 +176,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, /* * Backend-only bodies can be generated by systems such as streaming world collision. * Those bodies belong to the space/cache lifecycle and are removed when the space is - * deleted. Registered bodies are gameplay/runtime resources addressed by RigidBodyKey, - * so they still require an explicit clean/destroy before deleting the space. + * deleted. Registered bodies are gameplay/runtime resources addressed by durable body + * UUID or live PhysicsStore row ref, so they still require an explicit clean/destroy + * before deleting the space. */ int registeredBodies = countRegisteredBodies(resource, spaceId); return PhysicsStoreAsync.acceptOnWorldThread(world, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index e1321aa4..01f454d5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -25,7 +25,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -34,6 +33,7 @@ import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.joml.Vector3d; @@ -87,16 +87,16 @@ private static CompletionStage entityBodyFallsThroughEcs(CrucibleContex PhysicsStoreSpaceMutations.putSpaceGravity(physicsStore, spaceId, new Vector3f(0.0f, -9.81f, 0.0f)); - RigidBodyKey bodyKey = RigidBodyKey.random(); - submitLiveBody(physicsStore, spaceId, bodyKey, visualPosition); + UUID bodyUuid = UUID.randomUUID(); + submitLiveBody(physicsStore, spaceId, bodyUuid, visualPosition); - Ref ref = spawnLiveBlockBody(store, spaceId, bodyKey, visualPosition); + Ref ref = spawnLiveBlockBody(store, spaceId, bodyUuid, visualPosition); double startY = visualPosition.y; return context.waitApproxTicksOnWorld(40).thenApply(ignored -> bodyAndEntityMovedDown( store, ref, - bodyKey, + bodyUuid, startY)); } catch (ReflectiveOperationException e) { return CompletableFuture.failedFuture(e); @@ -105,7 +105,7 @@ private static CompletionStage entityBodyFallsThroughEcs(CrucibleContex private static boolean bodyAndEntityMovedDown(Store store, Ref ref, - RigidBodyKey bodyKey, + UUID bodyUuid, double startY) { if (!ref.isValid()) { @@ -118,7 +118,7 @@ private static boolean bodyAndEntityMovedDown(Store store, double transformY = transform.getPosition().y; PhysicsStoreBodySnapshot snapshot = physicsStore(store.getExternalData().getWorld()) .getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(bodyKey.value()); + .getBody(bodyUuid); if (snapshot == null) { return false; } @@ -141,12 +141,12 @@ private static SpaceId liveTestSpaceId(PhysicsWorldResource resource, World worl private static void submitLiveBody(Store store, SpaceId spaceId, - RigidBodyKey bodyKey, + UUID bodyUuid, Vector3d visualPosition) { PhysicsStoreThreading.requireWorldThread(store, "add Crucible live PhysicsStore body row"); BodyRowDescriptor row = PhysicsBodyRows.dynamicBody( PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), - bodyKey.value(), + bodyUuid, new Vector3f((float) visualPosition.x, (float) visualPosition.y, (float) visualPosition.z), @@ -172,7 +172,7 @@ private static Store physicsStore(World world) { private static Ref spawnLiveBlockBody(Store store, SpaceId spaceId, - RigidBodyKey bodyKey, + UUID bodyUuid, Vector3d visualPosition) { TimeResource time = store.getResource(TimeResource.getResourceType()); @@ -182,7 +182,7 @@ private static Ref spawnLiveBlockBody(Store store, new Vector3d(visualPosition)); holder.removeComponent(DESPAWN_TYPE); holder.addComponent(ATTACHMENT_TYPE, - new BodyAttachmentComponent(bodyKey.value(), + new BodyAttachmentComponent(bodyUuid, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY)); holder.addComponent(ImpulseControllableComponent.getComponentType(), From 6a7cd8404adc91d013a705bfad2fcdef4c322efa Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:30:18 +0200 Subject: [PATCH 265/534] refactor(core): remove resource body key adapters Signed-off-by: Blovien --- .../resources/PhysicsWorldResource.java | 66 +------------------ 1 file changed, 2 insertions(+), 64 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 8412457b..43983617 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -10,7 +10,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; @@ -23,7 +22,6 @@ import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; @@ -35,7 +33,7 @@ * Public alpha facade for a world's physics runtime resource. * *

    The concrete Impulse runtime lives in the internal package. Plugin-facing code should depend on - * this facade for explicit space lifecycle, world settings, body lifetime by key, + * this facade for explicit space lifecycle, world settings, body lifetime by durable UUID, * immutable snapshots, read-only registration views, public attachment/control hooks, and world * collision operations.

    * @@ -186,14 +184,6 @@ public abstract PhysicsMutationHandle createSpaceAsync( @Nonnull public abstract PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid); - /** - * Compatibility adapter for callers that still carry a legacy body key. - */ - @Nonnull - public PhysicsBodySnapshot getBodySnapshot(@Nonnull RigidBodyKey bodyKey) { - return getBodySnapshot(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - /** * Returns the number of body snapshots in the latest published frame. */ @@ -331,15 +321,6 @@ public abstract void setSpaceSettings(@Nonnull Ref spaceRef, public abstract PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings); - /** - * Destroys a registered body by stable key and removes it from its physics space. - * - *

    This overload is retained for compatibility with legacy event/facade APIs.

    - */ - public void destroyBody(@Nonnull RigidBodyKey bodyKey) { - destroyBody(Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - /** * Destroys a registered body by durable body UUID. * @@ -350,19 +331,6 @@ public void destroyBody(@Nonnull RigidBodyKey bodyKey) { @Nonnull public abstract PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid); - /** - * Queues destruction of a registered body by stable key. - * - *

    This overload is retained for compatibility with legacy event/facade APIs.

    - */ - @Nonnull - public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKey bodyKey) { - RigidBodyKey checkedBodyKey = Objects.requireNonNull(bodyKey, "bodyKey"); - return PhysicsMutationHandle.fromCompletion("destroy physics body", - checkedBodyKey, - destroyBodyAsync(checkedBodyKey.value()).completion()); - } - /** * Returns immutable registration metadata for a body UUID. * @@ -371,16 +339,6 @@ public PhysicsMutationHandle destroyBodyAsync(@Nonnull RigidBodyKe @Nullable public abstract PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid); - /** - * Returns immutable registration metadata for a body key. - * - *

    This overload is retained for compatibility with legacy event/facade APIs.

    - */ - @Nullable - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull RigidBodyKey bodyKey) { - return getBodyRegistrationView(bodyKey.value()); - } - /** * Returns immutable registration metadata for a live PhysicsStore body ref. */ @@ -413,16 +371,6 @@ public abstract int getBodyRegistrationCount( public abstract Collection getBodyRegistrationViews( @Nonnull PhysicsBodyKind kind); - /** - * Returns ECS attachments associated with a registered body key. - * - *

    This overload is retained for compatibility with legacy event/facade APIs.

    - */ - @Nonnull - public Collection> getBodyAttachments(@Nonnull RigidBodyKey bodyKey) { - return getBodyAttachments(bodyKey.value(), null); - } - /** * Returns ECS attachments associated with a durable body UUID and optional live body ref. */ @@ -434,23 +382,13 @@ public abstract Collection> getBodyAttachments(@Nonnull UUID bo * Returns ECS attachments associated with a live PhysicsStore body ref. * *

    Prefer this overload when a caller already has a body row ref, such as from a PhysicsStore - * raycast or copied registration. The key overload remains the compatibility boundary.

    + * raycast or copied registration.

    */ @Nonnull public Collection> getBodyAttachments(@Nonnull Ref bodyRef) { return List.of(); } - /** - * Returns whether a registered body has one or more ECS attachments without materializing the - * attachment collection. - * - *

    This overload is retained for compatibility with legacy event/facade APIs.

    - */ - public boolean hasBodyAttachments(@Nonnull RigidBodyKey bodyKey) { - return hasBodyAttachments(bodyKey.value(), null); - } - /** * Returns whether a durable body UUID and optional live body ref have one or more ECS attachments. */ From 295eaafebf4a14cc826f6eaa9123b02ba421aeb8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:38:53 +0200 Subject: [PATCH 266/534] refactor(core): remove key wrappers from snapshot events Signed-off-by: Blovien --- .../internal/resources/BackendBodyHandle.java | 4 +- .../resources/BackendJointHandle.java | 4 +- .../body/PhysicsBodyRegistrationView.java | 12 --- .../events/PhysicsBodyActivationEvent.java | 12 --- .../plugin/events/PhysicsContactEvent.java | 31 ------- .../plugin/events/PhysicsJointBreakEvent.java | 27 ------ .../snapshot/PhysicsBodySnapshotEntry.java | 18 ---- .../PublishedPhysicsBodyFrameStorage.java | 26 ------ .../PublishedPhysicsBodySnapshot.java | 82 ------------------- .../PublishedPhysicsBodySnapshotCursor.java | 6 -- .../PublishedPhysicsSnapshotFrame.java | 19 ----- 11 files changed, 4 insertions(+), 237 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java index 96c24d5c..c138d3fe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java @@ -3,8 +3,8 @@ /** * Backend-local physics-body handle. * - *

    This is an internal runtime identity. Stable plugin code should retain - * {@code RigidBodyKey}; core unwraps this value only at the backend-runtime boundary.

    + *

    This is an internal runtime identity. Plugin-facing code should retain durable body UUIDs + * or live PhysicsStore row refs and keep backend handles inside runtime resources.

    */ public record BackendBodyHandle(long value) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java index f3395571..674f16fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java @@ -3,8 +3,8 @@ /** * Backend-local physics-joint handle. * - *

    This is an internal runtime identity. Stable plugin code should retain {@code JointKey}; - * core unwraps this value only at the backend-runtime boundary.

    + *

    This is an internal runtime identity. Plugin-facing code should retain durable joint UUIDs + * or live PhysicsStore row refs and keep backend handles inside runtime resources.

    */ public record BackendJointHandle(long value) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java index d0dad4a9..69d9ded3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java @@ -19,16 +19,4 @@ public record PhysicsBodyRegistrationView(@Nonnull UUID bodyUuid, Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(persistenceMode, "persistenceMode"); } - - public PhysicsBodyRegistrationView(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - this(Objects.requireNonNull(bodyKey, "bodyKey").value(), spaceId, kind, persistenceMode); - } - - @Nonnull - public RigidBodyKey bodyKey() { - return RigidBodyKey.of(bodyUuid); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java index 6474eb94..16d5b561 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyActivationPhase; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -20,17 +19,6 @@ public record PhysicsBodyActivationEvent(@Nonnull SpaceId spaceId, Objects.requireNonNull(bodyUuid, "bodyUuid"); } - public PhysicsBodyActivationEvent(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyActivationPhase phase, - @Nonnull RigidBodyKey bodyKey) { - this(spaceId, phase, Objects.requireNonNull(bodyKey, "bodyKey").value()); - } - - @Nonnull - public RigidBodyKey bodyKey() { - return RigidBodyKey.of(bodyUuid); - } - @Nonnull @Override public PhysicsFrameEventKind kind() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java index a36dcc4c..0e248364 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -31,36 +30,6 @@ public record PhysicsContactEvent(@Nonnull SpaceId spaceId, normalOnB = new Vector3f(Objects.requireNonNull(normalOnB, "normalOnB")); } - public PhysicsContactEvent(@Nonnull SpaceId spaceId, - @Nonnull PhysicsContactPhase phase, - @Nonnull RigidBodyKey bodyAKey, - @Nonnull RigidBodyKey bodyBKey, - @Nonnull Vector3f pointOnA, - @Nonnull Vector3f pointOnB, - @Nonnull Vector3f normalOnB, - float distance, - float impulse) { - this(spaceId, - phase, - Objects.requireNonNull(bodyAKey, "bodyAKey").value(), - Objects.requireNonNull(bodyBKey, "bodyBKey").value(), - pointOnA, - pointOnB, - normalOnB, - distance, - impulse); - } - - @Nonnull - public RigidBodyKey bodyAKey() { - return RigidBodyKey.of(bodyAUuid); - } - - @Nonnull - public RigidBodyKey bodyBKey() { - return RigidBodyKey.of(bodyBUuid); - } - @Nonnull @Override public PhysicsFrameEventKind kind() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java index 9ade341c..af3e58ae 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java @@ -1,8 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.events; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -21,31 +19,6 @@ public record PhysicsJointBreakEvent(@Nonnull SpaceId spaceId, Objects.requireNonNull(jointUuid, "jointUuid"); } - public PhysicsJointBreakEvent(@Nonnull SpaceId spaceId, - @Nonnull JointKey jointKey, - @Nullable RigidBodyKey bodyAKey, - @Nullable RigidBodyKey bodyBKey) { - this(spaceId, - Objects.requireNonNull(jointKey, "jointKey").value(), - bodyAKey != null ? bodyAKey.value() : null, - bodyBKey != null ? bodyBKey.value() : null); - } - - @Nonnull - public JointKey jointKey() { - return JointKey.of(jointUuid); - } - - @Nullable - public RigidBodyKey bodyAKey() { - return bodyAUuid != null ? RigidBodyKey.of(bodyAUuid) : null; - } - - @Nullable - public RigidBodyKey bodyBKey() { - return bodyBUuid != null ? RigidBodyKey.of(bodyBUuid) : null; - } - @Nonnull @Override public PhysicsFrameEventKind kind() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java index a0af9d2e..28f0232f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; @@ -25,21 +24,4 @@ public record PhysicsBodySnapshotEntry(@Nonnull UUID bodyUuid, Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(persistenceMode, "persistenceMode"); } - - public PhysicsBodySnapshotEntry(@Nonnull RigidBodyKey bodyKey, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - this(Objects.requireNonNull(bodyKey, "bodyKey").value(), - snapshot, - spaceId, - kind, - persistenceMode); - } - - @Nonnull - public RigidBodyKey bodyKey() { - return RigidBodyKey.of(bodyUuid); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java index a1a53409..4c9a6c7d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java @@ -7,7 +7,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; @@ -433,24 +432,6 @@ void addSpace(@Nonnull SpaceId spaceId, long spaceEpoch, int bodyCount) { nextSpace++; } - void addBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - long spaceEpoch, - long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull PhysicsBodySnapshot snapshot) { - Objects.requireNonNull(bodyKey, "bodyKey"); - addBody(bodyKey.mostSignificantBits(), - bodyKey.leastSignificantBits(), - spaceId, - spaceEpoch, - registrationGeneration, - kind, - persistenceMode, - snapshot); - } - void addBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, long spaceEpoch, @@ -595,13 +576,6 @@ public UUID bodyUuid() { return PublishedPhysicsBodyFrameStorage.this.bodyUuid(index); } - @Nonnull - @Override - public RigidBodyKey bodyKey() { - return RigidBodyKey.of(bodyUuidMostSignificantBits(index), - bodyUuidLeastSignificantBits(index)); - } - @Nonnull @Override public SpaceId spaceId() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java index 4f89f7ee..50e1971b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java @@ -5,7 +5,6 @@ import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; @@ -72,51 +71,6 @@ public final class PublishedPhysicsBodySnapshot implements PublishedPhysicsBodyS @Nonnull private final PhysicsAxis shapeAxis; - public PublishedPhysicsBodySnapshot(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - long frameEpoch, - long worldEpoch, - long spaceEpoch, - long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - @Nonnull PhysicsBodyType bodyType, - boolean sleeping, - boolean sensor, - float centerOfMassOffsetY, - @Nonnull ShapeType shapeType, - @Nullable Vector3f boxHalfExtents, - float sphereRadius, - float halfHeight, - @Nonnull PhysicsAxis shapeAxis) { - this(bodyKeyMostSignificantBits(bodyKey), - bodyKeyLeastSignificantBits(bodyKey), - spaceId, - frameEpoch, - worldEpoch, - spaceEpoch, - registrationGeneration, - kind, - persistenceMode, - position, - rotation, - linearVelocity, - angularVelocity, - bodyType, - sleeping, - sensor, - centerOfMassOffsetY, - shapeType, - boxHalfExtents, - sphereRadius, - halfHeight, - shapeAxis); - } - public PublishedPhysicsBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, long frameEpoch, @@ -243,28 +197,6 @@ private PublishedPhysicsBodySnapshot(long bodyUuidMostSignificantBits, this.shapeAxis = Objects.requireNonNull(shapeAxis, "shapeAxis"); } - @Nonnull - public static PublishedPhysicsBodySnapshot from(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - long frameEpoch, - long worldEpoch, - long spaceEpoch, - long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull PhysicsBodySnapshot snapshot) { - return fromBits(bodyKeyMostSignificantBits(bodyKey), - bodyKeyLeastSignificantBits(bodyKey), - spaceId, - frameEpoch, - worldEpoch, - spaceEpoch, - registrationGeneration, - kind, - persistenceMode, - snapshot); - } - @Nonnull public static PublishedPhysicsBodySnapshot from(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, @@ -342,14 +274,6 @@ private static PublishedPhysicsBodySnapshot fromBits(long bodyUuidMostSignifican snapshot.shapeAxis()); } - private static long bodyKeyMostSignificantBits(@Nonnull RigidBodyKey bodyKey) { - return Objects.requireNonNull(bodyKey, "bodyKey").mostSignificantBits(); - } - - private static long bodyKeyLeastSignificantBits(@Nonnull RigidBodyKey bodyKey) { - return Objects.requireNonNull(bodyKey, "bodyKey").leastSignificantBits(); - } - private static long uuidMostSignificantBits(@Nonnull UUID bodyUuid) { return Objects.requireNonNull(bodyUuid, "bodyUuid").getMostSignificantBits(); } @@ -539,12 +463,6 @@ public UUID bodyUuid() { return new UUID(bodyUuidMostSignificantBits, bodyUuidLeastSignificantBits); } - @Nonnull - @Override - public RigidBodyKey bodyKey() { - return RigidBodyKey.of(bodyUuidMostSignificantBits, bodyUuidLeastSignificantBits); - } - @Nonnull @Override public SpaceId spaceId() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java index 9a355e91..b2da7380 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java @@ -7,7 +7,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Quaternionf; @@ -25,11 +24,6 @@ public interface PublishedPhysicsBodySnapshotCursor { @Nonnull UUID bodyUuid(); - @Nonnull - default RigidBodyKey bodyKey() { - return RigidBodyKey.of(bodyUuid()); - } - @Nonnull SpaceId spaceId(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java index 70920f84..d56b8e3e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java @@ -4,7 +4,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -447,24 +446,6 @@ public Builder addSpace(@Nonnull SpaceId spaceId, long spaceEpoch, int bodyCount return this; } - @Nonnull - public Builder addBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - long spaceEpoch, - long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull PhysicsBodySnapshot snapshot) { - bodyStorage.addBody(bodyKey, - spaceId, - spaceEpoch, - registrationGeneration, - kind, - persistenceMode, - snapshot); - return this; - } - @Nonnull public Builder addBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, From e49ceba004f398a5f050aeff91ddf1581fd2ecc4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:42:48 +0200 Subject: [PATCH 267/534] refactor(core): remove legacy physics key wrappers Signed-off-by: Blovien --- .../core/plugin/body/RigidBodyKey.java | 83 ------------------- .../impulse/core/plugin/joint/JointKey.java | 82 ------------------ 2 files changed, 165 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java deleted file mode 100644 index a2d50b0e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/RigidBodyKey.java +++ /dev/null @@ -1,83 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.body; - -import dev.hytalemodding.impulse.core.internal.math.UuidMath; -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Stable Impulse-side identity for a physics body. - * - *

    Backend {@code PhysicsBody} handles may change when spaces migrate between - * backends. This id is the durable handle used by ECS attachments, persistence, - * snapshots, compatibility lookups, and PhysicsStore row-local body commands.

    - */ -public final class RigidBodyKey { - - private final long mostSignificantBits; - private final long leastSignificantBits; - - public RigidBodyKey(@Nonnull UUID value) { - this(Objects.requireNonNull(value, "value").getMostSignificantBits(), - value.getLeastSignificantBits()); - } - - private RigidBodyKey(long mostSignificantBits, - long leastSignificantBits) { - this.mostSignificantBits = mostSignificantBits; - this.leastSignificantBits = leastSignificantBits; - } - - @Nonnull - public static RigidBodyKey random() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - long mostSignificantBits = UuidMath.version4MostSignificantBits(random.nextLong()); - long leastSignificantBits = UuidMath.ietfVariantLeastSignificantBits(random.nextLong()); - return new RigidBodyKey(mostSignificantBits, leastSignificantBits); - } - - @Nonnull - public static RigidBodyKey of(@Nonnull UUID value) { - return new RigidBodyKey(value); - } - - @Nonnull - public static RigidBodyKey of(long mostSignificantBits, - long leastSignificantBits) { - return new RigidBodyKey(mostSignificantBits, leastSignificantBits); - } - - @Nonnull - public UUID value() { - return new UUID(mostSignificantBits, leastSignificantBits); - } - - public long mostSignificantBits() { - return mostSignificantBits; - } - - public long leastSignificantBits() { - return leastSignificantBits; - } - - @Override - public boolean equals(Object other) { - return this == other - || other instanceof RigidBodyKey key - && mostSignificantBits == key.mostSignificantBits - && leastSignificantBits == key.leastSignificantBits; - } - - @Override - public int hashCode() { - long bits = mostSignificantBits ^ leastSignificantBits; - return (int) (bits >> Integer.SIZE) ^ (int) bits; - } - - @Nonnull - @Override - public String toString() { - return value().toString(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java deleted file mode 100644 index 2a15f92a..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/joint/JointKey.java +++ /dev/null @@ -1,82 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.joint; - -import dev.hytalemodding.impulse.core.internal.math.UuidMath; -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Stable Impulse-side identity for a physics joint. - * - *

    Backend {@code PhysicsJoint} handles are live backend objects. This id is the identity - * component state and plugin-facing lifecycle code should retain.

    - */ -public final class JointKey { - - private final long mostSignificantBits; - private final long leastSignificantBits; - - public JointKey(@Nonnull UUID value) { - this(Objects.requireNonNull(value, "value").getMostSignificantBits(), - value.getLeastSignificantBits()); - } - - private JointKey(long mostSignificantBits, - long leastSignificantBits) { - this.mostSignificantBits = mostSignificantBits; - this.leastSignificantBits = leastSignificantBits; - } - - @Nonnull - public static JointKey random() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - long mostSignificantBits = UuidMath.version4MostSignificantBits(random.nextLong()); - long leastSignificantBits = UuidMath.ietfVariantLeastSignificantBits(random.nextLong()); - return new JointKey(mostSignificantBits, leastSignificantBits); - } - - @Nonnull - public static JointKey of(@Nonnull UUID value) { - return new JointKey(value); - } - - @Nonnull - public static JointKey of(long mostSignificantBits, - long leastSignificantBits) { - return new JointKey(mostSignificantBits, leastSignificantBits); - } - - @Nonnull - public UUID value() { - return new UUID(mostSignificantBits, leastSignificantBits); - } - - public long mostSignificantBits() { - return mostSignificantBits; - } - - public long leastSignificantBits() { - return leastSignificantBits; - } - - @Override - public boolean equals(Object other) { - return this == other - || other instanceof JointKey key - && mostSignificantBits == key.mostSignificantBits - && leastSignificantBits == key.leastSignificantBits; - } - - @Override - public int hashCode() { - long bits = mostSignificantBits ^ leastSignificantBits; - return (int) (bits >> Integer.SIZE) ^ (int) bits; - } - - @Nonnull - @Override - public String toString() { - return value().toString(); - } -} From f122173adeb24f2f508f1907eb646a517ead7717 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:51:08 +0200 Subject: [PATCH 268/534] refactor(core): rename store tick profiling metrics Signed-off-by: Blovien --- .../WorldCollisionPerfReportCommand.java | 14 +-- .../PhysicsRuntimeProfilingResource.java | 116 +++++++++--------- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index 40dddc7c..f21d5903 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -92,15 +92,15 @@ private static void sendReport(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Physics snapshot avg ms/completedStep=" + formatAverageMillis(cumulativeStep.getSnapshotNanos(), cumulativeStep.getTickSamples()))); ctx.sender().sendMessage(Message.raw("Physics store tick avg queued/run/latency ms=" - + formatAverageMillis(cumulativeStep.getOwnerQueuedNanos(), cumulativeStep.getTickSamples()) - + "/" + formatAverageMillis(cumulativeStep.getOwnerRunNanos(), cumulativeStep.getTickSamples()) + + formatAverageMillis(cumulativeStep.getStoreTickQueuedNanos(), cumulativeStep.getTickSamples()) + + "/" + formatAverageMillis(cumulativeStep.getStoreTickRunNanos(), cumulativeStep.getTickSamples()) + "/" + formatAverageMillis( - cumulativeStep.getOwnerQueuedNanos() + cumulativeStep.getOwnerRunNanos(), + cumulativeStep.getStoreTickQueuedNanos() + cumulativeStep.getStoreTickRunNanos(), cumulativeStep.getTickSamples()) - + " storeTPS latest/avg=" + formatHertz(latestStep.getOwnerStepIntervalNanos()) - + "/" + formatAverageHertz(cumulativeStep.getOwnerStepIntervalNanos(), - cumulativeStep.getOwnerStepRateSamples()) - + " maxGapMs=" + formatMillis(cumulativeStep.getMaxOwnerStepIntervalNanos()) + + " storeTPS latest/avg=" + formatHertz(latestStep.getStoreTickStepIntervalNanos()) + + "/" + formatAverageHertz(cumulativeStep.getStoreTickStepIntervalNanos(), + cumulativeStep.getStoreTickStepRateSamples()) + + " maxGapMs=" + formatMillis(cumulativeStep.getMaxStoreTickStepIntervalNanos()) + " pendingSkips=" + cumulativeStep.getSkippedPendingSteps() + " pendingAge avg/max ms=" + formatAverageMillis(cumulativeStep.getPendingStepAgeNanos(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java index 23392a2e..e1c4f690 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java @@ -34,7 +34,7 @@ public class PhysicsRuntimeProfilingResource implements Resource { @Nullable private transient SyncCollector activeSyncCollector; - private transient long previousOwnerStepCompletedNanos; + private transient long previousStoreTickStepCompletedNanos; public PhysicsRuntimeProfilingResource() { } @@ -128,16 +128,16 @@ public synchronized void recordStep(int spaces, int bodySnapshots, int spatialIndexCells, long snapshotNanos, - long ownerQueuedNanos, - long ownerRunNanos) { + long storeTickQueuedNanos, + long storeTickRunNanos) { recordStep(spaces, substeps, nanos, bodySnapshots, spatialIndexCells, snapshotNanos, - ownerQueuedNanos, - ownerRunNanos, + storeTickQueuedNanos, + storeTickRunNanos, 0L, PhysicsStepPhaseStats.unavailable()); } @@ -148,8 +148,8 @@ public synchronized void recordStep(int spaces, int bodySnapshots, int spatialIndexCells, long snapshotNanos, - long ownerQueuedNanos, - long ownerRunNanos, + long storeTickQueuedNanos, + long storeTickRunNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats) { recordStep(spaces, substeps, @@ -157,8 +157,8 @@ public synchronized void recordStep(int spaces, bodySnapshots, spatialIndexCells, snapshotNanos, - ownerQueuedNanos, - ownerRunNanos, + storeTickQueuedNanos, + storeTickRunNanos, 0L, nativePhaseStats); } @@ -169,9 +169,9 @@ public synchronized void recordStep(int spaces, int bodySnapshots, int spatialIndexCells, long snapshotNanos, - long ownerQueuedNanos, - long ownerRunNanos, - long ownerCompletedNanos, + long storeTickQueuedNanos, + long storeTickRunNanos, + long storeTickCompletedNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats) { recordStep(spaces, substeps, @@ -179,9 +179,9 @@ public synchronized void recordStep(int spaces, bodySnapshots, spatialIndexCells, snapshotNanos, - ownerQueuedNanos, - ownerRunNanos, - ownerCompletedNanos, + storeTickQueuedNanos, + storeTickRunNanos, + storeTickCompletedNanos, nativePhaseStats, 0, 0L, @@ -194,9 +194,9 @@ public synchronized void recordStep(int spaces, int bodySnapshots, int spatialIndexCells, long snapshotNanos, - long ownerQueuedNanos, - long ownerRunNanos, - long ownerCompletedNanos, + long storeTickQueuedNanos, + long storeTickRunNanos, + long storeTickCompletedNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, int preStepDrainedMutations, long preStepDrainRunNanos, @@ -209,9 +209,9 @@ public synchronized void recordStep(int spaces, snapshot.setBodySnapshots(bodySnapshots); snapshot.setSpatialIndexCells(spatialIndexCells); snapshot.setSnapshotNanos(snapshotNanos); - snapshot.setOwnerQueuedNanos(ownerQueuedNanos); - snapshot.setOwnerRunNanos(ownerRunNanos); - snapshot.recordOwnerStepInterval(recordOwnerStepInterval(ownerCompletedNanos)); + snapshot.setStoreTickQueuedNanos(storeTickQueuedNanos); + snapshot.setStoreTickRunNanos(storeTickRunNanos); + snapshot.recordStoreTickStepInterval(recordStoreTickStepInterval(storeTickCompletedNanos)); snapshot.setNativePhaseStats(nativePhaseStats); snapshot.recordPreStepDrain(Math.max(0, preStepDrainedMutations), Math.max(0L, preStepDrainRunNanos), @@ -317,7 +317,7 @@ public synchronized void reset() { latestVisual.reset(); worstVisual.reset(); activeSyncCollector = null; - previousOwnerStepCompletedNanos = 0L; + previousStoreTickStepCompletedNanos = 0L; } @Nonnull @@ -335,23 +335,23 @@ public synchronized PhysicsRuntimeProfilingResource clone() { copy.cumulativeVisual.copyFrom(cumulativeVisual); copy.latestVisual.copyFrom(latestVisual); copy.worstVisual.copyFrom(worstVisual); - copy.previousOwnerStepCompletedNanos = previousOwnerStepCompletedNanos; + copy.previousStoreTickStepCompletedNanos = previousStoreTickStepCompletedNanos; return copy; } - private long recordOwnerStepInterval(long ownerCompletedNanos) { - if (ownerCompletedNanos <= 0L) { + private long recordStoreTickStepInterval(long storeTickCompletedNanos) { + if (storeTickCompletedNanos <= 0L) { return 0L; } - if (previousOwnerStepCompletedNanos <= 0L) { - previousOwnerStepCompletedNanos = ownerCompletedNanos; + if (previousStoreTickStepCompletedNanos <= 0L) { + previousStoreTickStepCompletedNanos = storeTickCompletedNanos; return 0L; } - if (ownerCompletedNanos <= previousOwnerStepCompletedNanos) { + if (storeTickCompletedNanos <= previousStoreTickStepCompletedNanos) { return 0L; } - long intervalNanos = ownerCompletedNanos - previousOwnerStepCompletedNanos; - previousOwnerStepCompletedNanos = ownerCompletedNanos; + long intervalNanos = storeTickCompletedNanos - previousStoreTickStepCompletedNanos; + previousStoreTickStepCompletedNanos = storeTickCompletedNanos; return intervalNanos; } @@ -408,17 +408,17 @@ public static final class StepSnapshot { @Setter private long snapshotNanos; @Setter - private long ownerQueuedNanos; + private long storeTickQueuedNanos; @Setter - private long ownerRunNanos; + private long storeTickRunNanos; private int preStepDrainedMutations; private int maxPreStepDrainedMutations; private long preStepDrainRunNanos; private int lateMutationBacklogAtStep; private int maxLateMutationBacklogAtStep; - private int ownerStepRateSamples; - private long ownerStepIntervalNanos; - private long maxOwnerStepIntervalNanos; + private int storeTickStepRateSamples; + private long storeTickStepIntervalNanos; + private long maxStoreTickStepIntervalNanos; @Setter private int skippedPendingSteps; @Setter @@ -454,16 +454,16 @@ public void copyFrom(@Nonnull StepSnapshot other) { spatialIndexCells = other.spatialIndexCells; tickNanos = other.tickNanos; snapshotNanos = other.snapshotNanos; - ownerQueuedNanos = other.ownerQueuedNanos; - ownerRunNanos = other.ownerRunNanos; + storeTickQueuedNanos = other.storeTickQueuedNanos; + storeTickRunNanos = other.storeTickRunNanos; preStepDrainedMutations = other.preStepDrainedMutations; maxPreStepDrainedMutations = other.maxPreStepDrainedMutations; preStepDrainRunNanos = other.preStepDrainRunNanos; lateMutationBacklogAtStep = other.lateMutationBacklogAtStep; maxLateMutationBacklogAtStep = other.maxLateMutationBacklogAtStep; - ownerStepRateSamples = other.ownerStepRateSamples; - ownerStepIntervalNanos = other.ownerStepIntervalNanos; - maxOwnerStepIntervalNanos = other.maxOwnerStepIntervalNanos; + storeTickStepRateSamples = other.storeTickStepRateSamples; + storeTickStepIntervalNanos = other.storeTickStepIntervalNanos; + maxStoreTickStepIntervalNanos = other.maxStoreTickStepIntervalNanos; skippedPendingSteps = other.skippedPendingSteps; pendingStepAgeNanos = other.pendingStepAgeNanos; maxPendingStepAgeNanos = other.maxPendingStepAgeNanos; @@ -492,8 +492,8 @@ public void add(@Nonnull StepSnapshot other) { spatialIndexCells += other.spatialIndexCells; tickNanos += other.tickNanos; snapshotNanos += other.snapshotNanos; - ownerQueuedNanos += other.ownerQueuedNanos; - ownerRunNanos += other.ownerRunNanos; + storeTickQueuedNanos += other.storeTickQueuedNanos; + storeTickRunNanos += other.storeTickRunNanos; preStepDrainedMutations += other.preStepDrainedMutations; maxPreStepDrainedMutations = Math.max(maxPreStepDrainedMutations, other.maxPreStepDrainedMutations); @@ -501,10 +501,10 @@ public void add(@Nonnull StepSnapshot other) { lateMutationBacklogAtStep += other.lateMutationBacklogAtStep; maxLateMutationBacklogAtStep = Math.max(maxLateMutationBacklogAtStep, other.maxLateMutationBacklogAtStep); - ownerStepRateSamples += other.ownerStepRateSamples; - ownerStepIntervalNanos += other.ownerStepIntervalNanos; - maxOwnerStepIntervalNanos = Math.max(maxOwnerStepIntervalNanos, - other.maxOwnerStepIntervalNanos); + storeTickStepRateSamples += other.storeTickStepRateSamples; + storeTickStepIntervalNanos += other.storeTickStepIntervalNanos; + maxStoreTickStepIntervalNanos = Math.max(maxStoreTickStepIntervalNanos, + other.maxStoreTickStepIntervalNanos); skippedPendingSteps += other.skippedPendingSteps; pendingStepAgeNanos += other.pendingStepAgeNanos; maxPendingStepAgeNanos = Math.max(maxPendingStepAgeNanos, @@ -535,16 +535,16 @@ public void reset() { spatialIndexCells = 0; tickNanos = 0L; snapshotNanos = 0L; - ownerQueuedNanos = 0L; - ownerRunNanos = 0L; + storeTickQueuedNanos = 0L; + storeTickRunNanos = 0L; preStepDrainedMutations = 0; maxPreStepDrainedMutations = 0; preStepDrainRunNanos = 0L; lateMutationBacklogAtStep = 0; maxLateMutationBacklogAtStep = 0; - ownerStepRateSamples = 0; - ownerStepIntervalNanos = 0L; - maxOwnerStepIntervalNanos = 0L; + storeTickStepRateSamples = 0; + storeTickStepIntervalNanos = 0L; + maxStoreTickStepIntervalNanos = 0L; skippedPendingSteps = 0; pendingStepAgeNanos = 0L; maxPendingStepAgeNanos = 0L; @@ -565,16 +565,16 @@ public void reset() { nativeSnapshotNanos = 0L; } - public void recordOwnerStepInterval(long intervalNanos) { + public void recordStoreTickStepInterval(long intervalNanos) { if (intervalNanos <= 0L) { - ownerStepRateSamples = 0; - ownerStepIntervalNanos = 0L; - maxOwnerStepIntervalNanos = 0L; + storeTickStepRateSamples = 0; + storeTickStepIntervalNanos = 0L; + maxStoreTickStepIntervalNanos = 0L; return; } - ownerStepRateSamples = 1; - ownerStepIntervalNanos = intervalNanos; - maxOwnerStepIntervalNanos = intervalNanos; + storeTickStepRateSamples = 1; + storeTickStepIntervalNanos = intervalNanos; + maxStoreTickStepIntervalNanos = intervalNanos; } public void recordPreStepDrain(int drainedMutations, From 6d10db373722d1bf813a12666464724bb98d0881 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 18:53:08 +0200 Subject: [PATCH 269/534] refactor(core): remove owner lane terminology Signed-off-by: Blovien --- .../core/internal/resources/PhysicsVisualRuntime.java | 2 +- .../core/internal/resources/PhysicsWorldLifecycleState.java | 6 +++--- .../core/internal/systems/debug/PhysicsDebugSystem.java | 2 +- .../core/plugin/body/PhysicsBodyRegistrationView.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index d4895dc6..409634d4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -685,7 +685,7 @@ private record BodyVisualInterestRefState(@Nonnull Ref bodyRef, * {@code VisualOcclusionMode.CULL} is enabled, so materialization and sync * share one occlusion decision window instead of spending duplicate raycasts. Raycasts are * submitted asynchronously; callers poll this state and use the last-known visibility while a - * owner query is still incomplete.

    + * store tick query is still incomplete.

    */ public static final class BodyVisualInterestState { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index 3c917443..0b85d1cd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -167,14 +167,14 @@ public void clearBodySnapshots() { snapshotState.clearBodySnapshots(); } - public void publishDetachedOwnerRegistrationViews(@Nonnull PhysicsBodyRegistry bodyRegistry) { + public void publishDetachedRegistrationViews(@Nonnull PhysicsBodyRegistry bodyRegistry) { bodyRegistry.publishLiveRegistrationViews(); } public void markWorldChanged(@Nonnull PhysicsBodyRegistry bodyRegistry, - boolean ownerExecutorAttached) { + boolean storeTickAttached) { snapshotState.markWorldChanged(); - if (!ownerExecutorAttached) { + if (!storeTickAttached) { bodyRegistry.publishLiveRegistrationViews(); } eventState.publishEmpty(snapshotState.worldEpoch(), snapshotState.getLatestPublishedFrame()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 95cdd613..b331c7f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -555,7 +555,7 @@ static final class DebugQueryCache { /* * Debug overlays run in the tick path, so contact/joint queries are cached and polled. - * If an owner query is still incomplete, the renderer uses the previous completed result + * If a store tick query is still incomplete, the renderer uses the previous completed result * or skips that overlay for the frame instead of joining the world thread. */ @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java index 69d9ded3..e02200b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java @@ -6,7 +6,7 @@ import javax.annotation.Nonnull; /** - * Immutable body registration metadata safe for public and off-owner callers. + * Immutable body registration metadata safe for public callers outside the store tick lane. */ public record PhysicsBodyRegistrationView(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, From b5738d4746a0e15ec3f595a850dd7569f7e0ea7e Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:00:20 +0200 Subject: [PATCH 270/534] refactor(core): rename physics store entity helpers Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 2 +- .../crucible/ImpulseApiCrucibleTests.java | 28 ++-- .../crucible/ImpulseLiveCrucibleTests.java | 24 +-- .../PhysicsStoreBenchmarkQueries.java | 2 +- .../crucible/PhysicsStoreCrucibleSupport.java | 26 +-- .../PhysicsKinematicControlSystem.java | 2 +- .../PhysicsStoreControlSessionMutations.java | 2 +- .../PhysicsStoreSpaceMutations.java | 18 +-- .../PhysicsStoreTopologyMutations.java | 6 +- .../PersistentPhysicsStoreResource.java | 2 +- .../systems/BodyBindingSystem.java | 2 +- .../systems/JointBindingSystem.java | 2 +- .../systems/PersistenceCaptureSystem.java | 2 +- .../systems/StaleBodyRemovalSystem.java | 2 +- .../systems/WorldCollisionIndexSystem.java | 2 +- .../internal/resources/BackendBodyHandle.java | 2 +- .../resources/BackendJointHandle.java | 2 +- .../PhysicsWorldRuntimeResource.java | 6 +- .../body/PhysicsBodySnapshotRefVisitor.java | 2 +- .../systems/sync/PhysicsSyncSystem.java | 4 +- .../control/PhysicsControlSessions.java | 8 +- ...criptor.java => BodyEntityDescriptor.java} | 32 ++-- ...BodyRows.java => PhysicsBodyEntities.java} | 22 +-- ...intRows.java => PhysicsJointEntities.java} | 14 +- ...wRefs.java => PhysicsStoreEntityRefs.java} | 14 +- .../physicsstore/PhysicsStoreThreading.java | 2 +- .../components/ColliderComponent.java | 2 +- .../components/CollisionFilterComponent.java | 2 +- .../CollisionLodSettingsComponent.java | 2 +- .../components/DynamicsComponent.java | 2 +- .../ExtensionSettingsComponent.java | 2 +- .../components/JointComponent.java | 2 +- .../components/MaterialComponent.java | 2 +- .../components/ShapeComponent.java | 2 +- .../components/SolverSettingsComponent.java | 2 +- .../components/SpaceComponent.java | 2 +- .../components/TerrainColliderComponent.java | 2 +- .../components/UuidComponent.java | 2 +- ...isualMaterializationSettingsComponent.java | 2 +- .../VisualSyncSettingsComponent.java | 2 +- .../components/WorldCollisionComponent.java | 2 +- .../resources/PhysicsWorldResource.java | 12 +- .../core/plugin/simulation/JointType.java | 2 +- .../simulation/view/RaycastHitView.java | 2 +- .../commands/ExamplePhysicsUtils.java | 148 +++++++++--------- .../examples/commands/ForcesCommand.java | 2 +- .../examples/commands/GrabCommand.java | 14 +- .../examples/commands/JointsCommand.java | 6 +- .../commands/PhysicsStoreExampleCommands.java | 4 +- .../stress/StressBenchmarkCommand.java | 16 +- .../commands/stress/StressBodiesCommand.java | 16 +- .../commands/stress/StressJointsCommand.java | 8 +- .../stress/StressRawBodiesCommand.java | 12 +- .../explosive/ExplosiveBlockRuntime.java | 2 +- 54 files changed, 255 insertions(+), 249 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{BodyRowDescriptor.java => BodyEntityDescriptor.java} (69%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsBodyRows.java => PhysicsBodyEntities.java} (90%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsJointRows.java => PhysicsJointEntities.java} (80%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreRowRefs.java => PhysicsStoreEntityRefs.java} (71%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index ddb745e4..4856632b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -177,7 +177,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, * Backend-only bodies can be generated by systems such as streaming world collision. * Those bodies belong to the space/cache lifecycle and are removed when the space is * deleted. Registered bodies are gameplay/runtime resources addressed by durable body - * UUID or live PhysicsStore row ref, so they still require an explicit clean/destroy + * UUID or live PhysicsStore entity ref, so they still require an explicit clean/destroy * before deleting the space. */ int registeredBodies = countRegisteredBodies(resource, spaceId); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 7423b897..250bf635 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -19,8 +19,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -109,7 +109,7 @@ private static CrucibleSuite runtimeStabilitySuite() { "Explicit space was not registered correctly"), CrucibleTestCase.async("clear populated spaces", ImpulseApiCrucibleTests::clearPopulatedSpaces, - "PhysicsStore row cleanup did not remove populated runtime spaces"), + "PhysicsStore entity cleanup did not remove populated runtime spaces"), CrucibleTestCase.async("detached unregister removes body", ImpulseApiCrucibleTests::detachedUnregisterRemovesBackendBody, "Detached unregister did not remove the backend body"), @@ -236,7 +236,7 @@ private static CompletionStage populatedBodyCleanup( PhysicsSpaceSettings.defaults()); Ref bodyRef = addCrucibleBox(store, spaceId, UUID.randomUUID()); return context.waitApproxTicksOnWorld(4) - .thenCompose(_ -> removeBodyRowAndWait(context, store, bodyRef)) + .thenCompose(_ -> removeBodyEntityAndWait(context, store, bodyRef)) .thenApply(_ -> { boolean spaceEmpty = PhysicsStoreDiagnostics.bodyCount(store, spaceId) == 0; boolean noRegistrations = resource.getBodyRegistrationViews().isEmpty(); @@ -252,7 +252,7 @@ private static CompletionStage populatedBodyCleanup( } } - private static CompletionStage removeBodyRowAndWait(@Nonnull CrucibleContext context, + private static CompletionStage removeBodyEntityAndWait(@Nonnull CrucibleContext context, @Nonnull Store store, @Nonnull Ref bodyRef) { if (bodyRef.isValid()) { @@ -270,7 +270,7 @@ private static Ref addCrucibleBox(@Nonnull Store sto @Nonnull SpaceId spaceId, @Nonnull UUID bodyUuid) { UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId); - BodyRowDescriptor row = PhysicsBodyRows.dynamicBody(spaceUuid, + BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody(spaceUuid, bodyUuid, new Vector3f(0.0f, 5.0f, 0.0f), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), @@ -279,14 +279,14 @@ private static Ref addCrucibleBox(@Nonnull Store sto null, PhysicsBodyPersistenceMode.RUNTIME_ONLY); return store.addEntity(PhysicsStoreEntities.bodyHolder(store, - row.bodyUuid(), - row.body(), - row.dynamics(), - row.target(), - row.collider(), - row.shape(), - row.material(), - row.filter()), AddReason.SPAWN); + descriptor.bodyUuid(), + descriptor.body(), + descriptor.dynamics(), + descriptor.target(), + descriptor.collider(), + descriptor.shape(), + descriptor.material(), + descriptor.filter()), AddReason.SPAWN); } private static CompletionStage settingsRoundTrip(@Nonnull CrucibleContext context) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 01f454d5..18f48327 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; @@ -143,8 +143,8 @@ private static void submitLiveBody(Store store, SpaceId spaceId, UUID bodyUuid, Vector3d visualPosition) { - PhysicsStoreThreading.requireWorldThread(store, "add Crucible live PhysicsStore body row"); - BodyRowDescriptor row = PhysicsBodyRows.dynamicBody( + PhysicsStoreThreading.requireWorldThread(store, "add Crucible live PhysicsStore body entity"); + BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody( PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), bodyUuid, new Vector3f((float) visualPosition.x, @@ -156,14 +156,14 @@ private static void submitLiveBody(Store store, null, PhysicsBodyPersistenceMode.PERSISTENT); store.addEntity(PhysicsStoreEntities.bodyHolder(store, - row.bodyUuid(), - row.body(), - row.dynamics(), - row.target(), - row.collider(), - row.shape(), - row.material(), - row.filter()), AddReason.SPAWN); + descriptor.bodyUuid(), + descriptor.body(), + descriptor.dynamics(), + descriptor.target(), + descriptor.collider(), + descriptor.shape(), + descriptor.material(), + descriptor.filter()), AddReason.SPAWN); } private static Store physicsStore(World world) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 9e8a4c1e..7b48ead8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -24,7 +24,7 @@ import org.joml.Vector3f; /** - * Crucible-only copied diagnostics sourced from authoritative PhysicsStore rows. + * Crucible-only copied diagnostics sourced from authoritative PhysicsStore entities. */ final class PhysicsStoreBenchmarkQueries { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index cb1fcf83..c5a9ad2c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -12,8 +12,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -24,7 +24,7 @@ import org.joml.Vector3f; /** - * Internal Crucible helpers for authoring and clearing live PhysicsStore rows. + * Internal Crucible helpers for authoring and clearing live PhysicsStore entities. */ final class PhysicsStoreCrucibleSupport { @@ -52,8 +52,8 @@ static Ref addBody(@Nonnull Store store, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - PhysicsStoreThreading.requireWorldThread(store, "add Crucible PhysicsStore body row"); - BodyRowDescriptor row = PhysicsBodyRows.body( + PhysicsStoreThreading.requireWorldThread(store, "add Crucible PhysicsStore body entity"); + BodyEntityDescriptor descriptor = PhysicsBodyEntities.body( PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), bodyUuid, bodyCenter, @@ -65,13 +65,13 @@ static Ref addBody(@Nonnull Store store, kind, persistenceMode); return store.addEntity(PhysicsStoreEntities.bodyHolder(store, - row.bodyUuid(), - row.body(), - row.dynamics(), - row.target(), - row.collider(), - row.shape(), - row.material(), - row.filter()), AddReason.SPAWN); + descriptor.bodyUuid(), + descriptor.body(), + descriptor.dynamics(), + descriptor.target(), + descriptor.collider(), + descriptor.shape(), + descriptor.material(), + descriptor.filter()), AddReason.SPAWN); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index f183a8ab..5e67892c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -51,7 +51,7 @@ public class PhysicsKinematicControlSystem extends EntityTickingSystem scratch = ThreadLocal.withInitial(Scratch::new); private static final Vector3f ZERO_VELOCITY = new Vector3f(); private static final Quaternionf IDENTITY_ROTATION = new Quaternionf(); - // Anchor updates are copied into PhysicsStore rows; avoid rewriting unchanged targets. + // Anchor updates are copied into PhysicsStore entities; avoid rewriting unchanged targets. @Nonnull private static final Map, ControlMutationState> STATES_BY_STORE = Collections.synchronizedMap(new WeakHashMap<>()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 8a8026a8..46c95f2a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -19,7 +19,7 @@ import org.joml.Vector3f; /** - * Direct PhysicsStore row mutations for kinematic control lifecycle cleanup. + * Direct PhysicsStore entity mutations for kinematic control lifecycle cleanup. */ public final class PhysicsStoreControlSessionMutations { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index d36e8562..588afeb0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -29,7 +29,7 @@ import org.joml.Vector3f; /** - * Direct PhysicsStore space row mutations for store-lane callers. + * Direct PhysicsStore space entity mutations for store-lane callers. */ public final class PhysicsStoreSpaceMutations { @@ -47,7 +47,7 @@ public static Ref addSpace(@Nonnull Store store, Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); Objects.requireNonNull(backendId, "backendId"); Objects.requireNonNull(settings, "settings"); - PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore space row"); + PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore space entity"); if (backendId.value().isBlank()) { throw new IllegalArgumentException("PhysicsStore space backend id is blank: " + spaceUuid); @@ -117,7 +117,7 @@ public static void putSpaceGravity(@Nonnull Store store, SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); if (space == null) { throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid - + " row has no SpaceComponent"); + + " entity has no SpaceComponent"); } SpaceComponent updated = space.clone(); updated.setGravity(gravity); @@ -134,7 +134,7 @@ public static void putSpaceSettings(@Nonnull Store store, Objects.requireNonNull(ref, "ref"); Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(settings, "settings"); - PhysicsStoreThreading.requireWorldThread(store, "update a PhysicsStore space row"); + PhysicsStoreThreading.requireWorldThread(store, "update a PhysicsStore space entity"); store.putComponent(ref, WorldCollisionComponent.getComponentType(), new WorldCollisionComponent(settings.getWorldCollisionSettings())); @@ -159,7 +159,7 @@ public static void removeEmptySpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { Objects.requireNonNull(store, "store"); Objects.requireNonNull(spaceUuid, "spaceUuid"); - PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space row"); + PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -208,7 +208,7 @@ public static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull private static Ref requireSpaceRef(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space row"); + PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space entity"); Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) .getByUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); if (ref == null || !ref.isValid()) { @@ -224,14 +224,14 @@ private static UUID requireSpaceUuid(@Nonnull Store store, Objects.requireNonNull(ref, "ref"); PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); if (ref.getStore() != store || !ref.isValid()) { - throw new IllegalArgumentException("PhysicsStore space row is not valid: " + ref); + throw new IllegalArgumentException("PhysicsStore space entity is not valid: " + ref); } if (store.getComponent(ref, SpaceComponent.getComponentType()) == null) { - throw new IllegalArgumentException("PhysicsStore row is not a space row: " + ref); + throw new IllegalArgumentException("PhysicsStore entity is not a space entity: " + ref); } UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); if (uuid == null) { - throw new IllegalArgumentException("PhysicsStore space row has no durable UUID: " + ref); + throw new IllegalArgumentException("PhysicsStore space entity has no durable UUID: " + ref); } return uuid.getUuid(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 7025182f..bb29b6de 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -43,7 +43,7 @@ private PhysicsStoreTopologyMutations() { public static void destroyBody(@Nonnull Store store, @Nonnull UUID bodyUuid) { - PhysicsStoreThreading.requireWorldThread(store, "destroy a PhysicsStore body row"); + PhysicsStoreThreading.requireWorldThread(store, "destroy a PhysicsStore body entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -56,7 +56,7 @@ public static void destroyBody(@Nonnull Store store, @Nonnull public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( @Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore body rows"); + PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore body entities"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -83,7 +83,7 @@ public static void removeSpaceWithContents(@Nonnull Store store, public static void removeSpaceWithContents(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space row"); + PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java index 620bb90b..92f3a715 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java @@ -13,7 +13,7 @@ import javax.annotation.Nonnull; /** - * Canonical compact DTO persistence for PhysicsStore rows. + * Canonical compact DTO persistence for PhysicsStore entities. */ public final class PersistentPhysicsStoreResource implements Resource { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index 4cbd676f..0fd8b9f6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -35,7 +35,7 @@ import org.joml.Vector3f; /** - * Creates backend bodies from authoritative PhysicsStore body rows. + * Creates backend bodies from authoritative PhysicsStore body entities. */ public final class BodyBindingSystem extends TickingSystem implements QuerySystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index 3f9592e7..ba45f0f1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -29,7 +29,7 @@ import org.joml.Vector3f; /** - * Binds joint rows once both endpoint bodies are bound. + * Binds joint entities once both endpoint bodies are bound. */ public final class JointBindingSystem extends TickingSystem implements QuerySystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index 16b6757d..27ecd87a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -54,7 +54,7 @@ import org.joml.Vector3f; /** - * Captures serializable PhysicsStore rows into compact DTO resources. + * Captures serializable PhysicsStore entities into compact DTO resources. */ public final class PersistenceCaptureSystem extends TickingSystem implements QuerySystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 46bf935a..13541f9b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -30,7 +30,7 @@ import javax.annotation.Nonnull; /** - * Removes backend bodies after their authoritative PhysicsStore body row is gone. + * Removes backend bodies after their authoritative PhysicsStore body entity is gone. */ public final class StaleBodyRemovalSystem extends TickingSystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java index 35c9e67b..8d0106b0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java @@ -22,7 +22,7 @@ import javax.annotation.Nonnull; /** - * Publishes copied world-collision settings for PhysicsStore space rows. + * Publishes copied world-collision settings for PhysicsStore space entities. */ public final class WorldCollisionIndexSystem extends TickingSystem implements QuerySystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java index c138d3fe..013eab5e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendBodyHandle.java @@ -4,7 +4,7 @@ * Backend-local physics-body handle. * *

    This is an internal runtime identity. Plugin-facing code should retain durable body UUIDs - * or live PhysicsStore row refs and keep backend handles inside runtime resources.

    + * or live PhysicsStore entity refs and keep backend handles inside runtime resources.

    */ public record BackendBodyHandle(long value) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java index 674f16fa..8349f6e4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/BackendJointHandle.java @@ -4,7 +4,7 @@ * Backend-local physics-joint handle. * *

    This is an internal runtime identity. Plugin-facing code should retain durable joint UUIDs - * or live PhysicsStore row refs and keep backend handles inside runtime resources.

    + * or live PhysicsStore entity refs and keep backend handles inside runtime resources.

    */ public record BackendJointHandle(long value) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 891aad36..70601017 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -198,7 +198,7 @@ private void requireLegacyMutationAllowed(@Nonnull String operation) { } throw new IllegalStateException("Legacy PhysicsWorldResource mutation is disabled while " + "authoritative PhysicsStore is active: " + operation - + ". Route this operation through PhysicsStore rows or a PhysicsStore-backed " + + ". Route this operation through PhysicsStore entities or a PhysicsStore-backed " + "compatibility bridge."); } @@ -1656,7 +1656,7 @@ public PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { @Override public PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef) { if (!isAuthoritativePhysicsStoreActive()) { - throw new IllegalStateException("Cannot read PhysicsStore space settings by row ref " + throw new IllegalStateException("Cannot read PhysicsStore space settings by entity ref " + "when authoritative PhysicsStore mode is unavailable"); } PhysicsSpaceSettings settings = getPhysicsStoreSpaceSettings( @@ -1692,7 +1692,7 @@ public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSett public void setSpaceSettings(@Nonnull Ref spaceRef, @Nonnull PhysicsSpaceSettings settings) { if (!isAuthoritativePhysicsStoreActive()) { - throw new IllegalStateException("Cannot set PhysicsStore space settings by row ref " + throw new IllegalStateException("Cannot set PhysicsStore space settings by entity ref " + "when authoritative PhysicsStore mode is unavailable"); } PhysicsStoreSpaceMutations.putSpaceSettings( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java index f142ac6e..73626420 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java @@ -11,7 +11,7 @@ import javax.annotation.Nullable; /** - * Internal snapshot visitor for authoritative paths that can use live PhysicsStore row refs. + * Internal snapshot visitor for authoritative paths that can use live PhysicsStore entity refs. */ @FunctionalInterface public interface PhysicsBodySnapshotRefVisitor { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 6b2283fb..9bd30586 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -51,7 +51,7 @@ * body transforms.

    * *

    Entities attach to authoritative PhysicsStore body UUIDs. Backend body destruction is explicit - * through PhysicsStore rows; removing an EntityStore attachment only removes the projection.

    + * through PhysicsStore entities; removing an EntityStore attachment only removes the projection.

    */ public class PhysicsSyncSystem extends EntityTickingSystem { @@ -235,7 +235,7 @@ private static void clearMissingPhysicsStoreAttachment(@Nonnull Ref @Nonnull BodyAttachmentComponent attachment, @Nonnull CommandBuffer commandBuffer) { // PhysicsStore snapshot publication is intentionally one completed frame behind row - // mutation. Absence from the latest frame is not enough evidence that the body row is gone. + // mutation. Absence from the latest frame is not enough evidence that the body entity is gone. } private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index f9e794e7..96165460 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -50,8 +50,8 @@ public static boolean hasSession(@Nonnull Store store, } /** - * Starts or replaces the controller entity's Impulse control session from durable row UUIDs. - * Prefer the ref overload when the caller already has live PhysicsStore row refs. + * Starts or replaces the controller entity's Impulse control session from durable entity UUIDs. + * Prefer the ref overload when the caller already has live PhysicsStore entity refs. */ public static void startSession(@Nonnull Store store, @Nonnull Ref controllerRef, @@ -85,7 +85,7 @@ public static void startSession(@Nonnull Store store, /** * Starts or replaces the controller entity's Impulse control session with live PhysicsStore - * row refs. + * entity refs. */ public static void startSession(@Nonnull Store store, @Nonnull Ref controllerRef, @@ -177,7 +177,7 @@ private static Ref requireRef(@Nonnull Store store, .getByUuid(uuid); if (ref == null || !ref.isValid()) { throw new IllegalArgumentException("PhysicsStore " + role - + " row is not loaded for uuid=" + uuid); + + " entity is not loaded for uuid=" + uuid); } return ref; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyRowDescriptor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java similarity index 69% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyRowDescriptor.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java index 86285336..56903ad5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyRowDescriptor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java @@ -13,22 +13,22 @@ import javax.annotation.Nullable; /** - * Copied component graph for one direct PhysicsStore body row. + * Copied component graph for one PhysicsStore body entity. */ -public record BodyRowDescriptor(@Nonnull UUID bodyUuid, - @Nonnull BodyComponent body, - @Nonnull DynamicsComponent dynamics, - @Nullable TargetComponent target, - @Nonnull UUID colliderUuid, - @Nonnull ColliderComponent collider, - @Nonnull UUID shapeUuid, - @Nonnull ShapeComponent shape, - @Nonnull UUID materialUuid, - @Nonnull MaterialComponent material, - @Nonnull UUID filterUuid, - @Nonnull CollisionFilterComponent filter) { +public record BodyEntityDescriptor(@Nonnull UUID bodyUuid, + @Nonnull BodyComponent body, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target, + @Nonnull UUID colliderUuid, + @Nonnull ColliderComponent collider, + @Nonnull UUID shapeUuid, + @Nonnull ShapeComponent shape, + @Nonnull UUID materialUuid, + @Nonnull MaterialComponent material, + @Nonnull UUID filterUuid, + @Nonnull CollisionFilterComponent filter) { - public BodyRowDescriptor { + public BodyEntityDescriptor { Objects.requireNonNull(bodyUuid, "bodyUuid"); body = Objects.requireNonNull(body, "body").clone(); dynamics = Objects.requireNonNull(dynamics, "dynamics").clone(); @@ -44,7 +44,7 @@ public record BodyRowDescriptor(@Nonnull UUID bodyUuid, } @Nonnull - public static BodyRowDescriptor of(@Nonnull UUID bodyUuid, + public static BodyEntityDescriptor of(@Nonnull UUID bodyUuid, @Nonnull BodyComponent body, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target, @@ -56,7 +56,7 @@ public static BodyRowDescriptor of(@Nonnull UUID bodyUuid, @Nonnull MaterialComponent material, @Nonnull UUID filterUuid, @Nonnull CollisionFilterComponent filter) { - return new BodyRowDescriptor(bodyUuid, + return new BodyEntityDescriptor(bodyUuid, body, dynamics, target, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java similarity index 90% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java index f9f082ce..c7f74456 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRows.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java @@ -23,15 +23,15 @@ import org.joml.Vector3f; /** - * Factories for direct PhysicsStore body row descriptors. + * Factories for PhysicsStore body entity descriptors. */ -public final class PhysicsBodyRows { +public final class PhysicsBodyEntities { - private PhysicsBodyRows() { + private PhysicsBodyEntities() { } @Nonnull - public static BodyRowDescriptor dynamicBody(@Nonnull UUID spaceUuid, + public static BodyEntityDescriptor dynamicBody(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -52,7 +52,7 @@ public static BodyRowDescriptor dynamicBody(@Nonnull UUID spaceUuid, } @Nonnull - public static BodyRowDescriptor dynamicBody(@Nonnull Ref spaceRef, + public static BodyEntityDescriptor dynamicBody(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -73,7 +73,7 @@ public static BodyRowDescriptor dynamicBody(@Nonnull Ref spaceRef, } @Nonnull - public static BodyRowDescriptor body(@Nonnull UUID spaceUuid, + public static BodyEntityDescriptor body(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -92,7 +92,7 @@ public static BodyRowDescriptor body(@Nonnull UUID spaceUuid, Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(persistenceMode, "persistenceMode"); - return BodyRowDescriptor.of(bodyUuid, + return BodyEntityDescriptor.of(bodyUuid, new BodyComponent(spaceUuid, kind, persistenceMode), @@ -124,7 +124,7 @@ public static BodyRowDescriptor body(@Nonnull UUID spaceUuid, } @Nonnull - public static BodyRowDescriptor body(@Nonnull Ref spaceRef, + public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -134,7 +134,7 @@ public static BodyRowDescriptor body(@Nonnull Ref spaceRef, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - BodyRowDescriptor row = body(PhysicsStoreRowRefs.rowUuid(spaceRef), + BodyEntityDescriptor descriptor = body(PhysicsStoreEntityRefs.entityUuid(spaceRef), bodyUuid, bodyCenter, shape, @@ -144,8 +144,8 @@ public static BodyRowDescriptor body(@Nonnull Ref spaceRef, linearVelocity, kind, persistenceMode); - row.body().setSpaceRef(spaceRef); - return row; + descriptor.body().setSpaceRef(spaceRef); + return descriptor; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointRows.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java similarity index 80% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointRows.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java index 2413a8f8..beede58a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointRows.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java @@ -12,9 +12,9 @@ /** * Factories for direct PhysicsStore joint components. */ -public final class PhysicsJointRows { +public final class PhysicsJointEntities { - private PhysicsJointRows() { + private PhysicsJointEntities() { } @Nonnull @@ -45,11 +45,11 @@ public static JointComponent joint(@Nonnull Ref spaceRef, @Nonnull Vector3f anchorA, @Nonnull Vector3f anchorB, @Nonnull Vector3f axis) { - PhysicsStoreRowRefs.requireSameStore(spaceRef, bodyARef, "bodyARef"); - PhysicsStoreRowRefs.requireSameStore(spaceRef, bodyBRef, "bodyBRef"); - JointComponent joint = joint(PhysicsStoreRowRefs.rowUuid(spaceRef), - PhysicsStoreRowRefs.rowUuid(bodyARef), - PhysicsStoreRowRefs.rowUuid(bodyBRef), + PhysicsStoreEntityRefs.requireSameStore(spaceRef, bodyARef, "bodyARef"); + PhysicsStoreEntityRefs.requireSameStore(spaceRef, bodyBRef, "bodyBRef"); + JointComponent joint = joint(PhysicsStoreEntityRefs.entityUuid(spaceRef), + PhysicsStoreEntityRefs.entityUuid(bodyARef), + PhysicsStoreEntityRefs.entityUuid(bodyBRef), type, anchorA, anchorB, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRowRefs.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntityRefs.java similarity index 71% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRowRefs.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntityRefs.java index 9d18cad1..b364c84f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRowRefs.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntityRefs.java @@ -8,22 +8,22 @@ import java.util.UUID; import javax.annotation.Nonnull; -final class PhysicsStoreRowRefs { +final class PhysicsStoreEntityRefs { - private PhysicsStoreRowRefs() { + private PhysicsStoreEntityRefs() { } @Nonnull - static UUID rowUuid(@Nonnull Ref ref) { + static UUID entityUuid(@Nonnull Ref ref) { Ref checkedRef = Objects.requireNonNull(ref, "ref"); Store store = checkedRef.getStore(); - PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore row UUID"); + PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore entity UUID"); if (!checkedRef.isValid()) { - throw new IllegalStateException("PhysicsStore row ref is not valid: " + checkedRef); + throw new IllegalStateException("PhysicsStore entity ref is not valid: " + checkedRef); } UuidComponent uuid = store.getComponent(checkedRef, UuidComponent.getComponentType()); if (uuid == null) { - throw new IllegalStateException("PhysicsStore row has no UUID component: " + checkedRef); + throw new IllegalStateException("PhysicsStore entity has no UUID component: " + checkedRef); } return uuid.getUuid(); } @@ -33,7 +33,7 @@ static void requireSameStore(@Nonnull Ref expectedStoreRef, @Nonnull String name) { if (Objects.requireNonNull(ref, name).getStore() != Objects.requireNonNull(expectedStoreRef, "expectedStoreRef").getStore()) { - throw new IllegalArgumentException("PhysicsStore row ref belongs to a different store: " + throw new IllegalArgumentException("PhysicsStore entity ref belongs to a different store: " + name); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java index 372ddd95..8bb04e42 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -14,7 +14,7 @@ import javax.annotation.Nonnull; /** - * Thread assertions for direct PhysicsStore row and backend access. + * Thread assertions for direct PhysicsStore entity and backend access. */ public final class PhysicsStoreThreading { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java index ca54818f..99db862c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java @@ -15,7 +15,7 @@ import org.joml.Vector3f; /** - * Local collider settings for one body aggregate row. + * Local collider settings for one body entity. */ public final class ColliderComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java index 49c11d73..19b43b4c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java @@ -11,7 +11,7 @@ import javax.annotation.Nonnull; /** - * Collision group/mask row referenced by colliders. + * Collision group/mask settings for one collider entity. */ public final class CollisionFilterComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java index 12a1c41f..8326ca80 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java @@ -12,7 +12,7 @@ import javax.annotation.Nonnull; /** - * Authored collision LOD policy for one PhysicsStore space row. + * Authored collision LOD policy for one PhysicsStore space entity. */ public final class CollisionLodSettingsComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java index 502f7246..bd318db2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java @@ -13,7 +13,7 @@ import javax.annotation.Nonnull; /** - * Authored motion mode, mass, damping, and CCD flags for a body row. + * Authored motion mode, mass, damping, and CCD flags for a body entity. */ public final class DynamicsComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java index 10c2faf5..6771b8e4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java @@ -19,7 +19,7 @@ import javax.annotation.Nonnull; /** - * Authored backend extension settings for one PhysicsStore space row. + * Authored backend extension settings for one PhysicsStore space entity. */ public final class ExtensionSettingsComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java index 2123aea9..6329f82f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java @@ -18,7 +18,7 @@ import org.joml.Vector3f; /** - * Authored joint row keyed by durable endpoint body UUIDs. + * Authored joint entity keyed by durable endpoint body UUIDs. */ public final class JointComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java index c6cf0065..2de296ec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java @@ -10,7 +10,7 @@ import javax.annotation.Nonnull; /** - * Physical material row referenced by colliders. + * Physical material settings for one collider entity. */ public final class MaterialComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java index 14590d7d..608578bd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java @@ -14,7 +14,7 @@ import javax.annotation.Nonnull; /** - * Authored collision shape row shared by one or more colliders. + * Authored collision shape for one collider entity. */ public final class ShapeComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java index 62909efb..370efe5c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java @@ -12,7 +12,7 @@ import javax.annotation.Nonnull; /** - * Authored backend solver and activation tuning for one PhysicsStore space row. + * Authored backend solver and activation tuning for one PhysicsStore space entity. */ public final class SolverSettingsComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java index 2b8c4b2b..dbdff941 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java @@ -14,7 +14,7 @@ import org.joml.Vector3f; /** - * Authored backend and gravity definition for one physics space row. + * Authored backend and gravity definition for one physics space entity. */ public final class SpaceComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java index 02e2571f..4d8cdfdc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java @@ -14,7 +14,7 @@ import javax.annotation.Nullable; /** - * Terrain collider row mirrored from ChunkStore terrain source data. + * Terrain collider entity mirrored from ChunkStore terrain source data. */ public final class TerrainColliderComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java index 8d763463..083a5d23 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java @@ -12,7 +12,7 @@ import javax.annotation.Nonnull; /** - * Durable identity for one PhysicsStore row. + * Durable identity for one PhysicsStore entity. */ public final class UuidComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java index 238e121b..3c407f0c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java @@ -13,7 +13,7 @@ import javax.annotation.Nonnull; /** - * Authored detached visual materialization policy for one PhysicsStore space row. + * Authored detached visual materialization policy for one PhysicsStore space entity. */ public final class VisualMaterializationSettingsComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java index ebbc447a..26eb62a6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java @@ -15,7 +15,7 @@ import javax.annotation.Nonnull; /** - * Authored visual synchronization policy for one PhysicsStore space row. + * Authored visual synchronization policy for one PhysicsStore space entity. */ public final class VisualSyncSettingsComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java index 44f6f0fb..6fac4ff8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java @@ -16,7 +16,7 @@ import javax.annotation.Nonnull; /** - * Authored world-collision streaming settings for one PhysicsStore space row. + * Authored world-collision streaming settings for one PhysicsStore space entity. */ public final class WorldCollisionComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 43983617..082d5fb2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -41,7 +41,7 @@ * target for each operation.

    * *

    This facade does not directly return live backend spaces or bodies. Gameplay code should use - * PhysicsStore rows for authoring, copied snapshots for body state, and explicit PhysicsStore + * PhysicsStore entities for authoring, copied snapshots for body state, and explicit PhysicsStore * diagnostics/raycast helpers for store tick lane backend reads.

    */ public abstract class PhysicsWorldResource implements Resource { @@ -291,10 +291,10 @@ public abstract PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId public abstract PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId); /** - * Returns the current settings for a live PhysicsStore space row. + * Returns the current settings for a live PhysicsStore space entity. * *

    Prefer this overload when command or gameplay code already resolved the target - * space row.

    + * space entity.

    */ @Nonnull public abstract PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef); @@ -306,10 +306,10 @@ public abstract void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings); /** - * Applies settings to a live PhysicsStore space row. + * Applies settings to a live PhysicsStore space entity. * *

    Prefer this overload when command or gameplay code already resolved the target - * space row.

    + * space entity.

    */ public abstract void setSpaceSettings(@Nonnull Ref spaceRef, @Nonnull PhysicsSpaceSettings settings); @@ -381,7 +381,7 @@ public abstract Collection> getBodyAttachments(@Nonnull UUID bo /** * Returns ECS attachments associated with a live PhysicsStore body ref. * - *

    Prefer this overload when a caller already has a body row ref, such as from a PhysicsStore + *

    Prefer this overload when a caller already has a body entity ref, such as from a PhysicsStore * raycast or copied registration.

    */ @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java index 58109752..5bd35e2c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.simulation; /** - * Public joint kinds supported by PhysicsStore joint rows and snapshot views. + * Public joint kinds supported by PhysicsStore joint entities and snapshot views. */ public enum JointType { FIXED, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java index c0ffbe3f..711ae41a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java @@ -10,7 +10,7 @@ import org.joml.Vector3f; /** - * Copied raycast geometry plus the PhysicsStore row ref hit by the backend. + * Copied raycast geometry plus the PhysicsStore entity ref hit by the backend. */ public record RaycastHitView(@Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index dd6aaab9..e5f2527f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; @@ -29,7 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -76,80 +76,86 @@ public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyRowDescriptor row) { + @Nonnull BodyEntityDescriptor descriptor) { Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - return addPhysicsStoreBody(store, row); + return addPhysicsStoreBody(store, descriptor); } @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyRowDescriptor row, + @Nonnull BodyEntityDescriptor descriptor, @Nonnull BodyCommandComponent command) { Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - Ref bodyRef = addPhysicsStoreBody(store, row); + Ref bodyRef = addPhysicsStoreBody(store, descriptor); appendPhysicsStoreBodyCommand(store, bodyRef, command); return bodyRef; } @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyRowDescriptor row, + @Nonnull BodyEntityDescriptor descriptor, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - return addPhysicsStoreBody(store, row, dynamics, target); + return addPhysicsStoreBody(store, descriptor, dynamics, target); } public static void addPhysicsStoreBodies(@Nonnull World world, - @Nonnull Iterable rows) { - Objects.requireNonNull(rows, "rows"); + @Nonnull Iterable descriptors) { + Objects.requireNonNull(descriptors, "descriptors"); Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(store, "add PhysicsStore body rows"); - for (BodyRowDescriptor row : rows) { - addPhysicsStoreBodyUnchecked(store, row, row.dynamics(), row.target()); + PhysicsStoreThreading.requireWorldThread(store, "add PhysicsStore body entities"); + for (BodyEntityDescriptor descriptor : descriptors) { + addPhysicsStoreBodyUnchecked(store, + descriptor, + descriptor.dynamics(), + descriptor.target()); } } @Nonnull private static Ref addPhysicsStoreBody(@Nonnull Store store, - @Nonnull BodyRowDescriptor row) { - Objects.requireNonNull(row, "row"); - return addPhysicsStoreBody(store, row, row.dynamics(), row.target()); + @Nonnull BodyEntityDescriptor descriptor) { + Objects.requireNonNull(descriptor, "descriptor"); + return addPhysicsStoreBody(store, + descriptor, + descriptor.dynamics(), + descriptor.target()); } @Nonnull private static Ref addPhysicsStoreBody(@Nonnull Store store, - @Nonnull BodyRowDescriptor row, + @Nonnull BodyEntityDescriptor descriptor, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { - Objects.requireNonNull(row, "row"); - PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore body row"); - return addPhysicsStoreBodyUnchecked(store, row, dynamics, target); + Objects.requireNonNull(descriptor, "descriptor"); + PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore body entity"); + return addPhysicsStoreBodyUnchecked(store, descriptor, dynamics, target); } @Nonnull private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store store, - @Nonnull BodyRowDescriptor row, + @Nonnull BodyEntityDescriptor descriptor, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { - Objects.requireNonNull(row, "row"); + Objects.requireNonNull(descriptor, "descriptor"); return store.addEntity(PhysicsStoreEntities.bodyHolder(store, - row.bodyUuid(), - row.body(), + descriptor.bodyUuid(), + descriptor.body(), Objects.requireNonNull(dynamics, "dynamics"), target, - row.collider(), - row.shape(), - row.material(), - row.filter()), AddReason.SPAWN); + descriptor.collider(), + descriptor.shape(), + descriptor.material(), + descriptor.filter()), AddReason.SPAWN); } @Nonnull @@ -159,7 +165,7 @@ public static Ref addPhysicsStoreJoint(@Nonnull World world, Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore joint row"); + PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore joint entity"); return store.addEntity(PhysicsStoreEntities.jointHolder(store, Objects.requireNonNull(jointUuid, "jointUuid"), joint), AddReason.SPAWN); @@ -341,7 +347,7 @@ private static CreatedBlockBody createPhysicsStoreBlockBody(@Nonnull World world @Nonnull UUID bodyUuid) { Vector3f bodyCenter = toVector3f(visualPosition); Ref bodyRef = addPhysicsStoreBody(world, - bodyRow(spaceRef, + bodyEntity(spaceRef, bodyUuid, bodyCenter, shape, @@ -360,14 +366,14 @@ private static CreatedBlockBody createPhysicsStoreBlockBody(@Nonnull World world } @Nonnull - public static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, + public static BodyEntityDescriptor bodyEntity(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - return PhysicsBodyRows.dynamicBody(spaceRef, + return PhysicsBodyEntities.dynamicBody(spaceRef, bodyUuid, bodyCenter, shape, @@ -378,7 +384,7 @@ public static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, } @Nonnull - private static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, + private static BodyEntityDescriptor bodyEntity(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -387,7 +393,7 @@ private static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return PhysicsBodyRows.body(spaceRef, + return PhysicsBodyEntities.body(spaceRef, bodyUuid, bodyCenter, shape, @@ -400,7 +406,7 @@ private static BodyRowDescriptor bodyRow(@Nonnull Ref spaceRef, } @Nonnull - public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, + public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, @Nonnull SpaceId spaceId, int expectedBodies, @Nonnull PhysicsShapeSpec shape, @@ -419,19 +425,19 @@ public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World worl persistenceMode, builder); if (plan.isEmpty()) { - return new BodyRowBatchTiming(0, plan.setupWallNanos(), 0L); + return new BodyEntityBatchTiming(0, plan.setupWallNanos(), 0L); } long applyStartNanos = System.nanoTime(); addPhysicsStoreBodies(world, plan.bodies()); - long rowApplyNanos = System.nanoTime() - applyStartNanos; - return new BodyRowBatchTiming(plan.count(), + long entityApplyNanos = System.nanoTime() - applyStartNanos; + return new BodyEntityBatchTiming(plan.count(), plan.setupWallNanos(), - rowApplyNanos); + entityApplyNanos); } @Nonnull - public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, + public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, int expectedBodies, @@ -451,15 +457,15 @@ public static BodyRowBatchTiming addDynamicBodyBatchMeasured(@Nonnull World worl persistenceMode, builder); if (plan.isEmpty()) { - return new BodyRowBatchTiming(0, plan.setupWallNanos(), 0L); + return new BodyEntityBatchTiming(0, plan.setupWallNanos(), 0L); } long applyStartNanos = System.nanoTime(); addPhysicsStoreBodies(world, plan.bodies()); - long rowApplyNanos = System.nanoTime() - applyStartNanos; - return new BodyRowBatchTiming(plan.count(), + long entityApplyNanos = System.nanoTime() - applyStartNanos; + return new BodyEntityBatchTiming(plan.count(), plan.setupWallNanos(), - rowApplyNanos); + entityApplyNanos); } @Nonnull @@ -489,7 +495,7 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, Ref spaceRef = resolvePhysicsStoreSpaceRef(world, spaceId); if (spaceRef == null) { - throw new IllegalStateException("Cannot add dynamic body rows because the target space is not " + throw new IllegalStateException("Cannot add dynamic body entities because the target space is not " + "bound in PhysicsStore: " + spaceId.value()); } @@ -522,10 +528,10 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref bodies = new ArrayList<>(batch.size()); + List bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); - bodies.add(bodyRow(spaceRef, + bodies.add(bodyEntity(spaceRef, bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -580,9 +586,9 @@ public static SpawnedBlockBody attachPhysicsStoreBlockBody(@Nonnull Store bodyRef = created.bodyRef(); PhysicsStoreThreading.requireWorldThread(bodyRef.getStore(), - "attach a visual to a created PhysicsStore body row"); + "attach a visual to a created PhysicsStore body entity"); if (!bodyRef.isValid()) { - throw new IllegalStateException("Cannot attach visual because PhysicsStore body row " + throw new IllegalStateException("Cannot attach visual because PhysicsStore body entity " + "is no longer valid: " + created.bodyUuid()); } Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, @@ -756,10 +762,10 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store rows = new ArrayList<>(batch.size()); + List descriptors = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); - rows.add(bodyRow(spaceRef, + descriptors.add(bodyEntity(spaceRef, bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -810,9 +816,9 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store bodies, + private record DynamicBodyBatchPlan(@Nonnull List bodies, long setupWallNanos) { DynamicBodyBatchPlan { @@ -1121,7 +1127,7 @@ private void assertMutable() { private record BlockBodyBatchResult(@Nullable SpawnedBlockBody[] bodies, int count, - long rowApplyNanos, + long entityApplyNanos, long entityAttachNanos) { private BlockBodyBatchResult { @@ -1129,7 +1135,7 @@ private record BlockBodyBatchResult(@Nullable SpawnedBlockBody[] bodies, throw new IllegalArgumentException("Collected body count does not match batch count"); } count = Math.max(0, count); - rowApplyNanos = Math.max(0L, rowApplyNanos); + entityApplyNanos = Math.max(0L, entityApplyNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } @@ -1144,7 +1150,7 @@ private SpawnedBlockBody[] collectedBodies() { @Nonnull private BlockBodyBatchTiming timing() { return new BlockBodyBatchTiming(count, - rowApplyNanos, + entityApplyNanos, entityAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index c867f8f4..1723acc0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -164,7 +164,7 @@ private static CreatedBlockBody spawnBox(@Nonnull World world, @Nonnull BodyCommandComponent command) { UUID bodyUuid = UUID.randomUUID(); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceRef, + ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 26485875..f7a51a66 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -26,14 +26,14 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyRows; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyRowDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; @@ -196,7 +196,7 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, BodyCommandComponent.wake()); try { Ref anchorBodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - anchorBodyRow(spaceRef, anchorBodyUuid, hitPoint)); + anchorBodyEntity(spaceRef, anchorBodyUuid, hitPoint)); Ref controlJointRef = ExamplePhysicsUtils.addPhysicsStoreJoint(world, controlJointUuid, controlJoint(spaceRef, anchorBodyRef, selectedBodyRef, bodyLocalHit)); @@ -210,10 +210,10 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, } @Nonnull - private static BodyRowDescriptor anchorBodyRow(@Nonnull Ref spaceRef, + private static BodyEntityDescriptor anchorBodyEntity(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f hitPoint) { - return PhysicsBodyRows.body(spaceRef, + return PhysicsBodyEntities.body(spaceRef, bodyUuid, hitPoint, PhysicsShapeSpec.sphere(0.08f), @@ -232,7 +232,7 @@ private static JointComponent controlJoint(@Nonnull Ref spaceRef, @Nonnull Ref anchorBodyRef, @Nonnull Ref bodyRef, @Nonnull Vector3f bodyLocalHit) { - return PhysicsJointRows.joint(spaceRef, + return PhysicsJointEntities.joint(spaceRef, anchorBodyRef, bodyRef, JointType.POINT, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index 16a62f38..ca1ff5bf 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -13,7 +13,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -235,7 +235,7 @@ private static CreatedBlockBody spawnBox(@Nonnull List created @Nullable Vector3f linearVelocity) { UUID bodyUuid = UUID.randomUUID(); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceRef, + ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), @@ -262,7 +262,7 @@ private static JointComponent joint(@Nonnull Ref spaceRef, @Nonnull Vector3f anchorA, @Nonnull Vector3f anchorB, @Nonnull Vector3f axis) { - return PhysicsJointRows.joint(spaceRef, + return PhysicsJointEntities.joint(spaceRef, bodyA.bodyRef(), bodyB.bodyRef(), type, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index cf92e2d9..b4511f51 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -165,7 +165,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } Vector3f targetPosition = vector(spawn); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceRef, + ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, targetPosition, PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), @@ -344,7 +344,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, UUID bodyUuid = UUID.randomUUID(); ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceRef, + ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, vector(spawn), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index d0389113..635d6179 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -119,14 +119,14 @@ private static void spawnBenchmark(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Added " + timing.spawned() + " " + request.mode().label() + " benchmark bodies: setupWallMs=" + millis(timing.setupWallNanos()) - + " rowApplyMs=" + millis(timing.rowApplyNanos()) + + " entityApplyMs=" + millis(timing.entityApplyNanos()) + (timing.entityAttachNanos() > 0L ? " entityAttachMs=" + millis(timing.entityAttachNanos()) : "") + " (" + microsPerBody(timing.setupWallNanos(), timing.spawned()) + " us/body). Space bodies before add: " + beforeBodies + (request.mode() == BenchmarkMode.ENTITY ? ". blockType=" + request.blockType() : "") - + ". Body-count updates are visible after PhysicsStore binds the new rows" + + ". Body-count updates are visible after PhysicsStore binds the new entities" + ". This command measures raw setup/entity attachment; use /impulse-examples stress bodies" + " for detached/detached-view scalability scenarios" + ". For clean comparisons run /impulse clean, /impulse-world-collision perf reset," @@ -165,7 +165,7 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, int count) { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - ExamplePhysicsUtils.BodyRowBatchTiming timing = + ExamplePhysicsUtils.BodyEntityBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, spaceRef, spaceId, @@ -184,7 +184,7 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, }); return new BenchmarkSpawnTiming(timing.count(), timing.setupWallNanos(), - timing.rowApplyNanos(), + timing.entityApplyNanos(), 0L); } @@ -217,8 +217,8 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st } }); return new BenchmarkSpawnTiming(timing.count(), - timing.rowApplyNanos() + timing.entityAttachNanos(), - timing.rowApplyNanos(), + timing.entityApplyNanos() + timing.entityAttachNanos(), + timing.entityApplyNanos(), timing.entityAttachNanos()); } @@ -285,12 +285,12 @@ private record BenchmarkRequest(BenchmarkMode mode, int count, @Nonnull String b private record BenchmarkSpawnTiming(int spawned, long setupWallNanos, - long rowApplyNanos, + long entityApplyNanos, long entityAttachNanos) { private BenchmarkSpawnTiming { setupWallNanos = Math.max(0L, setupWallNanos); - rowApplyNanos = Math.max(0L, rowApplyNanos); + entityApplyNanos = Math.max(0L, entityApplyNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 652bc017..9e4587ac 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -195,13 +195,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, layout.positionZ(i)); } }); - timing = new StressSpawnTiming(batchTiming.rowApplyNanos() + batchTiming.entityAttachNanos(), - batchTiming.rowApplyNanos(), + timing = new StressSpawnTiming(batchTiming.entityApplyNanos() + batchTiming.entityAttachNanos(), + batchTiming.entityApplyNanos(), batchTiming.entityAttachNanos()); } else { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = detachedSpawnSettings(collisionPolicy); - ExamplePhysicsUtils.BodyRowBatchTiming batchTiming = + ExamplePhysicsUtils.BodyEntityBatchTiming batchTiming = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, spaceRef, spaceId, @@ -219,7 +219,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } }); timing = new StressSpawnTiming(batchTiming.setupWallNanos(), - batchTiming.rowApplyNanos(), + batchTiming.entityApplyNanos(), 0L); } PhysicsWorldCollisionSettings worldCollisionSettings = @@ -234,7 +234,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " stress bodies: setupWallMs=" + millis(prewarmNanos + timing.setupWallNanos()) + " prewarmMs=" + millis(prewarmNanos) - + " rowApplyMs=" + millis(timing.rowApplyNanos()) + + " entityApplyMs=" + millis(timing.entityApplyNanos()) + (timing.entityAttachNanos() > 0L ? " entityAttachMs=" + millis(timing.entityAttachNanos()) : "") @@ -249,7 +249,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " visuals=" + mode.visualDescription() + (mode == StressMode.ENTITY ? " blockType=" + visualSettings.blockType() : "") + (mode.usesDetachedBodies() - ? " body-count and detached-view snapshots update after PhysicsStore binds the new rows" + ? " body-count and detached-view snapshots update after PhysicsStore binds the new entities" : "") + (mode == StressMode.DETACHED_VIEW ? " visualProxyCap=" @@ -599,12 +599,12 @@ private record StressVisualSettings(int materializationRadius, } private record StressSpawnTiming(long setupWallNanos, - long rowApplyNanos, + long entityApplyNanos, long entityAttachNanos) { private StressSpawnTiming { setupWallNanos = Math.max(0L, setupWallNanos); - rowApplyNanos = Math.max(0L, rowApplyNanos); + entityApplyNanos = Math.max(0L, entityApplyNanos); entityAttachNanos = Math.max(0L, entityAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 182e666b..2de93117 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -13,7 +13,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointRows; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -31,7 +31,7 @@ import org.joml.Vector3f; /** - * Builds separate joint rows so backend differences are easier to isolate. + * Builds separate joint entities so backend differences are easier to isolate. * Every row starts with matching body spacing and local anchors, avoiding correction from an * already invalid initial pose. */ @@ -155,7 +155,7 @@ private static int appendRow(@Nonnull List createdBodies, positions[positionOffset + 2] = (float) origin.z; float mass = i == 0 ? 0.0f : 1.0f; var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceRef, + ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, new Vector3f(positions[positionOffset], positions[positionOffset + 1], @@ -203,7 +203,7 @@ private static JointComponent joint(@Nonnull Ref spaceRef, case 3 -> JointType.SLIDER; default -> JointType.SPRING; }; - JointComponent joint = PhysicsJointRows.joint(spaceRef, + JointComponent joint = PhysicsJointEntities.joint(spaceRef, previous.bodyRef(), current.bodyRef(), type, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index 1bdcca52..2d03fc0f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.BodyRowBatchTiming; +import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.BodyEntityBatchTiming; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -77,7 +77,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); long totalStartNanos = System.nanoTime(); - BodyRowBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, + BodyEntityBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, spaceRef, spaceId, count, @@ -107,13 +107,13 @@ private static String millis(long nanos) { } @Nonnull - private static String successMessage(@Nonnull BodyRowBatchTiming timing, + private static String successMessage(@Nonnull BodyEntityBatchTiming timing, long totalWallNanos) { - return "PhysicsStore added raw body rows for " + timing.count() + return "PhysicsStore added raw body entities for " + timing.count() + " physics-only bodies: setupWallMs=" + millis(timing.setupWallNanos()) - + " rowApplyMs=" + millis(timing.rowApplyNanos()) + + " entityApplyMs=" + millis(timing.entityApplyNanos()) + " totalWallMs=" + millis(totalWallNanos) - + ". Body-count updates are visible after PhysicsStore binds the new rows."; + + ". Body-count updates are visible after PhysicsStore binds the new entities."; } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 24c04bd6..c3042f9f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -155,7 +155,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e settings.getVerticalLift()) .mul(group.mass()); var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyRow(spaceRef, + ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, toVector3f(groupCenter), group.shape(), From b698de345b969e22c0238563542a54b61cc148ab Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:01:56 +0200 Subject: [PATCH 271/534] fix(core): enumerate physics store debug spaces Signed-off-by: Blovien --- .../systems/debug/PhysicsDebugSystem.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index b331c7f7..f51d1bea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -22,7 +22,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; @@ -142,14 +141,14 @@ public void tick(float dt, int index, @Nonnull Store store) { overlayLifetime); } - for (PhysicsSpaceBinding space : resource.getSpaceBindings()) { + for (SpaceId spaceId : resource.getSpaceIds()) { if (overlayDue && debugShapes) { - renderSpaceOnlyShapes(target, resource, space, overlayLifetime); + renderSpaceOnlyShapes(target, resource, spaceId, overlayLifetime); } if (overlayDue && debugContacts) { renderContacts(target, physicsStore, - space, + spaceId, viewerUuid, queryCache, viewerPosition, @@ -160,7 +159,7 @@ public void tick(float dt, int index, @Nonnull Store store) { if (overlayDue && debugJoints) { renderJoints(target, physicsStore, - space, + spaceId, viewerUuid, queryCache, viewerPosition, @@ -171,7 +170,7 @@ public void tick(float dt, int index, @Nonnull Store store) { if (worldCollisionDue && debugWorldCollision) { renderWorldCollision(target, resource, - space, + spaceId, viewerPosition, debug.getViewRadius(), debug.getMaxWorldCollisionSections(), @@ -285,8 +284,8 @@ private static int renderDetachedBodies(@Nonnull Collection viewers, RenderedBodyCount rendered = new RenderedBodyCount(); double maxDistanceSquared = viewRadius * viewRadius; - for (PhysicsSpaceBinding space : resource.getSpaceBindings()) { - resource.forEachIndexedBodySnapshot(space.spaceId(), (bodyUuid, snapshot, spaceId, kind, persistenceMode) -> { + for (SpaceId spaceId : resource.getSpaceIds()) { + resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, snapshotSpaceId, kind, persistenceMode) -> { if (rendered.hasReached(maxBodies)) { return; } @@ -341,9 +340,9 @@ private int value() { private static void renderSpaceOnlyShapes(@Nonnull Collection viewers, @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsSpaceBinding space, + @Nonnull SpaceId spaceId, float time) { - resource.forEachIndexedBodySnapshot(space.spaceId(), (bodyUuid, snapshot, spaceId, kind, persistenceMode) -> { + resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, snapshotSpaceId, kind, persistenceMode) -> { if (snapshot.shapeType() != ShapeType.PLANE) { return; } @@ -362,18 +361,18 @@ private static void renderSpaceOnlyShapes(@Nonnull Collection viewers private static void renderContacts(@Nonnull Collection viewers, @Nonnull Store physicsStore, - @Nonnull PhysicsSpaceBinding space, + @Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid, @Nonnull DebugQueryCache queryCache, @Nonnull Vector3d viewerPosition, double viewRadius, int maxContacts, float time) { - DebugQueryKey key = DebugQueryKey.contacts(space.spaceId(), viewerUuid); + DebugQueryKey key = DebugQueryKey.contacts(spaceId, viewerUuid); try { queryCache.requestContactsIfIdle(key, () -> PhysicsStoreDebugQueries.contactsAsync(physicsStore, - space.spaceId(), + spaceId, viewerPosition, viewRadius, maxContacts)); @@ -387,18 +386,18 @@ private static void renderContacts(@Nonnull Collection viewers, private static void renderJoints(@Nonnull Collection viewers, @Nonnull Store physicsStore, - @Nonnull PhysicsSpaceBinding space, + @Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid, @Nonnull DebugQueryCache queryCache, @Nonnull Vector3d viewerPosition, double viewRadius, int maxJoints, float time) { - DebugQueryKey key = DebugQueryKey.joints(space.spaceId(), viewerUuid); + DebugQueryKey key = DebugQueryKey.joints(spaceId, viewerUuid); try { queryCache.requestJointsIfIdle(key, () -> PhysicsStoreDebugQueries.jointsAsync(physicsStore, - space.spaceId(), + spaceId, viewerPosition, viewRadius, maxJoints)); @@ -412,7 +411,7 @@ private static void renderJoints(@Nonnull Collection viewers, private static void renderWorldCollision(@Nonnull Collection viewers, @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsSpaceBinding space, + @Nonnull SpaceId spaceId, @Nonnull Vector3d viewerPosition, double viewRadius, int maxSections, @@ -420,7 +419,7 @@ private static void renderWorldCollision(@Nonnull Collection viewers, float time) { double maxDistanceSquared = viewRadius * viewRadius; List visibleSections = collectVisibleWorldCollisionSections( - resource, space, viewerPosition, maxDistanceSquared); + resource, spaceId, viewerPosition, maxDistanceSquared); visibleSections.sort(Comparator.comparingDouble(VisibleDebugSection::distanceSquared)); int sectionLimit = Math.min(maxSections, visibleSections.size()); @@ -451,11 +450,11 @@ private static void renderWorldCollision(@Nonnull Collection viewers, @Nonnull private static List collectVisibleWorldCollisionSections( @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsSpaceBinding space, + @Nonnull SpaceId spaceId, @Nonnull Vector3d viewerPosition, double maxDistanceSquared) { List visibleSections = new ArrayList<>(); - resource.worldCollisionCache().forEachDebugSection(space.spaceId(), section -> { + resource.worldCollisionCache().forEachDebugSection(spaceId, section -> { double distanceSquared = distanceSquaredToSection(viewerPosition, section); if (distanceSquared > maxDistanceSquared) { return; From 4553f4a50db8e776c42d19907a4fadfe8d98de24 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:12:56 +0200 Subject: [PATCH 272/534] fix(core): read world collision debug from physics store Signed-off-by: Blovien --- ...PhysicsDebugWorldCollisionSectionView.java | 21 ++ .../systems/debug/PhysicsDebugSystem.java | 85 ++++++-- .../debug/PhysicsStoreDebugQueries.java | 184 ++++++++++++++++++ 3 files changed, 276 insertions(+), 14 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java new file mode 100644 index 00000000..a0b5c2e7 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java @@ -0,0 +1,21 @@ +package dev.hytalemodding.impulse.core.internal.simulation.view; + +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Copied terrain debug section used by the internal debug renderer. + */ +public record PhysicsDebugWorldCollisionSectionView(int chunkX, + int sectionY, + int chunkZ, + boolean voxelTerrain, + @Nonnull List fullCubeBoxes, + @Nonnull List detailBoxes) { + + public PhysicsDebugWorldCollisionSectionView { + fullCubeBoxes = List.copyOf(fullCubeBoxes); + detailBoxes = List.copyOf(detailBoxes); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index f51d1bea..c063f03f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -28,7 +28,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.DebugSection; +import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugWorldCollisionSectionView; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -169,8 +169,10 @@ public void tick(float dt, int index, @Nonnull Store store) { } if (worldCollisionDue && debugWorldCollision) { renderWorldCollision(target, - resource, + physicsStore, spaceId, + viewerUuid, + queryCache, viewerPosition, debug.getViewRadius(), debug.getMaxWorldCollisionSections(), @@ -410,21 +412,36 @@ private static void renderJoints(@Nonnull Collection viewers, } private static void renderWorldCollision(@Nonnull Collection viewers, - @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull Store physicsStore, @Nonnull SpaceId spaceId, + @Nonnull UUID viewerUuid, + @Nonnull DebugQueryCache queryCache, @Nonnull Vector3d viewerPosition, double viewRadius, int maxSections, int maxBoxes, float time) { + DebugQueryKey key = DebugQueryKey.worldCollision(spaceId, viewerUuid); + try { + queryCache.requestWorldCollisionIfIdle(key, + () -> PhysicsStoreDebugQueries.worldCollisionSectionsAsync(physicsStore, + spaceId, + viewerPosition, + viewRadius)); + } catch (RuntimeException exception) { + return; + } + double maxDistanceSquared = viewRadius * viewRadius; List visibleSections = collectVisibleWorldCollisionSections( - resource, spaceId, viewerPosition, maxDistanceSquared); + queryCache.worldCollisionSectionsOrEmpty(key), + viewerPosition, + maxDistanceSquared); visibleSections.sort(Comparator.comparingDouble(VisibleDebugSection::distanceSquared)); int sectionLimit = Math.min(maxSections, visibleSections.size()); for (int i = 0; i < sectionLimit; i++) { - DebugSection section = visibleSections.get(i).section(); + PhysicsDebugWorldCollisionSectionView section = visibleSections.get(i).section(); PhysicsDebugRenderer.renderWorldCollisionSection(viewers, section.chunkX(), section.sectionY(), @@ -449,19 +466,18 @@ private static void renderWorldCollision(@Nonnull Collection viewers, @Nonnull private static List collectVisibleWorldCollisionSections( - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull SpaceId spaceId, + @Nonnull Iterable sections, @Nonnull Vector3d viewerPosition, double maxDistanceSquared) { List visibleSections = new ArrayList<>(); - resource.worldCollisionCache().forEachDebugSection(spaceId, section -> { + for (PhysicsDebugWorldCollisionSectionView section : sections) { double distanceSquared = distanceSquaredToSection(viewerPosition, section); if (distanceSquared > maxDistanceSquared) { - return; + continue; } visibleSections.add(new VisibleDebugSection(section, distanceSquared)); - }); + } return visibleSections; } @@ -472,7 +488,7 @@ private static List collectVisibleWorldCollisionBoxes( double maxDistanceSquared) { List visibleBoxes = new ArrayList<>(); for (VisibleDebugSection visibleSection : visibleSections) { - DebugSection section = visibleSection.section(); + PhysicsDebugWorldCollisionSectionView section = visibleSection.section(); collectVisibleWorldCollisionBoxes(viewerPosition, maxDistanceSquared, section.fullCubeBoxes(), @@ -503,7 +519,7 @@ private static void collectVisibleWorldCollisionBoxes(@Nonnull Vector3d viewerPo } private static double distanceSquaredToSection(@Nonnull Vector3d viewerPosition, - @Nonnull DebugSection section) { + @Nonnull PhysicsDebugWorldCollisionSectionView section) { double minX = section.chunkX() << ChunkUtil.BITS; double minY = section.sectionY() << ChunkUtil.BITS; double minZ = section.chunkZ() << ChunkUtil.BITS; @@ -569,6 +585,12 @@ static final class DebugQueryCache { @Nonnull private final Map> completedJoints = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map>> + pendingWorldCollisionSections = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map> + completedWorldCollisionSections = new Object2ObjectOpenHashMap<>(); synchronized boolean requestContactsIfIdle(@Nonnull DebugQueryKey key, @Nonnull Supplier>> completionSupplier) { @@ -602,6 +624,24 @@ synchronized List jointsOrEmpty(@Nonnull DebugQueryKey ke return completedJoints.getOrDefault(key, List.of()); } + synchronized boolean requestWorldCollisionIfIdle(@Nonnull DebugQueryKey key, + @Nonnull Supplier>> + completionSupplier) { + pollWorldCollision(key); + if (pendingWorldCollisionSections.containsKey(key)) { + return false; + } + pendingWorldCollisionSections.put(key, completionSupplier.get().toCompletableFuture()); + return true; + } + + @Nonnull + synchronized List worldCollisionSectionsOrEmpty( + @Nonnull DebugQueryKey key) { + pollWorldCollision(key); + return completedWorldCollisionSections.getOrDefault(key, List.of()); + } + private void pollContacts(@Nonnull DebugQueryKey key) { CompletableFuture> pending = pendingContacts.get(key); if (pending == null || !pending.isDone()) { @@ -620,6 +660,16 @@ private void pollJoints(@Nonnull DebugQueryKey key) { completedJoints.put(key, completedList(pending)); } + private void pollWorldCollision(@Nonnull DebugQueryKey key) { + CompletableFuture> pending = + pendingWorldCollisionSections.get(key); + if (pending == null || !pending.isDone()) { + return; + } + pendingWorldCollisionSections.remove(key); + completedWorldCollisionSections.put(key, completedList(pending)); + } + @Nonnull private static List completedList(@Nonnull CompletableFuture> future) { try { @@ -649,14 +699,21 @@ static DebugQueryKey contacts(@Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid static DebugQueryKey joints(@Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid) { return new DebugQueryKey(QueryKind.JOINTS, spaceId, viewerUuid); } + + @Nonnull + static DebugQueryKey worldCollision(@Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid) { + return new DebugQueryKey(QueryKind.WORLD_COLLISION, spaceId, viewerUuid); + } } enum QueryKind { CONTACTS, - JOINTS + JOINTS, + WORLD_COLLISION } - private record VisibleDebugSection(@Nonnull DebugSection section, double distanceSquared) { + private record VisibleDebugSection(@Nonnull PhysicsDebugWorldCollisionSectionView section, + double distanceSquared) { } private record VisibleDebugBox(@Nonnull BoxCollider box, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index d1b63089..e8099c24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -4,19 +4,25 @@ import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; +import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugWorldCollisionSectionView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import java.util.ArrayList; import java.util.List; @@ -82,6 +88,25 @@ static CompletionStage> jointsAsync( maxJoints)); } + @Nonnull + static CompletionStage> worldCollisionSectionsAsync( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d viewerPosition, + double viewRadius) { + double viewerX = viewerPosition.x; + double viewerY = viewerPosition.y; + double viewerZ = viewerPosition.z; + return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + "queue PhysicsStore world-collision debug read", + physics -> worldCollisionSections(physics, + spaceId, + viewerX, + viewerY, + viewerZ, + viewRadius)); + } + @Nonnull private static List contacts(@Nonnull Store store, @Nonnull SpaceId spaceId, @@ -174,6 +199,112 @@ private static List joints(@Nonnull Store s return List.copyOf(visible); } + @Nonnull + private static List worldCollisionSections( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + double viewerX, + double viewerY, + double viewerZ, + double viewRadius) { + PhysicsStoreThreading.requireWorldThread(store, + "read PhysicsStore world-collision debug sections"); + SpaceContext spaceContext = space(store, spaceId); + if (spaceContext == null) { + return List.of(); + } + + UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(spaceId); + if (spaceUuid == null) { + return List.of(); + } + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + + PhysicsTerrainPayloadResource payloads = store.getResource( + PhysicsTerrainPayloadResource.getResourceType()); + double maxDistanceSquared = viewRadius * viewRadius; + List visible = new ArrayList<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectWorldCollisionChunk(chunk, + payloads, + spaceContext, + spaceRef, + spaceUuid, + viewerX, + viewerY, + viewerZ, + maxDistanceSquared, + visible); + store.forEachChunk(TerrainColliderComponent.getComponentType(), collector); + return List.copyOf(visible); + } + + private static void collectWorldCollisionChunk(@Nonnull ArchetypeChunk chunk, + @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull SpaceContext spaceContext, + @Nullable Ref spaceRef, + @Nonnull UUID spaceUuid, + double viewerX, + double viewerY, + double viewerZ, + double maxDistanceSquared, + @Nonnull List visible) { + for (int index = 0; index < chunk.size(); index++) { + TerrainColliderComponent terrain = chunk.getComponent(index, + TerrainColliderComponent.getComponentType()); + if (terrain == null || !terrain.isRetained() + || !matchesSpace(terrain, spaceRef, spaceUuid)) { + continue; + } + if (distanceSquaredToSection(terrain, viewerX, viewerY, viewerZ) + > maxDistanceSquared) { + continue; + } + TerrainColliderPayload payload = payloads.get(terrain.getPayloadResourceKey()); + if (payload == null || payload.isEmpty()) { + continue; + } + visible.add(toWorldCollisionSectionView(terrain, payload, spaceContext)); + } + } + + @Nonnull + private static PhysicsDebugWorldCollisionSectionView toWorldCollisionSectionView( + @Nonnull TerrainColliderComponent terrain, + @Nonnull TerrainColliderPayload payload, + @Nonnull SpaceContext spaceContext) { + boolean voxelTerrain = payload.nativeVoxelTerrainEnabled() + && payload.hasFullCubeVoxels() + && spaceContext.backendRuntime() + .supportsVoxelTerrain(spaceContext.spaceHandle().value()); + return new PhysicsDebugWorldCollisionSectionView(terrain.getChunkX(), + terrain.getSectionY(), + terrain.getChunkZ(), + voxelTerrain, + boxes(payload.mergedFullCubeBoxes()), + boxes(payload.detailBoxes())); + } + + @Nonnull + private static List boxes( + @Nonnull List payloadBoxes) { + if (payloadBoxes.isEmpty()) { + return List.of(); + } + List boxes = new ArrayList<>(payloadBoxes.size()); + for (TerrainColliderPayload.BoxPayload box : payloadBoxes) { + boxes.add(new BoxCollider(box.centerX(), + box.centerY(), + box.centerZ(), + box.halfX(), + box.halfY(), + box.halfZ())); + } + return boxes; + } + private static void collectJointChunk(@Nonnull ArchetypeChunk chunk, @Nonnull PhysicsSnapshotResource snapshots, @Nullable Ref spaceRef, @@ -258,6 +389,16 @@ private static boolean matchesSpace(@Nonnull JointComponent joint, return spaceUuid.equals(joint.getSpaceUuid()); } + private static boolean matchesSpace(@Nonnull TerrainColliderComponent terrain, + @Nullable Ref spaceRef, + @Nonnull UUID spaceUuid) { + Ref terrainSpaceRef = terrain.getSpaceRef(); + if (terrainSpaceRef != null && spaceRef != null) { + return sameRef(terrainSpaceRef, spaceRef); + } + return spaceUuid.equals(terrain.getSpaceUuid()); + } + @Nullable private static PhysicsStoreBodySnapshot bodySnapshot( @Nonnull PhysicsSnapshotResource snapshots, @@ -348,6 +489,49 @@ private static double distanceSquared(double x, return dx * dx + dy * dy + dz * dz; } + private static double distanceSquaredToSection(@Nonnull TerrainColliderComponent terrain, + double viewerX, + double viewerY, + double viewerZ) { + double minX = terrain.getChunkX() << ChunkUtil.BITS; + double minY = terrain.getSectionY() << ChunkUtil.BITS; + double minZ = terrain.getChunkZ() << ChunkUtil.BITS; + return distanceSquaredToBounds(viewerX, + viewerY, + viewerZ, + minX, + minY, + minZ, + minX + ChunkUtil.SIZE, + minY + ChunkUtil.SIZE, + minZ + ChunkUtil.SIZE); + } + + private static double distanceSquaredToBounds(double viewerX, + double viewerY, + double viewerZ, + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ) { + double dx = axisDistance(viewerX, minX, maxX); + double dy = axisDistance(viewerY, minY, maxY); + double dz = axisDistance(viewerZ, minZ, maxZ); + return dx * dx + dy * dy + dz * dz; + } + + private static double axisDistance(double value, double min, double max) { + if (value < min) { + return min - value; + } + if (value > max) { + return value - max; + } + return 0.0; + } + private record SpaceContext(@Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } From 5844aec252f9ee06cd8341e66b2856de01d2794e Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:15:36 +0200 Subject: [PATCH 273/534] refactor(core): rename physics store entity holder helper Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreEntities.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java index eea9c63b..4e35534c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java @@ -35,18 +35,18 @@ private PhysicsStoreEntities() { } @Nonnull - public static Holder rowHolder(@Nonnull Store store, - @Nonnull UUID rowUuid) { + public static Holder entityHolder(@Nonnull Store store, + @Nonnull UUID entityUuid) { Holder holder = store.getRegistry().newHolder(); - addUuid(holder, rowUuid); + addUuid(holder, entityUuid); return holder; } public static void addUuid(@Nonnull Holder holder, - @Nonnull UUID rowUuid) { + @Nonnull UUID entityUuid) { Objects.requireNonNull(holder, "holder") .addComponent(UuidComponent.getComponentType(), - new UuidComponent(Objects.requireNonNull(rowUuid, "rowUuid"))); + new UuidComponent(Objects.requireNonNull(entityUuid, "entityUuid"))); } @Nonnull @@ -59,7 +59,7 @@ public static Holder spaceHolder(@Nonnull Store stor @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { - Holder holder = rowHolder(store, spaceUuid); + Holder holder = entityHolder(store, spaceUuid); addSpaceComponents(holder, space, worldCollision, @@ -81,7 +81,7 @@ public static Holder bodyHolder(@Nonnull Store store @Nonnull ShapeComponent shape, @Nonnull MaterialComponent material, @Nonnull CollisionFilterComponent filter) { - Holder holder = rowHolder(store, bodyUuid); + Holder holder = entityHolder(store, bodyUuid); addBodyComponents(holder, body, dynamics, target, collider, shape, material, filter); return holder; } @@ -90,7 +90,7 @@ public static Holder bodyHolder(@Nonnull Store store public static Holder jointHolder(@Nonnull Store store, @Nonnull UUID jointUuid, @Nonnull JointComponent joint) { - Holder holder = rowHolder(store, jointUuid); + Holder holder = entityHolder(store, jointUuid); holder.addComponent(JointComponent.getComponentType(), Objects.requireNonNull(joint, "joint").clone()); return holder; @@ -100,7 +100,7 @@ public static Holder jointHolder(@Nonnull Store stor public static Holder terrainColliderHolder(@Nonnull Store store, @Nonnull UUID terrainColliderUuid, @Nonnull TerrainColliderComponent terrainCollider) { - Holder holder = rowHolder(store, terrainColliderUuid); + Holder holder = entityHolder(store, terrainColliderUuid); holder.addComponent(TerrainColliderComponent.getComponentType(), Objects.requireNonNull(terrainCollider, "terrainCollider").clone()); return holder; From 30c775c0340749f801e521b959c35c42ce94753f Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:17:56 +0200 Subject: [PATCH 274/534] refactor(core): hide legacy world collision cache Signed-off-by: Blovien --- .../internal/resources/PhysicsWorldRuntimeResource.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 70601017..f17d037f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -45,7 +45,6 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsWorldCollisionRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -1143,11 +1142,6 @@ public int getBodySnapshotCellCount() { return lifecycleState.bodySnapshotCellCount(); } - @Nonnull - public WorldVoxelCollisionCache worldCollisionCache() { - return collisionRuntime.worldVoxelCollisionCache(); - } - @Nonnull @Override public WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world, From 30ad6c29e2f2c8e110dc756d33ffaf0297f28b98 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:20:13 +0200 Subject: [PATCH 275/534] refactor(core): narrow physics store space cleanup identity Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreTopologyMutations.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index bb29b6de..8697e6ec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; @@ -75,12 +74,6 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( keptSpaces); } - public static void removeSpaceWithContents(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId); - removeSpaceWithContents(store, spaceUuid); - } - public static void removeSpaceWithContents(@Nonnull Store store, @Nonnull UUID spaceUuid) { PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space entity"); From bbc0c93b925554672425391d4b62668a49f31cd8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:22:59 +0200 Subject: [PATCH 276/534] refactor(core): remove public clear bodies facade Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 31 ------------------- .../resources/PhysicsWorldResource.java | 11 ------- 2 files changed, 42 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index f17d037f..664a304f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2065,37 +2065,6 @@ public void clearSyntheticVisualInterests() { visualRuntime.clearSyntheticVisualInterests(); } - @Override - public void clearBodies() { - if (isAuthoritativePhysicsStoreActive()) { - Store store = authoritativePhysicsStore("clear physics bodies"); - clearAuthoritativeWorldCollisionStreaming(store); - PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); - return; - } - requireLegacyMutationAllowed("clear physics bodies"); - runDirectRuntimeMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); - } - - @Nonnull - @Override - public PhysicsMutationHandle clearBodiesAsync() { - if (isAuthoritativePhysicsStoreActive()) { - return enqueueAuthoritativePhysicsStoreMutation("clear physics bodies", - null, - store -> { - clearAuthoritativeWorldCollisionStreaming(store); - PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); - }); - } - requireLegacyMutationAllowed("clear physics bodies"); - return enqueueDirectRuntimeMutation("clear physics bodies", this::destroyRegisteredBodiesDirect); - } - - private void destroyRegisteredBodiesDirect() { - bodyRuntime.destroyRegisteredBodies(); - } - private void clearBodyStateDirect() { clearRuntimeTopologyDirect(false); markWorldChanged(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 082d5fb2..c87a5623 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -403,17 +403,6 @@ public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { return false; } - /** - * Destroys all registered bodies while preserving registered physics spaces. - */ - public abstract void clearBodies(); - - /** - * Queues destruction of all registered bodies while preserving registered physics spaces. - */ - @Nonnull - public abstract PhysicsMutationHandle clearBodiesAsync(); - public static ResourceType getResourceType() { return ImpulsePlugin.get().getPhysicsWorldResourceType(); } From 13f7f1950f82c4464555ca63c2b69cb094141ba1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:26:44 +0200 Subject: [PATCH 277/534] refactor(core): remove uuid controlled-body helpers Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 664a304f..102c0a5a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2112,36 +2112,14 @@ public void markBodyControlled(@Nonnull Ref bodyRef) { controlRuntime.markBodyControlled(bodyRef); } - public void markBodyControlled(@Nonnull UUID bodyUuid) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, - "resolve controlled body UUID"); - if (bodyRef != null) { - controlRuntime.markBodyControlled(bodyRef); - } - } - public void clearControlledBody(@Nonnull Ref bodyRef) { controlRuntime.clearControlledBody(bodyRef); } - public void clearControlledBody(@Nonnull UUID bodyUuid) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, - "resolve controlled body UUID"); - if (bodyRef != null) { - controlRuntime.clearControlledBody(bodyRef); - } - } - public boolean isBodyControlled(@Nonnull Ref bodyRef) { return controlRuntime.isBodyControlled(bodyRef); } - public boolean isBodyControlled(@Nonnull UUID bodyUuid) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, - "resolve controlled body UUID"); - return bodyRef != null && controlRuntime.isBodyControlled(bodyRef); - } - @Nullable private Ref resolvePhysicsStoreBodyRef(@Nonnull UUID bodyUuid, @Nonnull String operation) { From 74ac928aacdd2435de6baff161a1688710dcd52d Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:29:27 +0200 Subject: [PATCH 278/534] refactor(core): remove legacy runtime collection accessors Signed-off-by: Blovien --- .../resources/PhysicsSpaceRuntime.java | 5 --- .../PhysicsWorldRuntimeResource.java | 33 ------------------- 2 files changed, 38 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java index 2a5738e9..884c1c33 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java @@ -99,11 +99,6 @@ public synchronized Collection getBindings() { return new ArrayList<>(spaces.values()); } - @Nonnull - public synchronized Iterable iterateBindings() { - return new ArrayList<>(spaces.values()); - } - @Nonnull public synchronized List getSpaceIds() { List ids = new ArrayList<>(spaces.size()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 102c0a5a..da77131b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -38,7 +38,6 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotRefVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; @@ -575,11 +574,6 @@ public PhysicsSpaceBinding requireSpaceBinding(@Nonnull SpaceId spaceId) { return spaceRuntime.requireBinding(spaceId); } - @Nonnull - public Collection getSpaceBindings() { - return spaceRuntime.getBindings(); - } - @Nonnull @Override public Collection getSpaceIds() { @@ -601,15 +595,6 @@ public int getSpaceCount() { return spaceRuntime.getSpaceCount(); } - /** - * Iterate spaces without allocating a snapshot collection. - * Use this from tick systems that do not mutate the space map while iterating. - */ - @Nonnull - public Iterable iterateSpaceBindings() { - return spaceRuntime.iterateBindings(); - } - @Override public int refreshBodySnapshots() { if (isAuthoritativePhysicsStoreActive()) { @@ -1833,18 +1818,6 @@ public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref getJointRegistrations() { - assertCanAccessLiveBackendDirectly("list physics joint registrations"); - return jointRegistry.getRegistrations(); - } - - @Nonnull - public Collection getBodyRegistrations() { - assertCanAccessLiveBackendDirectly("list physics body registrations"); - return bodyRegistry.getRegistrations(); - } - @Nonnull @Override public Collection getBodyRegistrationViews() { @@ -1876,12 +1849,6 @@ public int getBodyRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persiste return bodyRegistry.getPublishedRegistrationCount(persistenceMode); } - @Nonnull - public Collection getBodyRegistrations(@Nonnull PhysicsBodyKind kind) { - assertCanAccessLiveBackendDirectly("list physics body registrations"); - return bodyRegistry.getRegistrations(kind); - } - @Nonnull @Override public Collection getBodyRegistrationViews(@Nonnull PhysicsBodyKind kind) { From e1c9ffa79772ce405b0cde5f6d5103328b27e11c Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:31:50 +0200 Subject: [PATCH 279/534] refactor(core): narrow live runtime helpers Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index da77131b..6e296d74 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -555,7 +555,7 @@ private PhysicsSpaceBinding createSpaceDirect(@Nonnull BackendId backendId, } @Nullable - public PhysicsSpaceBinding getSpaceBinding(@Nonnull SpaceId spaceId) { + private PhysicsSpaceBinding getSpaceBinding(@Nonnull SpaceId spaceId) { return spaceRuntime.getBinding(spaceId); } @@ -570,7 +570,7 @@ public boolean hasSpace(@Nonnull SpaceId spaceId) { } @Nonnull - public PhysicsSpaceBinding requireSpaceBinding(@Nonnull SpaceId spaceId) { + private PhysicsSpaceBinding requireSpaceBinding(@Nonnull SpaceId spaceId) { return spaceRuntime.requireBinding(spaceId); } @@ -990,7 +990,7 @@ private static float authoredMass(@Nullable DynamicsComponent dynamics) { } @Nonnull - public PhysicsBodySnapshot captureLiveBodySnapshot(@Nonnull PhysicsBodyRegistration registration) { + private PhysicsBodySnapshot captureLiveBodySnapshot(@Nonnull PhysicsBodyRegistration registration) { Objects.requireNonNull(registration, "registration"); assertCanAccessLiveBackendDirectly("capture live physics body snapshot"); PhysicsSpaceBinding space = requireSpaceBinding(registration.spaceId()); @@ -1783,19 +1783,6 @@ private void destroyBodyDirect(@Nonnull UUID bodyUuid, boolean removeFromSpace) bodyRuntime.destroyBody(bodyUuid, removeFromSpace); } - @Nullable - public PhysicsBodyRegistration getBodyRegistration(@Nonnull SpaceId spaceId, long backendBodyId) { - assertCanAccessLiveBackendDirectly("resolve physics body registration"); - UUID bodyUuid = bodyRegistry.getBodyUuid(spaceId, backendBodyId); - return bodyUuid != null ? bodyRegistry.getRegistration(bodyUuid) : null; - } - - @Nullable - public PhysicsBodyRegistration getRegistration(@Nonnull UUID bodyUuid) { - assertCanAccessLiveBackendDirectly("resolve physics body registration"); - return bodyRegistry.getRegistration(bodyUuid); - } - @Nullable @Override public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { From dd9d7df0774324264ea92fd03add0425f9564506 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:34:03 +0200 Subject: [PATCH 280/534] refactor(core): hide snapshot publication helpers Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 51 +------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 6e296d74..1240da1c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1010,39 +1010,6 @@ private PhysicsBodySnapshot captureLiveBodySnapshot(@Nonnull PhysicsBodyRegistra * publication ordering and stale-frame rejection. {@code stepSequence} and * {@code serverTick} are copied through as external correlation metadata.

    */ - @Nonnull - public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame(long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled) { - return capturePublishedSnapshotFrame(stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled, - List.of(), - 0); - } - - @Nonnull - public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame(long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled, - @Nonnull List physicsEvents, - int droppedBackendEventCount) { - return callDirectRuntime("capture published physics snapshot frame", - () -> capturePublishedSnapshotFrameDirect(stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled, - physicsEvents, - droppedBackendEventCount)); - } - @Nonnull private PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrameDirect(long stepSequence, long serverTick, @@ -1079,22 +1046,8 @@ private PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrameDirect(long s droppedBackendEventCount); } - public int applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { - return applyPublishedSnapshotFrame(frame, 0L); - } - - public int applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame frame, - long publicationServerTick) { - return lifecycleState.applyPublishedSnapshotFrame(frame, bodyRegistry, publicationServerTick); - } - - @Nonnull - public PublishedPhysicsSnapshotFrame getLatestPublishedFrame() { - return lifecycleState.latestPublishedFrame(); - } - - public long getLatestSnapshotAppliedNanos() { - return lifecycleState.latestSnapshotAppliedNanos(); + private int applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { + return lifecycleState.applyPublishedSnapshotFrame(frame, bodyRegistry, 0L); } @Override From 9b7b8254d0c4f635f9397a6b3c7ae28549eab7b4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:36:45 +0200 Subject: [PATCH 281/534] refactor(core): remove runtime cleanup facades Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 63 ++----------------- 1 file changed, 6 insertions(+), 57 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 1240da1c..1203ac0f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1705,21 +1705,6 @@ public void destroyBody(@Nonnull UUID bodyUuid) { @Nonnull @Override public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid) { - return destroyBodyAsync(bodyUuid, true); - } - - public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { - if (isAuthoritativePhysicsStoreActive()) { - destroyBody(bodyUuid); - return; - } - requireLegacyMutationAllowed("destroy physics body"); - runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyUuid, removeFromSpace)); - } - - @Nonnull - public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid, - boolean removeFromSpace) { UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", @@ -1729,7 +1714,12 @@ public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid, requireLegacyMutationAllowed("destroy physics body"); return enqueueDirectRuntimeMutation("destroy physics body", checkedBodyUuid, - () -> destroyBodyDirect(checkedBodyUuid, removeFromSpace)); + () -> destroyBodyDirect(checkedBodyUuid, true)); + } + + private void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { + requireLegacyMutationAllowed("destroy physics body"); + runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyUuid, removeFromSpace)); } private void destroyBodyDirect(@Nonnull UUID bodyUuid, boolean removeFromSpace) { @@ -2027,51 +2017,10 @@ public boolean isBodyControlled(@Nonnull Ref bodyRef) { return controlRuntime.isBodyControlled(bodyRef); } - @Nullable - private Ref resolvePhysicsStoreBodyRef(@Nonnull UUID bodyUuid, - @Nonnull String operation) { - if (!hasAttachedAuthoritativePhysicsStore()) { - return null; - } - World world = requireAuthoritativeWorld(operation); - if (!world.isInThread()) { - return null; - } - Ref ref = physicsStore(world) - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(bodyUuid); - return ref != null && ref.isValid() ? ref : null; - } - public void disableControlLifecycle() { controlRuntime.clear(); } - public void clearBodyRuntimeState(@Nonnull UUID bodyUuid) { - requireLegacyMutationAllowed("clear physics body runtime state"); - runDirectRuntimeMutation("clear physics body runtime state", () -> clearBodyRuntimeStateDirect(bodyUuid)); - } - - @Nonnull - public PhysicsMutationHandle clearBodyRuntimeStateAsync( - @Nonnull UUID bodyUuid) { - UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - requireLegacyMutationAllowed("clear physics body runtime state"); - return enqueueDirectRuntimeMutation("clear physics body runtime state", - checkedBodyUuid, - () -> clearBodyRuntimeStateDirect(checkedBodyUuid)); - } - - private void clearBodyRuntimeStateDirect(@Nonnull UUID bodyUuid) { - Ref bodyRef = resolvePhysicsStoreBodyRef(bodyUuid, - "resolve cleared body runtime key"); - if (bodyRef != null) { - controlRuntime.clearBody(bodyRef); - } - bodyRuntime.clearBodyRuntimeState(bodyUuid); - visualRuntime.clearBodyRuntimeState(bodyUuid, bodyRef); - } - public void copyFrom(@Nonnull PhysicsWorldResource other) { runDirectRuntimeMutation("copy physics world resource", () -> copyFromDirect(other)); } From 1de5ca75dceb689b2e0a2618f3abc9b59ecd5dee Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:39:17 +0200 Subject: [PATCH 282/534] refactor(core): hide runtime copy helper Signed-off-by: Blovien --- .../internal/resources/PhysicsWorldRuntimeResource.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 1203ac0f..92fc473c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -2021,15 +2021,10 @@ public void disableControlLifecycle() { controlRuntime.clear(); } - public void copyFrom(@Nonnull PhysicsWorldResource other) { + private void copyFrom(@Nonnull PhysicsWorldResource other) { runDirectRuntimeMutation("copy physics world resource", () -> copyFromDirect(other)); } - @Nonnull - public PhysicsMutationHandle copyFromAsync(@Nonnull PhysicsWorldResource other) { - return enqueueDirectRuntimeMutation("copy physics world resource", () -> copyFromDirect(other)); - } - private void copyFromDirect(@Nonnull PhysicsWorldResource other) { if (this == other) { return; From 2ad75f1355f0e5ca99e0fc5e8758c5854f9891cd Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:41:38 +0200 Subject: [PATCH 283/534] refactor(core): remove owner gateway runtime helpers Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 92fc473c..dd6c70c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -158,23 +158,6 @@ public void detachEntityStore(@Nonnull Store store) { } } - @Nonnull - public World requireAuthoritativeWorldForPhysicsStore(@Nonnull String operation) { - return requireAuthoritativeWorld(operation); - } - - public boolean canAccessLiveBackendDirectly() { - return true; - } - - public void rejectSynchronousCompletionCallbackWait(@Nonnull String operation) { - Objects.requireNonNull(operation, "operation"); - } - - public long worldEpoch() { - return lifecycleState.worldEpoch(); - } - @Nonnull @Override public PhysicsEventFrame getLatestEventFrame() { @@ -186,7 +169,7 @@ public PhysicsEventFrame getLatestEventFrame() { return lifecycleState.latestEventFrame(); } - public void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { + private void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { Objects.requireNonNull(operation, "operation"); } @@ -1211,10 +1194,6 @@ public int clearWorldCollision(@Nonnull SpaceId spaceId) { }); } - public long worldCollisionStreamingRevision(@Nonnull SpaceId spaceId) { - return collisionRuntime.streamingRevision(spaceId); - } - @Nonnull @Override public WorldCollisionStats getWorldCollisionStats() { From 9e5638fcb424e249b26e90db1ddb8f0eec2b65a0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:43:49 +0200 Subject: [PATCH 284/534] refactor(core): remove generated proxy read facades Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index dd6c70c2..db3098e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1837,36 +1837,6 @@ public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, visualRuntime.unregisterAttachment(bodyUuid, bodyRef, attachment); } - @Nullable - public Ref getGeneratedVisualProxy(@Nonnull Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativeProjectionIndex("read generated visual proxy") - .getGeneratedVisualProxy(bodyRef); - } - return visualRuntime.getGeneratedVisualProxy(bodyRef); - } - - @Nullable - public Ref getGeneratedVisualProxy(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("read generated visual proxy"); - return bodyRef != null && bodyRef.isValid() - ? projection.getGeneratedVisualProxy(bodyRef) - : projection.getGeneratedVisualProxy(bodyUuid); - } - return visualRuntime.getGeneratedVisualProxy(bodyUuid, bodyRef); - } - - public int getGeneratedVisualProxyCount() { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativeProjectionIndex("count generated visual proxies") - .generatedVisualProxyCount(); - } - return visualRuntime.generatedVisualProxyCount(); - } - public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref proxy) { @@ -1906,28 +1876,6 @@ public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, return visualRuntime.clearGeneratedVisualProxy(bodyUuid, bodyRef, expectedProxy); } - public boolean isGeneratedVisualProxy(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref proxy) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("check generated visual proxy"); - Ref registered = bodyRef != null - ? projection.getGeneratedVisualProxy(bodyRef) - : projection.getGeneratedVisualProxy(bodyUuid); - return sameRef(registered, proxy); - } - return visualRuntime.isGeneratedVisualProxy(bodyUuid, bodyRef, proxy); - } - - public boolean isGeneratedVisualProxy(@Nonnull Ref bodyRef, - @Nonnull Ref proxy) { - if (hasAttachedAuthoritativePhysicsStore()) { - return sameRef(getGeneratedVisualProxy(bodyRef), proxy); - } - return visualRuntime.isGeneratedVisualProxy(bodyRef, proxy); - } - public void setSyntheticVisualInterests(@Nonnull Collection interests) { visualRuntime.setSyntheticVisualInterests(interests); } From 8d24c88972fbf3935c722e0e40011619c779a7fc Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:46:58 +0200 Subject: [PATCH 285/534] refactor(core): remove attachment registration facade Signed-off-by: Blovien --- .../resources/PhysicsWorldRuntimeResource.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index db3098e2..b0cfee58 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1815,17 +1815,6 @@ public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, return visualRuntime.hasAttachments(bodyUuid, bodyRef); } - public void registerBodyAttachment(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref attachment) { - if (hasAttachedAuthoritativePhysicsStore()) { - authoritativeProjectionIndex("register physics body attachment") - .registerAttachment(bodyUuid, bodyRef, attachment); - return; - } - visualRuntime.registerAttachment(bodyUuid, bodyRef, attachment); - } - public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref attachment) { From 43be33dec5913784d1fe8e5616f8698b6f0f0bd7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:53:13 +0200 Subject: [PATCH 286/534] fix(core): attach physics runtime resource to entity store Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 2 + .../PhysicsWorldRuntimeResource.java | 5 ++- .../PhysicsWorldResourceAttachmentSystem.java | 39 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 9912f406..c5f7ab9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -28,6 +28,7 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; @@ -228,6 +229,7 @@ private void registerComponents() { private void registerSystems() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); persistenceRestoreGroup = entityRegistry.registerSystemGroup(); + entityRegistry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); entityRegistry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index b0cfee58..d4f0d9fc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -136,7 +136,10 @@ public PhysicsWorldRuntimeResource() { @Nonnull public static PhysicsWorldRuntimeResource require(@Nonnull Store store) { - return require(store.getResource(PhysicsWorldResource.getResourceType())); + PhysicsWorldRuntimeResource resource = + require(store.getResource(PhysicsWorldResource.getResourceType())); + resource.attachEntityStore(store); + return resource; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java new file mode 100644 index 00000000..768f1278 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java @@ -0,0 +1,39 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; +import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Attaches the concrete physics runtime resource to its owning EntityStore. + */ +public final class PhysicsWorldResourceAttachmentSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.BEFORE, PhysicsStoreEventPublicationSystem.class), + new SystemDependency<>(Order.BEFORE, PhysicsGeneratedProxyCleanupSystem.class), + new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class), + new SystemDependency<>(Order.BEFORE, PhysicsDebugSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsWorldRuntimeResource.require(store); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} From 9b716652d3b7a779c2066adacaf1f4382c03c085 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 19:55:33 +0200 Subject: [PATCH 287/534] refactor(core): rename stale owner lane wording Signed-off-by: Blovien --- .../settings/StepSchedulingSettingCommand.java | 2 +- .../modules/control/ControlLifecycle.java | 6 +++--- .../resources/PhysicsWorldSnapshotState.java | 18 +++++++++--------- .../settings/PhysicsVisualSyncSettings.java | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java index a7205eaf..35b34648 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java @@ -24,7 +24,7 @@ public class StepSchedulingSettingCommand extends AbstractAsyncPlayerCommand { ArgTypes.STRING); public StepSchedulingSettingCommand() { - super("scheduling", "Get or set how pending owner dt is handled"); + super("scheduling", "Get or set how pending store tick dt is handled"); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java index dbe248df..ea4de9db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java @@ -106,7 +106,7 @@ private static void cleanupStore(@Nonnull Store store, @Nullable ComponentType sessionType) { World world = store.getExternalData().getWorld(); if (world.isInThread()) { - cleanupStoreOnOwnerThread(store, controllableType, sessionType); + cleanupStoreOnWorldThread(store, controllableType, sessionType); return; } if (!world.isStarted()) { @@ -117,7 +117,7 @@ private static void cleanupStore(@Nonnull Store store, try { world.execute(() -> { try { - cleanupStoreOnOwnerThread(store, controllableType, sessionType); + cleanupStoreOnWorldThread(store, controllableType, sessionType); cleanup.complete(null); } catch (Throwable throwable) { cleanup.completeExceptionally(throwable); @@ -143,7 +143,7 @@ private static boolean isWorldTaskRejection(@Nonnull RuntimeException exception) return false; } - private static void cleanupStoreOnOwnerThread(@Nonnull Store store, + private static void cleanupStoreOnWorldThread(@Nonnull Store store, @Nullable ComponentType controllableType, @Nullable ComponentType sessionType) { if (sessionType != null) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index 8ad5c510..0c1d1a78 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -32,7 +32,7 @@ public final class PhysicsWorldSnapshotState { private final PhysicsBodySnapshotStore bodySnapshots = new PhysicsBodySnapshotStore(); - private final PhysicsBodySnapshotStore ownerBodySnapshots = new PhysicsBodySnapshotStore(); + private final PhysicsBodySnapshotStore captureBodySnapshots = new PhysicsBodySnapshotStore(); private final AtomicLong worldEpoch = new AtomicLong(); private final AtomicLong snapshotFrameEpoch = new AtomicLong(); @Nonnull @@ -62,7 +62,7 @@ public void putBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { bodySnapshots.put(bodyUuid, snapshot, spaceId, kind, persistenceMode); - ownerBodySnapshots.put(bodyUuid, snapshot, spaceId, kind, persistenceMode); + captureBodySnapshots.put(bodyUuid, snapshot, spaceId, kind, persistenceMode); } @Nonnull @@ -81,12 +81,12 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( long frameEpoch = snapshotFrameEpoch.incrementAndGet(); long frameWorldEpoch = worldEpoch.get(); long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; - ownerBodySnapshots.refresh(spaces, bodyRegistry); - int spatialIndexCellCount = ownerBodySnapshots.cellCount(); + captureBodySnapshots.refresh(spaces, bodyRegistry); + int spatialIndexCellCount = captureBodySnapshots.cellCount(); int bodyCount = 0; for (PhysicsSpaceBinding space : spaces) { - bodyCount += ownerBodySnapshots.bodyCount(space.spaceId()); + bodyCount += captureBodySnapshots.bodyCount(space.spaceId()); } long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; @@ -102,9 +102,9 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( bodyCount); for (PhysicsSpaceBinding space : spaces) { SpaceId spaceId = space.spaceId(); - int spaceBodyCount = ownerBodySnapshots.bodyCount(spaceId); + int spaceBodyCount = captureBodySnapshots.bodyCount(spaceId); frameBuilder.addSpace(spaceId, frameWorldEpoch, spaceBodyCount); - ownerBodySnapshots.forEachIndexed(spaceId, + captureBodySnapshots.forEachIndexed(spaceId, (bodyUuid, snapshot, bodySpaceId, kind, persistenceMode) -> frameBuilder.addBody(bodyUuid, bodySpaceId, frameWorldEpoch, @@ -181,12 +181,12 @@ public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, public void removeBodySnapshot(@Nonnull UUID bodyUuid) { bodySnapshots.remove(bodyUuid); - ownerBodySnapshots.remove(bodyUuid); + captureBodySnapshots.remove(bodyUuid); } public void clearBodySnapshots() { bodySnapshots.clear(); - ownerBodySnapshots.clear(); + captureBodySnapshots.clear(); } public void markWorldChanged() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java index d9f3fc6b..055eb393 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java @@ -114,7 +114,7 @@ public class PhysicsVisualSyncSettings { public static final float MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE = 120.0f; /** - * Whether owner entity transforms should use player-interest culling. + * Whether body-attached entity transforms should use player-interest culling. * Disabled by default because gameplay code may rely on server-side transforms even * when no player is currently near the body. */ From 9f9af79ddac86dc33cfff5b5d72d056ae9a8cdfa Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 20:01:09 +0200 Subject: [PATCH 288/534] refactor(examples): use public physics store read helpers Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreBodies.java | 79 +++++++++++++++++++ .../physicsstore/PhysicsStoreSpaces.java | 62 +++++++++++++++ .../commands/ExamplePhysicsUtils.java | 21 +---- .../examples/commands/GrabCommand.java | 15 +--- .../systems/ExplosiveFuseTickSystem.java | 20 ++--- 5 files changed, 153 insertions(+), 44 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBodies.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreSpaces.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBodies.java new file mode 100644 index 00000000..9dd62e39 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBodies.java @@ -0,0 +1,79 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Public copied body reads for PhysicsStore entities. + */ +public final class PhysicsStoreBodies { + + private PhysicsStoreBodies() { + } + + @Nullable + public static PhysicsBodyRegistrationView registrationView(@Nonnull Store store, + @Nonnull Ref bodyRef) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body registration"); + Ref checkedRef = Objects.requireNonNull(bodyRef, "bodyRef"); + if (!sameValidStore(checkedStore, checkedRef)) { + return null; + } + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(checkedRef); + } + + @Nullable + public static PhysicsBodyRegistrationView registrationView(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body registration"); + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + @Nullable + public static PhysicsStoreBodySnapshot snapshot(@Nonnull Store store, + @Nonnull Ref bodyRef) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body snapshot"); + Ref checkedRef = Objects.requireNonNull(bodyRef, "bodyRef"); + if (!sameValidStore(checkedStore, checkedRef)) { + return null; + } + return checkedStore.getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(checkedRef); + } + + @Nullable + public static PhysicsStoreBodySnapshot snapshot(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body snapshot"); + return checkedStore.getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + @Nonnull + private static Store requireWorldThread(@Nonnull Store store, + @Nonnull String operation) { + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsStoreThreading.requireWorldThread(checkedStore, operation); + return checkedStore; + } + + private static boolean sameValidStore(@Nonnull Store store, + @Nonnull Ref ref) { + return ref.getStore() == store && ref.isValid(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreSpaces.java new file mode 100644 index 00000000..9cd505e9 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreSpaces.java @@ -0,0 +1,62 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Public compatibility reads for PhysicsStore space entities. + */ +public final class PhysicsStoreSpaces { + + private PhysicsStoreSpaces() { + } + + @Nullable + public static Ref resolveRef(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Store checkedStore = requireWorldThread(store, "resolve a PhysicsStore space ref"); + UUID spaceUuid = checkedStore + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); + if (spaceUuid == null) { + return null; + } + Ref ref = checkedStore + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + return ref != null && ref.getStore() == checkedStore && ref.isValid() ? ref : null; + } + + public static boolean hasSpace(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Store checkedStore = requireWorldThread(store, "check a PhysicsStore space"); + return checkedStore.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .hasSpace(Objects.requireNonNull(spaceId, "spaceId")); + } + + @Nonnull + public static Collection spaceIds(@Nonnull Store store) { + Store checkedStore = requireWorldThread(store, "list PhysicsStore spaces"); + return List.copyOf(checkedStore + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .spaceIds()); + } + + @Nonnull + private static Store requireWorldThread(@Nonnull Store store, + @Nonnull String operation) { + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsStoreThreading.requireWorldThread(checkedStore, operation); + return checkedStore; + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java index e5f2527f..71619525 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java @@ -15,14 +15,13 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; @@ -62,16 +61,7 @@ public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space ref"); - UUID spaceUuid = store - .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); - if (spaceUuid == null) { - return null; - } - Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - return ref != null && ref.getStore() == store && ref.isValid() ? ref : null; + return PhysicsStoreSpaces.resolveRef(store, spaceId); } @Nonnull @@ -191,9 +181,6 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(store, "select a PhysicsStore space"); - PhysicsSpaceCompatibilityIndexResource compatibility = store - .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()); if (spaceArg.provided(ctx)) { int rawSpaceId = spaceArg.get(ctx); if (rawSpaceId <= 0) { @@ -201,14 +188,14 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, return null; } SpaceId spaceId = new SpaceId(rawSpaceId); - if (!compatibility.hasSpace(spaceId)) { + if (!PhysicsStoreSpaces.hasSpace(store, spaceId)) { ctx.sender().sendMessage(Message.raw("No physics space id=" + rawSpaceId + " exists.")); return null; } return spaceId; } - SpaceId firstSpaceId = compatibility.spaceIds() + SpaceId firstSpaceId = PhysicsStoreSpaces.spaceIds(store) .stream() .min(Comparator.comparingInt(SpaceId::value)) .orElse(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index f7a51a66..8ede2201 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -18,8 +18,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -29,8 +27,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; @@ -254,10 +252,7 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource continue; } PhysicsBodyRegistrationView registration = - hit.bodyRef() - .getStore() - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(hit.bodyRef()); + PhysicsStoreBodies.registrationView(hit.bodyRef().getStore(), hit.bodyRef()); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { continue; } @@ -291,11 +286,7 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource private static PhysicsStoreBodySnapshot bodyState(@Nonnull World world, @Nonnull Ref bodyRef) { Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); - PhysicsStoreThreading.requireWorldThread(store, - "read copied PhysicsStore grab body snapshot"); - return store - .getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(bodyRef); + return PhysicsStoreBodies.snapshot(store, bodyRef); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 57085e5a..add27526 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -15,10 +15,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; @@ -120,16 +118,12 @@ private static SpaceId attachmentSpaceId(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { Store physics = ((PhysicsStoreWorld) store.getExternalData().getWorld()) .getPhysicsStore().getStore(); - PhysicsStoreThreading.requireWorldThread(physics, - "read copied PhysicsStore explosive body registration"); - PhysicsBodyRegistrationResource registrations = physics - .getResource(PhysicsBodyRegistrationResource.getResourceType()); Ref bodyRef = attachment.getBodyRef(); PhysicsBodyRegistrationView registration = bodyRef != null && bodyRef.isValid() - ? registrations.getBodyRegistrationView(bodyRef) + ? PhysicsStoreBodies.registrationView(physics, bodyRef) : null; if (registration == null) { - registration = registrations.getBodyRegistrationView(attachment.getBodyUuid()); + registration = PhysicsStoreBodies.registrationView(physics, attachment.getBodyUuid()); } return registration != null ? registration.spaceId() : null; } @@ -140,19 +134,15 @@ private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store UUID bodyUuid = attachment.getBodyUuid(); Store physics = ((PhysicsStoreWorld) store.getExternalData().getWorld()) .getPhysicsStore().getStore(); - PhysicsStoreThreading.requireWorldThread(physics, - "read copied PhysicsStore explosive body snapshot"); - PhysicsSnapshotResource snapshots = physics - .getResource(PhysicsSnapshotResource.getResourceType()); Ref bodyRef = attachment.getBodyRef(); PhysicsStoreBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() - ? snapshots.getBody(bodyRef) + ? PhysicsStoreBodies.snapshot(physics, bodyRef) : null; if (snapshot != null && !bodyUuid.equals(snapshot.bodyUuid())) { snapshot = null; } if (snapshot == null) { - snapshot = snapshots.getBody(bodyUuid); + snapshot = PhysicsStoreBodies.snapshot(physics, bodyUuid); } return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; } From 29e964eb21995c7a7205256b4c112206d0da0b57 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 20:03:07 +0200 Subject: [PATCH 289/534] refactor(core): require physics store ref facade reads Signed-off-by: Blovien --- .../plugin/resources/PhysicsWorldResource.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index c87a5623..9584e93f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -21,7 +21,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import java.util.Collection; -import java.util.List; import java.util.UUID; import java.util.function.Consumer; import javax.annotation.Nonnull; @@ -343,9 +342,8 @@ public abstract PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull Sp * Returns immutable registration metadata for a live PhysicsStore body ref. */ @Nullable - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { - return null; - } + public abstract PhysicsBodyRegistrationView getBodyRegistrationView( + @Nonnull Ref bodyRef); /** * Returns immutable registration metadata for every registered body. @@ -385,9 +383,8 @@ public abstract Collection> getBodyAttachments(@Nonnull UUID bo * raycast or copied registration.

    */ @Nonnull - public Collection> getBodyAttachments(@Nonnull Ref bodyRef) { - return List.of(); - } + public abstract Collection> getBodyAttachments( + @Nonnull Ref bodyRef); /** * Returns whether a durable body UUID and optional live body ref have one or more ECS attachments. @@ -399,9 +396,7 @@ public abstract boolean hasBodyAttachments(@Nonnull UUID bodyUuid, * Returns whether a live PhysicsStore body ref has one or more ECS attachments without * materializing the attachment collection. */ - public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { - return false; - } + public abstract boolean hasBodyAttachments(@Nonnull Ref bodyRef); public static ResourceType getResourceType() { return ImpulsePlugin.get().getPhysicsWorldResourceType(); From af86ee1f774d0106eb772ca9b13dc7b9fe2769a0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 20:05:08 +0200 Subject: [PATCH 290/534] docs(core): update physics event identity wording Signed-off-by: Blovien --- impulse-core/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/impulse-core/README.md b/impulse-core/README.md index e38de04e..fde8024a 100644 --- a/impulse-core/README.md +++ b/impulse-core/README.md @@ -20,9 +20,9 @@ anywhere under the configured Hytale `mods` directories. `PhysicsWorldResource.getLatestEventFrame()` exposes the latest value-only physics event frame for diagnostics. When collection is enabled, backends emit bounded post-step `PhysicsBackendEvent` -batches; core translates them to stable `PhysicsFrameEvent` values keyed by `RigidBodyKey` and -`JointKey`, then publishes one `PhysicsEventFramePublishedEvent` Hytale world event for the -completed frame. +batches; core translates them to stable UUID-primary `PhysicsFrameEvent` values and copied +PhysicsStore refs where available, then publishes one `PhysicsEventFramePublishedEvent` Hytale +world event for the completed frame. Backend event collection is opt-in through `PhysicsWorldSettings.setEventCollectionMode(...)`. Worlds default to `PhysicsEventCollectionMode.DISABLED`; use From ac6d4b14a4dd5bcb003b96595d26bbfda7140df4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 20:33:24 +0200 Subject: [PATCH 291/534] test(core): align physics store tests with ecs runtime Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 2 + .../ImpulsePluginBackendSelectionTest.java | 32 - .../internal/commands/SpaceSelectionTest.java | 59 +- .../modules/control/ControlLifecycleTest.java | 23 +- .../PhysicsKinematicControlSystemTest.java | 143 +- .../WorldCollisionLifecycleTest.java | 295 +--- .../WorldVoxelCollisionCacheTest.java | 15 +- .../PhysicsChunkBoundarySystemTest.java | 432 ------ .../PhysicsCollisionLodSystemTest.java | 200 --- ...sicsWorldCollisionStreamingSystemTest.java | 98 -- .../PersistentPhysicsCodecValidationTest.java | 571 -------- ...PersistentPhysicsRestorePreflightTest.java | 184 --- ...csStoreRuntimeBoundarySourceGuardTest.java | 101 ++ .../PersistentPhysicsStoreResourceTest.java | 193 +++ .../PhysicsStoreResourceIndexTest.java | 87 +- .../BodyVisualInterestStateTest.java | 4 +- .../resources/PhysicsSpaceSettingsTest.java | 56 +- .../PhysicsStepSchedulingModeTest.java | 2 +- .../PhysicsWorldResourceStateTest.java | 964 ------------- .../body/PhysicsBodyRegistryTest.java | 16 +- .../body/PhysicsBodySnapshotStoreTest.java | 281 +--- .../PhysicsWorldLifecycleStateTest.java | 117 +- .../owner/PhysicsOwnerGatewayTest.java | 246 ---- .../owner/PhysicsOwnerLaneSchedulerTest.java | 1236 ----------------- .../owner/PhysicsOwnerStepCommandTest.java | 981 ------------- .../resources/owner/TestPhysicsOwnerLane.java | 165 --- .../PhysicsRuntimeProfilingResourceTest.java | 25 +- .../visual/PhysicsVisualRuntimeTest.java | 32 +- .../systems/debug/PhysicsDebugSystemTest.java | 7 +- .../PhysicsOwnerLifecycleSystemTest.java | 392 ------ ...istentPhysicsJointHydrationSystemTest.java | 29 - .../PersistentPhysicsWorldSyncSystemTest.java | 571 -------- .../PhysicsPublicationPipelineTest.java | 132 -- .../PhysicsSnapshotPublicationSystemTest.java | 355 ----- .../step/PhysicsStepRestoreGateTest.java | 29 - .../systems/step/PhysicsStepSystemTest.java | 631 --------- .../systems/sync/PhysicsSyncSystemTest.java | 12 +- ...tachedVisualMaterializationSystemTest.java | 230 --- .../LegacyLiveHandleTestResource.java | 329 ----- .../physicsstore/PhysicsBodyRowsTest.java | 63 - .../BodyAttachmentComponentTest.java | 4 +- .../PublishedPhysicsSnapshotFrameTest.java | 19 +- .../examples/commands/EventsCommandTest.java | 6 +- .../events/PhysicsEventTrackerTest.java | 6 +- 44 files changed, 685 insertions(+), 8690 deletions(-) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsCodecValidationTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflightTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGatewayTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneSchedulerTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommandTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/TestPhysicsOwnerLane.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipelineTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGateTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/testsupport/LegacyLiveHandleTestResource.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index 94d42f11..d4e659aa 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -29,6 +29,8 @@ dependencies { compileOnly(project(":impulse-early-plugin")) testImplementation(testFixtures(project(":impulse-api"))) testImplementation(libs.objenesis) + testCompileOnly(project(":impulse-early-plugin")) + testRuntimeOnly(project(":impulse-early-plugin")) testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") compileOnly(libs.crucible) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java index 0dfb9c3b..69ff4e07 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -35,37 +34,6 @@ class ImpulsePluginBackendSelectionTest { @TempDir private Path tempDir; - @Test - void ownerPoolSizeConfigReportsDefaultAcceptedAndFallbackValues() { - String property = ImpulsePlugin.OWNER_POOL_SIZE_PROPERTY; - String previous = System.getProperty(property); - try { - System.clearProperty(property); - ImpulsePlugin.ConfiguredPositiveInt defaultValue = - ImpulsePlugin.configuredPositiveIntDetails(property, 1); - assertEquals(1, defaultValue.value()); - assertFalse(defaultValue.usedFallback()); - - System.setProperty(property, "2"); - ImpulsePlugin.ConfiguredPositiveInt configuredValue = - ImpulsePlugin.configuredPositiveIntDetails(property, 1); - assertEquals(2, configuredValue.value()); - assertFalse(configuredValue.usedFallback()); - - System.setProperty(property, "0"); - ImpulsePlugin.ConfiguredPositiveInt invalidValue = - ImpulsePlugin.configuredPositiveIntDetails(property, 1); - assertEquals(1, invalidValue.value()); - assertTrue(invalidValue.usedFallback()); - } finally { - if (previous == null) { - System.clearProperty(property); - } else { - System.setProperty(property, previous); - } - } - } - @Test void singleRuntimeProviderIsDefaultBackend() { BackendId backendId = new BackendId("impulse:rapier"); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java index 5849f29b..5f3dcef6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java @@ -3,74 +3,45 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.util.concurrent.atomic.AtomicInteger; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import java.util.UUID; import org.junit.jupiter.api.Test; class SpaceSelectionTest { - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - @Test void specifiedSpaceIdReturnsExistingExplicitSpace() { - Fixture fixture = fixture(); + PhysicsSpaceCompatibilityIndexResource compatibility = new PhysicsSpaceCompatibilityIndexResource(); SpaceId spaceId = new SpaceId(12); - fixture.resource().createSpace(fixture.backend().getId(), - spaceId, - "test-world", - PhysicsSpaceSettings.defaults()); + compatibility.putSpace(spaceId, UUID.randomUUID()); - assertEquals(spaceId, SpaceSelection.specifiedSpaceId(fixture.resource(), 12)); + assertEquals(spaceId, SpaceSelection.specifiedSpaceId(compatibility, 12)); } @Test void specifiedSpaceIdRejectsInvalidOrMissingSpace() { - Fixture fixture = fixture(); - fixture.resource().createSpace(fixture.backend().getId(), - new SpaceId(7), - "test-world", - PhysicsSpaceSettings.defaults()); + PhysicsSpaceCompatibilityIndexResource compatibility = new PhysicsSpaceCompatibilityIndexResource(); + compatibility.putSpace(new SpaceId(7), UUID.randomUUID()); - assertNull(SpaceSelection.specifiedSpaceId(fixture.resource(), 0)); - assertNull(SpaceSelection.specifiedSpaceId(fixture.resource(), -1)); - assertNull(SpaceSelection.specifiedSpaceId(fixture.resource(), 8)); + assertNull(SpaceSelection.specifiedSpaceId(compatibility, 0)); + assertNull(SpaceSelection.specifiedSpaceId(compatibility, -1)); + assertNull(SpaceSelection.specifiedSpaceId(compatibility, 8)); } @Test void firstRegisteredSpaceIdSelectsLowestStableId() { - Fixture fixture = fixture(); + PhysicsSpaceCompatibilityIndexResource compatibility = new PhysicsSpaceCompatibilityIndexResource(); SpaceId higher = new SpaceId(20); SpaceId lower = new SpaceId(5); - fixture.resource().createSpace(fixture.backend().getId(), - higher, - "test-world", - PhysicsSpaceSettings.defaults()); - fixture.resource().createSpace(fixture.backend().getId(), - lower, - "test-world", - PhysicsSpaceSettings.defaults()); + compatibility.putSpace(higher, UUID.randomUUID()); + compatibility.putSpace(lower, UUID.randomUUID()); - assertEquals(lower, SpaceSelection.firstRegisteredSpaceId(fixture.resource())); + assertEquals(lower, SpaceSelection.firstRegisteredSpaceId(compatibility)); } @Test void firstRegisteredSpaceIdReturnsNullWhenNoSpacesExist() { - Fixture fixture = fixture(); - - assertNull(SpaceSelection.firstRegisteredSpaceId(fixture.resource())); - } - - private static Fixture fixture() { - FakePhysicsBackend backend = new FakePhysicsBackend("test:space-selection-" - + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - return new Fixture(backend, new LegacyLiveHandleTestResource()); - } - - private record Fixture(FakePhysicsBackend backend, LegacyLiveHandleTestResource resource) { + assertNull(SpaceSelection.firstRegisteredSpaceId(new PhysicsSpaceCompatibilityIndexResource())); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java index 67ead74a..708b7c95 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java @@ -4,14 +4,15 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.EmptyResourceStorage; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import org.junit.jupiter.api.AfterEach; @@ -57,14 +58,14 @@ void disablingLifecycleWithoutRegisteredSessionComponentDoesNotThrow() { void disablingLifecycleClearsRegisteredControlledBodies() { ControlLifecycle.enable(); PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - RigidBodyKey bodyKey = RigidBodyKey.random(); - resource.markBodyControlled(bodyKey); + Ref bodyRef = new TestPhysicsRef(7); + resource.markBodyControlled(bodyRef); - assertTrue(resource.isBodyControlled(bodyKey)); + assertTrue(resource.isBodyControlled(bodyRef)); ControlLifecycle.disable(); - assertFalse(resource.isBodyControlled(bodyKey)); + assertFalse(resource.isBodyControlled(bodyRef)); } @Test @@ -111,4 +112,16 @@ void disablingLifecycleSkipsStoresWhoseWorldThreadHasStopped() { assertFalse(ControlLifecycle.isEnabled()); registry.shutdown(); } + + private static final class TestPhysicsRef extends Ref { + + private TestPhysicsRef(int index) { + super(null, index); + } + + @Override + public boolean isValid() { + return true; + } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java index 2f456652..828b64a6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java @@ -3,41 +3,30 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.EmptyResourceStorage; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem.ControlAnchorUpdate; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem.ControlMutationState; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nonnull; import org.joml.Vector3f; import org.junit.jupiter.api.Test; class PhysicsKinematicControlSystemTest { - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - @Test void controlAnchorUpdateCopiesMutableVectors() { - UUID bodyId = UUID.randomUUID(); - UUID anchorBodyId = UUID.randomUUID(); + Ref bodyRef = new TestPhysicsRef(1); + Ref anchorBodyRef = new TestPhysicsRef(2); Vector3f target = new Vector3f(1.0f, 2.0f, 3.0f); Vector3f releaseVelocity = new Vector3f(4.0f, 5.0f, 6.0f); - ControlAnchorUpdate update = new ControlAnchorUpdate(bodyId, - anchorBodyId, + ControlAnchorUpdate update = new ControlAnchorUpdate(bodyRef, + anchorBodyRef, target, releaseVelocity); target.zero(); @@ -50,47 +39,49 @@ void controlAnchorUpdateCopiesMutableVectors() { @Test void submittedControlMutationSuppressesIdenticalTarget() { ControlMutationState state = new ControlMutationState(); - UUID bodyId = UUID.randomUUID(); - ControlAnchorUpdate first = update(bodyId, bodyId, 1.0f); - ControlAnchorUpdate sameTarget = update(bodyId, bodyId, 1.0f); - ControlAnchorUpdate changedTarget = update(bodyId, bodyId, 2.0f); + Ref bodyRef = new TestPhysicsRef(1); + Ref anchorBodyRef = new TestPhysicsRef(2); + ControlAnchorUpdate first = update(bodyRef, anchorBodyRef, 1.0f); + ControlAnchorUpdate sameTarget = update(bodyRef, anchorBodyRef, 1.0f); + ControlAnchorUpdate changedTarget = update(bodyRef, anchorBodyRef, 2.0f); - state.trackSubmittedMutation(bodyId, first); + state.trackSubmittedMutation(anchorBodyRef, first); - assertNull(state.selectReadyUpdate(bodyId, sameTarget)); - assertSame(changedTarget, state.selectReadyUpdate(bodyId, changedTarget)); + assertNull(state.selectReadyUpdate(anchorBodyRef, sameTarget)); + assertSame(changedTarget, state.selectReadyUpdate(anchorBodyRef, changedTarget)); } @Test void clearingControlMutationStateAllowsIdenticalTargetRetry() { ControlMutationState state = new ControlMutationState(); - UUID bodyId = UUID.randomUUID(); - ControlAnchorUpdate first = update(bodyId, bodyId, 1.0f); - ControlAnchorUpdate retry = update(bodyId, bodyId, 1.0f); + Ref bodyRef = new TestPhysicsRef(1); + Ref anchorBodyRef = new TestPhysicsRef(2); + ControlAnchorUpdate first = update(bodyRef, anchorBodyRef, 1.0f); + ControlAnchorUpdate retry = update(bodyRef, anchorBodyRef, 1.0f); - state.trackSubmittedMutation(bodyId, first); - state.clear(bodyId); + state.trackSubmittedMutation(anchorBodyRef, first); + state.clear(anchorBodyRef); - assertSame(retry, state.selectReadyUpdate(bodyId, retry)); + assertSame(retry, state.selectReadyUpdate(anchorBodyRef, retry)); } @Test void trackingSubmittedControlMutationUpdatesSuppressionTarget() { ControlMutationState state = new ControlMutationState(); - UUID bodyId = UUID.randomUUID(); - UUID anchorBodyId = UUID.randomUUID(); - ControlAnchorUpdate first = update(bodyId, anchorBodyId, 1.0f); - ControlAnchorUpdate second = update(bodyId, anchorBodyId, 2.0f); - ControlAnchorUpdate sameSecondTarget = update(bodyId, anchorBodyId, 2.0f); - ControlAnchorUpdate third = update(bodyId, anchorBodyId, 3.0f); + Ref bodyRef = new TestPhysicsRef(1); + Ref anchorBodyRef = new TestPhysicsRef(2); + ControlAnchorUpdate first = update(bodyRef, anchorBodyRef, 1.0f); + ControlAnchorUpdate second = update(bodyRef, anchorBodyRef, 2.0f); + ControlAnchorUpdate sameSecondTarget = update(bodyRef, anchorBodyRef, 2.0f); + ControlAnchorUpdate third = update(bodyRef, anchorBodyRef, 3.0f); - state.trackSubmittedMutation(anchorBodyId, first); - assertSame(second, state.selectReadyUpdate(anchorBodyId, second)); + state.trackSubmittedMutation(anchorBodyRef, first); + assertSame(second, state.selectReadyUpdate(anchorBodyRef, second)); - state.trackSubmittedMutation(anchorBodyId, second); + state.trackSubmittedMutation(anchorBodyRef, second); - assertNull(state.selectReadyUpdate(anchorBodyId, sameSecondTarget)); - assertSame(third, state.selectReadyUpdate(anchorBodyId, third)); + assertNull(state.selectReadyUpdate(anchorBodyRef, sameSecondTarget)); + assertSame(third, state.selectReadyUpdate(anchorBodyRef, third)); } @Test @@ -101,65 +92,43 @@ void clearingSystemMutationStateAfterReleaseAllowsIdenticalTargetRetry() { EmptyResourceStorage.get()); try { ControlMutationState state = PhysicsKinematicControlSystem.stateFor(store); - UUID bodyId = UUID.randomUUID(); - UUID anchorBodyId = UUID.randomUUID(); - ControlAnchorUpdate first = update(bodyId, anchorBodyId, 1.0f); - ControlAnchorUpdate queued = update(bodyId, anchorBodyId, 1.0f); - ControlAnchorUpdate afterRelease = update(bodyId, anchorBodyId, 1.0f); + Ref bodyRef = new TestPhysicsRef(1); + Ref anchorBodyRef = new TestPhysicsRef(2); + ControlAnchorUpdate first = update(bodyRef, anchorBodyRef, 1.0f); + ControlAnchorUpdate queued = update(bodyRef, anchorBodyRef, 1.0f); + ControlAnchorUpdate afterRelease = update(bodyRef, anchorBodyRef, 1.0f); - state.trackSubmittedMutation(anchorBodyId, first); - assertNull(state.selectReadyUpdate(anchorBodyId, queued)); + state.trackSubmittedMutation(anchorBodyRef, first); + assertNull(state.selectReadyUpdate(anchorBodyRef, queued)); - PhysicsKinematicControlSystem.clearMutationState(store, anchorBodyId); + PhysicsKinematicControlSystem.clearMutationState(store, anchorBodyRef); - assertSame(afterRelease, state.selectReadyUpdate(anchorBodyId, afterRelease)); + assertSame(afterRelease, state.selectReadyUpdate(anchorBodyRef, afterRelease)); } finally { registry.removeStore(store); registry.shutdown(); } } - @Test - void controlJointCleanupResolvesJointFromBodyIds() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:control-joint-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody anchorBody = space.createSphere(0.1f, 1.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey anchorBodyId = resource.addBody(space.id(), - anchorBody, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - space.createPointJoint(anchorBody, body, new Vector3f(), new Vector3f()); - - assertEquals(1, space.jointCount()); - - boolean removed = resource.callOwner("remove control joint", () -> { - if (space.getJoints().isEmpty()) { - return false; - } - space.removeJoint(space.getJoints().getFirst()); - return true; - }); - assertTrue(removed); - - assertEquals(0, space.jointCount()); - } - @Nonnull - private static ControlAnchorUpdate update(@Nonnull UUID bodyId, - @Nonnull UUID anchorBodyId, + private static ControlAnchorUpdate update(@Nonnull Ref bodyRef, + @Nonnull Ref anchorBodyRef, float coordinate) { - return new ControlAnchorUpdate(bodyId, - anchorBodyId, + return new ControlAnchorUpdate(bodyRef, + anchorBodyRef, new Vector3f(coordinate, coordinate, coordinate), new Vector3f(coordinate + 1.0f, coordinate + 1.0f, coordinate + 1.0f)); } + private static final class TestPhysicsRef extends Ref { + + private TestPhysicsRef(int index) { + super(null, index); + } + + @Override + public boolean isValid() { + return true; + } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java index e52fc06a..bb8e1e07 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java @@ -1,49 +1,14 @@ package dev.hytalemodding.impulse.core.internal.modules.worldcollision; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import org.joml.Vector3d; -import org.joml.Vector3f; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -@SuppressWarnings("DataFlowIssue") class WorldCollisionLifecycleTest { - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - @BeforeEach @AfterEach void disableLifecycle() { @@ -51,30 +16,8 @@ void disableLifecycle() { } @Test - void lifecycleStartsDisabledAndRejectsManualBuilds() { - PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = createSpace(resource, PhysicsSpaceSettings.streamingWorldCollision()); - + void lifecycleStartsDisabled() { assertFalse(WorldCollisionLifecycle.isEnabled()); - assertThrows(IllegalStateException.class, - () -> resource.rebuildWorldCollisionAround(null, spaceId, new Vector3d(), 1)); - assertThrows(IllegalStateException.class, - () -> resource.ensureWorldCollisionAround(null, spaceId, List.of(new Vector3d()), 1, 0L)); - assertDoesNotThrow(() -> resource.clearWorldCollision(spaceId)); - assertDoesNotThrow(resource::getWorldCollisionStats); - } - - @Test - void enabledLifecycleStillRequiresSpaceOptIn() { - WorldCollisionLifecycle.enable(); - PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = createSpace(resource, PhysicsSpaceSettings.defaults()); - - assertThrows(IllegalStateException.class, - () -> resource.rebuildWorldCollisionAround(null, spaceId, new Vector3d(), 1)); - assertThrows(IllegalStateException.class, - () -> resource.ensureWorldCollisionAround(null, spaceId, List.of(new Vector3d()), 1, 0L)); - assertDoesNotThrow(() -> resource.clearWorldCollision(spaceId)); } @Test @@ -84,241 +27,7 @@ void lifecycleGenerationChangesWhenLifecycleIsDisabled() { WorldCollisionLifecycle.disable(); + assertFalse(WorldCollisionLifecycle.isEnabled()); assertTrue(WorldCollisionLifecycle.generation() > enabledGeneration); } - - @Test - void disablingLifecycleRestoresChunkBoundaryPausedBodies() { - WorldCollisionLifecycle.enable(); - RuntimeFixture fixture = createRuntimeFixture(PhysicsSpaceSettings.streamingWorldCollision()); - RigidBodyKey bodyKey = spawnDynamicBody(fixture.resource(), fixture.spaceId()); - BodyHandle body = bodyHandle(fixture.resource(), bodyKey); - fixture.runtime().setBodyType(body.backendSpaceId(), - body.backendBodyId(), - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.KINEMATIC)); - fixture.resource().pauseChunkBoundaryBody(bodyKey, - 42L, - PhysicsBodyType.DYNAMIC, - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(4.0f, 5.0f, 6.0f)); - - WorldCollisionLifecycle.disable(); - - assertEquals(BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), - fixture.runtime().bodyTypeCode(body.backendSpaceId(), body.backendBodyId())); - assertNull(fixture.resource().getChunkBoundaryPauseState(bodyKey)); - } - - @Test - void disablingLifecycleRestoresCollisionLodFilters() { - WorldCollisionLifecycle.enable(); - RuntimeFixture fixture = createRuntimeFixture(PhysicsSpaceSettings.streamingWorldCollision()); - RigidBodyKey bodyKey = spawnDynamicBody(fixture.resource(), fixture.spaceId()); - BodyHandle body = bodyHandle(fixture.resource(), bodyKey); - fixture.runtime().setBodyCollisionFilter(body.backendSpaceId(), - body.backendBodyId(), - PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN); - - WorldCollisionLifecycle.disable(); - - assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, - fixture.runtime().bodyCollisionGroup(body.backendSpaceId(), body.backendBodyId())); - assertEquals(PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY, - fixture.runtime().bodyCollisionMask(body.backendSpaceId(), body.backendBodyId())); - } - - @Test - void disablingLifecycleDestroysRetainedTerrainBodiesBeforeClearingCache() throws Exception { - WorldCollisionLifecycle.enable(); - RuntimeFixture fixture = createRuntimeFixture(PhysicsSpaceSettings.streamingWorldCollision()); - PhysicsSpaceBinding space = requireSpaceBinding(fixture); - long backendBodyId = cacheTerrainBody(fixture.resource(), space); - - assertTrue(fixture.runtime().containsBody(space.backendSpaceHandle().value(), backendBodyId)); - assertEquals(1, fixture.resource().getWorldCollisionStats().bodies()); - - WorldCollisionLifecycle.disable(); - - assertFalse(fixture.runtime().containsBody(space.backendSpaceHandle().value(), backendBodyId)); - assertEquals(0, fixture.runtime().bodyCount(space.backendSpaceHandle().value())); - assertEquals(0, fixture.resource().getWorldCollisionStats().bodies()); - assertFalse(fixture.resource().worldCollisionCache().isStreamingApplyPending()); - assertTrue(fixture.resource().worldCollisionCache().tryBeginStreamingApply()); - - WorldCollisionLifecycle.enable(); - assertTrue(WorldCollisionLifecycle.isEnabled()); - } - - private static SpaceId createSpace(PhysicsWorldRuntimeResource resource, - PhysicsSpaceSettings settings) { - BackendId backendId = new BackendId("test:world-collision-lifecycle-" - + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider(backendId, - false, - false)); - return resource.createSpace(backendId, "test-world", settings); - } - - private static RuntimeFixture createRuntimeFixture(PhysicsSpaceSettings settings) { - BackendId backendId = new BackendId("test:world-collision-cleanup-" - + BACKEND_COUNTER.incrementAndGet()); - FakePhysicsBackendRuntimeProvider provider = - new FakePhysicsBackendRuntimeProvider(backendId, false, false); - Impulse.registerRuntimeProvider(provider); - PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = resource.createSpace(backendId, "test-world", settings); - return new RuntimeFixture(resource, spaceId, provider.createdRuntimes().getFirst()); - } - - private static long cacheTerrainBody(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsSpaceBinding space) throws Exception { - long backendBodyId = space.runtime().createBody(space.backendSpaceHandle().value(), - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 0.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - Object spaceCache = newSpaceCollisionCache(); - Object section = newCachedSection(0, 0, 0); - markBackendBody(section, backendBodyId); - putCachedSection(spaceCache, section); - putSpaceCache(resource.worldCollisionCache(), space.spaceId(), spaceCache); - return backendBodyId; - } - - private static Object newSpaceCollisionCache() throws Exception { - Class cacheType = nestedCollisionCacheClass("SpaceCollisionCache"); - Constructor constructor = cacheType.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor.newInstance(); - } - - private static Object newCachedSection(int chunkX, int sectionY, int chunkZ) throws Exception { - Class sectionType = nestedCollisionCacheClass("CachedSection"); - Constructor constructor = sectionType.getDeclaredConstructor(int.class, - int.class, - int.class, - long.class, - long.class); - constructor.setAccessible(true); - return constructor.newInstance(chunkX, sectionY, chunkZ, 0L, 1L); - } - - @SuppressWarnings("unchecked") - private static void markBackendBody(@Nonnull Object section, long backendBodyId) - throws Exception { - Field backendBodyIds = - section.getClass().getDeclaredField("backendBodyIds"); - backendBodyIds.setAccessible(true); - ((List) backendBodyIds.get(section)).add(backendBodyId); - } - - @SuppressWarnings("unchecked") - private static void putCachedSection(@Nonnull Object cache, @Nonnull Object section) - throws Exception { - Method keyMethod = WorldVoxelCollisionCache.class.getDeclaredMethod("packSectionKey", - int.class, - int.class, - int.class); - keyMethod.setAccessible(true); - long key = (long) keyMethod.invoke(null, - intField(section, "chunkX"), - intField(section, "sectionY"), - intField(section, "chunkZ")); - Field sectionsField = cache.getClass().getDeclaredField("sections"); - sectionsField.setAccessible(true); - ((Long2ObjectMap) sectionsField.get(cache)).put(key, section); - } - - @SuppressWarnings("unchecked") - private static void putSpaceCache(@Nonnull WorldVoxelCollisionCache cache, - @Nonnull SpaceId spaceId, - @Nonnull Object spaceCache) throws Exception { - Field spaces = WorldVoxelCollisionCache.class.getDeclaredField("spaces"); - spaces.setAccessible(true); - ((Int2ObjectMap) spaces.get(cache)).put(spaceId.value(), spaceCache); - } - - private static int intField(@Nonnull Object target, @Nonnull String name) throws Exception { - Field field = target.getClass().getDeclaredField(name); - field.setAccessible(true); - return field.getInt(target); - } - - @Nonnull - private static Class nestedCollisionCacheClass(@Nonnull String simpleName) { - return Arrays.stream(WorldVoxelCollisionCache.class.getDeclaredClasses()) - .filter(candidate -> candidate.getSimpleName().equals(simpleName)) - .findFirst() - .orElseThrow(); - } - - private static RigidBodyKey spawnDynamicBody(PhysicsWorldRuntimeResource resource, - SpaceId spaceId) { - RigidBodyKey bodyKey = RigidBodyKey.random(); - PhysicsSpaceBinding space = resource.getSpaceBinding(spaceId); - assertNotNull(space); - long backendBodyId = space.runtime().createBody(space.backendSpaceHandle().value(), - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 1.0f, - 0.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - resource.addBodyOnOwner(bodyKey, - spaceId, - new BackendBodyHandle(backendBodyId), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - return bodyKey; - } - - @Nonnull - private static PhysicsSpaceBinding requireSpaceBinding(@Nonnull RuntimeFixture fixture) { - PhysicsSpaceBinding space = fixture.resource().getSpaceBinding(fixture.spaceId()); - assertNotNull(space); - return space; - } - - private static BodyHandle bodyHandle(PhysicsWorldRuntimeResource resource, - RigidBodyKey bodyKey) { - PhysicsBodyRegistration registration = resource.getRegistration(bodyKey); - assertNotNull(registration); - PhysicsSpaceBinding space = resource.getSpaceBinding(registration.spaceId()); - assertNotNull(space); - return new BodyHandle(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value()); - } - - private record RuntimeFixture(PhysicsWorldRuntimeResource resource, - SpaceId spaceId, - FakePhysicsBackendRuntime runtime) { - } - - private record BodyHandle(int backendSpaceId, long backendBodyId) { - } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCacheTest.java index 81ca106b..c4941fab 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCacheTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCacheTest.java @@ -24,7 +24,6 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -96,7 +95,7 @@ void bodyTargetCacheRefreshesActiveBodiesEveryFourTicks() { WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1001); - RigidBodyKey bodyId = bodyId(1); + UUID bodyId = bodyId(1); WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, snapshot) @@ -124,7 +123,7 @@ void bodyTargetCacheRefreshesSleepingBodiesOnTtlBoundedInterval() { WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1002); - RigidBodyKey bodyId = bodyId(2); + UUID bodyId = bodyId(2); WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, true, 1L, 100, snapshot) @@ -148,7 +147,7 @@ void bodyTargetCacheRefreshesImmediatelyWhenBoundsChange() { WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1003); - RigidBodyKey bodyId = bodyId(3); + UUID bodyId = bodyId(3); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, @@ -184,7 +183,7 @@ void bodyTargetCacheRefreshesImmediatelyWhenBoundsChange() { void bodyTargetRefreshIsNotConsumedUntilTerrainApplyRecordsIt() { WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); SpaceId spaceId = new SpaceId(1005); - RigidBodyKey bodyId = bodyId(5); + UUID bodyId = bodyId(5); WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, null) @@ -204,7 +203,7 @@ void bodyTargetCachePrunesBodiesThatDisappearPastDoubleTtl() { WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1004); - RigidBodyKey bodyId = bodyId(4); + UUID bodyId = bodyId(4); WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, snapshot) @@ -421,8 +420,8 @@ void clearSectionsAroundKeepsDistantCachedTerrain() throws Exception { assertEquals(1, fixture.runtime().bodyCount(fixture.backendSpaceId())); } - private static RigidBodyKey bodyId(long leastSignificantBits) { - return RigidBodyKey.of(new UUID(0L, leastSignificantBits)); + private static UUID bodyId(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); } private static WorldCollisionStreamingBounds boundsAt(float x, float y, float z) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystemTest.java deleted file mode 100644 index 3a089dd9..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsChunkBoundarySystemTest.java +++ /dev/null @@ -1,432 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.math.util.ChunkUtil; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundarySafeState; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsChunkBoundarySystem; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsChunkBoundarySystemTest { - - @Test - void recordSafePoseUsesSnapshotPoseWithoutReadingBody() { - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - RigidBodyKey bodyId = RigidBodyKey.random(); - CountingBody body = new CountingBody(); - Quaternionf rotation = new Quaternionf().rotateY(0.5f); - PhysicsBodySnapshot snapshot = snapshot( - new Vector3f(3.0f, 4.0f, 5.0f), - rotation, - new Vector3f(), - new Vector3f(), - PhysicsBodyType.DYNAMIC); - - PhysicsChunkBoundarySystem.recordSafePose(bodyId, snapshot, resource); - - ChunkBoundarySafeState safeState = - resource.getChunkBoundarySafeState(bodyId); - assertNotNull(safeState); - assertEquals(3.0f, safeState.getPosition().x); - assertEquals(4.0f, safeState.getPosition().y); - assertEquals(5.0f, safeState.getPosition().z); - assertEquals(rotation.x, safeState.getRotation().x); - assertEquals(rotation.y, safeState.getRotation().y); - assertEquals(rotation.z, safeState.getRotation().z); - assertEquals(rotation.w, safeState.getRotation().w); - assertEquals(0, body.liveGetterCalls()); - } - - @Test - void chunkFootprintUsesBoxExtentsWhenCenterRemainsInLoadedChunk() { - PhysicsBodySnapshot snapshot = snapshot( - new Vector3f(31.75f, 0.0f, 8.0f), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - PhysicsBodyType.DYNAMIC, - new Vector3f(0.5f, 0.5f, 0.5f)); - - assertArrayEquals(new long[] { - ChunkUtil.indexChunk(0, 0), - ChunkUtil.indexChunk(1, 0) - }, - PhysicsChunkBoundarySystem.chunkIndices(snapshot)); - } - - @Test - void chunkFootprintUsesRotatedBoxExtents() { - PhysicsBodySnapshot snapshot = snapshot( - new Vector3f(30.25f, 0.0f, 8.0f), - new Quaternionf().rotateY((float) (Math.PI / 2.0)), - new Vector3f(), - new Vector3f(), - PhysicsBodyType.DYNAMIC, - new Vector3f(0.25f, 0.5f, 2.0f)); - - assertArrayEquals(new long[] { - ChunkUtil.indexChunk(0, 0), - ChunkUtil.indexChunk(1, 0) - }, - PhysicsChunkBoundarySystem.chunkIndices(snapshot)); - } - - @Test - void pauseBodyUsesSnapshotVelocityAndTypeWithoutReadingBody() { - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - RigidBodyKey bodyId = RigidBodyKey.random(); - CountingBody body = new CountingBody(); - Quaternionf safeRotation = new Quaternionf().rotateX(0.25f); - resource.updateChunkBoundarySafeState(bodyId, - new Vector3f(8.0f, 9.0f, 10.0f), - safeRotation); - Vector3f linearVelocity = new Vector3f(1.0f, 2.0f, 3.0f); - Vector3f angularVelocity = new Vector3f(4.0f, 5.0f, 6.0f); - PhysicsBodySnapshot snapshot = snapshot( - new Vector3f(24.0f, 0.0f, 24.0f), - new Quaternionf(), - linearVelocity, - angularVelocity, - PhysicsBodyType.DYNAMIC); - - resource.pauseChunkBoundaryBody(bodyId, 42L, snapshot); - - PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState pauseState = - resource.getChunkBoundaryPauseState(bodyId); - assertNotNull(pauseState); - assertEquals(42L, pauseState.getTargetChunkIndex()); - assertEquals(PhysicsBodyType.DYNAMIC, pauseState.getOriginalBodyType()); - assertEquals(linearVelocity, pauseState.getLinearVelocity()); - assertEquals(angularVelocity, pauseState.getAngularVelocity()); - assertEquals(0, body.liveGetterCalls()); - } - - @Nonnull - private static PhysicsBodySnapshot snapshot(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - @Nonnull PhysicsBodyType bodyType) { - return snapshot(position, - rotation, - linearVelocity, - angularVelocity, - bodyType, - new Vector3f(0.5f)); - } - - @Nonnull - private static PhysicsBodySnapshot snapshot(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - @Nonnull PhysicsBodyType bodyType, - @Nonnull Vector3f boxHalfExtents) { - return new PhysicsBodySnapshot(position, - rotation, - linearVelocity, - angularVelocity, - bodyType, - false, - false, - 0.0f, - ShapeType.BOX, - boxHalfExtents, - -1.0f, - -1.0f, - PhysicsAxis.Y); - } - - private static final class CountingBody implements PhysicsBody { - - private final Vector3f position = new Vector3f(); - private final Quaternionf rotation = new Quaternionf(); - private final Vector3f linearVelocity = new Vector3f(); - private final Vector3f angularVelocity = new Vector3f(); - private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; - private boolean forcesCleared; - private int liveGetterCalls; - - private int liveGetterCalls() { - return liveGetterCalls; - } - - @Override - public void setPosition(float x, float y, float z) { - position.set(x, y, z); - } - - @Override - public void setPosition(@Nonnull Vector3f pos) { - position.set(pos); - } - - @Nonnull - @Override - public Vector3f getPosition() { - liveGetterCalls++; - return new Vector3f(position); - } - - @Override - public void setRotation(float x, float y, float z, float w) { - rotation.set(x, y, z, w); - } - - @Override - public void setRotation(@Nonnull Quaternionf rot) { - rotation.set(rot); - } - - @Nonnull - @Override - public Quaternionf getRotation() { - liveGetterCalls++; - return new Quaternionf(rotation); - } - - @Override - public void setRestitution(float restitution) { - } - - @Override - public float getRestitution() { - return 0.0f; - } - - @Override - public void setFriction(float friction) { - } - - @Override - public float getFriction() { - return 0.0f; - } - - @Nonnull - @Override - public PhysicsBodyType getBodyType() { - liveGetterCalls++; - return bodyType; - } - - @Override - public void setBodyType(@Nonnull PhysicsBodyType bodyType) { - this.bodyType = bodyType; - } - - @Override - public boolean isStatic() { - liveGetterCalls++; - return bodyType == PhysicsBodyType.STATIC; - } - - @Override - public boolean isKinematic() { - liveGetterCalls++; - return bodyType == PhysicsBodyType.KINEMATIC; - } - - @Override - public void setKinematic(boolean kinematic) { - bodyType = kinematic ? PhysicsBodyType.KINEMATIC : PhysicsBodyType.DYNAMIC; - } - - @Override - public void activate() { - } - - @Override - public boolean isActive() { - return true; - } - - @Override - public boolean isSleeping() { - liveGetterCalls++; - return false; - } - - @Override - public void sleep() { - } - - @Override - public float getMass() { - return 1.0f; - } - - @Override - public void setMass(float mass) { - } - - @Nonnull - @Override - public Vector3f getLinearVelocity() { - liveGetterCalls++; - return new Vector3f(linearVelocity); - } - - @Override - public void setLinearVelocity(@Nonnull Vector3f vel) { - linearVelocity.set(vel); - } - - @Override - public void setLinearVelocity(float x, float y, float z) { - linearVelocity.set(x, y, z); - } - - @Nonnull - @Override - public Vector3f getAngularVelocity() { - liveGetterCalls++; - return new Vector3f(angularVelocity); - } - - @Override - public void setAngularVelocity(@Nonnull Vector3f vel) { - angularVelocity.set(vel); - } - - @Override - public void setAngularVelocity(float x, float y, float z) { - angularVelocity.set(x, y, z); - } - - @Override - public float getLinearDamping() { - return 0.0f; - } - - @Override - public void setLinearDamping(float damping) { - } - - @Override - public float getAngularDamping() { - return 0.0f; - } - - @Override - public void setAngularDamping(float damping) { - } - - @Override - public void applyCentralForce(@Nonnull Vector3f force) { - } - - @Override - public void applyCentralForce(float x, float y, float z) { - } - - @Override - public void applyForce(@Nonnull Vector3f force, @Nonnull Vector3f offset) { - } - - @Override - public void applyCentralImpulse(@Nonnull Vector3f impulse) { - } - - @Override - public void applyCentralImpulse(float x, float y, float z) { - } - - @Override - public void applyImpulse(@Nonnull Vector3f impulse, @Nonnull Vector3f offset) { - } - - @Override - public void applyTorque(@Nonnull Vector3f torque) { - } - - @Override - public void applyTorqueImpulse(@Nonnull Vector3f torqueImpulse) { - } - - @Override - public void clearForces() { - forcesCleared = true; - } - - @Override - public boolean isSensor() { - return false; - } - - @Override - public void setSensor(boolean sensor) { - } - - @Override - public int getCollisionGroup() { - return 0; - } - - @Override - public int getCollisionMask() { - return 0; - } - - @Override - public void setCollisionFilter(int group, int mask) { - } - - @Override - public boolean isContinuousCollisionEnabled() { - return false; - } - - @Override - public void setContinuousCollisionEnabled(boolean enabled) { - } - - @Nonnull - @Override - public ShapeType getShapeType() { - return ShapeType.BOX; - } - - @Nullable - @Override - public Vector3f getBoxHalfExtents() { - return null; - } - - @Override - public float getSphereRadius() { - return 0.0f; - } - - @Override - public float getHalfHeight() { - return 0.0f; - } - - @Nonnull - @Override - public PhysicsAxis getShapeAxis() { - return PhysicsAxis.Y; - } - - @Override - public float getCenterOfMassOffsetY() { - return 0.0f; - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java deleted file mode 100644 index 90920b23..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsCollisionLodSystemTest.java +++ /dev/null @@ -1,200 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsCollisionLodSystem; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsCollisionLodSystem.CollisionLodState; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsCollisionLodSystem.CollisionLodTier; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsCollisionLodSystem.CollisionLodUpdate; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.util.ArrayList; -import java.util.List; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsCollisionLodSystemTest { - - @Test - void resolvesDistanceTiersAroundInterest() { - PhysicsSpaceSettings settings = testSettings(); - List interests = interestsAtOrigin(); - - assertEquals(CollisionLodTier.NEAR_FULL, - PhysicsCollisionLodSystem.resolveTier(settings, - null, - new Vector3f(9.0f, 0.0f, 0.0f), - interests)); - assertEquals(CollisionLodTier.MID_TERRAIN, - PhysicsCollisionLodSystem.resolveTier(settings, - null, - new Vector3f(20.0f, 0.0f, 0.0f), - interests)); - assertEquals(CollisionLodTier.FAR_SLEEPING, - PhysicsCollisionLodSystem.resolveTier(settings, - null, - new Vector3f(31.0f, 0.0f, 0.0f), - interests)); - } - - @Test - void keepsPreviousTierInsideHysteresis() { - PhysicsSpaceSettings settings = testSettings(); - List interests = interestsAtOrigin(); - - assertEquals(CollisionLodTier.NEAR_FULL, - PhysicsCollisionLodSystem.resolveTier(settings, - CollisionLodTier.NEAR_FULL, - new Vector3f(14.0f, 0.0f, 0.0f), - interests)); - assertEquals(CollisionLodTier.MID_TERRAIN, - PhysicsCollisionLodSystem.resolveTier(settings, - CollisionLodTier.NEAR_FULL, - new Vector3f(16.0f, 0.0f, 0.0f), - interests)); - assertEquals(CollisionLodTier.MID_TERRAIN, - PhysicsCollisionLodSystem.resolveTier(settings, - CollisionLodTier.MID_TERRAIN, - new Vector3f(34.0f, 0.0f, 0.0f), - interests)); - assertEquals(CollisionLodTier.FAR_SLEEPING, - PhysicsCollisionLodSystem.resolveTier(settings, - CollisionLodTier.MID_TERRAIN, - new Vector3f(36.0f, 0.0f, 0.0f), - interests)); - } - - @Test - void noInterestsResolveToFarTier() { - PhysicsSpaceSettings settings = testSettings(); - - assertEquals(CollisionLodTier.FAR_SLEEPING, - PhysicsCollisionLodSystem.resolveTier(settings, - CollisionLodTier.NEAR_FULL, - new Vector3f(), - List.of())); - } - - @Test - void persistentBodiesAreNotCollisionLodCandidates() { - assertFalse(PhysicsCollisionLodSystem.isCollisionLodCandidate(snapshot( - PhysicsBodyType.DYNAMIC, - false), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT)); - } - - @Test - void onlyRuntimeDynamicBodiesAreCollisionLodCandidates() { - assertTrue(PhysicsCollisionLodSystem.isCollisionLodCandidate(snapshot( - PhysicsBodyType.DYNAMIC, - false), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - assertFalse(PhysicsCollisionLodSystem.isCollisionLodCandidate(snapshot( - PhysicsBodyType.STATIC, - false), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - assertFalse(PhysicsCollisionLodSystem.isCollisionLodCandidate(snapshot( - PhysicsBodyType.DYNAMIC, - true), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - } - - @Test - void tierStateCommitsAfterSuccessfulOwnerMutation() { - CollisionLodState state = new CollisionLodState(); - SpaceId spaceId = new SpaceId(1); - RigidBodyKey bodyId = RigidBodyKey.random(); - List updates = new ArrayList<>(); - - state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, false, updates); - - assertNull(state.tier(bodyId)); - state.trackPendingMutation(PhysicsMutationHandle.completed("test", null), updates); - state.refreshPendingMutation(); - - assertEquals(CollisionLodTier.MID_TERRAIN, state.tier(bodyId)); - assertFalse(state.hasPendingMutation()); - } - - @Test - void tierStateRetriesAfterFailedOwnerMutation() { - CollisionLodState state = new CollisionLodState(); - SpaceId spaceId = new SpaceId(1); - RigidBodyKey bodyId = RigidBodyKey.random(); - List updates = new ArrayList<>(); - - assertTrue(state.shouldRefresh(spaceId, 20, 1L)); - state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, false, updates); - state.trackPendingMutation(PhysicsMutationHandle.failed("test", - null, - new IllegalStateException("boom")), - updates); - state.refreshPendingMutation(); - - assertNull(state.tier(bodyId)); - assertTrue(state.shouldRefresh(spaceId, 20, 2L)); - } - - @Test - void restoreClearsTierStateAfterSuccessfulOwnerMutation() { - CollisionLodState state = new CollisionLodState(); - SpaceId spaceId = new SpaceId(1); - RigidBodyKey bodyId = RigidBodyKey.random(); - List updates = new ArrayList<>(); - - state.recordTier(spaceId, bodyId, CollisionLodTier.MID_TERRAIN, false, updates); - state.trackPendingMutation(PhysicsMutationHandle.completed("test", null), updates); - state.refreshPendingMutation(); - - updates = new ArrayList<>(); - state.recordRestore(spaceId, bodyId, updates); - state.trackPendingMutation(PhysicsMutationHandle.completed("test", null), updates); - state.refreshPendingMutation(); - - assertNull(state.tier(bodyId)); - } - - private static PhysicsSpaceSettings testSettings() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getCollisionLodSettings().setCollisionLodRadii(10, 30); - settings.getCollisionLodSettings().setCollisionLodHysteresis(5); - return settings; - } - - private static List interestsAtOrigin() { - return List.of(new PhysicsVisualRuntime.VisualInterest(new Vector3f(), null)); - } - - private static PhysicsBodySnapshot snapshot(PhysicsBodyType bodyType, boolean sensor) { - return new PhysicsBodySnapshot(new Vector3f(), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - bodyType, - false, - sensor, - 0.0f, - ShapeType.BOX, - new Vector3f(0.5f), - -1.0f, - -1.0f, - PhysicsAxis.Y); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystemTest.java deleted file mode 100644 index dbef59f8..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsWorldCollisionStreamingSystemTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.jupiter.api.Test; - -class PhysicsWorldCollisionStreamingSystemTest { - - @Test - void rejectedStreamingApplyClearsPendingGateAndRecordsSkip() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); - WorldCollisionProfilingResource.Snapshot snapshot = - new WorldCollisionProfilingResource.Snapshot(); - - assertTrue(cache.tryBeginStreamingApply()); - - PhysicsWorldCollisionStreamingSystem.finishRejectedStreamingApply(cache, snapshot); - - assertFalse(cache.isStreamingApplyPending()); - assertEquals(1, snapshot.getTerrainApplySkippedPending()); - assertEquals(0, snapshot.getTerrainApplyQueued()); - } - - @Test - void rejectedQueuedStreamingApplyClearsPendingGateAndKeepsSnapshotOpen() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); - WorldCollisionProfilingResource.Snapshot snapshot = - new WorldCollisionProfilingResource.Snapshot(); - - assertTrue(cache.tryBeginStreamingApply()); - - boolean queued = PhysicsWorldCollisionStreamingSystem.tryQueueStreamingApply(cache, - new WorldCollisionProfilingResource(), - snapshot, - System.nanoTime(), - _ -> { - throw new RejectedExecutionException("forced enqueue rejection"); - }); - - assertFalse(queued); - assertFalse(cache.isStreamingApplyPending()); - assertEquals(0, snapshot.getTerrainApplyQueued()); - assertEquals(1, snapshot.getTerrainApplySkippedPending()); - } - - @Test - void failedHandleStreamingApplyClearsPendingGateAndRecordsSkip() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); - WorldCollisionProfilingResource.Snapshot snapshot = - new WorldCollisionProfilingResource.Snapshot(); - - assertTrue(cache.tryBeginStreamingApply()); - - boolean queued = PhysicsWorldCollisionStreamingSystem.tryQueueStreamingApply(cache, - new WorldCollisionProfilingResource(), - snapshot, - System.nanoTime(), - _ -> PhysicsMutationHandle.failed("stream world collision terrain apply", - null, - new RejectedExecutionException("forced handle rejection"))); - - assertFalse(queued); - assertFalse(cache.isStreamingApplyPending()); - assertEquals(0, snapshot.getTerrainApplyQueued()); - assertEquals(1, snapshot.getTerrainApplySkippedPending()); - } - - @Test - void queuedStreamingApplyFinishesGateAndProfilingOnce() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); - WorldCollisionProfilingResource profiling = new WorldCollisionProfilingResource(); - WorldCollisionProfilingResource.Snapshot snapshot = profiling.beginTick(); - AtomicBoolean finished = new AtomicBoolean(); - - assertTrue(cache.tryBeginStreamingApply()); - - PhysicsWorldCollisionStreamingSystem.finishQueuedStreamingApply(cache, - profiling, - snapshot, - System.nanoTime(), - finished); - PhysicsWorldCollisionStreamingSystem.finishQueuedStreamingApply(cache, - profiling, - snapshot, - System.nanoTime(), - finished); - - assertFalse(cache.isStreamingApplyPending()); - assertEquals(1, profiling.getCumulativeSnapshot().getTickSamples()); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsCodecValidationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsCodecValidationTest.java deleted file mode 100644 index 724d96fe..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsCodecValidationTest.java +++ /dev/null @@ -1,571 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.codec.util.RawJsonReader; -import com.hypixel.hytale.server.core.util.BsonUtil; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import org.bson.BsonBinary; -import org.bson.BsonDocument; -import org.bson.BsonDouble; -import org.bson.BsonInt32; -import org.bson.BsonNull; -import org.bson.BsonString; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - -class PersistentPhysicsCodecValidationTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void runtimeRestoreGenerationChangesAcrossRestoreTransitions() { - PersistentPhysicsWorldResource resource = new PersistentPhysicsWorldResource(); - - long initialGeneration = resource.runtimeRestoreGeneration(); - resource.markRuntimeRestorePending(); - long pendingGeneration = resource.runtimeRestoreGeneration(); - resource.clearRuntimeRestorePending(); - - assertTrue(pendingGeneration > initialGeneration); - assertTrue(resource.runtimeRestoreGeneration() > pendingGeneration); - } - - @Test - void spaceStateCodecValidatorChecksCrossFieldSettingsAfterDecode() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualSyncRadii(64, 128); - SpaceFixture fixture = spaceFixture("test:space-codec-validation-", settings); - BsonDocument encoded = encodeSpace(PersistentPhysicsSpaceState.from(fixture.binding(), settings)); - encoded.put("VisualFullSyncRadius", new BsonInt32(128)); - encoded.put("VisualMaxSyncRadius", new BsonInt32(64)); - - assertValidationFails( - () -> PersistentPhysicsSpaceState.CODEC.decode(encoded, new ExtraInfo()), - "Visual full sync radius cannot exceed visual max sync radius"); - } - - @Test - void worldResourceCodecValidatorChecksStepBudgetAfterDecode() { - BsonDocument encoded = encodeWorld(new PersistentPhysicsWorldResource()); - encoded.put("SimulationSteps", new BsonInt32(0)); - - assertValidationFails( - () -> PersistentPhysicsWorldResource.CODEC.decode(encoded, new ExtraInfo()), - "Must be greater than or equal to 1"); - } - - @Test - void spaceStateFieldValidatorsRejectNullOptionalEnums() { - BsonDocument encoded = encodedSpaceState(); - encoded.put("WorldCollisionMode", BsonNull.VALUE); - - assertValidationFails( - () -> PersistentPhysicsSpaceState.CODEC.decode(encoded, new ExtraInfo()), - "Can't be null"); - } - - @Test - void spaceStateFieldValidatorsRejectBlankDetachedVisualBlockType() { - BsonDocument encoded = encodedSpaceState(); - encoded.put("DetachedVisualBlockType", new BsonString(" ")); - - assertValidationFails( - () -> PersistentPhysicsSpaceState.CODEC.decode(encoded, new ExtraInfo()), - "Persisted detached visual block type cannot be blank"); - } - - @Test - void worldResourceFieldValidatorsRejectUnknownStepMode() { - BsonDocument encoded = encodeWorld(new PersistentPhysicsWorldResource()); - encoded.put("StepMode", new BsonString("bogus")); - - assertValidationFails( - () -> PersistentPhysicsWorldResource.CODEC.decode(encoded, new ExtraInfo()), - "Persistent physics step mode is unknown: bogus"); - } - - @Test - void worldResourceFieldValidatorsRejectUnknownStepSchedulingMode() { - BsonDocument encoded = encodeWorld(new PersistentPhysicsWorldResource()); - encoded.put("StepSchedulingMode", new BsonString("bogus")); - - assertValidationFails( - () -> PersistentPhysicsWorldResource.CODEC.decode(encoded, new ExtraInfo()), - "Persistent physics step scheduling mode is unknown: bogus"); - } - - @Test - void worldResourceFieldValidatorsRejectUnknownEventCollectionMode() { - BsonDocument encoded = encodeWorld(new PersistentPhysicsWorldResource()); - encoded.put("EventCollectionMode", new BsonString("bogus")); - - assertValidationFails( - () -> PersistentPhysicsWorldResource.CODEC.decode(encoded, new ExtraInfo()), - "Persistent physics event collection mode is unknown: bogus"); - } - - @Test - void worldResourceCodecPreservesStepSchedulingMode() { - PersistentPhysicsWorldResource resource = new PersistentPhysicsWorldResource(); - PhysicsWorldSettings settings = resource.getWorldSettings(); - settings.setStepSchedulingMode(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT); - resource.setWorldSettings(settings); - - BsonDocument encoded = encodeWorld(resource); - PersistentPhysicsWorldResource decoded = PersistentPhysicsWorldResource.CODEC - .decode(encoded, new ExtraInfo()); - - assertEquals("accumulate_pending_dt", - encoded.getString("StepSchedulingMode").getValue()); - Assertions.assertNotNull(decoded); - assertEquals(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, - decoded.getWorldSettings().getStepSchedulingMode()); - } - - @Test - void worldResourceCodecPreservesEventCollectionMode() { - PersistentPhysicsWorldResource resource = new PersistentPhysicsWorldResource(); - PhysicsWorldSettings settings = resource.getWorldSettings(); - settings.setEventCollectionMode(PhysicsEventCollectionMode.CONTACTS); - resource.setWorldSettings(settings); - - BsonDocument encoded = encodeWorld(resource); - PersistentPhysicsWorldResource decoded = PersistentPhysicsWorldResource.CODEC - .decode(encoded, new ExtraInfo()); - - assertEquals("contacts", encoded.getString("EventCollectionMode").getValue()); - Assertions.assertNotNull(decoded); - assertEquals(PhysicsEventCollectionMode.CONTACTS, - decoded.getWorldSettings().getEventCollectionMode()); - } - - @Test - void worldResourceCodecWritesStateBlocksWithoutFlatBodiesAndJoints() { - PersistentPhysicsWorldResource resource = new PersistentPhysicsWorldResource(); - PersistentPhysicsBodyState body = persistentBodyState(); - PersistentPhysicsJointState joint = persistentJointState(body.getSpaceId()); - resource.setBodies(new PersistentPhysicsBodyState[] { body }); - resource.setJoints(new PersistentPhysicsJointState[] { joint }); - - BsonDocument encoded = encodeWorld(resource); - - assertEquals(PersistentPhysicsWorldResource.CURRENT_SCHEMA_VERSION, - encoded.getInt32("SchemaVersion").getValue()); - assertFalse(encoded.containsKey("Bodies")); - assertFalse(encoded.containsKey("Joints")); - assertFalse(encoded.getArray("BodyBlocks").isEmpty()); - assertFalse(encoded.getArray("JointBlocks").isEmpty()); - - PersistentPhysicsWorldResource decoded = PersistentPhysicsWorldResource.CODEC - .decode(encoded, new ExtraInfo()); - - Assertions.assertNotNull(decoded); - assertEquals(1, decoded.getBodyCount()); - assertEquals(1, decoded.getJointCount()); - assertEquals(body.getBodyIdValue(), decoded.getBodies()[0].getBodyIdValue()); - assertEquals(joint.key(), decoded.getJoints()[0].key()); - } - - @Test - void worldResourceCodecRejectsCorruptStateBlockPayload() { - PersistentPhysicsWorldResource resource = new PersistentPhysicsWorldResource(); - resource.setBodies(new PersistentPhysicsBodyState[] { persistentBodyState() }); - BsonDocument encoded = encodeWorld(resource); - BsonDocument block = encoded.getArray("BodyBlocks").getFirst().asDocument(); - byte[] payload = block.getBinary("Payload").getData(); - payload[payload.length / 2] ^= 0x01; - block.put("Payload", new BsonBinary(payload)); - - assertThrows(RuntimeException.class, - () -> PersistentPhysicsWorldResource.CODEC.decode(encoded, new ExtraInfo())); - } - - @Test - void worldResourceCodecRejectsOversizedStateBlockBeforeInflating() { - PersistentPhysicsWorldResource resource = new PersistentPhysicsWorldResource(); - resource.setBodies(new PersistentPhysicsBodyState[] { persistentBodyState() }); - BsonDocument encoded = encodeWorld(resource); - BsonDocument block = encoded.getArray("BodyBlocks").getFirst().asDocument(); - block.put("UncompressedBytes", new BsonInt32(Integer.MAX_VALUE)); - - assertValidationFails( - () -> PersistentPhysicsStateBlock.CODEC.decode(block, new ExtraInfo()), - "Persistent physics state block uncompressed size exceeds"); - } - - @Test - void bodyStateCodecRejectsInvalidSpaceIdWithoutRuntimeFallback() { - BsonDocument encoded = encodeBody(persistentBodyState()); - encoded.put("SpaceId", new BsonInt32(0)); - - assertValidationFails( - () -> PersistentPhysicsBodyState.CODEC.decode(encoded, new ExtraInfo()), - "Must be greater than or equal to 1"); - } - - @Test - void bodyStateCaptureRejectsMissingSpaceId() { - PersistentPhysicsBodyState state = new PersistentPhysicsBodyState(); - - assertThrows(NullPointerException.class, () -> state.updateFromSnapshot(boxSnapshot(0.0f), null)); - } - - @Test - void bodyStateCaptureRejectsInvalidSpaceId() { - PersistentPhysicsBodyState state = new PersistentPhysicsBodyState(); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> state.updateFromSnapshot(boxSnapshot(0.0f), new SpaceId(0))); - - assertEquals("Persistent body state requires a positive explicit space id", - exception.getMessage()); - } - - @Test - void bodyStateCodecRejectsInvalidMassInsteadOfDefaulting() { - BsonDocument encoded = encodeBody(persistentBodyState()); - encoded.put("Mass", new BsonDouble(Double.NaN)); - - assertValidationFails( - () -> PersistentPhysicsBodyState.CODEC.decode(encoded, new ExtraInfo()), - "Persisted body mass must be finite and >= 0"); - } - - @Test - void bodyStateCodecRejectsZeroQuaternionInsteadOfRestoringInvalidRotation() { - BsonDocument encoded = encodeBody(persistentBodyState()); - BsonDocument rotation = encoded.getDocument("Rotation"); - rotation.put("X", new BsonDouble(0.0)); - rotation.put("Y", new BsonDouble(0.0)); - rotation.put("Z", new BsonDouble(0.0)); - rotation.put("W", new BsonDouble(0.0)); - - assertValidationFails( - () -> PersistentPhysicsBodyState.CODEC.decode(encoded, new ExtraInfo()), - "Persisted quaternion must be finite and non-zero"); - } - - @Test - void bodyStateCodecNormalizesNonUnitQuaternion() { - BsonDocument encoded = encodeBody(persistentBodyState()); - BsonDocument rotation = encoded.getDocument("Rotation"); - rotation.put("X", new BsonDouble(0.0)); - rotation.put("Y", new BsonDouble(0.0)); - rotation.put("Z", new BsonDouble(0.0)); - rotation.put("W", new BsonDouble(2.0)); - - PersistentPhysicsBodyState decoded = - PersistentPhysicsBodyState.CODEC.decode(encoded, new ExtraInfo()); - - Assertions.assertNotNull(decoded); - assertEquals(1.0f, decoded.getRotation().lengthSquared(), 0.0001f); - assertEquals(1.0f, decoded.getRotation().w, 0.0001f); - } - - @Test - void bodyStateRestoreNormalizesQuaternionBeforeBackendHandoff() { - BsonDocument encoded = encodeBody(persistentBodyState()); - BsonDocument rotation = encoded.getDocument("Rotation"); - rotation.put("X", new BsonDouble(0.0)); - rotation.put("Y", new BsonDouble(0.0)); - rotation.put("Z", new BsonDouble(0.0)); - rotation.put("W", new BsonDouble(2.0)); - PersistentPhysicsBodyState decoded = - PersistentPhysicsBodyState.CODEC.decode(encoded, new ExtraInfo()); - SpaceFixture restore = spaceFixture("test:quaternion-restore-", PhysicsSpaceSettings.defaults()); - - Assertions.assertNotNull(decoded); - BackendBodyHandle restoredBodyHandle = decoded.createBackendBody(restore.binding()); - decoded.applyToBody(restore.binding(), restoredBodyHandle); - PhysicsBodySnapshot restored = PhysicsBodySnapshots.read(restore.binding(), restoredBodyHandle.value()); - - Assertions.assertNotNull(restored); - assertEquals(1.0f, restored.rotation().lengthSquared(), 0.0001f); - assertEquals(1.0f, restored.rotationW(), 0.0001f); - } - - @Test - void bodyStateCodecRejectsUnsupportedPersistentShape() { - BsonDocument encoded = encodeBody(persistentBodyState()); - encoded.put("ShapeType", new BsonString("Voxels")); - - assertValidationFails( - () -> PersistentPhysicsBodyState.CODEC.decode(encoded, new ExtraInfo()), - "Persisted body shape is unsupported: VOXELS"); - } - - @Test - void bodyStateCodecDoesNotWritePlaneGroundY() { - BsonDocument encoded = encodeBody(persistentPlaneBodyState(12.0f)); - - assertFalse(encoded.containsKey("PlaneGroundY")); - } - - @Test - void worldResourceCodecRestoresPlaneHeightFromPositionY() { - PersistentPhysicsBodyState state = persistentPlaneBodyState(12.0f); - BsonDocument encodedBody = encodeBody(state); - assertEquals(12.0, encodedBody.getDocument("Position").getDouble("Y").doubleValue(), 0.0001); - PersistentPhysicsWorldResource resource = new PersistentPhysicsWorldResource(); - resource.setBodies(new PersistentPhysicsBodyState[] { state }); - - PersistentPhysicsWorldResource decoded = PersistentPhysicsWorldResource.CODEC - .decode(encodeWorld(resource), new ExtraInfo()); - SpaceFixture restore = spaceFixture("test:plane-restore-", PhysicsSpaceSettings.defaults()); - - Assertions.assertNotNull(decoded); - assertEquals(1, decoded.getBodyCount()); - PersistentPhysicsBodyState decodedBody = decoded.getBodies()[0]; - assertEquals(12.0f, decodedBody.getPosition().y, 0.0001f); - BackendBodyHandle restoredBodyHandle = decodedBody.createBackendBody(restore.binding()); - decodedBody.applyToBody(restore.binding(), restoredBodyHandle); - PhysicsBodySnapshot restored = PhysicsBodySnapshots.read(restore.binding(), restoredBodyHandle.value()); - - Assertions.assertNotNull(restored); - assertEquals(ShapeType.PLANE, restored.shapeType()); - assertEquals(12.0f, restored.positionY(), 0.0001f); - } - - @Test - void jointStateCodecRejectsInvalidSpaceIdInsteadOfDefaulting() { - BsonDocument encoded = encodeJoint(persistentJointState(1)); - encoded.put("SpaceId", new BsonInt32(0)); - - assertValidationFails( - () -> PersistentPhysicsJointState.CODEC.decode(encoded, new ExtraInfo()), - "Must be greater than or equal to 1"); - } - - @Test - void jointStateCodecRejectsMissingAxisForAxisJoint() { - BsonDocument encoded = encodeJoint(persistentJointState(1)); - encoded.put("Type", new BsonString("Hinge")); - encoded.remove("Axis"); - - assertValidationFails( - () -> PersistentPhysicsJointState.CODEC.decode(encoded, new ExtraInfo()), - "Persisted HINGE joint requires an axis"); - } - - @Test - void jointStateCodecRejectsInvalidSpringValuesInsteadOfDefaulting() { - BsonDocument encoded = encodeJoint(persistentJointState(1)); - encoded.put("SpringStiffness", new BsonDouble(Double.NaN)); - - assertValidationFails( - () -> PersistentPhysicsJointState.CODEC.decode(encoded, new ExtraInfo()), - "Persisted joint spring stiffness must be finite and >= 0"); - } - - @Test - void stateBlockCodecRejectsOldSchemaVersionBeforeInflating() { - BsonDocument encoded = encodeStateBlock(bodyBlock()); - encoded.put("SchemaVersion", new BsonInt32(4)); - - assertValidationFails( - () -> PersistentPhysicsStateBlock.CODEC.decode(encoded, new ExtraInfo()), - "Must be greater than or equal to " - + PersistentPhysicsWorldResource.CURRENT_SCHEMA_VERSION); - } - - @Test - void stateBlockCodecRejectsUnsupportedEnvelopeFields() { - BsonDocument encoded = encodeStateBlock(bodyBlock()); - encoded.put("Codec", new BsonString("legacy-json-array")); - - assertValidationFails( - () -> PersistentPhysicsStateBlock.CODEC.decode(encoded, new ExtraInfo()), - "Persistent physics state block codec is unsupported"); - } - - @Test - void stateBlockCodecRejectsEmptyPayloadInsteadOfDefaulting() { - BsonDocument encoded = encodeStateBlock(bodyBlock()); - encoded.put("Payload", new BsonBinary(new byte[0])); - - assertValidationFails( - () -> PersistentPhysicsStateBlock.CODEC.decode(encoded, new ExtraInfo()), - "Persistent physics state block payload cannot be empty"); - } - - private static void assertValidationFails(Executable executable, String expectedMessagePart) { - RuntimeException exception = assertThrows(RuntimeException.class, executable); - - assertTrue(exceptionContainsMessage(exception, expectedMessagePart), - () -> "Expected validation message to contain: " + expectedMessagePart - + "\nActual: " + exceptionMessages(exception)); - } - - private static boolean exceptionContainsMessage(Throwable throwable, String expectedMessagePart) { - Throwable current = throwable; - while (current != null) { - if (current.getMessage() != null && current.getMessage().contains(expectedMessagePart)) { - return true; - } - current = current.getCause(); - } - return false; - } - - private static String exceptionMessages(Throwable throwable) { - StringBuilder messages = new StringBuilder(); - Throwable current = throwable; - while (current != null) { - if (!messages.isEmpty()) { - messages.append(" -> "); - } - messages.append(current.getMessage()); - current = current.getCause(); - } - return messages.toString(); - } - - private static BsonDocument encodedSpaceState() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - SpaceFixture fixture = spaceFixture("test:space-codec-validation-", settings); - return encodeSpace(PersistentPhysicsSpaceState.from(fixture.binding(), settings)); - } - - private static BsonDocument encodeSpace(PersistentPhysicsSpaceState state) { - return PersistentPhysicsSpaceState.CODEC.encode(state, new ExtraInfo()).asDocument(); - } - - private static BsonDocument encodeBody(PersistentPhysicsBodyState state) { - return PersistentPhysicsBodyState.CODEC.encode(state, new ExtraInfo()).asDocument(); - } - - private static BsonDocument encodeJoint(PersistentPhysicsJointState state) { - return PersistentPhysicsJointState.CODEC.encode(state, new ExtraInfo()).asDocument(); - } - - private static BsonDocument encodeStateBlock(PersistentPhysicsStateBlock block) { - return PersistentPhysicsStateBlock.CODEC.encode(block, new ExtraInfo()).asDocument(); - } - - private static BsonDocument encodeWorld(PersistentPhysicsWorldResource resource) { - return PersistentPhysicsWorldResource.CODEC.encode(resource, new ExtraInfo()).asDocument(); - } - - private static PersistentPhysicsStateBlock bodyBlock() { - return PersistentPhysicsStateBlock.bodyBlocks(new PersistentPhysicsBodyState[] { - persistentBodyState() - })[0]; - } - - private static PersistentPhysicsBodyState persistentBodyState() { - RigidBodyKey bodyId = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000001")); - PhysicsBodyRegistration registration = new PhysicsBodyRegistration( - bodyId, - new BackendBodyHandle(1L), - new SpaceId(1), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - return PersistentPhysicsBodyState.from(registration, boxSnapshot(0.0f)); - } - - private static PersistentPhysicsBodyState persistentPlaneBodyState(float groundY) { - RigidBodyKey bodyId = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000003")); - PhysicsBodyRegistration registration = new PhysicsBodyRegistration( - bodyId, - new BackendBodyHandle(1L), - new SpaceId(1), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - return PersistentPhysicsBodyState.from(registration, planeSnapshot(groundY)); - } - - @Nonnull - private static SpaceFixture spaceFixture(@Nonnull String backendPrefix, - @Nonnull PhysicsSpaceSettings settings) { - FakePhysicsBackend backend = new FakePhysicsBackend(backendPrefix + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), "test-world", settings); - return new SpaceFixture(resource, space); - } - - @Nonnull - private static PhysicsBodySnapshot boxSnapshot(float y) { - return new PhysicsBodySnapshot(new Vector3f(0.0f, y, 0.0f), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - PhysicsBodyType.DYNAMIC, - false, - false, - 0.0f, - ShapeType.BOX, - new Vector3f(0.5f, 0.75f, 1.0f), - 0.0f, - 0.0f, - PhysicsAxis.Y); - } - - @Nonnull - private static PhysicsBodySnapshot planeSnapshot(float groundY) { - return new PhysicsBodySnapshot(new Vector3f(0.0f, groundY, 0.0f), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - PhysicsBodyType.STATIC, - false, - false, - 0.0f, - ShapeType.PLANE, - null, - 0.0f, - 0.0f, - PhysicsAxis.Y); - } - - private record SpaceFixture(@Nonnull LegacyLiveHandleTestResource resource, - @Nonnull PhysicsSpace space) { - - @Nonnull - private PhysicsSpaceBinding binding() { - return resource.requireSpaceBinding(space.id()); - } - } - - private static PersistentPhysicsJointState persistentJointState(int spaceId) { - PersistentPhysicsJointState joint = new PersistentPhysicsJointState(); - joint.setSpaceId(spaceId); - joint.setBodyAKey(RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000001"))); - joint.setBodyBKey(RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000002"))); - joint.setType(PhysicsJointType.FIXED); - return joint; - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflightTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflightTest.java deleted file mode 100644 index 58337533..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsRestorePreflightTest.java +++ /dev/null @@ -1,184 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import java.lang.reflect.Field; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.Test; - -class PersistentPhysicsRestorePreflightTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void acceptsValidPersistedRuntimeSnapshot() { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.syncPersistentState(); - - assertNull(PersistentPhysicsRestorePreflight.validate(fixture.persistent)); - } - - @Test - void rejectsDuplicateSpaceIdsBeforeRuntimeStrip() { - RuntimeFixture fixture = createRuntimeFixture(); - PersistentPhysicsSpaceState first = - fixture.spaceState(); - PersistentPhysicsSpaceState second = first.copy(); - fixture.persistent.setSpaces(new PersistentPhysicsSpaceState[] { first, second }); - - String failure = PersistentPhysicsRestorePreflight.validate(fixture.persistent); - - assertNotNull(failure); - assertTrue(failure.contains("Duplicate persisted space id")); - } - - @Test - void rejectsDuplicateBodyKeysBeforeHydrationCanCreateBackendBodies() { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.syncPersistentState(); - PersistentPhysicsBodyState body = fixture.persistent.getBodies()[0]; - - fixture.persistent.setBodies(new PersistentPhysicsBodyState[] { body, body.copy() }); - String failure = PersistentPhysicsRestorePreflight.validate(fixture.persistent); - - assertNotNull(failure); - assertTrue(failure.contains("Duplicate persisted body key")); - } - - @Test - void rejectsInvalidWorldSettingsBeforeRuntimeStrip() throws Exception { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.syncPersistentState(); - PhysicsWorldSettings invalid = fixture.persistent.getWorldSettings(); - setFloatField(invalid, "maxStepDt", Float.NaN); - fixture.persistent.setWorldSettings(invalid); - - String failure = PersistentPhysicsRestorePreflight.validate(fixture.persistent); - - assertNotNull(failure); - assertTrue(failure.contains("Invalid persisted physics runtime settings")); - } - - @Test - void rejectsInvalidSpaceSettingsBeforeRuntimeStrip() throws Exception { - RuntimeFixture fixture = createRuntimeFixture(); - PersistentPhysicsSpaceState state = - fixture.spaceState(); - setIntField(state, "worldCollisionRadius", 0); - fixture.persistent.setSpaces(new PersistentPhysicsSpaceState[] { state }); - - String failure = PersistentPhysicsRestorePreflight.validate(fixture.persistent); - - assertNotNull(failure); - assertTrue(failure.contains("Invalid persisted physics space settings")); - } - - @Test - void rejectsBlankBackendIdBeforeRuntimeStrip() throws Exception { - RuntimeFixture fixture = createRuntimeFixture(); - PersistentPhysicsSpaceState state = - fixture.spaceState(); - setStringField(state, "backendId", " "); - fixture.persistent.setSpaces(new PersistentPhysicsSpaceState[] { state }); - - String failure = assertDoesNotThrow(() -> PersistentPhysicsRestorePreflight.validate(fixture.persistent)); - - assertNotNull(failure); - assertTrue(failure.contains("Invalid persisted physics space backend id")); - } - - @Test - void rejectsCcdModeForBackendWithoutCcdBeforeRuntimeStrip() { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.syncPersistentState(); - PhysicsWorldSettings settings = fixture.persistent.getWorldSettings(); - settings.setStepMode(PhysicsStepMode.CCD); - fixture.persistent.setWorldSettings(settings); - - String failure = PersistentPhysicsRestorePreflight.validate(fixture.persistent); - - assertNotNull(failure); - assertTrue(failure.contains("CCD mode")); - } - - @Test - void acceptsRuntimeProviderWithoutLegacyBackendRegistration() { - String backendId = "test:persistence-preflight-runtime-" + BACKEND_COUNTER.incrementAndGet(); - Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider(backendId)); - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - PersistentPhysicsSpaceState state = new PersistentPhysicsSpaceState(); - state.setSpaceId(50_001); - state.setBackendId(backendId); - persistent.setWorldSettings(PhysicsWorldSettings.defaults()); - persistent.setSpaces(new PersistentPhysicsSpaceState[] { state }); - - assertNull(PersistentPhysicsRestorePreflight.validate(persistent)); - } - - private static RuntimeFixture createRuntimeFixture() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:persistence-preflight-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource runtime = new LegacyLiveHandleTestResource(); - PhysicsSpace space = runtime.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - runtime.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - return new RuntimeFixture(runtime, new PersistentPhysicsWorldResource(), space); - } - - private static void setFloatField(Object target, String fieldName, float value) throws Exception { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.setFloat(target, value); - } - - private static void setIntField(Object target, String fieldName, int value) throws Exception { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.setInt(target, value); - } - - private static void setStringField(Object target, String fieldName, String value) throws Exception { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.set(target, value); - } - - private record RuntimeFixture(LegacyLiveHandleTestResource runtime, - PersistentPhysicsWorldResource persistent, - PhysicsSpace space) { - - private void syncPersistentState() { - persistent.setWorldSettings(runtime.getWorldSettings()); - persistent.setSpaces(new PersistentPhysicsSpaceState[] { - spaceState() - }); - persistent.setBodies(PersistentPhysicsRuntimeSnapshot.capture(runtime).getBodies()); - } - - private PersistentPhysicsSpaceState spaceState() { - return PersistentPhysicsSpaceState.from(runtime.requireSpaceBinding(space.id()), - runtime.getSpaceSettings(space.id())); - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java new file mode 100644 index 00000000..e59dee4a --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java @@ -0,0 +1,101 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class PhysicsStoreRuntimeBoundarySourceGuardTest { + + @Test + void spaceMutationRuntimeCleanupDoesNotResolveRuntimeBindingsByUuid() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java")); + + assertFalse(source.contains("runtime.getSpaceHandle(spaceUuid)")); + assertFalse(source.contains("runtime.getSpaceBackendId(spaceUuid)")); + } + + @Test + void staleBodyCleanupDoesNotFallbackToRuntimeUuidLookups() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java")); + + assertFalse(source.contains("runtime.getJointHandle(joint.jointUuid())")); + assertFalse(source.contains("runtime.getJointSpaceHandle(joint.jointUuid())")); + assertFalse(source.contains("runtime.getSpaceHandle(joint.spaceUuid())")); + assertFalse(source.contains("runtime.removeBodyHandle(body.bodyUuid())")); + } + + @Test + void backendAccessDoesNotResolveRuntimeSpacesByUuid() throws IOException { + String backendAccess = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java")); + String diagnostics = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java")); + + assertFalse(backendAccess.contains("runtime.getSpaceHandle(spaceUuid)")); + assertFalse(backendAccess.contains("runtime.getSpaceBackendId(spaceUuid)")); + assertFalse(diagnostics.contains("PhysicsStoreBackendAccess.space(runtime, spaceUuid)")); + } + + @Test + void completedStepPublicationIteratesRuntimeSpacesByRef() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java")); + + assertFalse(source.contains("runtime.forEachSpaceBinding")); + assertFalse(source.contains("runtime.hasTerrainBodyHandles(rowUuid)")); + } + + @Test + void terrainNeighborStitchingDoesNotResolveRuntimeBindingsByUuid() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java")); + + assertFalse(source.contains("runtime.getTerrainVoxelBodyHandle(neighborUuid)")); + assertFalse(source.contains("runtime.getTerrainSpaceHandle(neighborUuid)")); + } + + @Test + void debugQueriesDoNotResolveRuntimeSpacesByUuid() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java")); + + assertFalse(source.contains("runtime.getSpaceHandle(spaceUuid)")); + assertFalse(source.contains("runtime.getSpaceBackendId(spaceUuid)")); + } + + @Test + void legacyWorldResourceFacadeDoesNotIterateRuntimeSpacesByUuid() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java")); + + assertFalse(source.contains("forEachSpaceBinding")); + } + + @Test + void runtimeResourceDoesNotExposeUuidRuntimeReadApis() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java")); + + assertFalse(source.contains("getSpaceHandle(@Nonnull UUID")); + assertFalse(source.contains("getSpaceBackendId(@Nonnull UUID")); + assertFalse(source.contains("getBodyHandle(@Nonnull UUID")); + assertFalse(source.contains("getBodySpaceHandle(@Nonnull UUID")); + assertFalse(source.contains("getJointHandle(@Nonnull UUID")); + assertFalse(source.contains("getJointSpaceHandle(@Nonnull UUID")); + assertFalse(source.contains("getTerrainSpaceHandle(@Nonnull UUID")); + assertFalse(source.contains("getTerrainVoxelBodyHandle(@Nonnull UUID")); + assertFalse(source.contains("hasTerrainBodyHandles(@Nonnull UUID")); + assertFalse(source.contains("forEachTerrainBodyHandle(@Nonnull UUID")); + assertFalse(source.contains("bodyUuidsForSpaceHandle")); + assertFalse(source.contains("jointUuidsForSpaceHandle")); + assertFalse(source.contains("terrainUuidsForSpaceHandle")); + assertFalse(source.contains("forEachSpaceBinding")); + assertFalse(source.contains("@Nullable Ref spaceRef")); + assertFalse(source.contains("@Nullable Ref jointRef")); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java new file mode 100644 index 00000000..80293f22 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java @@ -0,0 +1,193 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.codec.ExtraInfo; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import org.bson.BsonDocument; +import org.bson.BsonDouble; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +class PersistentPhysicsStoreResourceTest { + + private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); + private static final UUID SPACE_UUID = + UUID.fromString("00000000-0000-0000-0000-000000000001"); + private static final UUID BODY_UUID = + UUID.fromString("00000000-0000-0000-0000-000000000002"); + private static final UUID SHAPE_UUID = + UUID.fromString("00000000-0000-0000-0000-000000000003"); + private static final UUID MATERIAL_UUID = + UUID.fromString("00000000-0000-0000-0000-000000000004"); + private static final UUID COLLIDER_UUID = + UUID.fromString("00000000-0000-0000-0000-000000000005"); + + @Test + void storeResourceCodecPreservesDtoRows() { + PersistentPhysicsStoreResource resource = validResource(registeredBackendId("codec")); + + BsonDocument encoded = PersistentPhysicsStoreResource.CODEC.encode(resource, + new ExtraInfo()).asDocument(); + PersistentPhysicsStoreResource decoded = PersistentPhysicsStoreResource.CODEC.decode(encoded, + new ExtraInfo()); + + assertEquals(PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION, + encoded.getInt32("SchemaVersion").getValue()); + assertNotNull(decoded); + assertEquals(1, decoded.getSpaces().length); + assertEquals(1, decoded.getBodies().length); + assertEquals(1, decoded.getColliders().length); + assertEquals(1, decoded.getShapes().length); + assertEquals(1, decoded.getMaterials().length); + assertEquals(BODY_UUID, decoded.getBodies()[0].getBodyUuid()); + assertEquals(COLLIDER_UUID, decoded.getBodies()[0].getColliderUuids()[0]); + } + + @Test + void preflightAcceptsAvailableRuntimeProviderWithoutLegacyBackendRegistration() { + PersistentPhysicsStoreResource resource = validResource(registeredBackendId("preflight")); + + PersistentPhysicsStorePreflight.Result result = resource.preflight(); + + assertTrue(result.valid(), () -> result.errors().toString()); + } + + @Test + void preflightRejectsDuplicateBodyUuidBeforeBackendHydration() { + PersistentPhysicsStoreResource resource = validResource(registeredBackendId("duplicate-body")); + PersistentBodyDto body = resource.getBodies()[0]; + resource.setBodies(new PersistentBodyDto[] { body, body.copy() }); + + PersistentPhysicsStorePreflight.Result result = resource.preflight(); + + assertTrue(result.errors().stream() + .anyMatch(error -> error.contains("Duplicate PhysicsStore body UUID"))); + } + + @Test + void bodyDtoCodecRejectsInvalidMassInsteadOfDefaulting() { + BsonDocument encoded = PersistentBodyDto.CODEC.encode(bodyDto(), new ExtraInfo()).asDocument(); + encoded.put("Mass", new BsonDouble(Double.NaN)); + + assertValidationFails( + () -> PersistentBodyDto.CODEC.decode(encoded, new ExtraInfo()), + "Persisted body mass must be finite and >= 0"); + } + + @Test + void shapeDtoCodecRejectsUnsupportedNonFinitePlaneGroundY() { + BsonDocument encoded = PersistentShapeDto.CODEC.encode(shapeDto(), new ExtraInfo()).asDocument(); + encoded.put("GroundY", new BsonDouble(Double.NaN)); + + assertValidationFails( + () -> PersistentShapeDto.CODEC.decode(encoded, new ExtraInfo()), + "Persisted shape ground Y must be finite"); + } + + private static PersistentPhysicsStoreResource validResource(String backendId) { + PersistentPhysicsStoreResource resource = new PersistentPhysicsStoreResource(); + resource.setSpaces(new PersistentSpaceDto[] { + new PersistentSpaceDto(SPACE_UUID, backendId, new Vector3f(0.0f, -9.81f, 0.0f)) + }); + resource.setBodies(new PersistentBodyDto[] { bodyDto() }); + resource.setShapes(new PersistentShapeDto[] { shapeDto() }); + resource.setMaterials(new PersistentMaterialDto[] { + new PersistentMaterialDto(MATERIAL_UUID, 0.5f, 0.0f) + }); + resource.setColliders(new PersistentColliderDto[] { + new PersistentColliderDto(COLLIDER_UUID, + BODY_UUID, + SHAPE_UUID, + MATERIAL_UUID, + new Vector3f(), + new Quaternionf(), + false, + 0, + -1) + }); + return resource; + } + + private static PersistentBodyDto bodyDto() { + return new PersistentBodyDto(BODY_UUID, + SPACE_UUID, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.PERSISTENT, + PhysicsBodyType.DYNAMIC, + 1.0f, + 0.0f, + 0.0f, + false, + new UUID[] { COLLIDER_UUID }, + new PersistentBodyRuntimeStateDto(new Vector3f(), + new Quaternionf(), + new Vector3f(), + new Vector3f(), + false)); + } + + private static PersistentShapeDto shapeDto() { + return new PersistentShapeDto(SHAPE_UUID, + ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""); + } + + private static String registeredBackendId(String suffix) { + String backendId = "test:persistent-store-" + suffix + "-" + + BACKEND_COUNTER.incrementAndGet(); + Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider(backendId)); + return backendId; + } + + private static void assertValidationFails(Executable executable, String expectedMessagePart) { + RuntimeException exception = assertThrows(RuntimeException.class, executable); + assertTrue(exceptionContainsMessage(exception, expectedMessagePart), + () -> "Expected validation message to contain: " + expectedMessagePart + + "\nActual: " + exceptionMessages(exception)); + } + + private static boolean exceptionContainsMessage(Throwable throwable, String expectedMessagePart) { + Throwable current = throwable; + while (current != null) { + if (current.getMessage() != null && current.getMessage().contains(expectedMessagePart)) { + return true; + } + current = current.getCause(); + } + return false; + } + + private static String exceptionMessages(Throwable throwable) { + StringBuilder messages = new StringBuilder(); + Throwable current = throwable; + while (current != null) { + if (!messages.isEmpty()) { + messages.append(" -> "); + } + messages.append(current.getMessage()); + current = current.getCause(); + } + return messages.toString(); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java index 638e8e1e..8ee4ced0 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import com.hypixel.hytale.component.Ref; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; @@ -11,8 +12,8 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; import java.util.ArrayList; @@ -51,29 +52,30 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000004"); BackendSpaceHandle spaceHandle = new BackendSpaceHandle(31); BackendBodyHandle bodyHandle = new BackendBodyHandle(42L); - RigidBodyKey bodyKey = RigidBodyKey.random(); + Ref spaceRef = new TestRef(true); + Ref bodyRef = new TestRef(true); runtime.putRuntime(backendId, backendRuntime); - runtime.putSpaceBinding(spaceUuid, backendId, spaceHandle); - runtime.putBodyHandle(bodyUuid, spaceUuid, spaceHandle, bodyHandle); - runtime.putBodyHitMetadata(bodyHandle, bodyKey, PhysicsBodyType.DYNAMIC, ShapeType.BOX); + runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); + runtime.putBodyHitMetadata(bodyHandle, bodyRef, PhysicsBodyType.DYNAMIC, ShapeType.BOX); assertSame(backendRuntime, runtime.getRuntime(backendId)); - assertEquals(spaceHandle, runtime.getSpaceHandle(spaceUuid)); - assertEquals(backendId, runtime.getSpaceBackendId(spaceUuid)); - assertEquals(bodyHandle, runtime.getBodyHandle(bodyUuid)); - assertEquals(spaceHandle, runtime.getBodySpaceHandle(bodyUuid)); + assertEquals(spaceUuid, runtime.getSpaceUuid(spaceRef)); + assertEquals(spaceHandle, runtime.getSpaceHandle(spaceRef)); + assertEquals(backendId, runtime.getSpaceBackendId(spaceRef)); + assertEquals(bodyHandle, runtime.getBodyHandle(bodyRef)); + assertEquals(spaceHandle, runtime.getBodySpaceHandle(bodyRef)); assertEquals(bodyUuid, runtime.getBodySnapshotMetadata(bodyHandle.value()).bodyUuid()); - assertEquals(bodyKey, runtime.getBodyHitMetadata(bodyHandle).bodyKey()); List handles = new ArrayList<>(); runtime.forEachBodyHandle(spaceHandle, handles::add); assertEquals(List.of(bodyHandle.value()), handles); - runtime.removeBodyHandle(bodyUuid); + runtime.removeBodyHandle(bodyUuid, bodyRef); - assertNull(runtime.getBodyHandle(bodyUuid)); - assertNull(runtime.getBodySpaceHandle(bodyUuid)); + assertNull(runtime.getBodyHandle(bodyRef)); + assertNull(runtime.getBodySpaceHandle(bodyRef)); assertNull(runtime.getBodySnapshotMetadata(bodyHandle.value())); assertNull(runtime.getBodyHitMetadata(bodyHandle)); handles.clear(); @@ -81,6 +83,41 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { assertEquals(List.of(), handles); } + @Test + void runtimeIndexesExposeRefsForTopologyCleanup() throws ReflectiveOperationException { + PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000007"); + UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000008"); + UUID jointUuid = UUID.fromString("00000000-0000-0000-0000-000000000009"); + UUID terrainUuid = UUID.fromString("00000000-0000-0000-0000-00000000000a"); + BackendId backendId = new BackendId("test:runtime-ref-index"); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(43); + BackendBodyHandle bodyHandle = new BackendBodyHandle(44L); + BackendJointHandle jointHandle = new BackendJointHandle(45L); + BackendBodyHandle terrainHandle = new BackendBodyHandle(46L); + Ref spaceRef = new TestRef(true); + Ref bodyRef = new TestRef(true); + Ref jointRef = new TestRef(true); + Ref terrainRef = new TestRef(true); + + runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); + runtime.putJointHandle(jointRef, jointUuid, spaceHandle, jointHandle); + runtime.putTerrainBodyHandle(terrainRef, terrainUuid, spaceHandle, terrainHandle, true); + + assertEquals(List.of(bodyRef), refsFor(runtime, "bodyRefsForSpaceHandle", spaceHandle)); + assertEquals(List.of(jointRef), refsFor(runtime, "jointRefsForSpaceHandle", spaceHandle)); + assertEquals(List.of(terrainRef), refsFor(runtime, "terrainRefsForSpaceHandle", spaceHandle)); + + runtime.removeBodyHandle(bodyUuid, bodyRef); + runtime.removeJointHandle(jointUuid, jointRef); + runtime.removeTerrainHandles(terrainRef, terrainUuid); + + assertEquals(List.of(), refsFor(runtime, "bodyRefsForSpaceHandle", spaceHandle)); + assertEquals(List.of(), refsFor(runtime, "jointRefsForSpaceHandle", spaceHandle)); + assertEquals(List.of(), refsFor(runtime, "terrainRefsForSpaceHandle", spaceHandle)); + } + @Test void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); @@ -108,4 +145,28 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { assertEquals(PhysicsStoreSnapshotFrame.EMPTY, resource.getLatestFrame()); assertNull(resource.getBody(bodyUuid)); } + + private static final class TestRef extends Ref { + + private final boolean valid; + + private TestRef(boolean valid) { + super(null); + this.valid = valid; + } + + @Override + public boolean isValid() { + return valid; + } + } + + @SuppressWarnings("unchecked") + private static List refsFor(PhysicsRuntimeResource runtime, + String methodName, + BackendSpaceHandle spaceHandle) throws ReflectiveOperationException { + return (List) PhysicsRuntimeResource.class + .getMethod(methodName, BackendSpaceHandle.class) + .invoke(runtime, spaceHandle); + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java index 91fa59f2..b93d5038 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java @@ -7,7 +7,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -59,8 +58,7 @@ void freshnessCheckUsesAdvancedVisualTick() { void pendingRaycastResultsArePolledWithoutBlocking() { BodyVisualInterestState state = new BodyVisualInterestState(); CompletableFuture> pending = new CompletableFuture<>(); - RigidBodyKey bodyKey = RigidBodyKey.random(); - RaycastHitView hit = new RaycastHitView(bodyKey, + RaycastHitView hit = new RaycastHitView(null, PhysicsBodyType.DYNAMIC, new Vector3f(), new Vector3f(0.0f, 1.0f, 0.0f), diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java index a610303b..0c933339 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java @@ -8,12 +8,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.codec.ExtraInfo; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsSpaceState; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -21,14 +22,13 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.UUID; import org.bson.BsonDocument; +import org.joml.Vector3f; import org.junit.jupiter.api.Test; class PhysicsSpaceSettingsTest { - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - @Test void defaultsExposeHeadlessVisualSyncRadii() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); @@ -326,7 +326,7 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { } @Test - void persistentSpaceStateRoundTripPreservesDetachedVisualCadenceSettings() { + void persistentSpaceDtoRoundTripPreservesDetachedVisualCadenceSettings() { PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); original.getWorldCollisionSettings().setNativeVoxelTerrainEnabled(true); original.getWorldCollisionSettings().setTerrainMaterial(0.85f, 0.2f); @@ -334,24 +334,32 @@ void persistentSpaceStateRoundTripPreservesDetachedVisualCadenceSettings() { original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); - FakePhysicsBackend backend = - new FakePhysicsBackend("test:settings-persistence-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), "test-world", original); - PersistentPhysicsSpaceState state = - PersistentPhysicsSpaceState.from(resource.requireSpaceBinding(space.id()), original); - - BsonDocument encoded = PersistentPhysicsSpaceState.CODEC.encode(state, new ExtraInfo()).asDocument(); - - assertTrue(encoded.containsKey("NativeVoxelTerrainEnabled")); + PhysicsWorldCollisionSettings collision = original.getWorldCollisionSettings(); + PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), + "test:settings-persistence", + new Vector3f(0.0f, -9.81f, 0.0f), + collision.getWorldCollisionMode(), + collision.getEntityChunkBoundaryMode(), + collision.isNativeVoxelTerrainEnabled(), + collision.getWorldCollisionRadius(), + collision.getWorldCollisionBodyRadius(), + collision.getWorldCollisionTtlTicks(), + collision.getTerrainFriction(), + collision.getTerrainRestitution(), + new SolverSettingsComponent(original.getSolverSettings()), + new VisualSyncSettingsComponent(original.getVisualSyncSettings()), + new VisualMaterializationSettingsComponent(original.getVisualMaterializationSettings()), + new CollisionLodSettingsComponent(original.getCollisionLodSettings()), + new ExtensionSettingsComponent(original.getExtensionSettings())); + + BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); + + assertTrue(encoded.containsKey("NativeVoxelTerrain")); assertTrue(encoded.containsKey("TerrainFriction")); assertTrue(encoded.containsKey("TerrainRestitution")); - assertTrue(encoded.containsKey("DetachedVisualInterestRefreshIntervalTicks")); - assertTrue(encoded.containsKey("DetachedVisualCandidateRefreshIntervalTicks")); - assertTrue(encoded.containsKey("DetachedVisualVisibilityCheckIntervalTicks")); + assertTrue(encoded.containsKey("VisualMaterializationSettings")); PhysicsSpaceSettings decoded = Objects.requireNonNull( - PersistentPhysicsSpaceState.CODEC.decode(encoded, new ExtraInfo())).toSettings(); + PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())).toSettings(); assertTrue(decoded.getWorldCollisionSettings().isNativeVoxelTerrainEnabled()); assertEquals(0.85f, decoded.getWorldCollisionSettings().getTerrainFriction(), 0.0001f); assertEquals(0.2f, decoded.getWorldCollisionSettings().getTerrainRestitution(), 0.0001f); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java index 8d7e181b..597f33f9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java @@ -26,7 +26,7 @@ void rejectsUnknownSerializedNames() { @Test void describesPendingStepBehavior() { - assertEquals("drop dt while an owner step is pending", + assertEquals("drop dt while a store tick step is pending", PhysicsStepSchedulingMode.DROP_PENDING_DT.describePendingStepBehavior()); assertEquals("accumulate pending dt for one capped catch-up step", PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT.describePendingStepBehavior()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java deleted file mode 100644 index 215f0819..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldResourceStateTest.java +++ /dev/null @@ -1,964 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsChunkBoundaryRuntime.ChunkBoundaryPauseState; -import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCommand; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsWorldResourceStateTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void worldSettingsAreCopiedAcrossResourceBoundary() { - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - - PhysicsWorldSettings copy = resource.getWorldSettings(); - copy.setSimulationSteps(4); - - assertEquals(PhysicsWorldSettings.MIN_SIMULATION_STEPS, - resource.getWorldSettings().getSimulationSteps()); - - resource.setWorldSettings(copy); - assertEquals(4, resource.getWorldSettings().getSimulationSteps()); - - copy.setSimulationSteps(2); - assertEquals(4, resource.getWorldSettings().getSimulationSteps()); - } - - @Test - void spaceSettingsAreCopiedAcrossResourceBoundary() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:space-settings-copy-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpaceSettings initial = PhysicsSpaceSettings.streamingWorldCollision(); - initial.getSolverSettings().setSolverIterations(7); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - initial); - - PhysicsSpaceSettings copy = resource.getSpaceSettings(space.id()); - copy.getSolverSettings().setSolverIterations(3); - copy.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.NONE); - - assertEquals(7, resource.getSpaceSettings(space.id()).getSolverSettings().getSolverIterations()); - assertEquals(WorldCollisionMode.STREAMING, - resource.getSpaceSettings(space.id()).getWorldCollisionSettings().getWorldCollisionMode()); - assertEquals(7, ((InMemoryPhysicsSpace) space).getSolverIterations()); - - resource.setSpaceSettings(space.id(), copy); - assertEquals(3, resource.getSpaceSettings(space.id()).getSolverSettings().getSolverIterations()); - assertEquals(WorldCollisionMode.NONE, - resource.getSpaceSettings(space.id()).getWorldCollisionSettings().getWorldCollisionMode()); - assertEquals(3, ((InMemoryPhysicsSpace) space).getSolverIterations()); - - copy.getSolverSettings().setSolverIterations(5); - assertEquals(3, resource.getSpaceSettings(space.id()).getSolverSettings().getSolverIterations()); - } - - @Test - void copyFromCopiesWorldSettingsWithoutLiveSpaceMetadata() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:copy-topology-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource source = new LegacyLiveHandleTestResource(); - PhysicsWorldSettings sourceSettings = source.getWorldSettings(); - sourceSettings.setSimulationSteps(4); - source.setWorldSettings(sourceSettings); - PhysicsSpaceSettings sourceSpaceSettings = PhysicsSpaceSettings.streamingWorldCollision(); - PhysicsSpace sourceSpace = source.createLiveSpace(backend.getId(), - "source-world", - sourceSpaceSettings); - - LegacyLiveHandleTestResource target = new LegacyLiveHandleTestResource(); - PhysicsSpace targetSpace = target.createLiveSpace(backend.getId(), - "target-world", - PhysicsSpaceSettings.defaults()); - - target.copyFrom(source); - - assertEquals(4, target.getWorldSettings().getSimulationSteps()); - assertEquals(0, target.getSpaceCount()); - assertTrue(target.getSpaceIds().isEmpty()); - assertFalse(target.hasSpace(sourceSpace.id())); - assertEquals(0L, target.worldCollisionStreamingRevision(targetSpace.id())); - assertThrows(IllegalStateException.class, () -> target.getSpaceSettings(sourceSpace.id())); - assertTrue(((InMemoryPhysicsSpace) targetSpace).isClosed()); - assertFalse(((InMemoryPhysicsSpace) sourceSpace).isClosed()); - } - - @Test - void bodySyncStateTracksLastSyncAndClampsNegativeSkipTime() { - BodySyncState syncState = new BodySyncState(); - - syncState.recordSync(new Vector3f(1.0f, 2.0f, 3.0f), - new Quaternionf().rotateY(0.5f), - true); - syncState.recordSkip(-5.0f); - syncState.recordSkip(0.75f); - - assertTrue(syncState.isInitialized()); - assertTrue(syncState.isSleeping()); - assertEquals(0.75f, syncState.getSecondsSinceSync(), 0.0001f); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), syncState.getLastSyncedPosition()); - assertEquals(new Quaternionf().rotateY(0.5f), syncState.getLastSyncedRotation()); - } - - @Test - void chunkBoundaryPauseStateCopiesProvidedVectors() { - ChunkBoundaryPauseState state = - new ChunkBoundaryPauseState(); - Vector3f linear = new Vector3f(1.0f, 2.0f, 3.0f); - Vector3f angular = new Vector3f(4.0f, 5.0f, 6.0f); - - state.set(42L, PhysicsBodyType.KINEMATIC, linear, angular); - linear.zero(); - angular.zero(); - - assertEquals(42L, state.getTargetChunkIndex()); - assertArrayEquals(new long[] {42L}, state.getTargetChunkIndices()); - assertEquals(PhysicsBodyType.KINEMATIC, state.getOriginalBodyType()); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), state.getLinearVelocity()); - assertEquals(new Vector3f(4.0f, 5.0f, 6.0f), state.getAngularVelocity()); - } - - @Test - void chunkBoundaryPauseStateCopiesTargetChunkFootprint() { - ChunkBoundaryPauseState state = new ChunkBoundaryPauseState(); - long[] targetChunks = {10L, 11L}; - - state.set(10L, targetChunks, physicsSnapshot(PhysicsBodyType.DYNAMIC)); - targetChunks[0] = 99L; - - assertEquals(10L, state.getTargetChunkIndex()); - assertArrayEquals(new long[] {10L, 11L}, state.getTargetChunkIndices()); - } - - @Test - void duplicateBodyKeyDoesNotLeaveUnregisteredBackendBodyInSpace() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:duplicate-body-key-no-leak-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyKey = RigidBodyKey.random(); - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - - resource.addBody(bodyKey, - space.id(), - first, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - assertThrows(IllegalArgumentException.class, () -> resource.addBody(bodyKey, - space.id(), - second, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); - - assertEquals(1, space.bodyCount()); - assertFalse(space.getBodies().contains(second)); - assertSame(first, resource.getBody(bodyKey)); - } - - @Test - void resetRuntimeStateKeepingSpacesReplacesNativeSpacesAndClearsRuntimeState() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:reset-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingWorldCollision(); - settings.getSolverSettings().setSolverIterations(7); - settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(64); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), "test-world", settings); - space.setGravity(0.0f, -3.0f, 0.0f); - - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey firstId = resource.addBody(RigidBodyKey.random(), - space.id(), - first, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.addBody(space.id(), - second, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsJoint fixedJoint = space.createFixedJoint(first, second, new Vector3f(), new Vector3f()); - resource.addJoint(space.id(), fixedJoint); - resource.markContinuousCollisionForced(firstId); - resource.markBodyControlled(firstId); - resource.updateChunkBoundarySafeState(firstId, new Vector3f(1.0f), new Quaternionf()); - - PhysicsRuntimeResetResult reset = - resource.resetRuntimeStateKeepingSpaces("test-world"); - - InMemoryPhysicsSpace original = backend.createdSpaces().get(0); - InMemoryPhysicsSpace replacement = backend.createdSpaces().get(1); - assertEquals(2, reset.removedBodies()); - assertEquals(1, reset.removedJoints()); - assertEquals(1, reset.keptSpaces()); - assertTrue(original.isClosed()); - assertFalse(replacement.isClosed()); - PhysicsSpace restoredSpace = resource.callOwner("resolve reset replacement space", - () -> resource.getLiveSpace(space.id())); - assertSame(replacement, restoredSpace); - assertNotSame(original, restoredSpace); - assertEquals(new Vector3f(0.0f, -3.0f, 0.0f), replacement.getGravity()); - assertEquals(0, replacement.bodyCount()); - assertEquals(0, replacement.jointCount()); - assertEquals(7, replacement.getSolverIterations()); - - PhysicsSpaceSettings preserved = resource.getSpaceSettings(space.id()); - assertEquals(WorldCollisionMode.STREAMING, preserved.getWorldCollisionSettings().getWorldCollisionMode()); - assertEquals(64, preserved.getVisualMaterializationSettings().getDetachedVisualMaxMaterialized()); - assertEquals(0, resource.getBodyRegistrationViews().size()); - assertEquals(0, resource.getBodySnapshotCount()); - assertNull(resource.getBody(firstId)); - assertFalse(resource.isBodyControlled(firstId)); - assertNull(resource.getChunkBoundarySafeState(firstId)); - assertForcedCcdRestoreDoesNotAffectReusedBodyId(resource, replacement, firstId); - } - - @Test - void failedRuntimeResetKeepsOriginalSpacesAndDiscardsReplacements() { - BackendId backendId = - new BackendId("test:reset-failure-" + BACKEND_COUNTER.incrementAndGet()); - FakePhysicsBackendRuntimeProvider provider = - new FakePhysicsBackendRuntimeProvider(backendId, false, false).withSolverTuning(); - Impulse.registerRuntimeProvider(provider); - - PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getSolverSettings().setSolverIterations(7); - SpaceId spaceId = resource.createSpace(backendId, "test-world", settings); - PhysicsSpaceBinding original = resource.requireSpaceBinding(spaceId); - FakePhysicsBackendRuntime originalRuntime = - (FakePhysicsBackendRuntime) original.runtime(); - - provider.failNextSolverTuning(new IllegalStateException("replacement tuning failed")); - - IllegalStateException failure = assertThrows(IllegalStateException.class, - () -> resource.resetRuntimeStateKeepingSpaces("test-world")); - - assertEquals("replacement tuning failed", failure.getMessage()); - assertSame(original, resource.requireSpaceBinding(spaceId)); - assertTrue(originalRuntime.hasSpace(original.backendSpaceHandle().value())); - assertEquals(2, provider.createdRuntimes().size()); - FakePhysicsBackendRuntime failedReplacement = provider.createdRuntimes().get(1); - assertFalse(failedReplacement.hasSpace(spaceId.value())); - } - - @Test - void removeSpaceClosesBackendSpaceWhenRuntimeCleanupThrows() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:remove-space-close-failure-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - ThrowingSyncCleanupResource resource = new ThrowingSyncCleanupResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.registerBodyAttachment(bodyId, new TestRef(true)); - - IllegalStateException failure = assertThrows(IllegalStateException.class, - () -> resource.removeSpace(space.id(), "test-world")); - - assertEquals("sync cleanup failed", failure.getMessage()); - assertTrue(((InMemoryPhysicsSpace) space).isClosed()); - } - - @Test - void worldCollisionStreamingRevisionTracksSettingsChangesAndClears() { - BackendId backendId = - new BackendId("test:world-collision-revision-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider(backendId, false, false)); - - PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - SpaceId spaceId = resource.createSpace(backendId, - "test-world", - PhysicsSpaceSettings.streamingWorldCollision()); - long initialRevision = resource.worldCollisionStreamingRevision(spaceId); - - PhysicsSpaceSettings visualOnly = resource.getSpaceSettings(spaceId); - visualOnly.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(32); - resource.setSpaceSettings(spaceId, visualOnly); - assertEquals(initialRevision, resource.worldCollisionStreamingRevision(spaceId)); - - PhysicsSpaceSettings radiusChanged = resource.getSpaceSettings(spaceId); - radiusChanged.getWorldCollisionSettings().setWorldCollisionRadius( - radiusChanged.getWorldCollisionSettings().getWorldCollisionRadius() + 1); - resource.setSpaceSettings(spaceId, radiusChanged); - long radiusRevision = resource.worldCollisionStreamingRevision(spaceId); - assertTrue(radiusRevision > initialRevision); - - PhysicsSpaceSettings materialChanged = resource.getSpaceSettings(spaceId); - materialChanged.getWorldCollisionSettings().setTerrainMaterial(0.8f, 0.1f); - resource.setSpaceSettings(spaceId, materialChanged); - long materialRevision = resource.worldCollisionStreamingRevision(spaceId); - assertTrue(materialRevision > radiusRevision); - - resource.clearWorldCollision(spaceId); - long clearRevision = resource.worldCollisionStreamingRevision(spaceId); - assertTrue(clearRevision > materialRevision); - } - - @Test - void destroyBodyClearsRuntimeStateAndSnapshotIndexes() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:destroy-cleanup-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - body.setContinuousCollisionEnabled(true); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.markContinuousCollisionForced(bodyId); - resource.markBodyControlled(bodyId); - resource.updateChunkBoundarySafeState(bodyId, new Vector3f(1.0f), new Quaternionf()); - resource.pauseChunkBoundaryBody(bodyId, - 42L, - PhysicsBodyType.DYNAMIC, - new Vector3f(2.0f, 0.0f, 0.0f), - new Vector3f(0.0f, 3.0f, 0.0f)); - resource.getBodySnapshot(bodyId); - - resource.destroyBody(bodyId); - - assertEquals(0, space.bodyCount()); - assertNull(resource.getBody(bodyId)); - assertFalse(resource.isBodyControlled(bodyId)); - assertNull(resource.getChunkBoundarySafeState(bodyId)); - assertNull(resource.getChunkBoundaryPauseState(bodyId)); - assertEquals(0, resource.getBodySnapshotCount()); - assertEquals(0, resource.getBodySnapshotCount(space.id())); - assertEquals(0, resource.getBodySnapshotCellCount()); - assertNull(resource.getBodySnapshotIfRegistered(bodyId)); - assertThrows(IllegalArgumentException.class, () -> resource.getBodySnapshot(bodyId)); - assertForcedCcdRestoreDoesNotAffectReusedBodyId(resource, space, bodyId); - } - - @Test - void clearBodiesDestroysRegisteredBackendBodiesAndJoints() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:clear-bodies-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey firstId = resource.addBody(space.id(), - first, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey secondId = resource.addBody(space.id(), - second, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PhysicsJoint fixedJoint = space.createFixedJoint(first, second, new Vector3f(), new Vector3f()); - resource.addJoint(space.id(), fixedJoint); - resource.markContinuousCollisionForced(firstId); - resource.markBodyControlled(firstId); - resource.updateChunkBoundarySafeState(firstId, new Vector3f(1.0f), new Quaternionf()); - resource.pauseChunkBoundaryBody(firstId, - 42L, - PhysicsBodyType.DYNAMIC, - new Vector3f(2.0f, 0.0f, 0.0f), - new Vector3f(0.0f, 3.0f, 0.0f)); - - assertEquals(2, resource.refreshBodySnapshots()); - assertEquals(2, space.bodyCount()); - assertEquals(1, space.jointCount()); - assertEquals(2, resource.getBodyRegistrationCount()); - - resource.clearBodies(); - - PhysicsSpace retainedSpace = resource.callOwner("resolve clear bodies space", - () -> resource.getLiveSpace(space.id())); - assertSame(space, retainedSpace); - assertEquals(0, space.bodyCount()); - assertEquals(0, space.jointCount()); - assertEquals(0, resource.getBodyRegistrationCount()); - assertEquals(0, resource.getBodySnapshotCount()); - assertEquals(0, resource.getBodySnapshotCellCount()); - assertNull(resource.getBody(firstId)); - assertNull(resource.getBody(secondId)); - assertFalse(resource.isBodyControlled(firstId)); - assertNull(resource.getChunkBoundarySafeState(firstId)); - assertNull(resource.getChunkBoundaryPauseState(firstId)); - assertThrows(IllegalArgumentException.class, () -> resource.getBodySnapshot(firstId)); - assertForcedCcdRestoreDoesNotAffectReusedBodyId(resource, space, firstId); - } - - @Test - void bodySnapshotStoreRefreshesQueriesAndDropsStaleBodies() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:snapshot-store-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody near = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - near.setPosition(0.0f, 0.0f, 0.0f); - PhysicsBody far = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - far.setPosition(40.0f, 0.0f, 0.0f); - RigidBodyKey nearId = resource.addBody(space.id(), - near, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.addBody(space.id(), - far, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - assertEquals(2, resource.refreshBodySnapshots()); - assertEquals(2, resource.getBodySnapshotCount(space.id())); - AtomicInteger nearMatches = new AtomicInteger(); - int candidates = resource.forEachBodySnapshotNear(space.id(), - new Vector3f(), - 8.0f, - entry -> { - assertEquals(nearId, entry.bodyKey()); - nearMatches.incrementAndGet(); - }); - assertEquals(1, candidates); - assertEquals(1, nearMatches.get()); - - space.removeBody(far); - assertEquals(1, resource.refreshBodySnapshots()); - - assertEquals(1, resource.getBodySnapshotCount()); - assertEquals(1, resource.getBodySnapshotCount(space.id())); - AtomicInteger remainingSnapshots = new AtomicInteger(); - resource.forEachBodySnapshot(space.id(), entry -> { - assertEquals(nearId, entry.bodyKey()); - remainingSnapshots.incrementAndGet(); - }); - assertEquals(1, remainingSnapshots.get()); - resource.clearBodies(); - assertEquals(0, resource.getBodySnapshotCount()); - assertEquals(0, resource.getBodySnapshotCellCount()); - } - - @Test - void getBodySnapshotFallsBackToLiveBackendWhenReaderSnapshotIsMissing() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:snapshot-live-fallback-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - body.setPosition(7.0f, 8.0f, 9.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PublishedPhysicsSnapshotFrame latestFrame = resource.getLatestPublishedFrame(); - resource.applyPublishedSnapshotFrame(PublishedPhysicsSnapshotFrame.empty( - latestFrame.frameEpoch() + 1L, - latestFrame.worldEpoch())); - - assertEquals(0, resource.getBodySnapshotCount()); - - PhysicsBodySnapshot requiredSnapshot = resource.getBodySnapshot(bodyId); - PhysicsBodySnapshot optionalSnapshot = resource.getBodySnapshotIfRegistered(bodyId); - - assertEquals(new Vector3f(7.0f, 8.0f, 9.0f), requiredSnapshot.position()); - assertNotNull(optionalSnapshot); - assertEquals(new Vector3f(7.0f, 8.0f, 9.0f), optionalSnapshot.position()); - assertEquals(0, resource.getBodySnapshotCount()); - } - - @Test - void bodySnapshotStoreIgnoresUnregisteredSpaceBodies() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:snapshot-store-unregistered-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody registered = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - registered.setPosition(0.0f, 0.0f, 0.0f); - PhysicsBody unregistered = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - unregistered.setPosition(2.0f, 0.0f, 0.0f); - RigidBodyKey registeredId = resource.addBody(space.id(), - registered, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - space.addBody(unregistered); - - assertEquals(1, resource.refreshBodySnapshots()); - assertEquals(1, resource.getBodySnapshotCount(space.id())); - - AtomicInteger snapshots = new AtomicInteger(); - resource.forEachBodySnapshot(space.id(), entry -> { - assertEquals(registeredId, entry.bodyKey()); - assertEquals(PhysicsBodyKind.BODY, entry.kind()); - assertEquals(PhysicsBodyPersistenceMode.PERSISTENT, entry.persistenceMode()); - snapshots.incrementAndGet(); - }); - assertEquals(1, snapshots.get()); - } - - @Test - void asyncBodyAddQueuesWithoutImmediateRegistration() throws Exception { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:async-body-add-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("async-body-add"); - resource.attachOwnerExecutor(owner); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - AtomicReference bodyRef = new AtomicReference<>(); - owner.submitAndDrain(() -> { - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - body.setPosition(1.0f, 2.0f, 3.0f); - bodyRef.set(body); - return PhysicsOwnerSnapshot.empty(); - }); - - CountDownLatch blockerStarted = new CountDownLatch(1); - CountDownLatch releaseBlocker = new CountDownLatch(1); - owner.submitMutation("block async topology", () -> { - blockerStarted.countDown(); - assertTrue(releaseBlocker.await(2, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(blockerStarted.await(2, TimeUnit.SECONDS)); - - RigidBodyKey bodyId = RigidBodyKey.random(); - PhysicsMutationHandle handle = resource.addBodyAsync(bodyId, - space.id(), - bodyRef.get(), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - assertEquals(bodyId, handle.value()); - assertFalse(handle.isDone()); - assertNull(resource.getBodyRegistrationView(bodyId)); - assertFalse(resource.isBodyCreationPending(bodyId)); - - releaseBlocker.countDown(); - pollMutations(owner, 2); - - assertTrue(handle.completedSuccessfully()); - assertFalse(handle.failed()); - assertFalse(resource.isBodyCreationPending(bodyId)); - assertEquals(bodyId, handle.join()); - PhysicsBody registeredBody = resource.callOwner("read registered test body", - () -> resource.getBody(bodyId)); - assertSame(bodyRef.get(), registeredBody); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), - resource.getBodySnapshot(bodyId).position()); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void asyncBodyAddHandleObservesOwnerFailureWithoutPublicationDrain() throws Exception { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:async-body-add-failure-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("async-body-add-failure"); - resource.attachOwnerExecutor(owner); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - AtomicReference bodyRef = new AtomicReference<>(); - owner.submitAndDrain(() -> { - bodyRef.set(space.createBox(0.5f, 0.5f, 0.5f, 1.0f)); - return PhysicsOwnerSnapshot.empty(); - }); - - RigidBodyKey bodyId = RigidBodyKey.random(); - PhysicsMutationHandle handle = resource.addBodyAsync(bodyId, - new SpaceId(Integer.MAX_VALUE), - bodyRef.get(), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - ExecutionException thrown = assertThrows(ExecutionException.class, - () -> handle.completion().toCompletableFuture().get(2, TimeUnit.SECONDS)); - - assertEquals(bodyId, handle.value()); - assertInstanceOf(IllegalArgumentException.class, thrown.getCause()); - assertInstanceOf(IllegalArgumentException.class, handle.failure()); - assertTrue(handle.failed()); - assertFalse(handle.completedSuccessfully()); - assertThrows(IllegalArgumentException.class, handle::throwIfFailed); - assertNull(resource.getBodyRegistrationView(bodyId)); - assertFalse(resource.isBodyCreationPending(bodyId)); - assertEquals(1, owner.pendingMutations()); - - pollMutations(owner, 1); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void runtimePhysicsOwnerRoutingRunsOnAttachedOwner() throws Exception { - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - Thread testThread = Thread.currentThread(); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("runtime-owner-routing"); - resource.attachOwnerExecutor(owner); - - AtomicReference callThread = new AtomicReference<>(); - int value = resource.callOwner("runtime physics owner call", () -> { - assertTrue(owner.isOwnerContext()); - callThread.set(Thread.currentThread()); - return 42; - }); - - assertEquals(42, value); - assertNotSame(testThread, callThread.get()); - - AtomicReference mutationThread = new AtomicReference<>(); - PhysicsMutationHandle handle = resource.enqueueOwnerMutation( - "runtime physics owner mutation", - "reserved-value", - () -> { - assertTrue(owner.isOwnerContext()); - mutationThread.set(Thread.currentThread()); - }); - - assertEquals("reserved-value", handle.value()); - assertEquals("reserved-value", handle.completion().toCompletableFuture() - .get(2, TimeUnit.SECONDS)); - assertTrue(handle.completedSuccessfully()); - assertNotSame(testThread, mutationThread.get()); - pollMutations(owner, 1); - resource.detachOwnerExecutor(owner); - } - } - - @Test - void ownerRoutingRunsInlineWithoutOwnerAndAfterDetach() throws Exception { - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - Thread testThread = Thread.currentThread(); - - AtomicReference inlineMutationThread = new AtomicReference<>(); - resource.runOwnerMutation("inline mutation", () -> inlineMutationThread.set(Thread.currentThread())); - assertSame(testThread, inlineMutationThread.get()); - - Thread inlineCallThread = resource.callOwner("inline call", Thread::currentThread); - assertSame(testThread, inlineCallThread); - - AtomicReference inlineAsyncThread = new AtomicReference<>(); - PhysicsMutationHandle inlineHandle = resource.enqueueOwnerMutation("inline async", - "inline", - () -> inlineAsyncThread.set(Thread.currentThread())); - assertSame(testThread, inlineAsyncThread.get()); - assertTrue(inlineHandle.completedSuccessfully()); - assertEquals("inline", inlineHandle.join()); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-routing-detach"); - resource.attachOwnerExecutor(owner); - Thread ownerCallThread = resource.callOwner("owner call", () -> { - assertTrue(owner.isOwnerContext()); - return Thread.currentThread(); - }); - assertNotSame(testThread, ownerCallThread); - - resource.detachOwnerExecutor(owner); - Thread detachedCallThread = resource.callOwner("detached inline call", - Thread::currentThread); - assertSame(testThread, detachedCallThread); - } - } - - @Test - void liveBackendAccessAssertionRejectsOutsidePhysicsOwner() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:owner-guard-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(4, - Duration.ofSeconds(2L))) { - owner.start("owner-guard"); - resource.attachOwnerExecutor(owner); - - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody body = resource.callOwner("create test body", () -> { - PhysicsBody created = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - resource.addBody(space.id(), - created, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - return created; - }); - - assertThrows(IllegalStateException.class, () -> resource.assertCanAccessLiveBackendDirectly( - "direct test access")); - - resource.runOwnerMutation("move test body", () -> { - resource.assertCanAccessLiveBackendDirectly("owner test access"); - body.setPosition(1.0f, 2.0f, 3.0f); - }); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), - resource.callOwner("read test body position", body::getPosition)); - - resource.detachOwnerExecutor(owner); - } - } - - @Test - void resetRuntimeStatePublishesEmptyFrameAndRejectsPreResetSnapshot() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:reset-snapshot-frame-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey bodyId = resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - PublishedPhysicsSnapshotFrame preResetFrame = resource.capturePublishedSnapshotFrame(10L, - 20L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 30L, - false); - assertEquals(1, preResetFrame.bodyCount()); - assertEquals(1, resource.getBodySnapshotCount()); - - PhysicsRuntimeResetResult reset = - resource.resetRuntimeStateKeepingSpaces("test-world"); - - PublishedPhysicsSnapshotFrame resetFrame = resource.getLatestPublishedFrame(); - assertEquals(1, reset.removedBodies()); - assertEquals(0, resource.getBodySnapshotCount()); - assertEquals(PublishedPhysicsSnapshotFrame.Status.EMPTY, resetFrame.status()); - assertEquals(0, resetFrame.bodyCount()); - assertTrue(resetFrame.worldEpoch() > preResetFrame.worldEpoch()); - - assertEquals(0, resource.applyPublishedSnapshotFrame(preResetFrame)); - assertEquals(0, resource.getBodySnapshotCount()); - assertNull(resource.getBodyRegistrationView(bodyId)); - } - - @Test - void inlineWorldEpochAndVisualInterestCountersDoNotLoseConcurrentUpdates() throws Exception { - int threads = 8; - int iterations = 500; - int expectedUpdates = threads * iterations; - - LegacyLiveHandleTestResource epochResource = new LegacyLiveHandleTestResource(); - runConcurrently(threads, iterations, epochResource::clearBodies); - - assertEquals(expectedUpdates, epochResource.getLatestPublishedFrame().worldEpoch()); - - LegacyLiveHandleTestResource visualInterestResource = new LegacyLiveHandleTestResource(); - Set ticks = ConcurrentHashMap.newKeySet(); - runConcurrently(threads, iterations, - () -> ticks.add(visualInterestResource.advanceVisualInterestTick())); - - assertEquals(expectedUpdates, ticks.size()); - assertEquals(expectedUpdates, ticks.stream().mapToLong(Long::longValue).max().orElseThrow()); - } - - private static void runConcurrently(int threads, - int iterations, - Runnable action) throws Exception { - ExecutorService executor = Executors.newFixedThreadPool(threads); - CountDownLatch start = new CountDownLatch(1); - List> futures = new ArrayList<>(threads); - try { - for (int thread = 0; thread < threads; thread++) { - futures.add(executor.submit(() -> { - assertTrue(start.await(2, TimeUnit.SECONDS)); - for (int iteration = 0; iteration < iterations; iteration++) { - action.run(); - } - return null; - })); - } - start.countDown(); - for (Future future : futures) { - future.get(5, TimeUnit.SECONDS); - } - } finally { - executor.shutdownNow(); - } - assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); - } - - @Nonnull - private static PhysicsBodySnapshot physicsSnapshot(@Nonnull PhysicsBodyType bodyType) { - return new PhysicsBodySnapshot(new Vector3f(), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - bodyType, - false, - false, - 0.0f, - ShapeType.BOX, - new Vector3f(0.5f), - 0.0f, - 0.0f, - PhysicsAxis.Y); - } - - private static void assertForcedCcdRestoreDoesNotAffectReusedBodyId( - @Nonnull LegacyLiveHandleTestResource resource, - @Nonnull PhysicsSpace space, - @Nonnull RigidBodyKey bodyId) { - PhysicsBody replacement = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - replacement.setContinuousCollisionEnabled(true); - resource.addBody(bodyId, - space.id(), - replacement, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PhysicsWorldSettings settings = resource.getWorldSettings(); - settings.setStepMode(PhysicsStepMode.FIXED); - resource.setWorldSettings(settings); - - new PhysicsOwnerStepCommand(resource, 0.05f, false).run(); - - assertTrue(replacement.isContinuousCollisionEnabled()); - resource.destroyBody(bodyId); - } - - private static void pollMutations(TestPhysicsOwnerLane owner, - int expected) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - int completed = 0; - while (System.nanoTime() < deadline) { - completed += owner.pollCompletedMutations(8).size(); - if (completed >= expected) { - return; - } - Thread.sleep(10L); - } - assertEquals(expected, completed); - } - - private static final class ThrowingSyncCleanupResource extends LegacyLiveHandleTestResource { - - @Override - public void clearBodySyncState(@Nonnull Ref entityRef) { - throw new IllegalStateException("sync cleanup failed"); - } - } - - private static final class TestRef extends Ref { - - private final boolean valid; - - private TestRef(boolean valid) { - super(null); - this.valid = valid; - } - - @Override - public boolean isValid() { - return valid; - } - } - -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java index 91bcae63..d6934509 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java @@ -9,9 +9,9 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import javax.annotation.Nonnull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -22,8 +22,8 @@ class PhysicsBodyRegistryTest { void indexesRegistrationsBySpaceWithoutScanningUnrelatedSpaces() { SpaceId firstSpace = new SpaceId(1); SpaceId secondSpace = new SpaceId(2); - RigidBodyKey firstId = RigidBodyKey.of(0L, 1L); - RigidBodyKey secondId = RigidBodyKey.of(0L, 2L); + UUID firstId = new UUID(0L, 1L); + UUID secondId = new UUID(0L, 2L); PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); registry.registerBody(firstId, @@ -37,9 +37,9 @@ void indexesRegistrationsBySpaceWithoutScanningUnrelatedSpaces() { PhysicsBodyKind.TEMPORARY, PhysicsBodyPersistenceMode.RUNTIME_ONLY); - List firstSpaceIds = new ArrayList<>(); + List firstSpaceIds = new ArrayList<>(); registry.forEachRegistration(firstSpace, - registration -> firstSpaceIds.add(registration.bodyKey())); + registration -> firstSpaceIds.add(registration.bodyUuid())); assertEquals(List.of(firstId), firstSpaceIds); assertEquals(1, registry.getRegistrationCount(firstSpace)); @@ -55,7 +55,7 @@ void indexesRegistrationsBySpaceWithoutScanningUnrelatedSpaces() { void reRegisteringSameBodyWithDifferentSpaceIsRejectedWithoutMovingIndex() { SpaceId firstSpace = new SpaceId(1); SpaceId secondSpace = new SpaceId(2); - RigidBodyKey bodyId = RigidBodyKey.of(0L, 3L); + UUID bodyId = new UUID(0L, 3L); PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); registry.registerBody(bodyId, handle(21L), @@ -76,7 +76,7 @@ void reRegisteringSameBodyWithDifferentSpaceIsRejectedWithoutMovingIndex() { @Test void registrationViewsReuseCachedImmutableMetadata() { SpaceId space = new SpaceId(1); - RigidBodyKey bodyId = RigidBodyKey.of(0L, 4L); + UUID bodyId = new UUID(0L, 4L); PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); registry.registerBody(bodyId, handle(31L), @@ -93,7 +93,7 @@ void registrationViewsReuseCachedImmutableMetadata() { assertSame(first, second); assertSame(first, fromCollection); Assertions.assertNotNull(first); - assertEquals(bodyId, first.bodyKey()); + assertEquals(bodyId, first.bodyUuid()); } @Nonnull diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java index 25f3e731..debea942 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java @@ -5,35 +5,25 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsJoint; import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.PhysicsRayHit; -import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSpaceFrame; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Function; -import javax.annotation.Nonnull; +import java.util.UUID; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -42,22 +32,38 @@ class PhysicsBodySnapshotStoreTest { @Test void refreshPassesLazySelectedBodiesToBackend() { - FakePhysicsBackend backend = new FakePhysicsBackend("test:snapshot-store-lazy-refresh"); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace delegate = resource.createLiveSpace(backend.getId(), "test-world"); - PhysicsBody body = delegate.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey bodyId = RigidBodyKey.of(0L, 1L); - resource.addBody(bodyId, - delegate.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - PhysicsSpaceBinding binding = resource.requireSpaceBinding(delegate.id()); + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider("test:snapshot-store-lazy-refresh"); + PhysicsBackendRuntime runtime = provider.createRuntime(); + SpaceId spaceId = new SpaceId(1); + int backendSpaceId = runtime.createSpace(spaceId); + long backendBodyId = runtime.createBody(backendSpaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.BODY_DYNAMIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + UUID bodyId = new UUID(0L, 1L); + PhysicsSpaceBinding binding = new PhysicsSpaceBinding(provider.getId(), + spaceId, + new BackendSpaceHandle(backendSpaceId), + runtime); PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); registry.registerBody(bodyId, - resource.requireBodyRegistration(bodyId).backendBodyHandle(), - delegate.id(), + new BackendBodyHandle(backendBodyId), + spaceId, PhysicsBodyKind.BODY, PhysicsBodyPersistenceMode.RUNTIME_ONLY); PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); @@ -70,7 +76,7 @@ void refreshPassesLazySelectedBodiesToBackend() { @Test void appliesPublishedFramesIncrementallyWithoutReinsertingUnchangedBodies() { SpaceId spaceId = new SpaceId(1); - RigidBodyKey bodyId = RigidBodyKey.of(0L, 1L); + UUID bodyId = new UUID(0L, 1L); PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); PhysicsBodySnapshotStore.ApplyStats firstApply = store.applyPublishedFrame( @@ -92,7 +98,7 @@ void appliesPublishedFramesIncrementallyWithoutReinsertingUnchangedBodies() { @Test void applyPublishedFrameUsesFrameMetadataWithoutLiveRegistry() { SpaceId spaceId = new SpaceId(1); - RigidBodyKey bodyId = RigidBodyKey.of(0L, 12L); + UUID bodyId = new UUID(0L, 12L); PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); PhysicsBodySnapshotStore.ApplyStats apply = store.applyPublishedFrame( @@ -107,7 +113,7 @@ void applyPublishedFrameUsesFrameMetadataWithoutLiveRegistry() { @Test void applyPublishedFrameReusesSnapshotWhenBodyStateIsUnchanged() { SpaceId spaceId = new SpaceId(1); - RigidBodyKey bodyId = RigidBodyKey.of(0L, 2L); + UUID bodyId = new UUID(0L, 2L); PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); store.applyPublishedFrame(frame(spaceId, bodyId, 1L, new Vector3f(1.0f, 2.0f, 3.0f))); @@ -120,8 +126,8 @@ void applyPublishedFrameReusesSnapshotWhenBodyStateIsUnchanged() { @Test void internalNearVisitorExposesSnapshotMetadataWithoutEntryDto() { SpaceId spaceId = new SpaceId(1); - RigidBodyKey nearBodyId = RigidBodyKey.of(0L, 10L); - RigidBodyKey farBodyId = RigidBodyKey.of(0L, 11L); + UUID nearBodyId = new UUID(0L, 10L); + UUID farBodyId = new UUID(0L, 11L); PhysicsBodySnapshot nearSnapshot = snapshotAt(1.0f, 2.0f, 3.0f); PhysicsBodySnapshot farSnapshot = snapshotAt(100.0f, 2.0f, 3.0f); PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); @@ -136,7 +142,7 @@ void internalNearVisitorExposesSnapshotMetadataWithoutEntryDto() { PhysicsBodyKind.TEMPORARY, PhysicsBodyPersistenceMode.PERSISTENT); - List visited = new ArrayList<>(); + List visited = new ArrayList<>(); int candidates = store.forEachIndexedNear(spaceId, new Vector3f(0.0f, 2.0f, 3.0f), 4.0f, @@ -153,7 +159,7 @@ void internalNearVisitorExposesSnapshotMetadataWithoutEntryDto() { } private static PublishedPhysicsSnapshotFrame frame(SpaceId spaceId, - RigidBodyKey bodyId, + UUID bodyId, long frameEpoch, Vector3f position) { PublishedPhysicsBodySnapshot body = new PublishedPhysicsBodySnapshot(bodyId, @@ -204,207 +210,4 @@ private static PhysicsBodySnapshot snapshotAt(float x, float y, float z) { PhysicsAxis.Y); } - @Nonnull - private static BackendBodyHandle handle(long value) { - return new BackendBodyHandle(value); - } - - private static final class RecordingSnapshotSpace implements PhysicsSpace { - - private final PhysicsSpace delegate; - private int selectedBodyCount; - - private RecordingSnapshotSpace(@Nonnull PhysicsSpace delegate) { - this.delegate = delegate; - } - - @Nonnull - @Override - public SpaceId id() { - return delegate.id(); - } - - @Nonnull - @Override - public BackendId backendId() { - return delegate.backendId(); - } - - @Override - public void step(float dt) { - delegate.step(dt); - } - - @Override - public void setGravity(float x, float y, float z) { - delegate.setGravity(x, y, z); - } - - @Nonnull - @Override - public Vector3f getGravity() { - return delegate.getGravity(); - } - - @Override - public void addBody(@Nonnull PhysicsBody body) { - delegate.addBody(body); - } - - @Override - public void removeBody(@Nonnull PhysicsBody body) { - delegate.removeBody(body); - } - - @Nonnull - @Override - public List getBodies() { - return delegate.getBodies(); - } - - @Override - public boolean containsBody(@Nonnull PhysicsBody body) { - return delegate.containsBody(body); - } - - @Override - public void snapshotBodies(@Nonnull Iterable selectedBodies, - @Nonnull Function previousSnapshots, - @Nonnull BiConsumer consumer) { - for (PhysicsBody body : selectedBodies) { - selectedBodyCount++; - consumer.accept(body, PhysicsBodySnapshot.from(body, previousSnapshots.apply(body))); - } - } - - @Nonnull - @Override - public PhysicsBody createStaticPlane(float groundY) { - return delegate.createStaticPlane(groundY); - } - - @Nonnull - @Override - public PhysicsBody createBox(float halfX, float halfY, float halfZ, float mass) { - return delegate.createBox(halfX, halfY, halfZ, mass); - } - - @Nonnull - @Override - public PhysicsBody createBox(@Nonnull Vector3f halfExtents, float mass) { - return delegate.createBox(halfExtents, mass); - } - - @Nonnull - @Override - public PhysicsBody createSphere(float radius, float mass) { - return delegate.createSphere(radius, mass); - } - - @Nonnull - @Override - public PhysicsBody createCapsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return delegate.createCapsule(radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return delegate.createCylinder(radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCone(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return delegate.createCone(radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public Optional raycastClosest(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return delegate.raycastClosest(from, to); - } - - @Nonnull - @Override - public List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return delegate.raycastAll(from, to); - } - - @Nonnull - @Override - public List getContacts() { - return delegate.getContacts(); - } - - @Nonnull - @Override - public PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - return delegate.createFixedJoint(bodyA, bodyB, anchorA, anchorB); - } - - @Nonnull - @Override - public PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - return delegate.createPointJoint(bodyA, bodyB, anchorA, anchorB); - } - - @Nonnull - @Override - public PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - return delegate.createHingeJoint(bodyA, bodyB, anchorA, anchorB, axis); - } - - @Nonnull - @Override - public PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - return delegate.createSliderJoint(bodyA, bodyB, anchorA, anchorB, axis); - } - - @Nonnull - @Override - public PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping) { - return delegate.createSpringJoint(bodyA, bodyB, anchorA, anchorB, restLength, stiffness, damping); - } - - @Override - public void removeJoint(@Nonnull PhysicsJoint joint) { - delegate.removeJoint(joint); - } - - @Nonnull - @Override - public List getJoints() { - return delegate.getJoints(); - } - } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java index e4e95176..f19637c5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java @@ -1,23 +1,27 @@ package dev.hytalemodding.impulse.core.internal.resources.lifecycle; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldLifecycleState; +import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; +import java.util.List; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; @@ -28,51 +32,60 @@ class PhysicsWorldLifecycleStateTest { @Test void stalePublishedFrameIsRejectedAfterWorldEpochChanges() { Fixture fixture = createFixture("stale-frame"); - RigidBodyKey bodyId = registerBox(fixture); - PublishedPhysicsSnapshotFrame staleFrame = fixture.resource.capturePublishedSnapshotFrame(10L, + UUID bodyUuid = registerBox(fixture); + PublishedPhysicsSnapshotFrame staleFrame = fixture.state.capturePublishedSnapshotFrame( + List.of(fixture.binding), + fixture.registry, + 10L, 20L, PublishedPhysicsSnapshotFrame.Status.COMPLETE, 0L, false); - fixture.resource.clearBodies(); + fixture.state.markWorldChanged(fixture.registry, true); - assertEquals(0, fixture.resource.applyPublishedSnapshotFrame(staleFrame)); - assertEquals(0, fixture.resource.getBodySnapshotCount()); - assertNull(fixture.resource.getBodyRegistrationView(bodyId)); - assertEquals(0, fixture.resource.getLatestEventFrame().snapshotPublicationCount()); + assertEquals(0, fixture.state.applyPublishedSnapshotFrame(staleFrame, fixture.registry, 21L)); + assertEquals(0, fixture.state.bodySnapshotCount()); + assertNull(fixture.registry.getPublishedRegistrationView(bodyUuid)); + assertEquals(0, fixture.state.latestEventFrame().snapshotPublicationCount()); } @Test void currentPublishedFrameAppliesReaderSnapshotState() { Fixture fixture = createFixture("current-frame"); - RigidBodyKey bodyId = registerBox(fixture); - long appliedBefore = fixture.resource.getLatestSnapshotAppliedNanos(); + UUID bodyUuid = registerBox(fixture); + long appliedBefore = fixture.state.latestSnapshotAppliedNanos(); - PublishedPhysicsSnapshotFrame frame = fixture.resource.capturePublishedSnapshotFrame(11L, + PublishedPhysicsSnapshotFrame frame = fixture.state.capturePublishedSnapshotFrame( + List.of(fixture.binding), + fixture.registry, + 11L, 21L, PublishedPhysicsSnapshotFrame.Status.COMPLETE, 0L, false); - assertEquals(1, fixture.resource.applyPublishedSnapshotFrame(frame)); - assertEquals(1, fixture.resource.getBodySnapshotCount()); - assertNotNull(fixture.resource.getBodySnapshot(bodyId)); - assertTrue(fixture.resource.getLatestSnapshotAppliedNanos() >= appliedBefore); + assertEquals(1, fixture.state.applyPublishedSnapshotFrame(frame, fixture.registry, 22L)); + assertEquals(1, fixture.state.bodySnapshotCount()); + assertNotNull(fixture.state.getBodySnapshot(bodyUuid)); + assertTrue(fixture.state.latestSnapshotAppliedNanos() >= appliedBefore); } @Test void currentFramePublicationCreatesSnapshotPublicationEvent() { Fixture fixture = createFixture("publication-event"); registerBox(fixture); - PublishedPhysicsSnapshotFrame frame = fixture.resource.capturePublishedSnapshotFrame(14L, + PublishedPhysicsSnapshotFrame frame = fixture.state.capturePublishedSnapshotFrame( + List.of(fixture.binding), + fixture.registry, + 14L, 42L, PublishedPhysicsSnapshotFrame.Status.COMPLETE, 0L, false); - int applied = fixture.resource.applyPublishedSnapshotFrame(frame, 43L); - PhysicsEventFrame eventFrame = fixture.resource.getLatestEventFrame(); + int applied = fixture.state.applyPublishedSnapshotFrame(frame, fixture.registry, 43L); + PhysicsEventFrame eventFrame = fixture.state.latestEventFrame(); PhysicsSnapshotPublicationEvent event = eventFrame.latestSnapshotPublication(); assertEquals(1, applied); @@ -88,25 +101,51 @@ void currentFramePublicationCreatesSnapshotPublicationEvent() { } private static Fixture createFixture(String name) { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:lifecycle-" + name + "-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpace space = resource.createLiveSpace(backend.getId(), - "test-world", - PhysicsSpaceSettings.defaults()); - return new Fixture(resource, space); + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider("test:lifecycle-" + name + "-" + + BACKEND_COUNTER.incrementAndGet()); + PhysicsBackendRuntime runtime = provider.createRuntime(); + SpaceId spaceId = new SpaceId(1); + int backendSpaceId = runtime.createSpace(spaceId); + PhysicsSpaceBinding binding = new PhysicsSpaceBinding(provider.getId(), + spaceId, + new BackendSpaceHandle(backendSpaceId), + runtime); + return new Fixture(new PhysicsWorldLifecycleState(), + new PhysicsBodyRegistry(), + binding); } - private static RigidBodyKey registerBox(Fixture fixture) { - PhysicsBody body = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - return fixture.resource.addBody(fixture.space.id(), - body, + private static UUID registerBox(Fixture fixture) { + long backendBodyId = fixture.binding.runtime().createBody(fixture.binding.backendSpaceHandle().value(), + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + UUID bodyUuid = UUID.randomUUID(); + fixture.registry.registerBody(bodyUuid, + new BackendBodyHandle(backendBodyId), + fixture.binding.spaceId(), PhysicsBodyKind.BODY, PhysicsBodyPersistenceMode.RUNTIME_ONLY); + return bodyUuid; } - private record Fixture(LegacyLiveHandleTestResource resource, - PhysicsSpace space) { + private record Fixture(PhysicsWorldLifecycleState state, + PhysicsBodyRegistry registry, + PhysicsSpaceBinding binding) { } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGatewayTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGatewayTest.java deleted file mode 100644 index 20b7c87a..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerGatewayTest.java +++ /dev/null @@ -1,246 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.junit.jupiter.api.Test; - -class PhysicsOwnerGatewayTest { - - @Test - void routesThroughInterfaceBackedOwnerExecutor() { - PhysicsOwnerGateway gateway = new PhysicsOwnerGateway(); - RecordingOwnerExecutor executor = new RecordingOwnerExecutor(); - AtomicInteger mutations = new AtomicInteger(); - - gateway.attachOwnerExecutor(executor); - - assertFalse(gateway.canAccessLiveBackendDirectly()); - - gateway.run("record routed mutation", mutations::incrementAndGet); - - assertEquals(1, mutations.get()); - assertEquals(1, executor.routedOperations()); - } - - @Test - void nestedOwnerCallsInlineOnlyForSameOwnerContext() { - PhysicsOwnerGateway gateway = new PhysicsOwnerGateway(); - RecordingOwnerExecutor executor = new RecordingOwnerExecutor(); - AtomicInteger nestedMutations = new AtomicInteger(); - - gateway.attachOwnerExecutor(executor); - - gateway.run("outer mutation", () -> { - assertTrue(gateway.canAccessLiveBackendDirectly()); - gateway.run("nested mutation", nestedMutations::incrementAndGet); - }); - - assertEquals(1, nestedMutations.get()); - assertEquals(1, executor.routedOperations()); - } - - @Test - void routesCallAndAsyncOperationsThroughInterfaceBackedOwnerExecutor() throws Exception { - PhysicsOwnerGateway gateway = new PhysicsOwnerGateway(); - RecordingOwnerExecutor executor = new RecordingOwnerExecutor(); - AtomicInteger mutations = new AtomicInteger(); - - gateway.attachOwnerExecutor(executor); - - PhysicsMutationHandle mutation = gateway.enqueue("record async mutation", - "done", - mutations::incrementAndGet); - CompletableFuture asyncCall = gateway.enqueueCall("record async call", - mutations::incrementAndGet); - int syncCall = gateway.call("record sync call", mutations::incrementAndGet); - - assertEquals("done", mutation.join()); - assertEquals(2, asyncCall.get()); - assertEquals(3, syncCall); - assertEquals(3, mutations.get()); - assertEquals(3, executor.routedOperations()); - } - - @Test - void detachOwnerExecutorRestoresDirectExecution() { - PhysicsOwnerGateway gateway = new PhysicsOwnerGateway(); - RecordingOwnerExecutor executor = new RecordingOwnerExecutor(); - AtomicInteger mutations = new AtomicInteger(); - - gateway.attachOwnerExecutor(executor); - gateway.detachOwnerExecutor(executor); - - assertTrue(gateway.canAccessLiveBackendDirectly()); - - gateway.run("direct mutation after detach", mutations::incrementAndGet); - - assertEquals(1, mutations.get()); - assertEquals(0, executor.routedOperations()); - } - - @Test - void routedFailuresPropagateThroughHandlesAndFutures() { - PhysicsOwnerGateway gateway = new PhysicsOwnerGateway(); - RecordingOwnerExecutor executor = new RecordingOwnerExecutor(); - - gateway.attachOwnerExecutor(executor); - - PhysicsMutationHandle mutation = gateway.enqueue("failing mutation", - "value", - () -> { - throw new IllegalStateException("boom"); - }); - CompletableFuture asyncCall = gateway.enqueueCall("failing call", - () -> { - throw new IllegalArgumentException("bad"); - }); - - assertTrue(mutation.failed()); - assertInstanceOf(IllegalStateException.class, mutation.failure()); - ExecutionException thrown = assertThrowsExecution(asyncCall); - assertInstanceOf(IllegalArgumentException.class, thrown.getCause()); - } - - @Test - void routedAsyncRejectionsReturnFailedHandlesAndFutures() { - PhysicsOwnerGateway gateway = new PhysicsOwnerGateway(); - RecordingOwnerExecutor executor = new RecordingOwnerExecutor(); - gateway.attachOwnerExecutor(executor); - executor.rejectAsync = true; - - PhysicsMutationHandle mutation = gateway.enqueue("rejected mutation", - "value", - () -> { - }); - CompletableFuture asyncCall = gateway.enqueueCall("rejected call", () -> "ignored"); - - assertTrue(mutation.failed()); - assertInstanceOf(RejectedExecutionException.class, mutation.failure()); - ExecutionException thrown = assertThrowsExecution(asyncCall); - assertInstanceOf(RejectedExecutionException.class, thrown.getCause()); - } - - @Test - void synchronousWaitGuardRejectsCompletionCallbackContext() { - PhysicsOwnerGateway gateway = new PhysicsOwnerGateway(); - RecordingOwnerExecutor executor = new RecordingOwnerExecutor(); - gateway.attachOwnerExecutor(executor); - - executor.completionCallbackContext = true; - - assertThrows(RejectedExecutionException.class, - () -> gateway.rejectSynchronousCompletionCallbackWait("release control session")); - } - - private static final class RecordingOwnerExecutor implements PhysicsOwnerExecutor { - - private final ThreadLocal ownerContext = ThreadLocal.withInitial(() -> false); - private final AtomicInteger routedOperations = new AtomicInteger(); - private boolean completionCallbackContext; - private boolean rejectAsync; - - @Override - public boolean isOwnerContext() { - return ownerContext.get(); - } - - @Override - public boolean isCompletionCallbackContext() { - return completionCallbackContext; - } - - @Override - public void run(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - routedOperations.incrementAndGet(); - runInOwnerContext(() -> { - mutation.run(); - return null; - }); - } - - @Nonnull - @Override - public PhysicsMutationHandle enqueue(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - if (rejectAsync) { - throw new RejectedExecutionException("forced async rejection"); - } - try { - run(operation, mutation); - return PhysicsMutationHandle.completed(operation, value); - } catch (Throwable throwable) { - return PhysicsMutationHandle.failed(operation, value, throwable); - } - } - - @Nonnull - @Override - public CompletableFuture enqueueCall(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - if (rejectAsync) { - throw new RejectedExecutionException("forced async call rejection"); - } - try { - return CompletableFuture.completedFuture(call(operation, callable)); - } catch (Throwable throwable) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(throwable); - return completion; - } - } - - @Nonnull - @Override - public T call(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - routedOperations.incrementAndGet(); - return Objects.requireNonNull(runInOwnerContext(callable)); - } - - int routedOperations() { - return routedOperations.get(); - } - - @Nullable - private T runInOwnerContext(@Nonnull PhysicsOwnerCallable callable) { - boolean previous = ownerContext.get(); - ownerContext.set(true); - try { - return callable.call(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("owner operation failed", exception); - } finally { - ownerContext.set(previous); - } - } - } - - @Nonnull - private static ExecutionException assertThrowsExecution(@Nonnull CompletableFuture future) { - try { - future.get(); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - throw new AssertionError("Interrupted while waiting for future", exception); - } catch (ExecutionException exception) { - return exception; - } - throw new AssertionError("Expected future to fail"); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneSchedulerTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneSchedulerTest.java deleted file mode 100644 index 0006e6d9..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerLaneSchedulerTest.java +++ /dev/null @@ -1,1236 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; - -class PhysicsOwnerLaneSchedulerTest { - - private static final Duration CLOSE_TIMEOUT = Duration.ofMillis(250L); - private static final long SHORT_TIMEOUT_MILLIS = 100L; - private static final long TIMEOUT_MILLIS = 2_000L; - - @Test - void defaultPoolSizeLeavesCpuHeadroomForBackendSolvers() { - assertEquals(1, PhysicsOwnerLaneScheduler.DEFAULT_POOL_SIZE); - } - - @Test - void sameLaneSerializesQueuedWorkEvenWhenPoolHasMultipleThreads() throws Exception { - CountDownLatch firstStarted = new CountDownLatch(1); - CountDownLatch releaseFirst = new CountDownLatch(1); - CountDownLatch secondStarted = new CountDownLatch(1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("same-lane"); - - lane.submitMutation("first", () -> { - firstStarted.countDown(); - assertTrue(releaseFirst.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(firstStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - lane.submitMutation("second", () -> { - secondStarted.countDown(); - return PhysicsOwnerSnapshot.empty(); - }); - - assertFalse(secondStarted.await(SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - releaseFirst.countDown(); - assertTrue(secondStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertEquals(2, pollMutationCompletions(lane, 2).size()); - } finally { - releaseFirst.countDown(); - } - } - - @Test - void differentLanesRunInParallelWhenPoolSizeAllowsIt() throws Exception { - CountDownLatch laneOneStarted = new CountDownLatch(1); - CountDownLatch releaseLaneOne = new CountDownLatch(1); - CountDownLatch laneTwoStarted = new CountDownLatch(1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource laneOne = scheduler.createLane(); - PhysicsOwnerLaneResource laneTwo = scheduler.createLane(); - laneOne.start("lane-one"); - laneTwo.start("lane-two"); - - laneOne.submitMutation("block lane one", () -> { - laneOneStarted.countDown(); - assertTrue(releaseLaneOne.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(laneOneStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - laneTwo.submitMutation("run lane two", () -> { - laneTwoStarted.countDown(); - return PhysicsOwnerSnapshot.empty(); - }); - - assertTrue(laneTwoStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - releaseLaneOne.countDown(); - assertEquals(1, pollMutationCompletions(laneOne, 1).size()); - assertEquals(1, pollMutationCompletions(laneTwo, 1).size()); - } finally { - releaseLaneOne.countDown(); - } - } - - @Test - void mutationsRunFifoWithinOneLane() throws Exception { - CountDownLatch activeStarted = new CountDownLatch(1); - CountDownLatch releaseActive = new CountDownLatch(1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - List order = Collections.synchronizedList(new ArrayList<>()); - lane.start("mutation-fifo"); - - lane.submitMutation("active", () -> { - order.add("active"); - activeStarted.countDown(); - assertTrue(releaseActive.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - lane.submitMutation("queued one", () -> { - order.add("queued-1"); - return PhysicsOwnerSnapshot.empty(); - }); - lane.submitMutation("queued two", () -> { - order.add("queued-2"); - return PhysicsOwnerSnapshot.empty(); - }); - - releaseActive.countDown(); - assertEquals(3, pollMutationCompletions(lane, 3).size()); - - assertEquals(List.of("active", "queued-1", "queued-2"), order); - } finally { - releaseActive.countDown(); - } - } - - @Test - void submittedStepDrainsPreCutoffMutationBacklogAfterActiveCommand() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch preCutoffMutationStarted = new CountDownLatch(1); - CountDownLatch releasePreCutoffMutation = new CountDownLatch(1); - CountDownLatch postCutoffMutationStarted = new CountDownLatch(1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("strict-pre-step-drain"); - - lane.submitMutation("active mutation", () -> { - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - lane.submitMutation("pre-cutoff mutation", () -> { - preCutoffMutationStarted.countDown(); - assertTrue(releasePreCutoffMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - - PhysicsOwnerStepCommand step = new PhysicsOwnerStepCommand( - new LegacyLiveHandleTestResource(), - 0.05f, - false, - 1L, - 1L); - assertTrue(lane.submitStepIfIdle(step)); - - lane.submitMutation("post-cutoff mutation", () -> { - postCutoffMutationStarted.countDown(); - return PhysicsOwnerSnapshot.empty(); - }); - - releaseActiveMutation.countDown(); - assertTrue(preCutoffMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertNull(pollStepCompletion(lane, Duration.ofMillis(SHORT_TIMEOUT_MILLIS)), - "step must not complete before queued pre-cutoff mutations drain"); - assertFalse(postCutoffMutationStarted.await(SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS), - "post-cutoff mutations must not run before the accepted step"); - - releasePreCutoffMutation.countDown(); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNotNull(completedStep, - "pre-cutoff mutations must drain before the accepted physics step"); - assertTrue(completedStep.completedSuccessfully()); - assertTrue(postCutoffMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertEquals(3, pollMutationCompletions(lane, 3).size()); - } finally { - releaseActiveMutation.countDown(); - releasePreCutoffMutation.countDown(); - } - } - - @Test - void submittedStepDrainsAllPreCutoffMutationsBeforePostCutoffBacklog() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch firstPreCutoffStarted = new CountDownLatch(1); - CountDownLatch releaseFirstPreCutoff = new CountDownLatch(1); - CountDownLatch secondPreCutoffStarted = new CountDownLatch(1); - CountDownLatch releaseSecondPreCutoff = new CountDownLatch(1); - CountDownLatch postCutoffStarted = new CountDownLatch(1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("strict-pre-step-drain-multiple"); - - lane.submitMutation("active mutation", () -> { - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - lane.submitMutation("first pre-cutoff mutation", () -> { - firstPreCutoffStarted.countDown(); - assertTrue(releaseFirstPreCutoff.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - lane.submitMutation("second pre-cutoff mutation", () -> { - secondPreCutoffStarted.countDown(); - assertTrue(releaseSecondPreCutoff.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - - assertTrue(lane.submitStepIfIdle(new PhysicsOwnerStepCommand( - new LegacyLiveHandleTestResource(), - 0.05f, - false, - 1L, - 1L))); - - lane.submitMutation("post-cutoff mutation", () -> { - postCutoffStarted.countDown(); - return PhysicsOwnerSnapshot.empty(); - }); - - releaseActiveMutation.countDown(); - assertTrue(firstPreCutoffStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertNull(pollStepCompletion(lane, Duration.ofMillis(SHORT_TIMEOUT_MILLIS))); - assertFalse(postCutoffStarted.await(SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - releaseFirstPreCutoff.countDown(); - assertTrue(secondPreCutoffStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertNull(pollStepCompletion(lane, Duration.ofMillis(SHORT_TIMEOUT_MILLIS))); - assertFalse(postCutoffStarted.await(SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - releaseSecondPreCutoff.countDown(); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNotNull(completedStep); - assertTrue(completedStep.completedSuccessfully()); - assertEquals(2, completedStep.preStepDrainedMutations()); - assertEquals(1, completedStep.lateMutationBacklogAtStep()); - assertTrue(postCutoffStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertEquals(4, pollMutationCompletions(lane, 4).size()); - } finally { - releaseActiveMutation.countDown(); - releaseFirstPreCutoff.countDown(); - releaseSecondPreCutoff.countDown(); - } - } - - @Test - void submittedStepDoesNotWaitForPostCutoffMutationBacklog() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch postCutoffMutationStarted = new CountDownLatch(1); - CountDownLatch releasePostCutoffMutation = new CountDownLatch(1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("post-cutoff-deferred"); - - lane.submitMutation("active mutation", () -> { - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - PhysicsOwnerStepCommand step = new PhysicsOwnerStepCommand( - new LegacyLiveHandleTestResource(), - 0.05f, - false, - 1L, - 1L); - assertTrue(lane.submitStepIfIdle(step)); - - lane.submitMutation("post-cutoff mutation", () -> { - postCutoffMutationStarted.countDown(); - assertTrue(releasePostCutoffMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - - releaseActiveMutation.countDown(); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNotNull(completedStep, - "post-cutoff mutations must not delay an already-accepted physics step"); - assertTrue(completedStep.completedSuccessfully()); - assertTrue(postCutoffMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - releasePostCutoffMutation.countDown(); - assertEquals(2, pollMutationCompletions(lane, 2).size()); - } finally { - releaseActiveMutation.countDown(); - releasePostCutoffMutation.countDown(); - } - } - - @Test - void failedPreCutoffMutationIsReportedAndDoesNotPreventAcceptedStep() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch failingPreCutoffStarted = new CountDownLatch(1); - RuntimeException expectedFailure = new RuntimeException("pre-cutoff boom"); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("strict-pre-step-drain-failure"); - - lane.submitMutation("active mutation", () -> { - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - lane.submitMutation("failing pre-cutoff mutation", () -> { - failingPreCutoffStarted.countDown(); - throw expectedFailure; - }); - - assertTrue(lane.submitStepIfIdle(new PhysicsOwnerStepCommand( - new LegacyLiveHandleTestResource(), - 0.05f, - false, - 1L, - 1L))); - - releaseActiveMutation.countDown(); - assertTrue(failingPreCutoffStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNotNull(completedStep); - assertTrue(completedStep.completedSuccessfully()); - assertEquals(1, completedStep.preStepDrainedMutations()); - assertTrue(completedStep.preStepDrainRunNanos() >= 0L); - - List completions = pollMutationCompletions(lane, 2); - assertEquals(2, completions.size()); - assertTrue(completions.get(0).completedSuccessfully()); - assertEquals(expectedFailure, completions.get(1).executionFailure()); - } finally { - releaseActiveMutation.countDown(); - } - } - - @Test - void completionCallbackCanWaitForQueuedMutationWithoutHoldingOwnerWorker() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch preCutoffMutationStarted = new CountDownLatch(1); - CountDownLatch callbackFinished = new CountDownLatch(1); - AtomicReference callbackFailure = new AtomicReference<>(); - List order = Collections.synchronizedList(new ArrayList<>()); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("completion-callback-wait"); - - lane.submitMutation("active mutation", () -> { - order.add("active"); - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - PhysicsMutationHandle preCutoff = lane.submitMutation("pre-cutoff mutation", - () -> { - order.add("pre"); - preCutoffMutationStarted.countDown(); - return PhysicsOwnerSnapshot.empty(); - }); - preCutoff.completion().whenComplete((ignored, failure) -> { - try { - assertNull(failure); - PhysicsMutationHandle callbackMutation = lane.enqueue("callback mutation", - null, - () -> order.add("callback")); - awaitHandle(callbackMutation); - } catch (Throwable throwable) { - callbackFailure.set(throwable); - } finally { - callbackFinished.countDown(); - } - }); - - assertTrue(lane.submitStepIfIdle(new PhysicsOwnerStepCommand( - new RecordingStepResource(order), - 0.05f, - false, - 1L, - 1L))); - - releaseActiveMutation.countDown(); - assertTrue(preCutoffMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertTrue(callbackFinished.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNull(callbackFailure.get()); - assertNotNull(completedStep); - assertTrue(completedStep.completedSuccessfully()); - assertEquals(List.of("active", "pre", "step", "callback"), order); - assertEquals(3, pollMutationCompletions(lane, 3).size()); - } finally { - releaseActiveMutation.countDown(); - } - } - - @Test - void ownerBridgeCallAsyncCompletionRunsOutsideOwnerContextAndQueuesAfterAcceptedStep() - throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch bridgeCallStarted = new CountDownLatch(1); - CountDownLatch callbackObserved = new CountDownLatch(1); - CountDownLatch callbackMutationStarted = new CountDownLatch(1); - AtomicReference callbackFailure = new AtomicReference<>(); - AtomicReference completedValue = new AtomicReference<>(); - List order = Collections.synchronizedList(new ArrayList<>()); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("owner-bridge-call-async-completion"); - - lane.submitMutation("active mutation", () -> { - order.add("active"); - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - CompletableFuture bridgeCall = PhysicsOwnerBridge.callAsync(lane, - "bridge async call", - () -> { - order.add("bridge"); - bridgeCallStarted.countDown(); - return "value"; - }); - bridgeCall.whenComplete((value, failure) -> { - try { - assertNull(failure); - assertFalse(lane.isOwnerContext()); - completedValue.set(value); - lane.enqueue("callback mutation", null, () -> { - order.add("callback"); - callbackMutationStarted.countDown(); - }); - } catch (Throwable throwable) { - callbackFailure.set(throwable); - } finally { - callbackObserved.countDown(); - } - }); - - assertTrue(lane.submitStepIfIdle(new PhysicsOwnerStepCommand( - new RecordingStepResource(order), - 0.05f, - false, - 1L, - 1L))); - - releaseActiveMutation.countDown(); - assertTrue(bridgeCallStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertTrue(callbackObserved.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNull(callbackFailure.get()); - assertEquals("value", completedValue.get()); - assertNotNull(completedStep); - assertTrue(completedStep.completedSuccessfully()); - assertTrue(callbackMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertEquals(List.of("active", "bridge", "step", "callback"), order); - assertEquals(2, pollMutationCompletions(lane, 2).size()); - } finally { - releaseActiveMutation.countDown(); - } - } - - @Test - void enqueueCallCompletionRunsOutsideOwnerContextAndQueuesAfterAcceptedStep() - throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch callStarted = new CountDownLatch(1); - CountDownLatch callbackObserved = new CountDownLatch(1); - CountDownLatch callbackMutationStarted = new CountDownLatch(1); - AtomicReference callbackFailure = new AtomicReference<>(); - AtomicReference completedValue = new AtomicReference<>(); - List order = Collections.synchronizedList(new ArrayList<>()); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("enqueue-call-completion"); - - lane.submitMutation("active mutation", () -> { - order.add("active"); - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - CompletableFuture call = lane.enqueueCall("queued call", () -> { - order.add("call"); - callStarted.countDown(); - return "value"; - }); - call.whenComplete((value, failure) -> { - try { - assertNull(failure); - assertFalse(lane.isOwnerContext()); - completedValue.set(value); - lane.enqueue("callback mutation", null, () -> { - order.add("callback"); - callbackMutationStarted.countDown(); - }); - } catch (Throwable throwable) { - callbackFailure.set(throwable); - } finally { - callbackObserved.countDown(); - } - }); - - assertTrue(lane.submitStepIfIdle(new PhysicsOwnerStepCommand( - new RecordingStepResource(order), - 0.05f, - false, - 1L, - 1L))); - - releaseActiveMutation.countDown(); - assertTrue(callStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertTrue(callbackObserved.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNull(callbackFailure.get()); - assertEquals("value", completedValue.get()); - assertNotNull(completedStep); - assertTrue(completedStep.completedSuccessfully()); - assertTrue(callbackMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertEquals(List.of("active", "call", "step", "callback"), order); - assertEquals(2, pollMutationCompletions(lane, 2).size()); - } finally { - releaseActiveMutation.countDown(); - } - } - - @Test - void completionCallbackSubmittedMutationRunsAfterAcceptedStep() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch preCutoffMutationStarted = new CountDownLatch(1); - CountDownLatch callbackObserved = new CountDownLatch(1); - CountDownLatch callbackMutationStarted = new CountDownLatch(1); - AtomicReference callbackFailure = new AtomicReference<>(); - List order = Collections.synchronizedList(new ArrayList<>()); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("completion-callback-reentrant-async"); - - lane.submitMutation("active mutation", () -> { - order.add("active"); - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - PhysicsMutationHandle preCutoff = lane.submitMutation("pre-cutoff mutation", - () -> { - order.add("pre"); - preCutoffMutationStarted.countDown(); - return PhysicsOwnerSnapshot.empty(); - }); - preCutoff.completion().whenComplete((ignored, failure) -> { - try { - assertNull(failure); - assertFalse(lane.isOwnerContext()); - lane.enqueue("callback mutation", null, () -> { - order.add("callback"); - callbackMutationStarted.countDown(); - }); - } catch (Throwable throwable) { - callbackFailure.set(throwable); - } finally { - callbackObserved.countDown(); - } - }); - - assertTrue(lane.submitStepIfIdle(new PhysicsOwnerStepCommand( - new RecordingStepResource(order), - 0.05f, - false, - 1L, - 1L))); - - releaseActiveMutation.countDown(); - assertTrue(preCutoffMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertTrue(callbackObserved.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNull(callbackFailure.get()); - assertNotNull(completedStep); - assertTrue(completedStep.completedSuccessfully()); - assertTrue(callbackMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertEquals(List.of("active", "pre", "step", "callback"), order); - assertEquals(3, pollMutationCompletions(lane, 3).size()); - } finally { - releaseActiveMutation.countDown(); - } - } - - @Test - void completionCallbackSynchronousOwnerWaitIsRejected() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch callbackObserved = new CountDownLatch(1); - AtomicReference syncFailure = new AtomicReference<>(); - AtomicReference callFailure = new AtomicReference<>(); - AtomicReference drainFailure = new AtomicReference<>(); - AtomicReference crossLaneSyncFailure = new AtomicReference<>(); - AtomicBoolean callRan = new AtomicBoolean(); - AtomicBoolean drainRan = new AtomicBoolean(); - List order = Collections.synchronizedList(new ArrayList<>()); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - PhysicsOwnerLaneResource otherLane = scheduler.createLane(); - lane.start("completion-callback-sync-reject"); - otherLane.start("completion-callback-cross-sync-reject"); - - lane.submitMutation("active mutation", () -> { - order.add("active"); - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - PhysicsMutationHandle preCutoff = lane.submitMutation("pre-cutoff mutation", - () -> { - order.add("pre"); - return PhysicsOwnerSnapshot.empty(); - }); - preCutoff.completion().whenComplete((ignored, failure) -> { - try { - assertNull(failure); - lane.run("sync callback mutation", () -> order.add("sync-callback")); - } catch (Throwable throwable) { - syncFailure.set(throwable); - } - try { - lane.call("sync callback call", () -> { - callRan.set(true); - return "rejected"; - }); - } catch (Throwable throwable) { - callFailure.set(throwable); - } - try { - lane.submitAndDrain(() -> { - drainRan.set(true); - return PhysicsOwnerSnapshot.empty(); - }); - } catch (Throwable throwable) { - drainFailure.set(throwable); - } - try { - otherLane.run("cross-lane sync callback mutation", - () -> order.add("cross-sync-callback")); - } catch (Throwable throwable) { - crossLaneSyncFailure.set(throwable); - } finally { - callbackObserved.countDown(); - } - }); - - assertTrue(lane.submitStepIfIdle(new PhysicsOwnerStepCommand( - new RecordingStepResource(order), - 0.05f, - false, - 1L, - 1L))); - - releaseActiveMutation.countDown(); - assertTrue(callbackObserved.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNotNull(syncFailure.get()); - assertInstanceOf(RejectedExecutionException.class, syncFailure.get()); - assertNotNull(callFailure.get()); - assertInstanceOf(RejectedExecutionException.class, callFailure.get()); - assertFalse(callRan.get()); - assertNotNull(drainFailure.get()); - assertInstanceOf(RejectedExecutionException.class, drainFailure.get()); - assertFalse(drainRan.get()); - assertNotNull(crossLaneSyncFailure.get()); - assertInstanceOf(RejectedExecutionException.class, crossLaneSyncFailure.get()); - assertNotNull(completedStep); - assertTrue(completedStep.completedSuccessfully()); - assertEquals(List.of("active", "pre", "step"), order); - assertEquals(2, pollMutationCompletions(lane, 2).size()); - assertTrue(otherLane.pollCompletedMutations(1).isEmpty()); - } finally { - releaseActiveMutation.countDown(); - } - } - - @Test - void completionCallbackCloseIsRejectedWithoutClosingLane() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch callbackObserved = new CountDownLatch(1); - AtomicReference callbackFailure = new AtomicReference<>(); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("completion-callback-close-reject"); - - lane.submitMutation("active mutation", () -> { - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - PhysicsMutationHandle mutation = lane.submitMutation("complete then close", - PhysicsOwnerSnapshot::empty); - mutation.completion().whenComplete((ignored, failure) -> { - try { - assertNull(failure); - assertFalse(lane.isOwnerContext()); - assertThrows(RejectedExecutionException.class, lane::close); - } catch (Throwable throwable) { - callbackFailure.set(throwable); - } finally { - callbackObserved.countDown(); - } - }); - - releaseActiveMutation.countDown(); - assertTrue(callbackObserved.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - assertNull(callbackFailure.get()); - assertFalse(lane.isClosed()); - assertEquals(2, pollMutationCompletions(lane, 2).size()); - } finally { - releaseActiveMutation.countDown(); - } - } - - @Test - void schedulerCloseWaitsForCompletionCallbacks() throws Exception { - CountDownLatch callbackStarted = new CountDownLatch(1); - CountDownLatch releaseCallback = new CountDownLatch(1); - AtomicBoolean closeReturned = new AtomicBoolean(); - PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - Duration.ofSeconds(2L)); - try { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("close-waits-for-completion"); - - PhysicsMutationHandle mutation = lane.submitMutation("blocked callback", - PhysicsOwnerSnapshot::empty); - mutation.completion().whenComplete((ignored, failure) -> { - callbackStarted.countDown(); - try { - assertTrue(releaseCallback.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - } - }); - - assertTrue(callbackStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - CompletableFuture close = CompletableFuture.runAsync(() -> { - scheduler.close(); - closeReturned.set(true); - }); - - assertFalse(closeReturned.get()); - assertFalse(close.isDone()); - - releaseCallback.countDown(); - close.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - - assertTrue(closeReturned.get()); - } finally { - releaseCallback.countDown(); - if (!closeReturned.get()) { - scheduler.close(); - } - } - } - - @Test - void laneCloseWaitsForCompletionCallbacks() throws Exception { - CountDownLatch callbackStarted = new CountDownLatch(1); - CountDownLatch releaseCallback = new CountDownLatch(1); - AtomicBoolean closeReturned = new AtomicBoolean(); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - Duration.ofSeconds(2L))) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("lane-close-waits-for-completion"); - - PhysicsMutationHandle mutation = lane.submitMutation("blocked lane callback", - PhysicsOwnerSnapshot::empty); - mutation.completion().whenComplete((ignored, failure) -> { - callbackStarted.countDown(); - try { - assertTrue(releaseCallback.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - } - }); - - assertTrue(callbackStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - CompletableFuture close = CompletableFuture.runAsync(() -> { - lane.close(); - closeReturned.set(true); - }); - - assertFalse(closeReturned.get()); - assertFalse(close.isDone()); - - releaseCallback.countDown(); - close.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - - assertTrue(closeReturned.get()); - assertTrue(lane.isClosed()); - } finally { - releaseCallback.countDown(); - } - } - - @Test - void schedulerCloseFromCompletionCallbackIsRejected() throws Exception { - CountDownLatch mutationStarted = new CountDownLatch(1); - CountDownLatch releaseMutation = new CountDownLatch(1); - CountDownLatch callbackObserved = new CountDownLatch(1); - AtomicReference callbackFailure = new AtomicReference<>(); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("scheduler-close-callback-reject"); - - PhysicsMutationHandle mutation = lane.submitMutation("complete then scheduler close", - () -> { - mutationStarted.countDown(); - assertTrue(releaseMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(mutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - mutation.completion().whenComplete((ignored, failure) -> { - try { - assertNull(failure); - assertThrows(RejectedExecutionException.class, scheduler::close); - } catch (Throwable throwable) { - callbackFailure.set(throwable); - } finally { - callbackObserved.countDown(); - } - }); - - releaseMutation.countDown(); - assertTrue(callbackObserved.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - assertNull(callbackFailure.get()); - assertEquals(1, pollMutationCompletions(lane, 1).size()); - } finally { - releaseMutation.countDown(); - } - } - - @Test - void ownerContextCloseIsRejectedWithoutClosingLane() throws Exception { - AtomicBoolean rejectionObserved = new AtomicBoolean(); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("owner-context-close-reject"); - - PhysicsMutationHandle mutation = lane.enqueue("close from owner", null, () -> { - assertTrue(lane.isOwnerContext()); - assertThrows(RejectedExecutionException.class, lane::close); - rejectionObserved.set(true); - }); - - awaitHandle(mutation); - - assertTrue(rejectionObserved.get()); - assertFalse(lane.isClosed()); - assertEquals(1, pollMutationCompletions(lane, 1).size()); - } - } - - @Test - void completedStepReportsPreStepDrainBackpressure() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch preCutoffMutationStarted = new CountDownLatch(1); - CountDownLatch releasePreCutoffMutation = new CountDownLatch(1); - CountDownLatch releasePostCutoffMutation = new CountDownLatch(1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("pre-step-drain-stats"); - - lane.submitMutation("active mutation", () -> { - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - lane.submitMutation("pre-cutoff mutation", () -> { - preCutoffMutationStarted.countDown(); - assertTrue(releasePreCutoffMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - - PhysicsOwnerStepCommand step = new PhysicsOwnerStepCommand( - new LegacyLiveHandleTestResource(), - 0.05f, - false, - 1L, - 1L); - assertTrue(lane.submitStepIfIdle(step)); - - lane.submitMutation("post-cutoff mutation", () -> { - assertTrue(releasePostCutoffMutation.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - - releaseActiveMutation.countDown(); - assertTrue(preCutoffMutationStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - releasePreCutoffMutation.countDown(); - PhysicsOwnerStepCompletion completedStep = pollStepCompletion(lane, - Duration.ofMillis(TIMEOUT_MILLIS)); - - assertNotNull(completedStep); - assertEquals(1, completedStep.preStepDrainedMutations()); - assertTrue(completedStep.preStepDrainRunNanos() > 0L); - assertEquals(1, completedStep.lateMutationBacklogAtStep()); - - releasePostCutoffMutation.countDown(); - assertEquals(2, pollMutationCompletions(lane, 2).size()); - } finally { - releaseActiveMutation.countDown(); - releasePreCutoffMutation.countDown(); - releasePostCutoffMutation.countDown(); - } - } - - @Test - void closedLaneRejectsNewWorkAndClosedSchedulerRejectsNewLanes() { - PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 2, - CLOSE_TIMEOUT); - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("close-reject"); - - lane.close(); - - assertTrue(lane.isClosed()); - assertFalse(lane.isStarted()); - assertThrows(RejectedExecutionException.class, - () -> lane.submitMutation("after close", PhysicsOwnerSnapshot::empty)); - assertThrows(RejectedExecutionException.class, - () -> lane.call("after close", () -> "rejected")); - - scheduler.close(); - assertThrows(RejectedExecutionException.class, scheduler::createLane); - } - - @Test - void nestedSameLaneOwnerCallsRunInlineWithoutQueuing() throws Exception { - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - List order = Collections.synchronizedList(new ArrayList<>()); - lane.start("nested-inline"); - - PhysicsMutationHandle outer = lane.enqueue("outer", null, () -> { - assertTrue(lane.isOwnerContext()); - order.add("outer-before"); - lane.run("nested run", () -> order.add("nested-run")); - String nestedValue = lane.call("nested call", () -> { - order.add("nested-call"); - return "done"; - }); - order.add(nestedValue); - }); - - awaitHandle(outer); - - assertEquals(List.of("outer-before", "nested-run", "nested-call", "done"), order); - assertEquals(1, pollMutationCompletions(lane, 1).size()); - } - } - - @Test - void completedMutationFutureIsNotReportedAsPendingCommand() throws Exception { - CountDownLatch commandStarted = new CountDownLatch(1); - CountDownLatch releaseCommand = new CountDownLatch(1); - CountDownLatch completionObserved = new CountDownLatch(1); - AtomicInteger pendingAtCompletion = new AtomicInteger(-1); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("completion-pending"); - - PhysicsMutationHandle handle = lane.submitMutation("blocked", () -> { - commandStarted.countDown(); - assertTrue(releaseCommand.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(commandStarted.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - handle.completion().whenComplete((ignored, failure) -> { - pendingAtCompletion.set(lane.pendingCommands()); - completionObserved.countDown(); - }); - - releaseCommand.countDown(); - assertTrue(completionObserved.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - - assertEquals(0, pendingAtCompletion.get()); - } finally { - releaseCommand.countDown(); - } - } - - @Test - void synchronousOwnerCallFromAnotherLaneContextIsRejected() throws Exception { - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource laneOne = scheduler.createLane(); - PhysicsOwnerLaneResource laneTwo = scheduler.createLane(); - AtomicBoolean crossLaneCallRan = new AtomicBoolean(); - laneOne.start("lane-one"); - laneTwo.start("lane-two"); - - PhysicsMutationHandle outer = laneOne.enqueue("outer", null, () -> { - assertTrue(laneOne.isOwnerContext()); - assertThrows(RejectedExecutionException.class, - () -> laneTwo.call("cross-lane call", () -> { - crossLaneCallRan.set(true); - return "not allowed"; - })); - }); - - awaitHandle(outer); - - assertFalse(crossLaneCallRan.get()); - assertEquals(1, pollMutationCompletions(laneOne, 1).size()); - assertTrue(laneTwo.pollCompletedMutations(1).isEmpty()); - } - } - - @Test - void pollCompletedMutationsReturnsBoundedFifoBatches() throws Exception { - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(2, - 8, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("bounded-poll"); - - PhysicsMutationHandle first = lane.submitMutation("first", - PhysicsOwnerSnapshot::empty); - PhysicsMutationHandle second = lane.submitMutation("second", - PhysicsOwnerSnapshot::empty); - PhysicsMutationHandle third = lane.submitMutation("third", - PhysicsOwnerSnapshot::empty); - awaitHandle(first); - awaitHandle(second); - awaitHandle(third); - - List firstBatch = lane.pollCompletedMutations(2); - List secondBatch = lane.pollCompletedMutations(2); - - assertEquals(List.of("first", "second"), operations(firstBatch)); - assertEquals(List.of("third"), operations(secondBatch)); - assertTrue(lane.pollCompletedMutations(2).isEmpty()); - } - } - - @Test - void unpolledMutationCompletionsApplyQueueBackpressure() throws Exception { - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 2, - CLOSE_TIMEOUT)) { - PhysicsOwnerLaneResource lane = scheduler.createLane(); - lane.start("pending-completion-backpressure"); - - PhysicsMutationHandle first = lane.submitMutation("first", - PhysicsOwnerSnapshot::empty); - PhysicsMutationHandle second = lane.submitMutation("second", - PhysicsOwnerSnapshot::empty); - awaitHandle(first); - awaitHandle(second); - - assertThrows(RejectedExecutionException.class, - () -> lane.submitMutation("third", PhysicsOwnerSnapshot::empty)); - - assertEquals(2, pollMutationCompletions(lane, 2).size()); - PhysicsMutationHandle third = lane.submitMutation("third", - PhysicsOwnerSnapshot::empty); - awaitHandle(third); - assertEquals(1, pollMutationCompletions(lane, 1).size()); - } - } - - private static List pollMutationCompletions( - @Nonnull PhysicsOwnerLaneResource lane, - int expected) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(TIMEOUT_MILLIS); - List completions = new ArrayList<>(expected); - while (System.nanoTime() < deadline) { - completions.addAll(lane.pollCompletedMutations(expected - completions.size())); - if (completions.size() == expected) { - return completions; - } - Thread.sleep(10L); - } - return completions; - } - - private static void awaitHandle(@Nonnull PhysicsMutationHandle handle) - throws InterruptedException, ExecutionException, TimeoutException { - CompletableFuture future = handle.completion().toCompletableFuture(); - future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } - - private static List operations( - @Nonnull List completions) { - return completions.stream() - .map(PhysicsOwnerMutationCompletion::operation) - .toList(); - } - - private static PhysicsOwnerStepCompletion pollStepCompletion( - @Nonnull PhysicsOwnerLaneResource lane, - @Nonnull Duration timeout) throws InterruptedException { - long deadline = System.nanoTime() + timeout.toNanos(); - PhysicsOwnerStepCompletion completion = null; - while (System.nanoTime() < deadline) { - completion = lane.pollCompletedStep(); - if (completion != null) { - return completion; - } - Thread.sleep(10L); - } - return lane.pollCompletedStep(); - } - - private static final class RecordingStepResource extends LegacyLiveHandleTestResource { - - @Nonnull - private final List order; - - private RecordingStepResource(@Nonnull List order) { - this.order = order; - } - - @Nonnull - @Override - public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame(long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled, - @Nonnull List physicsEvents, - int droppedBackendEventCount) { - order.add("step"); - return super.capturePublishedSnapshotFrame(stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled, - physicsEvents, - droppedBackendEventCount); - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommandTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommandTest.java deleted file mode 100644 index 5607069c..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/PhysicsOwnerStepCommandTest.java +++ /dev/null @@ -1,981 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBackendEventSink; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsRayHit; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.capability.PhysicsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsContinuousCollisionCapability; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.checkerframework.checker.nullness.compatqual.NonNullDecl; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class PhysicsOwnerStepCommandTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void runsStepOnOwnerLaneAndPublishesProfiledSnapshot() throws Exception { - CountingBackend backend = registerBackend(true); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - configureWorldSettings(resource, settings -> { - settings.setStepMode(PhysicsStepMode.FIXED); - settings.setSimulationSteps(2); - }); - - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - second.setPosition(32.0f, 0.0f, 0.0f); - resource.addBody(space.id(), - first, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.addBody(space.id(), - second, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, Duration.ofSeconds(2L))) { - owner.start("step-command-test"); - PhysicsOwnerStepCommand command = new PhysicsOwnerStepCommand(resource, - 0.05f, - true, - 12L, - 34L); - PhysicsOwnerResult result = owner.submitAndDrain(command); - - assertEquals(1, result.snapshot().spaces()); - assertEquals(2, result.snapshot().substeps()); - assertEquals(2, result.snapshot().bodySnapshots()); - assertEquals(2, result.snapshot().spatialIndexCells()); - assertTrue(result.snapshot().stepNanos() > 0L); - assertTrue(result.snapshot().snapshotNanos() > 0L); - PublishedPhysicsSnapshotFrame frame = command.publishedFrame(); - Assertions.assertNotNull(frame); - assertEquals(PublishedPhysicsSnapshotFrame.Status.COMPLETE, frame.status()); - assertEquals(12L, frame.stepSequence()); - assertEquals(34L, frame.serverTick()); - assertEquals(2, frame.bodyCount()); - assertEquals(2, space.stepThreadNames.size()); - assertTrue(space.stepThreadNames.stream() - .allMatch(name -> name.startsWith("Impulse physics owner lane executor "))); - assertEquals(List.of(0.025f, 0.025f), space.stepDts); - } - } - - @Test - void progressiveRefinementUsesMaxStepDtBudget() { - CountingBackend backend = registerBackend(false); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - configureWorldSettings(resource, settings -> { - settings.setStepMode(PhysicsStepMode.PROGRESSIVE_REFINEMENT); - settings.setSimulationSteps(2); - settings.setMaxStepDt(0.1f); - }); - - PhysicsOwnerSnapshot snapshot = PhysicsOwnerStepCommand.runStep(resource, 0.35f, false); - - assertEquals(1, snapshot.spaces()); - assertEquals(4, snapshot.substeps()); - assertEquals(List.of(0.0875f, 0.0875f, 0.0875f, 0.0875f), space.stepDts); - assertEquals(0L, snapshot.stepNanos()); - assertEquals(0L, snapshot.snapshotNanos()); - } - - @Test - void nonFiniteDtDoesNotReachBackendStep() { - CountingBackend backend = registerBackend(false); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - configureWorldSettings(resource, settings -> settings.setStepMode(PhysicsStepMode.FIXED)); - - PhysicsOwnerStepCommand.runStep(resource, Float.NaN, false); - PhysicsOwnerStepCommand.runStep(resource, Float.POSITIVE_INFINITY, false); - PhysicsOwnerStepCommand.runStep(resource, -1.0f, false); - - assertEquals(List.of(0.0f, 0.0f, 0.0f), space.stepDts); - } - - @Test - void adaptiveRefinementRaisesStepsForFastBodies() { - CountingBackend backend = registerBackend(false); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - configureWorldSettings(resource, settings -> { - settings.setStepMode(PhysicsStepMode.ADAPTIVE); - settings.setSimulationSteps(1); - settings.setMaxStepDt(1.0f); - }); - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - body.setLinearVelocity(2.0f, 0.0f, 0.0f); - resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - PhysicsOwnerSnapshot snapshot = PhysicsOwnerStepCommand.runStep(resource, 0.5f, false); - - assertEquals(3, snapshot.substeps()); - assertEquals(3, space.stepDts.size()); - assertEquals(1, snapshot.bodySnapshots()); - } - - @Test - void disabledEventCollectionStepsWithoutBackendEventSink() { - CountingBackend backend = registerBackend(false); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - configureWorldSettings(resource, settings -> { - settings.setStepMode(PhysicsStepMode.FIXED); - settings.setSimulationSteps(1); - }); - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - resource.addBody(space.id(), - first, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.addBody(space.id(), - second, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - space.contactToEmit = new PhysicsContact(first, - second, - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(4.0f, 5.0f, 6.0f), - new Vector3f(0.0f, 1.0f, 0.0f), - -0.125f, - 2.5f); - - PhysicsOwnerStepCommand.runStep(resource, 0.05f, false); - - PhysicsEventFrame frame = resource.getLatestEventFrame(); - assertEquals(0, space.eventStepCount); - assertEquals(0, frame.physicsEventCount()); - } - - @Test - void translatesBackendContactEventsToStableBodyKeysInStepFrame() { - CountingBackend backend = registerBackend(false); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - configureWorldSettings(resource, settings -> { - settings.setStepMode(PhysicsStepMode.FIXED); - settings.setSimulationSteps(1); - settings.setEventCollectionMode(PhysicsEventCollectionMode.CONTACTS); - }); - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey firstKey = resource.addBody(space.id(), - first, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey secondKey = resource.addBody(space.id(), - second, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - space.contactToEmit = new PhysicsContact(first, - second, - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(4.0f, 5.0f, 6.0f), - new Vector3f(0.0f, 1.0f, 0.0f), - -0.125f, - 2.5f); - - PhysicsOwnerStepCommand.runStep(resource, 0.05f, false); - - PhysicsEventFrame frame = resource.getLatestEventFrame(); - assertEquals(1, frame.physicsEventCount()); - PhysicsContactEvent event = - assertInstanceOf(PhysicsContactEvent.class, frame.physicsEvents().getFirst()); - assertEquals(space.id(), event.spaceId()); - assertEquals(PhysicsContactPhase.OBSERVED, event.phase()); - assertEquals(firstKey, event.bodyAKey()); - assertEquals(secondKey, event.bodyBKey()); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), event.pointOnA()); - assertEquals(new Vector3f(4.0f, 5.0f, 6.0f), event.pointOnB()); - assertEquals(new Vector3f(0.0f, 1.0f, 0.0f), event.normalOnB()); - assertEquals(-0.125f, event.distance()); - assertEquals(2.5f, event.impulse()); - } - - @Test - void ccdModeForcesAndRestoresOnlyOwnerAppliedOverrides() { - CountingBackend backend = registerBackend(true); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - PhysicsBody forced = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody alreadyEnabled = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody unregistered = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - space.addBody(unregistered); - alreadyEnabled.setContinuousCollisionEnabled(true); - resource.addBody(space.id(), - forced, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - resource.addBody(space.id(), - alreadyEnabled, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - configureWorldSettings(resource, settings -> settings.setStepMode(PhysicsStepMode.CCD)); - PhysicsOwnerStepCommand.runStep(resource, 0.05f, false); - - assertTrue(forced.isContinuousCollisionEnabled()); - assertTrue(alreadyEnabled.isContinuousCollisionEnabled()); - assertFalse(unregistered.isContinuousCollisionEnabled()); - - configureWorldSettings(resource, settings -> settings.setStepMode(PhysicsStepMode.FIXED)); - PhysicsOwnerStepCommand.runStep(resource, 0.05f, false); - - assertFalse(forced.isContinuousCollisionEnabled()); - assertTrue(alreadyEnabled.isContinuousCollisionEnabled()); - assertFalse(unregistered.isContinuousCollisionEnabled()); - } - - @Test - void stepFailuresPublishSnapshotsAndRemainInspectable() throws Exception { - CountingBackend backend = registerBackend(false); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - RuntimeException failure = new RuntimeException("step failed"); - space.stepFailure = failure; - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(1, - 1, - Duration.ofSeconds(2L))) { - owner.start("failing-step-command-test"); - PhysicsOwnerStepCommand command = new PhysicsOwnerStepCommand(resource, - 0.05f, - false); - PhysicsOwnerResult result = owner.submitAndDrain(command); - - assertSame(failure, command.failure()); - Assertions.assertNotNull(command.publishedFrame()); - assertEquals(PublishedPhysicsSnapshotFrame.Status.PARTIAL, - command.publishedFrame().status()); - assertEquals(1, result.snapshot().spaces()); - assertEquals(0, result.snapshot().substeps()); - assertEquals(0, owner.pendingCommands()); - } - } - - @Test - void snapshotFailureIsSuppressedWhenStepAlreadyFailed() { - CountingBackend backend = registerBackend(false); - FailingSnapshotWorldResource resource = new FailingSnapshotWorldResource(); - CountingSpace space = (CountingSpace) resource.createLiveSpace(backend.getId(), - "owner-test", - PhysicsSpaceSettings.defaults()); - RuntimeException stepFailure = new RuntimeException("step failed"); - RuntimeException snapshotFailure = new RuntimeException("snapshot failed"); - space.stepFailure = stepFailure; - resource.snapshotFailure = snapshotFailure; - - RuntimeException thrown = assertThrows(RuntimeException.class, - () -> PhysicsOwnerStepCommand.runStep(resource, 0.05f, false)); - - assertSame(stepFailure, thrown); - assertEquals(1, thrown.getSuppressed().length); - assertSame(snapshotFailure, thrown.getSuppressed()[0]); - } - - @Nonnull - private static CountingBackend registerBackend(boolean supportsContinuousCollision) { - CountingBackend backend = new CountingBackend("test:owner-step-" - + BACKEND_COUNTER.incrementAndGet(), supportsContinuousCollision); - Impulse.registerBackend(backend); - return backend; - } - - private static void configureWorldSettings(@Nonnull LegacyLiveHandleTestResource resource, - @Nonnull Consumer configurator) { - PhysicsWorldSettings settings = resource.getWorldSettings(); - configurator.accept(settings); - resource.setWorldSettings(settings); - } - - private static final class CountingBackend implements PhysicsBackend { - - @Nonnull - private final BackendId id; - private final boolean supportsContinuousCollision; - - private CountingBackend(@Nonnull String id, boolean supportsContinuousCollision) { - this.id = new BackendId(id); - this.supportsContinuousCollision = supportsContinuousCollision; - } - - @Nonnull - @Override - public BackendId getId() { - return id; - } - - @Override - public void init() { - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return createSpace(SpaceId.next()); - } - - @Nonnull - @Override - public PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - return new CountingSpace(spaceId, id, supportsContinuousCollision); - } - } - - private static final class FailingSnapshotWorldResource extends LegacyLiveHandleTestResource { - - @Nullable - private RuntimeException snapshotFailure; - - @NonNullDecl - @Override - public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame(long stepSequence, - long serverTick, - @NonNullDecl PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled, - @NonNullDecl List physicsEvents, - int droppedBackendEventCount) { - if (snapshotFailure != null) { - throw snapshotFailure; - } - return super.capturePublishedSnapshotFrame(stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled, - physicsEvents, - droppedBackendEventCount); - } - } - - private static final class CountingSpace implements PhysicsSpace { - - @Nonnull - private final SpaceId id; - @Nonnull - private final BackendId backendId; - private final boolean supportsContinuousCollision; - @Nonnull - private final List bodies = new ArrayList<>(); - @Nonnull - private final Vector3f gravity = new Vector3f(); - @Nonnull - private final List stepDts = new ArrayList<>(); - @Nonnull - private final List stepThreadNames = new ArrayList<>(); - @Nullable - private RuntimeException stepFailure; - @Nullable - private PhysicsContact contactToEmit; - private int eventStepCount; - - private CountingSpace(@Nonnull SpaceId id, - @Nonnull BackendId backendId, - boolean supportsContinuousCollision) { - this.id = id; - this.backendId = backendId; - this.supportsContinuousCollision = supportsContinuousCollision; - } - - @Nonnull - @Override - public SpaceId id() { - return id; - } - - @Nonnull - @Override - public BackendId backendId() { - return backendId; - } - - @Override - public void step(float dt) { - if (stepFailure != null) { - throw stepFailure; - } - stepDts.add(dt); - stepThreadNames.add(Thread.currentThread().getName()); - } - - @Override - public void step(float dt, @Nonnull PhysicsBackendEventSink events) { - eventStepCount++; - step(dt); - if (contactToEmit != null) { - events.contact(PhysicsContactPhase.OBSERVED, - contactToEmit.bodyA(), - contactToEmit.bodyB(), - contactToEmit.pointOnA(), - contactToEmit.pointOnB(), - contactToEmit.normalOnB(), - contactToEmit.distance(), - contactToEmit.impulse()); - } - } - - @Override - public void setGravity(float x, float y, float z) { - gravity.set(x, y, z); - } - - @Nonnull - @Override - public Vector3f getGravity() { - return new Vector3f(gravity); - } - - @Override - public void addBody(@Nonnull PhysicsBody body) { - bodies.add(body); - } - - @Override - public void removeBody(@Nonnull PhysicsBody body) { - bodies.remove(body); - } - - @Nonnull - @Override - public List getBodies() { - return new ArrayList<>(bodies); - } - - @Override - public int bodyCount() { - return bodies.size(); - } - - @Override - public boolean containsBody(@Nonnull PhysicsBody body) { - return bodies.contains(body); - } - - @NonNullDecl - @Override - public Optional getCapability(@Nonnull Class type) { - Objects.requireNonNull(type, "type"); - if (supportsContinuousCollision && type == PhysicsContinuousCollisionCapability.class) { - return Optional.of(type.cast(new PhysicsContinuousCollisionCapability() { - })); - } - return Optional.empty(); - } - - @Nonnull - @Override - public PhysicsBody createStaticPlane(float groundY) { - CountingBody body = new CountingBody(ShapeType.PLANE, PhysicsBodyType.STATIC); - body.position.y = groundY; - return body; - } - - @Nonnull - @Override - public PhysicsBody createBox(float halfX, float halfY, float halfZ, float mass) { - CountingBody body = new CountingBody(ShapeType.BOX, PhysicsBodyType.DYNAMIC); - body.halfExtents.set(halfX, halfY, halfZ); - body.mass = mass; - return body; - } - - @Nonnull - @Override - public PhysicsBody createBox(@Nonnull Vector3f halfExtents, float mass) { - return createBox(halfExtents.x, halfExtents.y, halfExtents.z, mass); - } - - @Nonnull - @Override - public PhysicsBody createSphere(float radius, float mass) { - CountingBody body = new CountingBody(ShapeType.SPHERE, PhysicsBodyType.DYNAMIC); - body.radius = radius; - body.mass = mass; - return body; - } - - @Nonnull - @Override - public PhysicsBody createCapsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return createRoundHeightBody(ShapeType.CAPSULE, radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return createRoundHeightBody(ShapeType.CYLINDER, radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCone(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return createRoundHeightBody(ShapeType.CONE, radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public Optional raycastClosest(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return Optional.empty(); - } - - @Nonnull - @Override - public List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return List.of(); - } - - @Nonnull - @Override - public List getContacts() { - return contactToEmit != null ? List.of(contactToEmit) : List.of(); - } - - @Nonnull - @Override - public PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeJoint(@Nonnull PhysicsJoint joint) { - } - - @Nonnull - @Override - public List getJoints() { - return List.of(); - } - - @Nonnull - private PhysicsBody createRoundHeightBody(@Nonnull ShapeType shapeType, - float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - CountingBody body = new CountingBody(shapeType, PhysicsBodyType.DYNAMIC); - body.radius = radius; - body.halfHeight = halfHeight; - body.axis = axis; - body.mass = mass; - return body; - } - } - - private static final class CountingBody implements PhysicsBody { - - @Nonnull - private final ShapeType shapeType; - @Nonnull - private final Vector3f position = new Vector3f(); - @Nonnull - private final Quaternionf rotation = new Quaternionf(); - @Nonnull - private final Vector3f linearVelocity = new Vector3f(); - @Nonnull - private final Vector3f angularVelocity = new Vector3f(); - @Nonnull - private final Vector3f halfExtents = new Vector3f(); - @Nonnull - private PhysicsBodyType bodyType; - @Nonnull - private PhysicsAxis axis = PhysicsAxis.Y; - private float mass = 1.0f; - private float radius; - private float halfHeight; - private float restitution; - private float friction; - private float linearDamping; - private float angularDamping; - private boolean sensor; - private boolean continuousCollision; - private int collisionGroup; - private int collisionMask; - - private CountingBody(@Nonnull ShapeType shapeType, @Nonnull PhysicsBodyType bodyType) { - this.shapeType = shapeType; - this.bodyType = bodyType; - } - - @Override - public void setPosition(float x, float y, float z) { - position.set(x, y, z); - } - - @Override - public void setPosition(@Nonnull Vector3f pos) { - position.set(pos); - } - - @Nonnull - @Override - public Vector3f getPosition() { - return new Vector3f(position); - } - - @Override - public void setRotation(float x, float y, float z, float w) { - rotation.set(x, y, z, w); - } - - @Override - public void setRotation(@Nonnull Quaternionf rot) { - rotation.set(rot); - } - - @Nonnull - @Override - public Quaternionf getRotation() { - return new Quaternionf(rotation); - } - - @Override - public void setRestitution(float restitution) { - this.restitution = restitution; - } - - @Override - public float getRestitution() { - return restitution; - } - - @Override - public void setFriction(float friction) { - this.friction = friction; - } - - @Override - public float getFriction() { - return friction; - } - - @Nonnull - @Override - public PhysicsBodyType getBodyType() { - return bodyType; - } - - @Override - public void setBodyType(@Nonnull PhysicsBodyType bodyType) { - this.bodyType = bodyType; - } - - @Override - public boolean isStatic() { - return bodyType == PhysicsBodyType.STATIC; - } - - @Override - public boolean isKinematic() { - return bodyType == PhysicsBodyType.KINEMATIC; - } - - @Override - public void setKinematic(boolean kinematic) { - bodyType = kinematic ? PhysicsBodyType.KINEMATIC : PhysicsBodyType.DYNAMIC; - } - - @Override - public void activate() { - } - - @Override - public boolean isActive() { - return true; - } - - @Override - public boolean isSleeping() { - return false; - } - - @Override - public void sleep() { - } - - @Override - public float getMass() { - return mass; - } - - @Override - public void setMass(float mass) { - this.mass = mass; - } - - @Nonnull - @Override - public Vector3f getLinearVelocity() { - return new Vector3f(linearVelocity); - } - - @Override - public void setLinearVelocity(@Nonnull Vector3f vel) { - linearVelocity.set(vel); - } - - @Override - public void setLinearVelocity(float x, float y, float z) { - linearVelocity.set(x, y, z); - } - - @Nonnull - @Override - public Vector3f getAngularVelocity() { - return new Vector3f(angularVelocity); - } - - @Override - public void setAngularVelocity(@Nonnull Vector3f vel) { - angularVelocity.set(vel); - } - - @Override - public void setAngularVelocity(float x, float y, float z) { - angularVelocity.set(x, y, z); - } - - @Override - public float getLinearDamping() { - return linearDamping; - } - - @Override - public void setLinearDamping(float damping) { - linearDamping = damping; - } - - @Override - public float getAngularDamping() { - return angularDamping; - } - - @Override - public void setAngularDamping(float damping) { - angularDamping = damping; - } - - @Override - public void applyCentralForce(@Nonnull Vector3f force) { - } - - @Override - public void applyCentralForce(float x, float y, float z) { - } - - @Override - public void applyForce(@Nonnull Vector3f force, @Nonnull Vector3f offset) { - } - - @Override - public void applyCentralImpulse(@Nonnull Vector3f impulse) { - } - - @Override - public void applyCentralImpulse(float x, float y, float z) { - } - - @Override - public void applyImpulse(@Nonnull Vector3f impulse, @Nonnull Vector3f offset) { - } - - @Override - public void applyTorque(@Nonnull Vector3f torque) { - } - - @Override - public void applyTorqueImpulse(@Nonnull Vector3f torqueImpulse) { - } - - @Override - public void clearForces() { - } - - @Override - public boolean isSensor() { - return sensor; - } - - @Override - public void setSensor(boolean sensor) { - this.sensor = sensor; - } - - @Override - public int getCollisionGroup() { - return collisionGroup; - } - - @Override - public int getCollisionMask() { - return collisionMask; - } - - @Override - public void setCollisionFilter(int group, int mask) { - collisionGroup = group; - collisionMask = mask; - } - - @Override - public boolean isContinuousCollisionEnabled() { - return continuousCollision; - } - - @Override - public void setContinuousCollisionEnabled(boolean enabled) { - continuousCollision = enabled; - } - - @Nonnull - @Override - public ShapeType getShapeType() { - return shapeType; - } - - @Nonnull - @Override - public Vector3f getBoxHalfExtents() { - return new Vector3f(halfExtents); - } - - @Override - public float getSphereRadius() { - return radius; - } - - @Override - public float getHalfHeight() { - return halfHeight; - } - - @Nonnull - @Override - public PhysicsAxis getShapeAxis() { - return axis; - } - - @Override - public float getCenterOfMassOffsetY() { - return 0.0f; - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/TestPhysicsOwnerLane.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/TestPhysicsOwnerLane.java deleted file mode 100644 index 2d391985..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/owner/TestPhysicsOwnerLane.java +++ /dev/null @@ -1,165 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.owner; - -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Test fixture that exposes a pooled owner lane as an ECS resource. - */ -public final class TestPhysicsOwnerLane implements PhysicsOwnerResource { - - private final int poolSize; - private final int queueCapacity; - @Nonnull - private final Duration closeTimeout; - private final PhysicsOwnerLaneScheduler scheduler; - private final PhysicsOwnerLaneResource lane; - - public TestPhysicsOwnerLane() { - this(2, - PhysicsOwnerLaneScheduler.DEFAULT_QUEUE_CAPACITY, - PhysicsOwnerLaneScheduler.DEFAULT_CLOSE_TIMEOUT); - } - - public TestPhysicsOwnerLane(int queueCapacity, @Nonnull Duration closeTimeout) { - this(2, queueCapacity, closeTimeout); - } - - public TestPhysicsOwnerLane(int poolSize, - int queueCapacity, - @Nonnull Duration closeTimeout) { - this.poolSize = poolSize; - this.queueCapacity = queueCapacity; - this.closeTimeout = closeTimeout; - scheduler = new PhysicsOwnerLaneScheduler(poolSize, queueCapacity, closeTimeout); - lane = scheduler.createLane(); - } - - @Override - public void start(@Nonnull String worldName) { - lane.start(worldName); - } - - @Override - public boolean isStarted() { - return lane.isStarted(); - } - - @Override - public boolean isClosed() { - return lane.isClosed(); - } - - @Override - public void close() { - scheduler.close(); - } - - @Nonnull - @Override - public PhysicsOwnerResult submitAndDrain(@Nonnull PhysicsOwnerCommand command) - throws InterruptedException, ExecutionException { - return lane.submitAndDrain(command); - } - - @Override - public boolean submitStepIfIdle(@Nonnull PhysicsOwnerCommand command) { - return lane.submitStepIfIdle(command); - } - - @Nonnull - @Override - public PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nonnull PhysicsOwnerCommand command) { - return lane.submitMutation(operation, command); - } - - @Nonnull - @Override - public PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerCommand command) { - return lane.submitMutation(operation, value, command); - } - - @Nonnull - @Override - public CompletableFuture submitMutationFuture(@Nonnull String operation, - @Nonnull PhysicsOwnerCommand command) { - return lane.submitMutationFuture(operation, command); - } - - @Nonnull - @Override - public List pollCompletedMutations(int maxCompletions) { - return lane.pollCompletedMutations(maxCompletions); - } - - @Nullable - @Override - public PhysicsOwnerStepCompletion pollCompletedStep() { - return lane.pollCompletedStep(); - } - - @Override - public boolean hasPendingStep() { - return lane.hasPendingStep(); - } - - @Override - public long pendingStepAgeNanos() { - return lane.pendingStepAgeNanos(); - } - - @Override - public int pendingMutations() { - return lane.pendingMutations(); - } - - @Override - public int pendingCommands() { - return lane.pendingCommands(); - } - - @Override - public boolean isOwnerContext() { - return lane.isOwnerContext(); - } - - @Override - public void run(@Nonnull String operation, @Nonnull PhysicsOwnerMutation mutation) { - lane.run(operation, mutation); - } - - @Nonnull - @Override - public PhysicsMutationHandle enqueue(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - return lane.enqueue(operation, value, mutation); - } - - @Nonnull - @Override - public CompletableFuture enqueueCall(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - return lane.enqueueCall(operation, callable); - } - - @Nonnull - @Override - public T call(@Nonnull String operation, @Nonnull PhysicsOwnerCallable callable) { - return lane.call(operation, callable); - } - - @Nonnull - @Override - public PhysicsOwnerResource clone() { - return new TestPhysicsOwnerLane(poolSize, queueCapacity, closeTimeout); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResourceTest.java index 5941bfe3..97d6971c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResourceTest.java @@ -42,8 +42,8 @@ void recordStepTracksLatestCumulativeAndWorstSamples() { assertEquals(9, resource.getCumulativeStep().getSubsteps()); assertEquals(140L, resource.getCumulativeStep().getTickNanos()); assertEquals(30L, resource.getCumulativeStep().getSnapshotNanos()); - assertEquals(10L, resource.getCumulativeStep().getOwnerQueuedNanos()); - assertEquals(90L, resource.getCumulativeStep().getOwnerRunNanos()); + assertEquals(10L, resource.getCumulativeStep().getStoreTickQueuedNanos()); + assertEquals(90L, resource.getCumulativeStep().getStoreTickRunNanos()); assertEquals(1, resource.getCumulativeStep().getSkippedPendingSteps()); assertEquals(75L, resource.getCumulativeStep().getPendingStepAgeNanos()); assertEquals(75L, resource.getCumulativeStep().getMaxPendingStepAgeNanos()); @@ -183,7 +183,7 @@ void finishSyncSampleCapturesCollectorMetricsAndClearsActiveCollector() { } @Test - void ownerStepRateUsesCompletedOwnerStepIntervals() { + void storeTickStepRateUsesCompletedStoreTickStepIntervals() { PhysicsRuntimeProfilingResource resource = new PhysicsRuntimeProfilingResource(); resource.recordStep(1, @@ -196,8 +196,8 @@ void ownerStepRateUsesCompletedOwnerStepIntervals() { 7L, 1_000_000_000L, PhysicsStepPhaseStats.unavailable()); - assertEquals(0, resource.getLatestStep().getOwnerStepRateSamples()); - assertEquals(0L, resource.getLatestStep().getOwnerStepIntervalNanos()); + assertEquals(0, resource.getLatestStep().getStoreTickStepRateSamples()); + assertEquals(0L, resource.getLatestStep().getStoreTickStepIntervalNanos()); resource.recordStep(1, 2, @@ -220,11 +220,12 @@ void ownerStepRateUsesCompletedOwnerStepIntervals() { 1_150_000_000L, PhysicsStepPhaseStats.unavailable()); - assertEquals(1, resource.getLatestStep().getOwnerStepRateSamples()); - assertEquals(100_000_000L, resource.getLatestStep().getOwnerStepIntervalNanos()); - assertEquals(2, resource.getCumulativeStep().getOwnerStepRateSamples()); - assertEquals(150_000_000L, resource.getCumulativeStep().getOwnerStepIntervalNanos()); - assertEquals(100_000_000L, resource.getCumulativeStep().getMaxOwnerStepIntervalNanos()); + assertEquals(1, resource.getLatestStep().getStoreTickStepRateSamples()); + assertEquals(100_000_000L, resource.getLatestStep().getStoreTickStepIntervalNanos()); + assertEquals(2, resource.getCumulativeStep().getStoreTickStepRateSamples()); + assertEquals(150_000_000L, resource.getCumulativeStep().getStoreTickStepIntervalNanos()); + assertEquals(100_000_000L, + resource.getCumulativeStep().getMaxStoreTickStepIntervalNanos()); resource.reset(); resource.recordStep(1, @@ -238,8 +239,8 @@ void ownerStepRateUsesCompletedOwnerStepIntervals() { 2_000_000_000L, PhysicsStepPhaseStats.unavailable()); - assertEquals(0, resource.getLatestStep().getOwnerStepRateSamples()); - assertEquals(0L, resource.getLatestStep().getOwnerStepIntervalNanos()); + assertEquals(0, resource.getLatestStep().getStoreTickStepRateSamples()); + assertEquals(0L, resource.getLatestStep().getStoreTickStepIntervalNanos()); } @Test diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/visual/PhysicsVisualRuntimeTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/visual/PhysicsVisualRuntimeTest.java index 02968271..20dee980 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/visual/PhysicsVisualRuntimeTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/visual/PhysicsVisualRuntimeTest.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -15,10 +14,10 @@ class PhysicsVisualRuntimeTest { - private static final RigidBodyKey FIRST_BODY = - RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000001")); - private static final RigidBodyKey SECOND_BODY = - RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000002")); + private static final UUID FIRST_BODY = + UUID.fromString("00000000-0000-0000-0000-000000000001"); + private static final UUID SECOND_BODY = + UUID.fromString("00000000-0000-0000-0000-000000000002"); @Test void hasAttachmentsPrunesStaleReferencesWithoutCopyingLiveAttachments() { @@ -27,29 +26,28 @@ void hasAttachmentsPrunesStaleReferencesWithoutCopyingLiveAttachments() { TestRef liveRef = new TestRef(true); TestRef staleRef = new TestRef(false); - assertFalse(runtime.hasAttachments(FIRST_BODY)); + assertFalse(runtime.hasAttachments(FIRST_BODY, null)); - runtime.registerAttachment(FIRST_BODY, staleRef); - runtime.registerAttachment(FIRST_BODY, liveRef); + runtime.registerAttachment(FIRST_BODY, null, staleRef); + runtime.registerAttachment(FIRST_BODY, null, liveRef); - assertTrue(runtime.hasAttachments(FIRST_BODY)); + assertTrue(runtime.hasAttachments(FIRST_BODY, null)); assertEquals(1, cleaned.get()); - runtime.unregisterAttachment(FIRST_BODY, liveRef); + runtime.unregisterAttachment(FIRST_BODY, null, liveRef); - assertFalse(runtime.hasAttachments(FIRST_BODY)); + assertFalse(runtime.hasAttachments(FIRST_BODY, null)); } @Test void generatedVisualProxyCountPrunesStaleReferencesWithoutBodyIdCopy() { AtomicInteger cleaned = new AtomicInteger(); PhysicsVisualRuntime runtime = new PhysicsVisualRuntime(_ -> cleaned.incrementAndGet()); - runtime.setGeneratedVisualProxy(FIRST_BODY, new TestRef(true)); - runtime.setGeneratedVisualProxy(SECOND_BODY, new TestRef(false)); + runtime.setGeneratedVisualProxy(FIRST_BODY, null, new TestRef(true)); + runtime.setGeneratedVisualProxy(SECOND_BODY, null, new TestRef(false)); assertEquals(1, runtime.generatedVisualProxyCount()); assertEquals(1, cleaned.get()); - assertEquals(1, runtime.getGeneratedVisualProxyBodyKeys().size()); } @Test @@ -62,10 +60,10 @@ void staleReferenceCleanerRunsOutsideVisualRuntimeLock() { }); runtimeRef.set(runtime); - runtime.registerAttachment(FIRST_BODY, new TestRef(false)); - runtime.setGeneratedVisualProxy(SECOND_BODY, new TestRef(false)); + runtime.registerAttachment(FIRST_BODY, null, new TestRef(false)); + runtime.setGeneratedVisualProxy(SECOND_BODY, null, new TestRef(false)); - assertFalse(runtime.hasAttachments(FIRST_BODY)); + assertFalse(runtime.hasAttachments(FIRST_BODY, null)); assertEquals(0, runtime.generatedVisualProxyCount()); assertEquals(2, cleaned.get()); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java index a01492d3..5d721206 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java @@ -12,7 +12,6 @@ import dev.hytalemodding.impulse.api.PhysicsContact; import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; @@ -62,7 +61,7 @@ void debugCenterInvertsSyncedAttachmentLocalPositionOffset() { Vector3d syncedVisualPosition = new Vector3d(snapshot.positionX(), snapshot.positionY() - snapshot.centerOfMassOffsetY(), snapshot.positionZ()).add(localOffset.x, localOffset.y, localOffset.z); - BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(RigidBodyKey.random().value(), + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(UUID.randomUUID(), localOffset, new Quaternionf()); @@ -111,7 +110,7 @@ void debugPoseUsesSyncedTransformRotationWhenSnapshotRotationIsStale() { syncedVisualPosition.add(syncedBodyRotation.transform(new Vector3d(localOffset.x, localOffset.y, localOffset.z))); - BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(RigidBodyKey.random().value(), + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(UUID.randomUUID(), localOffset, new Quaternionf()); @@ -157,7 +156,7 @@ void debugCenterUsesAttachmentVisualOriginOffset() { 0.0f, PhysicsAxis.Y); Vector3f localOffset = new Vector3f(0.0f, -0.5f, 0.0f); - BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(RigidBodyKey.random().value(), + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(UUID.randomUUID(), localOffset, new Quaternionf(), 0.5f); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystemTest.java deleted file mode 100644 index 05df0dca..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/owner/PhysicsOwnerLifecycleSystemTest.java +++ /dev/null @@ -1,392 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.owner; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.EmptyResourceStorage; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerHandle; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerLaneResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerLaneScheduler; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerCallable; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerCommand; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerMutation; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerMutationCompletion; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResult; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCompletion; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.junit.jupiter.api.Test; - -class PhysicsOwnerLifecycleSystemTest { - - @Test - void storeAddAndRemoveStartAndCloseOwnerResource() { - ComponentRegistry registry = new ComponentRegistry<>(); - ResourceType ownerLaneType = - registry.registerResource(TestPhysicsOwnerLane.class, - TestPhysicsOwnerLane::new); - ResourceType physicsType = - registry.registerResource(CountingPhysicsWorldResource.class, - CountingPhysicsWorldResource::new); - PhysicsOwnerLifecycleSystem lifecycle = - new PhysicsOwnerLifecycleSystem(ownerLaneType, physicsType); - registry.registerSystem(lifecycle); - - Store store = registry.addStore(testEntityStore("lifecycle-test"), - EmptyResourceStorage.get()); - TestPhysicsOwnerLane owner = store.getResource(ownerLaneType); - CountingPhysicsWorldResource physics = store.getResource(physicsType); - - assertTrue(owner.isStarted()); - assertEquals(1, lifecycle.activeOwnerCount()); - - registry.removeStore(store); - - assertTrue(owner.isClosed()); - assertEquals(1, physics.clearCalls); - assertEquals(0, lifecycle.activeOwnerCount()); - lifecycle.close(); - registry.shutdown(); - } - - @Test - void storeAddAndRemoveAttachInterfaceBackedOwnerResource() { - ComponentRegistry registry = new ComponentRegistry<>(); - ResourceType ownerType = - registry.registerResource(PhysicsOwnerResource.class, - TestPhysicsOwnerLane::new); - ResourceType physicsType = - registry.registerResource(CountingPhysicsWorldResource.class, - CountingPhysicsWorldResource::new); - PhysicsOwnerLifecycleSystem lifecycle = - new PhysicsOwnerLifecycleSystem(ownerType, physicsType); - registry.registerSystem(lifecycle); - - Store store = registry.addStore(testEntityStore("owner-lifecycle-test"), - EmptyResourceStorage.get()); - PhysicsOwnerResource owner = store.getResource(ownerType); - CountingPhysicsWorldResource physics = store.getResource(physicsType); - - assertInstanceOf(TestPhysicsOwnerLane.class, owner); - assertTrue(owner.isStarted()); - assertEquals(1, lifecycle.activeOwnerCount()); - assertTrue(physics.hasOwnerExecutor()); - - registry.removeStore(store); - - assertTrue(owner.isClosed()); - assertEquals(1, physics.clearCalls); - assertEquals(0, lifecycle.activeOwnerCount()); - assertTrue(physics.detachedOwnerPublication()); - lifecycle.close(); - registry.shutdown(); - } - - @Test - void storeAddAndRemoveAttachPooledOwnerLaneResource() { - ComponentRegistry registry = new ComponentRegistry<>(); - try (PhysicsOwnerLaneScheduler scheduler = new PhysicsOwnerLaneScheduler(1, - 4, - Duration.ofSeconds(2L))) { - ResourceType ownerType = - registry.registerResource(PhysicsOwnerResource.class, scheduler::createLane); - ResourceType physicsType = - registry.registerResource(CountingPhysicsWorldResource.class, - CountingPhysicsWorldResource::new); - PhysicsOwnerLifecycleSystem lifecycle = - new PhysicsOwnerLifecycleSystem(ownerType, physicsType); - registry.registerSystem(lifecycle); - - Store store = registry.addStore(testEntityStore("owner-lane-test"), - EmptyResourceStorage.get()); - PhysicsOwnerResource owner = store.getResource(ownerType); - CountingPhysicsWorldResource physics = store.getResource(physicsType); - - assertInstanceOf(PhysicsOwnerLaneResource.class, owner); - assertTrue(owner.isStarted()); - assertEquals(1, lifecycle.activeOwnerCount()); - assertTrue(physics.hasOwnerExecutor()); - - registry.removeStore(store); - - assertTrue(owner.isClosed()); - assertEquals(1, physics.clearCalls); - assertEquals(0, lifecycle.activeOwnerCount()); - assertTrue(physics.detachedOwnerPublication()); - lifecycle.close(); - } finally { - registry.shutdown(); - } - } - - @Test - void startFailureDoesNotAttachOwnerExecutor() { - ComponentRegistry registry = new ComponentRegistry<>(); - ResourceType ownerType = - registry.registerResource(FailingStartOwnerResource.class, - FailingStartOwnerResource::new); - ResourceType physicsType = - registry.registerResource(CountingPhysicsWorldResource.class, - CountingPhysicsWorldResource::new); - PhysicsOwnerLifecycleSystem lifecycle = - new PhysicsOwnerLifecycleSystem(ownerType, physicsType); - registry.registerSystem(lifecycle); - - Store store = registry.addStore(testEntityStore("start-failure-test"), - EmptyResourceStorage.get()); - FailingStartOwnerResource owner = store.getResource(ownerType); - CountingPhysicsWorldResource physics = store.getResource(physicsType); - - assertTrue(owner.startAttempted); - assertEquals(0, lifecycle.activeOwnerCount()); - assertTrue(physics.canAccessLiveBackendDirectly()); - - registry.removeStore(store); - lifecycle.close(); - registry.shutdown(); - } - - @Test - void closeClosesTrackedOwnersAsShutdownFallback() { - ComponentRegistry registry = new ComponentRegistry<>(); - ResourceType ownerLaneType = - registry.registerResource(TestPhysicsOwnerLane.class, - TestPhysicsOwnerLane::new); - ResourceType physicsType = - registry.registerResource(CountingPhysicsWorldResource.class, - CountingPhysicsWorldResource::new); - PhysicsOwnerLifecycleSystem lifecycle = - new PhysicsOwnerLifecycleSystem(ownerLaneType, physicsType); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane()) { - - lifecycle.startOwner(owner, "fallback-world"); - assertTrue(owner.isStarted()); - assertEquals(1, lifecycle.activeOwnerCount()); - - lifecycle.close(); - - assertTrue(owner.isClosed()); - assertEquals(0, lifecycle.activeOwnerCount()); - } - registry.shutdown(); - } - - @Test - void removeStoreFallsBackToDirectClearWhenOwnerClearFails() { - ComponentRegistry registry = new ComponentRegistry<>(); - ResourceType ownerLaneType = - registry.registerResource(TestPhysicsOwnerLane.class, - TestPhysicsOwnerLane::new); - ResourceType physicsType = - registry.registerResource(FailingOncePhysicsWorldResource.class, - FailingOncePhysicsWorldResource::new); - PhysicsOwnerLifecycleSystem lifecycle = - new PhysicsOwnerLifecycleSystem(ownerLaneType, physicsType); - registry.registerSystem(lifecycle); - - Store store = registry.addStore(testEntityStore("fallback-test"), - EmptyResourceStorage.get()); - TestPhysicsOwnerLane owner = store.getResource(ownerLaneType); - FailingOncePhysicsWorldResource physics = store.getResource(physicsType); - - registry.removeStore(store); - - assertTrue(owner.isClosed()); - assertEquals(2, physics.clearCalls); - assertEquals(0, lifecycle.activeOwnerCount()); - lifecycle.close(); - registry.shutdown(); - } - - private static final class CountingPhysicsWorldResource extends LegacyLiveHandleTestResource { - - private int clearCalls; - private boolean detachedOwnerPublication; - - @Override - public void clearAllSpaces(@Nonnull String worldName) { - clearCalls++; - super.clearAllSpaces(worldName); - } - - private boolean hasOwnerExecutor() { - return !canAccessLiveBackendDirectly(); - } - - private boolean detachedOwnerPublication() { - return detachedOwnerPublication; - } - - @Override - public void detachOwnerExecutor(@Nonnull PhysicsOwnerHandle ownerExecutor) { - super.detachOwnerExecutor(ownerExecutor); - detachedOwnerPublication = canAccessLiveBackendDirectly(); - } - } - - private static final class FailingOncePhysicsWorldResource extends LegacyLiveHandleTestResource { - - private int clearCalls; - - @Override - public void clearAllSpaces(@Nonnull String worldName) { - clearCalls++; - if (clearCalls == 1) { - throw new IllegalStateException("forced clear failure"); - } - super.clearAllSpaces(worldName); - } - } - - private static final class FailingStartOwnerResource implements PhysicsOwnerResource { - - private boolean startAttempted; - private boolean closed; - - @Override - public void start(@Nonnull String worldName) { - startAttempted = true; - throw new IllegalStateException("forced start failure"); - } - - @Override - public boolean isStarted() { - return false; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - public void close() { - closed = true; - } - - @Nonnull - @Override - public PhysicsOwnerResult submitAndDrain(@Nonnull PhysicsOwnerCommand command) - throws InterruptedException, ExecutionException { - throw new UnsupportedOperationException("not started"); - } - - @Override - public boolean submitStepIfIdle(@Nonnull PhysicsOwnerCommand command) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nonnull PhysicsOwnerCommand command) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public PhysicsMutationHandle submitMutation(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerCommand command) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public CompletableFuture submitMutationFuture( - @Nonnull String operation, - @Nonnull PhysicsOwnerCommand command) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public List pollCompletedMutations(int maxCompletions) { - return List.of(); - } - - @Nullable - @Override - public PhysicsOwnerStepCompletion pollCompletedStep() { - return null; - } - - @Override - public boolean hasPendingStep() { - return false; - } - - @Override - public long pendingStepAgeNanos() { - return 0L; - } - - @Override - public int pendingMutations() { - return 0; - } - - @Override - public int pendingCommands() { - return 0; - } - - @Override - public boolean isOwnerContext() { - return false; - } - - @Override - public void run(@Nonnull String operation, - @Nonnull PhysicsOwnerMutation mutation) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public PhysicsMutationHandle enqueue(@Nonnull String operation, - @Nullable T value, - @Nonnull PhysicsOwnerMutation mutation) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public CompletableFuture enqueueCall(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public T call(@Nonnull String operation, - @Nonnull PhysicsOwnerCallable callable) { - throw new UnsupportedOperationException("not started"); - } - - @Nonnull - @Override - public PhysicsOwnerResource clone() { - return new FailingStartOwnerResource(); - } - } - - @Nonnull - private static EntityStore testEntityStore(@Nonnull String worldName) { - return new EntityStore(TestInstanceFactory.world(worldName)); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystemTest.java deleted file mode 100644 index c814eff7..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsJointHydrationSystemTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import org.junit.jupiter.api.Test; - -class PersistentPhysicsJointHydrationSystemTest { - - @Test - void hardJointHydrationFailureDoesNotFinalizeRestoreAsSuccessful() { - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - - persistent.markRuntimeRestorePending(); - persistent.failRuntimeRestore("forced joint failure"); - - assertFalse(PersistentPhysicsJointHydrationSystem.shouldFinalizeRuntimeRestore(persistent)); - } - - @Test - void pendingRestoreWithoutHardFailureCanFinalizeAfterJointHydration() { - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - - persistent.markRuntimeRestorePending(); - - assertTrue(PersistentPhysicsJointHydrationSystem.shouldFinalizeRuntimeRestore(persistent)); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystemTest.java deleted file mode 100644 index 97157bec..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/persistence/PersistentPhysicsWorldSyncSystemTest.java +++ /dev/null @@ -1,571 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsBodyState; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsJointState; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRuntimeSnapshot; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsRuntimeSupport; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsSpaceState; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3d; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PersistentPhysicsWorldSyncSystemTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void persistentBodyCountChangeForcesSnapshotBeforeCadence() { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.syncPersistentSpaces(); - fixture.persistent.markRuntimeSnapshotSynced(); - - assertFalse(fixture.persistent.shouldSyncRuntimeSnapshot(20)); - assertFalse(PersistentPhysicsWorldSyncSystem.hasRuntimePersistenceFootprintChanged( - fixture.persistent, - PersistentPhysicsRuntimeSnapshot.captureFootprint(fixture.runtime))); - - PhysicsBody body = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - fixture.runtime.addBody(fixture.space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - assertTrue(PersistentPhysicsWorldSyncSystem.hasRuntimePersistenceFootprintChanged( - fixture.persistent, - PersistentPhysicsRuntimeSnapshot.captureFootprint(fixture.runtime))); - } - - @Test - void runtimeOnlyBodiesDoNotChangePersistentFootprint() { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.syncPersistentSpaces(); - fixture.persistent.markRuntimeSnapshotSynced(); - - PhysicsBody body = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - fixture.runtime.addBody(fixture.space.id(), - body, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - - assertFalse(PersistentPhysicsWorldSyncSystem.hasRuntimePersistenceFootprintChanged( - fixture.persistent, - PersistentPhysicsRuntimeSnapshot.captureFootprint(fixture.runtime))); - } - - @Test - void persistentJointCountChangeForcesSnapshotBeforeCadence() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsBody first = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey firstId = fixture.runtime.addBody(fixture.space.id(), - first, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey secondId = fixture.runtime.addBody(fixture.space.id(), - second, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - fixture.syncPersistentSpaces(); - fixture.persistent.setBodies(new PersistentPhysicsBodyState[] { - fixture.bodyState(firstId), - fixture.bodyState(secondId) - }); - fixture.persistent.markRuntimeSnapshotSynced(); - - assertFalse(fixture.persistent.shouldSyncRuntimeSnapshot(20)); - assertFalse(PersistentPhysicsWorldSyncSystem.hasRuntimePersistenceFootprintChanged( - fixture.persistent, - PersistentPhysicsRuntimeSnapshot.captureFootprint(fixture.runtime))); - - PhysicsJoint joint = fixture.space.createFixedJoint(first, second, new Vector3f(), new Vector3f()); - fixture.runtime.addJoint(fixture.space.id(), joint); - - assertTrue(PersistentPhysicsWorldSyncSystem.hasRuntimePersistenceFootprintChanged( - fixture.persistent, - PersistentPhysicsRuntimeSnapshot.captureFootprint(fixture.runtime))); - } - - @Test - void explicitRuntimeSnapshotSyncCopiesPersistentState() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsWorldSettings settings = fixture.runtime.getWorldSettings(); - settings.setStepSchedulingMode(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT); - settings.setEventCollectionMode(PhysicsEventCollectionMode.CONTACTS); - fixture.runtime.setWorldSettings(settings); - PhysicsBody body = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - fixture.runtime.addBody(fixture.space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - PersistentPhysicsWorldSyncSystem.SyncResult result = - PersistentPhysicsWorldSyncSystem.syncRuntimeSnapshot(fixture.persistent, - PersistentPhysicsRuntimeSnapshot.capture(fixture.runtime)); - - assertTrue(result.synced()); - assertEquals(1, result.spaces()); - assertEquals(1, result.bodies()); - assertEquals(0, result.joints()); - assertEquals(1, fixture.persistent.getSpaceCount()); - assertEquals(1, fixture.persistent.getBodyCount()); - assertEquals(0, fixture.persistent.getJointCount()); - assertEquals(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, - fixture.persistent.getWorldSettings().getStepSchedulingMode()); - assertEquals(PhysicsEventCollectionMode.CONTACTS, - fixture.persistent.getWorldSettings().getEventCollectionMode()); - } - - @Test - void runtimeSnapshotCaptureUsesLiveBodyPoseWhenReaderSnapshotIsStale() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsBody body = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - body.setPosition(1.0f, 2.0f, 3.0f); - RigidBodyKey bodyKey = fixture.runtime.addBody(fixture.space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - fixture.runtime.refreshBodySnapshots(); - - body.setPosition(9.0f, 8.0f, 7.0f); - - PhysicsBodySnapshot readerSnapshot = fixture.runtime.getBodySnapshot(bodyKey); - assertEquals(1.0f, readerSnapshot.positionX(), 0.0001f); - assertEquals(2.0f, readerSnapshot.positionY(), 0.0001f); - assertEquals(3.0f, readerSnapshot.positionZ(), 0.0001f); - - PersistentPhysicsRuntimeSnapshot snapshot = - PersistentPhysicsRuntimeSnapshot.capture(fixture.runtime); - PersistentPhysicsBodyState state = snapshot.getBodies()[0]; - - assertEquals(9.0f, state.getPosition().x, 0.0001f); - assertEquals(8.0f, state.getPosition().y, 0.0001f); - assertEquals(7.0f, state.getPosition().z, 0.0001f); - } - - @Test - void explicitRuntimeSnapshotSyncSkipsPendingRestore() { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.persistent.markRuntimeRestorePending(); - - PersistentPhysicsWorldSyncSystem.SyncResult result = - PersistentPhysicsWorldSyncSystem.syncRuntimeSnapshot(fixture.persistent, - PersistentPhysicsRuntimeSnapshot.capture(fixture.runtime)); - - assertFalse(result.synced()); - assertEquals("restore pending", result.skippedReason()); - } - - @Test - void explicitRuntimeSnapshotSyncSkipsFailedRestore() { - RuntimeFixture fixture = createRuntimeFixture(); - fixture.persistent.failRuntimeRestore("bad persisted state"); - - PersistentPhysicsWorldSyncSystem.SyncResult result = - PersistentPhysicsWorldSyncSystem.syncRuntimeSnapshot(fixture.persistent, - PersistentPhysicsRuntimeSnapshot.capture(fixture.runtime)); - - assertFalse(result.synced()); - assertEquals("restore failed", result.skippedReason()); - } - - @Test - void softRestoreSkipsDoNotBlockRuntimeSnapshotTickPolicy() { - RuntimeFixture fixture = createRuntimeFixture(); - - fixture.persistent.recordRuntimeBodySkipped("missing entity"); - - assertFalse(PersistentPhysicsWorldSyncSystem.shouldSkipRuntimeSnapshotTick(fixture.persistent)); - - fixture.persistent.markRuntimeRestorePending(); - - assertTrue(PersistentPhysicsWorldSyncSystem.shouldSkipRuntimeSnapshotTick(fixture.persistent)); - } - - @Test - void runtimeSnapshotSyncSkipsChangedRestoreGeneration() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsBody body = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - fixture.runtime.addBody(fixture.space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PersistentPhysicsRuntimeSnapshot snapshot = - PersistentPhysicsRuntimeSnapshot.capture(fixture.runtime); - long capturedGeneration = fixture.persistent.runtimeRestoreGeneration(); - - fixture.persistent.markRuntimeRestorePending(); - fixture.persistent.clearRuntimeRestorePending(); - - PersistentPhysicsWorldSyncSystem.SyncResult result = - PersistentPhysicsWorldSyncSystem.syncRuntimeSnapshot(fixture.persistent, - snapshot, - capturedGeneration); - - assertFalse(result.synced()); - assertEquals("restore generation changed", result.skippedReason()); - assertEquals(0, fixture.persistent.getBodyCount()); - } - - @Test - void bodyHydrationChecksLiveRegistrationBeforeCreatingBackendBody() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsBody body = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - RigidBodyKey bodyKey = fixture.runtime.addBody(fixture.space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PersistentPhysicsBodyState state = fixture.bodyState(bodyKey); - - PersistentPhysicsBodyHydrationSystem.RestoreBodyResult result = - PersistentPhysicsBodyHydrationSystem.restoreBodyOnOwner(fixture.runtime, state, bodyKey); - - assertEquals(PersistentPhysicsBodyHydrationSystem.RestoreBodyResult.ALREADY_REGISTERED, result); - assertEquals(1, fixture.space.bodyCount()); - } - - @Test - void bodyHydrationRemovesBackendBodyWhenRegistrationFails() { - BackendRuntimeFixture fixture = createBackendRuntimeFixture("body-cleanup"); - FailingRegistrationResource runtime = fixture.runtime(); - RigidBodyKey bodyKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000101")); - PersistentPhysicsBodyState state = persistentBodyState(fixture.spaceId(), bodyKey); - runtime.failBodyRegistration = true; - - assertThrows(IllegalStateException.class, - () -> PersistentPhysicsBodyHydrationSystem.restoreBodyOnOwner(runtime, state, bodyKey)); - - assertEquals(0, fixture.backendRuntime().bodyCount(fixture.spaceId().value())); - } - - @Test - void jointHydrationRemovesBackendJointWhenRegistrationFails() { - BackendRuntimeFixture fixture = createBackendRuntimeFixture("joint-cleanup"); - FailingRegistrationResource runtime = fixture.runtime(); - RigidBodyKey bodyAKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000102")); - RigidBodyKey bodyBKey = RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000103")); - PhysicsSpaceBinding binding = runtime.requireSpaceBinding(fixture.spaceId()); - BackendBodyHandle bodyAHandle = createBackendBox(binding); - BackendBodyHandle bodyBHandle = createBackendBox(binding); - runtime.addBodyOnOwner(bodyAKey, - fixture.spaceId(), - bodyAHandle, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - runtime.addBodyOnOwner(bodyBKey, - fixture.spaceId(), - bodyBHandle, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PersistentPhysicsJointState state = new PersistentPhysicsJointState(); - state.setSpaceId(fixture.spaceId().value()); - state.setBodyAKey(bodyAKey); - state.setBodyBKey(bodyBKey); - state.setType(PhysicsJointType.FIXED); - runtime.failJointRegistration = true; - - assertThrows(IllegalStateException.class, - () -> PersistentPhysicsRuntimeSupport.createJoint(runtime, - binding, - state, - bodyAKey, - runtime.requireBodyRegistration(bodyAKey), - bodyBKey, - runtime.requireBodyRegistration(bodyBKey))); - - assertEquals(0, fixture.backendRuntime().jointCount(fixture.spaceId().value())); - } - - @Test - void persistentBodyStateCapturesRuntimeMaterialAndCollisionSettings() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsBody body = fixture.space.createBox(1.0f, 2.0f, 3.0f, 7.0f); - body.setFriction(0.65f); - body.setRestitution(0.15f); - body.setDamping(0.2f, 0.3f); - body.setCollisionFilter(PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - body.setContinuousCollisionEnabled(true); - RigidBodyKey bodyKey = fixture.runtime.addBody(fixture.space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - PersistentPhysicsBodyState state = fixture.bodyState(bodyKey); - - assertEquals(7.0f, state.getMass(), 0.0001f); - assertEquals(0.65f, state.getFriction(), 0.0001f); - assertEquals(0.15f, state.getRestitution(), 0.0001f); - assertEquals(0.2f, state.getLinearDamping(), 0.0001f); - assertEquals(0.3f, state.getAngularDamping(), 0.0001f); - assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, state.getCollisionGroup()); - assertEquals(PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY, - state.getCollisionMask()); - assertTrue(state.isContinuousCollisionEnabled()); - } - - @Test - void restoreTerrainPrewarmTargetsOnlyRestoredDynamicBodies() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsBody fallingBody = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - fallingBody.setPosition(12.0f, 70.0f, -4.0f); - PhysicsBody staticBody = fixture.space.createBox(0.5f, 0.5f, 0.5f, 0.0f); - staticBody.setBodyType(PhysicsBodyType.STATIC); - staticBody.setPosition(40.0f, 64.0f, 40.0f); - RigidBodyKey fallingBodyKey = fixture.runtime.addBody(fixture.space.id(), - fallingBody, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - RigidBodyKey staticBodyKey = fixture.runtime.addBody(fixture.space.id(), - staticBody, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PersistentPhysicsBodyState fallingState = fixture.bodyState(fallingBodyKey); - PersistentPhysicsBodyState staticState = fixture.bodyState(staticBodyKey); - assertEquals(PhysicsBodyType.DYNAMIC, fallingState.getBodyType()); - assertEquals(PhysicsBodyType.STATIC, staticState.getBodyType()); - - Map> targets = - PersistentPhysicsRestoreTerrainPrewarm.dynamicPrewarmTargetsBySpace( - new PersistentPhysicsBodyState[] { - fallingState, - staticState - }, - 4); - - assertEquals(List.of(new Vector3d(12.0, 70.0, -4.0)), - targets.get(fixture.space.id().value())); - } - - @Test - void restoreTerrainPrewarmExpandsDownwardForFallingBodies() { - RuntimeFixture fixture = createRuntimeFixture(); - PhysicsBody fallingBody = fixture.space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - fallingBody.setPosition(12.0f, 20.0f, -4.0f); - fallingBody.setLinearVelocity(0.0f, -12.0f, 0.0f); - RigidBodyKey fallingBodyKey = fixture.runtime.addBody(fixture.space.id(), - fallingBody, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - PersistentPhysicsBodyState fallingState = fixture.bodyState(fallingBodyKey); - - Map> targets = - PersistentPhysicsRestoreTerrainPrewarm.dynamicPrewarmTargetsBySpace( - new PersistentPhysicsBodyState[] {fallingState}, - 4); - - assertEquals(List.of(new Vector3d(12.0, 20.0, -4.0), - new Vector3d(12.0, 12.0, -4.0), - new Vector3d(12.0, 4.0, -4.0)), - targets.get(fixture.space.id().value())); - } - - private static RuntimeFixture createRuntimeFixture() { - FakePhysicsBackend backend = - new FakePhysicsBackend("test:persistence-sync-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - - LegacyLiveHandleTestResource runtime = new LegacyLiveHandleTestResource(); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - PhysicsSpace space = runtime.createLiveSpace(backend.getId(), - "test-world", - settings); - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - return new RuntimeFixture(runtime, persistent, space); - } - - private static BackendRuntimeFixture createBackendRuntimeFixture(@Nonnull String name) { - BackendId backendId = - new BackendId("test:persistence-" + name + "-" + BACKEND_COUNTER.incrementAndGet()); - FakePhysicsBackendRuntimeProvider provider = - new FakePhysicsBackendRuntimeProvider(backendId, false, false); - Impulse.registerRuntimeProvider(provider); - FailingRegistrationResource runtime = new FailingRegistrationResource(); - SpaceId spaceId = runtime.createSpace(backendId, - "test-world", - PhysicsSpaceSettings.defaults()); - return new BackendRuntimeFixture(runtime, provider.createdRuntimes().getFirst(), spaceId); - } - - @Nonnull - private static PersistentPhysicsBodyState persistentBodyState(@Nonnull SpaceId spaceId, - @Nonnull RigidBodyKey bodyKey) { - PhysicsBodyRegistration registration = new PhysicsBodyRegistration(bodyKey, - new BackendBodyHandle(1L), - spaceId, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - return PersistentPhysicsBodyState.from(registration, - new PhysicsBodySnapshot(new Vector3f(), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - PhysicsBodyType.DYNAMIC, - false, - false, - 1.0f, - ShapeType.BOX, - new Vector3f(0.5f, 0.5f, 0.5f), - 0.0f, - 0.0f, - PhysicsAxis.Y)); - } - - @Nonnull - private static BackendBodyHandle createBackendBox(@Nonnull PhysicsSpaceBinding binding) { - return new BackendBodyHandle(binding.runtime().createBody(binding.backendSpaceHandle().value(), - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 1.0f, - BackendRuntimeCodes.BODY_DYNAMIC, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f)); - } - - private record RuntimeFixture(LegacyLiveHandleTestResource runtime, - PersistentPhysicsWorldResource persistent, - PhysicsSpace space) { - - private void syncPersistentSpaces() { - persistent.setSpaces(new PersistentPhysicsSpaceState[] { - PersistentPhysicsSpaceState.from(runtime.requireSpaceBinding(space.id()), - runtime.getSpaceSettings(space.id())) - }); - } - - private PersistentPhysicsBodyState bodyState(RigidBodyKey bodyKey) { - return PersistentPhysicsBodyState.from(runtime.requireBodyRegistration(bodyKey), - runtime.getBodySnapshot(bodyKey)); - } - } - - private record BackendRuntimeFixture(@Nonnull FailingRegistrationResource runtime, - @Nonnull FakePhysicsBackendRuntime backendRuntime, - @Nonnull SpaceId spaceId) { - } - - private static final class FailingRegistrationResource extends PhysicsWorldRuntimeResource { - - private boolean failBodyRegistration; - private boolean failJointRegistration; - - @Nonnull - @Override - public RigidBodyKey addBodyOnOwner(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - if (failBodyRegistration) { - throw new IllegalStateException("forced body registration failure"); - } - return super.addBodyOnOwner(bodyKey, spaceId, backendBodyHandle, kind, persistenceMode); - } - - @Nonnull - @Override - public JointKey addJointOnOwner(@Nonnull JointKey jointKey, - @Nonnull SpaceId spaceId, - @Nonnull BackendJointHandle backendJointHandle, - @Nonnull RigidBodyKey bodyA, - @Nonnull RigidBodyKey bodyB, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - if (failJointRegistration) { - throw new IllegalStateException("forced joint registration failure"); - } - return super.addJointOnOwner(jointKey, - spaceId, - backendJointHandle, - bodyA, - bodyB, - type, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce); - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipelineTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipelineTest.java deleted file mode 100644 index 789dee39..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsPublicationPipelineTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.publication; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCommand; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.Test; - -class PhysicsPublicationPipelineTest { - - @Test - void completedMutationsDrainThroughPublicationPipelineCap() throws Exception { - AtomicInteger mutations = new AtomicInteger(); - int mutationCount = 80; - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane()) { - owner.start("publication-pipeline-mutation-drain-test"); - for (int index = 0; index < mutationCount; index++) { - owner.submitMutation("test mutation " + index, () -> { - mutations.incrementAndGet(); - return PhysicsOwnerSnapshot.empty(); - }); - } - - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - while (System.nanoTime() < deadline && owner.pendingCommands() > 0) { - Thread.sleep(10L); - } - - assertEquals(64, PhysicsPublicationPipeline.publishCompletedMutations(owner)); - assertEquals(16, PhysicsPublicationPipeline.publishCompletedMutations(owner)); - assertEquals(mutationCount, mutations.get()); - } - } - - @Test - void completedStepRecordsPreStepDrainBackpressure() throws Exception { - CountDownLatch activeMutationStarted = new CountDownLatch(1); - CountDownLatch releaseActiveMutation = new CountDownLatch(1); - CountDownLatch preCutoffMutationStarted = new CountDownLatch(1); - CountDownLatch releasePreCutoffMutation = new CountDownLatch(1); - CountDownLatch releasePostCutoffMutation = new CountDownLatch(1); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - profiling.setEnabled(true); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane()) { - owner.start("publication-pipeline-pre-step-drain-test"); - owner.submitMutation("active mutation", () -> { - activeMutationStarted.countDown(); - assertTrue(releaseActiveMutation.await(2L, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - assertTrue(activeMutationStarted.await(2L, TimeUnit.SECONDS)); - - owner.submitMutation("pre-cutoff mutation", () -> { - preCutoffMutationStarted.countDown(); - assertTrue(releasePreCutoffMutation.await(2L, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - - assertTrue(owner.submitStepIfIdle(new PhysicsOwnerStepCommand(resource, - 0.05f, - false, - 1L, - 1L))); - - owner.submitMutation("post-cutoff mutation", () -> { - assertTrue(releasePostCutoffMutation.await(2L, TimeUnit.SECONDS)); - return PhysicsOwnerSnapshot.empty(); - }); - - releaseActiveMutation.countDown(); - assertTrue(preCutoffMutationStarted.await(2L, TimeUnit.SECONDS)); - releasePreCutoffMutation.countDown(); - - PhysicsEventFrame eventFrame = publishStepWhenReady(owner, resource, profiling); - - assertNotNull(eventFrame); - assertEquals(1, profiling.getCumulativeStep().getTickSamples()); - assertEquals(1, profiling.getCumulativeStep().getPreStepDrainedMutations()); - assertTrue(profiling.getCumulativeStep().getPreStepDrainRunNanos() > 0L); - assertEquals(1, profiling.getCumulativeStep().getLateMutationBacklogAtStep()); - assertEquals(1, profiling.getCumulativeStep().getMaxLateMutationBacklogAtStep()); - - releasePostCutoffMutation.countDown(); - assertEquals(3, waitForCompletedMutations(owner, 3)); - } finally { - releaseActiveMutation.countDown(); - releasePreCutoffMutation.countDown(); - releasePostCutoffMutation.countDown(); - } - } - - private static PhysicsEventFrame publishStepWhenReady(TestPhysicsOwnerLane owner, - LegacyLiveHandleTestResource resource, - PhysicsRuntimeProfilingResource profiling) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - PhysicsEventFrame eventFrame = null; - while (System.nanoTime() < deadline && eventFrame == null) { - eventFrame = PhysicsPublicationPipeline.publishCompletedStep(owner, - resource, - profiling, - 1L); - if (eventFrame == null) { - Thread.sleep(10L); - } - } - return eventFrame; - } - - private static int waitForCompletedMutations(TestPhysicsOwnerLane owner, - int expected) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - int completions = 0; - while (System.nanoTime() < deadline && completions < expected) { - completions += PhysicsPublicationPipeline.publishCompletedMutations(owner); - if (completions < expected) { - Thread.sleep(10L); - } - } - return completions; - } - -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystemTest.java deleted file mode 100644 index c154e96a..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsSnapshotPublicationSystemTest.java +++ /dev/null @@ -1,355 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.publication; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemGroupDependency; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsChunkBoundarySystem; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsCollisionLodSystem; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsWorldCollisionStreamingSystem; -import dev.hytalemodding.impulse.core.internal.systems.persistence.PersistentPhysicsWorldSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsDetachedVisualMaterializationSystem; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerBridge; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCommand; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsSnapshotPublicationSystemTest { - - @Test - void publicationDependenciesKeepReadersBehindSnapshotApply() throws Exception { - Set> dependencies = - new PhysicsSnapshotPublicationSystem().getDependencies(); - - assertNoSystemDependency(dependencies, PhysicsCollisionLodSystem.class); - assertNoSystemDependency(dependencies, PhysicsChunkBoundarySystem.class); - assertNoSystemDependency(dependencies, PhysicsWorldCollisionStreamingSystem.class); - assertSystemDependency(dependencies, - Order.BEFORE, - PhysicsDetachedVisualMaterializationSystem.class); - assertSystemDependency(dependencies, Order.BEFORE, PhysicsSyncSystem.class); - assertSystemDependency(new PhysicsCollisionLodSystem().getDependencies(), - Order.AFTER, - PhysicsSnapshotPublicationSystem.class); - assertSystemDependency(new PhysicsWorldCollisionStreamingSystem().getDependencies(), - Order.AFTER, - PhysicsSnapshotPublicationSystem.class); - assertSystemDependency(new PersistentPhysicsWorldSyncSystem().getDependencies(), - Order.AFTER, - PhysicsSnapshotPublicationSystem.class); - withTestImpulsePlugin(() -> { - assertSystemDependency(new PhysicsChunkBoundarySystem().getDependencies(), - Order.AFTER, - PhysicsSnapshotPublicationSystem.class); - assertSystemGroupDependency( - new PhysicsDetachedVisualMaterializationSystem().getDependencies(), - Order.AFTER, - ImpulsePlugin.get().getPersistenceRestoreGroup()); - }); - } - - @Test - void completedOwnerStepPublishesSnapshotWithoutDrain() throws Exception { - BackendId backendId = new BackendId("test:async-publication"); - Impulse.registerBackend(new FakePhysicsBackend(backendId)); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - var settings = resource.getWorldSettings(); - settings.setStepMode(PhysicsStepMode.FIXED); - settings.setSimulationSteps(1); - settings.setEventCollectionMode(PhysicsEventCollectionMode.CONTACTS); - resource.setWorldSettings(settings); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - profiling.setEnabled(true); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane()) { - owner.start("async-publication-test"); - resource.attachOwnerExecutor(owner); - PhysicsSpace space = resource.createLiveSpace(backendId, - "async-publication-test", - PhysicsSpaceSettings.defaults()); - FakePhysicsBackend.InMemoryPhysicsSpace inMemorySpace = - (FakePhysicsBackend.InMemoryPhysicsSpace) space; - AtomicReference bodyRef = new AtomicReference<>(); - RigidBodyKey bodyId = PhysicsOwnerBridge.call(owner, - "create async publication body", - () -> { - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - body.setPosition(1.0f, 2.0f, 3.0f); - bodyRef.set(body); - return resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - }); - PhysicsBody secondBody = PhysicsOwnerBridge.call(owner, - "create async publication contact body", - () -> { - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - return body; - }); - inMemorySpace.addContact(new PhysicsContact(bodyRef.get(), - secondBody, - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(4.0f, 5.0f, 6.0f), - new Vector3f(0.0f, 1.0f, 0.0f), - -0.125f, - 2.5f)); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), - resource.getBodySnapshot(bodyId).position()); - - PhysicsOwnerBridge.run(owner, "move async publication body", - () -> bodyRef.get().setPosition(4.0f, 5.0f, 6.0f)); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), - resource.getBodySnapshot(bodyId).position()); - - PhysicsOwnerStepCommand command = new PhysicsOwnerStepCommand(resource, - 0.05f, - true, - 1L, - 1L); - assertTrue(owner.submitStepIfIdle(command)); - assertFalse(owner.submitStepIfIdle(new PhysicsOwnerStepCommand(resource, - 0.05f, - false, - 2L, - 2L))); - - PhysicsEventFrame eventFrame = publishWhenReady(owner, resource, profiling); - - PhysicsBodySnapshot snapshot = resource.getBodySnapshot(bodyId); - assertEquals(new Vector3f(4.0f, 5.0f, 6.0f), snapshot.position()); - assertEquals(1, eventFrame.physicsEventCount()); - assertEquals(1, profiling.getCumulativeStep().getTickSamples()); - assertEquals(2, profiling.getCumulativeStep().getBodySnapshots()); - assertTrue(profiling.getCumulativeStep().getOwnerRunNanos() > 0L); - assertTrue(profiling.getCumulativeStep().getOwnerQueuedNanos() >= 0L); - assertFalse(owner.hasPendingStep()); - } - } - - @Test - void publicationSystemDrainsCompletedAsyncMutations() throws Exception { - AtomicInteger mutations = new AtomicInteger(); - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane()) { - owner.start("async-mutation-publication-test"); - owner.submitMutation("test mutation", () -> { - mutations.incrementAndGet(); - return PhysicsOwnerSnapshot.empty(); - }); - - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - int published = 0; - while (System.nanoTime() < deadline && published == 0) { - published += PhysicsSnapshotPublicationSystem.publishCompletedMutations(owner); - if (published == 0) { - Thread.sleep(10L); - } - } - - assertEquals(1, published); - assertEquals(1, mutations.get()); - assertEquals(0, owner.pendingMutations()); - } - } - - @Test - void publicationSystemCapsCompletedAsyncMutationDrainPerTick() throws Exception { - AtomicInteger mutations = new AtomicInteger(); - int mutationCount = 80; - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane()) { - owner.start("async-mutation-publication-cap-test"); - for (int index = 0; index < mutationCount; index++) { - owner.submitMutation("test mutation " + index, () -> { - mutations.incrementAndGet(); - return PhysicsOwnerSnapshot.empty(); - }); - } - - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - while (System.nanoTime() < deadline && owner.pendingCommands() > 0) { - Thread.sleep(10L); - } - assertEquals(0, owner.pendingCommands()); - assertEquals(mutationCount, mutations.get()); - - int firstTickPublished = PhysicsSnapshotPublicationSystem.publishCompletedMutations(owner); - - assertEquals(64, firstTickPublished); - assertEquals(mutationCount - 64, owner.pendingMutations()); - assertEquals(mutationCount - 64, - PhysicsSnapshotPublicationSystem.publishCompletedMutations(owner)); - assertEquals(0, owner.pendingMutations()); - } - } - - @Test - void completedOwnerStepDoesNotRepublishAfterWorldMutation() throws Exception { - BackendId backendId = new BackendId("test:stale-owner-publication"); - Impulse.registerBackend(new FakePhysicsBackend(backendId)); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane()) { - owner.start("stale-owner-publication-test"); - resource.attachOwnerExecutor(owner); - PhysicsSpace space = resource.createLiveSpace(backendId, - "stale-owner-publication-test", - PhysicsSpaceSettings.defaults()); - RigidBodyKey bodyId = PhysicsOwnerBridge.call(owner, - "create stale publication body", - () -> { - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - body.setPosition(1.0f, 2.0f, 3.0f); - return resource.addBody(space.id(), - body, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); - }); - - PhysicsOwnerStepCommand command = new PhysicsOwnerStepCommand(resource, - 0.05f, - false, - 1L, - 1L); - assertTrue(owner.submitStepIfIdle(command)); - waitForPublishedFrame(command); - assertEquals(1, command.publishedFrame().bodyCount()); - - resource.destroyBody(bodyId); - assertEquals(0, resource.getBodySnapshotCount()); - assertNull(resource.getBodyRegistrationView(bodyId)); - - PhysicsEventFrame eventFrame = - PhysicsSnapshotPublicationSystem.publishCompletedStep(owner, resource, profiling); - - assertNull(eventFrame); - assertFalse(owner.hasPendingStep()); - assertEquals(0, resource.getBodySnapshotCount()); - assertNull(resource.getBodyRegistrationView(bodyId)); - resource.detachOwnerExecutor(owner); - } - } - - private static PhysicsEventFrame publishWhenReady(PhysicsOwnerResource owner, - LegacyLiveHandleTestResource resource, - PhysicsRuntimeProfilingResource profiling) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - PhysicsEventFrame eventFrame = null; - while (System.nanoTime() < deadline) { - eventFrame = PhysicsSnapshotPublicationSystem.publishCompletedStep(owner, resource, profiling); - if (!owner.hasPendingStep()) { - return eventFrame; - } - Thread.sleep(10L); - } - eventFrame = PhysicsSnapshotPublicationSystem.publishCompletedStep(owner, resource, profiling); - assertFalse(owner.hasPendingStep()); - return eventFrame; - } - - private static void waitForPublishedFrame(PhysicsOwnerStepCommand command) - throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - while (System.nanoTime() < deadline && command.publishedFrame() == null) { - Thread.sleep(10L); - } - assertTrue(command.publishedFrame() != null); - } - - private static void assertSystemDependency(Set> dependencies, - Order order, - Class systemClass) { - assertTrue(dependencies.stream().anyMatch(dependency -> - dependency.getOrder() == order - && dependency instanceof SystemDependency systemDependency - && systemDependency.getSystemClass().equals(systemClass))); - } - - private static void assertNoSystemDependency(Set> dependencies, - Class systemClass) { - assertFalse(dependencies.stream().anyMatch(dependency -> - dependency instanceof SystemDependency systemDependency - && systemDependency.getSystemClass().equals(systemClass))); - } - - private static void assertSystemGroupDependency(Set> dependencies, - Order order, - Object group) { - assertTrue(dependencies.stream().anyMatch(dependency -> - dependency.getOrder() == order - && dependency instanceof SystemGroupDependency groupDependency - && groupDependency.getGroup().equals(group))); - } - - private static void withTestImpulsePlugin(ThrowingRunnable assertion) throws Exception { - SystemGroup restoreGroup = testSystemGroup(); - ImpulsePlugin plugin = TestInstanceFactory.impulsePlugin(); - Field groupField = ImpulsePlugin.class.getDeclaredField("persistenceRestoreGroup"); - groupField.setAccessible(true); - groupField.set(plugin, restoreGroup); - - Field instanceField = ImpulsePlugin.class.getDeclaredField("instance"); - instanceField.setAccessible(true); - Object previous = instanceField.get(null); - instanceField.set(null, plugin); - try { - assertion.run(); - } finally { - instanceField.set(null, previous); - } - } - - @SuppressWarnings("unchecked") - private static SystemGroup testSystemGroup() throws Exception { - Class componentRegistry = Class.forName("com.hypixel.hytale.component.ComponentRegistry"); - Constructor constructor = - SystemGroup.class.getDeclaredConstructor(componentRegistry, int.class, Set.class); - constructor.setAccessible(true); - return (SystemGroup) constructor.newInstance(null, 1, Set.of()); - } - - @FunctionalInterface - private interface ThrowingRunnable { - - void run() throws Exception; - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGateTest.java deleted file mode 100644 index 209e3781..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepRestoreGateTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.step; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import org.junit.jupiter.api.Test; - -class PhysicsStepRestoreGateTest { - - @Test - void allowsStepSubmissionWhenRestoreIsSettled() { - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - - assertTrue(PhysicsStepRestoreGate.canSubmitStep(persistent)); - } - - @Test - void blocksStepSubmissionDuringPendingOrFailedRestore() { - PersistentPhysicsWorldResource pending = new PersistentPhysicsWorldResource(); - pending.markRuntimeRestorePending(); - PersistentPhysicsWorldResource failed = new PersistentPhysicsWorldResource(); - failed.markRuntimeRestorePending(); - failed.failRuntimeRestore("test failure"); - - assertFalse(PhysicsStepRestoreGate.canSubmitStep(pending)); - assertFalse(PhysicsStepRestoreGate.canSubmitStep(failed)); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystemTest.java deleted file mode 100644 index 8267974d..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/step/PhysicsStepSystemTest.java +++ /dev/null @@ -1,631 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.step; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsRayHit; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.TestPhysicsOwnerLane; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerResource; -import dev.hytalemodding.impulse.core.internal.resources.owner.PhysicsOwnerStepCompletion; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.joml.Vector3f; - -class PhysicsStepSystemTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void submittedStepKeepsSchedulerSequenceSeparateFromServerTick() throws Exception { - PhysicsStepSystem system = new PhysicsStepSystem(); - PhysicsStepSystem.StepSchedulerState state = new PhysicsStepSystem.StepSchedulerState(); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, - Duration.ofSeconds(2L))) { - owner.start("step-system-sequence-test"); - resource.attachOwnerExecutor(owner); - - // Owner steps run detached from the scheduling tick, so the published - // frame must preserve the scheduling metadata captured at submission. - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 42L); - - PhysicsOwnerStepCompletion completion = pollCompletedStep(owner); - PublishedPhysicsSnapshotFrame frame = completion.frame(); - assertNotNull(frame); - assertEquals(1L, frame.stepSequence()); - assertEquals(42L, frame.serverTick()); - } - } - - @Test - void pendingOwnerStepAccumulatesElapsedDtForNextSubmission() throws Exception { - PhysicsStepSystem system = new PhysicsStepSystem(); - PhysicsStepSystem.StepSchedulerState state = new PhysicsStepSystem.StepSchedulerState(); - RecordingBackend backend = registerBackend(); - LegacyLiveHandleTestResource resource = fixedStepResource(backend); - configureWorldSettings(resource, settings -> { - settings.setStepSchedulingMode(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT); - settings.setStepMode(PhysicsStepMode.PROGRESSIVE_REFINEMENT); - settings.setMaxStepDt(1.0f); - }); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - RecordingSpace space = backend.space(); - CountDownLatch stepStarted = new CountDownLatch(1); - CountDownLatch releaseStep = new CountDownLatch(1); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, - Duration.ofSeconds(2L))) { - owner.start("step-system-accumulated-dt-test"); - resource.attachOwnerExecutor(owner); - space.blockNextStep(stepStarted, releaseStep); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 1L); - assertTrue(stepStarted.await(2, TimeUnit.SECONDS)); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 2L); - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 3L); - - releaseStep.countDown(); - pollCompletedStep(owner); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 4L); - PhysicsOwnerStepCompletion completion = pollCompletedStep(owner); - - assertEquals(2, space.stepDts.size()); - assertEquals(0.05f, space.stepDts.get(0), 0.00001f); - assertEquals(0.15f, space.stepDts.get(1), 0.00001f); - PublishedPhysicsSnapshotFrame frame = completion.frame(); - assertNotNull(frame); - assertEquals(2L, frame.stepSequence()); - assertEquals(4L, frame.serverTick()); - } finally { - releaseStep.countDown(); - } - } - - @Test - void accumulatedDtIsCappedAndDroppedBacklogIsProfiled() throws Exception { - PhysicsStepSystem system = new PhysicsStepSystem(); - PhysicsStepSystem.StepSchedulerState state = new PhysicsStepSystem.StepSchedulerState(); - RecordingBackend backend = registerBackend(); - LegacyLiveHandleTestResource resource = fixedStepResource(backend); - configureWorldSettings(resource, settings -> { - settings.setStepSchedulingMode(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT); - settings.setMaxStepDt(0.05f); - }); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - profiling.setEnabled(true); - RecordingSpace space = backend.space(); - CountDownLatch stepStarted = new CountDownLatch(1); - CountDownLatch releaseStep = new CountDownLatch(1); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, - Duration.ofSeconds(2L))) { - owner.start("step-system-capped-dt-test"); - resource.attachOwnerExecutor(owner); - space.blockNextStep(stepStarted, releaseStep); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 1L); - assertTrue(stepStarted.await(2, TimeUnit.SECONDS)); - for (int tick = 2; tick < 12; tick++) { - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, tick); - } - - releaseStep.countDown(); - pollCompletedStep(owner); - - system.submitStepIfIdle(state, owner, resource, 0.0f, profiling, 12L); - pollCompletedStep(owner); - - assertEquals(2, space.stepDts.size()); - assertEquals(0.05f, space.stepDts.get(1), 0.00001f); - assertTrue(profiling.getCumulativeStep().getDtCapHits() > 0); - assertTrue(profiling.getCumulativeStep().getDroppedBacklogTicks() > 0); - assertTrue(profiling.getCumulativeStep().getDroppedBacklogDtNanos() > 0L); - long maxBacklogDtNanos = - profiling.getCumulativeStep().getMaxSchedulerBacklogDtNanos(); - assertTrue(Math.abs(maxBacklogDtNanos - 50_000_000L) <= 1L); - } finally { - releaseStep.countDown(); - } - } - - @Test - void fixedAndCcdAccumulatedDtCapUsesConfiguredStepBudget() { - LegacyLiveHandleTestResource fixed = new LegacyLiveHandleTestResource(); - configureWorldSettings(fixed, settings -> { - settings.setStepMode(PhysicsStepMode.FIXED); - settings.setSimulationSteps(3); - settings.setMaxStepDt(0.02f); - }); - - LegacyLiveHandleTestResource ccd = new LegacyLiveHandleTestResource(); - configureWorldSettings(ccd, settings -> { - settings.setStepMode(PhysicsStepMode.CCD); - settings.setSimulationSteps(2); - settings.setMaxStepDt(0.02f); - }); - - assertEquals(0.06f, PhysicsStepSystem.maxAccumulatedStepDt(fixed), 0.00001f); - assertEquals(0.04f, PhysicsStepSystem.maxAccumulatedStepDt(ccd), 0.00001f); - } - - @Test - void nonFinitePendingDtDoesNotPoisonAccumulator() throws Exception { - PhysicsStepSystem system = new PhysicsStepSystem(); - PhysicsStepSystem.StepSchedulerState state = new PhysicsStepSystem.StepSchedulerState(); - RecordingBackend backend = registerBackend(); - LegacyLiveHandleTestResource resource = fixedStepResource(backend); - configureWorldSettings(resource, settings -> { - settings.setStepSchedulingMode(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT); - settings.setMaxStepDt(0.05f); - }); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - RecordingSpace space = backend.space(); - CountDownLatch stepStarted = new CountDownLatch(1); - CountDownLatch releaseStep = new CountDownLatch(1); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, - Duration.ofSeconds(2L))) { - owner.start("step-system-safe-dt-test"); - resource.attachOwnerExecutor(owner); - space.blockNextStep(stepStarted, releaseStep); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 1L); - assertTrue(stepStarted.await(2, TimeUnit.SECONDS)); - system.submitStepIfIdle(state, owner, resource, Float.NaN, profiling, 2L); - system.submitStepIfIdle(state, owner, resource, Float.POSITIVE_INFINITY, profiling, 3L); - system.submitStepIfIdle(state, owner, resource, -1.0f, profiling, 4L); - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 5L); - - releaseStep.countDown(); - pollCompletedStep(owner); - - system.submitStepIfIdle(state, owner, resource, 0.0f, profiling, 6L); - pollCompletedStep(owner); - - assertEquals(2, space.stepDts.size()); - assertEquals(0.05f, space.stepDts.get(1), 0.00001f); - } finally { - releaseStep.countDown(); - } - } - - @Test - void dropPendingDtPolicySubmitsOnlyCurrentTickDtAfterPendingStep() throws Exception { - PhysicsStepSystem system = new PhysicsStepSystem(); - PhysicsStepSystem.StepSchedulerState state = new PhysicsStepSystem.StepSchedulerState(); - RecordingBackend backend = registerBackend(); - LegacyLiveHandleTestResource resource = fixedStepResource(backend); - configureWorldSettings(resource, - settings -> settings.setStepSchedulingMode(PhysicsStepSchedulingMode.DROP_PENDING_DT)); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - profiling.setEnabled(true); - RecordingSpace space = backend.space(); - CountDownLatch stepStarted = new CountDownLatch(1); - CountDownLatch releaseStep = new CountDownLatch(1); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, - Duration.ofSeconds(2L))) { - owner.start("step-system-drop-pending-dt-test"); - resource.attachOwnerExecutor(owner); - space.blockNextStep(stepStarted, releaseStep); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 1L); - assertTrue(stepStarted.await(2, TimeUnit.SECONDS)); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 2L); - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 3L); - - releaseStep.countDown(); - pollCompletedStep(owner); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 4L); - PhysicsOwnerStepCompletion completion = pollCompletedStep(owner); - - assertEquals(2, space.stepDts.size()); - assertEquals(0.05f, space.stepDts.get(0), 0.00001f); - assertEquals(0.05f, space.stepDts.get(1), 0.00001f); - assertEquals(0, profiling.getCumulativeStep().getSchedulerSamples()); - assertEquals(2, profiling.getCumulativeStep().getSkippedPendingSteps()); - PublishedPhysicsSnapshotFrame frame = completion.frame(); - assertNotNull(frame); - assertEquals(2L, frame.stepSequence()); - assertEquals(4L, frame.serverTick()); - } finally { - releaseStep.countDown(); - } - } - - @Test - void restoreGuardSkipsStepSubmissionAndClearsAccumulatedDt() throws Exception { - PhysicsStepSystem system = new PhysicsStepSystem(); - PhysicsStepSystem.StepSchedulerState state = new PhysicsStepSystem.StepSchedulerState(); - RecordingBackend backend = registerBackend(); - LegacyLiveHandleTestResource resource = fixedStepResource(backend); - configureWorldSettings(resource, settings -> { - settings.setStepSchedulingMode(PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT); - settings.setMaxStepDt(1.0f); - }); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - RecordingSpace space = backend.space(); - CountDownLatch stepStarted = new CountDownLatch(1); - CountDownLatch releaseStep = new CountDownLatch(1); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, - Duration.ofSeconds(2L))) { - owner.start("step-system-restore-guard-test"); - resource.attachOwnerExecutor(owner); - space.blockNextStep(stepStarted, releaseStep); - - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 1L); - assertTrue(stepStarted.await(2, TimeUnit.SECONDS)); - system.submitStepIfIdle(state, owner, resource, 0.05f, profiling, 2L); - - persistent.markRuntimeRestorePending(); - assertFalse(system.submitStepIfRestoreReady(state, - persistent, - owner, - resource, - 0.05f, - profiling, - 3L)); - - releaseStep.countDown(); - pollCompletedStep(owner); - - system.submitStepIfIdle(state, owner, resource, 0.0f, profiling, 4L); - pollCompletedStep(owner); - - assertEquals(2, space.stepDts.size()); - assertEquals(0.05f, space.stepDts.get(0), 0.00001f); - assertEquals(0.0f, space.stepDts.get(1), 0.00001f); - } finally { - releaseStep.countDown(); - } - } - - @Test - void failedRestoreGuardSkipsStepSubmission() { - PhysicsStepSystem system = new PhysicsStepSystem(); - PhysicsStepSystem.StepSchedulerState state = new PhysicsStepSystem.StepSchedulerState(); - RecordingBackend backend = registerBackend(); - LegacyLiveHandleTestResource resource = fixedStepResource(backend); - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - persistent.failRuntimeRestore("bad persisted state"); - - try (TestPhysicsOwnerLane owner = new TestPhysicsOwnerLane(2, - Duration.ofSeconds(2L))) { - assertFalse(system.submitStepIfRestoreReady(state, - persistent, - owner, - resource, - 0.05f, - profiling, - 1L)); - assertFalse(owner.hasPendingStep()); - } - } - - @Nonnull - private static RecordingBackend registerBackend() { - RecordingBackend backend = new RecordingBackend("test:step-system-" - + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(backend); - return backend; - } - - @Nonnull - private static LegacyLiveHandleTestResource fixedStepResource(@Nonnull RecordingBackend backend) { - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - configureWorldSettings(resource, settings -> { - settings.setStepMode(PhysicsStepMode.FIXED); - settings.setSimulationSteps(1); - }); - resource.createSpace(backend.getId(), - "step-system-test", - PhysicsSpaceSettings.defaults()); - return resource; - } - - private static void configureWorldSettings(@Nonnull LegacyLiveHandleTestResource resource, - @Nonnull Consumer configurator) { - PhysicsWorldSettings settings = resource.getWorldSettings(); - configurator.accept(settings); - resource.setWorldSettings(settings); - } - - @Nonnull - private static PhysicsOwnerStepCompletion pollCompletedStep( - @Nonnull PhysicsOwnerResource owner) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2L); - while (System.nanoTime() < deadline) { - PhysicsOwnerStepCompletion completion = owner.pollCompletedStep(); - if (completion != null) { - return completion; - } - Thread.sleep(10L); - } - fail("Timed out waiting for physics step completion"); - throw new AssertionError(); - } - - private static final class RecordingBackend implements PhysicsBackend { - - @Nonnull - private final BackendId id; - private RecordingSpace space; - - private RecordingBackend(@Nonnull String id) { - this.id = new BackendId(id); - } - - @Nonnull - @Override - public BackendId getId() { - return id; - } - - @Override - public void init() { - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return createSpace(SpaceId.next()); - } - - @Nonnull - @Override - public PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - space = new RecordingSpace(spaceId, id); - return space; - } - - @Nonnull - private RecordingSpace space() { - return space; - } - } - - private static final class RecordingSpace implements PhysicsSpace { - - @Nonnull - private final SpaceId id; - @Nonnull - private final BackendId backendId; - @Nonnull - private final Vector3f gravity = new Vector3f(); - @Nonnull - private final List stepDts = new ArrayList<>(); - @Nonnull - private CountDownLatch stepStarted = new CountDownLatch(0); - @Nonnull - private CountDownLatch releaseStep = new CountDownLatch(0); - - private RecordingSpace(@Nonnull SpaceId id, @Nonnull BackendId backendId) { - this.id = id; - this.backendId = backendId; - } - - private void blockNextStep(@Nonnull CountDownLatch stepStarted, - @Nonnull CountDownLatch releaseStep) { - this.stepStarted = stepStarted; - this.releaseStep = releaseStep; - } - - @Nonnull - @Override - public SpaceId id() { - return id; - } - - @Nonnull - @Override - public BackendId backendId() { - return backendId; - } - - @Override - public void step(float dt) { - stepDts.add(dt); - stepStarted.countDown(); - try { - releaseStep.await(2, TimeUnit.SECONDS); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Interrupted while blocking physics step", exception); - } finally { - stepStarted = new CountDownLatch(0); - releaseStep = new CountDownLatch(0); - } - } - - @Override - public void setGravity(float x, float y, float z) { - gravity.set(x, y, z); - } - - @Nonnull - @Override - public Vector3f getGravity() { - return new Vector3f(gravity); - } - - @Override - public void addBody(@Nonnull PhysicsBody body) { - } - - @Override - public void removeBody(@Nonnull PhysicsBody body) { - } - - @Nonnull - @Override - public List getBodies() { - return List.of(); - } - - @Nonnull - @Override - public PhysicsBody createStaticPlane(float groundY) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsBody createBox(float halfX, float halfY, float halfZ, float mass) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsBody createBox(@Nonnull Vector3f halfExtents, float mass) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsBody createSphere(float radius, float mass) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsBody createCapsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsBody createCylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsBody createCone(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public Optional raycastClosest(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return Optional.empty(); - } - - @Nonnull - @Override - public List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return List.of(); - } - - @Nonnull - @Override - public List getContacts() { - return List.of(); - } - - @Nonnull - @Override - public PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeJoint(@Nonnull PhysicsJoint joint) { - } - - @Nonnull - @Override - public List getJoints() { - return List.of(); - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java index 2110b761..0db92664 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java @@ -5,11 +5,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import java.util.UUID; import org.joml.Quaterniond; import org.joml.Quaternionf; import org.joml.Vector3d; @@ -49,15 +49,15 @@ void visualPredictionSecondsStayZeroWhenDisabledOrMissingFrame() { @Test void bodyTransformSyncOnlyAppliesToBodyAuthoritativeAttachments() { - RigidBodyKey bodyKey = RigidBodyKey.random(); + UUID bodyUuid = UUID.randomUUID(); - assertTrue(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyKey.value(), + assertTrue(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY))); - assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyKey.value(), + assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, TransformAuthority.CONTROLLER, AttachmentLifecycle.EXTERNAL_ENTITY))); - assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyKey.value(), + assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, TransformAuthority.ENTITY_KINEMATIC, AttachmentLifecycle.EXTERNAL_ENTITY))); } @@ -118,7 +118,7 @@ void bodyCenterInvertsWorldUpCenterOfMassOffsetAndRotatedLocalOffset() { @Test void attachmentVisualOriginOffsetOverridesBodyShapeOffset() { BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity( - RigidBodyKey.random().value(), + UUID.randomUUID(), new Vector3f(0.0f, -0.5f, 0.0f), new Quaternionf(), 0.5f); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java deleted file mode 100644 index d6d4d8ee..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsDetachedVisualMaterializationSystemTest.java +++ /dev/null @@ -1,230 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsWorldResource; -import dev.hytalemodding.impulse.core.internal.testsupport.LegacyLiveHandleTestResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import java.util.List; -import java.util.Set; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsDetachedVisualMaterializationSystemTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void materializationTreatsEcsGameplayAttachmentSnapshotAsAttachmentWhenRuntimeIndexIsStale() { - RigidBodyKey bodyKey = RigidBodyKey.random(); - RigidBodyKey otherBodyKey = RigidBodyKey.random(); - AtomicBoolean queriedSnapshot = new AtomicBoolean(); - - GameplayAttachmentSnapshot snapshot = GameplayAttachmentSnapshot.fromSource(() -> Set.of(bodyKey)); - assertTrue(snapshot.hasKnownGameplayAttachment(false, bodyKey)); - - GameplayAttachmentSnapshot currentRuntimeIndex = GameplayAttachmentSnapshot.fromSource( - () -> { - queriedSnapshot.set(true); - return Set.of(); - }); - assertTrue(currentRuntimeIndex.hasKnownGameplayAttachment(true, bodyKey)); - assertFalse(queriedSnapshot.get()); - - GameplayAttachmentSnapshot otherSnapshot = GameplayAttachmentSnapshot.fromSource(() -> Set.of(otherBodyKey)); - assertFalse(otherSnapshot.hasKnownGameplayAttachment(false, bodyKey)); - } - - @Test - void impulseOwnedVisualAttachmentIsDisposableWhenBodyMissing() { - BodyAttachmentComponent ownedVisual = BodyAttachmentComponent.impulseOwnedVisual( - RigidBodyKey.random().value(), - new Vector3f(), - new Quaternionf(), - 0.5f); - BodyAttachmentComponent gameplayEntity = BodyAttachmentComponent.externalEntity( - RigidBodyKey.random().value()); - - assertTrue(ownedVisual.shouldRemoveEntityWhenBodyMissing()); - assertFalse(gameplayEntity.shouldRemoveEntityWhenBodyMissing()); - } - - @Test - void scalarVisibleDistanceRespectsRadiusAndViewCone() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualVisibilityCullingEnabled(true); - List interests = List.of( - new PhysicsVisualRuntime.VisualInterest(new Vector3f(), new Vector3f(1.0f, 0.0f, 0.0f))); - - assertEquals(9.0f, - DetachedVisualGeometry.visibleDistanceSquared(3.0f, - 0.0f, - 0.0f, - settings, - interests, - 10.0f)); - assertEquals(Float.POSITIVE_INFINITY, - DetachedVisualGeometry.visibleDistanceSquared(-9.0f, - 0.0f, - 0.0f, - settings, - interests, - 10.0f)); - assertEquals(16.0f, - DetachedVisualGeometry.visibleDistanceSquared(-4.0f, - 0.0f, - 0.0f, - settings, - interests, - 10.0f)); - assertEquals(Float.POSITIVE_INFINITY, - DetachedVisualGeometry.visibleDistanceSquared(11.0f, - 0.0f, - 0.0f, - settings, - interests, - 10.0f)); - } - - @Test - void cachedMaterializationPolicyRejectsCurrentPoseOutsideRadius() { - BackendId backendId = new BackendId("test:visual-policy-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(new FakePhysicsBackend(backendId)); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualMaterializationSettings().setDetachedVisualMaterializationEnabled(true); - settings.getVisualMaterializationSettings().setDetachedVisualRadii(4, 8); - PhysicsSpace space = resource.createLiveSpace(backendId, "test-world", settings); - List interests = List.of( - new PhysicsVisualRuntime.VisualInterest(new Vector3f(), null)); - - DetachedVisualOcclusion.Result result = - PhysicsDetachedVisualMaterializationSystem.resolveCurrentMaterializationPolicy(resource, - RigidBodyKey.random(), - resource.requireSpaceBinding(space.id()), - snapshotAt(9.0f, 0.0f, 0.0f), - settings, - interests, - 1L, - new DetachedVisualOcclusion.RaycastBudget(), - null); - - assertFalse(result.shouldMaterialize()); - assertEquals(Float.POSITIVE_INFINITY, result.distanceSquared()); - } - - @Test - void failedRestoreBlocksDetachedVisualMaterialization() { - PersistentPhysicsWorldResource persistent = new PersistentPhysicsWorldResource(); - - assertFalse(PhysicsDetachedVisualMaterializationSystem.isRestoreBlockingVisuals(persistent)); - - persistent.failRuntimeRestore("bad saved backend"); - - assertTrue(PhysicsDetachedVisualMaterializationSystem.isRestoreBlockingVisuals(persistent)); - } - - @Test - void firstOcclusionFrameKeepsCandidateVisibleWhileRaycastIsPending() { - BackendId backendId = new BackendId("test:visual-occlusion-" + BACKEND_COUNTER.incrementAndGet()); - Impulse.registerBackend(new FakePhysicsBackend(backendId)); - LegacyLiveHandleTestResource resource = new LegacyLiveHandleTestResource(); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualOcclusionMode(VisualOcclusionMode.CULL); - settings.getVisualSyncSettings().setVisualOcclusionRaycastsPerTick(1); - PhysicsSpace space = resource.createLiveSpace(backendId, "test-world", settings); - RigidBodyKey bodyKey = RigidBodyKey.random(); - PhysicsBodySnapshot snapshot = PhysicsBodySnapshot.of(0.0f, - 0.0f, - 4.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - PhysicsBodyType.DYNAMIC, - false, - false, - 0.0f, - ShapeType.BOX, - true, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - PhysicsAxis.Y); - List interests = List.of( - new PhysicsVisualRuntime.VisualInterest(new Vector3f(), null)); - resource.getOrCreateBodyVisualInterestState(bodyKey) - .startPendingRaycast(new CompletableFuture>() - .minimalCompletionStage()); - - DetachedVisualOcclusion.Result result = assertDoesNotThrow(() -> DetachedVisualOcclusion.resolve(resource, - bodyKey, - resource.requireSpaceBinding(space.id()), - snapshot, - settings, - interests, - 16.0f, - 1L, - new DetachedVisualOcclusion.RaycastBudget(), - null)); - - assertTrue(result.shouldMaterialize()); - } - - private static PhysicsBodySnapshot snapshotAt(float x, float y, float z) { - return PhysicsBodySnapshot.of(x, - y, - z, - 0.0f, - 0.0f, - 0.0f, - 1.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - PhysicsBodyType.DYNAMIC, - false, - false, - 0.0f, - ShapeType.BOX, - true, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - PhysicsAxis.Y); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/testsupport/LegacyLiveHandleTestResource.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/testsupport/LegacyLiveHandleTestResource.java deleted file mode 100644 index 471f1cb1..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/testsupport/LegacyLiveHandleTestResource.java +++ /dev/null @@ -1,329 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.testsupport; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; -import dev.hytalemodding.impulse.core.plugin.joint.JointKey; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; -import java.lang.reflect.Field; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Test-only bridge for legacy unit tests that still assert live-object behavior. - */ -public class LegacyLiveHandleTestResource extends PhysicsWorldRuntimeResource { - - private static final Field SPACES = field(LegacyPhysicsBackendRuntime.class, "spaces"); - private static final Field NEXT_BODY_ID = field(LegacyPhysicsBackendRuntime.class, "nextBodyId"); - private static final Field NEXT_JOINT_ID = field(LegacyPhysicsBackendRuntime.class, "nextJointId"); - - @Nonnull - public PhysicsSpace createLiveSpace(@Nonnull BackendId backendId) { - return createLiveSpace(backendId, "test-world", PhysicsSpaceSettings.defaults()); - } - - @Nonnull - public PhysicsSpace createLiveSpace(@Nonnull BackendId backendId, - @Nonnull String worldName) { - return createLiveSpace(backendId, worldName, PhysicsSpaceSettings.defaults()); - } - - @Nonnull - public PhysicsSpace createLiveSpace(@Nonnull BackendId backendId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings) { - SpaceId spaceId = createSpace(backendId, worldName, settings); - return liveSpace(spaceId); - } - - @Nonnull - public RigidBodyKey addBody(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBody body, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return addBody(RigidBodyKey.random(), spaceId, body, kind, persistenceMode); - } - - @Nonnull - public RigidBodyKey addBody(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBody body, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return addBodyOnOwner(bodyKey, spaceId, body, kind, persistenceMode); - } - - @Nonnull - public RigidBodyKey addBodyOnOwner(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBody body, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - RegisteredBody registered = registerLiveBody(spaceId, body); - try { - return super.addBodyOnOwner(bodyKey, spaceId, registered.backendBodyHandle(), kind, persistenceMode); - } catch (RuntimeException exception) { - if (registered.created()) { - unregisterLiveBody(spaceId, registered.backendBodyHandle().value(), body); - } - throw exception; - } - } - - @Nonnull - public PhysicsMutationHandle addBodyAsync(@Nonnull RigidBodyKey bodyKey, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBody body, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return enqueueOwnerMutation("add test physics body", - bodyKey, - () -> addBodyOnOwner(bodyKey, spaceId, body, kind, persistenceMode)); - } - - @Nullable - public PhysicsBody getBody(@Nonnull RigidBodyKey bodyKey) { - PhysicsBodyRegistration registration = getRegistration(bodyKey); - if (registration == null) { - return null; - } - Object state = legacySpaceState(requireSpaceBinding(registration.spaceId())); - return bodiesById(state).get(registration.backendBodyHandle().value()); - } - - @Nonnull - public PhysicsSpace getLiveSpace(@Nonnull SpaceId spaceId) { - return liveSpace(spaceId); - } - - @Nonnull - public JointKey addJoint(@Nonnull SpaceId spaceId, @Nonnull PhysicsJoint joint) { - BackendJointHandle backendJointHandle = registerLiveJoint(spaceId, joint); - PhysicsBodyRegistration bodyA = getRegistrationByLiveBody(spaceId, joint.getBodyA()); - PhysicsBodyRegistration bodyB = getRegistrationByLiveBody(spaceId, joint.getBodyB()); - if (bodyA == null || bodyB == null) { - throw new IllegalArgumentException("Both joint bodies must be registered before registering the joint"); - } - JointKey jointKey = JointKey.random(); - Vector3f anchorA = joint.getAnchorA(); - Vector3f anchorB = joint.getAnchorB(); - Vector3f axis = joint.getAxis(); - if (axis == null) { - axis = new Vector3f(0.0f, 1.0f, 0.0f); - } - return super.addJointOnOwner(jointKey, - spaceId, - backendJointHandle, - bodyA.bodyKey(), - bodyB.bodyKey(), - jointType(joint.getType()), - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z, - 0.0f, - 0.0f, - 0.0f, - joint.getLowerLimit(), - joint.getUpperLimit(), - joint.isMotorEnabled(), - joint.getMotorTargetVelocity(), - joint.getMotorMaxForce()); - } - - @Nullable - public PhysicsJoint getJoint(@Nonnull JointKey jointKey) { - PhysicsJointRegistration registration = getJointRegistration(jointKey); - if (registration == null) { - return null; - } - Object state = legacySpaceState(requireSpaceBinding(registration.spaceId())); - return jointsById(state).get(registration.backendJointHandle().value()); - } - - @Nonnull - private PhysicsSpace liveSpace(@Nonnull SpaceId spaceId) { - Object state = legacySpaceState(requireSpaceBinding(spaceId)); - return liveSpaceFromState(state); - } - - @Nonnull - private RegisteredBody registerLiveBody(@Nonnull SpaceId spaceId, @Nonnull PhysicsBody body) { - Object state = legacySpaceState(requireSpaceBinding(spaceId)); - Map bodyIdsByBody = bodyIdsByBody(state); - Long existing = bodyIdsByBody.get(body); - if (existing != null) { - return new RegisteredBody(new BackendBodyHandle(existing), false); - } - - PhysicsSpace space = liveSpaceFromState(state); - if (!space.getBodies().contains(body)) { - space.addBody(body); - } - - LegacyPhysicsBackendRuntime runtime = legacyRuntime(requireSpaceBinding(spaceId)); - long bodyId = nextId(runtime, NEXT_BODY_ID); - bodiesById(state).put(bodyId, body); - bodyIdsByBody.put(body, bodyId); - return new RegisteredBody(new BackendBodyHandle(bodyId), true); - } - - private void unregisterLiveBody(@Nonnull SpaceId spaceId, - long backendBodyId, - @Nonnull PhysicsBody body) { - Object state = legacySpaceState(requireSpaceBinding(spaceId)); - bodiesById(state).remove(backendBodyId); - bodyIdsByBody(state).remove(body); - liveSpaceFromState(state).removeBody(body); - } - - @Nonnull - private BackendJointHandle registerLiveJoint(@Nonnull SpaceId spaceId, @Nonnull PhysicsJoint joint) { - Object state = legacySpaceState(requireSpaceBinding(spaceId)); - Long existing = liveJointId(state, joint); - if (existing != null) { - return new BackendJointHandle(existing); - } - LegacyPhysicsBackendRuntime runtime = legacyRuntime(requireSpaceBinding(spaceId)); - long jointId = nextId(runtime, NEXT_JOINT_ID); - jointsById(state).put(jointId, joint); - return new BackendJointHandle(jointId); - } - - @Nullable - private PhysicsBodyRegistration getRegistrationByLiveBody(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBody body) { - Long backendBodyId = bodyIdsByBody(legacySpaceState(requireSpaceBinding(spaceId))).get(body); - if (backendBodyId == null) { - return null; - } - RigidBodyKey bodyKey = getBodyKey(spaceId, backendBodyId); - return bodyKey != null ? getRegistration(bodyKey) : null; - } - - @Nonnull - private static JointType jointType(@Nonnull PhysicsJointType type) { - return switch (type) { - case FIXED -> JointType.FIXED; - case POINT -> JointType.POINT; - case HINGE -> JointType.HINGE; - case SLIDER -> JointType.SLIDER; - case SPRING -> JointType.SPRING; - }; - } - - @Nonnull - private static Object legacySpaceState(@Nonnull PhysicsSpaceBinding binding) { - LegacyPhysicsBackendRuntime runtime = legacyRuntime(binding); - Map spaces = spaces(runtime); - Object state = spaces.get(binding.backendSpaceHandle().value()); - if (state == null) { - throw new IllegalStateException("Missing legacy test space state for " + binding.spaceId()); - } - return state; - } - - @Nonnull - private static LegacyPhysicsBackendRuntime legacyRuntime(@Nonnull PhysicsSpaceBinding binding) { - if (binding.runtime() instanceof LegacyPhysicsBackendRuntime runtime) { - return runtime; - } - throw new IllegalStateException("Legacy live-handle test support requires the legacy runtime adapter"); - } - - @Nonnull - @SuppressWarnings("unchecked") - private static Map spaces(@Nonnull LegacyPhysicsBackendRuntime runtime) { - return (Map) get(SPACES, runtime); - } - - @Nonnull - private static PhysicsSpace liveSpaceFromState(@Nonnull Object state) { - return (PhysicsSpace) get(field(state.getClass(), "space"), state); - } - - @Nonnull - @SuppressWarnings("unchecked") - private static Map bodiesById(@Nonnull Object state) { - return (Map) get(field(state.getClass(), "bodiesById"), state); - } - - @Nonnull - @SuppressWarnings("unchecked") - private static Map bodyIdsByBody(@Nonnull Object state) { - return (Map) get(field(state.getClass(), "bodyIdsByBody"), state); - } - - @Nonnull - @SuppressWarnings("unchecked") - private static Map jointsById(@Nonnull Object state) { - return (Map) get(field(state.getClass(), "jointsById"), state); - } - - private static long nextId(@Nonnull LegacyPhysicsBackendRuntime runtime, @Nonnull Field field) { - try { - long id = field.getLong(runtime); - field.setLong(runtime, id + 1L); - return id; - } catch (IllegalAccessException exception) { - throw new IllegalStateException("Cannot update legacy runtime id counter", exception); - } - } - - @Nullable - private static Long liveJointId(@Nonnull Object state, @Nonnull PhysicsJoint joint) { - for (Map.Entry entry : jointsById(state).entrySet()) { - if (entry.getValue() == joint) { - return entry.getKey(); - } - } - return null; - } - - private record RegisteredBody(@Nonnull BackendBodyHandle backendBodyHandle, boolean created) { - } - - @Nonnull - private static Field field(@Nonnull Class owner, @Nonnull String name) { - try { - Field field = owner.getDeclaredField(name); - field.setAccessible(true); - return field; - } catch (ReflectiveOperationException exception) { - throw new IllegalStateException("Cannot access " + owner.getName() + "." + name, exception); - } - } - - @Nonnull - private static Object get(@Nonnull Field field, @Nonnull Object target) { - try { - return field.get(target); - } catch (IllegalAccessException exception) { - throw new IllegalStateException("Cannot read " + field.getName(), exception); - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java deleted file mode 100644 index 622029b6..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyRowsTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import java.util.UUID; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsBodyRowsTest { - - @Test - void dynamicBodyRowBuildsSingleBodyGraph() { - UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000007"); - UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000042"); - - BodyRowDescriptor row = PhysicsBodyRows.body(spaceUuid, - bodyUuid, - new Vector3f(1.0f, 2.0f, 3.0f), - PhysicsShapeSpec.box(0.5f, 0.75f, 1.0f), - PhysicsBodyType.DYNAMIC, - 2.0f, - RigidBodySpawnSettings.fromOptionalValues(0.4f, - 0.2f, - 0.05f, - 0.1f, - PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN, - true), - null, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT); - - assertEquals(bodyUuid, row.bodyUuid()); - assertEquals(spaceUuid, row.body().getSpaceUuid()); - assertEquals(PhysicsBodyKind.BODY, row.body().getKind()); - assertEquals(PhysicsBodyPersistenceMode.PERSISTENT, row.body().getPersistenceMode()); - assertEquals(PhysicsBodyType.DYNAMIC, row.dynamics().getBodyType()); - assertEquals(2.0f, row.dynamics().getMass(), 0.0001f); - assertEquals(0.05f, row.dynamics().getLinearDamping(), 0.0001f); - assertEquals(bodyUuid, row.colliderUuid()); - assertEquals(bodyUuid, row.shapeUuid()); - assertEquals(bodyUuid, row.materialUuid()); - assertEquals(bodyUuid, row.filterUuid()); - assertEquals(ShapeType.BOX, row.shape().getShapeType()); - assertEquals(0.75f, row.shape().getHalfExtentY(), 0.0001f); - assertEquals(0.4f, row.material().getFriction(), 0.0001f); - assertEquals(PhysicsCollisionFilters.TERRAIN, row.filter().getCollisionMask()); - assertTrue(row.collider().isSensor()); - assertFalse(row.target().isActive()); - assertTrue(row.target().isTransformEnabled()); - assertFalse(row.target().isVelocityEnabled()); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), row.target().getPosition()); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java index 9a371684..cefdaf5d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java @@ -51,8 +51,10 @@ void externalEntityDefaultsToBodyAuthorityAndKeepsEntityWhenMissing() { @Test void normalizesInvalidVisualOriginOffsetToBodyValue() { - BodyAttachmentComponent attachment = BodyAttachmentComponent.generatedProxy( + BodyAttachmentComponent attachment = new BodyAttachmentComponent( UUID.randomUUID(), + BodyAttachmentComponent.TransformAuthority.BODY, + BodyAttachmentComponent.AttachmentLifecycle.GENERATED_PROXY, new Vector3f(), new Quaternionf(), Float.NaN); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java index aaf137ce..4054fcea 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java @@ -8,7 +8,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.ArrayList; @@ -21,10 +20,10 @@ class PublishedPhysicsSnapshotFrameTest { - private static final RigidBodyKey BODY_ID = - RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000001")); - private static final RigidBodyKey SECOND_BODY_ID = - RigidBodyKey.of(UUID.fromString("00000000-0000-0000-0000-000000000002")); + private static final UUID BODY_ID = + UUID.fromString("00000000-0000-0000-0000-000000000001"); + private static final UUID SECOND_BODY_ID = + UUID.fromString("00000000-0000-0000-0000-000000000002"); private static final SpaceId SPACE_ID = new SpaceId(7); @Test @@ -234,13 +233,13 @@ void framesVisitBodiesInPublicationOrder() { 20L, 30L, SPACE_ID); - List visited = getRigidBodyKeys(firstBody, secondBody); + List visited = getBodyUuids(firstBody, secondBody); assertEquals(List.of(BODY_ID, SECOND_BODY_ID), visited); } @NonNullDecl - private static List getRigidBodyKeys(PublishedPhysicsBodySnapshot firstBody, + private static List getBodyUuids(PublishedPhysicsBodySnapshot firstBody, PublishedPhysicsBodySnapshot secondBody) { PublishedPhysicsSnapshotFrame frame = new PublishedPhysicsSnapshotFrame(10L, 20L, @@ -255,9 +254,9 @@ private static List getRigidBodyKeys(PublishedPhysicsBodySnapshot 20L, 30L, List.of(firstBody, secondBody)))); - List visited = new ArrayList<>(); + List visited = new ArrayList<>(); - frame.forEachBody(body -> visited.add(body.bodyKey())); + frame.forEachBody(body -> visited.add(body.bodyUuid())); return visited; } @@ -316,7 +315,7 @@ private static PublishedPhysicsBodySnapshot bodySnapshot(long frameEpoch, return bodySnapshot(BODY_ID, frameEpoch, worldEpoch, spaceEpoch, spaceId); } - private static PublishedPhysicsBodySnapshot bodySnapshot(RigidBodyKey bodyId, + private static PublishedPhysicsBodySnapshot bodySnapshot(UUID bodyId, long frameEpoch, long worldEpoch, long spaceEpoch, diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java index e8f33bde..a6eed464 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java @@ -4,11 +4,11 @@ import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.examples.events.PhysicsEventSummary; import java.util.List; +import java.util.UUID; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -16,8 +16,8 @@ class EventsCommandTest { @Test void formatsLatestContactEventSummary() { - RigidBodyKey first = RigidBodyKey.of(1L, 2L); - RigidBodyKey second = RigidBodyKey.of(3L, 4L); + UUID first = new UUID(1L, 2L); + UUID second = new UUID(3L, 4L); PhysicsEventFrame frame = new PhysicsEventFrame(12L, 34L, 56L, diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java index 84740410..daa1a615 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java @@ -4,10 +4,10 @@ import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.RigidBodyKey; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import java.util.List; +import java.util.UUID; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -16,8 +16,8 @@ class PhysicsEventTrackerTest { @Test void tracksContactEventsFromPublishedFrames() { PhysicsEventTracker.reset(); - RigidBodyKey first = RigidBodyKey.of(1L, 2L); - RigidBodyKey second = RigidBodyKey.of(3L, 4L); + UUID first = new UUID(1L, 2L); + UUID second = new UUID(3L, 4L); PhysicsEventFrame frame = new PhysicsEventFrame(12L, 34L, 56L, From 0f8e4276ea3261b058f04d168f0e8647089cc8e5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 20:48:01 +0200 Subject: [PATCH 292/534] fix(build): enable physics store early plugin for runAllMods Signed-off-by: Blovien --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bc02fddd..6cd4184c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -70,7 +70,7 @@ val stagedBackendJarDirectory = layout.projectDirectory.dir("run/mods/impulse-ba val stagedEarlyPluginJarDirectory = layout.projectDirectory.dir("run/earlyplugins") val physicsStoreEarlyPluginEnabled = providers.gradleProperty("impulse.physicsStoreEarlyPlugin") .map(String::toBoolean) - .orElse(false) + .orElse(true) val hytaleToolProjectPaths = listOf( ":impulse-core", ":impulse-examples") @@ -123,7 +123,7 @@ val stagePhysicsStoreEarlyPluginJar by tasks.registering(Copy::class) { group = "hytale" description = "Stages the PhysicsStore early plugin for runAllMods" - onlyIf("PhysicsStore early plugin opt-in is enabled") { + onlyIf("PhysicsStore early plugin is enabled") { physicsStoreEarlyPluginEnabled.get() } dependsOn(cleanStagedPhysicsStoreEarlyPluginJar) From 731fbfad6dd7d87019041d8826c35bf674072260 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 21:06:07 +0200 Subject: [PATCH 293/534] fix(core): repair physics store early startup Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 4 ++-- .../registration/PhysicsStoreRegistration.java | 2 +- .../early/PhysicsStoreEarlyTransformer.java | 17 +++++++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index c5f7ab9d..60c8558b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -229,12 +229,12 @@ private void registerComponents() { private void registerSystems() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); persistenceRestoreGroup = entityRegistry.registerSystemGroup(); - entityRegistry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); - entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); entityRegistry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); entityRegistry.registerSystem(new PhysicsSyncSystem()); entityRegistry.registerSystem(new PhysicsDebugSystem()); + entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); + entityRegistry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); } private void registerCommands() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 2a01715d..c71dda22 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -203,8 +203,8 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); - registry.registerSystem(new PhysicsStoreQueuedReadSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); + registry.registerSystem(new PhysicsStoreQueuedReadSystem()); registry.registerSystem(new PersistenceCaptureSystem()); registry.registerSystem(new StepSubmissionSystem()); } diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java index dab36802..82f54b61 100644 --- a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreEarlyTransformer.java @@ -17,6 +17,7 @@ import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; import javax.annotation.Nonnull; public final class PhysicsStoreEarlyTransformer implements ClassTransformer { @@ -161,7 +162,7 @@ private static byte[] transformWorld(@Nonnull byte[] bytes) { } return CLASS_FILE.transformClass(model, ClassTransform.ACCEPT_ALL.andThen(ClassTransform.endHandler(builder -> - builder.withInterfaceSymbols(CD_PHYSICS_STORE_WORLD)))); + addPhysicsStoreWorldInterface(model, builder)))); } boolean fieldPresent = hasField(model.fields(), WORLD_STORE_FIELD, CD_PHYSICS_STORE); boolean methodPresent = hasMethod(model.methods(), WORLD_STORE_METHOD, MTD_PHYSICS_STORE); @@ -190,7 +191,7 @@ private static byte[] transformWorld(@Nonnull byte[] bytes) { addWorldStoreAccessor(builder); } if (!interfacePresent) { - builder.withInterfaceSymbols(CD_PHYSICS_STORE_WORLD); + addPhysicsStoreWorldInterface(model, builder); } builder.withField(WORLD_PATCH_MARKER_FIELD, ConstantDescs.CD_boolean, @@ -256,6 +257,18 @@ private static void addWorldStoreAccessor(@Nonnull ClassBuilder builder) { .areturn()); } + private static void addPhysicsStoreWorldInterface(@Nonnull ClassModel model, + @Nonnull ClassBuilder builder) { + ArrayList interfaces = new ArrayList<>(model.interfaces().size() + 1); + for (var entry : model.interfaces()) { + interfaces.add(entry.asSymbol()); + } + if (!interfaces.contains(CD_PHYSICS_STORE_WORLD)) { + interfaces.add(CD_PHYSICS_STORE_WORLD); + } + builder.withInterfaceSymbols(interfaces); + } + private static final class InitializePhysicsStoreTransform implements CodeTransform { private boolean injected; From 5d9c901260ceda3762b40e7c666f9eb6463c52da Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 21:41:08 +0200 Subject: [PATCH 294/534] fix(core): restore physics store owner lane stepping Signed-off-by: Blovien --- .../PhysicsStoreRuntimeCleaner.java | 2 +- .../PhysicsStoreSpaceMutations.java | 2 +- .../PhysicsStoreTopologyMutations.java | 8 +- .../PhysicsStoreRegistration.java | 68 ++++ .../resources/PhysicsProfilingResource.java | 52 +++ .../PhysicsStepSchedulerResource.java | 303 ++++++++++++++++++ .../CompletedStepPublicationSystem.java | 3 +- .../StepCompletionPublicationSystem.java | 46 +++ .../systems/StepSubmissionSystem.java | 79 ++++- .../PhysicsStoreEventPublicationSystem.java | 7 + .../PhysicsStoreBackendAccess.java | 6 +- .../physicsstore/PhysicsStoreDiagnostics.java | 18 +- .../physicsstore/PhysicsStoreThreading.java | 12 + .../physicsstore/PhysicsStoreTypes.java | 13 + .../impulse/early/PhysicsStoreHooks.java | 32 +- 15 files changed, 617 insertions(+), 34 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index 443ce673..5a856665 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -27,7 +27,7 @@ private PhysicsStoreRuntimeCleaner() { } public static void clearAll(@Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore runtime rows"); + PhysicsStoreThreading.requireBackendIdle(store, "clear PhysicsStore runtime rows"); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, commandBuffer) -> commandBuffer.removeEntity( chunk.getReferenceTo(index), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 588afeb0..e762e9c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -159,7 +159,7 @@ public static void removeEmptySpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { Objects.requireNonNull(store, "store"); Objects.requireNonNull(spaceUuid, "spaceUuid"); - PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space entity"); + PhysicsStoreThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 8697e6ec..bbfaafc0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -42,7 +42,7 @@ private PhysicsStoreTopologyMutations() { public static void destroyBody(@Nonnull Store store, @Nonnull UUID bodyUuid) { - PhysicsStoreThreading.requireWorldThread(store, "destroy a PhysicsStore body entity"); + PhysicsStoreThreading.requireBackendIdle(store, "destroy a PhysicsStore body entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -55,7 +55,7 @@ public static void destroyBody(@Nonnull Store store, @Nonnull public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( @Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore body entities"); + PhysicsStoreThreading.requireBackendIdle(store, "clear PhysicsStore body entities"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -76,7 +76,7 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( public static void removeSpaceWithContents(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "remove a PhysicsStore space entity"); + PhysicsStoreThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -91,7 +91,7 @@ public static void removeSpaceWithContents(@Nonnull Store store, public static int clearTerrainForSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "clear PhysicsStore terrain rows"); + PhysicsStoreThreading.requireBackendIdle(store, "clear PhysicsStore terrain rows"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); int removedBodies = 0; Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index c71dda22..9dbfdad2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; @@ -16,6 +17,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.TickDecision; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; @@ -32,11 +35,13 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; +import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepCompletionPublicationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.WorldCollisionIndexSystem; +import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; @@ -56,6 +61,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Consumer; @@ -71,6 +77,9 @@ public final class PhysicsStoreRegistration { @Nonnull private static final Consumer SHUTDOWN_CLEANUP = PhysicsStoreRegistration::clearRuntimeStateBeforeShutdown; + @Nonnull + private static final PhysicsStoreHooks.TickGate STEP_TICK_GATE = + PhysicsStoreRegistration::shouldTickPhysicsStore; private PhysicsStoreRegistration() { } @@ -78,6 +87,7 @@ private PhysicsStoreRegistration() { public static void register(@Nonnull PluginBase plugin) { ComponentRegistryProxy registry = physicsStoreRegistry(plugin); PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); + PhysicsStoreHooks.registerTickGate(STEP_TICK_GATE); PhysicsStoreTypes.setUuidComponentType(registry.registerComponent(UuidComponent.class, "Uuid", @@ -149,6 +159,9 @@ public static void register(@Nonnull PluginBase plugin) { PhysicsStoreTypes.setWorldSettingsResourceType(registry.registerResource( PhysicsWorldSettingsResource.class, PhysicsWorldSettingsResource::new)); + PhysicsStoreTypes.setStepSchedulerResourceType(registry.registerResource( + PhysicsStepSchedulerResource.class, + PhysicsStepSchedulerResource::new)); PhysicsStoreTypes.setSpaceCompatibilityIndexResourceType(registry.registerResource( PhysicsSpaceCompatibilityIndexResource.class, PhysicsSpaceCompatibilityIndexResource::new)); @@ -203,6 +216,7 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); + registry.registerSystem(new StepCompletionPublicationSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); registry.registerSystem(new PhysicsStoreQueuedReadSystem()); registry.registerSystem(new PersistenceCaptureSystem()); @@ -217,6 +231,8 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic RuntimeException failure = null; failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear()); + failure = runShutdownCleanup(failure, + () -> store.getResource(PhysicsStepSchedulerResource.getResourceType()).close()); failure = runShutdownCleanup(failure, () -> store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings()); failure = runShutdownCleanup(failure, @@ -242,6 +258,58 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic } } + private static boolean shouldTickPhysicsStore(@Nonnull PhysicsStore physicsStore, float dt) { + Store store = physicsStore.getStore(); + if (store.isShutdown()) { + return true; + } + PhysicsWorldSettings settings = store.getResource(PhysicsWorldSettingsResource.getResourceType()) + .getSettings(); + TickDecision decision = store.getResource(PhysicsStepSchedulerResource.getResourceType()) + .beforeStoreTick(dt, + settings.getStepSchedulingMode(), + maxSubmittedDtSeconds(settings), + System.nanoTime()); + if (decision.shouldTick()) { + return true; + } + recordPendingStepSkip(store, decision); + return false; + } + + private static void recordPendingStepSkip(@Nonnull Store store, + @Nonnull TickDecision decision) { + Store entityStore = store.getExternalData() + .getWorld() + .getEntityStore() + .getStore(); + if (entityStore.isShutdown()) { + return; + } + PhysicsRuntimeProfilingResource profiling = entityStore.getResource( + PhysicsRuntimeProfilingResource.getResourceType()); + if (!profiling.isEnabled()) { + return; + } + profiling.recordStepSkippedPending(decision.pendingStepAgeNanos()); + profiling.recordStepScheduling(decision.inputDtSeconds(), + decision.submittedDtSeconds(), + decision.backlogDtSeconds(), + decision.droppedBacklogDtSeconds(), + decision.dtCapHit()); + } + + private static float maxSubmittedDtSeconds(@Nonnull PhysicsWorldSettings settings) { + float maxStepDt = settings.getMaxStepDt() > 0.0f + ? settings.getMaxStepDt() + : PhysicsWorldSettings.DEFAULT_MAX_STEP_DT; + int maxSteps = switch (settings.getStepMode()) { + case FIXED, CCD -> settings.getSimulationSteps(); + case ADAPTIVE, PROGRESSIVE_REFINEMENT -> PhysicsWorldSettings.MAX_SIMULATION_STEPS; + }; + return maxStepDt * maxSteps; + } + @Nullable private static RuntimeException runShutdownCleanup(@Nullable RuntimeException failure, @Nonnull Runnable cleanup) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java index 4119bb41..cc7b6aa8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java @@ -19,6 +19,12 @@ public final class PhysicsProfilingResource implements Resource { private int spaces; private int substeps; private int publishedBodies; + private int schedulerSamples; + private float schedulerInputDtSeconds; + private float schedulerSubmittedDtSeconds; + private float schedulerBacklogDtSeconds; + private float droppedBacklogDtSeconds; + private boolean dtCapHit; @Nonnull private PhysicsStepPhaseStats nativePhaseStats = PhysicsStepPhaseStats.unavailable(); @@ -48,12 +54,31 @@ public void recordSnapshot(long snapshotNanos, int publishedBodies) { this.publishedBodies = Math.max(0, publishedBodies); } + public void recordStepScheduling(float inputDtSeconds, + float submittedDtSeconds, + float backlogDtSeconds, + float droppedBacklogDtSeconds, + boolean dtCapHit) { + schedulerSamples = 1; + schedulerInputDtSeconds = safeDt(inputDtSeconds); + schedulerSubmittedDtSeconds = safeDt(submittedDtSeconds); + schedulerBacklogDtSeconds = safeDt(backlogDtSeconds); + this.droppedBacklogDtSeconds = safeDt(droppedBacklogDtSeconds); + this.dtCapHit = dtCapHit; + } + public void reset() { snapshotNanos = 0L; stepSubmitNanos = 0L; spaces = 0; substeps = 0; publishedBodies = 0; + schedulerSamples = 0; + schedulerInputDtSeconds = 0.0f; + schedulerSubmittedDtSeconds = 0.0f; + schedulerBacklogDtSeconds = 0.0f; + droppedBacklogDtSeconds = 0.0f; + dtCapHit = false; nativePhaseStats = PhysicsStepPhaseStats.unavailable(); } @@ -64,6 +89,12 @@ public StepSample latestStepSample() { stepSubmitNanos, snapshotNanos, publishedBodies, + schedulerSamples, + schedulerInputDtSeconds, + schedulerSubmittedDtSeconds, + schedulerBacklogDtSeconds, + droppedBacklogDtSeconds, + dtCapHit, nativePhaseStats); } @@ -102,6 +133,12 @@ public PhysicsProfilingResource clone() { copy.spaces = spaces; copy.substeps = substeps; copy.publishedBodies = publishedBodies; + copy.schedulerSamples = schedulerSamples; + copy.schedulerInputDtSeconds = schedulerInputDtSeconds; + copy.schedulerSubmittedDtSeconds = schedulerSubmittedDtSeconds; + copy.schedulerBacklogDtSeconds = schedulerBacklogDtSeconds; + copy.droppedBacklogDtSeconds = droppedBacklogDtSeconds; + copy.dtCapHit = dtCapHit; copy.nativePhaseStats = nativePhaseStats; return copy; } @@ -116,6 +153,12 @@ public record StepSample(int spaces, long stepSubmitNanos, long snapshotNanos, int publishedBodies, + int schedulerSamples, + float schedulerInputDtSeconds, + float schedulerSubmittedDtSeconds, + float schedulerBacklogDtSeconds, + float droppedBacklogDtSeconds, + boolean dtCapHit, @Nonnull PhysicsStepPhaseStats nativePhaseStats) { public StepSample { @@ -124,7 +167,16 @@ public record StepSample(int spaces, stepSubmitNanos = Math.max(0L, stepSubmitNanos); snapshotNanos = Math.max(0L, snapshotNanos); publishedBodies = Math.max(0, publishedBodies); + schedulerSamples = Math.max(0, schedulerSamples); + schedulerInputDtSeconds = safeDt(schedulerInputDtSeconds); + schedulerSubmittedDtSeconds = safeDt(schedulerSubmittedDtSeconds); + schedulerBacklogDtSeconds = safeDt(schedulerBacklogDtSeconds); + droppedBacklogDtSeconds = safeDt(droppedBacklogDtSeconds); Objects.requireNonNull(nativePhaseStats, "nativePhaseStats"); } } + + private static float safeDt(float dtSeconds) { + return Float.isFinite(dtSeconds) ? Math.max(0.0f, dtSeconds) : 0.0f; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java new file mode 100644 index 00000000..1cbdddf8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java @@ -0,0 +1,303 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Per-PhysicsStore backend owner lane. + * + *

    Only the backend step itself runs off the world thread. PhysicsStore ECS systems are skipped + * while a step is pending, so backend mutations and snapshot reads remain serialized around the + * owner lane.

    + */ +public final class PhysicsStepSchedulerResource implements Resource, AutoCloseable { + + private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); + + @Nonnull + private final ExecutorService ownerLane = Executors.newSingleThreadExecutor( + new OwnerLaneThreadFactory()); + @Nullable + private PendingStep pendingStep; + @Nullable + private CompletedStep completedStep; + private float backlogDtSeconds; + private boolean closed; + + public PhysicsStepSchedulerResource() { + } + + @Nonnull + public synchronized TickDecision beforeStoreTick(float dtSeconds, + @Nonnull PhysicsStepSchedulingMode mode, + float maxSubmittedDtSeconds, + long nowNanos) { + pollPendingStep(); + if (pendingStep == null) { + return TickDecision.tick(); + } + StepInput skipped = accumulatePendingDt(dtSeconds, mode, maxSubmittedDtSeconds); + return TickDecision.skip(pendingAgeNanos(nowNanos), skipped); + } + + @Nonnull + public synchronized StepInput acceptStepInput(float dtSeconds, + @Nonnull PhysicsStepSchedulingMode mode, + float maxSubmittedDtSeconds) { + pollPendingStep(); + if (pendingStep != null) { + throw new IllegalStateException("Cannot submit a PhysicsStore step while another step is pending"); + } + float inputDtSeconds = safeDt(dtSeconds); + float candidateDtSeconds = inputDtSeconds; + if (mode == PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT) { + candidateDtSeconds += backlogDtSeconds; + } else { + backlogDtSeconds = 0.0f; + } + SubmittedDt submittedDt = capSubmittedDt(candidateDtSeconds, maxSubmittedDtSeconds); + backlogDtSeconds = 0.0f; + return new StepInput(inputDtSeconds, + submittedDt.submittedDtSeconds(), + backlogDtSeconds, + submittedDt.droppedDtSeconds(), + submittedDt.dtCapHit()); + } + + public boolean submitStep(@Nonnull StepInput input, + @Nonnull StepTask task, + long nowNanos) { + Objects.requireNonNull(input, "input"); + Objects.requireNonNull(task, "task"); + synchronized (this) { + pollPendingStep(); + if (closed || pendingStep != null) { + return false; + } + CompletableFuture future = CompletableFuture.supplyAsync( + () -> task.run().withInput(input), + ownerLane); + pendingStep = new PendingStep(input, future, Math.max(0L, nowNanos)); + return true; + } + } + + public synchronized boolean isStepPending() { + pollPendingStep(); + return pendingStep != null; + } + + @Nullable + public synchronized CompletedStep pollCompletedStep() { + pollPendingStep(); + CompletedStep completed = completedStep; + completedStep = null; + return completed; + } + + @Override + public void close() { + PendingStep pending; + synchronized (this) { + closed = true; + pending = pendingStep; + } + if (pending != null) { + try { + pending.future().join(); + } catch (CompletionException ignored) { + // The next store tick will publish the failure if shutdown did not consume it first. + } + } + ownerLane.shutdown(); + } + + @Nonnull + @Override + public PhysicsStepSchedulerResource clone() { + return new PhysicsStepSchedulerResource(); + } + + @Nonnull + public static ResourceType getResourceType() { + return PhysicsStoreTypes.stepSchedulerResourceType(); + } + + private StepInput accumulatePendingDt(float dtSeconds, + @Nonnull PhysicsStepSchedulingMode mode, + float maxSubmittedDtSeconds) { + float inputDtSeconds = safeDt(dtSeconds); + if (inputDtSeconds <= 0.0f) { + return new StepInput(0.0f, 0.0f, backlogDtSeconds, 0.0f, false); + } + if (mode != PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT) { + return new StepInput(inputDtSeconds, 0.0f, backlogDtSeconds, inputDtSeconds, false); + } + SubmittedDt submittedDt = capSubmittedDt(backlogDtSeconds + inputDtSeconds, + maxSubmittedDtSeconds); + backlogDtSeconds = submittedDt.submittedDtSeconds(); + return new StepInput(inputDtSeconds, + 0.0f, + backlogDtSeconds, + submittedDt.droppedDtSeconds(), + submittedDt.dtCapHit()); + } + + private void pollPendingStep() { + PendingStep pending = pendingStep; + if (pending == null || !pending.future().isDone()) { + return; + } + try { + completedStep = pending.future().join(); + } catch (CompletionException exception) { + completedStep = CompletedStep.failed(pending.input(), + exception.getCause() != null ? exception.getCause() : exception); + } + pendingStep = null; + } + + private long pendingAgeNanos(long nowNanos) { + PendingStep pending = pendingStep; + return pending == null ? 0L : Math.max(0L, nowNanos - pending.startedNanos()); + } + + private static float safeDt(float dtSeconds) { + return Float.isFinite(dtSeconds) ? Math.max(0.0f, dtSeconds) : 0.0f; + } + + private static SubmittedDt capSubmittedDt(float dtSeconds, float maxSubmittedDtSeconds) { + float safeDtSeconds = safeDt(dtSeconds); + float safeMax = Float.isFinite(maxSubmittedDtSeconds) && maxSubmittedDtSeconds > 0.0f + ? maxSubmittedDtSeconds + : safeDtSeconds; + if (safeDtSeconds <= safeMax) { + return new SubmittedDt(safeDtSeconds, 0.0f, false); + } + return new SubmittedDt(safeMax, safeDtSeconds - safeMax, true); + } + + @FunctionalInterface + public interface StepTask { + + @Nonnull + CompletedStep run(); + } + + public record StepInput(float inputDtSeconds, + float submittedDtSeconds, + float backlogDtSeconds, + float droppedBacklogDtSeconds, + boolean dtCapHit) { + + public StepInput { + inputDtSeconds = safeDt(inputDtSeconds); + submittedDtSeconds = safeDt(submittedDtSeconds); + backlogDtSeconds = safeDt(backlogDtSeconds); + droppedBacklogDtSeconds = safeDt(droppedBacklogDtSeconds); + } + } + + public record TickDecision(boolean shouldTick, + long pendingStepAgeNanos, + float inputDtSeconds, + float submittedDtSeconds, + float backlogDtSeconds, + float droppedBacklogDtSeconds, + boolean dtCapHit) { + + private static TickDecision tick() { + return new TickDecision(true, 0L, 0.0f, 0.0f, 0.0f, 0.0f, false); + } + + private static TickDecision skip(long pendingStepAgeNanos, @Nonnull StepInput input) { + return new TickDecision(false, + Math.max(0L, pendingStepAgeNanos), + input.inputDtSeconds(), + input.submittedDtSeconds(), + input.backlogDtSeconds(), + input.droppedBacklogDtSeconds(), + input.dtCapHit()); + } + } + + public record CompletedStep(@Nullable StepInput input, + int spaces, + int substeps, + long stepSubmitNanos, + @Nonnull PhysicsStepPhaseStats nativePhaseStats, + @Nullable Throwable failure) { + + public CompletedStep(int spaces, + int substeps, + long stepSubmitNanos, + @Nonnull PhysicsStepPhaseStats nativePhaseStats) { + this(null, spaces, substeps, stepSubmitNanos, nativePhaseStats, null); + } + + public CompletedStep { + spaces = Math.max(0, spaces); + substeps = Math.max(0, substeps); + stepSubmitNanos = Math.max(0L, stepSubmitNanos); + Objects.requireNonNull(nativePhaseStats, "nativePhaseStats"); + } + + @Nonnull + private CompletedStep withInput(@Nonnull StepInput input) { + return new CompletedStep(input, + spaces, + substeps, + stepSubmitNanos, + nativePhaseStats, + failure); + } + + private static CompletedStep failed(@Nonnull StepInput input, + @Nonnull Throwable failure) { + return new CompletedStep(input, + 0, + 0, + 0L, + PhysicsStepPhaseStats.unavailable(), + Objects.requireNonNull(failure, "failure")); + } + + public boolean failed() { + return failure != null; + } + } + + private record PendingStep(@Nonnull StepInput input, + @Nonnull CompletableFuture future, + long startedNanos) { + } + + private record SubmittedDt(float submittedDtSeconds, + float droppedDtSeconds, + boolean dtCapHit) { + } + + private static final class OwnerLaneThreadFactory implements ThreadFactory { + + @Override + public Thread newThread(@Nonnull Runnable runnable) { + Thread thread = new Thread(runnable, + "Impulse PhysicsStore owner lane-" + THREAD_COUNTER.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 2aa45181..e9fe9af1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -50,7 +50,8 @@ public final class CompletedStepPublicationSystem extends TickingSystem> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class), + new SystemDependency<>(Order.AFTER, StepCompletionPublicationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java new file mode 100644 index 00000000..7deb2572 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java @@ -0,0 +1,46 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; +import javax.annotation.Nonnull; + +/** + * Publishes completed owner-lane step profiling back onto the PhysicsStore world thread. + */ +public final class StepCompletionPublicationSystem extends TickingSystem { + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + CompletedStep completed = store.getResource(PhysicsStepSchedulerResource.getResourceType()) + .pollCompletedStep(); + if (completed == null) { + return; + } + if (completed.failed()) { + Throwable failure = completed.failure(); + String message = failure != null ? failure.getMessage() : null; + store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .markFailed(message != null ? message : "PhysicsStore owner-lane step failed"); + throw new IllegalStateException("PhysicsStore owner-lane step failed", failure); + } + PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); + profiling.recordStep(completed.stepSubmitNanos(), + completed.spaces(), + completed.substeps(), + completed.nativePhaseStats()); + StepInput input = completed.input(); + if (input != null) { + profiling.recordStepScheduling(input.inputDtSeconds(), + input.submittedDtSeconds(), + input.backlogDtSeconds(), + input.droppedBacklogDtSeconds(), + input.dtCapHit()); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index 2bcb5931..46edae16 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -17,12 +17,17 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import javax.annotation.Nonnull; @@ -55,17 +60,26 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsWorldSettingsResource.getResourceType()); PhysicsWorldSettings settings = settingsResource.getSettings(); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsStepSchedulerResource scheduler = store.getResource( + PhysicsStepSchedulerResource.getResourceType()); + StepInput input = scheduler.acceptStepInput(safeDt, + settings.getStepSchedulingMode(), + maxSubmittedDtSeconds(settings)); + float submittedDt = input.submittedDtSeconds(); + if (submittedDt <= 0.0f) { + return; + } PhysicsStepMode stepMode = settings.getStepMode(); float maxStepDt = settings.getMaxStepDt() > 0.0f ? settings.getMaxStepDt() : PhysicsWorldSettings.DEFAULT_MAX_STEP_DT; int steps = stepMode == PhysicsStepMode.ADAPTIVE - ? resolveAdaptiveStepCount(runtime, safeDt, settings.getSimulationSteps(), maxStepDt) - : PhysicsStepCountPolicy.resolveStepCount(safeDt, + ? resolveAdaptiveStepCount(runtime, submittedDt, settings.getSimulationSteps(), maxStepDt) + : PhysicsStepCountPolicy.resolveStepCount(submittedDt, settings.getSimulationSteps(), maxStepDt, stepMode); - float stepDt = safeDt / steps; + float stepDt = submittedDt / steps; boolean ccdMode = stepMode == PhysicsStepMode.CCD; if (ccdMode || settingsResource.isCcdStepModeActive()) { syncContinuousCollisionMode(store, runtime, ccdMode); @@ -76,25 +90,48 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (profilingEnabled) { resetStepPhaseStats(runtime); } + List bindings = runtimeStepBindings(runtime); + boolean submitted = scheduler.submitStep(input, + () -> runOwnerStep(bindings, steps, stepDt, profilingEnabled), + System.nanoTime()); + if (!submitted) { + throw new IllegalStateException("PhysicsStore owner-lane scheduler refused a submitted step"); + } + } + + @Nonnull + private static CompletedStep runOwnerStep(@Nonnull List bindings, + int steps, + float stepDt, + boolean profilingEnabled) { long stepStartNanos = profilingEnabled ? System.nanoTime() : 0L; StepCounters counters = new StepCounters(); - runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + for (RuntimeStepBinding binding : bindings) { counters.spaceCount++; for (int step = 0; step < steps; step++) { - backendRuntime.step(spaceHandle.value(), stepDt); + binding.backendRuntime().step(binding.spaceHandle(), stepDt); counters.substeps++; } - }); + } long stepNanos = profilingEnabled ? System.nanoTime() - stepStartNanos : 0L; PhysicsStepPhaseStats nativePhaseStats = profilingEnabled - ? collectStepPhaseStats(runtime) + ? collectStepPhaseStats(bindings) : PhysicsStepPhaseStats.unavailable(); - profiling.recordStep(stepNanos, - counters.spaceCount, + return new CompletedStep(counters.spaceCount, counters.substeps, + stepNanos, nativePhaseStats); } + @Nonnull + private static List runtimeStepBindings( + @Nonnull PhysicsRuntimeResource runtime) { + List bindings = new ArrayList<>(); + runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> + bindings.add(new RuntimeStepBinding(spaceHandle.value(), backendRuntime))); + return bindings; + } + private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runtime, float dt, int simulationSteps, @@ -110,6 +147,17 @@ private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runt return risk.steps(); } + private static float maxSubmittedDtSeconds(@Nonnull PhysicsWorldSettings settings) { + float maxStepDt = settings.getMaxStepDt() > 0.0f + ? settings.getMaxStepDt() + : PhysicsWorldSettings.DEFAULT_MAX_STEP_DT; + int maxSteps = switch (settings.getStepMode()) { + case FIXED, CCD -> settings.getSimulationSteps(); + case ADAPTIVE, PROGRESSIVE_REFINEMENT -> PhysicsWorldSettings.MAX_SIMULATION_STEPS; + }; + return maxStepDt * maxSteps; + } + private static void syncContinuousCollisionMode(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, boolean forceDynamicBodies) { @@ -145,14 +193,15 @@ private static void resetStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) } @Nonnull - private static PhysicsStepPhaseStats collectStepPhaseStats(@Nonnull PhysicsRuntimeResource runtime) { + private static PhysicsStepPhaseStats collectStepPhaseStats( + @Nonnull List bindings) { StepPhaseStatsAccumulator stats = new StepPhaseStatsAccumulator(); StepPhaseStatsCapture capture = new StepPhaseStatsCapture(); - runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + for (RuntimeStepBinding binding : bindings) { capture.reset(); - backendRuntime.stepPhaseStats(spaceHandle.value(), capture); + binding.backendRuntime().stepPhaseStats(binding.spaceHandle(), capture); stats.add(capture.value()); - }); + } return stats.value(); } @@ -368,6 +417,10 @@ private static final class StepCounters { private int substeps; } + private record RuntimeStepBinding(int spaceHandle, + @Nonnull PhysicsBackendRuntime backendRuntime) { + } + private static int requiredSteps(float travel, float safeTravel) { if (travel <= safeTravel) { return 1; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index 12943f5f..86aff124 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -88,6 +88,13 @@ private static void recordProfiling(@Nonnull Store store, 0L, System.nanoTime(), sample.nativePhaseStats()); + if (sample.schedulerSamples() > 0) { + runtimeProfiling.recordStepScheduling(sample.schedulerInputDtSeconds(), + sample.schedulerSubmittedDtSeconds(), + sample.schedulerBacklogDtSeconds(), + sample.droppedBacklogDtSeconds(), + sample.dtCapHit()); + } } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java index 5a05ea73..62c4f4dd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java @@ -27,7 +27,7 @@ private PhysicsStoreBackendAccess() { @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); UUID spaceUuid = compatibility.getSpaceUuid(spaceId); @@ -36,7 +36,7 @@ static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId s @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) .getByUuid(spaceUuid); @@ -46,7 +46,7 @@ static SpaceContext space(@Nonnull Store store, @Nonnull UUID spac @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull Ref spaceRef) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); Objects.requireNonNull(spaceRef, "spaceRef"); if (spaceRef.getStore() != store || !spaceRef.isValid()) { return null; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java index 0e768bf8..85096d09 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java @@ -102,7 +102,7 @@ public static CompletionStage bodyCountAsync(@Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); JointCountCapture count = new JointCountCapture(); runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> @@ -125,7 +125,7 @@ public static CompletionStage runtimeJointCountAsync(@Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); CcdSupportCapture supported = new CcdSupportCapture(); runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { @@ -161,7 +161,7 @@ public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull Ref spaceRef) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsStoreBackendAccess.SpaceContext space = @@ -237,7 +237,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); SpaceId spaceId = compatibility.getSpaceId(Objects.requireNonNull(spaceUuid, "spaceUuid")); @@ -250,7 +250,7 @@ public static SolverCapabilitySummary solverCapability(@Nonnull Store spaceSummaries(@Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); @@ -283,7 +283,7 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsStoreBackendAccess.SpaceContext space = @@ -296,7 +296,7 @@ public static List spaceSummaries(@Nonnull Store sto @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull Ref spaceRef) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsStoreBackendAccess.SpaceContext space = @@ -347,7 +347,7 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsStoreBackendAccess.SpaceContext space = @@ -378,7 +378,7 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull public static List unsupportedCcdSpaces(@Nonnull Store store) { - PhysicsStoreThreading.requireWorldThread(store, "read live PhysicsStore backend state"); + PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java index 8bb04e42..69294eb0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -32,6 +33,17 @@ public static World requireWorldThread(@Nonnull Store store, return world; } + public static void requireBackendIdle(@Nonnull Store store, + @Nonnull String operation) { + requireWorldThread(store, operation); + PhysicsStepSchedulerResource scheduler = store.getResource( + PhysicsStepSchedulerResource.getResourceType()); + if (scheduler.isStepPending()) { + throw new IllegalStateException("Cannot " + operation + + " while a PhysicsStore owner-lane step is pending"); + } + } + @Nonnull public static World world(@Nonnull Store store) { return Objects.requireNonNull(store, "store").getExternalData().getWorld(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index ab7fe95a..f92a71f5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; @@ -88,6 +89,8 @@ public final class PhysicsStoreTypes { @Nullable private static ResourceType worldSettingsResourceType; @Nullable + private static ResourceType stepSchedulerResourceType; + @Nullable private static ResourceType spaceCompatibilityIndexResourceType; @Nullable @@ -219,6 +222,11 @@ public static void setWorldSettingsResourceType( worldSettingsResourceType = Objects.requireNonNull(type, "type"); } + public static void setStepSchedulerResourceType( + @Nonnull ResourceType type) { + stepSchedulerResourceType = Objects.requireNonNull(type, "type"); + } + public static void setSpaceCompatibilityIndexResourceType( @Nonnull ResourceType type) { spaceCompatibilityIndexResourceType = Objects.requireNonNull(type, "type"); @@ -389,6 +397,11 @@ public static ResourceType worldSett return require(worldSettingsResourceType, "PhysicsWorldSettingsResource"); } + @Nonnull + public static ResourceType stepSchedulerResourceType() { + return require(stepSchedulerResourceType, "PhysicsStepSchedulerResource"); + } + @Nonnull public static ResourceType spaceCompatibilityIndexResourceType() { diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java index d3161dd8..085b1b92 100644 --- a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java @@ -15,6 +15,8 @@ public final class PhysicsStoreHooks { @Nonnull private static final Set> SHUTDOWN_HOOKS = new CopyOnWriteArraySet<>(); + @Nonnull + private static final Set TICK_GATES = new CopyOnWriteArraySet<>(); private PhysicsStoreHooks() { } @@ -27,6 +29,14 @@ public static void unregisterShutdownHook(@Nonnull Consumer hook) SHUTDOWN_HOOKS.remove(Objects.requireNonNull(hook, "hook")); } + public static void registerTickGate(@Nonnull TickGate gate) { + TICK_GATES.add(Objects.requireNonNull(gate, "gate")); + } + + public static void unregisterTickGate(@Nonnull TickGate gate) { + TICK_GATES.remove(Objects.requireNonNull(gate, "gate")); + } + public static void start(@Nonnull PhysicsStore physicsStore, @Nonnull IResourceStorage resourceStorage) { Objects.requireNonNull(physicsStore, "physicsStore") @@ -37,8 +47,11 @@ public static void tickAfterChunk(@Nonnull PhysicsStore physicsStore, float dt, boolean ticking, boolean paused) { - Store store = Objects.requireNonNull(physicsStore, "physicsStore") - .getStore(); + PhysicsStore checked = Objects.requireNonNull(physicsStore, "physicsStore"); + if (!shouldTick(checked, dt)) { + return; + } + Store store = checked.getStore(); if (ticking && !paused) { store.tick(dt); return; @@ -77,4 +90,19 @@ public static void shutdown(@Nonnull PhysicsStore physicsStore) { throw hookFailure; } } + + private static boolean shouldTick(@Nonnull PhysicsStore physicsStore, float dt) { + for (TickGate gate : TICK_GATES) { + if (!gate.shouldTick(physicsStore, dt)) { + return false; + } + } + return true; + } + + @FunctionalInterface + public interface TickGate { + + boolean shouldTick(@Nonnull PhysicsStore physicsStore, float dt); + } } From 214c47cce3d10a16131a570d1d33f38bc86d3ac4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 21:54:07 +0200 Subject: [PATCH 295/534] fix(core): defer clean until physics backend idle Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 53 +++++++++++++++- .../PhysicsStepSchedulerResource.java | 11 ++++ .../PhysicsWorldRuntimeResource.java | 25 ++++++++ .../physicsstore/PhysicsStoreThreading.java | 61 +++++++++++++++++++ 4 files changed, 147 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 9693a83b..edbf887e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -26,6 +26,8 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicIntegerArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -108,17 +110,62 @@ private static void cleanAll(@Nonnull CommandContext context, } PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - PhysicsRuntimeResetResult reset = - resource.resetRuntimeStateKeepingSpaces(world.getName()); + CompletionStage reset = + resource.resetRuntimeStateKeepingSpacesAsync(world.getName()); + reset.whenComplete((result, failure) -> sendCleanAllResult(world, + context, + removedEntities, + result, + failure)); + } + + private static void sendCleanAllResult(@Nonnull World world, + @Nonnull CommandContext context, + @Nonnull AtomicIntegerArray removedEntities, + @Nullable PhysicsRuntimeResetResult reset, + @Nullable Throwable failure) { + Runnable sender = () -> { + if (failure != null) { + Throwable cause = unwrap(failure); + String message = cause.getMessage() != null ? cause.getMessage() : cause.toString(); + context.sendMessage(Message.raw("Failed to clean Impulse physics runtime state: " + + message)); + return; + } + if (reset == null) { + context.sendMessage(Message.raw("Failed to clean Impulse physics runtime state.")); + return; + } + sendCleanAllSuccess(context, removedEntities, reset, world.getName()); + }; + if (world.isInThread()) { + sender.run(); + return; + } + world.execute(sender); + } + private static void sendCleanAllSuccess(@Nonnull CommandContext context, + @Nonnull AtomicIntegerArray removedEntities, + @Nonnull PhysicsRuntimeResetResult reset, + @Nonnull String worldName) { context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_BODY_ENTITIES) + " Impulse attachment entities, " + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) + " orphan visual proxy entities, " + reset.removedBodies() + " runtime bodies, " + reset.removedJoints() + " joints, and " - + removedEntities.get(REMOVED_SESSIONS) + " control sessions in world " + world.getName() + + removedEntities.get(REMOVED_SESSIONS) + " control sessions in world " + worldName + ". Kept " + reset.keptSpaces() + " explicit physics spaces.")); } + @Nonnull + private static Throwable unwrap(@Nonnull Throwable failure) { + if (failure instanceof CompletionException completionException + && completionException.getCause() != null) { + return completionException.getCause(); + } + return failure; + } + private void cleanWithinRadius(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java index 1cbdddf8..d490f5f6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java @@ -9,6 +9,7 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -100,6 +101,16 @@ public synchronized boolean isStepPending() { return pendingStep != null; } + @Nonnull + public synchronized CompletionStage whenIdle() { + pollPendingStep(); + PendingStep pending = pendingStep; + if (pending == null) { + return CompletableFuture.completedFuture(null).minimalCompletionStage(); + } + return pending.future().thenApply(_ -> (Void) null).minimalCompletionStage(); + } + @Nullable public synchronized CompletedStep pollCompletedStep() { pollPendingStep(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index d4f0d9fc..91d44e34 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -84,6 +84,8 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -1540,6 +1542,29 @@ public PhysicsRuntimeResetResult resetRuntimeStateKeepingSpaces(@Nonnull String () -> resetRuntimeStateKeepingSpacesDirect(worldName)); } + @Nonnull + public CompletionStage resetRuntimeStateKeepingSpacesAsync( + @Nonnull String worldName) { + Objects.requireNonNull(worldName, "worldName"); + if (isAuthoritativePhysicsStoreActive()) { + World world = requireAuthoritativeWorld("reset physics runtime state"); + return PhysicsStoreThreading.callWhenBackendIdleOnWorldThread(world, + "reset physics runtime state", + store -> { + clearAuthoritativeWorldCollisionStreaming(store); + return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); + }); + } + CompletableFuture completion = new CompletableFuture<>(); + try { + requireLegacyMutationAllowed("reset physics runtime state"); + completion.complete(resetRuntimeStateKeepingSpacesDirect(worldName)); + } catch (RuntimeException exception) { + completion.completeExceptionally(exception); + } + return completion.minimalCompletionStage(); + } + @Nonnull private PhysicsRuntimeResetResult resetRuntimeStateKeepingSpacesDirect(@Nonnull String worldName) { PhysicsRuntimeResetResult reset = spaceRuntime.resetKeepingSpaces(worldName, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java index 69294eb0..3e9a8d28 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java @@ -99,6 +99,27 @@ public static CompletionStage enqueueReadOnWorldThread(@Nonnull World wor return completion.minimalCompletionStage(); } + @Nonnull + public static CompletionStage callWhenBackendIdleOnWorldThread(@Nonnull World world, + @Nonnull String operation, + @Nonnull Function, R> action) { + Objects.requireNonNull(world, "world"); + Objects.requireNonNull(operation, "operation"); + Objects.requireNonNull(action, "action"); + CompletableFuture completion = new CompletableFuture<>(); + Runnable task = () -> callWhenBackendIdle(world, operation, action, completion); + try { + if (world.isInThread()) { + task.run(); + } else { + world.execute(task); + } + } catch (RuntimeException exception) { + PhysicsStoreAsyncCompletions.fail(completion, exception); + } + return completion.minimalCompletionStage(); + } + private static void execute(@Nonnull World world, @Nonnull String operation, @Nonnull Consumer> mutation, @@ -113,6 +134,46 @@ private static void execute(@Nonnull World world, } } + private static void callWhenBackendIdle(@Nonnull World world, + @Nonnull String operation, + @Nonnull Function, R> action, + @Nonnull CompletableFuture completion) { + try { + Store store = store(world); + requireWorldThread(store, operation); + PhysicsStepSchedulerResource scheduler = store.getResource( + PhysicsStepSchedulerResource.getResourceType()); + if (scheduler.isStepPending()) { + scheduler.whenIdle() + .whenComplete((_, failure) -> rescheduleBackendIdleCall(world, + operation, + action, + completion, + failure)); + return; + } + PhysicsStoreAsyncCompletions.complete(completion, action.apply(store)); + } catch (RuntimeException | Error throwable) { + PhysicsStoreAsyncCompletions.fail(completion, throwable); + } + } + + private static void rescheduleBackendIdleCall(@Nonnull World world, + @Nonnull String operation, + @Nonnull Function, R> action, + @Nonnull CompletableFuture completion, + Throwable failure) { + if (failure != null) { + PhysicsStoreAsyncCompletions.fail(completion, failure); + return; + } + try { + world.execute(() -> callWhenBackendIdle(world, operation, action, completion)); + } catch (RuntimeException exception) { + PhysicsStoreAsyncCompletions.fail(completion, exception); + } + } + private static void enqueueRead(@Nonnull World world, @Nonnull String operation, @Nonnull Function, R> read, From 356036d5948440464e63e03c23f052d0f650670a Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 22:03:17 +0200 Subject: [PATCH 296/534] fix(core): tolerate partial physics store shutdown Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 9dbfdad2..9d3b1f7d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -1,6 +1,8 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.registration; import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; @@ -230,34 +232,77 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic } RuntimeException failure = null; failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear()); + () -> ensurePersistentResourcePresent(store)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsStepSchedulerResource.getResourceType()).close()); + () -> cleanupResource(store, + PhysicsTerrainMutationQueueResource.getResourceType(), + PhysicsTerrainMutationQueueResource::clear)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings()); + () -> cleanupResource(store, + PhysicsStepSchedulerResource.getResourceType(), + PhysicsStepSchedulerResource::close)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsRuntimeResource.getResourceType(), + PhysicsRuntimeResource::destroyBackendBindings)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsIdentityIndexResource.getResourceType(), + PhysicsIdentityIndexResource::clear)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsSnapshotResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsSpaceCompatibilityIndexResource.getResourceType(), + PhysicsSpaceCompatibilityIndexResource::clear)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsBodyRegistrationResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsSnapshotResource.getResourceType(), + PhysicsSnapshotResource::clear)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsEventResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsBodyRegistrationResource.getResourceType(), + PhysicsBodyRegistrationResource::clear)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsProfilingResource.getResourceType()).reset()); + () -> cleanupResource(store, + PhysicsEventResource.getResourceType(), + PhysicsEventResource::clear)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsProfilingResource.getResourceType(), + PhysicsProfilingResource::reset)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsStoreReadQueueResource.getResourceType(), + PhysicsStoreReadQueueResource::clear)); failure = runShutdownCleanup(failure, - () -> store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()).clear()); + () -> cleanupResource(store, + PhysicsTerrainPayloadResource.getResourceType(), + PhysicsTerrainPayloadResource::clear)); + failure = runShutdownCleanup(failure, + () -> cleanupResource(store, + PhysicsWorldCollisionIndexResource.getResourceType(), + PhysicsWorldCollisionIndexResource::clear)); if (failure != null) { throw failure; } } + private static void ensurePersistentResourcePresent(@Nonnull Store store) { + if (store.getResource(PersistentPhysicsStoreResource.getResourceType()) == null) { + store.replaceResource(PersistentPhysicsStoreResource.getResourceType(), + new PersistentPhysicsStoreResource()); + } + } + + private static > void cleanupResource( + @Nonnull Store store, + @Nonnull ResourceType type, + @Nonnull Consumer cleanup) { + T resource = store.getResource(type); + if (resource != null) { + cleanup.accept(resource); + } + } + private static boolean shouldTickPhysicsStore(@Nonnull PhysicsStore physicsStore, float dt) { Store store = physicsStore.getStore(); if (store.isShutdown()) { From 5b1bc3d1841071804758c62e78928434adb53b18 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 22:28:22 +0200 Subject: [PATCH 297/534] fix(core): publish physics snapshots from owner lane Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 2 - .../PhysicsStepSchedulerResource.java | 29 +++- .../CompletedStepPublicationSystem.java | 144 +++++------------- .../StepCompletionPublicationSystem.java | 46 ------ .../systems/StepSubmissionSystem.java | 124 ++++++++++++++- 5 files changed, 184 insertions(+), 161 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 9d3b1f7d..0ca9c39d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -37,7 +37,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepCompletionPublicationSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TargetBindingSystem; @@ -218,7 +217,6 @@ public static void register(@Nonnull PluginBase plugin) { registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); - registry.registerSystem(new StepCompletionPublicationSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); registry.registerSystem(new PhysicsStoreQueuedReadSystem()); registry.registerSystem(new PersistenceCaptureSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java index d490f5f6..c3955c82 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java @@ -4,8 +4,10 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; +import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -249,21 +251,42 @@ public record CompletedStep(@Nullable StepInput input, int spaces, int substeps, long stepSubmitNanos, + long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, + @Nonnull List bodySnapshots, @Nullable Throwable failure) { public CompletedStep(int spaces, int substeps, long stepSubmitNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats) { - this(null, spaces, substeps, stepSubmitNanos, nativePhaseStats, null); + this(spaces, substeps, stepSubmitNanos, 0L, nativePhaseStats, List.of()); + } + + public CompletedStep(int spaces, + int substeps, + long stepSubmitNanos, + long snapshotNanos, + @Nonnull PhysicsStepPhaseStats nativePhaseStats, + @Nonnull List bodySnapshots) { + this(null, + spaces, + substeps, + stepSubmitNanos, + snapshotNanos, + nativePhaseStats, + bodySnapshots, + null); } public CompletedStep { spaces = Math.max(0, spaces); substeps = Math.max(0, substeps); stepSubmitNanos = Math.max(0L, stepSubmitNanos); + snapshotNanos = Math.max(0L, snapshotNanos); Objects.requireNonNull(nativePhaseStats, "nativePhaseStats"); + bodySnapshots = List.copyOf(Objects.requireNonNull(bodySnapshots, + "bodySnapshots")); } @Nonnull @@ -272,7 +295,9 @@ private CompletedStep withInput(@Nonnull StepInput input) { spaces, substeps, stepSubmitNanos, + snapshotNanos, nativePhaseStats, + bodySnapshots, failure); } @@ -282,7 +307,9 @@ private static CompletedStep failed(@Nonnull StepInput input, 0, 0, 0L, + 0L, PhysicsStepPhaseStats.unavailable(), + List.of(), Objects.requireNonNull(failure, "failure")); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index e9fe9af1..0e62e42c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -12,16 +12,18 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource.BodyRegistrationPublication; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -39,7 +41,6 @@ import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nonnull; -import org.joml.Quaternionf; import org.joml.Vector3f; /** @@ -50,80 +51,50 @@ public final class CompletedStepPublicationSystem extends TickingSystem> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class), - new SystemDependency<>(Order.AFTER, StepCompletionPublicationSystem.class) + new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) ); @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { + CompletedStep completed = store.getResource(PhysicsStepSchedulerResource.getResourceType()) + .pollCompletedStep(); + if (completed == null) { + return; + } + if (completed.failed()) { + Throwable failure = completed.failure(); + String message = failure != null ? failure.getMessage() : null; + store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .markFailed(message != null ? message : "PhysicsStore owner-lane step failed"); + throw new IllegalStateException("PhysicsStore owner-lane step failed", failure); + } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()); PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - boolean profilingEnabled = profiling.isEnabled(); - long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; - List bodies = new ArrayList<>(); + profiling.recordStep(completed.stepSubmitNanos(), + completed.spaces(), + completed.substeps(), + completed.nativePhaseStats()); + StepInput input = completed.input(); + if (input != null) { + profiling.recordStepScheduling(input.inputDtSeconds(), + input.submittedDtSeconds(), + input.backlogDtSeconds(), + input.droppedBacklogDtSeconds(), + input.dtCapHit()); + } + List bodies = completed.bodySnapshots(); Set snapshotBodyUuids = new ObjectOpenHashSet<>(); - runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> - backendRuntime.snapshotBodies(spaceHandle.value(), - bodyConsumer -> runtime.forEachBodyHandle(spaceHandle, bodyConsumer::accept), - (bodyId, - _, - bodyTypeCode, - positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - linearVelocityX, - linearVelocityY, - linearVelocityZ, - angularVelocityX, - angularVelocityY, - angularVelocityZ, - sleeping, - _, - _, - _, - _, - _, - _, - _, - _, - _, - centerOfMassOffsetY, - _, - _, - _, - _, - _, - _, - _) -> collectBodySnapshot(runtime, - bodies, - snapshotBodyUuids, - bodyId, - bodyTypeCode, - positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - linearVelocityX, - linearVelocityY, - linearVelocityZ, - angularVelocityX, - angularVelocityY, - angularVelocityZ, - centerOfMassOffsetY, - sleeping))); + for (PhysicsStoreBodySnapshot body : bodies) { + snapshotBodyUuids.add(body.bodyUuid()); + } long nextSequence = snapshot.getLatestFrame().sequence() + 1L; - PhysicsStoreSnapshotFrame frame = new PhysicsStoreSnapshotFrame(nextSequence, dt, bodies); - long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; + float frameDt = input != null ? input.submittedDtSeconds() : dt; + PhysicsStoreSnapshotFrame frame = new PhysicsStoreSnapshotFrame(nextSequence, + frameDt, + bodies); snapshot.publish(frame); store.getResource(PhysicsBodyRegistrationResource.getResourceType()) .publish(collectRegistrationViews(store, @@ -131,55 +102,18 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) runtime, compatibility, snapshotBodyUuids)); - profiling.recordSnapshot(snapshotNanos, bodies.size()); + profiling.recordSnapshot(completed.snapshotNanos(), bodies.size()); StepBackendEvents backendEvents = collectBackendEvents(store, runtime); store.getResource(PhysicsEventResource.getResourceType()) .publishStepFrame(frame.sequence(), Math.max(0L, store.getExternalData().getWorld().getTick()), bodies.size(), profiling.getStepSubmitNanos(), - snapshotNanos, + completed.snapshotNanos(), backendEvents.physicsEvents, backendEvents.droppedBackendEventCount); } - private static void collectBodySnapshot(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull List bodies, - @Nonnull Set snapshotBodyUuids, - long bodyId, - int bodyTypeCode, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - float linearVelocityX, - float linearVelocityY, - float linearVelocityZ, - float angularVelocityX, - float angularVelocityY, - float angularVelocityZ, - float centerOfMassOffsetY, - boolean sleeping) { - BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); - if (metadata == null) { - return; - } - snapshotBodyUuids.add(metadata.bodyUuid()); - bodies.add(new PhysicsStoreBodySnapshot(metadata.bodyRef(), - metadata.bodyUuid(), - metadata.spaceUuid(), - BackendRuntimeCodes.bodyType(bodyTypeCode), - new Vector3f(positionX, positionY, positionZ), - new Quaternionf(rotationX, rotationY, rotationZ, rotationW), - new Vector3f(linearVelocityX, linearVelocityY, linearVelocityZ), - new Vector3f(angularVelocityX, angularVelocityY, angularVelocityZ), - centerOfMassOffsetY, - sleeping)); - } - @Nonnull private static List collectRegistrationViews( @Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java deleted file mode 100644 index 7deb2572..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepCompletionPublicationSystem.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; -import javax.annotation.Nonnull; - -/** - * Publishes completed owner-lane step profiling back onto the PhysicsStore world thread. - */ -public final class StepCompletionPublicationSystem extends TickingSystem { - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - CompletedStep completed = store.getResource(PhysicsStepSchedulerResource.getResourceType()) - .pollCompletedStep(); - if (completed == null) { - return; - } - if (completed.failed()) { - Throwable failure = completed.failure(); - String message = failure != null ? failure.getMessage() : null; - store.getResource(PhysicsRestoreStatusResource.getResourceType()) - .markFailed(message != null ? message : "PhysicsStore owner-lane step failed"); - throw new IllegalStateException("PhysicsStore owner-lane step failed", failure); - } - PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); - profiling.recordStep(completed.stepSubmitNanos(), - completed.spaces(), - completed.substeps(), - completed.nativePhaseStats()); - StepInput input = completed.input(); - if (input != null) { - profiling.recordStepScheduling(input.inputDtSeconds(), - input.submittedDtSeconds(), - input.backlogDtSeconds(), - input.droppedBacklogDtSeconds(), - input.dtCapHit()); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index 46edae16..667ffabb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -24,12 +24,15 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; /** * Submits the next backend step from PhysicsStore.tick(). @@ -92,7 +95,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } List bindings = runtimeStepBindings(runtime); boolean submitted = scheduler.submitStep(input, - () -> runOwnerStep(bindings, steps, stepDt, profilingEnabled), + () -> runOwnerStep(runtime, bindings, steps, stepDt, profilingEnabled), System.nanoTime()); if (!submitted) { throw new IllegalStateException("PhysicsStore owner-lane scheduler refused a submitted step"); @@ -100,7 +103,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } @Nonnull - private static CompletedStep runOwnerStep(@Nonnull List bindings, + private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull List bindings, int steps, float stepDt, boolean profilingEnabled) { @@ -109,7 +113,7 @@ private static CompletedStep runOwnerStep(@Nonnull List bind for (RuntimeStepBinding binding : bindings) { counters.spaceCount++; for (int step = 0; step < steps; step++) { - binding.backendRuntime().step(binding.spaceHandle(), stepDt); + binding.backendRuntime().step(binding.spaceHandle().value(), stepDt); counters.substeps++; } } @@ -117,10 +121,16 @@ private static CompletedStep runOwnerStep(@Nonnull List bind PhysicsStepPhaseStats nativePhaseStats = profilingEnabled ? collectStepPhaseStats(bindings) : PhysicsStepPhaseStats.unavailable(); + long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; + List bodySnapshots = collectOwnerLaneSnapshots(runtime, + bindings); + long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; return new CompletedStep(counters.spaceCount, counters.substeps, stepNanos, - nativePhaseStats); + snapshotNanos, + nativePhaseStats, + bodySnapshots); } @Nonnull @@ -128,10 +138,110 @@ private static List runtimeStepBindings( @Nonnull PhysicsRuntimeResource runtime) { List bindings = new ArrayList<>(); runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> - bindings.add(new RuntimeStepBinding(spaceHandle.value(), backendRuntime))); + bindings.add(new RuntimeStepBinding(spaceHandle, backendRuntime))); return bindings; } + @Nonnull + private static List collectOwnerLaneSnapshots( + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull List bindings) { + List snapshots = new ArrayList<>(); + for (RuntimeStepBinding binding : bindings) { + binding.backendRuntime().snapshotBodies(binding.spaceHandle().value(), + bodyIds -> runtime.forEachBodyHandle(binding.spaceHandle(), + bodyIds::accept), + (bodyId, + _, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + sleeping, + _, + _, + _, + _, + _, + _, + _, + _, + _, + centerOfMassOffsetY, + _, + _, + _, + _, + _, + _, + _) -> collectOwnerLaneSnapshot(runtime, + snapshots, + bodyId, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + centerOfMassOffsetY, + sleeping)); + } + return snapshots; + } + + private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull List snapshots, + long bodyId, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + float centerOfMassOffsetY, + boolean sleeping) { + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + if (metadata == null) { + return; + } + snapshots.add(new PhysicsStoreBodySnapshot(metadata.bodyRef(), + metadata.bodyUuid(), + metadata.spaceUuid(), + BackendRuntimeCodes.bodyType(bodyTypeCode), + new Vector3f(positionX, positionY, positionZ), + new Quaternionf(rotationX, rotationY, rotationZ, rotationW), + new Vector3f(linearVelocityX, linearVelocityY, linearVelocityZ), + new Vector3f(angularVelocityX, angularVelocityY, angularVelocityZ), + centerOfMassOffsetY, + sleeping)); + } + private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runtime, float dt, int simulationSteps, @@ -199,7 +309,7 @@ private static PhysicsStepPhaseStats collectStepPhaseStats( StepPhaseStatsCapture capture = new StepPhaseStatsCapture(); for (RuntimeStepBinding binding : bindings) { capture.reset(); - binding.backendRuntime().stepPhaseStats(binding.spaceHandle(), capture); + binding.backendRuntime().stepPhaseStats(binding.spaceHandle().value(), capture); stats.add(capture.value()); } return stats.value(); @@ -417,7 +527,7 @@ private static final class StepCounters { private int substeps; } - private record RuntimeStepBinding(int spaceHandle, + private record RuntimeStepBinding(@Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } From e48a12cbc075b63bf0e49a4daea9313a6211bdec Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 22:46:00 +0200 Subject: [PATCH 298/534] fix(core): avoid streamed float persistence decoding Signed-off-by: Blovien --- .../PersistentBodyRuntimeStateCodec.java | 324 ++++++++++++++++++ .../PersistentBodyRuntimeStateDto.java | 32 +- 2 files changed, 326 insertions(+), 30 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateCodec.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateCodec.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateCodec.java new file mode 100644 index 00000000..f534f3a5 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateCodec.java @@ -0,0 +1,324 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Base64; +import java.util.Objects; +import javax.annotation.Nonnull; +import org.bson.BsonBoolean; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +final class PersistentBodyRuntimeStateCodec implements Codec { + + static final PersistentBodyRuntimeStateCodec INSTANCE = + new PersistentBodyRuntimeStateCodec(); + + private static final byte PACKED_VERSION = 1; + private static final int PACKED_FLOATS = 13; + private static final int PACKED_BYTES = 1 + PACKED_FLOATS * Float.BYTES + 1; + + private PersistentBodyRuntimeStateCodec() { + } + + @Override + public PersistentBodyRuntimeStateDto decode(@Nonnull BsonValue value, + @Nonnull ExtraInfo extraInfo) { + if (Codec.isNullBsonValue(value)) { + return new PersistentBodyRuntimeStateDto(); + } + if (value.isString()) { + return decodePacked(value.asString().getValue()); + } + if (value.isDocument()) { + return decodeLegacyDocument(value.asDocument()); + } + throw new IllegalArgumentException("Persistent body runtime state must be a string"); + } + + @Override + public BsonValue encode(@Nonnull PersistentBodyRuntimeStateDto value, + @Nonnull ExtraInfo extraInfo) { + return new BsonString(encodePacked(value)); + } + + @Override + public PersistentBodyRuntimeStateDto decodeJson(@Nonnull RawJsonReader reader, + @Nonnull ExtraInfo extraInfo) throws IOException { + reader.consumeWhiteSpace(); + int next = reader.peek(); + if (next == '"') { + return decodePacked(reader.readString()); + } + if (next == 'n') { + readNullToken(reader); + return new PersistentBodyRuntimeStateDto(); + } + if (next == '{') { + return decodeLegacyObject(reader); + } + throw new IOException("Persistent body runtime state must be a string or object"); + } + + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Codec.STRING.toSchema(context); + } + + @Nonnull + private static String encodePacked(@Nonnull PersistentBodyRuntimeStateDto dto) { + Objects.requireNonNull(dto, "dto"); + ByteBuffer buffer = ByteBuffer.allocate(PACKED_BYTES).order(ByteOrder.BIG_ENDIAN); + buffer.put(PACKED_VERSION); + Vector3f position = dto.getPosition(); + buffer.putFloat(position.x).putFloat(position.y).putFloat(position.z); + Quaternionf rotation = dto.getRotation(); + buffer.putFloat(rotation.x).putFloat(rotation.y).putFloat(rotation.z).putFloat(rotation.w); + Vector3f linearVelocity = dto.getLinearVelocity(); + buffer.putFloat(linearVelocity.x).putFloat(linearVelocity.y).putFloat(linearVelocity.z); + Vector3f angularVelocity = dto.getAngularVelocity(); + buffer.putFloat(angularVelocity.x).putFloat(angularVelocity.y).putFloat(angularVelocity.z); + buffer.put((byte) (dto.isSleeping() ? 1 : 0)); + return Base64.getEncoder().encodeToString(buffer.array()); + } + + @Nonnull + private static PersistentBodyRuntimeStateDto decodePacked(@Nonnull String encoded) { + byte[] bytes = Base64.getDecoder().decode(encoded); + if (bytes.length != PACKED_BYTES) { + throw new IllegalArgumentException("Packed runtime state has invalid length"); + } + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + byte version = buffer.get(); + if (version != PACKED_VERSION) { + throw new IllegalArgumentException("Unsupported packed runtime state version"); + } + Vector3f position = new Vector3f(buffer.getFloat(), buffer.getFloat(), buffer.getFloat()); + Quaternionf rotation = new Quaternionf(buffer.getFloat(), + buffer.getFloat(), + buffer.getFloat(), + buffer.getFloat()); + Vector3f linearVelocity = new Vector3f(buffer.getFloat(), + buffer.getFloat(), + buffer.getFloat()); + Vector3f angularVelocity = new Vector3f(buffer.getFloat(), + buffer.getFloat(), + buffer.getFloat()); + boolean sleeping = buffer.get() != 0; + return new PersistentBodyRuntimeStateDto(position, + rotation, + linearVelocity, + angularVelocity, + sleeping); + } + + @Nonnull + private static PersistentBodyRuntimeStateDto decodeLegacyDocument(@Nonnull BsonDocument document) { + return new PersistentBodyRuntimeStateDto(vector(document.getDocument("Position", + new BsonDocument())), + quaternion(document.getDocument("Rotation", new BsonDocument())), + vector(document.getDocument("LinearVelocity", new BsonDocument())), + vector(document.getDocument("AngularVelocity", new BsonDocument())), + document.getBoolean("Sleeping", BsonBoolean.FALSE).getValue()); + } + + @Nonnull + private static Vector3f vector(@Nonnull BsonDocument document) { + return new Vector3f(number(document, "X", 0.0f), + number(document, "Y", 0.0f), + number(document, "Z", 0.0f)); + } + + @Nonnull + private static Quaternionf quaternion(@Nonnull BsonDocument document) { + return new Quaternionf(number(document, "X", 0.0f), + number(document, "Y", 0.0f), + number(document, "Z", 0.0f), + number(document, "W", 1.0f)); + } + + private static float number(@Nonnull BsonDocument document, + @Nonnull String key, + float fallback) { + BsonValue value = document.get(key); + return value != null && value.isNumber() ? (float) value.asNumber().doubleValue() : fallback; + } + + @Nonnull + private static PersistentBodyRuntimeStateDto decodeLegacyObject(@Nonnull RawJsonReader reader) + throws IOException { + Vector3f position = new Vector3f(); + Quaternionf rotation = new Quaternionf(); + Vector3f linearVelocity = new Vector3f(); + Vector3f angularVelocity = new Vector3f(); + boolean sleeping = false; + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return new PersistentBodyRuntimeStateDto(position, + rotation, + linearVelocity, + angularVelocity, + sleeping); + } + while (true) { + reader.consumeWhiteSpace(); + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + switch (key) { + case "Position" -> position = readVector(reader); + case "Rotation" -> rotation = readQuaternion(reader); + case "LinearVelocity" -> linearVelocity = readVector(reader); + case "AngularVelocity" -> angularVelocity = readVector(reader); + case "Sleeping" -> sleeping = readBooleanToken(reader); + default -> reader.skipValue(); + } + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + break; + } + reader.expect(','); + } + return new PersistentBodyRuntimeStateDto(position, + rotation, + linearVelocity, + angularVelocity, + sleeping); + } + + @Nonnull + private static Vector3f readVector(@Nonnull RawJsonReader reader) throws IOException { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return new Vector3f(); + } + while (true) { + reader.consumeWhiteSpace(); + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + switch (key) { + case "X" -> x = readFloatToken(reader); + case "Y" -> y = readFloatToken(reader); + case "Z" -> z = readFloatToken(reader); + default -> reader.skipValue(); + } + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + break; + } + reader.expect(','); + } + return new Vector3f(x, y, z); + } + + @Nonnull + private static Quaternionf readQuaternion(@Nonnull RawJsonReader reader) throws IOException { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float w = 1.0f; + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return new Quaternionf(); + } + while (true) { + reader.consumeWhiteSpace(); + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + switch (key) { + case "X" -> x = readFloatToken(reader); + case "Y" -> y = readFloatToken(reader); + case "Z" -> z = readFloatToken(reader); + case "W" -> w = readFloatToken(reader); + default -> reader.skipValue(); + } + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + break; + } + reader.expect(','); + } + return new Quaternionf(x, y, z, w); + } + + private static float readFloatToken(@Nonnull RawJsonReader reader) throws IOException { + StringBuilder token = new StringBuilder(32); + while (true) { + int next = reader.peek(); + if (!isNumberCharacter(next)) { + break; + } + token.append((char) reader.read()); + } + if (token.isEmpty()) { + throw new IOException("Expected persisted float value"); + } + try { + return Float.parseFloat(token.toString()); + } catch (NumberFormatException exception) { + throw new IOException("Invalid persisted float value: " + token, exception); + } + } + + private static boolean readBooleanToken(@Nonnull RawJsonReader reader) throws IOException { + String token = readWordToken(reader); + return switch (token.toString()) { + case "true" -> true; + case "false" -> false; + default -> throw new IOException("Invalid persisted boolean value: " + token); + }; + } + + private static void readNullToken(@Nonnull RawJsonReader reader) throws IOException { + String token = readWordToken(reader); + if (!"null".equals(token)) { + throw new IOException("Invalid persisted null value: " + token); + } + } + + @Nonnull + private static String readWordToken(@Nonnull RawJsonReader reader) throws IOException { + StringBuilder token = new StringBuilder(5); + while (true) { + int next = reader.peek(); + if (!isWordCharacter(next)) { + break; + } + token.append((char) reader.read()); + } + return token.toString(); + } + + private static boolean isWordCharacter(int value) { + return value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'; + } + + private static boolean isNumberCharacter(int value) { + return value == '-' + || value == '+' + || value == '.' + || value == 'e' + || value == 'E' + || value >= '0' && value <= '9'; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java index 65757fc1..3bf239df 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java @@ -1,10 +1,6 @@ package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; -import com.hypixel.hytale.codec.KeyedCodec; import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; import java.util.Objects; import javax.annotation.Nonnull; import org.joml.Quaternionf; @@ -15,33 +11,9 @@ */ public final class PersistentBodyRuntimeStateDto { - private static final Vector3f ZERO = new Vector3f(); - private static final Quaternionf IDENTITY = new Quaternionf(); - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentBodyRuntimeStateDto.class, PersistentBodyRuntimeStateDto::new) - .append(new KeyedCodec<>("Position", Vector3fUtil.CODEC, false), - (dto, value) -> dto.position.set(value != null ? value : ZERO), - PersistentBodyRuntimeStateDto::getPosition) - .add() - .append(new KeyedCodec<>("Rotation", ImpulseCodecs.QUATERNIONF, false), - (dto, value) -> dto.rotation.set(value != null ? value : IDENTITY), - PersistentBodyRuntimeStateDto::getRotation) - .add() - .append(new KeyedCodec<>("LinearVelocity", Vector3fUtil.CODEC, false), - (dto, value) -> dto.linearVelocity.set(value != null ? value : ZERO), - PersistentBodyRuntimeStateDto::getLinearVelocity) - .add() - .append(new KeyedCodec<>("AngularVelocity", Vector3fUtil.CODEC, false), - (dto, value) -> dto.angularVelocity.set(value != null ? value : ZERO), - PersistentBodyRuntimeStateDto::getAngularVelocity) - .add() - .append(new KeyedCodec<>("Sleeping", Codec.BOOLEAN, false), - (dto, value) -> dto.sleeping = value != null && value, - PersistentBodyRuntimeStateDto::isSleeping) - .add() - .build(); + public static final Codec CODEC = + PersistentBodyRuntimeStateCodec.INSTANCE; @Nonnull private final Vector3f position = new Vector3f(); From 8c467c844132a159efdebaa9a21cdc23feb9f9fd Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 16 Jun 2026 23:07:21 +0200 Subject: [PATCH 299/534] fix(core): detach external physics attachments on clean Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index edbf887e..2630f99e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -21,6 +21,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; +import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -37,10 +38,11 @@ /** * Clears Impulse-owned runtime state from the target world. * - *

    This removes Hytale adapter entities, visual proxies, runtime bodies, joints, - * and current world-collision cache bodies. Explicit physics spaces are kept, including - * their world-collision settings. Spaces with streaming world collision enabled may - * build fresh backend terrain bodies again on the next streaming tick.

    + *

    This removes Impulse-owned visual entities, detaches external physics attachments, + * clears runtime bodies, joints, and current world-collision cache bodies. Explicit + * physics spaces are kept, including their world-collision settings. Spaces with streaming + * world collision enabled may build fresh backend terrain bodies again on the next + * streaming tick.

    * *

    When a radius is provided, cleanup is intentionally narrower: it selects * registered body snapshots near the player, removes those bodies and their @@ -48,10 +50,11 @@ */ public class CleanCommand extends AbstractWorldCommand { - private static final int REMOVED_BODY_ENTITIES = 0; - private static final int REMOVED_ORPHAN_VISUAL_ENTITIES = 1; - private static final int REMOVED_SESSIONS = 2; - private static final int REMOVED_ENTITY_COUNTERS = 3; + private static final int REMOVED_ATTACHMENT_ENTITIES = 0; + private static final int DETACHED_EXTERNAL_ATTACHMENTS = 1; + private static final int REMOVED_ORPHAN_VISUAL_ENTITIES = 2; + private static final int REMOVED_SESSIONS = 3; + private static final int REMOVED_ENTITY_COUNTERS = 4; private final OptionalArg radiusArg = this.withOptionalArg( "radius", @@ -81,11 +84,22 @@ private static void cleanAll(@Nonnull CommandContext context, BodyAttachmentComponent.getComponentType(); ComponentType generatedProxyType = GeneratedVisualProxyComponent.getComponentType(); + ComponentType controllableType = + controllableTypeOrNull(); AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, commandBuffer) -> { - removedEntities.incrementAndGet(REMOVED_BODY_ENTITIES); - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + BodyAttachmentComponent attachment = + archetypeChunk.getComponent(index, attachmentType); + if (attachment == null) { + return; + } + cleanAttachedEntity(removedEntities, + commandBuffer, + archetypeChunk.getReferenceTo(index), + attachmentType, + controllableType, + attachment); }); store.forEachEntityParallel(generatedProxyType, @@ -149,10 +163,13 @@ private static void sendCleanAllSuccess(@Nonnull CommandContext context, @Nonnull AtomicIntegerArray removedEntities, @Nonnull PhysicsRuntimeResetResult reset, @Nonnull String worldName) { - context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_BODY_ENTITIES) - + " Impulse attachment entities, " + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) - + " orphan visual proxy entities, " + reset.removedBodies() + " runtime bodies, " - + reset.removedJoints() + " joints, and " + context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + + " Impulse-owned attachment entities, " + + removedEntities.get(DETACHED_EXTERNAL_ATTACHMENTS) + + " detached external attachments, " + + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) + + " orphan visual proxy entities, " + reset.removedBodies() + + " runtime bodies, " + reset.removedJoints() + " joints, and " + removedEntities.get(REMOVED_SESSIONS) + " control sessions in world " + worldName + ". Kept " + reset.keptSpaces() + " explicit physics spaces.")); } @@ -193,6 +210,8 @@ private void cleanWithinRadius(@Nonnull CommandContext context, BodyAttachmentComponent.getComponentType(); ComponentType generatedProxyType = GeneratedVisualProxyComponent.getComponentType(); + ComponentType controllableType = + controllableTypeOrNull(); AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); store.forEachEntityParallel(attachmentType, @@ -204,8 +223,12 @@ private void cleanWithinRadius(@Nonnull CommandContext context, return; } - removedEntities.incrementAndGet(REMOVED_BODY_ENTITIES); - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + cleanAttachedEntity(removedEntities, + commandBuffer, + archetypeChunk.getReferenceTo(index), + attachmentType, + controllableType, + attachment); }); store.forEachEntityParallel(generatedProxyType, @@ -250,8 +273,11 @@ private void cleanWithinRadius(@Nonnull CommandContext context, removedBodies++; } - context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_BODY_ENTITIES) - + " Impulse attachment entities, " + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) + context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + + " Impulse-owned attachment entities, " + + removedEntities.get(DETACHED_EXTERNAL_ATTACHMENTS) + + " detached external attachments, " + + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) + " orphan visual proxy entities, " + removedBodies + " runtime bodies, and " + removedEntities.get(REMOVED_SESSIONS) + " control sessions within radius " + radius + " in world " + world.getName() @@ -318,6 +344,33 @@ private static ComponentType contro : null; } + @Nullable + private static ComponentType controllableTypeOrNull() { + return ImpulseControllableComponent.isComponentTypeRegistered() + ? ImpulseControllableComponent.getComponentType() + : null; + } + + private static void cleanAttachedEntity( + @Nonnull AtomicIntegerArray removedEntities, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref entityRef, + @Nonnull ComponentType attachmentType, + @Nullable ComponentType controllableType, + @Nonnull BodyAttachmentComponent attachment) { + if (attachment.shouldRemoveEntityWhenBodyMissing()) { + removedEntities.incrementAndGet(REMOVED_ATTACHMENT_ENTITIES); + commandBuffer.removeEntity(entityRef, RemoveReason.REMOVE); + return; + } + removedEntities.incrementAndGet(DETACHED_EXTERNAL_ATTACHMENTS); + if (controllableType != null + && commandBuffer.getComponent(entityRef, controllableType) != null) { + commandBuffer.removeComponent(entityRef, controllableType); + } + commandBuffer.removeComponent(entityRef, attachmentType); + } + private static boolean containsBody(@Nonnull Set bodyUuids, @Nullable Ref bodyRef) { UUID bodyUuid = rowUuid(bodyRef); From 9a06b5ae0539e0cd6c2060d74fb72de341dea221 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 08:27:10 +0200 Subject: [PATCH 300/534] refactor(core): normalize physics plugin api names Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 2 +- .../core/internal/commands/CleanCommand.java | 4 +- .../core/internal/commands/SpaceCommand.java | 12 +- .../internal/commands/SpaceSelection.java | 4 +- .../commands/perf/PerfStatsCommand.java | 8 +- .../settings/SolverSettingsCommand.java | 8 +- .../settings/StepModeSettingCommand.java | 8 +- .../crucible/ImpulseApiCrucibleTests.java | 8 +- .../crucible/ImpulseLiveCrucibleTests.java | 18 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 2 +- .../PhysicsStoreBenchmarkQueries.java | 16 +- .../crucible/PhysicsStoreCrucibleSupport.java | 8 +- .../diagnostics/PhysicsEntityDiagnostics.java | 4 +- .../PhysicsKinematicControlSystem.java | 8 +- .../PhysicsStoreControlSessionMutations.java | 12 +- .../WorldCollisionPerfReportCommand.java | 8 +- ...sicsStoreWorldCollisionProducerSystem.java | 16 +- .../PhysicsStoreRuntimeCleaner.java | 6 +- .../PhysicsStoreSpaceMutations.java | 38 +-- .../PhysicsStoreTopologyMutations.java | 19 +- .../persistence/PersistentSpaceDto.java | 12 +- .../PhysicsStoreRegistration.java | 36 +-- .../resources/PhysicsSnapshotResource.java | 38 +-- .../PhysicsStepSchedulerResource.java | 6 +- .../systems/BodyBindingSystem.java | 14 +- .../systems/BodyCommandApplicationSystem.java | 6 +- .../CompletedStepPublicationSystem.java | 14 +- .../systems/JointBindingSystem.java | 2 +- .../systems/PersistenceCaptureSystem.java | 46 ++-- .../systems/PersistenceHydrationSystem.java | 38 +-- .../systems/PhysicsStoreSystemSupport.java | 2 +- .../systems/SpaceBindingSystem.java | 6 +- .../SpaceSettingsApplicationSystem.java | 6 +- .../systems/StaleBodyRemovalSystem.java | 6 +- .../systems/StepSubmissionSystem.java | 118 ++++----- .../systems/TargetBindingSystem.java | 2 +- .../systems/TerrainColliderBindingSystem.java | 2 +- .../systems/TerrainMutationDrainSystem.java | 10 +- .../systems/WorldCollisionIndexSystem.java | 4 +- .../terrain/TerrainColliderPayload.java | 12 +- .../PhysicsWorldRuntimeResource.java | 100 ++++---- .../systems/debug/PhysicsDebugRenderer.java | 2 +- .../systems/debug/PhysicsDebugSystem.java | 4 +- .../debug/PhysicsStoreDebugQueries.java | 28 +-- .../PhysicsStoreEventPublicationSystem.java | 4 +- .../PhysicsBodyAttachmentIndexSystem.java | 4 +- .../systems/sync/PhysicsSyncSystem.java | 27 +- .../sync/PhysicsTransformAuthority.java | 4 +- .../visual/GeneratedProxyLifecycle.java | 4 +- .../PhysicsGeneratedProxyCleanupSystem.java | 4 +- .../components/BodyCommandComponent.java | 2 +- .../components/BodyComponent.java | 2 +- .../components/ColliderComponent.java | 2 +- .../components/CollisionFilterComponent.java | 2 +- .../CollisionLodSettingsComponent.java | 2 +- .../components/DynamicsComponent.java | 2 +- .../ExtensionSettingsComponent.java | 2 +- .../components/JointComponent.java | 2 +- .../components/MaterialComponent.java | 2 +- .../components/ShapeComponent.java | 2 +- .../components/SolverSettingsComponent.java | 2 +- .../components/SpaceComponent.java | 2 +- .../components/TargetComponent.java | 44 +--- .../components/TerrainColliderComponent.java | 44 +--- .../components/UuidComponent.java | 2 +- ...isualMaterializationSettingsComponent.java | 2 +- .../VisualSyncSettingsComponent.java | 2 +- .../components/WorldCollisionComponent.java | 2 +- .../control/PhysicsControlSessions.java | 6 +- .../persistence/PhysicsPersistence.java | 10 +- .../physicsstore/BodyEntityDescriptor.java | 14 +- ...ysicsStoreAsync.java => PhysicsAsync.java} | 4 +- ...dAccess.java => PhysicsBackendAccess.java} | 10 +- ...icsStoreBodies.java => PhysicsBodies.java} | 12 +- .../physicsstore/PhysicsBodyEntities.java | 16 +- ...agnostics.java => PhysicsDiagnostics.java} | 144 +++++------ ...toreEntities.java => PhysicsEntities.java} | 48 ++-- ...EntityRefs.java => PhysicsEntityRefs.java} | 8 +- .../physicsstore/PhysicsJointEntities.java | 12 +- ...toreRaycasts.java => PhysicsRaycasts.java} | 88 +++---- ...icsStoreSpaces.java => PhysicsSpaces.java} | 6 +- .../physicsstore/PhysicsStoreTypes.java | 36 +-- ...reThreading.java => PhysicsThreading.java} | 4 +- .../snapshots/PhysicsStoreSnapshotFrame.java | 20 -- .../projection/BodyAttachmentComponent.java | 2 +- .../PhysicsBodySnapshot.java} | 42 ++-- .../snapshots/PhysicsSnapshotFrame.java | 20 ++ impulse-core/src/module-info/module-info.java | 1 - .../examples/commands/DropCommand.java | 6 +- .../examples/commands/ForcesCommand.java | 15 +- .../examples/commands/GrabCommand.java | 31 +-- .../examples/commands/JointsCommand.java | 9 +- .../examples/commands/MaterialsCommand.java | 3 +- .../examples/commands/PersistenceCommand.java | 8 +- .../commands/PhysicsStoreExampleCommands.java | 30 +-- .../examples/commands/RaycastCommand.java | 11 +- .../examples/commands/ShapesCommand.java | 3 +- .../commands/WorldCollisionCommand.java | 2 + .../stress/StressBenchmarkCommand.java | 14 +- .../commands/stress/StressBodiesCommand.java | 6 +- .../commands/stress/StressJointsCommand.java | 12 +- .../stress/StressRawBodiesCommand.java | 6 +- .../commands/stress/StressRaycastCommand.java | 12 +- .../commands/stress/StressShapesCommand.java | 4 +- .../explosive/ExplosiveBlockPolicy.java | 2 +- .../explosive/ExplosiveBlockRuntime.java | 8 +- ...nchmarkEntityRemovalDiagnosticsSystem.java | 2 +- .../systems/ExplosiveFuseContactSystem.java | 2 +- .../systems/ExplosiveFuseTickSystem.java | 18 +- .../examples/utils/BlockBodyBatchBuilder.java | 144 +++++++++++ .../examples/utils/BlockBodyBatchResult.java | 34 +++ .../ExampleBlockEntityVisuals.java | 2 +- .../ExamplePhysicsOriginMath.java | 2 +- .../ExamplePhysicsUtils.java | 232 +++--------------- 114 files changed, 982 insertions(+), 1029 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/BodyCommandComponent.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/BodyComponent.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/ColliderComponent.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/CollisionFilterComponent.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/CollisionLodSettingsComponent.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/DynamicsComponent.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/ExtensionSettingsComponent.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/JointComponent.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/MaterialComponent.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/ShapeComponent.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/SolverSettingsComponent.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/SpaceComponent.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/TargetComponent.java (86%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/TerrainColliderComponent.java (88%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/UuidComponent.java (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/VisualMaterializationSettingsComponent.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/VisualSyncSettingsComponent.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/WorldCollisionComponent.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreAsync.java => PhysicsAsync.java} (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreBackendAccess.java => PhysicsBackendAccess.java} (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreBodies.java => PhysicsBodies.java} (87%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreDiagnostics.java => PhysicsDiagnostics.java} (73%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreEntities.java => PhysicsEntities.java} (86%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreEntityRefs.java => PhysicsEntityRefs.java} (84%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreRaycasts.java => PhysicsRaycasts.java} (85%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreSpaces.java => PhysicsSpaces.java} (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/{PhysicsStoreThreading.java => PhysicsThreading.java} (99%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/projection/BodyAttachmentComponent.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore/snapshots/PhysicsStoreBodySnapshot.java => snapshots/PhysicsBodySnapshot.java} (56%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsSnapshotFrame.java create mode 100644 impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java create mode 100644 impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java rename impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/{commands => utils}/ExampleBlockEntityVisuals.java (97%) rename impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/{commands => utils}/ExamplePhysicsOriginMath.java (92%) rename impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/{commands => utils}/ExamplePhysicsUtils.java (81%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 60c8558b..882bb9ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -35,7 +35,7 @@ import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.nio.file.Path; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 2630f99e..b9b9f075 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -22,8 +22,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 4856632b..b83e2e4c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -104,8 +104,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, @Nonnull World world) { Store store = world.getEntityStore().getStore(); PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.spaceSummariesAsync(world), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.spaceSummariesAsync(world), summaries -> sendSpaces(context, world, resource, summaries)); } @@ -181,8 +181,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, * before deleting the space. */ int registeredBodies = countRegisteredBodies(resource, spaceId); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.spaceSummariesAsync(world, selectedSpace.spaceRef()), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.spaceSummariesAsync(world, selectedSpace.spaceRef()), summaries -> deleteIfEmpty(context, world, resource, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java index 63cad2d4..cea55c4a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.Comparator; import java.util.Objects; import java.util.UUID; @@ -106,7 +106,7 @@ private static Store store(@Nonnull World world) { Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(store, "select a PhysicsStore space"); + PhysicsThreading.requireWorldThread(store, "select a PhysicsStore space"); return store; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java index 78aea161..c4293b9f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java @@ -4,8 +4,8 @@ import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -21,8 +21,8 @@ public PerfStatsCommand() { @Override protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.spaceSummariesAsync(world), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.spaceSummariesAsync(world), spaces -> sendStats(ctx, world, spaces)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index bcac7a3a..640bf707 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -12,8 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; @@ -64,8 +64,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } SpaceId spaceId = selectedSpace.spaceId(); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.solverCapabilityAsync(world, selectedSpace.spaceRef()), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.solverCapabilityAsync(world, selectedSpace.spaceRef()), summary -> applySettings(ctx, resource, selectedSpace.spaceRef(), spaceId, summary)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java index 0b15cde7..fb3cb01f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java @@ -10,8 +10,8 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; @@ -55,8 +55,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } if (stepMode == PhysicsStepMode.CCD) { - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.unsupportedCcdSpacesAsync(world), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.unsupportedCcdSpacesAsync(world), summaries -> applyStepModeIfSupported(ctx, resource, stepMode, summaries)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 250bf635..08b0e144 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -21,8 +21,8 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; @@ -238,7 +238,7 @@ private static CompletionStage populatedBodyCleanup( return context.waitApproxTicksOnWorld(4) .thenCompose(_ -> removeBodyEntityAndWait(context, store, bodyRef)) .thenApply(_ -> { - boolean spaceEmpty = PhysicsStoreDiagnostics.bodyCount(store, spaceId) == 0; + boolean spaceEmpty = PhysicsDiagnostics.bodyCount(store, spaceId) == 0; boolean noRegistrations = resource.getBodyRegistrationViews().isEmpty(); boolean removedSpace = true; if (checkSpaceRemoval || spaceEmpty) { @@ -278,7 +278,7 @@ private static Ref addCrucibleBox(@Nonnull Store sto RigidBodySpawnSettings.defaults(), null, PhysicsBodyPersistenceMode.RUNTIME_ONLY); - return store.addEntity(PhysicsStoreEntities.bodyHolder(store, + return store.addEntity(PhysicsEntities.bodyHolder(store, descriptor.bodyUuid(), descriptor.body(), descriptor.dynamics(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 18f48327..5561c81f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -12,19 +12,19 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -116,7 +116,7 @@ private static boolean bodyAndEntityMovedDown(Store store, return false; } double transformY = transform.getPosition().y; - PhysicsStoreBodySnapshot snapshot = physicsStore(store.getExternalData().getWorld()) + PhysicsBodySnapshot snapshot = physicsStore(store.getExternalData().getWorld()) .getResource(PhysicsSnapshotResource.getResourceType()) .getBody(bodyUuid); if (snapshot == null) { @@ -143,7 +143,7 @@ private static void submitLiveBody(Store store, SpaceId spaceId, UUID bodyUuid, Vector3d visualPosition) { - PhysicsStoreThreading.requireWorldThread(store, "add Crucible live PhysicsStore body entity"); + PhysicsThreading.requireWorldThread(store, "add Crucible live PhysicsStore body entity"); BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody( PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), bodyUuid, @@ -155,7 +155,7 @@ private static void submitLiveBody(Store store, RigidBodySpawnSettings.defaults(), null, PhysicsBodyPersistenceMode.PERSISTENT); - store.addEntity(PhysicsStoreEntities.bodyHolder(store, + store.addEntity(PhysicsEntities.bodyHolder(store, descriptor.bodyUuid(), descriptor.body(), descriptor.dynamics(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index f08409ce..815ec241 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 7b48ead8..30f1d484 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -12,11 +12,11 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nonnull; @@ -35,7 +35,7 @@ private PhysicsStoreBenchmarkQueries() { static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store store, @Nullable PhysicsStoreWorldCollisionStreamingResource streaming, @Nonnull BenchmarkSpaceStatsRequest query) { - PhysicsStoreThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); + PhysicsThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, query.spaceId()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); BenchmarkSpaceStatsAccumulator stats = new BenchmarkSpaceStatsAccumulator(); @@ -62,7 +62,7 @@ private static void collectBodyRows(@Nonnull ArchetypeChunk chunk, if (uuid == null) { continue; } - PhysicsStoreBodySnapshot snapshot = snapshots.getBody(uuid.getUuid()); + PhysicsBodySnapshot snapshot = snapshots.getBody(uuid.getUuid()); if (snapshot == null) { continue; } @@ -74,7 +74,7 @@ private static void collectBodyRows(@Nonnull ArchetypeChunk chunk, private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, @Nonnull BodyComponent body, @Nullable ShapeComponent shape, - @Nonnull PhysicsStoreBodySnapshot snapshot, + @Nonnull PhysicsBodySnapshot snapshot, @Nonnull BenchmarkSpaceStatsRequest query) { stats.bodies++; if (snapshot.bodyType() == PhysicsBodyType.DYNAMIC) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index c5a9ad2c..74ebe445 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; @@ -14,8 +15,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -52,7 +52,7 @@ static Ref addBody(@Nonnull Store store, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - PhysicsStoreThreading.requireWorldThread(store, "add Crucible PhysicsStore body entity"); + PhysicsThreading.requireWorldThread(store, "add Crucible PhysicsStore body entity"); BodyEntityDescriptor descriptor = PhysicsBodyEntities.body( PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), bodyUuid, @@ -64,7 +64,7 @@ static Ref addBody(@Nonnull Store store, linearVelocity, kind, persistenceMode); - return store.addEntity(PhysicsStoreEntities.bodyHolder(store, + return store.addEntity(PhysicsEntities.bodyHolder(store, descriptor.bodyUuid(), descriptor.body(), descriptor.dynamics(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java index 18bd2aa9..97402278 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java @@ -7,8 +7,8 @@ import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems.Visible; import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerArray; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 5e67892c..75d5c734 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -22,9 +22,9 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.util.Collections; @@ -167,7 +167,7 @@ private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( PhysicsStore physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); Store physics = physicsStore.getStore(); - PhysicsStoreThreading.requireWorldThread(physics, + PhysicsThreading.requireWorldThread(physics, "resolve PhysicsStore kinematic control targets"); if (!validBodyRef(physics, bodyRef) || !validBodyRef(physics, anchorBodyRef)) { return null; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 46c95f2a..461dec9f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -6,14 +6,14 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -33,7 +33,7 @@ public static void applyRelease(@Nonnull Store store, Store physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(physicsStore, + PhysicsThreading.requireWorldThread(physicsStore, "apply PhysicsStore control-session release mutations"); Ref controlJointRef = session.getControlJointRef(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java index f21d5903..3a82c728 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java @@ -12,8 +12,8 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.VisualSnapshot; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; @@ -34,8 +34,8 @@ public WorldCollisionPerfReportCommand() { protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Store store = world.getEntityStore().getStore(); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.spaceSummariesAsync(world), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.spaceSummariesAsync(world), summaries -> sendReport(ctx, world, store, summaries)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index 21559cd3..82c4e83d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -30,9 +30,9 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -79,7 +79,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { World world = store.getExternalData().getWorld(); PhysicsStore physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore(); Store physics = physicsStore.getStore(); - PhysicsStoreThreading.requireWorldThread(physics, + PhysicsThreading.requireWorldThread(physics, "produce PhysicsStore world-collision terrain mutations"); PhysicsTerrainMutationQueueResource queue = physics.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); @@ -107,7 +107,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } streaming.retainSpaces(retainedSpaces, queue); - PhysicsStoreSnapshotFrame physicsFrame = snapshotResource.getLatestFrame(); + PhysicsSnapshotFrame physicsFrame = snapshotResource.getLatestFrame(); for (SpaceWorldCollisionSettings settings : spaces) { if (snapshot != null) { snapshot.incrementStreamingSpaces(); @@ -134,7 +134,7 @@ private static void processSpace(@Nonnull World world, @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull SpaceWorldCollisionSettings settings, @Nonnull List playerPositions, - @Nonnull PhysicsStoreSnapshotFrame physicsFrame, + @Nonnull PhysicsSnapshotFrame physicsFrame, long currentTick, @Nullable Snapshot snapshot) { LongSet visitedSections = new LongOpenHashSet(); @@ -195,14 +195,14 @@ private static void processSpace(@Nonnull World world, private static List collectDynamicBodyTargets( @Nonnull PhysicsStoreWorldCollisionStreamingResource streaming, @Nonnull SpaceWorldCollisionSettings settings, - @Nonnull PhysicsStoreSnapshotFrame physicsFrame, + @Nonnull PhysicsSnapshotFrame physicsFrame, long currentTick, @Nullable Snapshot snapshot) { Map uniqueTargets = new Object2ObjectOpenHashMap<>(); int spatialCandidates = 0; int dynamicCandidates = 0; - for (PhysicsStoreBodySnapshot body : physicsFrame.bodies()) { + for (PhysicsBodySnapshot body : physicsFrame.bodies()) { if (!body.spaceUuid().equals(settings.spaceUuid())) { continue; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index 5a856665..c689777d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -14,8 +14,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import javax.annotation.Nonnull; /** @@ -27,7 +27,7 @@ private PhysicsStoreRuntimeCleaner() { } public static void clearAll(@Nonnull Store store) { - PhysicsStoreThreading.requireBackendIdle(store, "clear PhysicsStore runtime rows"); + PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore runtime rows"); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, commandBuffer) -> commandBuffer.removeEntity( chunk.getReferenceTo(index), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index e762e9c2..bdc34459 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -12,16 +12,16 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; @@ -47,7 +47,7 @@ public static Ref addSpace(@Nonnull Store store, Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); Objects.requireNonNull(backendId, "backendId"); Objects.requireNonNull(settings, "settings"); - PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore space entity"); + PhysicsThreading.requireWorldThread(store, "add a PhysicsStore space entity"); if (backendId.value().isBlank()) { throw new IllegalArgumentException("PhysicsStore space backend id is blank: " + spaceUuid); @@ -65,7 +65,7 @@ public static Ref addSpace(@Nonnull Store store, throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid + " is already registered"); } - Ref ref = store.addEntity(PhysicsStoreEntities.spaceHolder(store, + Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, spaceUuid, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), new WorldCollisionComponent(settings.getWorldCollisionSettings()), @@ -113,7 +113,7 @@ public static void putSpaceGravity(@Nonnull Store store, Objects.requireNonNull(ref, "ref"); Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(gravity, "gravity"); - PhysicsStoreThreading.requireWorldThread(store, "update PhysicsStore space gravity"); + PhysicsThreading.requireWorldThread(store, "update PhysicsStore space gravity"); SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); if (space == null) { throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid @@ -134,11 +134,11 @@ public static void putSpaceSettings(@Nonnull Store store, Objects.requireNonNull(ref, "ref"); Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(settings, "settings"); - PhysicsStoreThreading.requireWorldThread(store, "update a PhysicsStore space entity"); + PhysicsThreading.requireWorldThread(store, "update a PhysicsStore space entity"); store.putComponent(ref, WorldCollisionComponent.getComponentType(), new WorldCollisionComponent(settings.getWorldCollisionSettings())); - PhysicsStoreEntities.putSpaceSettingsComponents(store, + PhysicsEntities.putSpaceSettingsComponents(store, ref, new SolverSettingsComponent(settings.getSolverSettings()), new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), @@ -159,7 +159,7 @@ public static void removeEmptySpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { Objects.requireNonNull(store, "store"); Objects.requireNonNull(spaceUuid, "spaceUuid"); - PhysicsStoreThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); + PhysicsThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -195,7 +195,7 @@ public static void removeEmptySpace(@Nonnull Store store, @Nonnull public static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); + PhysicsThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); if (spaceUuid == null) { @@ -208,7 +208,7 @@ public static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull private static Ref requireSpaceRef(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space entity"); + PhysicsThreading.requireWorldThread(store, "resolve a PhysicsStore space entity"); Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) .getByUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); if (ref == null || !ref.isValid()) { @@ -222,7 +222,7 @@ private static Ref requireSpaceRef(@Nonnull Store st private static UUID requireSpaceUuid(@Nonnull Store store, @Nonnull Ref ref) { Objects.requireNonNull(ref, "ref"); - PhysicsStoreThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); + PhysicsThreading.requireWorldThread(store, "resolve a PhysicsStore space UUID"); if (ref.getStore() != store || !ref.isValid()) { throw new IllegalArgumentException("PhysicsStore space entity is not valid: " + ref); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index bbfaafc0..258e1668 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -18,15 +18,14 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import it.unimi.dsi.fastutil.longs.LongArrayList; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nonnull; @@ -42,7 +41,7 @@ private PhysicsStoreTopologyMutations() { public static void destroyBody(@Nonnull Store store, @Nonnull UUID bodyUuid) { - PhysicsStoreThreading.requireBackendIdle(store, "destroy a PhysicsStore body entity"); + PhysicsThreading.requireBackendIdle(store, "destroy a PhysicsStore body entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -55,7 +54,7 @@ public static void destroyBody(@Nonnull Store store, @Nonnull public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( @Nonnull Store store) { - PhysicsStoreThreading.requireBackendIdle(store, "clear PhysicsStore body entities"); + PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore body entities"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -76,7 +75,7 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( public static void removeSpaceWithContents(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); + PhysicsThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); @@ -91,7 +90,7 @@ public static void removeSpaceWithContents(@Nonnull Store store, public static int clearTerrainForSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireBackendIdle(store, "clear PhysicsStore terrain rows"); + PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore terrain rows"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); int removedBodies = 0; Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java index 31372d4c..d8f8a128 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java @@ -7,12 +7,12 @@ import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 0ca9c39d..0502ca6d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -44,24 +44,24 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.WorldCollisionIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index 50e09a0f..087c06a5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -5,8 +5,8 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.ArrayList; @@ -29,27 +29,27 @@ public PhysicsSnapshotResource() { } @Nonnull - public PhysicsStoreSnapshotFrame getLatestFrame() { + public PhysicsSnapshotFrame getLatestFrame() { return snapshot.frame(); } @Nullable - public PhysicsStoreBodySnapshot getBody(@Nonnull UUID bodyUuid) { + public PhysicsBodySnapshot getBody(@Nonnull UUID bodyUuid) { return snapshot.bodiesByUuid().get(bodyUuid); } @Nullable - public PhysicsStoreBodySnapshot getBody(@Nonnull Ref bodyRef) { - PhysicsStoreBodySnapshot body = snapshot.bodiesByRowIndex() + public PhysicsBodySnapshot getBody(@Nonnull Ref bodyRef) { + PhysicsBodySnapshot body = snapshot.bodiesByRowIndex() .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); return body != null && sameRef(body.bodyRef(), bodyRef) ? body : null; } - public void publish(@Nonnull PhysicsStoreSnapshotFrame frame) { - Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); - Int2ObjectOpenHashMap bodiesByRowIndex = + public void publish(@Nonnull PhysicsSnapshotFrame frame) { + Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); + Int2ObjectOpenHashMap bodiesByRowIndex = new Int2ObjectOpenHashMap<>(); - for (PhysicsStoreBodySnapshot body : frame.bodies()) { + for (PhysicsBodySnapshot body : frame.bodies()) { bodiesByUuid.put(body.bodyUuid(), body); Ref bodyRef = body.bodyRef(); if (bodyRef != null) { @@ -76,11 +76,11 @@ public void clear() { @Nonnull private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, @Nonnull UUID bodyUuid) { - List bodies = new ArrayList<>(); - Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); - Int2ObjectOpenHashMap bodiesByRowIndex = + List bodies = new ArrayList<>(); + Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); + Int2ObjectOpenHashMap bodiesByRowIndex = new Int2ObjectOpenHashMap<>(); - for (PhysicsStoreBodySnapshot body : current.frame().bodies()) { + for (PhysicsBodySnapshot body : current.frame().bodies()) { if (bodyUuid.equals(body.bodyUuid())) { continue; } @@ -92,7 +92,7 @@ private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, } } return new PublishedSnapshot( - new PhysicsStoreSnapshotFrame(current.frame().sequence(), + new PhysicsSnapshotFrame(current.frame().sequence(), current.frame().dt(), bodies), Map.copyOf(bodiesByUuid), @@ -113,12 +113,12 @@ public static ResourceType getResourceTyp } private record PublishedSnapshot( - @Nonnull PhysicsStoreSnapshotFrame frame, - @Nonnull Map bodiesByUuid, - @Nonnull Int2ObjectOpenHashMap bodiesByRowIndex) { + @Nonnull PhysicsSnapshotFrame frame, + @Nonnull Map bodiesByUuid, + @Nonnull Int2ObjectOpenHashMap bodiesByRowIndex) { private static final PublishedSnapshot EMPTY = - new PublishedSnapshot(PhysicsStoreSnapshotFrame.EMPTY, + new PublishedSnapshot(PhysicsSnapshotFrame.EMPTY, Map.of(), new Int2ObjectOpenHashMap<>()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java index c3955c82..249eb207 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import java.util.List; @@ -253,7 +253,7 @@ public record CompletedStep(@Nullable StepInput input, long stepSubmitNanos, long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, - @Nonnull List bodySnapshots, + @Nonnull List bodySnapshots, @Nullable Throwable failure) { public CompletedStep(int spaces, @@ -268,7 +268,7 @@ public CompletedStep(int spaces, long stepSubmitNanos, long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, - @Nonnull List bodySnapshots) { + @Nonnull List bodySnapshots) { this(null, spaces, substeps, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java index 0fd8b9f6..9572dd5f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java @@ -19,13 +19,13 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java index d6405f12..bd6805cc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java @@ -19,9 +19,9 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java index 0e62e42c..944cc57a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java @@ -30,10 +30,10 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; @@ -85,14 +85,14 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) input.droppedBacklogDtSeconds(), input.dtCapHit()); } - List bodies = completed.bodySnapshots(); + List bodies = completed.bodySnapshots(); Set snapshotBodyUuids = new ObjectOpenHashSet<>(); - for (PhysicsStoreBodySnapshot body : bodies) { + for (PhysicsBodySnapshot body : bodies) { snapshotBodyUuids.add(body.bodyUuid()); } long nextSequence = snapshot.getLatestFrame().sequence() + 1L; float frameDt = input != null ? input.submittedDtSeconds() : dt; - PhysicsStoreSnapshotFrame frame = new PhysicsStoreSnapshotFrame(nextSequence, + PhysicsSnapshotFrame frame = new PhysicsSnapshotFrame(nextSequence, frameDt, bodies); snapshot.publish(frame); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java index ba45f0f1..bf31329c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java index 27ecd87a..2ccbe1b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java @@ -22,23 +22,23 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; @@ -78,11 +78,11 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } @Nonnull - private static Map snapshotBodiesByUuid( + private static Map snapshotBodiesByUuid( @Nonnull Store store) { PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - Map bodies = new Object2ObjectOpenHashMap<>(); - for (PhysicsStoreBodySnapshot body : snapshots.getLatestFrame().bodies()) { + Map bodies = new Object2ObjectOpenHashMap<>(); + for (PhysicsBodySnapshot body : snapshots.getLatestFrame().bodies()) { bodies.put(body.bodyUuid(), body); } return bodies; @@ -103,7 +103,7 @@ public Set> getDependencies() { private static final class Capture { @Nonnull - private final Map snapshotsByBodyUuid; + private final Map snapshotsByBodyUuid; @Nonnull private final List spaceRows = new ArrayList<>(); @Nonnull @@ -113,7 +113,7 @@ private static final class Capture { @Nonnull private final List terrainRows = new ArrayList<>(); - private Capture(@Nonnull Map snapshotsByBodyUuid) { + private Capture(@Nonnull Map snapshotsByBodyUuid) { this.snapshotsByBodyUuid = snapshotsByBodyUuid; } @@ -260,7 +260,7 @@ private PersistentBodyDto bodyDto(@Nonnull BodyRow row) { @Nonnull private PersistentBodyRuntimeStateDto runtimeState(@Nonnull UUID bodyUuid, @Nullable TargetComponent target) { - PhysicsStoreBodySnapshot snapshot = snapshotsByBodyUuid.get(bodyUuid); + PhysicsBodySnapshot snapshot = snapshotsByBodyUuid.get(bodyUuid); if (snapshot != null) { return new PersistentBodyRuntimeStateDto(snapshot.position(), snapshot.rotation(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java index 5db5cce1..b742eede 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java @@ -18,24 +18,24 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentTerrainColliderDto; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; @@ -146,7 +146,7 @@ private static void addBody(@Nonnull Store store, PersistentShapeDto shape = shapesByUuid.get(collider.getShapeUuid()); PersistentMaterialDto material = materialsByUuid.get(collider.getMaterialUuid()); if (shape != null && material != null) { - PhysicsStoreEntities.addBodyComponents(holder, + PhysicsEntities.addBodyComponents(holder, body, dynamics, target, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java index 9c7016b8..d9103086 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java index a4e3e39f..4aef352b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java @@ -21,9 +21,9 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import java.util.Set; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java index 9777a27b..05b3cbb2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java @@ -14,9 +14,9 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettingValue; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.api.BackendId; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java index 13541f9b..63a913ee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java @@ -18,9 +18,9 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java index 667ffabb..62ce1f45 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java @@ -23,8 +23,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import java.util.ArrayList; @@ -122,7 +122,7 @@ private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtim ? collectStepPhaseStats(bindings) : PhysicsStepPhaseStats.unavailable(); long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; - List bodySnapshots = collectOwnerLaneSnapshots(runtime, + List bodySnapshots = collectOwnerLaneSnapshots(runtime, bindings); long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; return new CompletedStep(counters.spaceCount, @@ -143,10 +143,10 @@ private static List runtimeStepBindings( } @Nonnull - private static List collectOwnerLaneSnapshots( + private static List collectOwnerLaneSnapshots( @Nonnull PhysicsRuntimeResource runtime, @Nonnull List bindings) { - List snapshots = new ArrayList<>(); + List snapshots = new ArrayList<>(); for (RuntimeStepBinding binding : bindings) { binding.backendRuntime().snapshotBodies(binding.spaceHandle().value(), bodyIds -> runtime.forEachBodyHandle(binding.spaceHandle(), @@ -208,7 +208,7 @@ private static List collectOwnerLaneSnapshots( } private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull List snapshots, + @Nonnull List snapshots, long bodyId, int bodyTypeCode, float positionX, @@ -230,7 +230,7 @@ private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource run if (metadata == null) { return; } - snapshots.add(new PhysicsStoreBodySnapshot(metadata.bodyRef(), + snapshots.add(new PhysicsBodySnapshot(metadata.bodyRef(), metadata.bodyUuid(), metadata.spaceUuid(), BackendRuntimeCodes.bodyType(bodyTypeCode), @@ -321,69 +321,55 @@ public Set> getDependencies() { return DEPENDENCIES; } - private static final class ContinuousCollisionSync implements BackendBodySnapshotSink { - - @Nonnull - private final PhysicsBackendRuntime backendRuntime; - @Nonnull - private final BackendSpaceHandle spaceHandle; - private final long bodyId; - private final boolean targetEnabled; - - private ContinuousCollisionSync(@Nonnull PhysicsBackendRuntime backendRuntime, - @Nonnull BackendSpaceHandle spaceHandle, - long bodyId, - boolean targetEnabled) { - this.backendRuntime = backendRuntime; - this.spaceHandle = spaceHandle; - this.bodyId = bodyId; - this.targetEnabled = targetEnabled; - } + private record ContinuousCollisionSync(@Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, long bodyId, + boolean targetEnabled) implements + BackendBodySnapshotSink { @Override - public void accept(long bodyId, - int shapeTypeCode, - int bodyTypeCode, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - float linearVelocityX, - float linearVelocityY, - float linearVelocityZ, - float angularVelocityX, - float angularVelocityY, - float angularVelocityZ, - boolean sleeping, - boolean sensor, - float mass, - float friction, - float restitution, - float linearDamping, - float angularDamping, - int collisionGroup, - int collisionMask, - boolean continuousCollisionEnabled, - float centerOfMassOffsetY, - boolean hasBoxHalfExtents, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - int axisCode) { - if (BackendRuntimeCodes.bodyType(bodyTypeCode) != PhysicsBodyType.DYNAMIC - || continuousCollisionEnabled == targetEnabled) { - return; + public void accept(long bodyId, + int shapeTypeCode, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping, + boolean sensor, + float mass, + float friction, + float restitution, + float linearDamping, + float angularDamping, + int collisionGroup, + int collisionMask, + boolean continuousCollisionEnabled, + float centerOfMassOffsetY, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode) { + if (BackendRuntimeCodes.bodyType(bodyTypeCode) != PhysicsBodyType.DYNAMIC + || continuousCollisionEnabled == targetEnabled) { + return; + } + backendRuntime.setBodyContinuousCollision(spaceHandle.value(), + this.bodyId, + targetEnabled); } - backendRuntime.setBodyContinuousCollision(spaceHandle.value(), - this.bodyId, - targetEnabled); } - } private static final class StepRisk implements BackendBodySnapshotSink { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java index dd65d115..9a4db8cf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import java.util.Set; import java.util.function.BiConsumer; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java index 8a549759..205b5a96 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java @@ -26,7 +26,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.TerrainNeighbor; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java index 59030770..ef2014d8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java @@ -14,8 +14,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; import java.util.Map; @@ -114,7 +114,7 @@ private static void applyTerrainMutation(@Nonnull Store store, if (existing != null) { removePayload(terrainPayloads, existing.getPayloadResourceKey()); } - PhysicsStoreEntities.putTerrainColliderComponent(store, + PhysicsEntities.putTerrainColliderComponent(store, ref, removedTerrainComponent(identity, mutation)); } @@ -135,12 +135,12 @@ private static void applyTerrainMutation(@Nonnull Store store, && !existing.getPayloadResourceKey().equals(component.getPayloadResourceKey())) { removePayload(terrainPayloads, existing.getPayloadResourceKey()); } - PhysicsStoreEntities.putTerrainColliderComponent(store, ref, component); + PhysicsEntities.putTerrainColliderComponent(store, ref, component); refsThisDrain.put(terrainUuid, ref); return; } refsThisDrain.put(terrainUuid, - store.addEntity(PhysicsStoreEntities.terrainColliderHolder(store, + store.addEntity(PhysicsEntities.terrainColliderHolder(store, terrainUuid, component), AddReason.SPAWN)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java index 8d0106b0..73601948 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java @@ -12,8 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java index c239fd12..69706700 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java @@ -22,14 +22,10 @@ public record TerrainColliderPayload(float voxelSizeX, @Nonnull List neighbors) { public TerrainColliderPayload { - voxelCoordinates = voxelCoordinates != null - ? Arrays.copyOf(voxelCoordinates, voxelCoordinates.length) - : new int[0]; - mergedFullCubeBoxes = mergedFullCubeBoxes != null - ? List.copyOf(mergedFullCubeBoxes) - : List.of(); - detailBoxes = detailBoxes != null ? List.copyOf(detailBoxes) : List.of(); - neighbors = neighbors != null ? List.copyOf(neighbors) : List.of(); + voxelCoordinates = Arrays.copyOf(voxelCoordinates, voxelCoordinates.length); + mergedFullCubeBoxes = List.copyOf(mergedFullCubeBoxes); + detailBoxes = List.copyOf(detailBoxes); + neighbors = List.copyOf(neighbors); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 91d44e34..76dcfe12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -9,12 +9,11 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; @@ -55,21 +54,20 @@ import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -210,7 +208,7 @@ private World requireAuthoritativeWorld(@Nonnull String operation) { @Nonnull private Store authoritativePhysicsStore(@Nonnull String operation) { Store store = physicsStore(requireAuthoritativeWorld(operation)); - PhysicsStoreThreading.requireWorldThread(store, operation); + PhysicsThreading.requireWorldThread(store, operation); return store; } @@ -340,7 +338,7 @@ private PhysicsMutationHandle enqueueAuthoritativePhysicsStoreMutation( World world = requireAuthoritativeWorld(operation); return PhysicsMutationHandle.fromCompletion(operation, value, - PhysicsStoreThreading.executeOnWorldThread(world, operation, mutation)); + PhysicsThreading.executeOnWorldThread(world, operation, mutation)); } private void runDirectRuntimeMutation(@Nonnull String operation, @@ -604,19 +602,19 @@ public int refreshBodySnapshots() { @Nonnull @Override - public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { + public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("read copied physics body snapshot"); - PhysicsBodySnapshot snapshot = getAuthoritativeBodySnapshot(store, bodyUuid); + dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = getAuthoritativeBodySnapshot(store, bodyUuid); if (snapshot == null) { throw new IllegalStateException("No copied PhysicsStore body snapshot is available for " + bodyUuid); } return snapshot; } - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); + dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); if (snapshot != null) { return snapshot; } @@ -625,23 +623,23 @@ public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { } @Nullable - public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid) { + public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid) { return getBodySnapshotIfRegistered(bodyUuid, null); } @Nullable - public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid, + public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { if (isAuthoritativePhysicsStoreActive()) { Objects.requireNonNull(bodyUuid, "bodyUuid"); Store store = authoritativePhysicsStore("read optional copied physics body snapshot"); - PhysicsStoreBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() + PhysicsBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() ? store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyRef) : store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyUuid); return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; } - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); + dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); if (snapshot != null) { return snapshot; } @@ -650,17 +648,17 @@ public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid, } @Nullable - public PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull Ref bodyRef) { + public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull Ref bodyRef) { Store store = Objects.requireNonNull(bodyRef, "bodyRef").getStore(); - PhysicsStoreThreading.requireWorldThread(store, "read optional copied physics body snapshot"); - PhysicsStoreBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) + PhysicsThreading.requireWorldThread(store, "read optional copied physics body snapshot"); + PhysicsBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) .getBody(bodyRef); return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; } @Nonnull - private PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull UUID bodyUuid) { - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); + private dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull UUID bodyUuid) { + dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); if (snapshot != null) { return snapshot; } @@ -672,8 +670,8 @@ private PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull UUID bodyUuid) { } @Nullable - private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull UUID bodyUuid) { - PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); + private dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull UUID bodyUuid) { + dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); if (snapshot != null) { return snapshot; } @@ -682,10 +680,10 @@ private PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull UUID body } @Nullable - private static PhysicsBodySnapshot getAuthoritativeBodySnapshot( + private static dev.hytalemodding.impulse.api.PhysicsBodySnapshot getAuthoritativeBodySnapshot( @Nonnull Store store, @Nonnull UUID bodyUuid) { - PhysicsStoreBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) + PhysicsBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) .getBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; } @@ -698,7 +696,7 @@ private static int countAuthoritativeBodySnapshots(@Nonnull Store return 0; } int count = 0; - for (PhysicsStoreBodySnapshot body : store.getResource(PhysicsSnapshotResource.getResourceType()) + for (PhysicsBodySnapshot body : store.getResource(PhysicsSnapshotResource.getResourceType()) .getLatestFrame() .bodies()) { if (spaceUuid.equals(body.spaceUuid())) { @@ -717,7 +715,7 @@ private static void forEachAuthoritativeBodySnapshot(@Nonnull Store store, } @Nonnull - private static PhysicsStoreSnapshotFrame authoritativeSnapshotFrame( + private static PhysicsSnapshotFrame authoritativeSnapshotFrame( @Nonnull Store store) { return store.getResource(PhysicsSnapshotResource.getResourceType()).getLatestFrame(); } @@ -874,7 +872,7 @@ private static PhysicsStoreSnapshotFrame authoritativeSnapshotFrame( private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( @Nonnull Store store, @Nonnull PhysicsBodyRegistrationResource registrations, - @Nonnull PhysicsStoreBodySnapshot body) { + @Nonnull PhysicsBodySnapshot body) { PhysicsBodyRegistrationView registration = registrations.getBodyRegistrationView(body.bodyUuid()); if (registration == null) { return null; @@ -888,14 +886,14 @@ private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( @Nullable private static Ref validSnapshotBodyRef(@Nonnull Store store, - @Nonnull PhysicsStoreBodySnapshot body) { + @Nonnull PhysicsBodySnapshot body) { Ref bodyRef = body.bodyRef(); return bodyRef != null && bodyRef.getStore() == store && bodyRef.isValid() ? bodyRef : null; } - private static boolean withinRadius(@Nonnull PhysicsBodySnapshot snapshot, + private static boolean withinRadius(@Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, @Nonnull Vector3f center, float radiusSquared) { Objects.requireNonNull(center, "center"); @@ -906,8 +904,8 @@ private static boolean withinRadius(@Nonnull PhysicsBodySnapshot snapshot, } @Nonnull - private static PhysicsBodySnapshot toPublicBodySnapshot(@Nonnull Store store, - @Nonnull PhysicsStoreBodySnapshot body) { + private static dev.hytalemodding.impulse.api.PhysicsBodySnapshot toPublicBodySnapshot(@Nonnull Store store, + @Nonnull PhysicsBodySnapshot body) { Ref ref = body.bodyRef(); if (ref == null || ref.getStore() != store || !ref.isValid()) { ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) @@ -938,7 +936,7 @@ private static PhysicsBodySnapshot toPublicBodySnapshot(@Nonnull Store resetRuntimeStateKeepingSpaces Objects.requireNonNull(worldName, "worldName"); if (isAuthoritativePhysicsStoreActive()) { World world = requireAuthoritativeWorld("reset physics runtime state"); - return PhysicsStoreThreading.callWhenBackendIdleOnWorldThread(world, + return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, "reset physics runtime state", store -> { clearAuthoritativeWorldCollisionStreaming(store); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java index 3c14cb3c..d85fea54 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import java.util.Collection; import javax.annotation.Nonnull; import org.joml.Matrix4d; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index c063f03f..7a88ede0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -19,8 +19,8 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index e8099c24..427af392 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -20,10 +20,10 @@ import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugWorldCollisionSectionView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -56,7 +56,7 @@ static CompletionStage> contactsAsync( double viewerX = viewerPosition.x; double viewerY = viewerPosition.y; double viewerZ = viewerPosition.z; - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore debug contact read", physics -> contacts(physics, spaceId, @@ -77,7 +77,7 @@ static CompletionStage> jointsAsync( double viewerX = viewerPosition.x; double viewerY = viewerPosition.y; double viewerZ = viewerPosition.z; - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore debug joint read", physics -> joints(physics, spaceId, @@ -97,7 +97,7 @@ static CompletionStage> worldCollisi double viewerX = viewerPosition.x; double viewerY = viewerPosition.y; double viewerZ = viewerPosition.z; - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore world-collision debug read", physics -> worldCollisionSections(physics, spaceId, @@ -115,7 +115,7 @@ private static List contacts(@Nonnull Store joints(@Nonnull Store s double viewerZ, double viewRadius, int maxJoints) { - PhysicsStoreThreading.requireWorldThread(store, "read PhysicsStore debug joints"); + PhysicsThreading.requireWorldThread(store, "read PhysicsStore debug joints"); int limit = Math.max(0, maxJoints); if (limit == 0) { return List.of(); @@ -207,7 +207,7 @@ private static List worldCollisionSection double viewerY, double viewerZ, double viewRadius) { - PhysicsStoreThreading.requireWorldThread(store, + PhysicsThreading.requireWorldThread(store, "read PhysicsStore world-collision debug sections"); SpaceContext spaceContext = space(store, spaceId); if (spaceContext == null) { @@ -341,9 +341,9 @@ private static void collectJointChunk(@Nonnull ArchetypeChunk chun @Nullable private static PhysicsDebugJointView toDebugJointView(@Nonnull JointComponent joint, @Nonnull PhysicsSnapshotResource snapshots) { - PhysicsStoreBodySnapshot bodyA = + PhysicsBodySnapshot bodyA = bodySnapshot(snapshots, joint.getBodyARef(), joint.getBodyAUuid()); - PhysicsStoreBodySnapshot bodyB = + PhysicsBodySnapshot bodyB = bodySnapshot(snapshots, joint.getBodyBRef(), joint.getBodyBUuid()); if (bodyA == null || bodyB == null) { return null; @@ -400,7 +400,7 @@ private static boolean matchesSpace(@Nonnull TerrainColliderComponent terrain, } @Nullable - private static PhysicsStoreBodySnapshot bodySnapshot( + private static PhysicsBodySnapshot bodySnapshot( @Nonnull PhysicsSnapshotResource snapshots, @Nullable Ref bodyRef, @Nonnull UUID bodyUuid) { @@ -414,7 +414,7 @@ private static boolean sameRef(@Nonnull Ref first, } @Nonnull - private static Vector3f worldAnchor(@Nonnull PhysicsStoreBodySnapshot body, + private static Vector3f worldAnchor(@Nonnull PhysicsBodySnapshot body, @Nonnull Vector3f localAnchor) { Vector3f anchor = new Vector3f(localAnchor); Quaternionf rotation = body.rotation(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index 86aff124..16ab57df 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -8,6 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; @@ -17,7 +18,6 @@ import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -48,7 +48,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { if (physics.isShutdown()) { return; } - PhysicsStoreThreading.requireWorldThread(physics, "publish PhysicsStore event frame"); + PhysicsThreading.requireWorldThread(physics, "publish PhysicsStore event frame"); PhysicsEventFrame frame = physics.getResource(PhysicsEventResource.getResourceType()) .getLatestFrame(); if (frame.frameSequence() <= 0L || !markPublished(store, frame.frameSequence())) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index db249c90..c7fa94c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -9,8 +9,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 9bd30586..b56dc4fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -16,7 +16,6 @@ import com.hypixel.hytale.server.core.modules.entity.system.UpdateLocationSystems; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.ImpulsePlugin; @@ -26,14 +25,12 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.systems.visual.GeneratedProxyLifecycle; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.List; import java.util.Set; @@ -141,7 +138,7 @@ public void tick(float dt, collector.incrementBodiesInspected(); } PhysicsSnapshotResource snapshotResource = physicsStoreSnapshots.get(); - PhysicsStoreBodySnapshot physicsStoreSnapshot = resolvePhysicsStoreSnapshot(entityRef, + PhysicsBodySnapshot physicsStoreSnapshot = resolvePhysicsStoreSnapshot(entityRef, attachment, snapshotResource, store); @@ -159,13 +156,13 @@ public void tick(float dt, } @Nullable - private static PhysicsStoreBodySnapshot resolvePhysicsStoreSnapshot( + private static PhysicsBodySnapshot resolvePhysicsStoreSnapshot( @Nonnull Ref entityRef, @Nonnull BodyAttachmentComponent attachment, @Nonnull PhysicsSnapshotResource snapshotResource, @Nonnull Store store) { Ref oldBodyRef = attachment.getBodyRef(); - PhysicsStoreBodySnapshot snapshot = null; + PhysicsBodySnapshot snapshot = null; if (oldBodyRef != null && oldBodyRef.isValid()) { snapshot = snapshotResource.getBody(oldBodyRef); if (snapshot != null && !snapshot.bodyUuid().equals(attachment.getBodyUuid())) { @@ -204,7 +201,7 @@ private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( PhysicsStore physicsStore = ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); Store physics = physicsStore.getStore(); - PhysicsStoreThreading.requireWorldThread(physics, + PhysicsThreading.requireWorldThread(physics, "read copied PhysicsStore sync snapshots"); return physics.getResource( PhysicsSnapshotResource.getResourceType()); @@ -212,7 +209,7 @@ private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transform, @Nonnull BodyAttachmentComponent attachment, - @Nonnull PhysicsStoreBodySnapshot snapshot, + @Nonnull PhysicsBodySnapshot snapshot, @Nonnull Scratch scratch) { scratch.position.set(snapshot.position()); scratch.rotation.set(snapshot.rotation()); @@ -249,7 +246,7 @@ private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { return (float) Math.sqrt(distanceSquared); } - private void applyVisualPose(@Nonnull PhysicsBodySnapshot snapshot, + private void applyVisualPose(@Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, @Nonnull BodyAttachmentComponent attachment, @Nonnull Scratch scratch) { PhysicsVisualPoseMath.visualPositionFromBodyPose(scratch.position, @@ -264,7 +261,7 @@ private void applyVisualPose(@Nonnull PhysicsBodySnapshot snapshot, } private static boolean shouldSmoothVisual(@Nullable PhysicsSpaceSettings settings, - @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, boolean controlled, @Nonnull PhysicsSyncPolicy.SyncRangeTier rangeTier, @Nonnull PhysicsBodyRuntimeState.BodySyncState syncState, @@ -307,7 +304,7 @@ static float smoothingAlpha(@Nonnull PhysicsSpaceSettings settings, float dt) { MIN_SMOOTHING_ALPHA, 1.0f); } - private static void applySnapshotPrediction(@Nonnull PhysicsBodySnapshot snapshot, + private static void applySnapshotPrediction(@Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, float predictionSeconds, @Nonnull Scratch scratch) { if (predictionSeconds <= 0.0f || !snapshot.isDynamic() || snapshot.sleeping()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java index d58752c8..15a1c43f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.internal.systems.sync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.TransformAuthority; import javax.annotation.Nonnull; final class PhysicsTransformAuthority { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index bd30dab9..775efcaa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -10,8 +10,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java index 0f83d716..610cf9c1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java @@ -10,8 +10,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; import java.util.Collections; import java.util.Map; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java index 25f29dee..d8bd8474 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java index 7370a6ff..eb0dff25 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ColliderComponent.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ColliderComponent.java index 99db862c..5cab5421 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ColliderComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionFilterComponent.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionFilterComponent.java index 19b43b4c..62d105de 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionFilterComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionFilterComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java index 8326ca80..cb357cb7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/DynamicsComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/DynamicsComponent.java index bd318db2..6741eda0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/DynamicsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/DynamicsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java index 6771b8e4..1172acba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ExtensionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java index 6329f82f..de8ca85f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/JointComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java index 2de296ec..80bd6761 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/MaterialComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java index 608578bd..cb00eeb7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/ShapeComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java index 370efe5c..4fdf2531 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SolverSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java index dbdff941..2b5af4de 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/SpaceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TargetComponent.java similarity index 86% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TargetComponent.java index c4fe9f26..fbce374e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TargetComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TargetComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -10,6 +10,8 @@ import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import javax.annotation.Nonnull; +import lombok.Getter; +import lombok.Setter; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -59,9 +61,17 @@ public final class TargetComponent implements Component { .add() .build(); + @Setter + @Getter private boolean active; + @Getter + @Setter private boolean transformEnabled = true; + @Getter + @Setter private boolean velocityEnabled = true; + @Getter + @Setter private boolean activate = true; @Nonnull private final Vector3f position = new Vector3f(); @@ -75,38 +85,6 @@ public final class TargetComponent implements Component { public TargetComponent() { } - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - public boolean isTransformEnabled() { - return transformEnabled; - } - - public void setTransformEnabled(boolean transformEnabled) { - this.transformEnabled = transformEnabled; - } - - public boolean isVelocityEnabled() { - return velocityEnabled; - } - - public void setVelocityEnabled(boolean velocityEnabled) { - this.velocityEnabled = velocityEnabled; - } - - public boolean isActivate() { - return activate; - } - - public void setActivate(boolean activate) { - this.activate = activate; - } - @Nonnull public Vector3f getPosition() { return new Vector3f(position); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java similarity index 88% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java index 4d8cdfdc..2df520e0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/TerrainColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -8,6 +8,8 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import lombok.Getter; +import lombok.Setter; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -58,11 +60,19 @@ public final class TerrainColliderComponent implements Component { private transient Ref spaceRef; @Nonnull private String sourceKey = ""; + @Setter + @Getter private int chunkX; + @Setter + @Getter private int sectionY; + @Setter + @Getter private int chunkZ; @Nonnull private String payloadResourceKey = ""; + @Setter + @Getter private boolean retained = true; public TerrainColliderComponent() { @@ -112,30 +122,6 @@ public void setSourceKey(@Nonnull String sourceKey) { this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); } - public int getChunkX() { - return chunkX; - } - - public void setChunkX(int chunkX) { - this.chunkX = chunkX; - } - - public int getSectionY() { - return sectionY; - } - - public void setSectionY(int sectionY) { - this.sectionY = sectionY; - } - - public int getChunkZ() { - return chunkZ; - } - - public void setChunkZ(int chunkZ) { - this.chunkZ = chunkZ; - } - @Nonnull public String getPayloadResourceKey() { return payloadResourceKey; @@ -145,14 +131,6 @@ public void setPayloadResourceKey(@Nonnull String payloadResourceKey) { this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); } - public boolean isRetained() { - return retained; - } - - public void setRetained(boolean retained) { - this.retained = retained; - } - @Nonnull public static ComponentType getComponentType() { return PhysicsStoreTypes.terrainColliderComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/UuidComponent.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/UuidComponent.java index 083a5d23..36da32f8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/UuidComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/UuidComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java index 3c407f0c..9f2438e0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java index 26eb62a6..9452bab4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java index 6fac4ff8..979a71e9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 96165460..dc939808 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; @@ -13,7 +14,6 @@ import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -64,7 +64,7 @@ public static void startSession(@Nonnull Store store, @Nonnull Vector3f viewOffset, @Nonnull Vector3f previousTarget) { Store physicsStore = physicsStore(store); - PhysicsStoreThreading.requireWorldThread(physicsStore, + PhysicsThreading.requireWorldThread(physicsStore, "resolve PhysicsStore control-session UUIDs"); Ref bodyRef = requireRef(physicsStore, bodyUuid, "body"); Ref anchorBodyRef = requireRef(physicsStore, anchorBodyUuid, "anchor body"); @@ -186,7 +186,7 @@ private static void validateControlRefs(@Nonnull Ref bodyRef, @Nonnull Ref anchorBodyRef, @Nullable Ref controlJointRef) { Store store = bodyRef.getStore(); - PhysicsStoreThreading.requireWorldThread(store, + PhysicsThreading.requireWorldThread(store, "start PhysicsStore control session"); requireValidRef(bodyRef, "body"); requireValidRef(anchorBodyRef, "anchor body"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 5a45d70f..bef13fc5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -3,13 +3,13 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.concurrent.CompletionStage; @@ -69,7 +69,7 @@ public static Status status(@Nonnull Store store) { @Nonnull public static CompletionStage statusAsync(@Nonnull Store store) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(store.getExternalData().getWorld(), + return PhysicsThreading.enqueueReadOnWorldThread(store.getExternalData().getWorld(), "queue PhysicsStore persistence status read", PhysicsPersistence::liveStatus); } @@ -86,7 +86,7 @@ private static Status liveStatus(@Nonnull Store physicsStore) { PersistentPhysicsStoreResource.getResourceType()); PhysicsRestoreStatusResource restore = physicsStore.getResource( PhysicsRestoreStatusResource.getResourceType()); - List summaries = PhysicsStoreDiagnostics.spaceSummaries(physicsStore); + List summaries = PhysicsDiagnostics.spaceSummaries(physicsStore); int runtimeBodies = summaries.stream().mapToInt(SpaceSummary::bodyCount).sum(); int runtimeJoints = summaries.stream().mapToInt(SpaceSummary::jointCount).sum(); int physicsStoreSpaces = physicsStore.getResource( @@ -106,7 +106,7 @@ private static Status liveStatus(@Nonnull Store physicsStore) { @Nonnull private static Status copiedStatus(@Nonnull Store physicsStore) { - PhysicsStoreThreading.requireWorldThread(physicsStore, + PhysicsThreading.requireWorldThread(physicsStore, "read copied PhysicsStore persistence status"); PersistentPhysicsStoreResource persistent = physicsStore.getResource( PersistentPhysicsStoreResource.getResourceType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java index 56903ad5..0a99c6ad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java @@ -1,12 +1,12 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAsync.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAsync.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java index 8e8480cd..8f7a912d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreAsync.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java @@ -11,9 +11,9 @@ /** * Helpers for consuming copied PhysicsStore async results from world-thread code. */ -public final class PhysicsStoreAsync { +public final class PhysicsAsync { - private PhysicsStoreAsync() { + private PhysicsAsync() { } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java index 62c4f4dd..f3212935 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java @@ -20,14 +20,14 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -final class PhysicsStoreBackendAccess { +final class PhysicsBackendAccess { - private PhysicsStoreBackendAccess() { + private PhysicsBackendAccess() { } @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); UUID spaceUuid = compatibility.getSpaceUuid(spaceId); @@ -36,7 +36,7 @@ static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId s @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) .getByUuid(spaceUuid); @@ -46,7 +46,7 @@ static SpaceContext space(@Nonnull Store store, @Nonnull UUID spac @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull Ref spaceRef) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); Objects.requireNonNull(spaceRef, "spaceRef"); if (spaceRef.getStore() != store || !spaceRef.isValid()) { return null; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java similarity index 87% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBodies.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java index 9dd62e39..01b35d9c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java @@ -6,7 +6,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -15,9 +15,9 @@ /** * Public copied body reads for PhysicsStore entities. */ -public final class PhysicsStoreBodies { +public final class PhysicsBodies { - private PhysicsStoreBodies() { + private PhysicsBodies() { } @Nullable @@ -43,7 +43,7 @@ public static PhysicsBodyRegistrationView registrationView(@Nonnull Store store, + public static PhysicsBodySnapshot snapshot(@Nonnull Store store, @Nonnull Ref bodyRef) { Store checkedStore = requireWorldThread(store, "read copied PhysicsStore body snapshot"); @@ -56,7 +56,7 @@ public static PhysicsStoreBodySnapshot snapshot(@Nonnull Store sto } @Nullable - public static PhysicsStoreBodySnapshot snapshot(@Nonnull Store store, + public static PhysicsBodySnapshot snapshot(@Nonnull Store store, @Nonnull UUID bodyUuid) { Store checkedStore = requireWorldThread(store, "read copied PhysicsStore body snapshot"); @@ -68,7 +68,7 @@ public static PhysicsStoreBodySnapshot snapshot(@Nonnull Store sto private static Store requireWorldThread(@Nonnull Store store, @Nonnull String operation) { Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsStoreThreading.requireWorldThread(checkedStore, operation); + PhysicsThreading.requireWorldThread(checkedStore, operation); return checkedStore; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java index c7f74456..030d2f49 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java @@ -6,13 +6,13 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.Objects; @@ -134,7 +134,7 @@ public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - BodyEntityDescriptor descriptor = body(PhysicsStoreEntityRefs.entityUuid(spaceRef), + BodyEntityDescriptor descriptor = body(PhysicsEntityRefs.entityUuid(spaceRef), bodyUuid, bodyCenter, shape, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java similarity index 73% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java index 85096d09..8683e19d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java @@ -23,27 +23,27 @@ * owning PhysicsStore world thread. Off-thread callers should use the {@code *Async} methods, * which enqueue copied reads on that world thread.

    */ -public final class PhysicsStoreDiagnostics { +public final class PhysicsDiagnostics { - private PhysicsStoreDiagnostics() { + private PhysicsDiagnostics() { } public static int bodyCount(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; } public static int bodyCount(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; } public static int bodyCount(@Nonnull Store store, @Nonnull Ref spaceRef) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; } @@ -51,7 +51,7 @@ public static int bodyCount(@Nonnull Store store, public static CompletionStage bodyCountAsync(@Nonnull World world, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore body count read", physics -> bodyCount(physics, spaceId)); } @@ -60,7 +60,7 @@ public static CompletionStage bodyCountAsync(@Nonnull World world, public static CompletionStage bodyCountAsync(@Nonnull Store store, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore body count read", physics -> bodyCount(physics, spaceId)); } @@ -69,7 +69,7 @@ public static CompletionStage bodyCountAsync(@Nonnull Store bodyCountAsync(@Nonnull World world, @Nonnull UUID spaceUuid) { Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore body count read", physics -> bodyCount(physics, spaceUuid)); } @@ -78,7 +78,7 @@ public static CompletionStage bodyCountAsync(@Nonnull World world, public static CompletionStage bodyCountAsync(@Nonnull Store store, @Nonnull UUID spaceUuid) { Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore body count read", physics -> bodyCount(physics, spaceUuid)); } @@ -87,7 +87,7 @@ public static CompletionStage bodyCountAsync(@Nonnull Store bodyCountAsync(@Nonnull World world, @Nonnull Ref spaceRef) { Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore body count read", physics -> bodyCount(physics, spaceRef)); } @@ -96,13 +96,13 @@ public static CompletionStage bodyCountAsync(@Nonnull World world, public static CompletionStage bodyCountAsync(@Nonnull Store store, @Nonnull Ref spaceRef) { Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore body count read", physics -> bodyCount(physics, spaceRef)); } public static int runtimeJointCount(@Nonnull Store store) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); JointCountCapture count = new JointCountCapture(); runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> @@ -112,20 +112,20 @@ public static int runtimeJointCount(@Nonnull Store store) { @Nonnull public static CompletionStage runtimeJointCountAsync(@Nonnull World world) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore joint count read", - PhysicsStoreDiagnostics::runtimeJointCount); + PhysicsDiagnostics::runtimeJointCount); } @Nonnull public static CompletionStage runtimeJointCountAsync(@Nonnull Store store) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore joint count read", - PhysicsStoreDiagnostics::runtimeJointCount); + PhysicsDiagnostics::runtimeJointCount); } public static boolean ccdSupported(@Nonnull Store store) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); CcdSupportCapture supported = new CcdSupportCapture(); runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { @@ -138,34 +138,34 @@ public static boolean ccdSupported(@Nonnull Store store) { @Nonnull public static CompletionStage ccdSupportedAsync(@Nonnull World world) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore CCD support read", - PhysicsStoreDiagnostics::ccdSupported); + PhysicsDiagnostics::ccdSupported); } @Nonnull public static CompletionStage ccdSupportedAsync(@Nonnull Store store) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore CCD support read", - PhysicsStoreDiagnostics::ccdSupported); + PhysicsDiagnostics::ccdSupported); } @Nonnull public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.requireSpace(store, Objects.requireNonNull(spaceId, "spaceId")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.requireSpace(store, Objects.requireNonNull(spaceId, "spaceId")); return solverCapability(spaceId, space); } @Nonnull public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull Ref spaceRef) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.requireSpace(store, Objects.requireNonNull(spaceRef, "spaceRef")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.requireSpace(store, Objects.requireNonNull(spaceRef, "spaceRef")); SpaceId spaceId = compatibility.getSpaceId(space.spaceUuid()); if (spaceId == null) { throw new IllegalArgumentException("Physics space ref=" + spaceRef @@ -179,7 +179,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull World world, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore solver capability read", physics -> solverCapability(physics, spaceId)); } @@ -189,7 +189,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull Store store, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore solver capability read", physics -> solverCapability(physics, spaceId)); } @@ -199,7 +199,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull World world, @Nonnull UUID spaceUuid) { Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore solver capability read", physics -> solverCapability(physics, spaceUuid)); } @@ -209,7 +209,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull World world, @Nonnull Ref spaceRef) { Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore solver capability read", physics -> solverCapability(physics, spaceRef)); } @@ -219,7 +219,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull Store store, @Nonnull Ref spaceRef) { Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore solver capability read", physics -> solverCapability(physics, spaceRef)); } @@ -229,7 +229,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull Store store, @Nonnull UUID spaceUuid) { Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore solver capability read", physics -> solverCapability(physics, spaceUuid)); } @@ -237,7 +237,7 @@ public static CompletionStage solverCapabilityAsync( @Nonnull public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); SpaceId spaceId = compatibility.getSpaceId(Objects.requireNonNull(spaceUuid, "spaceUuid")); @@ -245,21 +245,21 @@ public static SolverCapabilitySummary solverCapability(@Nonnull Store spaceSummaries(@Nonnull Store store) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); List summaries = new ArrayList<>(); runtime.forEachRuntimeSpaceBinding((spaceRef, _, _, _) -> { - PhysicsStoreBackendAccess.SpaceContext context = - PhysicsStoreBackendAccess.space(runtime, spaceRef); + PhysicsBackendAccess.SpaceContext context = + PhysicsBackendAccess.space(runtime, spaceRef); if (context != null && compatibility.getSpaceId(context.spaceUuid()) != null) { - summaries.add(PhysicsStoreBackendAccess.summary(compatibility, context)); + summaries.add(PhysicsBackendAccess.summary(compatibility, context)); } }); return summaries.isEmpty() ? List.of() : List.copyOf(summaries); @@ -267,42 +267,42 @@ public static List spaceSummaries(@Nonnull Store sto @Nonnull public static CompletionStage> spaceSummariesAsync(@Nonnull World world) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore space summaries read", - PhysicsStoreDiagnostics::spaceSummaries); + PhysicsDiagnostics::spaceSummaries); } @Nonnull public static CompletionStage> spaceSummariesAsync( @Nonnull Store store) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore space summaries read", - PhysicsStoreDiagnostics::spaceSummaries); + PhysicsDiagnostics::spaceSummaries); } @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); return space != null - ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) + ? List.of(PhysicsBackendAccess.summary(compatibility, space)) : List.of(); } @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull Ref spaceRef) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); return space != null && compatibility.getSpaceId(space.spaceUuid()) != null - ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) + ? List.of(PhysicsBackendAccess.summary(compatibility, space)) : List.of(); } @@ -310,7 +310,7 @@ public static List spaceSummaries(@Nonnull Store sto public static CompletionStage> spaceSummariesAsync(@Nonnull World world, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore space summary read", physics -> spaceSummaries(physics, spaceId)); } @@ -319,7 +319,7 @@ public static CompletionStage> spaceSummariesAsync(@Nonnull W public static CompletionStage> spaceSummariesAsync(@Nonnull World world, @Nonnull Ref spaceRef) { Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore space summary read", physics -> spaceSummaries(physics, spaceRef)); } @@ -329,7 +329,7 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull Store store, @Nonnull Ref spaceRef) { Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore space summary read", physics -> spaceSummaries(physics, spaceRef)); } @@ -339,7 +339,7 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull Store store, @Nonnull SpaceId spaceId) { Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore space summary read", physics -> spaceSummaries(physics, spaceId)); } @@ -347,13 +347,13 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); return space != null && compatibility.getSpaceId(space.spaceUuid()) != null - ? List.of(PhysicsStoreBackendAccess.summary(compatibility, space)) + ? List.of(PhysicsBackendAccess.summary(compatibility, space)) : List.of(); } @@ -361,7 +361,7 @@ public static List spaceSummaries(@Nonnull Store sto public static CompletionStage> spaceSummariesAsync(@Nonnull World world, @Nonnull UUID spaceUuid) { Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore space summary read", physics -> spaceSummaries(physics, spaceUuid)); } @@ -371,24 +371,24 @@ public static CompletionStage> spaceSummariesAsync( @Nonnull Store store, @Nonnull UUID spaceUuid) { Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore space summary read", physics -> spaceSummaries(physics, spaceUuid)); } @Nonnull public static List unsupportedCcdSpaces(@Nonnull Store store) { - PhysicsStoreThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); + PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); List spaces = new ArrayList<>(); runtime.forEachRuntimeSpaceBinding((spaceRef, _, spaceHandle, backendRuntime) -> { if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { - PhysicsStoreBackendAccess.SpaceContext context = - PhysicsStoreBackendAccess.space(runtime, spaceRef); + PhysicsBackendAccess.SpaceContext context = + PhysicsBackendAccess.space(runtime, spaceRef); if (context != null) { - spaces.add(PhysicsStoreBackendAccess.summary(compatibility, context)); + spaces.add(PhysicsBackendAccess.summary(compatibility, context)); } } }); @@ -398,22 +398,22 @@ public static List unsupportedCcdSpaces(@Nonnull Store> unsupportedCcdSpacesAsync( @Nonnull World world) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore unsupported CCD spaces read", - PhysicsStoreDiagnostics::unsupportedCcdSpaces); + PhysicsDiagnostics::unsupportedCcdSpaces); } @Nonnull public static CompletionStage> unsupportedCcdSpacesAsync( @Nonnull Store store) { - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore unsupported CCD spaces read", - PhysicsStoreDiagnostics::unsupportedCcdSpaces); + PhysicsDiagnostics::unsupportedCcdSpaces); } @Nonnull private static SolverCapabilitySummary solverCapability(@Nonnull SpaceId spaceId, - @Nonnull PhysicsStoreBackendAccess.SpaceContext space) { + @Nonnull PhysicsBackendAccess.SpaceContext space) { return new SolverCapabilitySummary(spaceId, space.backendId().value(), space.backendRuntime().supportsSolverTuning(space.spaceHandle().value()), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java similarity index 86% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index 4e35534c..cf415bc5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -4,23 +4,23 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -29,9 +29,9 @@ /** * Holder/component helpers for authoritative PhysicsStore aggregate entities. */ -public final class PhysicsStoreEntities { +public final class PhysicsEntities { - private PhysicsStoreEntities() { + private PhysicsEntities() { } @Nonnull @@ -183,7 +183,7 @@ public static void putSpaceComponents(@Nonnull Store store, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsStoreThreading.requireWorldThread(checkedStore, "put PhysicsStore space components"); + PhysicsThreading.requireWorldThread(checkedStore, "put PhysicsStore space components"); Objects.requireNonNull(ref, "ref"); checkedStore.putComponent(ref, SpaceComponent.getComponentType(), @@ -208,7 +208,7 @@ public static void putSpaceSettingsComponents(@Nonnull Store store @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsStoreThreading.requireWorldThread(checkedStore, + PhysicsThreading.requireWorldThread(checkedStore, "put PhysicsStore space settings components"); Objects.requireNonNull(ref, "ref"); checkedStore.putComponent(ref, @@ -239,7 +239,7 @@ public static void putBodyComponents(@Nonnull Store store, @Nonnull MaterialComponent material, @Nonnull CollisionFilterComponent filter) { Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsStoreThreading.requireWorldThread(checkedStore, "put PhysicsStore body components"); + PhysicsThreading.requireWorldThread(checkedStore, "put PhysicsStore body components"); Objects.requireNonNull(ref, "ref"); checkedStore.putComponent(ref, BodyComponent.getComponentType(), @@ -270,7 +270,7 @@ public static void putJointComponent(@Nonnull Store store, @Nonnull Ref ref, @Nonnull JointComponent joint) { Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsStoreThreading.requireWorldThread(checkedStore, "put PhysicsStore joint component"); + PhysicsThreading.requireWorldThread(checkedStore, "put PhysicsStore joint component"); checkedStore .putComponent(Objects.requireNonNull(ref, "ref"), JointComponent.getComponentType(), @@ -281,7 +281,7 @@ public static void putTerrainColliderComponent(@Nonnull Store stor @Nonnull Ref ref, @Nonnull TerrainColliderComponent terrainCollider) { Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsStoreThreading.requireWorldThread(checkedStore, + PhysicsThreading.requireWorldThread(checkedStore, "put PhysicsStore terrain collider component"); checkedStore .putComponent(Objects.requireNonNull(ref, "ref"), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntityRefs.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntityRefs.java similarity index 84% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntityRefs.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntityRefs.java index b364c84f..b4489186 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreEntityRefs.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntityRefs.java @@ -3,21 +3,21 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; -final class PhysicsStoreEntityRefs { +final class PhysicsEntityRefs { - private PhysicsStoreEntityRefs() { + private PhysicsEntityRefs() { } @Nonnull static UUID entityUuid(@Nonnull Ref ref) { Ref checkedRef = Objects.requireNonNull(ref, "ref"); Store store = checkedRef.getStore(); - PhysicsStoreThreading.requireWorldThread(store, "read a PhysicsStore entity UUID"); + PhysicsThreading.requireWorldThread(store, "read a PhysicsStore entity UUID"); if (!checkedRef.isValid()) { throw new IllegalStateException("PhysicsStore entity ref is not valid: " + checkedRef); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java index beede58a..64cada70 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java @@ -2,7 +2,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import java.util.Objects; import java.util.UUID; @@ -45,11 +45,11 @@ public static JointComponent joint(@Nonnull Ref spaceRef, @Nonnull Vector3f anchorA, @Nonnull Vector3f anchorB, @Nonnull Vector3f axis) { - PhysicsStoreEntityRefs.requireSameStore(spaceRef, bodyARef, "bodyARef"); - PhysicsStoreEntityRefs.requireSameStore(spaceRef, bodyBRef, "bodyBRef"); - JointComponent joint = joint(PhysicsStoreEntityRefs.entityUuid(spaceRef), - PhysicsStoreEntityRefs.entityUuid(bodyARef), - PhysicsStoreEntityRefs.entityUuid(bodyBRef), + PhysicsEntityRefs.requireSameStore(spaceRef, bodyARef, "bodyARef"); + PhysicsEntityRefs.requireSameStore(spaceRef, bodyBRef, "bodyBRef"); + JointComponent joint = joint(PhysicsEntityRefs.entityUuid(spaceRef), + PhysicsEntityRefs.entityUuid(bodyARef), + PhysicsEntityRefs.entityUuid(bodyBRef), type, anchorA, anchorB, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java index 65d90c17..fed44df2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java @@ -27,9 +27,9 @@ * {@code *Async} methods, which copy inputs, enqueue the read on that world thread, and complete * with copied hit views.

    */ -public final class PhysicsStoreRaycasts { +public final class PhysicsRaycasts { - private PhysicsStoreRaycasts() { + private PhysicsRaycasts() { } @Nonnull @@ -37,8 +37,8 @@ public static Optional closest(@Nonnull Store stor @Nonnull SpaceId spaceId, @Nonnull Vector3f from, @Nonnull Vector3f to) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); return space != null ? closest(store, space, from, to) : Optional.empty(); } @@ -47,8 +47,8 @@ public static Optional closest(@Nonnull Store stor @Nonnull UUID spaceUuid, @Nonnull Vector3f from, @Nonnull Vector3f to) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); return space != null ? closest(store, space, from, to) : Optional.empty(); } @@ -57,8 +57,8 @@ public static Optional closest(@Nonnull Store stor @Nonnull Ref spaceRef, @Nonnull Vector3f from, @Nonnull Vector3f to) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); return space != null ? closest(store, space, from, to) : Optional.empty(); } @@ -67,8 +67,8 @@ public static List all(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3f from, @Nonnull Vector3f to) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); return space != null ? all(store, space, from, to) : List.of(); } @@ -77,8 +77,8 @@ public static List all(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull Vector3f from, @Nonnull Vector3f to) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); return space != null ? all(store, space, from, to) : List.of(); } @@ -87,8 +87,8 @@ public static List all(@Nonnull Store store, @Nonnull Ref spaceRef, @Nonnull Vector3f from, @Nonnull Vector3f to) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); return space != null ? all(store, space, from, to) : List.of(); } @@ -96,8 +96,8 @@ public static List all(@Nonnull Store store, public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull List rays) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); return closestBatch(store, space, rays); } @@ -105,8 +105,8 @@ public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull List rays) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); return closestBatch(store, space, rays); } @@ -114,8 +114,8 @@ public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, @Nonnull Ref spaceRef, @Nonnull List rays) { - PhysicsStoreBackendAccess.SpaceContext space = - PhysicsStoreBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); + PhysicsBackendAccess.SpaceContext space = + PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceRef, "spaceRef")); return closestBatch(store, space, rays); } @@ -126,7 +126,7 @@ public static CompletionStage> closestAsync(@Nonnull Wo @Nonnull Vector3f to) { Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore closest raycast read", physics -> closest(physics, spaceId, copiedFrom, copiedTo)); } @@ -139,7 +139,7 @@ public static CompletionStage> closestAsync( @Nonnull Vector3f to) { Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore closest raycast read", physics -> closest(physics, spaceId, copiedFrom, copiedTo)); } @@ -152,7 +152,7 @@ public static CompletionStage> closestAsync(@Nonnull Wo Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore closest raycast read", physics -> closest(physics, spaceUuid, copiedFrom, copiedTo)); } @@ -166,7 +166,7 @@ public static CompletionStage> closestAsync( Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore closest raycast read", physics -> closest(physics, spaceUuid, copiedFrom, copiedTo)); } @@ -179,7 +179,7 @@ public static CompletionStage> closestAsync(@Nonnull Wo Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore closest raycast read", physics -> closest(physics, spaceRef, copiedFrom, copiedTo)); } @@ -193,7 +193,7 @@ public static CompletionStage> closestAsync( Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore closest raycast read", physics -> closest(physics, spaceRef, copiedFrom, copiedTo)); } @@ -205,7 +205,7 @@ public static CompletionStage> allAsync(@Nonnull World worl @Nonnull Vector3f to) { Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore all raycast read", physics -> all(physics, spaceId, copiedFrom, copiedTo)); } @@ -217,7 +217,7 @@ public static CompletionStage> allAsync(@Nonnull Store all(physics, spaceId, copiedFrom, copiedTo)); } @@ -230,7 +230,7 @@ public static CompletionStage> allAsync(@Nonnull World worl Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore all raycast read", physics -> all(physics, spaceUuid, copiedFrom, copiedTo)); } @@ -244,7 +244,7 @@ public static CompletionStage> allAsync( Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore all raycast read", physics -> all(physics, spaceUuid, copiedFrom, copiedTo)); } @@ -257,7 +257,7 @@ public static CompletionStage> allAsync(@Nonnull World worl Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore all raycast read", physics -> all(physics, spaceRef, copiedFrom, copiedTo)); } @@ -270,7 +270,7 @@ public static CompletionStage> allAsync(@Nonnull Store all(physics, spaceRef, copiedFrom, copiedTo)); } @@ -281,7 +281,7 @@ public static CompletionStage closestBatchAsync( @Nonnull SpaceId spaceId, @Nonnull List rays) { List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore batch raycast read", physics -> closestBatch(physics, spaceId, copied)); } @@ -292,7 +292,7 @@ public static CompletionStage closestBatchAsync( @Nonnull SpaceId spaceId, @Nonnull List rays) { List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore batch raycast read", physics -> closestBatch(physics, spaceId, copied)); } @@ -304,7 +304,7 @@ public static CompletionStage closestBatchAsync( @Nonnull List rays) { List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore batch raycast read", physics -> closestBatch(physics, spaceUuid, copied)); } @@ -316,7 +316,7 @@ public static CompletionStage closestBatchAsync( @Nonnull List rays) { List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore batch raycast read", physics -> closestBatch(physics, spaceUuid, copied)); } @@ -328,7 +328,7 @@ public static CompletionStage closestBatchAsync( @Nonnull List rays) { List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(world, + return PhysicsThreading.enqueueReadOnWorldThread(world, "queue PhysicsStore batch raycast read", physics -> closestBatch(physics, spaceRef, copied)); } @@ -340,14 +340,14 @@ public static CompletionStage closestBatchAsync( @Nonnull List rays) { List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); Objects.requireNonNull(spaceRef, "spaceRef"); - return PhysicsStoreThreading.enqueueReadOnWorldThread(store, + return PhysicsThreading.enqueueReadOnWorldThread(store, "queue PhysicsStore batch raycast read", physics -> closestBatch(physics, spaceRef, copied)); } @Nonnull private static Optional closest(@Nonnull Store store, - @Nonnull PhysicsStoreBackendAccess.SpaceContext space, + @Nonnull PhysicsBackendAccess.SpaceContext space, @Nonnull Vector3f from, @Nonnull Vector3f to) { PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); @@ -367,7 +367,7 @@ private static Optional closest(@Nonnull Store sto @Nonnull private static List all(@Nonnull Store store, - @Nonnull PhysicsStoreBackendAccess.SpaceContext space, + @Nonnull PhysicsBackendAccess.SpaceContext space, @Nonnull Vector3f from, @Nonnull Vector3f to) { PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); @@ -389,7 +389,7 @@ private static List all(@Nonnull Store store, normalY, normalZ, fraction, - distance) -> hits.add(PhysicsStoreBackendAccess.toView(runtime, + distance) -> hits.add(PhysicsBackendAccess.toView(runtime, bodyId, pointX, pointY, @@ -404,7 +404,7 @@ private static List all(@Nonnull Store store, @Nonnull private static RaycastClosestBatchResult closestBatch(@Nonnull Store store, - PhysicsStoreBackendAccess.SpaceContext space, + PhysicsBackendAccess.SpaceContext space, @Nonnull List rays) { List copiedRays = List.copyOf(Objects.requireNonNull(rays, "rays")); int rayCount = copiedRays.size(); @@ -435,7 +435,7 @@ private static RaycastClosestBatchResult closestBatch(@Nonnull Store hits[rayIndex] = PhysicsStoreBackendAccess.toView(runtime, + distance) -> hits[rayIndex] = PhysicsBackendAccess.toView(runtime, bodyId, pointX, pointY, @@ -470,7 +470,7 @@ public void accept(long bodyId, float normalZ, float fraction, float distance) { - view = PhysicsStoreBackendAccess.toView(runtime, + view = PhysicsBackendAccess.toView(runtime, bodyId, pointX, pointY, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreSpaces.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 9cd505e9..5a5314f6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -16,9 +16,9 @@ /** * Public compatibility reads for PhysicsStore space entities. */ -public final class PhysicsStoreSpaces { +public final class PhysicsSpaces { - private PhysicsStoreSpaces() { + private PhysicsSpaces() { } @Nullable @@ -56,7 +56,7 @@ public static Collection spaceIds(@Nonnull Store store) { private static Store requireWorldThread(@Nonnull Store store, @Nonnull String operation) { Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsStoreThreading.requireWorldThread(checkedStore, operation); + PhysicsThreading.requireWorldThread(checkedStore, operation); return checkedStore; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java index f92a71f5..18a6a6a6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java @@ -19,24 +19,24 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java index 3e9a8d28..7a141755 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java @@ -17,9 +17,9 @@ /** * Thread assertions for direct PhysicsStore entity and backend access. */ -public final class PhysicsStoreThreading { +public final class PhysicsThreading { - private PhysicsStoreThreading() { + private PhysicsThreading() { } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java deleted file mode 100644 index a0325ff8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreSnapshotFrame.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; - -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Immutable copied snapshot frame published by PhysicsStore after a completed backend step. - */ -public record PhysicsStoreSnapshotFrame(long sequence, - float dt, - @Nonnull List bodies) { - - public static final PhysicsStoreSnapshotFrame EMPTY = - new PhysicsStoreSnapshotFrame(0L, 0.0f, List.of()); - - public PhysicsStoreSnapshotFrame { - bodies = List.copyOf(Objects.requireNonNull(bodies, "bodies")); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java index 17d8e454..23de5260 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.projection; +package dev.hytalemodding.impulse.core.plugin.projection; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsBodySnapshot.java similarity index 56% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsBodySnapshot.java index 11e488f7..cc4dfbf7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/snapshots/PhysicsStoreBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsBodySnapshot.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; +package dev.hytalemodding.impulse.core.plugin.snapshots; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -13,26 +13,26 @@ /** * Copied body snapshot published out of PhysicsStore for projection and queries. */ -public record PhysicsStoreBodySnapshot(@Nullable Ref bodyRef, - @Nonnull UUID bodyUuid, - @Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyType bodyType, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - float centerOfMassOffsetY, - boolean sleeping) { +public record PhysicsBodySnapshot(@Nullable Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + float centerOfMassOffsetY, + boolean sleeping) { - public PhysicsStoreBodySnapshot(@Nonnull UUID bodyUuid, - @Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyType bodyType, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - float centerOfMassOffsetY, - boolean sleeping) { + public PhysicsBodySnapshot(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + float centerOfMassOffsetY, + boolean sleeping) { this(null, bodyUuid, spaceUuid, @@ -45,7 +45,7 @@ public PhysicsStoreBodySnapshot(@Nonnull UUID bodyUuid, sleeping); } - public PhysicsStoreBodySnapshot { + public PhysicsBodySnapshot { Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(bodyType, "bodyType"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsSnapshotFrame.java new file mode 100644 index 00000000..2fe88593 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsSnapshotFrame.java @@ -0,0 +1,20 @@ +package dev.hytalemodding.impulse.core.plugin.snapshots; + +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Immutable copied snapshot frame published by PhysicsStore after a completed backend step. + */ +public record PhysicsSnapshotFrame(long sequence, + float dt, + @Nonnull List bodies) { + + public static final PhysicsSnapshotFrame EMPTY = + new PhysicsSnapshotFrame(0L, 0.0f, List.of()); + + public PhysicsSnapshotFrame { + bodies = List.copyOf(Objects.requireNonNull(bodies, "bodies")); + } +} diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 87f244f0..820f6cd2 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -7,7 +7,6 @@ exports dev.hytalemodding.impulse.core.plugin.body; exports dev.hytalemodding.impulse.core.plugin.codec; exports dev.hytalemodding.impulse.core.plugin.events; - exports dev.hytalemodding.impulse.core.plugin.joint; exports dev.hytalemodding.impulse.core.plugin.modules.control; exports dev.hytalemodding.impulse.core.plugin.modules.worldcollision; exports dev.hytalemodding.impulse.core.plugin.persistence; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index 43d4a34e..81f8f5f3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -17,6 +17,8 @@ import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import org.joml.Vector3d; /** @@ -53,10 +55,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { - ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() + " is not bound yet.")); return CompletableFuture.completedFuture(null); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index 1723acc0..78757810 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -14,10 +14,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -75,10 +76,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, drawArrow(world, offCenterPosition, new Vector3d(2.0, 0.0, 0.0), DebugUtils.COLOR_YELLOW); drawArrow(world, torquePosition, new Vector3d(0.0, 0.0, 2.0), DebugUtils.COLOR_MAGENTA); drawArrow(world, forcePosition, new Vector3d(2.0, 0.0, 0.0), DebugUtils.COLOR_CYAN); - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, central); - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, offCenter); - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, torque); - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, force); + ExamplePhysicsUtils.attachBlockBody(store, time, central); + ExamplePhysicsUtils.attachBlockBody(store, time, offCenter); + ExamplePhysicsUtils.attachBlockBody(store, time, torque); + ExamplePhysicsUtils.attachBlockBody(store, time, force); ctx.sender().sendMessage(Message.raw( "Spawned force demo: central impulse, off-center impulse, torque, and force.")); @@ -94,7 +95,7 @@ private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, @Nonnull Vector3d forcePosition) { Ref spaceRef; try { - spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); } catch (IllegalStateException exception) { return null; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 8ede2201..f663ddd0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -19,20 +19,20 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -44,6 +44,7 @@ import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; @@ -83,7 +84,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (targetSpaceId == null) { return CompletableFuture.completedFuture(null); } - Ref targetSpaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref targetSpaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, targetSpaceId); if (targetSpaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + targetSpaceId.value() @@ -97,8 +98,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d direction = new Vector3d(look.getDirection()).mul(RAY_LENGTH); Vector3d end = new Vector3d(start).add(direction); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreRaycasts.allAsync(world, + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsRaycasts.allAsync(world, targetSpaceRef, ExamplePhysicsUtils.toVector3f(start), ExamplePhysicsUtils.toVector3f(end)), @@ -164,13 +165,13 @@ private static void finishGrab(@Nonnull CommandContext ctx, private static GrabPhysicsState createGrabControl(@Nonnull World world, @Nonnull SpaceId selectedSpaceId, @Nonnull HitSelection selection) { - PhysicsStoreBodySnapshot selectedState = bodyState(world, selection.bodyRef()); + PhysicsBodySnapshot selectedState = bodyState(world, selection.bodyRef()); if (selectedState == null) { return null; } Ref spaceRef; try { - spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, selectedSpaceId); + spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, selectedSpaceId); } catch (IllegalStateException exception) { return null; } @@ -252,7 +253,7 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource continue; } PhysicsBodyRegistrationView registration = - PhysicsStoreBodies.registrationView(hit.bodyRef().getStore(), hit.bodyRef()); + PhysicsBodies.registrationView(hit.bodyRef().getStore(), hit.bodyRef()); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { continue; } @@ -283,10 +284,10 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource } @Nullable - private static PhysicsStoreBodySnapshot bodyState(@Nonnull World world, + private static PhysicsBodySnapshot bodyState(@Nonnull World world, @Nonnull Ref bodyRef) { Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); - return PhysicsStoreBodies.snapshot(store, bodyRef); + return PhysicsBodies.snapshot(store, bodyRef); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index ca1ff5bf..b535d159 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -14,11 +14,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -67,7 +68,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } for (CreatedBlockBody created : createdBodies) { - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, created); + ExamplePhysicsUtils.attachBlockBody(store, time, created); } ctx.sender().sendMessage(Message.raw( @@ -81,7 +82,7 @@ private static List tryCreatePhysicsStoreDemo(@Nonnull World w @Nonnull Vector3d origin) { Ref spaceRef; try { - spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); } catch (IllegalStateException exception) { return null; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 8ecfd741..3850681b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -17,6 +17,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import org.joml.Vector3d; import org.joml.Vector3f; @@ -44,7 +45,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java index 6cf308f4..3e1bd87b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.RestoreRequestResult; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.SaveResult; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.Status; @@ -38,7 +38,7 @@ private SaveCommand() { protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Store store = world.getEntityStore().getStore(); - return PhysicsStoreAsync.acceptOnWorldThread(world, + return PhysicsAsync.acceptOnWorldThread(world, PhysicsPersistence.saveRuntimeSnapshotAsync(store), result -> sendSaveResult(ctx, world, result)); } @@ -78,7 +78,7 @@ private LoadCommand() { protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Store store = world.getEntityStore().getStore(); - return PhysicsStoreAsync.acceptOnWorldThread(world, + return PhysicsAsync.acceptOnWorldThread(world, PhysicsPersistence.requestRuntimeRestoreAsync(store), result -> sendLoadResult(ctx, world, result)); } @@ -127,7 +127,7 @@ private StatusCommand() { protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Store store = world.getEntityStore().getStore(); - return PhysicsStoreAsync.acceptOnWorldThread(world, + return PhysicsAsync.acceptOnWorldThread(world, PhysicsPersistence.statusAsync(store), status -> sendStatus(ctx, world, status)); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index b4511f51..18d415b1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -20,12 +20,12 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -40,6 +40,8 @@ import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; @@ -92,14 +94,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - return PhysicsStoreAsync.acceptOnWorldThread(world, + return PhysicsAsync.acceptOnWorldThread(world, raycastAsync(store, ref, spaceRef), hit -> applyImpulse(ctx, store, ref, world, hit)); } @@ -156,7 +158,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, UUID bodyUuid = UUID.randomUUID(); Vector3d spawn = new Vector3d(playerPos).add(0.0, 2.0, 0.0); - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() @@ -180,7 +182,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, target(targetPosition)); TimeResource time = store.getResource(TimeResource.getResourceType()); - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, + ExamplePhysicsUtils.attachBlockBody(store, time, new ExamplePhysicsUtils.CreatedBlockBody(bodyUuid, bodyRef, @@ -214,14 +216,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - return PhysicsStoreAsync.acceptOnWorldThread(world, + return PhysicsAsync.acceptOnWorldThread(world, raycastAsync(store, ref, spaceRef), hit -> attachView(ctx, store, hit)); } @@ -329,7 +331,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); boolean contactEventsEnabled = contactEventsEnabled(resource); - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() @@ -442,7 +444,7 @@ private static CompletionStage raycastAsync(@Nonnull Store executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() @@ -60,8 +61,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, DebugUtils.addArrow(world, start, direction, DebugUtils.COLOR_WHITE, 0.8f, 4.0f, DebugUtils.FLAG_FADE); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreRaycasts.closestAsync(world, + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsRaycasts.closestAsync(world, spaceRef, ExamplePhysicsUtils.toVector3f(start), ExamplePhysicsUtils.toVector3f(end)), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index e21ab37d..3079ea1e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import org.joml.Vector3d; public class ShapesCommand extends AbstractAsyncPlayerCommand { @@ -44,7 +45,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java index f4bab0f9..bc48d7d8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; + +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import org.joml.Vector3d; /** diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 635d6179..4a7685fe 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -15,12 +15,12 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -76,15 +76,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound.")); return CompletableFuture.completedFuture(null); } BenchmarkLayout layout = BenchmarkLayout.around(playerPos, request.count()); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreDiagnostics.bodyCountAsync(world, spaceRef), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.bodyCountAsync(world, spaceRef), beforeBodies -> spawnBenchmark(ctx, store, world, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 9e4587ac..9b268c55 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -30,8 +30,8 @@ import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.Iterator; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -147,7 +147,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound.")); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 2de93117..f5a7296b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -14,13 +14,13 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExampleBlockEntityVisuals; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; +import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -81,7 +81,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); Ref spaceRef; try { - spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); } catch (IllegalStateException exception) { spaceRef = null; } @@ -120,7 +120,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } for (CreatedBlockBody createdBody : createdBodyRows) { - ExamplePhysicsUtils.attachPhysicsStoreBlockBody(store, time, createdBody); + ExamplePhysicsUtils.attachBlockBody(store, time, createdBody); } ctx.sender().sendMessage(Message.raw("Queued " + createdJoints diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index 2d03fc0f..2d35a263 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.BodyEntityBatchTiming; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.BodyEntityBatchTiming; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -61,7 +61,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, spaceId); + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " is not bound.")); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index 1439e406..f4450d93 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -12,10 +12,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreRaycasts; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -57,7 +57,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() @@ -69,8 +69,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, List segments = getRaycastSegments(side, rays, playerPos); long startNanos = System.nanoTime(); - return PhysicsStoreAsync.acceptOnWorldThread(world, - PhysicsStoreRaycasts.closestBatchAsync(world, spaceRef, segments), + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsRaycasts.closestBatchAsync(world, spaceRef, segments), result -> { long elapsedNanos = System.nanoTime() - startNanos; ctx.sender().sendMessage(Message.raw("Ran " + rays + " raycasts: " diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index 38925e4d..f3bcaac2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -58,7 +58,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicy.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicy.java index 02d74b7b..5c1e9906 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicy.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicy.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.protocol.BlockMaterial; import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index c3042f9f..31b8730c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -21,12 +21,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.commands.ExamplePhysicsUtils.CreatedBlockBody; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -109,7 +109,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @Nonnull ExplosiveBlockComponent settings) { - Ref spaceRef = ExamplePhysicsUtils.resolvePhysicsStoreSpaceRef(world, + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { throw new IllegalStateException("Cannot spawn explosive fragments because PhysicsStore " diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java index 99cf1990..4d1ddb07 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index 9f3699f2..ce3982c4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index add27526..342b6cd8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -16,9 +16,9 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -120,10 +120,10 @@ private static SpaceId attachmentSpaceId(@Nonnull Store store, .getPhysicsStore().getStore(); Ref bodyRef = attachment.getBodyRef(); PhysicsBodyRegistrationView registration = bodyRef != null && bodyRef.isValid() - ? PhysicsStoreBodies.registrationView(physics, bodyRef) + ? PhysicsBodies.registrationView(physics, bodyRef) : null; if (registration == null) { - registration = PhysicsStoreBodies.registrationView(physics, attachment.getBodyUuid()); + registration = PhysicsBodies.registrationView(physics, attachment.getBodyUuid()); } return registration != null ? registration.spaceId() : null; } @@ -135,14 +135,14 @@ private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store Store physics = ((PhysicsStoreWorld) store.getExternalData().getWorld()) .getPhysicsStore().getStore(); Ref bodyRef = attachment.getBodyRef(); - PhysicsStoreBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() - ? PhysicsStoreBodies.snapshot(physics, bodyRef) + PhysicsBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() + ? PhysicsBodies.snapshot(physics, bodyRef) : null; if (snapshot != null && !bodyUuid.equals(snapshot.bodyUuid())) { snapshot = null; } if (snapshot == null) { - snapshot = PhysicsStoreBodies.snapshot(physics, bodyUuid); + snapshot = PhysicsBodies.snapshot(physics, bodyUuid); } return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; } @@ -153,7 +153,7 @@ private record BodyMotionSnapshot(float positionX, float linearVelocityY) { @Nonnull - private static BodyMotionSnapshot from(@Nonnull PhysicsStoreBodySnapshot snapshot) { + private static BodyMotionSnapshot from(@Nonnull PhysicsBodySnapshot snapshot) { Vector3f position = snapshot.position(); Vector3f velocity = snapshot.linearVelocity(); return new BodyMotionSnapshot(position.x, position.y, position.z, velocity.y); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java new file mode 100644 index 00000000..77cac9d2 --- /dev/null +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java @@ -0,0 +1,144 @@ +package dev.hytalemodding.impulse.examples.utils; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; + +final class BlockBodyBatchBuilder { + + private static final int POSITION_STRIDE = 3; + + private final long bodyUuidRunId = UUID.randomUUID().getMostSignificantBits(); + private long[] bodyUuidMostSignificantBits; + private long[] bodyUuidLeastSignificantBits; + private float[] positions; + private int size; + private boolean sealed; + + BlockBodyBatchBuilder(int expectedBodies) { + int capacity = Math.max(1, expectedBodies); + bodyUuidMostSignificantBits = new long[capacity]; + bodyUuidLeastSignificantBits = new long[capacity]; + positions = new float[capacity * POSITION_STRIDE]; + } + + @Nonnull + public BlockBodyBatchBuilder addBody(float positionX, + float positionY, + float positionZ) { + return addBody(bodyUuidRunId, + size + 1L, + positionX, + positionY, + positionZ); + } + + @Nonnull + public BlockBodyBatchBuilder addBody(@Nonnull UUID bodyUuid, + float positionX, + float positionY, + float positionZ) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + return addBody(bodyUuid.getMostSignificantBits(), + bodyUuid.getLeastSignificantBits(), + positionX, + positionY, + positionZ); + } + + @Nonnull + public UUID body(float positionX, + float positionY, + float positionZ) { + long leastSignificantBits = size + 1L; + addBody(bodyUuidRunId, leastSignificantBits, positionX, positionY, positionZ); + return new UUID(bodyUuidRunId, leastSignificantBits); + } + + @Nonnull + public UUID body(@Nonnull UUID bodyUuid, + float positionX, + float positionY, + float positionZ) { + addBody(bodyUuid, positionX, positionY, positionZ); + return bodyUuid; + } + + @Nonnull + public BlockBodyBatchBuilder addBody(long bodyUuidMostSignificantBits, + long bodyUuidLeastSignificantBits, + float positionX, + float positionY, + float positionZ) { + assertMutable(); + ensureCapacity(size + 1); + this.bodyUuidMostSignificantBits[size] = bodyUuidMostSignificantBits; + this.bodyUuidLeastSignificantBits[size] = bodyUuidLeastSignificantBits; + int positionOffset = size * POSITION_STRIDE; + positions[positionOffset] = positionX; + positions[positionOffset + 1] = positionY; + positions[positionOffset + 2] = positionZ; + size++; + return this; + } + + void seal() { + sealed = true; + } + + boolean isEmpty() { + return size == 0; + } + + int size() { + return size; + } + + @Nonnull + UUID bodyUuid(int index) { + checkIndex(index); + return new UUID(bodyUuidMostSignificantBits[index], + bodyUuidLeastSignificantBits[index]); + } + + float positionX(int index) { + return position(index, 0); + } + + float positionY(int index) { + return position(index, 1); + } + + float positionZ(int index) { + return position(index, 2); + } + + private float position(int index, int slot) { + checkIndex(index); + return positions[index * POSITION_STRIDE + slot]; + } + + private void ensureCapacity(int required) { + if (required <= bodyUuidMostSignificantBits.length) { + return; + } + int nextCapacity = Math.max(required, + bodyUuidMostSignificantBits.length + (bodyUuidMostSignificantBits.length >> 1) + 1); + bodyUuidMostSignificantBits = Arrays.copyOf(bodyUuidMostSignificantBits, nextCapacity); + bodyUuidLeastSignificantBits = Arrays.copyOf(bodyUuidLeastSignificantBits, nextCapacity); + positions = Arrays.copyOf(positions, nextCapacity * POSITION_STRIDE); + } + + private void checkIndex(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(index); + } + } + + private void assertMutable() { + if (sealed) { + throw new IllegalStateException("Block body batch builder is already sealed"); + } + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java new file mode 100644 index 00000000..3e0cf421 --- /dev/null +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java @@ -0,0 +1,34 @@ +package dev.hytalemodding.impulse.examples.utils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +record BlockBodyBatchResult(@Nullable ExamplePhysicsUtils.SpawnedBlockBody[] bodies, + int count, + long entityApplyNanos, + long entityAttachNanos) { + + BlockBodyBatchResult { + if (bodies != null && bodies.length != count) { + throw new IllegalArgumentException("Collected body count does not match batch count"); + } + count = Math.max(0, count); + entityApplyNanos = Math.max(0L, entityApplyNanos); + entityAttachNanos = Math.max(0L, entityAttachNanos); + } + + @Nonnull + ExamplePhysicsUtils.SpawnedBlockBody[] collectedBodies() { + if (bodies == null) { + throw new IllegalStateException("Block body batch did not collect body results"); + } + return bodies; + } + + @Nonnull + ExamplePhysicsUtils.BlockBodyBatchTiming timing() { + return new ExamplePhysicsUtils.BlockBodyBatchTiming(count, + entityApplyNanos, + entityAttachNanos); + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisuals.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisuals.java similarity index 97% rename from impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisuals.java rename to impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisuals.java index feb7f30e..0af9b398 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisuals.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisuals.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.examples.commands; +package dev.hytalemodding.impulse.examples.utils; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsOriginMath.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsOriginMath.java similarity index 92% rename from impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsOriginMath.java rename to impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsOriginMath.java index ad8bfe1c..f3ee08df 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsOriginMath.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsOriginMath.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.examples.commands; +package dev.hytalemodding.impulse.examples.utils; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import java.util.Objects; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java similarity index 81% rename from impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java rename to impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 71619525..0284fb3d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.examples.commands; +package dev.hytalemodding.impulse.examples.utils; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentType; @@ -14,26 +14,25 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreThreading; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -56,12 +55,12 @@ private ExamplePhysicsUtils() { } @Nullable - public static Ref resolvePhysicsStoreSpaceRef(@Nonnull World world, + public static Ref resolveSpaceRef(@Nonnull World world, @Nonnull SpaceId spaceId) { Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - return PhysicsStoreSpaces.resolveRef(store, spaceId); + return PhysicsSpaces.resolveRef(store, spaceId); } @Nonnull @@ -102,7 +101,7 @@ public static void addPhysicsStoreBodies(@Nonnull World world, Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(store, "add PhysicsStore body entities"); + PhysicsThreading.requireWorldThread(store, "add PhysicsStore body entities"); for (BodyEntityDescriptor descriptor : descriptors) { addPhysicsStoreBodyUnchecked(store, descriptor, @@ -127,7 +126,7 @@ private static Ref addPhysicsStoreBody(@Nonnull Store addPhysicsStoreBodyUnchecked(@Nonnull Store addPhysicsStoreJoint(@Nonnull World world, Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) .getPhysicsStore() .getStore(); - PhysicsStoreThreading.requireWorldThread(store, "add a PhysicsStore joint entity"); - return store.addEntity(PhysicsStoreEntities.jointHolder(store, + PhysicsThreading.requireWorldThread(store, "add a PhysicsStore joint entity"); + return store.addEntity(PhysicsEntities.jointHolder(store, Objects.requireNonNull(jointUuid, "jointUuid"), joint), AddReason.SPAWN); } @@ -167,7 +166,7 @@ public static void appendPhysicsStoreBodyCommand(@Nonnull Store st Objects.requireNonNull(store, "store"); Objects.requireNonNull(bodyRef, "bodyRef"); Objects.requireNonNull(command, "command"); - PhysicsStoreThreading.requireWorldThread(store, "append a PhysicsStore body command"); + PhysicsThreading.requireWorldThread(store, "append a PhysicsStore body command"); BodyCommandComponent existing = store.getComponent(bodyRef, BodyCommandComponent.getComponentType()); BodyCommandComponent merged = existing != null ? existing.append(command) : command; @@ -188,14 +187,14 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, return null; } SpaceId spaceId = new SpaceId(rawSpaceId); - if (!PhysicsStoreSpaces.hasSpace(store, spaceId)) { + if (!PhysicsSpaces.hasSpace(store, spaceId)) { ctx.sender().sendMessage(Message.raw("No physics space id=" + rawSpaceId + " exists.")); return null; } return spaceId; } - SpaceId firstSpaceId = PhysicsStoreSpaces.spaceIds(store) + SpaceId firstSpaceId = PhysicsSpaces.spaceIds(store) .stream() .min(Comparator.comparingInt(SpaceId::value)) .orElse(null); @@ -225,7 +224,7 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, settings, linearVelocity); if (physicsStoreBody != null) { - return attachPhysicsStoreBlockBody(store, time, physicsStoreBody); + return attachBlockBody(store, time, physicsStoreBody); } throw new IllegalStateException("Cannot spawn block body because the target space is not " @@ -243,7 +242,7 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - return attachPhysicsStoreBlockBody(store, + return attachBlockBody(store, time, createPhysicsStoreBlockBody(store.getExternalData().getWorld(), spaceRef, @@ -274,7 +273,7 @@ private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store spaceRef; try { - spaceRef = resolvePhysicsStoreSpaceRef(world, spaceId); + spaceRef = resolveSpaceRef(world, spaceId); } catch (IllegalStateException exception) { return null; } @@ -480,7 +479,7 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, return new DynamicBodyBatchPlan(List.of(), 0L); } - Ref spaceRef = resolvePhysicsStoreSpaceRef(world, spaceId); + Ref spaceRef = resolveSpaceRef(world, spaceId); if (spaceRef == null) { throw new IllegalStateException("Cannot add dynamic body entities because the target space is not " + "bound in PhysicsStore: " + spaceId.value()); @@ -514,7 +513,7 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref store, + public static SpawnedBlockBody attachBlockBody(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull CreatedBlockBody created) { Ref bodyRef = created.bodyRef(); - PhysicsStoreThreading.requireWorldThread(bodyRef.getStore(), + PhysicsThreading.requireWorldThread(bodyRef.getStore(), "attach a visual to a created PhysicsStore body entity"); if (!bodyRef.isValid()) { throw new IllegalStateException("Cannot attach visual because PhysicsStore body entity " + "is no longer valid: " + created.bodyUuid()); } - Ref entity = spawnAttachedPhysicsStoreBlockEntity(store, + Ref entity = spawnAttachedBlockEntity(store, time, created.bodyUuid(), created.blockType(), @@ -711,7 +710,7 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store spaceRef = resolvePhysicsStoreSpaceRef(world, spaceId); + Ref spaceRef = resolveSpaceRef(world, spaceId); if (spaceRef == null) { throw new IllegalStateException("Cannot spawn block body batch because the target space is not " + "bound in PhysicsStore: " + spaceId.value()); @@ -748,7 +747,7 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store entity = spawnAttachedPhysicsStoreBlockEntity(store, + Ref entity = spawnAttachedBlockEntity(store, time, bodyUuid, blockType, @@ -852,7 +851,7 @@ public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store spawnAttachedPhysicsStoreBlockEntity(@Nonnull Store store, + private static Ref spawnAttachedBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull UUID physicsBodyUuid, @Nullable String blockType, @@ -910,7 +909,7 @@ public static int optionalInt(@Nonnull CommandContext ctx, return Math.min(value, max); } - static Vector3f toVector3f(@Nonnull Vector3d vector) { + public static Vector3f toVector3f(@Nonnull Vector3d vector) { return new Vector3f((float) vector.x, (float) vector.y, (float) vector.z); } @@ -974,171 +973,4 @@ public record CreatedBlockBody(@Nonnull UUID bodyUuid, } } - public static final class BlockBodyBatchBuilder { - - private static final int POSITION_STRIDE = 3; - - private final long bodyUuidRunId = UUID.randomUUID().getMostSignificantBits(); - private long[] bodyUuidMostSignificantBits; - private long[] bodyUuidLeastSignificantBits; - private float[] positions; - private int size; - private boolean sealed; - - private BlockBodyBatchBuilder(int expectedBodies) { - int capacity = Math.max(1, expectedBodies); - bodyUuidMostSignificantBits = new long[capacity]; - bodyUuidLeastSignificantBits = new long[capacity]; - positions = new float[capacity * POSITION_STRIDE]; - } - - @Nonnull - public BlockBodyBatchBuilder addBody(float positionX, - float positionY, - float positionZ) { - return addBody(bodyUuidRunId, - size + 1L, - positionX, - positionY, - positionZ); - } - - @Nonnull - public BlockBodyBatchBuilder addBody(@Nonnull UUID bodyUuid, - float positionX, - float positionY, - float positionZ) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - return addBody(bodyUuid.getMostSignificantBits(), - bodyUuid.getLeastSignificantBits(), - positionX, - positionY, - positionZ); - } - - @Nonnull - public UUID body(float positionX, - float positionY, - float positionZ) { - long leastSignificantBits = size + 1L; - addBody(bodyUuidRunId, leastSignificantBits, positionX, positionY, positionZ); - return new UUID(bodyUuidRunId, leastSignificantBits); - } - - @Nonnull - public UUID body(@Nonnull UUID bodyUuid, - float positionX, - float positionY, - float positionZ) { - addBody(bodyUuid, positionX, positionY, positionZ); - return bodyUuid; - } - - @Nonnull - private BlockBodyBatchBuilder addBody(long bodyUuidMostSignificantBits, - long bodyUuidLeastSignificantBits, - float positionX, - float positionY, - float positionZ) { - assertMutable(); - ensureCapacity(size + 1); - this.bodyUuidMostSignificantBits[size] = bodyUuidMostSignificantBits; - this.bodyUuidLeastSignificantBits[size] = bodyUuidLeastSignificantBits; - int positionOffset = size * POSITION_STRIDE; - positions[positionOffset] = positionX; - positions[positionOffset + 1] = positionY; - positions[positionOffset + 2] = positionZ; - size++; - return this; - } - - private void seal() { - sealed = true; - } - - private boolean isEmpty() { - return size == 0; - } - - private int size() { - return size; - } - - @Nonnull - private UUID bodyUuid(int index) { - checkIndex(index); - return new UUID(bodyUuidMostSignificantBits[index], - bodyUuidLeastSignificantBits[index]); - } - - private float positionX(int index) { - return position(index, 0); - } - - private float positionY(int index) { - return position(index, 1); - } - - private float positionZ(int index) { - return position(index, 2); - } - - private float position(int index, int slot) { - checkIndex(index); - return positions[index * POSITION_STRIDE + slot]; - } - - private void ensureCapacity(int required) { - if (required <= bodyUuidMostSignificantBits.length) { - return; - } - int nextCapacity = Math.max(required, - bodyUuidMostSignificantBits.length + (bodyUuidMostSignificantBits.length >> 1) + 1); - bodyUuidMostSignificantBits = Arrays.copyOf(bodyUuidMostSignificantBits, nextCapacity); - bodyUuidLeastSignificantBits = Arrays.copyOf(bodyUuidLeastSignificantBits, nextCapacity); - positions = Arrays.copyOf(positions, nextCapacity * POSITION_STRIDE); - } - - private void checkIndex(int index) { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(index); - } - } - - private void assertMutable() { - if (sealed) { - throw new IllegalStateException("Block body batch builder is already sealed"); - } - } - } - - private record BlockBodyBatchResult(@Nullable SpawnedBlockBody[] bodies, - int count, - long entityApplyNanos, - long entityAttachNanos) { - - private BlockBodyBatchResult { - if (bodies != null && bodies.length != count) { - throw new IllegalArgumentException("Collected body count does not match batch count"); - } - count = Math.max(0, count); - entityApplyNanos = Math.max(0L, entityApplyNanos); - entityAttachNanos = Math.max(0L, entityAttachNanos); - } - - @Nonnull - private SpawnedBlockBody[] collectedBodies() { - if (bodies == null) { - throw new IllegalStateException("Block body batch did not collect body results"); - } - return bodies; - } - - @Nonnull - private BlockBodyBatchTiming timing() { - return new BlockBodyBatchTiming(count, - entityApplyNanos, - entityAttachNanos); - } - } } From 91b330f2fcad9858e055de4543ab48363dc74ead Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 08:32:02 +0200 Subject: [PATCH 301/534] refactor(core): split physics type registries Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 105 +++- .../PersistentPhysicsStoreResource.java | 4 +- .../PhysicsStoreRegistration.java | 121 +---- .../PhysicsBodyRegistrationResource.java | 3 +- .../resources/PhysicsDebugResource.java | 3 +- .../resources/PhysicsEventResource.java | 3 +- .../PhysicsIdentityIndexResource.java | 3 +- .../resources/PhysicsProfilingResource.java | 3 +- .../resources/PhysicsResourceTypes.java | 213 ++++++++ .../PhysicsRestoreStatusResource.java | 3 +- .../resources/PhysicsRuntimeResource.java | 3 +- .../resources/PhysicsSnapshotResource.java | 3 +- ...hysicsSpaceCompatibilityIndexResource.java | 3 +- .../PhysicsStepSchedulerResource.java | 3 +- .../PhysicsStoreReadQueueResource.java | 3 +- .../PhysicsTerrainMutationQueueResource.java | 3 +- .../PhysicsTerrainPayloadResource.java | 3 +- .../PhysicsWorldCollisionIndexResource.java | 3 +- .../PhysicsWorldSettingsResource.java | 3 +- .../components/BodyCommandComponent.java | 3 +- .../core/plugin/components/BodyComponent.java | 3 +- .../plugin/components/ColliderComponent.java | 3 +- .../components/CollisionFilterComponent.java | 3 +- .../CollisionLodSettingsComponent.java | 3 +- .../plugin/components/DynamicsComponent.java | 3 +- .../ExtensionSettingsComponent.java | 3 +- .../plugin/components/JointComponent.java | 3 +- .../plugin/components/MaterialComponent.java | 3 +- .../components/PhysicsComponentTypes.java | 237 +++++++++ .../plugin/components/ShapeComponent.java | 3 +- .../components/SolverSettingsComponent.java | 3 +- .../plugin/components/SpaceComponent.java | 3 +- .../plugin/components/TargetComponent.java | 3 +- .../components/TerrainColliderComponent.java | 3 +- .../core/plugin/components/UuidComponent.java | 3 +- ...isualMaterializationSettingsComponent.java | 3 +- .../VisualSyncSettingsComponent.java | 3 +- .../components/WorldCollisionComponent.java | 3 +- .../physicsstore/PhysicsStoreTypes.java | 479 ------------------ impulse-core/src/module-info/module-info.java | 6 +- .../examples/utils/BlockBodyBatchBuilder.java | 2 +- 41 files changed, 610 insertions(+), 656 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsResourceTypes.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 882bb9ff..5a5abb78 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -15,6 +15,7 @@ import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.plugin.PluginManager; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBackend; @@ -34,6 +35,25 @@ import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -95,11 +115,14 @@ public BackendId getDefaultBackendId() { @Override protected void setup() { PhysicsStoreEarlyPluginProbe.requireAvailable(); - PhysicsStoreRegistration.register(this); + ComponentRegistryProxy physicsStoreRegistry = + PhysicsStoreRegistration.physicsStoreRegistry(this); + registerPhysicsStoreComponents(physicsStoreRegistry); + PhysicsStoreRegistration.register(physicsStoreRegistry); ImpulseSubPluginRegistration.register(this); discoverBackends(); - registerComponents(); + registerEntityStoreComponents(); registerSystems(); registerCommands(); } @@ -202,7 +225,83 @@ private String getAvailableBackendIds() { return ids.toString(); } - private void registerComponents() { + private static void registerPhysicsStoreComponents( + @Nonnull ComponentRegistryProxy physicsRegistry) { + PhysicsComponentTypes.setUuidComponentType(physicsRegistry.registerComponent( + UuidComponent.class, + "Uuid", + UuidComponent.CODEC)); + PhysicsComponentTypes.setSpaceComponentType(physicsRegistry.registerComponent( + SpaceComponent.class, + "Space", + SpaceComponent.CODEC)); + PhysicsComponentTypes.setBodyComponentType(physicsRegistry.registerComponent( + BodyComponent.class, + "Body", + BodyComponent.CODEC)); + PhysicsComponentTypes.setBodyCommandComponentType(physicsRegistry.registerComponent( + BodyCommandComponent.class, + "BodyCommand", + BodyCommandComponent.CODEC)); + PhysicsComponentTypes.setDynamicsComponentType(physicsRegistry.registerComponent( + DynamicsComponent.class, + "Dynamics", + DynamicsComponent.CODEC)); + PhysicsComponentTypes.setColliderComponentType(physicsRegistry.registerComponent( + ColliderComponent.class, + "Collider", + ColliderComponent.CODEC)); + PhysicsComponentTypes.setShapeComponentType(physicsRegistry.registerComponent( + ShapeComponent.class, + "Shape", + ShapeComponent.CODEC)); + PhysicsComponentTypes.setMaterialComponentType(physicsRegistry.registerComponent( + MaterialComponent.class, + "Material", + MaterialComponent.CODEC)); + PhysicsComponentTypes.setCollisionFilterComponentType(physicsRegistry.registerComponent( + CollisionFilterComponent.class, + "CollisionFilter", + CollisionFilterComponent.CODEC)); + PhysicsComponentTypes.setJointComponentType(physicsRegistry.registerComponent( + JointComponent.class, + "Joint", + JointComponent.CODEC)); + PhysicsComponentTypes.setTargetComponentType(physicsRegistry.registerComponent( + TargetComponent.class, + "Target", + TargetComponent.CODEC)); + PhysicsComponentTypes.setTerrainColliderComponentType(physicsRegistry.registerComponent( + TerrainColliderComponent.class, + "TerrainCollider", + TerrainColliderComponent.CODEC)); + PhysicsComponentTypes.setWorldCollisionComponentType(physicsRegistry.registerComponent( + WorldCollisionComponent.class, + "WorldCollision", + WorldCollisionComponent.CODEC)); + PhysicsComponentTypes.setSolverSettingsComponentType(physicsRegistry.registerComponent( + SolverSettingsComponent.class, + "SolverSettings", + SolverSettingsComponent.CODEC)); + PhysicsComponentTypes.setVisualSyncSettingsComponentType(physicsRegistry.registerComponent( + VisualSyncSettingsComponent.class, + "VisualSyncSettings", + VisualSyncSettingsComponent.CODEC)); + PhysicsComponentTypes.setVisualMaterializationSettingsComponentType( + physicsRegistry.registerComponent(VisualMaterializationSettingsComponent.class, + "VisualMaterializationSettings", + VisualMaterializationSettingsComponent.CODEC)); + PhysicsComponentTypes.setCollisionLodSettingsComponentType(physicsRegistry.registerComponent( + CollisionLodSettingsComponent.class, + "CollisionLodSettings", + CollisionLodSettingsComponent.CODEC)); + PhysicsComponentTypes.setExtensionSettingsComponentType(physicsRegistry.registerComponent( + ExtensionSettingsComponent.class, + "ExtensionSettings", + ExtensionSettingsComponent.CODEC)); + } + + private void registerEntityStoreComponents() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); bodyAttachmentComponentType = entityRegistry.registerComponent( BodyAttachmentComponent.class, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java index 92f3a715..a6500e6e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsResourceTypes; import java.util.Arrays; import javax.annotation.Nonnull; @@ -212,7 +212,7 @@ public PersistentPhysicsStoreResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.persistentStoreResourceType(); + return PhysicsResourceTypes.persistentStoreResourceType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java index 0502ca6d..021aaebe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; @@ -43,25 +44,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.physicsstore.systems.WorldCollisionIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; -import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -85,122 +67,57 @@ public final class PhysicsStoreRegistration { private PhysicsStoreRegistration() { } - public static void register(@Nonnull PluginBase plugin) { - ComponentRegistryProxy registry = physicsStoreRegistry(plugin); + public static void register(@Nonnull ComponentRegistryProxy registry) { PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); PhysicsStoreHooks.registerTickGate(STEP_TICK_GATE); - PhysicsStoreTypes.setUuidComponentType(registry.registerComponent(UuidComponent.class, - "Uuid", - UuidComponent.CODEC)); - PhysicsStoreTypes.setSpaceComponentType(registry.registerComponent(SpaceComponent.class, - "Space", - SpaceComponent.CODEC)); - PhysicsStoreTypes.setBodyComponentType(registry.registerComponent(BodyComponent.class, - "Body", - BodyComponent.CODEC)); - PhysicsStoreTypes.setBodyCommandComponentType(registry.registerComponent( - BodyCommandComponent.class, - "BodyCommand", - BodyCommandComponent.CODEC)); - PhysicsStoreTypes.setDynamicsComponentType(registry.registerComponent(DynamicsComponent.class, - "Dynamics", - DynamicsComponent.CODEC)); - PhysicsStoreTypes.setColliderComponentType(registry.registerComponent(ColliderComponent.class, - "Collider", - ColliderComponent.CODEC)); - PhysicsStoreTypes.setShapeComponentType(registry.registerComponent(ShapeComponent.class, - "Shape", - ShapeComponent.CODEC)); - PhysicsStoreTypes.setMaterialComponentType(registry.registerComponent(MaterialComponent.class, - "Material", - MaterialComponent.CODEC)); - PhysicsStoreTypes.setCollisionFilterComponentType(registry.registerComponent( - CollisionFilterComponent.class, - "CollisionFilter", - CollisionFilterComponent.CODEC)); - PhysicsStoreTypes.setJointComponentType(registry.registerComponent(JointComponent.class, - "Joint", - JointComponent.CODEC)); - PhysicsStoreTypes.setTargetComponentType(registry.registerComponent(TargetComponent.class, - "Target", - TargetComponent.CODEC)); - PhysicsStoreTypes.setTerrainColliderComponentType(registry.registerComponent( - TerrainColliderComponent.class, - "TerrainCollider", - TerrainColliderComponent.CODEC)); - PhysicsStoreTypes.setWorldCollisionComponentType(registry.registerComponent( - WorldCollisionComponent.class, - "WorldCollision", - WorldCollisionComponent.CODEC)); - PhysicsStoreTypes.setSolverSettingsComponentType(registry.registerComponent( - SolverSettingsComponent.class, - "SolverSettings", - SolverSettingsComponent.CODEC)); - PhysicsStoreTypes.setVisualSyncSettingsComponentType(registry.registerComponent( - VisualSyncSettingsComponent.class, - "VisualSyncSettings", - VisualSyncSettingsComponent.CODEC)); - PhysicsStoreTypes.setVisualMaterializationSettingsComponentType(registry.registerComponent( - VisualMaterializationSettingsComponent.class, - "VisualMaterializationSettings", - VisualMaterializationSettingsComponent.CODEC)); - PhysicsStoreTypes.setCollisionLodSettingsComponentType(registry.registerComponent( - CollisionLodSettingsComponent.class, - "CollisionLodSettings", - CollisionLodSettingsComponent.CODEC)); - PhysicsStoreTypes.setExtensionSettingsComponentType(registry.registerComponent( - ExtensionSettingsComponent.class, - "ExtensionSettings", - ExtensionSettingsComponent.CODEC)); - - PhysicsStoreTypes.setRuntimeResourceType(registry.registerResource( + PhysicsResourceTypes.setRuntimeResourceType(registry.registerResource( PhysicsRuntimeResource.class, PhysicsRuntimeResource::new)); - PhysicsStoreTypes.setWorldSettingsResourceType(registry.registerResource( + PhysicsResourceTypes.setWorldSettingsResourceType(registry.registerResource( PhysicsWorldSettingsResource.class, PhysicsWorldSettingsResource::new)); - PhysicsStoreTypes.setStepSchedulerResourceType(registry.registerResource( + PhysicsResourceTypes.setStepSchedulerResourceType(registry.registerResource( PhysicsStepSchedulerResource.class, PhysicsStepSchedulerResource::new)); - PhysicsStoreTypes.setSpaceCompatibilityIndexResourceType(registry.registerResource( + PhysicsResourceTypes.setSpaceCompatibilityIndexResourceType(registry.registerResource( PhysicsSpaceCompatibilityIndexResource.class, PhysicsSpaceCompatibilityIndexResource::new)); - PhysicsStoreTypes.setTerrainMutationQueueResourceType(registry.registerResource( + PhysicsResourceTypes.setTerrainMutationQueueResourceType(registry.registerResource( PhysicsTerrainMutationQueueResource.class, PhysicsTerrainMutationQueueResource::new)); - PhysicsStoreTypes.setIdentityIndexResourceType(registry.registerResource( + PhysicsResourceTypes.setIdentityIndexResourceType(registry.registerResource( PhysicsIdentityIndexResource.class, PhysicsIdentityIndexResource::new)); - PhysicsStoreTypes.setSnapshotResourceType(registry.registerResource( + PhysicsResourceTypes.setSnapshotResourceType(registry.registerResource( PhysicsSnapshotResource.class, PhysicsSnapshotResource::new)); - PhysicsStoreTypes.setBodyRegistrationResourceType(registry.registerResource( + PhysicsResourceTypes.setBodyRegistrationResourceType(registry.registerResource( PhysicsBodyRegistrationResource.class, PhysicsBodyRegistrationResource::new)); - PhysicsStoreTypes.setEventResourceType(registry.registerResource( + PhysicsResourceTypes.setEventResourceType(registry.registerResource( PhysicsEventResource.class, PhysicsEventResource::new)); - PhysicsStoreTypes.setReadQueueResourceType(registry.registerResource( + PhysicsResourceTypes.setReadQueueResourceType(registry.registerResource( PhysicsStoreReadQueueResource.class, PhysicsStoreReadQueueResource::new)); - PhysicsStoreTypes.setTerrainPayloadResourceType(registry.registerResource( + PhysicsResourceTypes.setTerrainPayloadResourceType(registry.registerResource( PhysicsTerrainPayloadResource.class, PhysicsTerrainPayloadResource::new)); - PhysicsStoreTypes.setWorldCollisionIndexResourceType(registry.registerResource( + PhysicsResourceTypes.setWorldCollisionIndexResourceType(registry.registerResource( PhysicsWorldCollisionIndexResource.class, PhysicsWorldCollisionIndexResource::new)); - PhysicsStoreTypes.setPersistentStoreResourceType(registry.registerResource( + PhysicsResourceTypes.setPersistentStoreResourceType(registry.registerResource( PersistentPhysicsStoreResource.class, "PersistentPhysicsStore", PersistentPhysicsStoreResource.CODEC)); - PhysicsStoreTypes.setRestoreStatusResourceType(registry.registerResource( + PhysicsResourceTypes.setRestoreStatusResourceType(registry.registerResource( PhysicsRestoreStatusResource.class, PhysicsRestoreStatusResource::new)); - PhysicsStoreTypes.setProfilingResourceType(registry.registerResource( + PhysicsResourceTypes.setProfilingResourceType(registry.registerResource( PhysicsProfilingResource.class, PhysicsProfilingResource::new)); - PhysicsStoreTypes.setDebugResourceType(registry.registerResource( + PhysicsResourceTypes.setDebugResourceType(registry.registerResource( PhysicsDebugResource.class, PhysicsDebugResource::new)); @@ -370,7 +287,7 @@ private static RuntimeException runShutdownCleanup(@Nullable RuntimeException fa @Nonnull @SuppressWarnings("unchecked") - private static ComponentRegistryProxy physicsStoreRegistry( + public static ComponentRegistryProxy physicsStoreRegistry( @Nonnull PluginBase plugin) { try { Method method = plugin.getClass().getMethod(REGISTRY_METHOD); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index 049314ad..275ecfcf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -7,7 +7,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import java.util.ArrayList; @@ -133,7 +132,7 @@ public PhysicsBodyRegistrationResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.bodyRegistrationResourceType(); + return PhysicsResourceTypes.bodyRegistrationResourceType(); } public record BodyRegistrationPublication( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java index 157443a1..d002dc7f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import lombok.Getter; import lombok.Setter; import javax.annotation.Nonnull; @@ -34,6 +33,6 @@ public PhysicsDebugResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.debugResourceType(); + return PhysicsResourceTypes.debugResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java index cf394616..05bea534 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java @@ -6,7 +6,6 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsStepEvent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import java.util.List; import java.util.Objects; @@ -74,6 +73,6 @@ public PhysicsEventResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.eventResourceType(); + return PhysicsResourceTypes.eventResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java index 0dba8b1e..81d3fd3a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java @@ -7,7 +7,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -112,6 +111,6 @@ public PhysicsIdentityIndexResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.identityIndexResourceType(); + return PhysicsResourceTypes.identityIndexResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java index cc7b6aa8..ef8edfd6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import javax.annotation.Nonnull; @@ -145,7 +144,7 @@ public PhysicsProfilingResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.profilingResourceType(); + return PhysicsResourceTypes.profilingResourceType(); } public record StepSample(int spaces, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsResourceTypes.java new file mode 100644 index 00000000..e2b5b20a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsResourceTypes.java @@ -0,0 +1,213 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore.resources; + +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Registered Hytale ECS resource type handles for the internal PhysicsStore runtime. + */ +public final class PhysicsResourceTypes { + + @Nullable + private static ResourceType runtimeResourceType; + @Nullable + private static ResourceType worldSettingsResourceType; + @Nullable + private static ResourceType stepSchedulerResourceType; + @Nullable + private static ResourceType + spaceCompatibilityIndexResourceType; + @Nullable + private static ResourceType terrainMutationQueueResourceType; + @Nullable + private static ResourceType identityIndexResourceType; + @Nullable + private static ResourceType snapshotResourceType; + @Nullable + private static ResourceType + bodyRegistrationResourceType; + @Nullable + private static ResourceType eventResourceType; + @Nullable + private static ResourceType readQueueResourceType; + @Nullable + private static ResourceType terrainPayloadResourceType; + @Nullable + private static ResourceType worldCollisionIndexResourceType; + @Nullable + private static ResourceType persistentStoreResourceType; + @Nullable + private static ResourceType restoreStatusResourceType; + @Nullable + private static ResourceType profilingResourceType; + @Nullable + private static ResourceType debugResourceType; + + private PhysicsResourceTypes() { + } + + public static void setRuntimeResourceType( + @Nonnull ResourceType type) { + runtimeResourceType = type; + } + + public static void setWorldSettingsResourceType( + @Nonnull ResourceType type) { + worldSettingsResourceType = type; + } + + public static void setStepSchedulerResourceType( + @Nonnull ResourceType type) { + stepSchedulerResourceType = type; + } + + public static void setSpaceCompatibilityIndexResourceType( + @Nonnull ResourceType type) { + spaceCompatibilityIndexResourceType = type; + } + + public static void setTerrainMutationQueueResourceType( + @Nonnull ResourceType type) { + terrainMutationQueueResourceType = type; + } + + public static void setIdentityIndexResourceType( + @Nonnull ResourceType type) { + identityIndexResourceType = type; + } + + public static void setSnapshotResourceType( + @Nonnull ResourceType type) { + snapshotResourceType = type; + } + + public static void setBodyRegistrationResourceType( + @Nonnull ResourceType type) { + bodyRegistrationResourceType = type; + } + + public static void setEventResourceType( + @Nonnull ResourceType type) { + eventResourceType = type; + } + + public static void setReadQueueResourceType( + @Nonnull ResourceType type) { + readQueueResourceType = type; + } + + public static void setTerrainPayloadResourceType( + @Nonnull ResourceType type) { + terrainPayloadResourceType = type; + } + + public static void setWorldCollisionIndexResourceType( + @Nonnull ResourceType type) { + worldCollisionIndexResourceType = type; + } + + public static void setPersistentStoreResourceType( + @Nonnull ResourceType type) { + persistentStoreResourceType = type; + } + + public static void setRestoreStatusResourceType( + @Nonnull ResourceType type) { + restoreStatusResourceType = type; + } + + public static void setProfilingResourceType( + @Nonnull ResourceType type) { + profilingResourceType = type; + } + + public static void setDebugResourceType( + @Nonnull ResourceType type) { + debugResourceType = type; + } + + @Nonnull + public static ResourceType runtimeResourceType() { + return runtimeResourceType; + } + + @Nonnull + public static ResourceType worldSettingsResourceType() { + return worldSettingsResourceType; + } + + @Nonnull + public static ResourceType stepSchedulerResourceType() { + return stepSchedulerResourceType; + } + + @Nonnull + public static ResourceType + spaceCompatibilityIndexResourceType() { + return spaceCompatibilityIndexResourceType; + } + + @Nonnull + public static ResourceType terrainMutationQueueResourceType() { + return terrainMutationQueueResourceType; + } + + @Nonnull + public static ResourceType identityIndexResourceType() { + return identityIndexResourceType; + } + + @Nonnull + public static ResourceType snapshotResourceType() { + return snapshotResourceType; + } + + @Nonnull + public static ResourceType + bodyRegistrationResourceType() { + return bodyRegistrationResourceType; + } + + @Nonnull + public static ResourceType eventResourceType() { + return eventResourceType; + } + + @Nonnull + public static ResourceType readQueueResourceType() { + return readQueueResourceType; + } + + @Nonnull + public static ResourceType terrainPayloadResourceType() { + return terrainPayloadResourceType; + } + + @Nonnull + public static ResourceType worldCollisionIndexResourceType() { + return worldCollisionIndexResourceType; + } + + @Nonnull + public static ResourceType persistentStoreResourceType() { + return persistentStoreResourceType; + } + + @Nonnull + public static ResourceType restoreStatusResourceType() { + return restoreStatusResourceType; + } + + @Nonnull + public static ResourceType profilingResourceType() { + return profilingResourceType; + } + + @Nonnull + public static ResourceType debugResourceType() { + return debugResourceType; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java index 9903e95d..65b08de4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import javax.annotation.Nonnull; @@ -89,6 +88,6 @@ public PhysicsRestoreStatusResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.restoreStatusResourceType(); + return PhysicsResourceTypes.restoreStatusResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index e919aa4c..7741c46c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -11,7 +11,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; @@ -733,7 +732,7 @@ public PhysicsRuntimeResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.runtimeResourceType(); + return PhysicsResourceTypes.runtimeResourceType(); } @FunctionalInterface diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index 087c06a5..3adf00ab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -109,7 +108,7 @@ public PhysicsSnapshotResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.snapshotResourceType(); + return PhysicsResourceTypes.snapshotResourceType(); } private record PublishedSnapshot( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java index cb5b6372..12bca1bb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.util.ArrayList; @@ -93,6 +92,6 @@ public PhysicsSpaceCompatibilityIndexResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.spaceCompatibilityIndexResourceType(); + return PhysicsResourceTypes.spaceCompatibilityIndexResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java index 249eb207..34d818af 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import java.util.List; import java.util.Objects; @@ -146,7 +145,7 @@ public PhysicsStepSchedulerResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.stepSchedulerResourceType(); + return PhysicsResourceTypes.stepSchedulerResourceType(); } private StepInput accumulatePendingDt(float dtSeconds, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java index 5dbeaf57..72f0eb3b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -76,7 +75,7 @@ public PhysicsStoreReadQueueResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.readQueueResourceType(); + return PhysicsResourceTypes.readQueueResourceType(); } public static final class QueuedRead { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java index f1445e07..d31d4ec5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -63,6 +62,6 @@ public synchronized PhysicsTerrainMutationQueueResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.terrainMutationQueueResourceType(); + return PhysicsResourceTypes.terrainMutationQueueResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java index 9cf5931a..b4822583 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import javax.annotation.Nonnull; @@ -49,6 +48,6 @@ public PhysicsTerrainPayloadResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.terrainPayloadResourceType(); + return PhysicsResourceTypes.terrainPayloadResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java index 3d6cc06b..e245e935 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java @@ -6,7 +6,6 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.TerrainColliderMode; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; @@ -58,7 +57,7 @@ public synchronized PhysicsWorldCollisionIndexResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.worldCollisionIndexResourceType(); + return PhysicsResourceTypes.worldCollisionIndexResourceType(); } public record SpaceWorldCollisionSettings(@Nonnull UUID spaceUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java index 581eca5c..6650548f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import javax.annotation.Nonnull; @@ -47,6 +46,6 @@ public PhysicsWorldSettingsResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsStoreTypes.worldSettingsResourceType(); + return PhysicsResourceTypes.worldSettingsResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java index d8bd8474..04970be0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java @@ -10,7 +10,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Arrays; import java.util.Objects; import javax.annotation.Nonnull; @@ -113,7 +112,7 @@ public Entry[] entries() { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.bodyCommandComponentType(); + return PhysicsComponentTypes.bodyCommandComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java index eb0dff25..16b39223 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java @@ -10,7 +10,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -100,7 +99,7 @@ public void setPersistenceMode(@Nonnull PhysicsBodyPersistenceMode persistenceMo @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.bodyComponentType(); + return PhysicsComponentTypes.bodyComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ColliderComponent.java index 5cab5421..beffc7b9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ColliderComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import javax.annotation.Nonnull; import org.joml.Quaternionf; @@ -85,7 +84,7 @@ public void setSensor(boolean sensor) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.colliderComponentType(); + return PhysicsComponentTypes.colliderComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionFilterComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionFilterComponent.java index 62d105de..75482477 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionFilterComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionFilterComponent.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import javax.annotation.Nonnull; /** @@ -62,7 +61,7 @@ public void setCollisionMask(int collisionMask) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.collisionFilterComponentType(); + return PhysicsComponentTypes.collisionFilterComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java index cb357cb7..c5c38935 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import javax.annotation.Nonnull; @@ -132,7 +131,7 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.collisionLodSettingsComponentType(); + return PhysicsComponentTypes.collisionLodSettingsComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/DynamicsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/DynamicsComponent.java index 6741eda0..01423dca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/DynamicsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/DynamicsComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import javax.annotation.Nonnull; @@ -108,7 +107,7 @@ public void setContinuousCollisionEnabled(boolean continuousCollisionEnabled) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.dynamicsComponentType(); + return PhysicsComponentTypes.dynamicsComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java index 1172acba..0173c94e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettingValue; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; @@ -68,7 +67,7 @@ public void copyTo(@Nonnull PhysicsExtensionSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.extensionSettingsComponentType(); + return PhysicsComponentTypes.extensionSettingsComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java index de8ca85f..c35c16a8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java @@ -9,7 +9,6 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import java.util.Objects; import java.util.UUID; @@ -294,7 +293,7 @@ public void setSpringDamping(float springDamping) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.jointComponentType(); + return PhysicsComponentTypes.jointComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java index 80bd6761..3d969ecc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import javax.annotation.Nonnull; /** @@ -57,7 +56,7 @@ public void setRestitution(float restitution) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.materialComponentType(); + return PhysicsComponentTypes.materialComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java new file mode 100644 index 00000000..7b7889ad --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -0,0 +1,237 @@ +package dev.hytalemodding.impulse.core.plugin.components; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Registered Hytale ECS component type handles for PhysicsStore entities. + */ +public final class PhysicsComponentTypes { + + @Nullable + private static ComponentType uuidComponentType; + @Nullable + private static ComponentType spaceComponentType; + @Nullable + private static ComponentType bodyComponentType; + @Nullable + private static ComponentType bodyCommandComponentType; + @Nullable + private static ComponentType dynamicsComponentType; + @Nullable + private static ComponentType colliderComponentType; + @Nullable + private static ComponentType shapeComponentType; + @Nullable + private static ComponentType materialComponentType; + @Nullable + private static ComponentType collisionFilterComponentType; + @Nullable + private static ComponentType jointComponentType; + @Nullable + private static ComponentType targetComponentType; + @Nullable + private static ComponentType terrainColliderComponentType; + @Nullable + private static ComponentType worldCollisionComponentType; + @Nullable + private static ComponentType solverSettingsComponentType; + @Nullable + private static ComponentType visualSyncSettingsComponentType; + @Nullable + private static ComponentType + visualMaterializationSettingsComponentType; + @Nullable + private static ComponentType collisionLodSettingsComponentType; + @Nullable + private static ComponentType extensionSettingsComponentType; + + private PhysicsComponentTypes() { + } + + public static void setUuidComponentType( + @Nonnull ComponentType type) { + uuidComponentType = type; + } + + public static void setSpaceComponentType( + @Nonnull ComponentType type) { + spaceComponentType = type; + } + + public static void setBodyComponentType( + @Nonnull ComponentType type) { + bodyComponentType = type; + } + + public static void setBodyCommandComponentType( + @Nonnull ComponentType type) { + bodyCommandComponentType = type; + } + + public static void setDynamicsComponentType( + @Nonnull ComponentType type) { + dynamicsComponentType = type; + } + + public static void setColliderComponentType( + @Nonnull ComponentType type) { + colliderComponentType = type; + } + + public static void setShapeComponentType( + @Nonnull ComponentType type) { + shapeComponentType = type; + } + + public static void setMaterialComponentType( + @Nonnull ComponentType type) { + materialComponentType = type; + } + + public static void setCollisionFilterComponentType( + @Nonnull ComponentType type) { + collisionFilterComponentType = type; + } + + public static void setJointComponentType( + @Nonnull ComponentType type) { + jointComponentType = type; + } + + public static void setTargetComponentType( + @Nonnull ComponentType type) { + targetComponentType = type; + } + + public static void setTerrainColliderComponentType( + @Nonnull ComponentType type) { + terrainColliderComponentType = type; + } + + public static void setWorldCollisionComponentType( + @Nonnull ComponentType type) { + worldCollisionComponentType = type; + } + + public static void setSolverSettingsComponentType( + @Nonnull ComponentType type) { + solverSettingsComponentType = type; + } + + public static void setVisualSyncSettingsComponentType( + @Nonnull ComponentType type) { + visualSyncSettingsComponentType = type; + } + + public static void setVisualMaterializationSettingsComponentType( + @Nonnull ComponentType type) { + visualMaterializationSettingsComponentType = type; + } + + public static void setCollisionLodSettingsComponentType( + @Nonnull ComponentType type) { + collisionLodSettingsComponentType = type; + } + + public static void setExtensionSettingsComponentType( + @Nonnull ComponentType type) { + extensionSettingsComponentType = type; + } + + @Nonnull + public static ComponentType uuidComponentType() { + return uuidComponentType; + } + + @Nonnull + public static ComponentType spaceComponentType() { + return spaceComponentType; + } + + @Nonnull + public static ComponentType bodyComponentType() { + return bodyComponentType; + } + + @Nonnull + public static ComponentType bodyCommandComponentType() { + return bodyCommandComponentType; + } + + @Nonnull + public static ComponentType dynamicsComponentType() { + return dynamicsComponentType; + } + + @Nonnull + public static ComponentType colliderComponentType() { + return colliderComponentType; + } + + @Nonnull + public static ComponentType shapeComponentType() { + return shapeComponentType; + } + + @Nonnull + public static ComponentType materialComponentType() { + return materialComponentType; + } + + @Nonnull + public static ComponentType collisionFilterComponentType() { + return collisionFilterComponentType; + } + + @Nonnull + public static ComponentType jointComponentType() { + return jointComponentType; + } + + @Nonnull + public static ComponentType targetComponentType() { + return targetComponentType; + } + + @Nonnull + public static ComponentType terrainColliderComponentType() { + return terrainColliderComponentType; + } + + @Nonnull + public static ComponentType worldCollisionComponentType() { + return worldCollisionComponentType; + } + + @Nonnull + public static ComponentType solverSettingsComponentType() { + return solverSettingsComponentType; + } + + @Nonnull + public static ComponentType + visualSyncSettingsComponentType() { + return visualSyncSettingsComponentType; + } + + @Nonnull + public static ComponentType + visualMaterializationSettingsComponentType() { + return visualMaterializationSettingsComponentType; + } + + @Nonnull + public static ComponentType + collisionLodSettingsComponentType() { + return collisionLodSettingsComponentType; + } + + @Nonnull + public static ComponentType + extensionSettingsComponentType() { + return extensionSettingsComponentType; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java index cb00eeb7..de856a23 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java @@ -9,7 +9,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import javax.annotation.Nonnull; @@ -173,7 +172,7 @@ public void setResourceKey(@Nonnull String resourceKey) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.shapeComponentType(); + return PhysicsComponentTypes.shapeComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java index 4fdf2531..7758a141 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import javax.annotation.Nonnull; @@ -138,7 +137,7 @@ public void copyTo(@Nonnull PhysicsSolverSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.solverSettingsComponentType(); + return PhysicsComponentTypes.solverSettingsComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java index 2b5af4de..3f649c3b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import javax.annotation.Nonnull; import org.joml.Vector3f; @@ -74,7 +73,7 @@ public void setGravity(@Nonnull Vector3f gravity) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.spaceComponentType(); + return PhysicsComponentTypes.spaceComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TargetComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TargetComponent.java index fbce374e..0fff31ab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TargetComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TargetComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import javax.annotation.Nonnull; import lombok.Getter; import lombok.Setter; @@ -123,7 +122,7 @@ public void setAngularVelocity(@Nonnull Vector3f angularVelocity) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.targetComponentType(); + return PhysicsComponentTypes.targetComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java index 2df520e0..3c3fec72 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import lombok.Getter; import lombok.Setter; import java.util.Objects; @@ -133,7 +132,7 @@ public void setPayloadResourceKey(@Nonnull String payloadResourceKey) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.terrainColliderComponentType(); + return PhysicsComponentTypes.terrainColliderComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/UuidComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/UuidComponent.java index 36da32f8..c88e4343 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/UuidComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/UuidComponent.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -47,7 +46,7 @@ public void setUuid(@Nonnull UUID uuid) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.uuidComponentType(); + return PhysicsComponentTypes.uuidComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java index 9f2438e0..85c543db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import java.util.Objects; @@ -211,7 +210,7 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.visualMaterializationSettingsComponentType(); + return PhysicsComponentTypes.visualMaterializationSettingsComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java index 9452bab4..bfa9ba30 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; @@ -268,7 +267,7 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.visualSyncSettingsComponentType(); + return PhysicsComponentTypes.visualSyncSettingsComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java index 979a71e9..ece1491f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsStoreTypes; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; @@ -219,7 +218,7 @@ public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsStoreTypes.worldCollisionComponentType(); + return PhysicsComponentTypes.worldCollisionComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java deleted file mode 100644 index 18a6a6a6..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreTypes.java +++ /dev/null @@ -1,479 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; - -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Registered Hytale ECS type handles for the authoritative PhysicsStore. - */ -public final class PhysicsStoreTypes { - - @Nullable - private static ComponentType uuidComponentType; - @Nullable - private static ComponentType spaceComponentType; - @Nullable - private static ComponentType bodyComponentType; - @Nullable - private static ComponentType bodyCommandComponentType; - @Nullable - private static ComponentType dynamicsComponentType; - @Nullable - private static ComponentType colliderComponentType; - @Nullable - private static ComponentType shapeComponentType; - @Nullable - private static ComponentType materialComponentType; - @Nullable - private static ComponentType collisionFilterComponentType; - @Nullable - private static ComponentType jointComponentType; - @Nullable - private static ComponentType targetComponentType; - @Nullable - private static ComponentType terrainColliderComponentType; - @Nullable - private static ComponentType worldCollisionComponentType; - @Nullable - private static ComponentType solverSettingsComponentType; - @Nullable - private static ComponentType visualSyncSettingsComponentType; - @Nullable - private static ComponentType - visualMaterializationSettingsComponentType; - @Nullable - private static ComponentType collisionLodSettingsComponentType; - @Nullable - private static ComponentType extensionSettingsComponentType; - - @Nullable - private static ResourceType runtimeResourceType; - @Nullable - private static ResourceType worldSettingsResourceType; - @Nullable - private static ResourceType stepSchedulerResourceType; - @Nullable - private static ResourceType - spaceCompatibilityIndexResourceType; - @Nullable - private static ResourceType terrainMutationQueueResourceType; - @Nullable - private static ResourceType identityIndexResourceType; - @Nullable - private static ResourceType snapshotResourceType; - @Nullable - private static ResourceType - bodyRegistrationResourceType; - @Nullable - private static ResourceType eventResourceType; - @Nullable - private static ResourceType readQueueResourceType; - @Nullable - private static ResourceType terrainPayloadResourceType; - @Nullable - private static ResourceType worldCollisionIndexResourceType; - @Nullable - private static ResourceType persistentStoreResourceType; - @Nullable - private static ResourceType restoreStatusResourceType; - @Nullable - private static ResourceType profilingResourceType; - @Nullable - private static ResourceType debugResourceType; - - private PhysicsStoreTypes() { - } - - public static void setUuidComponentType( - @Nonnull ComponentType type) { - uuidComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setSpaceComponentType( - @Nonnull ComponentType type) { - spaceComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setBodyComponentType( - @Nonnull ComponentType type) { - bodyComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setBodyCommandComponentType( - @Nonnull ComponentType type) { - bodyCommandComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setDynamicsComponentType( - @Nonnull ComponentType type) { - dynamicsComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setColliderComponentType( - @Nonnull ComponentType type) { - colliderComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setShapeComponentType( - @Nonnull ComponentType type) { - shapeComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setMaterialComponentType( - @Nonnull ComponentType type) { - materialComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setCollisionFilterComponentType( - @Nonnull ComponentType type) { - collisionFilterComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setJointComponentType( - @Nonnull ComponentType type) { - jointComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setTargetComponentType( - @Nonnull ComponentType type) { - targetComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setTerrainColliderComponentType( - @Nonnull ComponentType type) { - terrainColliderComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setWorldCollisionComponentType( - @Nonnull ComponentType type) { - worldCollisionComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setSolverSettingsComponentType( - @Nonnull ComponentType type) { - solverSettingsComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setVisualSyncSettingsComponentType( - @Nonnull ComponentType type) { - visualSyncSettingsComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setVisualMaterializationSettingsComponentType( - @Nonnull ComponentType type) { - visualMaterializationSettingsComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setCollisionLodSettingsComponentType( - @Nonnull ComponentType type) { - collisionLodSettingsComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setExtensionSettingsComponentType( - @Nonnull ComponentType type) { - extensionSettingsComponentType = Objects.requireNonNull(type, "type"); - } - - public static void setRuntimeResourceType( - @Nonnull ResourceType type) { - runtimeResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setWorldSettingsResourceType( - @Nonnull ResourceType type) { - worldSettingsResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setStepSchedulerResourceType( - @Nonnull ResourceType type) { - stepSchedulerResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setSpaceCompatibilityIndexResourceType( - @Nonnull ResourceType type) { - spaceCompatibilityIndexResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setTerrainMutationQueueResourceType( - @Nonnull ResourceType type) { - terrainMutationQueueResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setIdentityIndexResourceType( - @Nonnull ResourceType type) { - identityIndexResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setSnapshotResourceType( - @Nonnull ResourceType type) { - snapshotResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setBodyRegistrationResourceType( - @Nonnull ResourceType type) { - bodyRegistrationResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setEventResourceType( - @Nonnull ResourceType type) { - eventResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setReadQueueResourceType( - @Nonnull ResourceType type) { - readQueueResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setTerrainPayloadResourceType( - @Nonnull ResourceType type) { - terrainPayloadResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setWorldCollisionIndexResourceType( - @Nonnull ResourceType type) { - worldCollisionIndexResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setPersistentStoreResourceType( - @Nonnull ResourceType type) { - persistentStoreResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setRestoreStatusResourceType( - @Nonnull ResourceType type) { - restoreStatusResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setProfilingResourceType( - @Nonnull ResourceType type) { - profilingResourceType = Objects.requireNonNull(type, "type"); - } - - public static void setDebugResourceType( - @Nonnull ResourceType type) { - debugResourceType = Objects.requireNonNull(type, "type"); - } - - @Nonnull - public static ComponentType uuidComponentType() { - return require(uuidComponentType, "UuidComponent"); - } - - @Nonnull - public static ComponentType spaceComponentType() { - return require(spaceComponentType, "SpaceComponent"); - } - - @Nonnull - public static ComponentType bodyComponentType() { - return require(bodyComponentType, "BodyComponent"); - } - - @Nonnull - public static ComponentType bodyCommandComponentType() { - return require(bodyCommandComponentType, "BodyCommandComponent"); - } - - @Nonnull - public static ComponentType dynamicsComponentType() { - return require(dynamicsComponentType, "DynamicsComponent"); - } - - @Nonnull - public static ComponentType colliderComponentType() { - return require(colliderComponentType, "ColliderComponent"); - } - - @Nonnull - public static ComponentType shapeComponentType() { - return require(shapeComponentType, "ShapeComponent"); - } - - @Nonnull - public static ComponentType materialComponentType() { - return require(materialComponentType, "MaterialComponent"); - } - - @Nonnull - public static ComponentType collisionFilterComponentType() { - return require(collisionFilterComponentType, "CollisionFilterComponent"); - } - - @Nonnull - public static ComponentType jointComponentType() { - return require(jointComponentType, "JointComponent"); - } - - @Nonnull - public static ComponentType targetComponentType() { - return require(targetComponentType, "TargetComponent"); - } - - @Nonnull - public static ComponentType terrainColliderComponentType() { - return require(terrainColliderComponentType, "TerrainColliderComponent"); - } - - @Nonnull - public static ComponentType worldCollisionComponentType() { - return require(worldCollisionComponentType, "WorldCollisionComponent"); - } - - @Nonnull - public static ComponentType solverSettingsComponentType() { - return require(solverSettingsComponentType, "SolverSettingsComponent"); - } - - @Nonnull - public static ComponentType - visualSyncSettingsComponentType() { - return require(visualSyncSettingsComponentType, "VisualSyncSettingsComponent"); - } - - @Nonnull - public static ComponentType - visualMaterializationSettingsComponentType() { - return require(visualMaterializationSettingsComponentType, - "VisualMaterializationSettingsComponent"); - } - - @Nonnull - public static ComponentType - collisionLodSettingsComponentType() { - return require(collisionLodSettingsComponentType, "CollisionLodSettingsComponent"); - } - - @Nonnull - public static ComponentType - extensionSettingsComponentType() { - return require(extensionSettingsComponentType, "ExtensionSettingsComponent"); - } - - @Nonnull - public static ResourceType runtimeResourceType() { - return require(runtimeResourceType, "PhysicsRuntimeResource"); - } - - @Nonnull - public static ResourceType worldSettingsResourceType() { - return require(worldSettingsResourceType, "PhysicsWorldSettingsResource"); - } - - @Nonnull - public static ResourceType stepSchedulerResourceType() { - return require(stepSchedulerResourceType, "PhysicsStepSchedulerResource"); - } - - @Nonnull - public static ResourceType - spaceCompatibilityIndexResourceType() { - return require(spaceCompatibilityIndexResourceType, "PhysicsSpaceCompatibilityIndexResource"); - } - - @Nonnull - public static ResourceType terrainMutationQueueResourceType() { - return require(terrainMutationQueueResourceType, "PhysicsTerrainMutationQueueResource"); - } - - @Nonnull - public static ResourceType identityIndexResourceType() { - return require(identityIndexResourceType, "PhysicsIdentityIndexResource"); - } - - @Nonnull - public static ResourceType snapshotResourceType() { - return require(snapshotResourceType, "PhysicsSnapshotResource"); - } - - @Nonnull - public static ResourceType - bodyRegistrationResourceType() { - return require(bodyRegistrationResourceType, "PhysicsBodyRegistrationResource"); - } - - @Nonnull - public static ResourceType eventResourceType() { - return require(eventResourceType, "PhysicsEventResource"); - } - - @Nonnull - public static ResourceType readQueueResourceType() { - return require(readQueueResourceType, "PhysicsStoreReadQueueResource"); - } - - @Nonnull - public static ResourceType terrainPayloadResourceType() { - return require(terrainPayloadResourceType, "PhysicsTerrainPayloadResource"); - } - - @Nonnull - public static ResourceType worldCollisionIndexResourceType() { - return require(worldCollisionIndexResourceType, "PhysicsWorldCollisionIndexResource"); - } - - @Nonnull - public static ResourceType persistentStoreResourceType() { - return require(persistentStoreResourceType, "PersistentPhysicsStoreResource"); - } - - @Nonnull - public static ResourceType restoreStatusResourceType() { - return require(restoreStatusResourceType, "PhysicsRestoreStatusResource"); - } - - @Nonnull - public static ResourceType profilingResourceType() { - return require(profilingResourceType, "PhysicsProfilingResource"); - } - - @Nonnull - public static ResourceType debugResourceType() { - return require(debugResourceType, "PhysicsDebugResource"); - } - - @Nonnull - private static T require(@Nullable T type, @Nonnull String name) { - if (type == null) { - throw new IllegalStateException("PhysicsStore " + name + " is not registered"); - } - return type; - } -} diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 820f6cd2..01db8c72 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -6,17 +6,17 @@ exports dev.hytalemodding.impulse.core.plugin.body; exports dev.hytalemodding.impulse.core.plugin.codec; + exports dev.hytalemodding.impulse.core.plugin.components; exports dev.hytalemodding.impulse.core.plugin.events; exports dev.hytalemodding.impulse.core.plugin.modules.control; exports dev.hytalemodding.impulse.core.plugin.modules.worldcollision; exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physicsstore; - exports dev.hytalemodding.impulse.core.plugin.physicsstore.components; - exports dev.hytalemodding.impulse.core.plugin.physicsstore.projection; - exports dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots; + exports dev.hytalemodding.impulse.core.plugin.projection; exports dev.hytalemodding.impulse.core.plugin.resources; exports dev.hytalemodding.impulse.core.plugin.settings; exports dev.hytalemodding.impulse.core.plugin.simulation; exports dev.hytalemodding.impulse.core.plugin.simulation.view; exports dev.hytalemodding.impulse.core.plugin.snapshot; + exports dev.hytalemodding.impulse.core.plugin.snapshots; } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java index 77cac9d2..40c2cb9b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java @@ -5,7 +5,7 @@ import java.util.Objects; import java.util.UUID; -final class BlockBodyBatchBuilder { +public final class BlockBodyBatchBuilder { private static final int POSITION_STRIDE = 3; From 9fc8bcbdbef2b97556a2b84c3b6fdf92174d9c30 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 08:36:39 +0200 Subject: [PATCH 302/534] refactor(core): flatten physics store internals Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 2 +- ...tachedStreamingBenchmarkCrucibleTests.java | 2 +- .../PhysicsStoreTerrainMutationCache.java | 2 +- .../PhysicsStoreTerrainMutations.java | 8 +-- ...sStoreWorldCollisionStreamingResource.java | 2 +- ...sicsStoreWorldCollisionProducerSystem.java | 6 +-- .../persistence/PersistentBodyDto.java | 2 +- .../PersistentBodyRuntimeStateCodec.java | 2 +- .../PersistentBodyRuntimeStateDto.java | 2 +- .../persistence/PersistentColliderDto.java | 2 +- .../persistence/PersistentJointDto.java | 2 +- .../persistence/PersistentMaterialDto.java | 2 +- .../PersistentPhysicsStorePreflight.java | 2 +- .../PersistentPhysicsStoreResource.java | 4 +- .../persistence/PersistentShapeDto.java | 2 +- .../persistence/PersistentSpaceDto.java | 2 +- .../PersistentTerrainColliderDto.java | 2 +- .../PhysicsStorePersistenceValidation.java | 2 +- .../PhysicsStoreRuntimeCleaner.java | 10 ++-- .../PhysicsStoreTopologyMutations.java | 6 +-- .../PhysicsBodyRegistrationResource.java | 1 + .../resources/PhysicsDebugResource.java | 1 + .../PhysicsIdentityIndexResource.java | 1 + .../resources/PhysicsProfilingResource.java | 1 + .../resources/PhysicsRuntimeResource.java | 1 + .../resources/PhysicsSnapshotResource.java | 1 + ...hysicsSpaceCompatibilityIndexResource.java | 1 + .../PhysicsStepSchedulerResource.java | 1 + .../PhysicsStoreRegistration.java | 54 +++++++++---------- .../resources/PhysicsEventResource.java | 2 +- .../resources/PhysicsResourceTypes.java | 20 +++++-- .../PhysicsRestoreStatusResource.java | 2 +- .../PhysicsStoreReadQueueResource.java | 2 +- .../PhysicsTerrainMutationQueueResource.java | 4 +- .../PhysicsTerrainPayloadResource.java | 4 +- .../PhysicsWorldCollisionIndexResource.java | 2 +- .../PhysicsWorldRuntimeResource.java | 8 +-- .../PhysicsWorldSettingsResource.java | 2 +- .../systems/BodyBindingSystem.java | 4 +- .../systems/BodyCommandApplicationSystem.java | 4 +- .../systems/ColliderBindingSystem.java | 2 +- .../CompletedStepPublicationSystem.java | 8 +-- .../systems/IdentityIndexSystem.java | 2 +- .../systems/JointBindingSystem.java | 4 +- .../systems/PersistenceCaptureSystem.java | 22 ++++---- .../systems/PersistenceHydrationSystem.java | 24 ++++----- .../systems/PhysicsStoreQueuedReadSystem.java | 6 +-- .../systems/PhysicsStoreSystemSupport.java | 2 +- .../systems/SpaceBindingSystem.java | 6 +-- .../SpaceSettingsApplicationSystem.java | 4 +- .../systems/StaleBodyRemovalSystem.java | 4 +- .../systems/StepSubmissionSystem.java | 6 +-- .../systems/TargetBindingSystem.java | 4 +- .../systems/TerrainColliderBindingSystem.java | 14 ++--- .../systems/TerrainMutationDrainSystem.java | 12 ++--- .../systems/WorldCollisionIndexSystem.java | 6 +-- .../debug/PhysicsStoreDebugQueries.java | 4 +- .../PhysicsStoreEventPublicationSystem.java | 2 +- .../PhysicsBodyAttachmentIndexSystem.java | 13 +++-- .../systems/sync/PhysicsSyncSystem.java | 1 - .../terrain/TerrainColliderMutation.java | 2 +- .../terrain/TerrainColliderPayload.java | 2 +- .../persistence/PhysicsPersistence.java | 4 +- .../plugin/physicsstore/PhysicsThreading.java | 2 +- 64 files changed, 176 insertions(+), 160 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentBodyDto.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentBodyRuntimeStateCodec.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentBodyRuntimeStateDto.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentColliderDto.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentJointDto.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentMaterialDto.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentPhysicsStorePreflight.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentPhysicsStoreResource.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentShapeDto.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentSpaceDto.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PersistentTerrainColliderDto.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/persistence/PhysicsStorePersistenceValidation.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/registration/PhysicsStoreRegistration.java (84%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsEventResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsResourceTypes.java (85%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsRestoreStatusResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsStoreReadQueueResource.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsTerrainMutationQueueResource.java (92%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsTerrainPayloadResource.java (90%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsWorldCollisionIndexResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsWorldSettingsResource.java (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/BodyBindingSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/BodyCommandApplicationSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/ColliderBindingSystem.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/CompletedStepPublicationSystem.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/IdentityIndexSystem.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/JointBindingSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/PersistenceCaptureSystem.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/PersistenceHydrationSystem.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/PhysicsStoreQueuedReadSystem.java (87%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/PhysicsStoreSystemSupport.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/SpaceBindingSystem.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/SpaceSettingsApplicationSystem.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/StaleBodyRemovalSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/StepSubmissionSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/TargetBindingSystem.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/TerrainColliderBindingSystem.java (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/TerrainMutationDrainSystem.java (93%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/systems/WorldCollisionIndexSystem.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/terrain/TerrainColliderMutation.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/terrain/TerrainColliderPayload.java (97%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 5a5abb78..ec7ed351 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -27,7 +27,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.registration.PhysicsStoreRegistration; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 5727eff3..29b1ecf4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java index c0d95714..ccf24768 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.MissingSectionReason; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2LongMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java index 66e7836f..e53a4403 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java @@ -2,10 +2,10 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.BoxPayload; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.TerrainNeighbor; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.TerrainNeighbor; import java.util.List; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java index 801e318d..95019309 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index 82c4e83d..12433591 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -25,10 +25,10 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java index 1b389c20..f77f2356 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateCodec.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateCodec.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java index f534f3a5..c2fb31b8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateCodec.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.ExtraInfo; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java index 3bf239df..76404b53 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentBodyRuntimeStateDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentColliderDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentColliderDto.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentColliderDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentColliderDto.java index 25971a7f..c0f38484 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentColliderDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentColliderDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java index 8e66b2c3..0cd320f3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentJointDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentMaterialDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentMaterialDto.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentMaterialDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentMaterialDto.java index 093955d0..169a5c8b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentMaterialDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentMaterialDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index 54aa2620..76c7f1c1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java index a6500e6e..ecdea439 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import java.util.Arrays; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentShapeDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentShapeDto.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentShapeDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentShapeDto.java index a25443b0..7f57dccb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentShapeDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentShapeDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index d8f8a128..ffa0a7ae 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentTerrainColliderDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentTerrainColliderDto.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentTerrainColliderDto.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentTerrainColliderDto.java index 8068d377..821a43f6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentTerrainColliderDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentTerrainColliderDto.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStorePersistenceValidation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStorePersistenceValidation.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStorePersistenceValidation.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStorePersistenceValidation.java index 7f5c077f..5aa1ed48 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStorePersistenceValidation.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStorePersistenceValidation.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import com.hypixel.hytale.codec.schema.SchemaContext; import com.hypixel.hytale.codec.schema.config.Schema; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index c689777d..6ef35ae6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -5,15 +5,15 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 258e1668..75569a1c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -7,13 +7,13 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java index 275ecfcf..2e17c4e0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java index d002dc7f..73d0ff50 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import lombok.Getter; import lombok.Setter; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java index 81d3fd3a..2e480dcc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java index ef8edfd6..097a5624 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import java.util.Objects; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java index 7741c46c..2c44c9e7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java index 3adf00ab..3dc2e8ee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java index 12bca1bb..5147d572 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.SpaceId; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java index 34d818af..70682841 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java similarity index 84% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 021aaebe..39a1b90d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.registration; +package dev.hytalemodding.impulse.core.internal.registration; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.Resource; @@ -8,41 +8,41 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.TickDecision; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyBindingSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.BodyCommandApplicationSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.ColliderBindingSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.CompletedStepPublicationSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.IdentityIndexSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.JointBindingSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceCaptureSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PersistenceHydrationSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.PhysicsStoreQueuedReadSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainMutationDrainSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceBindingSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.SpaceSettingsApplicationSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StepSubmissionSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.StaleBodyRemovalSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TargetBindingSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.TerrainColliderBindingSystem; -import dev.hytalemodding.impulse.core.internal.physicsstore.systems.WorldCollisionIndexSystem; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.ColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.CompletedStepPublicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.JointBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.PersistenceCaptureSystem; +import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreQueuedReadSystem; +import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; +import dev.hytalemodding.impulse.core.internal.systems.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; +import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; +import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.WorldCollisionIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEventResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEventResource.java index 05bea534..f80a42f0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsEventResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEventResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsResourceTypes.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index e2b5b20a..be9b65d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -1,8 +1,15 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -44,7 +51,8 @@ public final class PhysicsResourceTypes { @Nullable private static ResourceType profilingResourceType; @Nullable - private static ResourceType debugResourceType; + private static ResourceType debugResourceType; private PhysicsResourceTypes() { } @@ -125,7 +133,8 @@ public static void setProfilingResourceType( } public static void setDebugResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { debugResourceType = type; } @@ -207,7 +216,8 @@ public static ResourceType profilingReso } @Nonnull - public static ResourceType debugResourceType() { + public static ResourceType debugResourceType() { return debugResourceType; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java index 65b08de4..22f32b92 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRestoreStatusResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreReadQueueResource.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreReadQueueResource.java index 72f0eb3b..209f5966 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreReadQueueResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java similarity index 92% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java index d31d4ec5..5cd81700 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java @@ -1,9 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java similarity index 90% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java index b4822583..cc6ad309 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsTerrainPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java @@ -1,9 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java index e245e935..33122e2f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 76dcfe12..629cb86e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -21,14 +21,14 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsResource.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsResource.java index 6650548f..ca356ad4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsWorldSettingsResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java index 9572dd5f..289b65d6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index bd6805cc..a32ec99e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ColliderBindingSystem.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ColliderBindingSystem.java index 2db4894e..b05a79e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/ColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ColliderBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 944cc57a..8a72b7a5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -14,9 +14,9 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource.BodyRegistrationPublication; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; @@ -24,7 +24,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java index 93891699..8f2be40f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java index bf31329c..a8cc79a8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index 2ccbe1b2..a3cb6dd0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -10,16 +10,16 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyRuntimeStateDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentColliderDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentJointDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentMaterialDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentShapeDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentTerrainColliderDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyRuntimeStateDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentJointDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentMaterialDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentTerrainColliderDto; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index b742eede..d2d77c39 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Holder; @@ -7,17 +7,17 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentBodyRuntimeStateDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentColliderDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentJointDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentMaterialDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStorePreflight; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentShapeDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentTerrainColliderDto; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyRuntimeStateDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentJointDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentMaterialDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStorePreflight; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentTerrainColliderDto; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreQueuedReadSystem.java similarity index 87% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreQueuedReadSystem.java index 13ae057d..9bd117be 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreQueuedReadSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreQueuedReadSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; @@ -6,8 +6,8 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java index d9103086..5d4a25d0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.Component; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java index 4aef352b..c8a72308 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -16,10 +16,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java index 05b3cbb2..30636499 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index 63a913ee..101aa372 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index 62ce1f45..dbb18467 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; @@ -14,13 +14,13 @@ import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java index 9a4db8cf..9a2bd83c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -12,7 +12,7 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java index 205b5a96..45ce0cdc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -17,13 +17,13 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.BoxPayload; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload.TerrainNeighbor; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.TerrainNeighbor; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java index ef2014d8..d2916368 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; @@ -9,11 +9,11 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/WorldCollisionIndexSystem.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/WorldCollisionIndexSystem.java index 73601948..faa5bb25 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/WorldCollisionIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/WorldCollisionIndexSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.systems; +package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -10,8 +10,8 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 427af392..b0ce074e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -14,8 +14,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index 16ab57df..2f7cd17d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -10,7 +10,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource.StepSample; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index c7fa94c9..c5c6d29c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -78,10 +78,10 @@ private static void updateAttachment(@Nonnull Ref ref, ref, newGeneratedProxy); } - if (oldGeneratedProxy && (!sameUuid || !sameBodyRef || !newGeneratedProxy)) { + if (oldGeneratedProxy) { resource.clearGeneratedVisualProxy(oldUuid, oldBodyRef, ref); } - if (newGeneratedProxy && (!sameUuid || !sameBodyRef || !oldGeneratedProxy)) { + if (newGeneratedProxy) { resource.setGeneratedVisualProxy(newUuid, newBodyRef, ref); } } @@ -113,11 +113,10 @@ private static void unregisterAttachment(@Nonnull Ref ref, private static boolean sameRef(@Nullable Ref first, @Nullable Ref second) { return first == second - || (first != null - && second != null - && first.getStore() != null - && first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); + || first != null + && second != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index b56dc4fa..2827aaf6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -190,7 +190,6 @@ private static boolean sameRef(@Nullable Ref first, return first == second || (first != null && second != null - && first.getStore() != null && first.getStore() == second.getStore() && first.getIndex() == second.getIndex()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderMutation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderMutation.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderMutation.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderMutation.java index 569e19f1..a6d588a4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderMutation.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderMutation.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.terrain; +package dev.hytalemodding.impulse.core.internal.terrain; import java.nio.charset.StandardCharsets; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderPayload.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderPayload.java index 69706700..1abb65d4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/terrain/TerrainColliderPayload.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderPayload.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.terrain; +package dev.hytalemodding.impulse.core.internal.terrain; import java.util.Arrays; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index bef13fc5..60e20811 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -5,8 +5,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java index 7a141755..56ecf600 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java @@ -6,7 +6,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; From 1b54e08cec75d487729574dfa6ca33ced9b84f36 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 08:42:29 +0200 Subject: [PATCH 303/534] refactor(core): simplify visual runtime refs Signed-off-by: Blovien --- .../resources/PhysicsVisualRuntime.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index 409634d4..71f3070c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -408,9 +408,9 @@ public void clearBodyRuntimeState(@Nonnull UUID bodyUuid, } public void clear() { - List> staleRefs = new ArrayList<>(); + List> staleRefs; synchronized (this) { - staleRefs.addAll(generatedVisualProxies.values()); + staleRefs = new ArrayList<>(generatedVisualProxies.values()); for (Set> attachments : bodyAttachments.values()) { for (Ref attachment : attachments) { if (attachment != null) { @@ -423,9 +423,7 @@ public void clear() { } for (var entry : generatedVisualProxiesByRowIndex.int2ObjectEntrySet()) { Ref proxy = entry.getValue().proxy(); - if (proxy != null) { - staleRefs.add(proxy); - } + staleRefs.add(proxy); } bodyAttachments.clear(); bodyAttachmentsByRowIndex.clear(); @@ -597,7 +595,7 @@ private Ref liveGeneratedVisualProxy(@Nonnull Ref bod return null; } Ref proxy = row.proxy(); - if (proxy != null && proxy.isValid()) { + if (proxy.isValid()) { return proxy; } generatedVisualProxiesByRowIndex.remove(rowIndex); @@ -658,11 +656,10 @@ private void cleanSyncStates(@Nonnull Collection> refs) { private static boolean sameRef(@Nullable Ref first, @Nullable Ref second) { return first == second - || (first != null - && second != null - && first.getStore() != null - && first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); + || first != null + && second != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); } private record BodyAttachmentRefs(@Nonnull Ref bodyRef, From d6f526df00c04ef5046ec43f0b0307ff459652b9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 08:47:34 +0200 Subject: [PATCH 304/534] refactor(core): centralize physics type registration Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 96 +---------- .../PhysicsStoreRegistration.java | 51 +----- .../resources/PhysicsResourceTypes.java | 131 ++++++-------- .../components/PhysicsComponentTypes.java | 163 ++++++++---------- 4 files changed, 129 insertions(+), 312 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index ec7ed351..7af02584 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -35,25 +35,7 @@ import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; -import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -117,7 +99,7 @@ protected void setup() { PhysicsStoreEarlyPluginProbe.requireAvailable(); ComponentRegistryProxy physicsStoreRegistry = PhysicsStoreRegistration.physicsStoreRegistry(this); - registerPhysicsStoreComponents(physicsStoreRegistry); + PhysicsComponentTypes.registerComponentTypes(physicsStoreRegistry); PhysicsStoreRegistration.register(physicsStoreRegistry); ImpulseSubPluginRegistration.register(this); discoverBackends(); @@ -225,82 +207,6 @@ private String getAvailableBackendIds() { return ids.toString(); } - private static void registerPhysicsStoreComponents( - @Nonnull ComponentRegistryProxy physicsRegistry) { - PhysicsComponentTypes.setUuidComponentType(physicsRegistry.registerComponent( - UuidComponent.class, - "Uuid", - UuidComponent.CODEC)); - PhysicsComponentTypes.setSpaceComponentType(physicsRegistry.registerComponent( - SpaceComponent.class, - "Space", - SpaceComponent.CODEC)); - PhysicsComponentTypes.setBodyComponentType(physicsRegistry.registerComponent( - BodyComponent.class, - "Body", - BodyComponent.CODEC)); - PhysicsComponentTypes.setBodyCommandComponentType(physicsRegistry.registerComponent( - BodyCommandComponent.class, - "BodyCommand", - BodyCommandComponent.CODEC)); - PhysicsComponentTypes.setDynamicsComponentType(physicsRegistry.registerComponent( - DynamicsComponent.class, - "Dynamics", - DynamicsComponent.CODEC)); - PhysicsComponentTypes.setColliderComponentType(physicsRegistry.registerComponent( - ColliderComponent.class, - "Collider", - ColliderComponent.CODEC)); - PhysicsComponentTypes.setShapeComponentType(physicsRegistry.registerComponent( - ShapeComponent.class, - "Shape", - ShapeComponent.CODEC)); - PhysicsComponentTypes.setMaterialComponentType(physicsRegistry.registerComponent( - MaterialComponent.class, - "Material", - MaterialComponent.CODEC)); - PhysicsComponentTypes.setCollisionFilterComponentType(physicsRegistry.registerComponent( - CollisionFilterComponent.class, - "CollisionFilter", - CollisionFilterComponent.CODEC)); - PhysicsComponentTypes.setJointComponentType(physicsRegistry.registerComponent( - JointComponent.class, - "Joint", - JointComponent.CODEC)); - PhysicsComponentTypes.setTargetComponentType(physicsRegistry.registerComponent( - TargetComponent.class, - "Target", - TargetComponent.CODEC)); - PhysicsComponentTypes.setTerrainColliderComponentType(physicsRegistry.registerComponent( - TerrainColliderComponent.class, - "TerrainCollider", - TerrainColliderComponent.CODEC)); - PhysicsComponentTypes.setWorldCollisionComponentType(physicsRegistry.registerComponent( - WorldCollisionComponent.class, - "WorldCollision", - WorldCollisionComponent.CODEC)); - PhysicsComponentTypes.setSolverSettingsComponentType(physicsRegistry.registerComponent( - SolverSettingsComponent.class, - "SolverSettings", - SolverSettingsComponent.CODEC)); - PhysicsComponentTypes.setVisualSyncSettingsComponentType(physicsRegistry.registerComponent( - VisualSyncSettingsComponent.class, - "VisualSyncSettings", - VisualSyncSettingsComponent.CODEC)); - PhysicsComponentTypes.setVisualMaterializationSettingsComponentType( - physicsRegistry.registerComponent(VisualMaterializationSettingsComponent.class, - "VisualMaterializationSettings", - VisualMaterializationSettingsComponent.CODEC)); - PhysicsComponentTypes.setCollisionLodSettingsComponentType(physicsRegistry.registerComponent( - CollisionLodSettingsComponent.class, - "CollisionLodSettings", - CollisionLodSettingsComponent.CODEC)); - PhysicsComponentTypes.setExtensionSettingsComponentType(physicsRegistry.registerComponent( - ExtensionSettingsComponent.class, - "ExtensionSettings", - ExtensionSettingsComponent.CODEC)); - } - private void registerEntityStoreComponents() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); bodyAttachmentComponentType = entityRegistry.registerComponent( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 39a1b90d..a069627b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -10,7 +10,6 @@ import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; @@ -71,55 +70,7 @@ public static void register(@Nonnull ComponentRegistryProxy regist PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); PhysicsStoreHooks.registerTickGate(STEP_TICK_GATE); - PhysicsResourceTypes.setRuntimeResourceType(registry.registerResource( - PhysicsRuntimeResource.class, - PhysicsRuntimeResource::new)); - PhysicsResourceTypes.setWorldSettingsResourceType(registry.registerResource( - PhysicsWorldSettingsResource.class, - PhysicsWorldSettingsResource::new)); - PhysicsResourceTypes.setStepSchedulerResourceType(registry.registerResource( - PhysicsStepSchedulerResource.class, - PhysicsStepSchedulerResource::new)); - PhysicsResourceTypes.setSpaceCompatibilityIndexResourceType(registry.registerResource( - PhysicsSpaceCompatibilityIndexResource.class, - PhysicsSpaceCompatibilityIndexResource::new)); - PhysicsResourceTypes.setTerrainMutationQueueResourceType(registry.registerResource( - PhysicsTerrainMutationQueueResource.class, - PhysicsTerrainMutationQueueResource::new)); - PhysicsResourceTypes.setIdentityIndexResourceType(registry.registerResource( - PhysicsIdentityIndexResource.class, - PhysicsIdentityIndexResource::new)); - PhysicsResourceTypes.setSnapshotResourceType(registry.registerResource( - PhysicsSnapshotResource.class, - PhysicsSnapshotResource::new)); - PhysicsResourceTypes.setBodyRegistrationResourceType(registry.registerResource( - PhysicsBodyRegistrationResource.class, - PhysicsBodyRegistrationResource::new)); - PhysicsResourceTypes.setEventResourceType(registry.registerResource( - PhysicsEventResource.class, - PhysicsEventResource::new)); - PhysicsResourceTypes.setReadQueueResourceType(registry.registerResource( - PhysicsStoreReadQueueResource.class, - PhysicsStoreReadQueueResource::new)); - PhysicsResourceTypes.setTerrainPayloadResourceType(registry.registerResource( - PhysicsTerrainPayloadResource.class, - PhysicsTerrainPayloadResource::new)); - PhysicsResourceTypes.setWorldCollisionIndexResourceType(registry.registerResource( - PhysicsWorldCollisionIndexResource.class, - PhysicsWorldCollisionIndexResource::new)); - PhysicsResourceTypes.setPersistentStoreResourceType(registry.registerResource( - PersistentPhysicsStoreResource.class, - "PersistentPhysicsStore", - PersistentPhysicsStoreResource.CODEC)); - PhysicsResourceTypes.setRestoreStatusResourceType(registry.registerResource( - PhysicsRestoreStatusResource.class, - PhysicsRestoreStatusResource::new)); - PhysicsResourceTypes.setProfilingResourceType(registry.registerResource( - PhysicsProfilingResource.class, - PhysicsProfilingResource::new)); - PhysicsResourceTypes.setDebugResourceType(registry.registerResource( - PhysicsDebugResource.class, - PhysicsDebugResource::new)); + PhysicsResourceTypes.registerResourceTypes(registry); registry.registerSystem(new PersistenceHydrationSystem()); registry.registerSystem(new TerrainMutationDrainSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index be9b65d9..0b7124f1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.resources; +import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; @@ -57,85 +58,57 @@ public final class PhysicsResourceTypes { private PhysicsResourceTypes() { } - public static void setRuntimeResourceType( - @Nonnull ResourceType type) { - runtimeResourceType = type; - } - - public static void setWorldSettingsResourceType( - @Nonnull ResourceType type) { - worldSettingsResourceType = type; - } - - public static void setStepSchedulerResourceType( - @Nonnull ResourceType type) { - stepSchedulerResourceType = type; - } - - public static void setSpaceCompatibilityIndexResourceType( - @Nonnull ResourceType type) { - spaceCompatibilityIndexResourceType = type; - } - - public static void setTerrainMutationQueueResourceType( - @Nonnull ResourceType type) { - terrainMutationQueueResourceType = type; - } - - public static void setIdentityIndexResourceType( - @Nonnull ResourceType type) { - identityIndexResourceType = type; - } - - public static void setSnapshotResourceType( - @Nonnull ResourceType type) { - snapshotResourceType = type; - } - - public static void setBodyRegistrationResourceType( - @Nonnull ResourceType type) { - bodyRegistrationResourceType = type; - } - - public static void setEventResourceType( - @Nonnull ResourceType type) { - eventResourceType = type; - } - - public static void setReadQueueResourceType( - @Nonnull ResourceType type) { - readQueueResourceType = type; - } - - public static void setTerrainPayloadResourceType( - @Nonnull ResourceType type) { - terrainPayloadResourceType = type; - } - - public static void setWorldCollisionIndexResourceType( - @Nonnull ResourceType type) { - worldCollisionIndexResourceType = type; - } - - public static void setPersistentStoreResourceType( - @Nonnull ResourceType type) { - persistentStoreResourceType = type; - } - - public static void setRestoreStatusResourceType( - @Nonnull ResourceType type) { - restoreStatusResourceType = type; - } - - public static void setProfilingResourceType( - @Nonnull ResourceType type) { - profilingResourceType = type; - } - - public static void setDebugResourceType( - @Nonnull ResourceType type) { - debugResourceType = type; + public static void registerResourceTypes( + @Nonnull ComponentRegistryProxy registry) { + runtimeResourceType = registry.registerResource( + PhysicsRuntimeResource.class, + PhysicsRuntimeResource::new); + worldSettingsResourceType = registry.registerResource( + PhysicsWorldSettingsResource.class, + PhysicsWorldSettingsResource::new); + stepSchedulerResourceType = registry.registerResource( + PhysicsStepSchedulerResource.class, + PhysicsStepSchedulerResource::new); + spaceCompatibilityIndexResourceType = registry.registerResource( + PhysicsSpaceCompatibilityIndexResource.class, + PhysicsSpaceCompatibilityIndexResource::new); + terrainMutationQueueResourceType = registry.registerResource( + PhysicsTerrainMutationQueueResource.class, + PhysicsTerrainMutationQueueResource::new); + identityIndexResourceType = registry.registerResource( + PhysicsIdentityIndexResource.class, + PhysicsIdentityIndexResource::new); + snapshotResourceType = registry.registerResource( + PhysicsSnapshotResource.class, + PhysicsSnapshotResource::new); + bodyRegistrationResourceType = registry.registerResource( + PhysicsBodyRegistrationResource.class, + PhysicsBodyRegistrationResource::new); + eventResourceType = registry.registerResource( + PhysicsEventResource.class, + PhysicsEventResource::new); + readQueueResourceType = registry.registerResource( + PhysicsStoreReadQueueResource.class, + PhysicsStoreReadQueueResource::new); + terrainPayloadResourceType = registry.registerResource( + PhysicsTerrainPayloadResource.class, + PhysicsTerrainPayloadResource::new); + worldCollisionIndexResourceType = registry.registerResource( + PhysicsWorldCollisionIndexResource.class, + PhysicsWorldCollisionIndexResource::new); + persistentStoreResourceType = registry.registerResource( + PersistentPhysicsStoreResource.class, + "PersistentPhysicsStore", + PersistentPhysicsStoreResource.CODEC); + restoreStatusResourceType = registry.registerResource( + PhysicsRestoreStatusResource.class, + PhysicsRestoreStatusResource::new); + profilingResourceType = registry.registerResource( + PhysicsProfilingResource.class, + PhysicsProfilingResource::new); + debugResourceType = registry.registerResource( + dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource.class, + dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource::new); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 7b7889ad..72c10db1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.components; +import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import javax.annotation.Nonnull; @@ -51,94 +52,80 @@ public final class PhysicsComponentTypes { private PhysicsComponentTypes() { } - public static void setUuidComponentType( - @Nonnull ComponentType type) { - uuidComponentType = type; - } - - public static void setSpaceComponentType( - @Nonnull ComponentType type) { - spaceComponentType = type; - } - - public static void setBodyComponentType( - @Nonnull ComponentType type) { - bodyComponentType = type; - } - - public static void setBodyCommandComponentType( - @Nonnull ComponentType type) { - bodyCommandComponentType = type; - } - - public static void setDynamicsComponentType( - @Nonnull ComponentType type) { - dynamicsComponentType = type; - } - - public static void setColliderComponentType( - @Nonnull ComponentType type) { - colliderComponentType = type; - } - - public static void setShapeComponentType( - @Nonnull ComponentType type) { - shapeComponentType = type; - } - - public static void setMaterialComponentType( - @Nonnull ComponentType type) { - materialComponentType = type; - } - - public static void setCollisionFilterComponentType( - @Nonnull ComponentType type) { - collisionFilterComponentType = type; - } - - public static void setJointComponentType( - @Nonnull ComponentType type) { - jointComponentType = type; - } - - public static void setTargetComponentType( - @Nonnull ComponentType type) { - targetComponentType = type; - } - - public static void setTerrainColliderComponentType( - @Nonnull ComponentType type) { - terrainColliderComponentType = type; - } - - public static void setWorldCollisionComponentType( - @Nonnull ComponentType type) { - worldCollisionComponentType = type; - } - - public static void setSolverSettingsComponentType( - @Nonnull ComponentType type) { - solverSettingsComponentType = type; - } - - public static void setVisualSyncSettingsComponentType( - @Nonnull ComponentType type) { - visualSyncSettingsComponentType = type; - } - - public static void setVisualMaterializationSettingsComponentType( - @Nonnull ComponentType type) { - visualMaterializationSettingsComponentType = type; - } - - public static void setCollisionLodSettingsComponentType( - @Nonnull ComponentType type) { - collisionLodSettingsComponentType = type; - } - - public static void setExtensionSettingsComponentType( - @Nonnull ComponentType type) { - extensionSettingsComponentType = type; + public static void registerComponentTypes( + @Nonnull ComponentRegistryProxy registry) { + uuidComponentType = registry.registerComponent( + UuidComponent.class, + "Uuid", + UuidComponent.CODEC); + spaceComponentType = registry.registerComponent( + SpaceComponent.class, + "Space", + SpaceComponent.CODEC); + bodyComponentType = registry.registerComponent( + BodyComponent.class, + "Body", + BodyComponent.CODEC); + bodyCommandComponentType = registry.registerComponent( + BodyCommandComponent.class, + "BodyCommand", + BodyCommandComponent.CODEC); + dynamicsComponentType = registry.registerComponent( + DynamicsComponent.class, + "Dynamics", + DynamicsComponent.CODEC); + colliderComponentType = registry.registerComponent( + ColliderComponent.class, + "Collider", + ColliderComponent.CODEC); + shapeComponentType = registry.registerComponent( + ShapeComponent.class, + "Shape", + ShapeComponent.CODEC); + materialComponentType = registry.registerComponent( + MaterialComponent.class, + "Material", + MaterialComponent.CODEC); + collisionFilterComponentType = registry.registerComponent( + CollisionFilterComponent.class, + "CollisionFilter", + CollisionFilterComponent.CODEC); + jointComponentType = registry.registerComponent( + JointComponent.class, + "Joint", + JointComponent.CODEC); + targetComponentType = registry.registerComponent( + TargetComponent.class, + "Target", + TargetComponent.CODEC); + terrainColliderComponentType = registry.registerComponent( + TerrainColliderComponent.class, + "TerrainCollider", + TerrainColliderComponent.CODEC); + worldCollisionComponentType = registry.registerComponent( + WorldCollisionComponent.class, + "WorldCollision", + WorldCollisionComponent.CODEC); + solverSettingsComponentType = registry.registerComponent( + SolverSettingsComponent.class, + "SolverSettings", + SolverSettingsComponent.CODEC); + visualSyncSettingsComponentType = registry.registerComponent( + VisualSyncSettingsComponent.class, + "VisualSyncSettings", + VisualSyncSettingsComponent.CODEC); + visualMaterializationSettingsComponentType = registry.registerComponent( + VisualMaterializationSettingsComponent.class, + "VisualMaterializationSettings", + VisualMaterializationSettingsComponent.CODEC); + collisionLodSettingsComponentType = registry.registerComponent( + CollisionLodSettingsComponent.class, + "CollisionLodSettings", + CollisionLodSettingsComponent.CODEC); + extensionSettingsComponentType = registry.registerComponent( + ExtensionSettingsComponent.class, + "ExtensionSettings", + ExtensionSettingsComponent.CODEC); } @Nonnull From 4a85ba74cbc9da5bbfdc07db3e94530a805108ff Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 09:28:11 +0200 Subject: [PATCH 305/534] refactor(core): flatten physicsstore runtime resources Signed-off-by: Blovien --- .../internal/commands/SpaceSelection.java | 4 +-- .../crucible/ImpulseLiveCrucibleTests.java | 2 +- .../PhysicsStoreBenchmarkQueries.java | 2 +- .../PhysicsStoreControlSessionMutations.java | 2 +- .../WorldCollisionPerfResetCommand.java | 2 +- .../WorldCollisionPerfToggleCommand.java | 2 +- ...sicsStoreWorldCollisionProducerSystem.java | 2 +- .../PersistentBodyRuntimeStateCodec.java | 2 +- .../PersistentBodyRuntimeStateDto.java | 6 ++--- .../PhysicsStoreRuntimeCleaner.java | 12 ++++----- .../PhysicsStoreSpaceMutations.java | 6 ++--- .../PhysicsStoreTopologyMutations.java | 10 +++---- .../PhysicsStoreRegistration.java | 26 +++++++------------ .../PhysicsBodyRegistrationResource.java | 3 +-- .../PhysicsIdentityIndexResource.java | 6 +---- .../resources/PhysicsProfilingResource.java | 3 +-- .../resources/PhysicsResourceTypes.java | 9 ++----- .../resources/PhysicsRuntimeResource.java | 6 +---- .../resources/PhysicsSnapshotResource.java | 3 +-- ...hysicsSpaceCompatibilityIndexResource.java | 3 +-- .../PhysicsStepSchedulerResource.java | 3 +-- .../PhysicsWorldRuntimeResource.java | 9 +------ .../internal/systems/BodyBindingSystem.java | 4 +-- .../systems/BodyCommandApplicationSystem.java | 4 +-- .../CompletedStepPublicationSystem.java | 20 +++++++------- .../internal/systems/IdentityIndexSystem.java | 2 +- .../internal/systems/JointBindingSystem.java | 4 +-- .../systems/PersistenceCaptureSystem.java | 2 +- .../systems/PhysicsStoreSystemSupport.java | 2 +- .../internal/systems/SpaceBindingSystem.java | 6 ++--- .../SpaceSettingsApplicationSystem.java | 2 +- .../systems/StaleBodyRemovalSystem.java | 10 +++---- .../systems/StepSubmissionSystem.java | 12 ++++----- .../internal/systems/TargetBindingSystem.java | 4 +-- .../systems/TerrainColliderBindingSystem.java | 4 +-- .../systems/TerrainMutationDrainSystem.java | 2 +- .../debug/PhysicsStoreDebugQueries.java | 8 +++--- .../PhysicsStoreEventPublicationSystem.java | 4 +-- .../systems/sync/PhysicsSyncSystem.java | 2 +- .../control/PhysicsControlSessions.java | 2 +- .../persistence/PhysicsPersistence.java | 4 +-- .../physicsstore/PhysicsBackendAccess.java | 8 +++--- .../plugin/physicsstore/PhysicsBodies.java | 4 +-- .../physicsstore/PhysicsDiagnostics.java | 4 +-- .../plugin/physicsstore/PhysicsRaycasts.java | 2 +- .../plugin/physicsstore/PhysicsSpaces.java | 4 +-- .../plugin/physicsstore/PhysicsThreading.java | 2 +- 47 files changed, 106 insertions(+), 139 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsBodyRegistrationResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsIdentityIndexResource.java (90%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsProfilingResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsRuntimeResource.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsSnapshotResource.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsSpaceCompatibilityIndexResource.java (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsStepSchedulerResource.java (98%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java index cea55c4a..0714d9af 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java @@ -9,8 +9,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.Comparator; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 5561c81f..d33ed143 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 30f1d484..2a6f1977 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 461dec9f..641eee97 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java index 86429957..99c7f860 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java index 33cc89de..e0c5f341 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java index 12433591..acf54fcf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -26,7 +26,7 @@ import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java index c2fb31b8..48748692 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java @@ -282,7 +282,7 @@ private static float readFloatToken(@Nonnull RawJsonReader reader) throws IOExce private static boolean readBooleanToken(@Nonnull RawJsonReader reader) throws IOException { String token = readWordToken(reader); - return switch (token.toString()) { + return switch (token) { case "true" -> true; case "false" -> false; default -> throw new IOException("Invalid persisted boolean value: " + token); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java index 76404b53..24001c15 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.codec.Codec; import java.util.Objects; import javax.annotation.Nonnull; +import lombok.Getter; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -23,6 +24,7 @@ public final class PersistentBodyRuntimeStateDto { private final Vector3f linearVelocity = new Vector3f(); @Nonnull private final Vector3f angularVelocity = new Vector3f(); + @Getter private boolean sleeping; public PersistentBodyRuntimeStateDto() { @@ -60,10 +62,6 @@ public Vector3f getAngularVelocity() { return new Vector3f(angularVelocity); } - public boolean isSleeping() { - return sleeping; - } - @Nonnull public PersistentBodyRuntimeStateDto copy() { return new PersistentBodyRuntimeStateDto(position, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index 6ef35ae6..7bd9ed59 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -3,13 +3,13 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index bdc34459..997d2312 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -8,9 +8,9 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 75569a1c..44b30b01 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -6,12 +6,12 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index a069627b..1529d981 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -9,18 +9,17 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.TickDecision; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.TickDecision; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; @@ -153,10 +152,7 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic } private static void ensurePersistentResourcePresent(@Nonnull Store store) { - if (store.getResource(PersistentPhysicsStoreResource.getResourceType()) == null) { - store.replaceResource(PersistentPhysicsStoreResource.getResourceType(), - new PersistentPhysicsStoreResource()); - } + store.getResource(PersistentPhysicsStoreResource.getResourceType()); } private static > void cleanupResource( @@ -164,9 +160,7 @@ private static > void cleanupResource( @Nonnull ResourceType type, @Nonnull Consumer cleanup) { T resource = store.getResource(type); - if (resource != null) { - cleanup.accept(resource); - } + cleanup.accept(resource); } private static boolean shouldTickPhysicsStore(@Nonnull PhysicsStore physicsStore, float dt) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java index 2e17c4e0..ee0b3218 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java @@ -1,10 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java similarity index 90% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java index 2e480dcc..135774ec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsIdentityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java @@ -1,13 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java index 097a5624..aa5f0fda 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java @@ -1,9 +1,8 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import java.util.Objects; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 0b7124f1..66b9f55a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -4,13 +4,8 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index 2c44c9e7..402fd98f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -1,17 +1,13 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java index 3dc2e8ee..e2d1f05f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java @@ -1,10 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceCompatibilityIndexResource.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceCompatibilityIndexResource.java index 5147d572..71d69dcb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsSpaceCompatibilityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceCompatibilityIndexResource.java @@ -1,9 +1,8 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.SpaceId; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java index 70682841..db77d1aa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java @@ -1,9 +1,8 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 629cb86e..ef6b08b0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -20,15 +20,8 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java index 289b65d6..2c722522 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java @@ -14,9 +14,9 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index a32ec99e..1dac9a79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -15,8 +15,8 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 8a72b7a5..29f74a94 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -12,18 +12,18 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource.BodyRegistrationPublication; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource.BodyRegistrationPublication; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java index 8f2be40f..49e180bd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java @@ -11,7 +11,7 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java index a8cc79a8..7d08a1fb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java @@ -14,9 +14,9 @@ import dev.hytalemodding.impulse.api.runtime.BackendJointType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index a3cb6dd0..0cf0e7c6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentTerrainColliderDto; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java index 5d4a25d0..9ead3668 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java index c8a72308..86f25ded 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java @@ -15,10 +15,10 @@ import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java index 30636499..60a36b46 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java @@ -12,7 +12,7 @@ import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index 101aa372..c6a1977f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -9,12 +9,12 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index dbb18467..a7bbfb58 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -13,13 +13,13 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodySnapshotMetadata; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.CompletedStep; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource.StepInput; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java index 9a2bd83c..5242df6f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java @@ -13,8 +13,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.PendingBodyOperation; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java index 45ce0cdc..d2b735fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java @@ -16,9 +16,9 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java index d2916368..2d4dcae8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index b0ce074e..6347755e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -10,10 +10,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index 2f7cd17d..a58b1d79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -11,8 +11,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsProfilingResource.StepSample; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource.StepSample; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 2827aaf6..715ad565 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index dc939808..8b5c2f1a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -12,7 +12,7 @@ import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 60e20811..6268fb26 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -7,8 +7,8 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java index f3212935..331bbcb5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java @@ -8,10 +8,10 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource.BodyHitMetadata; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java index 01b35d9c..9cfe1af1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java @@ -3,8 +3,8 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java index 8683e19d..de0d665c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java @@ -5,8 +5,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java index fed44df2..7009f7d0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 5a5314f6..4feaa9be 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -4,8 +4,8 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import java.util.Collection; import java.util.List; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java index 56ecf600..c61b58cb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java @@ -5,7 +5,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import java.util.Objects; import java.util.concurrent.CompletableFuture; From 32b4c295177f0dd873bb58162b4d6261fd355756 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 09:36:03 +0200 Subject: [PATCH 306/534] refactor(core): introduce physics entity subplugin Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 6 + .../impulse/core/ImpulsePlugin.java | 81 ------------ .../GeneratedVisualProxyComponent.java | 4 +- .../resources/PhysicsDebugResource.java | 4 +- .../PhysicsProjectionIndexResource.java | 4 +- .../PhysicsRuntimeProfilingResource.java | 4 +- .../systems/sync/PhysicsSyncSystem.java | 4 +- .../PhysicsGeneratedProxyCleanupSystem.java | 4 +- .../ImpulsePhysicsEntityPlugin.java | 38 ++++++ .../physicsentity/PhysicsEntityTypes.java | 125 ++++++++++++++++++ .../projection/BodyAttachmentComponent.java | 4 +- .../resources/PhysicsWorldResource.java | 4 +- impulse-core/src/module-info/module-info.java | 1 + 13 files changed, 186 insertions(+), 97 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/ImpulsePhysicsEntityPlugin.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index d4e659aa..789041a8 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -102,6 +102,12 @@ hytaleTools { manifestDependencies = impulseManifestDependencies manifestOptionalDependencies = "com.ionforgelabs:crucible=*" + subPlugin ( + "ImpulsePhysicsEntity", + "dev.hytalemodding.impulse.core.plugin.modules.physicsentity.ImpulsePhysicsEntityPlugin", + false, /* disabledByDefault */ + false /* includeAssetPack */ + ) subPlugin ( "ImpulseWorldCollision", "dev.hytalemodding.impulse.core.plugin.modules.worldcollision.ImpulseWorldCollisionPlugin", diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 7af02584..b67bfeee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -1,10 +1,6 @@ package dev.hytalemodding.impulse.core; import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.event.WorldEventType; import com.hypixel.hytale.common.plugin.PluginIdentifier; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.HytaleServer; @@ -14,31 +10,16 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.plugin.PluginManager; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBackend; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; -import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; @@ -46,37 +27,12 @@ import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import lombok.Getter; public final class ImpulsePlugin extends JavaPlugin { private static ImpulsePlugin instance; private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - @Getter - private ComponentType bodyAttachmentComponentType; - - @Getter - private ComponentType generatedVisualProxyComponentType; - - @Getter - private ResourceType physicsWorldResourceType; - - @Getter - private ResourceType physicsDebugResourceType; - - @Getter - private ResourceType physicsRuntimeProfilingResourceType; - - @Getter - private ResourceType physicsProjectionIndexResourceType; - - @Getter - private WorldEventType physicsEventFramePublishedEventType; - - @Getter - private SystemGroup persistenceRestoreGroup; - @Nullable private BackendId defaultBackendId; @@ -104,8 +60,6 @@ protected void setup() { ImpulseSubPluginRegistration.register(this); discoverBackends(); - registerEntityStoreComponents(); - registerSystems(); registerCommands(); } @@ -207,41 +161,6 @@ private String getAvailableBackendIds() { return ids.toString(); } - private void registerEntityStoreComponents() { - ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - bodyAttachmentComponentType = entityRegistry.registerComponent( - BodyAttachmentComponent.class, - "BodyAttachment", - BodyAttachmentComponent.CODEC); - generatedVisualProxyComponentType = entityRegistry.registerComponent( - GeneratedVisualProxyComponent.class, - "GeneratedVisualProxy", - GeneratedVisualProxyComponent.CODEC); - physicsWorldResourceType = entityRegistry.registerResource(PhysicsWorldResource.class, - PhysicsWorldRuntimeResource::new); - physicsDebugResourceType = entityRegistry.registerResource(PhysicsDebugResource.class, - PhysicsDebugResource::new); - physicsRuntimeProfilingResourceType = entityRegistry.registerResource( - PhysicsRuntimeProfilingResource.class, - PhysicsRuntimeProfilingResource::new); - physicsProjectionIndexResourceType = entityRegistry.registerResource( - PhysicsProjectionIndexResource.class, - PhysicsProjectionIndexResource::new); - physicsEventFramePublishedEventType = - entityRegistry.registerWorldEventType(PhysicsEventFramePublishedEvent.class); - } - - private void registerSystems() { - ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - persistenceRestoreGroup = entityRegistry.registerSystemGroup(); - entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); - entityRegistry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); - entityRegistry.registerSystem(new PhysicsSyncSystem()); - entityRegistry.registerSystem(new PhysicsDebugSystem()); - entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); - entityRegistry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); - } - private void registerCommands() { CommandRegistry commandRegistry = getCommandRegistry(); ImpulseCommandContributionRegistry.register(commandRegistry); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java index 0fbad7c6..99bcb5f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; /** @@ -19,7 +19,7 @@ public final class GeneratedVisualProxyComponent implements Component getComponentType() { - return ImpulsePlugin.get().getGeneratedVisualProxyComponentType(); + return PhysicsEntityTypes.generatedVisualProxyComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index 59e8b015..ad94e4e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; @@ -166,7 +166,7 @@ public PhysicsDebugResource clone() { } public static ResourceType getResourceType() { - return ImpulsePlugin.get().getPhysicsDebugResourceType(); + return PhysicsEntityTypes.physicsDebugResourceType(); } private static float clampRefresh(float value) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index 2b7eac78..cf87dea2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -5,7 +5,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -276,7 +276,7 @@ public PhysicsProjectionIndexResource clone() { } public static ResourceType getResourceType() { - return ImpulsePlugin.get().getPhysicsProjectionIndexResourceType(); + return PhysicsEntityTypes.physicsProjectionIndexResourceType(); } private void unregisterAttachmentRef(@Nonnull Ref bodyRef, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java index e1c4f690..e2dfb2c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; @@ -367,7 +367,7 @@ private static long secondsToNanos(float seconds) { } public static ResourceType getResourceType() { - return ImpulsePlugin.get().getPhysicsRuntimeProfilingResourceType(); + return PhysicsEntityTypes.physicsRuntimeProfilingResourceType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 715ad565..a13aa5e0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -18,7 +18,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; @@ -27,6 +26,7 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; @@ -59,7 +59,7 @@ public class PhysicsSyncSystem extends EntityTickingSystem { private static final Query QUERY = Query.and(ATTACHMENT_TYPE, TRANSFORM_TYPE); private final Set> dependencies = Set.of( - new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()), + new SystemGroupDependency<>(Order.AFTER, PhysicsEntityTypes.persistenceRestoreGroup()), new SystemDependency<>(Order.AFTER, PhysicsGeneratedProxyCleanupSystem.class), new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class), new SystemDependency<>(Order.BEFORE, UpdateLocationSystems.TickingSystem.class) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java index 610cf9c1..4e8bdee1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java @@ -8,8 +8,8 @@ import com.hypixel.hytale.component.dependency.SystemGroupDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; import java.util.Collections; @@ -26,7 +26,7 @@ public class PhysicsGeneratedProxyCleanupSystem extends TickingSystem> dependencies = Set.of( - new SystemGroupDependency<>(Order.AFTER, ImpulsePlugin.get().getPersistenceRestoreGroup()) + new SystemGroupDependency<>(Order.AFTER, PhysicsEntityTypes.persistenceRestoreGroup()) ); @Nonnull private final Map, Integer> cleanupCooldowns = diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/ImpulsePhysicsEntityPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/ImpulsePhysicsEntityPlugin.java new file mode 100644 index 00000000..a8d9ba1c --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/ImpulsePhysicsEntityPlugin.java @@ -0,0 +1,38 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; +import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; +import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import javax.annotation.Nonnull; + +/** + * Subplugin that integrates authoritative PhysicsStore bodies with EntityStore entities. + */ +public final class ImpulsePhysicsEntityPlugin extends JavaPlugin { + + public ImpulsePhysicsEntityPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); + PhysicsEntityTypes.registerComponentTypes(entityRegistry); + PhysicsEntityTypes.registerResourceTypes(entityRegistry); + PhysicsEntityTypes.registerEventTypes(entityRegistry); + PhysicsEntityTypes.registerSystemGroups(entityRegistry); + entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); + entityRegistry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); + entityRegistry.registerSystem(new PhysicsSyncSystem()); + entityRegistry.registerSystem(new PhysicsDebugSystem()); + entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); + entityRegistry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java new file mode 100644 index 00000000..cc4e68d5 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -0,0 +1,125 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Registered EntityStore type handles for the PhysicsEntity integration module. + */ +public final class PhysicsEntityTypes { + + @Nullable + private static ComponentType bodyAttachmentComponentType; + @Nullable + private static ComponentType + generatedVisualProxyComponentType; + @Nullable + private static ResourceType physicsWorldResourceType; + @Nullable + private static ResourceType physicsDebugResourceType; + @Nullable + private static ResourceType + physicsRuntimeProfilingResourceType; + @Nullable + private static ResourceType + physicsProjectionIndexResourceType; + @Nullable + private static WorldEventType + physicsEventFramePublishedEventType; + @Nullable + private static SystemGroup persistenceRestoreGroup; + + private PhysicsEntityTypes() { + } + + public static void registerComponentTypes(@Nonnull ComponentRegistryProxy registry) { + bodyAttachmentComponentType = registry.registerComponent( + BodyAttachmentComponent.class, + "BodyAttachment", + BodyAttachmentComponent.CODEC); + generatedVisualProxyComponentType = registry.registerComponent( + GeneratedVisualProxyComponent.class, + "GeneratedVisualProxy", + GeneratedVisualProxyComponent.CODEC); + } + + public static void registerResourceTypes(@Nonnull ComponentRegistryProxy registry) { + physicsWorldResourceType = registry.registerResource(PhysicsWorldResource.class, + PhysicsWorldRuntimeResource::new); + physicsDebugResourceType = registry.registerResource(PhysicsDebugResource.class, + PhysicsDebugResource::new); + physicsRuntimeProfilingResourceType = registry.registerResource( + PhysicsRuntimeProfilingResource.class, + PhysicsRuntimeProfilingResource::new); + physicsProjectionIndexResourceType = registry.registerResource( + PhysicsProjectionIndexResource.class, + PhysicsProjectionIndexResource::new); + } + + public static void registerEventTypes(@Nonnull ComponentRegistryProxy registry) { + physicsEventFramePublishedEventType = + registry.registerWorldEventType(PhysicsEventFramePublishedEvent.class); + } + + public static void registerSystemGroups(@Nonnull ComponentRegistryProxy registry) { + persistenceRestoreGroup = registry.registerSystemGroup(); + } + + @Nonnull + public static ComponentType bodyAttachmentComponentType() { + return bodyAttachmentComponentType; + } + + @Nonnull + public static ComponentType + generatedVisualProxyComponentType() { + return generatedVisualProxyComponentType; + } + + @Nonnull + public static ResourceType physicsWorldResourceType() { + return physicsWorldResourceType; + } + + @Nonnull + public static ResourceType physicsDebugResourceType() { + return physicsDebugResourceType; + } + + @Nonnull + public static ResourceType + physicsRuntimeProfilingResourceType() { + return physicsRuntimeProfilingResourceType; + } + + @Nonnull + public static ResourceType + physicsProjectionIndexResourceType() { + return physicsProjectionIndexResourceType; + } + + @Nonnull + public static WorldEventType + physicsEventFramePublishedEventType() { + return physicsEventFramePublishedEventType; + } + + @Nonnull + public static SystemGroup persistenceRestoreGroup() { + return persistenceRestoreGroup; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java index 23de5260..e78123a5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java @@ -10,8 +10,8 @@ import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -225,7 +225,7 @@ public boolean shouldRemoveEntityWhenBodyMissing() { } public static ComponentType getComponentType() { - return ImpulsePlugin.get().getBodyAttachmentComponentType(); + return PhysicsEntityTypes.bodyAttachmentComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 9584e93f..c3216ad2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; @@ -399,7 +399,7 @@ public abstract boolean hasBodyAttachments(@Nonnull UUID bodyUuid, public abstract boolean hasBodyAttachments(@Nonnull Ref bodyRef); public static ResourceType getResourceType() { - return ImpulsePlugin.get().getPhysicsWorldResourceType(); + return PhysicsEntityTypes.physicsWorldResourceType(); } @Nonnull diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 01db8c72..d81eb28a 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -9,6 +9,7 @@ exports dev.hytalemodding.impulse.core.plugin.components; exports dev.hytalemodding.impulse.core.plugin.events; exports dev.hytalemodding.impulse.core.plugin.modules.control; + exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity; exports dev.hytalemodding.impulse.core.plugin.modules.worldcollision; exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physicsstore; From 162d0efa21552d5715bd0ee7450f61be0664fb70 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 09:40:05 +0200 Subject: [PATCH 307/534] refactor(core): rename world collision subplugin to physics chunk Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 4 ++-- .../core/internal/commands/SpaceCommand.java | 2 +- .../crucible/ImpulseApiCrucibleTests.java | 2 +- ...eDetachedStreamingBenchmarkCrucibleTests.java | 12 ++++++------ .../ImpulseRapierBodyBenchmarkCrucibleTests.java | 6 +++--- .../crucible/PhysicsStoreBenchmarkQueries.java | 2 +- .../WorldCollisionSubPluginCrucibleSupport.java | 4 ++-- .../ChunkSectionAccess.java | 2 +- .../PhysicsStoreTerrainMutationCache.java | 8 ++++---- .../PhysicsStoreTerrainMutations.java | 4 ++-- ...sicsStoreWorldCollisionStreamingResource.java | 16 ++++++++-------- .../PhysicsWorldCollisionRuntime.java | 8 ++++---- .../SectionBlockReader.java | 4 ++-- .../SectionColliderBuilder.java | 6 +++--- .../SectionCollisionGeometry.java | 2 +- .../ShapeTemplateCache.java | 2 +- .../TerrainColliderMode.java | 2 +- .../WorldCollisionBuildOptions.java | 2 +- .../WorldCollisionLifecycle.java | 2 +- .../WorldCollisionStreamingBounds.java | 2 +- .../WorldVoxelCollisionCache.java | 10 +++++----- .../commands/CollisionLodSettingsCommand.java | 2 +- .../commands/WorldCollisionCommand.java | 2 +- .../WorldCollisionCommandContributions.java | 2 +- .../commands/WorldCollisionPerfCommand.java | 2 +- .../WorldCollisionPerfReportCommand.java | 6 +++--- .../commands/WorldCollisionPerfResetCommand.java | 4 ++-- .../WorldCollisionPerfToggleCommand.java | 4 ++-- .../commands/WorldCollisionSettingsCommand.java | 4 ++-- .../WorldCollisionProfilingResource.java | 4 ++-- ...PhysicsStoreWorldCollisionProducerSystem.java | 16 ++++++++-------- .../internal/persistence/PersistentSpaceDto.java | 2 +- .../PhysicsWorldCollisionIndexResource.java | 6 +++--- .../resources/PhysicsWorldRuntimeResource.java | 16 ++++++++-------- .../PhysicsDebugWorldCollisionSectionView.java | 2 +- .../systems/debug/PhysicsDebugRenderer.java | 2 +- .../systems/debug/PhysicsDebugSystem.java | 2 +- .../systems/debug/PhysicsStoreDebugQueries.java | 2 +- .../components/WorldCollisionComponent.java | 2 +- .../ImpulsePhysicsChunkPlugin.java} | 14 +++++++------- .../WorldCollisionBuildStats.java | 2 +- .../WorldCollisionMode.java | 2 +- .../WorldCollisionPrewarmStats.java | 2 +- .../WorldCollisionStats.java | 2 +- .../plugin/resources/PhysicsWorldResource.java | 6 +++--- .../plugin/settings/PhysicsSpaceSettings.java | 2 +- .../settings/PhysicsWorldCollisionSettings.java | 2 +- impulse-core/src/module-info/module-info.java | 2 +- .../commands/PhysicsStoreExampleCommands.java | 2 +- .../examples/commands/WorldCollisionCommand.java | 6 +++--- .../commands/stress/StressBodiesCommand.java | 4 ++-- 51 files changed, 114 insertions(+), 114 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/ChunkSectionAccess.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/PhysicsStoreTerrainMutationCache.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/PhysicsStoreTerrainMutations.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/PhysicsStoreWorldCollisionStreamingResource.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/PhysicsWorldCollisionRuntime.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/SectionBlockReader.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/SectionColliderBuilder.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/SectionCollisionGeometry.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/ShapeTemplateCache.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/TerrainColliderMode.java (85%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/WorldCollisionBuildOptions.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/WorldCollisionLifecycle.java (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/WorldCollisionStreamingBounds.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/WorldVoxelCollisionCache.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/CollisionLodSettingsCommand.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/WorldCollisionCommand.java (83%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/WorldCollisionCommandContributions.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/WorldCollisionPerfCommand.java (85%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/WorldCollisionPerfReportCommand.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/WorldCollisionPerfResetCommand.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/WorldCollisionPerfToggleCommand.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/commands/WorldCollisionSettingsCommand.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/profiling/WorldCollisionProfilingResource.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/systems/PhysicsStoreWorldCollisionProducerSystem.java (93%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/{worldcollision/ImpulseWorldCollisionPlugin.java => physicschunk/ImpulsePhysicsChunkPlugin.java} (70%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/{worldcollision => physicschunk}/WorldCollisionBuildStats.java (90%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/{worldcollision => physicschunk}/WorldCollisionMode.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/{worldcollision => physicschunk}/WorldCollisionPrewarmStats.java (76%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/{worldcollision => physicschunk}/WorldCollisionStats.java (78%) diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index 789041a8..81731b90 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -109,8 +109,8 @@ hytaleTools { false /* includeAssetPack */ ) subPlugin ( - "ImpulseWorldCollision", - "dev.hytalemodding.impulse.core.plugin.modules.worldcollision.ImpulseWorldCollisionPlugin", + "ImpulsePhysicsChunk", + "dev.hytalemodding.impulse.core.plugin.modules.physicschunk.ImpulsePhysicsChunkPlugin", false, /* disabledByDefault */ false /* includeAssetPack */ ) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index b83e2e4c..3a0763cc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 08b0e144..aef3b412 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -29,7 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import java.util.UUID; import java.util.Collection; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 29b1ecf4..10ae06fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -18,23 +18,23 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 815ec241..b669e0f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -28,7 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 2a6f1977..5a6a20a9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -7,7 +7,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/WorldCollisionSubPluginCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/WorldCollisionSubPluginCrucibleSupport.java index 59b186be..5c1c7c24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/WorldCollisionSubPluginCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/WorldCollisionSubPluginCrucibleSupport.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.common.plugin.PluginIdentifier; import com.hypixel.hytale.server.core.plugin.PluginManager; import com.hypixel.hytale.server.core.plugin.PluginBase; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @@ -15,7 +15,7 @@ final class WorldCollisionSubPluginCrucibleSupport { private static final PluginIdentifier PLUGIN_ID = - new PluginIdentifier("HytaleModding", "ImpulseWorldCollision"); + new PluginIdentifier("HytaleModding", "ImpulsePhysicsChunk"); private WorldCollisionSubPluginCrucibleSupport() { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/ChunkSectionAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkSectionAccess.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/ChunkSectionAccess.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkSectionAccess.java index 86551d0b..05da62b0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/ChunkSectionAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkSectionAccess.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutationCache.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutationCache.java index ccf24768..a98a5333 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutationCache.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -8,9 +8,9 @@ import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.MissingSectionReason; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.MissingSectionReason; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java index e53a4403..76d25bd6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreTerrainMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java @@ -1,7 +1,7 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.BoxPayload; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java index 95019309..11a3bb50 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsStoreWorldCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; @@ -6,14 +6,14 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsWorldCollisionRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsWorldCollisionRuntime.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java index d3c0774f..556dec9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/PhysicsWorldCollisionRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java @@ -1,11 +1,11 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.server.core.universe.world.World; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import it.unimi.dsi.fastutil.ints.Int2LongMap; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionBlockReader.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionBlockReader.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java index 69a020de..08f6303b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionBlockReader.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java @@ -1,9 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.ShapeTemplateCache.ShapeTemplate; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ShapeTemplateCache.ShapeTemplate; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionColliderBuilder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionColliderBuilder.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java index 234fa592..ed1de1fe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionColliderBuilder.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java @@ -1,11 +1,11 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.math.shape.Box; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.ShapeTemplateCache.ShapeTemplate; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ShapeTemplateCache.ShapeTemplate; import it.unimi.dsi.fastutil.ints.IntArrayList; import java.util.ArrayList; import java.util.BitSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionCollisionGeometry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionCollisionGeometry.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionCollisionGeometry.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionCollisionGeometry.java index da229ad3..9fae1232 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/SectionCollisionGeometry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionCollisionGeometry.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import java.util.List; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/ShapeTemplateCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ShapeTemplateCache.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/ShapeTemplateCache.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ShapeTemplateCache.java index 8e83a29d..47d1047e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/ShapeTemplateCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ShapeTemplateCache.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.math.shape.Box; import com.hypixel.hytale.protocol.BlockMaterial; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/TerrainColliderMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/TerrainColliderMode.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/TerrainColliderMode.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/TerrainColliderMode.java index 83989d16..23ba3abb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/TerrainColliderMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/TerrainColliderMode.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; /** * Runtime representation used for full-cube terrain collision. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionBuildOptions.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionBuildOptions.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionBuildOptions.java index 402899bb..ecb71926 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionBuildOptions.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycle.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycle.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycle.java index cdeb770a..39319528 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycle.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import dev.hytalemodding.impulse.core.internal.modules.SubPluginLifecycleGate; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionStreamingBounds.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBounds.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionStreamingBounds.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBounds.java index 6e4c2843..5a838e5a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionStreamingBounds.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBounds.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.math.util.ChunkUtil; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCache.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCache.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCache.java index 33aa8d2f..a1358100 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCache.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -12,10 +12,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.MissingSectionReason; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.MissingSectionReason; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2LongMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java index ebafc426..7f7db9f8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/CollisionLodSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommand.java similarity index 83% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommand.java index 75fa2e36..ccf17673 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionCommandContributions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommandContributions.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionCommandContributions.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommandContributions.java index 405fed73..8142f593 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionCommandContributions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommandContributions.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfCommand.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfCommand.java index 920bb2db..2378be19 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfReportCommand.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfReportCommand.java index 3a82c728..1bbe2e79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfResetCommand.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfResetCommand.java index 99c7f860..e0679ce6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfResetCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfResetCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import javax.annotation.Nonnull; public class WorldCollisionPerfResetCommand extends AbstractWorldCommand { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfToggleCommand.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfToggleCommand.java index e0c5f341..1483d631 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfToggleCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import javax.annotation.Nonnull; public class WorldCollisionPerfToggleCommand extends AbstractWorldCommand { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionSettingsCommand.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionSettingsCommand.java index d00344cf..2e19fc15 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java index 3db2f360..1d54a534 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java @@ -1,9 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache.BuildStats; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java index acf54fcf..bfb352b5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -18,13 +18,13 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionStreamingBounds; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionStreamingBounds; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index ffa0a7ae..e8f09d56 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java index 33122e2f..1905ad46 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java @@ -3,9 +3,9 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.TerrainColliderMode; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.TerrainColliderMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index ef6b08b0..4c9c7367 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -29,22 +29,22 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotRefVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsWorldCollisionRuntime; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsWorldCollisionRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java index a0b5c2e7..142304ab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/PhysicsDebugWorldCollisionSectionView.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.internal.simulation.view; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import java.util.List; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java index d85fea54..e5072913 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java @@ -7,7 +7,7 @@ import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 7a88ede0..da0fd259 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -24,7 +24,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 6347755e..96bf4624 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java index ece1491f..b877e74c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java similarity index 70% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java index decdfb69..e378dabc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/ImpulseWorldCollisionPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -1,25 +1,25 @@ -package dev.hytalemodding.impulse.core.plugin.modules.worldcollision; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.systems.PhysicsStoreWorldCollisionProducerSystem; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import java.util.logging.Level; import javax.annotation.Nonnull; /** * Subplugin that enables Impulse world collision. */ -public final class ImpulseWorldCollisionPlugin extends JavaPlugin { +public final class ImpulsePhysicsChunkPlugin extends JavaPlugin { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - public ImpulseWorldCollisionPlugin(@Nonnull JavaPluginInit init) { + public ImpulsePhysicsChunkPlugin(@Nonnull JavaPluginInit init) { super(init); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java similarity index 90% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionBuildStats.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java index b6821cc3..61a63717 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionBuildStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.worldcollision; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** * Aggregate statistics from building or rebuilding streamed world-collision geometry. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionMode.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java index 06961cd1..df4993b8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.worldcollision; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** * Controls how a physics space interacts with Hytale world voxel collision. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java similarity index 76% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionPrewarmStats.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java index bca78885..82c2bb2a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionPrewarmStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.worldcollision; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** * Statistics from ensuring world collision around multiple target positions. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java similarity index 78% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionStats.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java index a107c609..45eab549 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/worldcollision/WorldCollisionStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.worldcollision; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** * Current size of the generated world-collision cache. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index c3216ad2..9edb0899 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -13,9 +13,9 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index 4a22fc59..e0f22e30 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java index fd083d6d..68397c94 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import lombok.Getter; import lombok.Setter; import javax.annotation.Nonnull; diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index d81eb28a..f35aa2c3 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -10,7 +10,7 @@ exports dev.hytalemodding.impulse.core.plugin.events; exports dev.hytalemodding.impulse.core.plugin.modules.control; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity; - exports dev.hytalemodding.impulse.core.plugin.modules.worldcollision; + exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk; exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physicsstore; exports dev.hytalemodding.impulse.core.plugin.projection; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 18d415b1..3a532ee1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -19,7 +19,7 @@ import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java index bc48d7d8..b0bad454 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java @@ -13,9 +13,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 9b268c55..a88c1064 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; From b07cf73cfa2428a425b232653a8479ccc4d20cc3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 09:46:50 +0200 Subject: [PATCH 308/534] refactor(core): register chunk physics types in physics chunk Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 21 --- .../resources/PhysicsResourceTypes.java | 32 ----- .../PhysicsTerrainMutationQueueResource.java | 3 +- .../PhysicsTerrainPayloadResource.java | 3 +- .../PhysicsWorldCollisionIndexResource.java | 3 +- .../components/PhysicsComponentTypes.java | 22 --- .../components/TerrainColliderComponent.java | 3 +- .../components/WorldCollisionComponent.java | 3 +- .../ImpulsePhysicsChunkPlugin.java | 17 +++ .../physicschunk/PhysicsChunkTypes.java | 126 ++++++++++++++++++ 10 files changed, 153 insertions(+), 80 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 1529d981..d4e47b64 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -13,7 +13,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -21,9 +20,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.TickDecision; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; import dev.hytalemodding.impulse.core.internal.systems.ColliderBindingSystem; @@ -33,14 +30,11 @@ import dev.hytalemodding.impulse.core.internal.systems.PersistenceCaptureSystem; import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreQueuedReadSystem; -import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.systems.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.WorldCollisionIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; @@ -72,16 +66,13 @@ public static void register(@Nonnull ComponentRegistryProxy regist PhysicsResourceTypes.registerResourceTypes(registry); registry.registerSystem(new PersistenceHydrationSystem()); - registry.registerSystem(new TerrainMutationDrainSystem()); registry.registerSystem(new IdentityIndexSystem()); - registry.registerSystem(new WorldCollisionIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); registry.registerSystem(new BodyBindingSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); - registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); @@ -98,10 +89,6 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic RuntimeException failure = null; failure = runShutdownCleanup(failure, () -> ensurePersistentResourcePresent(store)); - failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsTerrainMutationQueueResource.getResourceType(), - PhysicsTerrainMutationQueueResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsStepSchedulerResource.getResourceType(), @@ -138,14 +125,6 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic () -> cleanupResource(store, PhysicsStoreReadQueueResource.getResourceType(), PhysicsStoreReadQueueResource::clear)); - failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsTerrainPayloadResource.getResourceType(), - PhysicsTerrainPayloadResource::clear)); - failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsWorldCollisionIndexResource.getResourceType(), - PhysicsWorldCollisionIndexResource::clear)); if (failure != null) { throw failure; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 66b9f55a..59a0350a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -4,8 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -24,8 +22,6 @@ public final class PhysicsResourceTypes { private static ResourceType spaceCompatibilityIndexResourceType; @Nullable - private static ResourceType terrainMutationQueueResourceType; - @Nullable private static ResourceType identityIndexResourceType; @Nullable private static ResourceType snapshotResourceType; @@ -37,10 +33,6 @@ public final class PhysicsResourceTypes { @Nullable private static ResourceType readQueueResourceType; @Nullable - private static ResourceType terrainPayloadResourceType; - @Nullable - private static ResourceType worldCollisionIndexResourceType; - @Nullable private static ResourceType persistentStoreResourceType; @Nullable private static ResourceType restoreStatusResourceType; @@ -67,9 +59,6 @@ public static void registerResourceTypes( spaceCompatibilityIndexResourceType = registry.registerResource( PhysicsSpaceCompatibilityIndexResource.class, PhysicsSpaceCompatibilityIndexResource::new); - terrainMutationQueueResourceType = registry.registerResource( - PhysicsTerrainMutationQueueResource.class, - PhysicsTerrainMutationQueueResource::new); identityIndexResourceType = registry.registerResource( PhysicsIdentityIndexResource.class, PhysicsIdentityIndexResource::new); @@ -85,12 +74,6 @@ public static void registerResourceTypes( readQueueResourceType = registry.registerResource( PhysicsStoreReadQueueResource.class, PhysicsStoreReadQueueResource::new); - terrainPayloadResourceType = registry.registerResource( - PhysicsTerrainPayloadResource.class, - PhysicsTerrainPayloadResource::new); - worldCollisionIndexResourceType = registry.registerResource( - PhysicsWorldCollisionIndexResource.class, - PhysicsWorldCollisionIndexResource::new); persistentStoreResourceType = registry.registerResource( PersistentPhysicsStoreResource.class, "PersistentPhysicsStore", @@ -127,11 +110,6 @@ public static ResourceType stepSched return spaceCompatibilityIndexResourceType; } - @Nonnull - public static ResourceType terrainMutationQueueResourceType() { - return terrainMutationQueueResourceType; - } - @Nonnull public static ResourceType identityIndexResourceType() { return identityIndexResourceType; @@ -158,16 +136,6 @@ public static ResourceType readQueu return readQueueResourceType; } - @Nonnull - public static ResourceType terrainPayloadResourceType() { - return terrainPayloadResourceType; - } - - @Nonnull - public static ResourceType worldCollisionIndexResourceType() { - return worldCollisionIndexResourceType; - } - @Nonnull public static ResourceType persistentStoreResourceType() { return persistentStoreResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java index 5cd81700..0a536f12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -62,6 +63,6 @@ public synchronized PhysicsTerrainMutationQueueResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsResourceTypes.terrainMutationQueueResourceType(); + return PhysicsChunkTypes.terrainMutationQueueResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java index cc6ad309..e785f1e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import javax.annotation.Nonnull; @@ -48,6 +49,6 @@ public PhysicsTerrainPayloadResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsResourceTypes.terrainPayloadResourceType(); + return PhysicsChunkTypes.terrainPayloadResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java index 1905ad46..22c1f838 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.TerrainColliderMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -57,7 +58,7 @@ public synchronized PhysicsWorldCollisionIndexResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsResourceTypes.worldCollisionIndexResourceType(); + return PhysicsChunkTypes.worldCollisionIndexResourceType(); } public record SpaceWorldCollisionSettings(@Nonnull UUID spaceUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 72c10db1..049265d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -34,10 +34,6 @@ public final class PhysicsComponentTypes { @Nullable private static ComponentType targetComponentType; @Nullable - private static ComponentType terrainColliderComponentType; - @Nullable - private static ComponentType worldCollisionComponentType; - @Nullable private static ComponentType solverSettingsComponentType; @Nullable private static ComponentType visualSyncSettingsComponentType; @@ -98,14 +94,6 @@ public static void registerComponentTypes( TargetComponent.class, "Target", TargetComponent.CODEC); - terrainColliderComponentType = registry.registerComponent( - TerrainColliderComponent.class, - "TerrainCollider", - TerrainColliderComponent.CODEC); - worldCollisionComponentType = registry.registerComponent( - WorldCollisionComponent.class, - "WorldCollision", - WorldCollisionComponent.CODEC); solverSettingsComponentType = registry.registerComponent( SolverSettingsComponent.class, "SolverSettings", @@ -183,16 +171,6 @@ public static ComponentType targetComponentType() return targetComponentType; } - @Nonnull - public static ComponentType terrainColliderComponentType() { - return terrainColliderComponentType; - } - - @Nonnull - public static ComponentType worldCollisionComponentType() { - return worldCollisionComponentType; - } - @Nonnull public static ComponentType solverSettingsComponentType() { return solverSettingsComponentType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java index 3c3fec72..0ca25580 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java @@ -7,6 +7,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import lombok.Getter; import lombok.Setter; import java.util.Objects; @@ -132,7 +133,7 @@ public void setPayloadResourceKey(@Nonnull String payloadResourceKey) { @Nonnull public static ComponentType getComponentType() { - return PhysicsComponentTypes.terrainColliderComponentType(); + return PhysicsChunkTypes.terrainColliderComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java index b877e74c..4bf09ec0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java @@ -7,6 +7,7 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -218,7 +219,7 @@ public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsComponentTypes.worldCollisionComponentType(); + return PhysicsChunkTypes.worldCollisionComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java index e378dabc..9e2e4155 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -5,11 +5,16 @@ import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.early.PhysicsStoreHooks; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.WorldCollisionCommandContributions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import java.util.logging.Level; +import java.util.function.Consumer; import javax.annotation.Nonnull; /** @@ -18,6 +23,8 @@ public final class ImpulsePhysicsChunkPlugin extends JavaPlugin { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); + private static final Consumer SHUTDOWN_CLEANUP = + PhysicsChunkTypes::clearRuntimeStateBeforeShutdown; public ImpulsePhysicsChunkPlugin(@Nonnull JavaPluginInit init) { super(init); @@ -25,6 +32,13 @@ public ImpulsePhysicsChunkPlugin(@Nonnull JavaPluginInit init) { @Override protected void setup() { + ComponentRegistryProxy physicsStoreRegistry = + PhysicsStoreRegistration.physicsStoreRegistry(this); + PhysicsChunkTypes.registerComponentTypes(physicsStoreRegistry); + PhysicsChunkTypes.registerResourceTypes(physicsStoreRegistry); + PhysicsChunkTypes.registerSystems(physicsStoreRegistry); + PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); + ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); WorldCollisionProfilingResource.setResourceType(entityRegistry.registerResource( WorldCollisionProfilingResource.class, @@ -33,6 +47,7 @@ protected void setup() { PhysicsStoreWorldCollisionStreamingResource.class, PhysicsStoreWorldCollisionStreamingResource::new)); entityRegistry.registerSystem(new PhysicsStoreWorldCollisionProducerSystem()); + WorldCollisionCommandContributions.register(); WorldCollisionLifecycle.enable(); LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore terrain producer enabled."); } @@ -40,6 +55,8 @@ protected void setup() { @Override protected void shutdown() { WorldCollisionLifecycle.disable(); + WorldCollisionCommandContributions.unregister(); + PhysicsStoreHooks.unregisterShutdownHook(SHUTDOWN_CLEANUP); WorldCollisionProfilingResource.clearResourceType(); PhysicsStoreWorldCollisionStreamingResource.clearResourceType(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java new file mode 100644 index 00000000..ec3f1dd9 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java @@ -0,0 +1,126 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; +import dev.hytalemodding.impulse.core.internal.systems.WorldCollisionIndexSystem; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Registered PhysicsStore type handles owned by the PhysicsChunk integration module. + */ +public final class PhysicsChunkTypes { + + @Nullable + private static ComponentType terrainColliderComponentType; + @Nullable + private static ComponentType worldCollisionComponentType; + @Nullable + private static ResourceType + terrainMutationQueueResourceType; + @Nullable + private static ResourceType + terrainPayloadResourceType; + @Nullable + private static ResourceType + worldCollisionIndexResourceType; + + private PhysicsChunkTypes() { + } + + public static void registerComponentTypes( + @Nonnull ComponentRegistryProxy registry) { + terrainColliderComponentType = registry.registerComponent( + TerrainColliderComponent.class, + "TerrainCollider", + TerrainColliderComponent.CODEC); + worldCollisionComponentType = registry.registerComponent( + WorldCollisionComponent.class, + "WorldCollision", + WorldCollisionComponent.CODEC); + } + + public static void registerResourceTypes( + @Nonnull ComponentRegistryProxy registry) { + terrainMutationQueueResourceType = registry.registerResource( + PhysicsTerrainMutationQueueResource.class, + PhysicsTerrainMutationQueueResource::new); + terrainPayloadResourceType = registry.registerResource( + PhysicsTerrainPayloadResource.class, + PhysicsTerrainPayloadResource::new); + worldCollisionIndexResourceType = registry.registerResource( + PhysicsWorldCollisionIndexResource.class, + PhysicsWorldCollisionIndexResource::new); + } + + public static void registerSystems(@Nonnull ComponentRegistryProxy registry) { + registry.registerSystem(new TerrainMutationDrainSystem()); + registry.registerSystem(new WorldCollisionIndexSystem()); + registry.registerSystem(new TerrainColliderBindingSystem()); + } + + public static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physicsStore) { + Store store = physicsStore.getStore(); + if (store.isShutdown()) { + return; + } + cleanupResource(store, + PhysicsTerrainMutationQueueResource.getResourceType(), + PhysicsTerrainMutationQueueResource::clear); + cleanupResource(store, + PhysicsTerrainPayloadResource.getResourceType(), + PhysicsTerrainPayloadResource::clear); + cleanupResource(store, + PhysicsWorldCollisionIndexResource.getResourceType(), + PhysicsWorldCollisionIndexResource::clear); + } + + private static > void cleanupResource( + @Nonnull Store store, + @Nonnull ResourceType type, + @Nonnull Consumer cleanup) { + cleanup.accept(store.getResource(type)); + } + + @Nonnull + public static ComponentType + terrainColliderComponentType() { + return terrainColliderComponentType; + } + + @Nonnull + public static ComponentType + worldCollisionComponentType() { + return worldCollisionComponentType; + } + + @Nonnull + public static ResourceType + terrainMutationQueueResourceType() { + return terrainMutationQueueResourceType; + } + + @Nonnull + public static ResourceType + terrainPayloadResourceType() { + return terrainPayloadResourceType; + } + + @Nonnull + public static ResourceType + worldCollisionIndexResourceType() { + return worldCollisionIndexResourceType; + } +} From 0bba274b6407c17b5908384dcce8524aab64a776 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 09:54:12 +0200 Subject: [PATCH 309/534] feat(core): add physics store helper APIs Signed-off-by: Blovien --- .../physicschunk/PhysicsWorldCollision.java | 207 ++++++++++++++++++ .../plugin/physicsstore/PhysicsBodies.java | 50 +++++ .../plugin/physicsstore/PhysicsSpaces.java | 138 ++++++++++++ .../plugin/physicsstore/PhysicsWorlds.java | 105 +++++++++ .../plugin/projection/PhysicsAttachments.java | 101 +++++++++ 5 files changed, 601 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/PhysicsAttachments.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java new file mode 100644 index 00000000..c5b9493f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -0,0 +1,207 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3d; + +/** + * Public PhysicsChunk operations for terrain-backed world collision. + */ +public final class PhysicsWorldCollision { + + private PhysicsWorldCollision() { + } + + @Nonnull + public static WorldCollisionBuildStats rebuildAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d center, + int radius) { + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "rebuild PhysicsStore world collision"); + SpaceWorldCollisionSettings settings = requireSettings(checkedStore, spaceId); + PhysicsTerrainMutationQueueResource queue = checkedStore.getResource( + PhysicsTerrainMutationQueueResource.getResourceType()); + int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); + WorldCollisionPrewarmStats stats = streaming(world).ensureAround(world, + settings.spaceUuid(), + queue, + List.of(Objects.requireNonNull(center, "center")), + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + return withRemovedBodies(stats.buildStats(), + stats.buildStats().removedBodies() + removed); + } + + @Nonnull + public static WorldCollisionBuildStats refreshAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d center, + int radius) { + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "refresh PhysicsStore world collision"); + SpaceWorldCollisionSettings settings = requireSettings(checkedStore, spaceId); + return streaming(world).refreshAround(world, + settings.spaceUuid(), + checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + Objects.requireNonNull(center, "center"), + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + } + + @Nonnull + public static WorldCollisionPrewarmStats ensureAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Iterable centers, + int radius, + long tick) { + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "ensure PhysicsStore world collision"); + SpaceWorldCollisionSettings settings = requireSettings(checkedStore, spaceId); + return streaming(world).ensureAround(world, + settings.spaceUuid(), + checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + Objects.requireNonNull(centers, "centers"), + radius, + tick, + null, + settings.buildOptions()); + } + + public static int clearSpace(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Store checkedStore = requireMatchingWorldThread(world, + store, + "clear PhysicsStore world collision"); + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, + Objects.requireNonNull(spaceId, "spaceId")); + return clearSpaceRows(world, checkedStore, spaceUuid); + } + + @Nonnull + public static WorldCollisionStats stats(@Nonnull World world) { + Objects.requireNonNull(world, "world"); + if (!world.isInThread()) { + throw new IllegalStateException("Cannot read PhysicsChunk world-collision stats " + + "outside the owning world thread"); + } + return WorldCollisionLifecycle.isEnabled() + ? streaming(world).stats() + : new WorldCollisionStats(0, 0, 0, 0); + } + + private static void requireEnabled() { + if (!WorldCollisionLifecycle.isEnabled()) { + throw new IllegalStateException("Impulse physics chunk subplugin is disabled"); + } + } + + @Nonnull + private static Store requireMatchingWorldThread(@Nonnull World world, + @Nonnull Store store, + @Nonnull String operation) { + World checkedWorld = Objects.requireNonNull(world, "world"); + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsThreading.requireWorldThread(checkedStore, operation); + if (PhysicsThreading.world(checkedStore) != checkedWorld) { + throw new IllegalArgumentException("PhysicsStore does not belong to the supplied world"); + } + return checkedStore; + } + + @Nonnull + private static SpaceWorldCollisionSettings requireSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, + Objects.requireNonNull(spaceId, "spaceId")); + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + if (spaceRef == null || !spaceRef.isValid()) { + throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() + + " is not bound yet"); + } + WorldCollisionComponent component = + store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); + WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); + if (settings.getMode() == WorldCollisionMode.NONE) { + throw new IllegalStateException("World collision is disabled for space " + spaceId); + } + return new SpaceWorldCollisionSettings(spaceUuid, + settings.getMode(), + settings.getEntityChunkBoundaryMode(), + settings.isNativeVoxelTerrainEnabled(), + settings.getRadius(), + settings.getBodyRadius(), + settings.getTtlTicks(), + settings.getTerrainFriction(), + settings.getTerrainRestitution()); + } + + @Nonnull + private static PhysicsStoreWorldCollisionStreamingResource streaming(@Nonnull World world) { + Store entityStore = Objects.requireNonNull(world, "world") + .getEntityStore() + .getStore(); + return entityStore.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()); + } + + private static int clearSpaceRows(@Nonnull World world, + @Nonnull Store store, + @Nonnull UUID spaceUuid) { + int removed = 0; + if (WorldCollisionLifecycle.isEnabled()) { + removed = streaming(world).clearSpace(spaceUuid, + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType())); + } + int directlyRemoved = PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); + return removed != 0 ? removed : directlyRemoved; + } + + @Nonnull + private static WorldCollisionBuildStats withRemovedBodies( + @Nonnull WorldCollisionBuildStats stats, + int removedBodies) { + return new WorldCollisionBuildStats(stats.scannedBlocks(), + stats.solidBlocks(), + stats.culledInteriorBlocks(), + stats.fullCubeRuns(), + stats.detailBoxes(), + stats.colliderBodies(), + removedBodies, + stats.sectionsBuilt(), + stats.sectionsRebuilt(), + stats.voxelBodies()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java index 9cfe1af1..a5a09c17 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java @@ -5,8 +5,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.util.Collection; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -42,6 +46,40 @@ public static PhysicsBodyRegistrationView registrationView(@Nonnull Store registrationViews( + @Nonnull Store store) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body registrations"); + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationViews(); + } + + @Nonnull + public static Collection registrationViews( + @Nonnull Store store, + @Nonnull PhysicsBodyKind kind) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body registrations"); + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationViews(Objects.requireNonNull(kind, "kind")); + } + + public static int registrationCount(@Nonnull Store store) { + Store checkedStore = requireWorldThread(store, + "count copied PhysicsStore body registrations"); + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationCount(); + } + + public static int registrationCount(@Nonnull Store store, + @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + Store checkedStore = requireWorldThread(store, + "count copied PhysicsStore body registrations"); + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationCount(Objects.requireNonNull(persistenceMode, "persistenceMode")); + } + @Nullable public static PhysicsBodySnapshot snapshot(@Nonnull Store store, @Nonnull Ref bodyRef) { @@ -64,6 +102,18 @@ public static PhysicsBodySnapshot snapshot(@Nonnull Store store, .getBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); } + @Nonnull + public static PhysicsSnapshotFrame snapshotFrame(@Nonnull Store store) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body snapshot frame"); + return checkedStore.getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame(); + } + + public static int snapshotCount(@Nonnull Store store) { + return snapshotFrame(store).bodies().size(); + } + @Nonnull private static Store requireWorldThread(@Nonnull Store store, @Nonnull String operation) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 4feaa9be..a323d253 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -3,9 +3,20 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -44,6 +55,36 @@ public static boolean hasSpace(@Nonnull Store store, .hasSpace(Objects.requireNonNull(spaceId, "spaceId")); } + @Nonnull + public static SpaceId create(@Nonnull Store store, + @Nonnull BackendId backendId) { + return create(store, backendId, PhysicsSpaceSettings.defaults()); + } + + @Nonnull + public static SpaceId create(@Nonnull Store store, + @Nonnull BackendId backendId, + @Nonnull PhysicsSpaceSettings settings) { + SpaceId spaceId = SpaceId.next(); + create(store, UUID.randomUUID(), spaceId, backendId, settings); + return spaceId; + } + + @Nonnull + public static Ref create(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId spaceId, + @Nonnull BackendId backendId, + @Nonnull PhysicsSpaceSettings settings) { + Store checkedStore = requireWorldThread(store, + "create a PhysicsStore space"); + return PhysicsStoreSpaceMutations.addSpace(checkedStore, + Objects.requireNonNull(spaceUuid, "spaceUuid"), + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(backendId, "backendId"), + Objects.requireNonNull(settings, "settings")); + } + @Nonnull public static Collection spaceIds(@Nonnull Store store) { Store checkedStore = requireWorldThread(store, "list PhysicsStore spaces"); @@ -52,6 +93,103 @@ public static Collection spaceIds(@Nonnull Store store) { .spaceIds()); } + public static int count(@Nonnull Store store) { + Store checkedStore = requireWorldThread(store, "count PhysicsStore spaces"); + return checkedStore.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .size(); + } + + @Nullable + public static PhysicsSpaceSettings settings(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + return ref != null ? settings(store, ref) : null; + } + + @Nullable + public static PhysicsSpaceSettings settings(@Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore space settings"); + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (checkedRef.getStore() != checkedStore || !checkedRef.isValid()) { + return null; + } + if (checkedStore.getComponent(checkedRef, SpaceComponent.getComponentType()) == null) { + return null; + } + PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); + WorldCollisionComponent worldCollision = checkedStore.getComponent(checkedRef, + WorldCollisionComponent.getComponentType()); + if (worldCollision != null) { + worldCollision.copyTo(settings); + } + SolverSettingsComponent solverSettings = checkedStore.getComponent(checkedRef, + SolverSettingsComponent.getComponentType()); + if (solverSettings != null) { + solverSettings.copyTo(settings); + } + VisualSyncSettingsComponent visualSyncSettings = checkedStore.getComponent(checkedRef, + VisualSyncSettingsComponent.getComponentType()); + if (visualSyncSettings != null) { + visualSyncSettings.copyTo(settings); + } + VisualMaterializationSettingsComponent visualMaterializationSettings = + checkedStore.getComponent(checkedRef, + VisualMaterializationSettingsComponent.getComponentType()); + if (visualMaterializationSettings != null) { + visualMaterializationSettings.copyTo(settings); + } + CollisionLodSettingsComponent collisionLodSettings = checkedStore.getComponent(checkedRef, + CollisionLodSettingsComponent.getComponentType()); + if (collisionLodSettings != null) { + collisionLodSettings.copyTo(settings); + } + ExtensionSettingsComponent extensionSettings = checkedStore.getComponent(checkedRef, + ExtensionSettingsComponent.getComponentType()); + if (extensionSettings != null) { + extensionSettings.copyTo(settings); + } + return settings; + } + + public static void putSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsSpaceSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore space settings"); + PhysicsStoreSpaceMutations.putSpaceSettings(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putSettings(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull PhysicsSpaceSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore space settings"); + PhysicsStoreSpaceMutations.putSpaceSettings(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef"), + Objects.requireNonNull(settings, "settings")); + } + + public static void removeEmpty(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Store checkedStore = requireWorldThread(store, + "remove an empty PhysicsStore space"); + PhysicsStoreSpaceMutations.removeEmptySpace(checkedStore, + Objects.requireNonNull(spaceId, "spaceId")); + } + + public static void removeWithContents(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Store checkedStore = requireWorldThread(store, + "remove a PhysicsStore space with contents"); + PhysicsStoreTopologyMutations.removeSpaceWithContents(checkedStore, + PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"))); + } + @Nonnull private static Store requireWorldThread(@Nonnull Store store, @Nonnull String operation) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java new file mode 100644 index 00000000..495d1cab --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java @@ -0,0 +1,105 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; + +/** + * Public world-level reads and settings writes for PhysicsStore resources. + */ +public final class PhysicsWorlds { + + private PhysicsWorlds() { + } + + @Nonnull + public static PhysicsEventFrame latestEventFrame(@Nonnull Store store) { + Store checkedStore = requireWorldThread(store, + "read the latest PhysicsStore event frame"); + return checkedStore.getResource(PhysicsEventResource.getResourceType()).getLatestFrame(); + } + + @Nonnull + public static CompletionStage latestEventFrameAsync( + @Nonnull World world) { + return PhysicsThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore event frame read", + PhysicsWorlds::latestEventFrame); + } + + @Nonnull + public static PhysicsWorldSettings settings(@Nonnull Store store) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore world settings"); + return checkedStore.getResource(PhysicsWorldSettingsResource.getResourceType()) + .getSettings(); + } + + @Nonnull + public static CompletionStage settingsAsync(@Nonnull World world) { + return PhysicsThreading.enqueueReadOnWorldThread(world, + "queue PhysicsStore world settings read", + PhysicsWorlds::settings); + } + + public static void putSettings(@Nonnull Store store, + @Nonnull PhysicsWorldSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore world settings"); + PhysicsWorldSettings requested = new PhysicsWorldSettings( + Objects.requireNonNull(settings, "settings")); + validateStepModeSupported(checkedStore, requested.getStepMode()); + checkedStore.getResource(PhysicsWorldSettingsResource.getResourceType()) + .setSettings(requested); + } + + @Nonnull + public static CompletionStage putSettingsAsync(@Nonnull World world, + @Nonnull PhysicsWorldSettings settings) { + PhysicsWorldSettings requested = new PhysicsWorldSettings( + Objects.requireNonNull(settings, "settings")); + return PhysicsThreading.executeOnWorldThread(world, + "queue PhysicsStore world settings update", + store -> putSettings(store, requested)); + } + + private static void validateStepModeSupported(@Nonnull Store store, + @Nonnull PhysicsStepMode stepMode) { + if (stepMode != PhysicsStepMode.CCD) { + return; + } + List unsupportedSpaces = new ArrayList<>(); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.forEachRuntimeSpaceBinding((spaceRef, backendId, spaceHandle, backendRuntime) -> { + if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { + UUID spaceUuid = runtime.getSpaceUuid(spaceRef); + unsupportedSpaces.add((spaceUuid != null ? spaceUuid : spaceRef) + + " backend=" + backendId.value()); + } + }); + if (!unsupportedSpaces.isEmpty()) { + throw new IllegalArgumentException("CCD step mode is not supported by PhysicsStore " + + "spaces: " + unsupportedSpaces); + } + } + + @Nonnull + private static Store requireWorldThread(@Nonnull Store store, + @Nonnull String operation) { + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsThreading.requireWorldThread(checkedStore, operation); + return checkedStore; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/PhysicsAttachments.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/PhysicsAttachments.java new file mode 100644 index 00000000..bcd052ad --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/PhysicsAttachments.java @@ -0,0 +1,101 @@ +package dev.hytalemodding.impulse.core.plugin.projection; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; +import java.util.Collection; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Public EntityStore projection reads for PhysicsStore body attachments. + */ +public final class PhysicsAttachments { + + private PhysicsAttachments() { + } + + @Nonnull + public static Collection> attachments(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + return requireWorldThread(store, "read PhysicsStore body attachments") + .getResource(PhysicsProjectionIndexResource.getResourceType()) + .getAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + @Nonnull + public static Collection> attachments(@Nonnull Store store, + @Nonnull Ref bodyRef) { + return requireWorldThread(store, "read PhysicsStore body attachments") + .getResource(PhysicsProjectionIndexResource.getResourceType()) + .getAttachments(Objects.requireNonNull(bodyRef, "bodyRef")); + } + + @Nonnull + public static Collection> attachments(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + PhysicsProjectionIndexResource projection = + requireWorldThread(store, "read PhysicsStore body attachments") + .getResource(PhysicsProjectionIndexResource.getResourceType()); + return bodyRef != null && bodyRef.isValid() + ? projection.getAttachments(bodyRef) + : projection.getAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + public static boolean hasAttachments(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + return requireWorldThread(store, "check PhysicsStore body attachments") + .getResource(PhysicsProjectionIndexResource.getResourceType()) + .hasAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + public static boolean hasAttachments(@Nonnull Store store, + @Nonnull Ref bodyRef) { + return requireWorldThread(store, "check PhysicsStore body attachments") + .getResource(PhysicsProjectionIndexResource.getResourceType()) + .hasAttachments(Objects.requireNonNull(bodyRef, "bodyRef")); + } + + public static boolean hasAttachments(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + PhysicsProjectionIndexResource projection = + requireWorldThread(store, "check PhysicsStore body attachments") + .getResource(PhysicsProjectionIndexResource.getResourceType()); + return bodyRef != null && bodyRef.isValid() + ? projection.hasAttachments(bodyRef) + : projection.hasAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + @Nullable + public static Ref generatedVisualProxy(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + return requireWorldThread(store, "read PhysicsStore generated visual proxy") + .getResource(PhysicsProjectionIndexResource.getResourceType()) + .getGeneratedVisualProxy(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + @Nullable + public static Ref generatedVisualProxy(@Nonnull Store store, + @Nonnull Ref bodyRef) { + return requireWorldThread(store, "read PhysicsStore generated visual proxy") + .getResource(PhysicsProjectionIndexResource.getResourceType()) + .getGeneratedVisualProxy(Objects.requireNonNull(bodyRef, "bodyRef")); + } + + @Nonnull + private static Store requireWorldThread(@Nonnull Store store, + @Nonnull String operation) { + Store checkedStore = Objects.requireNonNull(store, "store"); + if (!checkedStore.getExternalData().getWorld().isInThread()) { + throw new IllegalStateException("Cannot " + operation + + " outside the owning EntityStore world thread"); + } + return checkedStore; + } +} From fa2d7227d6e2027e5c29729561f35bdd3aa3fc66 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:11:42 +0200 Subject: [PATCH 310/534] refactor(core): split physics integration modules Signed-off-by: Blovien --- build.gradle.kts | 24 ++++++++++------ gradle/libs.versions.toml | 2 +- impulse-core/build.gradle.kts | 12 -------- impulse-examples/build.gradle.kts | 2 +- impulse-physics-chunk/.gitignore | 1 + impulse-physics-chunk/build.gradle.kts | 28 +++++++++++++++++++ .../ImpulsePhysicsChunkPlugin.java | 15 +++++----- impulse-physics-entity/.gitignore | 1 + impulse-physics-entity/build.gradle.kts | 28 +++++++++++++++++++ .../ImpulsePhysicsEntityPlugin.java | 5 ++-- settings.gradle.kts | 2 ++ 11 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 impulse-physics-chunk/.gitignore create mode 100644 impulse-physics-chunk/build.gradle.kts rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/ImpulsePhysicsChunkPlugin.java (93%) create mode 100644 impulse-physics-entity/.gitignore create mode 100644 impulse-physics-entity/build.gradle.kts rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules => impulse-physics-entity/src/main/java/dev/hytalemodding/impulse}/physicsentity/ImpulsePhysicsEntityPlugin.java (89%) diff --git a/build.gradle.kts b/build.gradle.kts index 6cd4184c..697d9fd3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,13 +17,19 @@ version = property("version") as String val coreOnlyWorkspace = providers.gradleProperty("impulse.coreOnlyWorkspace") .map(String::toBoolean) .orElse(false) +val coreModProjects = listOf( + ":impulse-core", + ":impulse-physics-entity", + ":impulse-physics-chunk" +) +val workspaceModProjects = if (coreOnlyWorkspace.get()) { + coreModProjects +} else { + listOf(":impulse-examples") + coreModProjects +} hytaleWorkspace { - modProjects = if (coreOnlyWorkspace.get()) { - listOf(":impulse-core") - } else { - listOf(":impulse-examples", ":impulse-core") - } + modProjects = workspaceModProjects hostProject = if (coreOnlyWorkspace.get()) { ":impulse-core" } else { @@ -71,9 +77,7 @@ val stagedEarlyPluginJarDirectory = layout.projectDirectory.dir("run/earlyplugin val physicsStoreEarlyPluginEnabled = providers.gradleProperty("impulse.physicsStoreEarlyPlugin") .map(String::toBoolean) .orElse(true) -val hytaleToolProjectPaths = listOf( - ":impulse-core", - ":impulse-examples") +val hytaleToolProjectPaths = workspaceModProjects gradle.projectsEvaluated { val hytaleAssetDownloads = hytaleToolProjectPaths @@ -151,6 +155,8 @@ tasks.register("headlessTest") { ":impulse-bullet:test", ":impulse-rapier:test", ":impulse-core:test", + ":impulse-physics-entity:test", + ":impulse-physics-chunk:test", ":impulse-early-plugin:test" ) } @@ -168,7 +174,7 @@ gradle.projectsEvaluated { val runTask = this as JavaExec runTask.standardInput = System.`in` - // hytale-gradle 1.0.37 can omit project resources from run task classpaths. + // hytale-gradle can omit project resources from run task classpaths. val toolRuntimeClasspaths = hytaleToolProjectPaths.map { path -> val sourceSets = project(path).extensions.getByType() sourceSets.named("main").get().runtimeClasspath diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d4e59a58..b461370e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] libbulletjme = "23.0.0" crucible = "1.0.0" -hytale-gradle = "1.0.37" +hytale-gradle = "1.0.41" lombok = "1.18.38" joml = "1.10.5" jsr305 = "3.0.2" diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index 81731b90..132af1c5 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -102,18 +102,6 @@ hytaleTools { manifestDependencies = impulseManifestDependencies manifestOptionalDependencies = "com.ionforgelabs:crucible=*" - subPlugin ( - "ImpulsePhysicsEntity", - "dev.hytalemodding.impulse.core.plugin.modules.physicsentity.ImpulsePhysicsEntityPlugin", - false, /* disabledByDefault */ - false /* includeAssetPack */ - ) - subPlugin ( - "ImpulsePhysicsChunk", - "dev.hytalemodding.impulse.core.plugin.modules.physicschunk.ImpulsePhysicsChunkPlugin", - false, /* disabledByDefault */ - false /* includeAssetPack */ - ) subPlugin ( "ImpulseControl", "dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControlPlugin", diff --git a/impulse-examples/build.gradle.kts b/impulse-examples/build.gradle.kts index 0337f2d5..b5d6bf10 100644 --- a/impulse-examples/build.gradle.kts +++ b/impulse-examples/build.gradle.kts @@ -38,5 +38,5 @@ hytaleTools { modUrl = property("mod_website") as String modDescription = "Example plugins for Impulse" manifestServerVersion = property("hytale_version") as String - manifestDependencies = "HytaleModding:Impulse=*" + manifestDependencies = "HytaleModding:Impulse=*,HytaleModding:ImpulsePhysicsEntity=*,HytaleModding:ImpulsePhysicsChunk=*" } diff --git a/impulse-physics-chunk/.gitignore b/impulse-physics-chunk/.gitignore new file mode 100644 index 00000000..8c25fdb9 --- /dev/null +++ b/impulse-physics-chunk/.gitignore @@ -0,0 +1 @@ +/src/main/resources/manifest.json diff --git a/impulse-physics-chunk/build.gradle.kts b/impulse-physics-chunk/build.gradle.kts new file mode 100644 index 00000000..90c353bb --- /dev/null +++ b/impulse-physics-chunk/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id("com.azuredoom.hytale-tools") +} + +version = rootProject.version +val impulsePhysicsChunkDependencies = listOf( + "HytaleModding:Impulse=*", + "Hytale:AssetModule=*", + "Hytale:BlockTypeModule=*", + "Hytale:EntityModule=*", + "Hytale:LegacyModule=*" +).joinToString(",") + +dependencies { + compileOnly(project(":impulse-core")) + compileOnly(project(":impulse-early-plugin")) + testImplementation(project(":impulse-core")) +} + +hytaleTools { + modId = "ImpulsePhysicsChunk" + mainClass = "dev.hytalemodding.impulse.physicschunk.ImpulsePhysicsChunkPlugin" + modCredits = property("mod_credits") as String + modUrl = property("mod_website") as String + modDescription = "Impulse ChunkStore world-collision integration" + manifestServerVersion = property("hytale_version") as String + manifestDependencies = impulsePhysicsChunkDependencies +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java index 9e2e4155..01a46b2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; +package dev.hytalemodding.impulse.physicschunk; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.logger.HytaleLogger; @@ -6,19 +6,20 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreHooks; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.WorldCollisionCommandContributions; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.WorldCollisionCommandContributions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; -import java.util.logging.Level; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; +import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import java.util.function.Consumer; +import java.util.logging.Level; import javax.annotation.Nonnull; /** - * Subplugin that enables Impulse world collision. + * Plugin module that enables Impulse ChunkStore world-collision integration. */ public final class ImpulsePhysicsChunkPlugin extends JavaPlugin { diff --git a/impulse-physics-entity/.gitignore b/impulse-physics-entity/.gitignore new file mode 100644 index 00000000..8c25fdb9 --- /dev/null +++ b/impulse-physics-entity/.gitignore @@ -0,0 +1 @@ +/src/main/resources/manifest.json diff --git a/impulse-physics-entity/build.gradle.kts b/impulse-physics-entity/build.gradle.kts new file mode 100644 index 00000000..d1ffba10 --- /dev/null +++ b/impulse-physics-entity/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id("com.azuredoom.hytale-tools") +} + +version = rootProject.version +val impulsePhysicsEntityDependencies = listOf( + "HytaleModding:Impulse=*", + "Hytale:AssetModule=*", + "Hytale:BlockTypeModule=*", + "Hytale:EntityModule=*", + "Hytale:LegacyModule=*" +).joinToString(",") + +dependencies { + compileOnly(project(":impulse-core")) + compileOnly(project(":impulse-early-plugin")) + testImplementation(project(":impulse-core")) +} + +hytaleTools { + modId = "ImpulsePhysicsEntity" + mainClass = "dev.hytalemodding.impulse.physicsentity.ImpulsePhysicsEntityPlugin" + modCredits = property("mod_credits") as String + modUrl = property("mod_website") as String + modDescription = "Impulse EntityStore projection integration" + manifestServerVersion = property("hytale_version") as String + manifestDependencies = impulsePhysicsEntityDependencies +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/ImpulsePhysicsEntityPlugin.java b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java similarity index 89% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/ImpulsePhysicsEntityPlugin.java rename to impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java index a8d9ba1c..3c4cc971 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/ImpulsePhysicsEntityPlugin.java +++ b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; +package dev.hytalemodding.impulse.physicsentity; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.server.core.plugin.JavaPlugin; @@ -10,10 +10,11 @@ import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; /** - * Subplugin that integrates authoritative PhysicsStore bodies with EntityStore entities. + * Plugin module that integrates authoritative PhysicsStore bodies with EntityStore entities. */ public final class ImpulsePhysicsEntityPlugin extends JavaPlugin { diff --git a/settings.gradle.kts b/settings.gradle.kts index 614eaaa8..923c87fa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,5 +31,7 @@ include("impulse-native-loader") include("impulse-bullet") include("impulse-rapier") include("impulse-core") +include("impulse-physics-entity") +include("impulse-physics-chunk") include("impulse-examples") include("impulse-early-plugin") From a3e825b6997474c4f00d9d1454eb107348bce2bd Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:23:04 +0200 Subject: [PATCH 311/534] refactor(core): move physics chunk commands to module Signed-off-by: Blovien --- impulse-physics-chunk/build.gradle.kts | 1 + .../impulse/physicschunk/ImpulsePhysicsChunkPlugin.java | 2 +- .../physicschunk/commands/CollisionLodSettingsCommand.java | 2 +- .../impulse}/physicschunk/commands/WorldCollisionCommand.java | 2 +- .../commands/WorldCollisionCommandContributions.java | 2 +- .../physicschunk/commands/WorldCollisionPerfCommand.java | 2 +- .../physicschunk/commands/WorldCollisionPerfReportCommand.java | 2 +- .../physicschunk/commands/WorldCollisionPerfResetCommand.java | 2 +- .../physicschunk/commands/WorldCollisionPerfToggleCommand.java | 2 +- .../physicschunk/commands/WorldCollisionSettingsCommand.java | 2 +- 10 files changed, 10 insertions(+), 9 deletions(-) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/CollisionLodSettingsCommand.java (99%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/WorldCollisionCommand.java (83%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/WorldCollisionCommandContributions.java (91%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/WorldCollisionPerfCommand.java (85%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/WorldCollisionPerfReportCommand.java (99%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/WorldCollisionPerfResetCommand.java (96%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/WorldCollisionPerfToggleCommand.java (96%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules => impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse}/physicschunk/commands/WorldCollisionSettingsCommand.java (99%) diff --git a/impulse-physics-chunk/build.gradle.kts b/impulse-physics-chunk/build.gradle.kts index 90c353bb..bbede0b2 100644 --- a/impulse-physics-chunk/build.gradle.kts +++ b/impulse-physics-chunk/build.gradle.kts @@ -12,6 +12,7 @@ val impulsePhysicsChunkDependencies = listOf( ).joinToString(",") dependencies { + compileOnly(project(":impulse-api")) compileOnly(project(":impulse-core")) compileOnly(project(":impulse-early-plugin")) testImplementation(project(":impulse-core")) diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java index 01a46b2e..d99a69dc 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.WorldCollisionCommandContributions; +import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java index 7f7db9f8..845743b4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommand.java similarity index 83% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommand.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommand.java index ccf17673..516b1081 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommandContributions.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommandContributions.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java index 8142f593..1b64a6d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionCommandContributions.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfCommand.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfCommand.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfCommand.java index 2378be19..16e9d313 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfReportCommand.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java index 1bbe2e79..30056c9f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfResetCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfResetCommand.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java index e0679ce6..f8135115 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfResetCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfToggleCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfToggleCommand.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java index 1483d631..310d15e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionPerfToggleCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionSettingsCommand.java rename to impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java index 2e19fc15..02051a61 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/WorldCollisionSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; From de7beb11a7311e115b87300fc1037c2074cf2534 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:26:56 +0200 Subject: [PATCH 312/534] refactor(core): delegate world collision resource helpers Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 108 +++--------------- 1 file changed, 18 insertions(+), 90 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 4c9c7367..bbdde716 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -21,7 +21,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; @@ -41,6 +40,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; @@ -1066,23 +1066,11 @@ public WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world @Nonnull Vector3d center, int radius) { if (isAuthoritativePhysicsStoreActive()) { - requireWorldCollisionLifecycleEnabled(); - Store store = authoritativePhysicsStore("rebuild world collision"); - SpaceWorldCollisionSettings settings = - requireAuthoritativeWorldCollisionSettings(store, spaceId); - PhysicsTerrainMutationQueueResource queue = - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); - int removed = clearAuthoritativeWorldCollisionSpace(store, settings.spaceUuid()); - WorldCollisionPrewarmStats stats = authoritativeWorldCollisionStreaming() - .ensureAround(world, - settings.spaceUuid(), - queue, - List.of(center), - radius, - Math.max(0L, world.getTick()), - null, - settings.buildOptions()); - return withRemovedBodies(stats.buildStats(), stats.buildStats().removedBodies() + removed); + return PhysicsWorldCollision.rebuildAround(world, + authoritativePhysicsStore("rebuild world collision"), + spaceId, + center, + radius); } requireLegacyMutationAllowed("rebuild world collision"); requireWorldCollisionLifecycleEnabled(); @@ -1107,18 +1095,11 @@ public WorldCollisionBuildStats refreshWorldCollisionAround(@Nonnull World world @Nonnull Vector3d center, int radius) { if (isAuthoritativePhysicsStoreActive()) { - requireWorldCollisionLifecycleEnabled(); - Store store = authoritativePhysicsStore("refresh world collision"); - SpaceWorldCollisionSettings settings = - requireAuthoritativeWorldCollisionSettings(store, spaceId); - return authoritativeWorldCollisionStreaming().refreshAround(world, - settings.spaceUuid(), - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + return PhysicsWorldCollision.refreshAround(world, + authoritativePhysicsStore("refresh world collision"), + spaceId, center, - radius, - Math.max(0L, world.getTick()), - null, - settings.buildOptions()); + radius); } requireLegacyMutationAllowed("refresh world collision"); requireWorldCollisionLifecycleEnabled(); @@ -1144,19 +1125,12 @@ public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World worl int radius, long tick) { if (isAuthoritativePhysicsStoreActive()) { - requireWorldCollisionLifecycleEnabled(); - Objects.requireNonNull(centers, "centers"); - Store store = authoritativePhysicsStore("ensure world collision"); - SpaceWorldCollisionSettings settings = - requireAuthoritativeWorldCollisionSettings(store, spaceId); - return authoritativeWorldCollisionStreaming().ensureAround(world, - settings.spaceUuid(), - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + return PhysicsWorldCollision.ensureAround(world, + authoritativePhysicsStore("ensure world collision"), + spaceId, centers, radius, - tick, - null, - settings.buildOptions()); + tick); } requireLegacyMutationAllowed("ensure world collision"); Objects.requireNonNull(centers, "centers"); @@ -1179,9 +1153,9 @@ public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World worl @Override public int clearWorldCollision(@Nonnull SpaceId spaceId) { if (isAuthoritativePhysicsStoreActive()) { - Store store = authoritativePhysicsStore("clear world collision"); - UUID spaceUuid = requireSpaceUuid(store, spaceId); - return clearAuthoritativeWorldCollisionSpace(store, spaceUuid); + return PhysicsWorldCollision.clearSpace(requireAuthoritativeWorld("clear world collision"), + authoritativePhysicsStore("clear world collision"), + spaceId); } requireLegacyMutationAllowed("clear world collision"); return callDirectRuntime("clear world collision", () -> { @@ -1194,9 +1168,7 @@ public int clearWorldCollision(@Nonnull SpaceId spaceId) { @Override public WorldCollisionStats getWorldCollisionStats() { if (isAuthoritativePhysicsStoreActive()) { - return WorldCollisionLifecycle.isEnabled() - ? authoritativeWorldCollisionStreaming().stats() - : new WorldCollisionStats(0, 0, 0, 0); + return PhysicsWorldCollision.stats(requireAuthoritativeWorld("read world collision stats")); } return callDirectRuntime("read world collision stats", collisionRuntime::getStats); } @@ -1211,34 +1183,6 @@ private PhysicsStoreWorldCollisionStreamingResource authoritativeWorldCollisionS return entityStore.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()); } - @Nonnull - private SpaceWorldCollisionSettings requireAuthoritativeWorldCollisionSettings( - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - UUID spaceUuid = requireSpaceUuid(store, spaceId); - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - if (spaceRef == null || !spaceRef.isValid()) { - throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() - + " is not bound yet"); - } - WorldCollisionComponent component = - store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); - WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); - if (settings.getMode() == WorldCollisionMode.NONE) { - throw new IllegalStateException("World collision is disabled for space " + spaceId); - } - return new SpaceWorldCollisionSettings(spaceUuid, - settings.getMode(), - settings.getEntityChunkBoundaryMode(), - settings.isNativeVoxelTerrainEnabled(), - settings.getRadius(), - settings.getBodyRadius(), - settings.getTtlTicks(), - settings.getTerrainFriction(), - settings.getTerrainRestitution()); - } - private void clearAuthoritativeWorldCollisionStreaming(@Nonnull Store store) { if (!WorldCollisionLifecycle.isEnabled() || owningStore == null) { return; @@ -1262,22 +1206,6 @@ private int clearAuthoritativeWorldCollisionSpace(@Nonnull Store s return removed != 0 ? removed : directlyRemoved; } - @Nonnull - private static WorldCollisionBuildStats withRemovedBodies( - @Nonnull WorldCollisionBuildStats stats, - int removedBodies) { - return new WorldCollisionBuildStats(stats.scannedBlocks(), - stats.solidBlocks(), - stats.culledInteriorBlocks(), - stats.fullCubeRuns(), - stats.detailBoxes(), - stats.colliderBodies(), - removedBodies, - stats.sectionsBuilt(), - stats.sectionsRebuilt(), - stats.voxelBodies()); - } - public void disableWorldCollisionLifecycle() { if (isAuthoritativePhysicsStoreActive()) { return; From 38df47edc2712825af54de0cfa9eb1c61f8763b3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:31:23 +0200 Subject: [PATCH 313/534] refactor(core): centralize module system registration Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkTypes.java | 24 +++++++++++++++++++ .../physicsentity/PhysicsEntityTypes.java | 15 ++++++++++++ .../ImpulsePhysicsChunkPlugin.java | 15 +++--------- .../ImpulsePhysicsEntityPlugin.java | 13 +--------- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java index ec3f1dd9..c4725928 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java @@ -5,7 +5,11 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; @@ -71,6 +75,26 @@ public static void registerSystems(@Nonnull ComponentRegistryProxy registry.registerSystem(new TerrainColliderBindingSystem()); } + public static void registerEntityStoreResourceTypes( + @Nonnull ComponentRegistryProxy registry) { + WorldCollisionProfilingResource.setResourceType(registry.registerResource( + WorldCollisionProfilingResource.class, + WorldCollisionProfilingResource::new)); + PhysicsStoreWorldCollisionStreamingResource.setResourceType(registry.registerResource( + PhysicsStoreWorldCollisionStreamingResource.class, + PhysicsStoreWorldCollisionStreamingResource::new)); + } + + public static void registerEntityStoreSystems( + @Nonnull ComponentRegistryProxy registry) { + registry.registerSystem(new PhysicsStoreWorldCollisionProducerSystem()); + } + + public static void clearEntityStoreResourceTypes() { + WorldCollisionProfilingResource.clearResourceType(); + PhysicsStoreWorldCollisionStreamingResource.clearResourceType(); + } + public static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physicsStore) { Store store = physicsStore.getStore(); if (store.isShutdown()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index cc4e68d5..0dfbfcfd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -11,6 +11,12 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; +import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; +import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -79,6 +85,15 @@ public static void registerSystemGroups(@Nonnull ComponentRegistryProxy registry) { + registry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); + registry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); + registry.registerSystem(new PhysicsSyncSystem()); + registry.registerSystem(new PhysicsDebugSystem()); + registry.registerSystem(new PhysicsStoreEventPublicationSystem()); + registry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); + } + @Nonnull public static ComponentType bodyAttachmentComponentType() { return bodyAttachmentComponentType; diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java index d99a69dc..dbb3d347 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -6,11 +6,8 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; @@ -41,13 +38,8 @@ protected void setup() { PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - WorldCollisionProfilingResource.setResourceType(entityRegistry.registerResource( - WorldCollisionProfilingResource.class, - WorldCollisionProfilingResource::new)); - PhysicsStoreWorldCollisionStreamingResource.setResourceType(entityRegistry.registerResource( - PhysicsStoreWorldCollisionStreamingResource.class, - PhysicsStoreWorldCollisionStreamingResource::new)); - entityRegistry.registerSystem(new PhysicsStoreWorldCollisionProducerSystem()); + PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); + PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); WorldCollisionCommandContributions.register(); WorldCollisionLifecycle.enable(); LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore terrain producer enabled."); @@ -58,7 +50,6 @@ protected void shutdown() { WorldCollisionLifecycle.disable(); WorldCollisionCommandContributions.unregister(); PhysicsStoreHooks.unregisterShutdownHook(SHUTDOWN_CLEANUP); - WorldCollisionProfilingResource.clearResourceType(); - PhysicsStoreWorldCollisionStreamingResource.clearResourceType(); + PhysicsChunkTypes.clearEntityStoreResourceTypes(); } } diff --git a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java index 3c4cc971..003671a1 100644 --- a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java +++ b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java @@ -4,12 +4,6 @@ import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; -import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; @@ -29,11 +23,6 @@ protected void setup() { PhysicsEntityTypes.registerResourceTypes(entityRegistry); PhysicsEntityTypes.registerEventTypes(entityRegistry); PhysicsEntityTypes.registerSystemGroups(entityRegistry); - entityRegistry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); - entityRegistry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); - entityRegistry.registerSystem(new PhysicsSyncSystem()); - entityRegistry.registerSystem(new PhysicsDebugSystem()); - entityRegistry.registerSystem(new PhysicsStoreEventPublicationSystem()); - entityRegistry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); + PhysicsEntityTypes.registerSystems(entityRegistry); } } From de66adc2ec0aa63659d09eb39c583bf16b8b9833 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:38:38 +0200 Subject: [PATCH 314/534] test(core): migrate physics module tests Signed-off-by: Blovien --- .../CleanCommandLifecycleGuardTest.java | 24 ++++ ...mpulseCommandContributionRegistryTest.java | 31 ----- .../internal/commands/SpaceSelectionTest.java | 2 +- .../ImpulseSubPluginRegistrationTest.java | 10 +- .../WorldCollisionLifecycleTest.java | 2 +- .../WorldCollisionStreamingBoundsTest.java | 4 +- .../WorldVoxelCollisionCacheTest.java | 14 +-- .../WorldCollisionProfilingResourceTest.java | 8 +- ...icsStoreOwnerLaneSnapshotBoundaryTest.java | 19 +++ ...csStoreRuntimeBoundarySourceGuardTest.java | 14 +-- .../PersistentPhysicsStoreResourceTest.java | 2 +- .../PhysicsTypeRegistrationApiTest.java | 33 +++++ .../resources/PhysicsSpaceSettingsTest.java | 14 +-- .../PhysicsStepSchedulerResourceTest.java | 113 ++++++++++++++++++ .../PhysicsStoreResourceIndexTest.java | 15 ++- .../systems/debug/PhysicsDebugSystemTest.java | 2 +- .../systems/sync/PhysicsSyncSystemTest.java | 6 +- .../components/BodyCommandComponentTest.java | 3 +- .../BodyAttachmentComponentTest.java | 4 +- .../ExampleBlockEntityVisualsTest.java | 2 +- .../ExamplePhysicsUtilsTest.java | 33 +++-- impulse-physics-chunk/build.gradle.kts | 9 ++ ...sChunkCommandContributionRegistryTest.java | 50 ++++++++ .../WorldCollisionPerfReportCommandTest.java | 2 +- 24 files changed, 316 insertions(+), 100 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/WorldCollisionLifecycleTest.java (92%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/WorldCollisionStreamingBoundsTest.java (92%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/WorldVoxelCollisionCacheTest.java (97%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/{worldcollision => physicschunk}/profiling/WorldCollisionProfilingResourceTest.java (95%) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{physicsstore => }/resources/PhysicsStoreResourceIndexTest.java (91%) rename impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/{commands => utils}/ExampleBlockEntityVisualsTest.java (96%) rename impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/{commands => utils}/ExamplePhysicsUtilsTest.java (85%) create mode 100644 impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java rename {impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision => impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk}/commands/WorldCollisionPerfReportCommandTest.java (97%) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java new file mode 100644 index 00000000..0224b4c7 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -0,0 +1,24 @@ +package dev.hytalemodding.impulse.core.internal.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class CleanCommandLifecycleGuardTest { + + @Test + void cleanCommandDoesNotRemoveEveryBodyAttachmentEntity() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java")); + + assertTrue(source.contains("cleanAttachedEntity(")); + assertTrue(source.contains("shouldRemoveEntityWhenBodyMissing()")); + assertFalse(source.contains("removedEntities.incrementAndGet(REMOVED_BODY_ENTITIES);\n" + + " commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), " + + "RemoveReason.REMOVE);")); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java index 5dcd1ab5..94f54bb9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java @@ -1,10 +1,8 @@ package dev.hytalemodding.impulse.core.internal.commands; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.server.core.command.system.AbstractCommand; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands.WorldCollisionCommandContributions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -23,35 +21,6 @@ void coreRootDoesNotOwnWorldCollisionCommandsByDefault() { assertFalse(settings(root).getSubCommands().containsKey("collision-lod")); } - @Test - void worldCollisionContributesCommandsUnderImpulseRoot() { - WorldCollisionCommandContributions.register(); - - ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); - - AbstractCommand worldCollision = root.getSubCommands().get("worldcollision"); - assertTrue(root.getSubCommands().containsKey("worldcollision")); - assertTrue(worldCollision.getSubCommands().containsKey("settings")); - assertTrue(worldCollision.getSubCommands().containsKey("perf")); - assertTrue(settings(root).getSubCommands().containsKey("collision-lod")); - } - - @Test - void worldCollisionContributionsAreIdempotentAndRemovable() { - WorldCollisionCommandContributions.register(); - WorldCollisionCommandContributions.register(); - - ImpulseCommand contributed = ImpulseCommandContributionRegistry.createRootCommandForTests(); - assertTrue(contributed.getSubCommands().containsKey("worldcollision")); - assertTrue(settings(contributed).getSubCommands().containsKey("collision-lod")); - - WorldCollisionCommandContributions.unregister(); - - ImpulseCommand removed = ImpulseCommandContributionRegistry.createRootCommandForTests(); - assertFalse(removed.getSubCommands().containsKey("worldcollision")); - assertFalse(settings(removed).getSubCommands().containsKey("collision-lod")); - } - private static AbstractCommand settings(ImpulseCommand root) { return root.getSubCommands().get("settings"); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java index 5f3dcef6..b9d0f202 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelectionTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import java.util.UUID; import org.junit.jupiter.api.Test; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java index e03d8c4a..220f6c7d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java @@ -39,11 +39,6 @@ void preparesEverySubPluginManifestForDynamicLoad() { "Impulse", "dev.hytalemodding.impulse.core.ImpulsePlugin", List.of( - manifest(null, - "ImpulseWorldCollision", - "dev.hytalemodding.impulse.core.plugin.modules.worldcollision.ImpulseWorldCollisionPlugin", - List.of(), - false), manifest(null, "ImpulseControl", "dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControlPlugin", @@ -54,9 +49,8 @@ void preparesEverySubPluginManifestForDynamicLoad() { List prepared = ImpulseSubPluginRegistration.prepareSubPluginManifests(parent); - assertEquals(2, prepared.size()); - assertPreparedSubPlugin(prepared.get(0), "ImpulseWorldCollision", false); - assertPreparedSubPlugin(prepared.get(1), "ImpulseControl", false); + assertEquals(1, prepared.size()); + assertPreparedSubPlugin(prepared.getFirst(), "ImpulseControl", false); } private static void assertPreparedSubPlugin(PluginManifest manifest, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycleTest.java similarity index 92% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycleTest.java index bb8e1e07..b5800bfc 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycleTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionStreamingBoundsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBoundsTest.java similarity index 92% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionStreamingBoundsTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBoundsTest.java index 8a7da48a..c68c20b1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldCollisionStreamingBoundsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBoundsTest.java @@ -1,9 +1,9 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionStreamingBounds; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionStreamingBounds; import org.joml.Vector3f; import org.junit.jupiter.api.Test; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java similarity index 97% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCacheTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java index c4941fab..3f9f5fe4 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/WorldVoxelCollisionCacheTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -15,14 +15,14 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.CombineCall; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.VoxelTerrainCall; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldCollisionStreamingBounds; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionStreamingBounds; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.SectionCollisionGeometry.BoxCollider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.lang.reflect.Constructor; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java similarity index 95% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResourceTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java index 6024c0a4..f49e2e61 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/profiling/WorldCollisionProfilingResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -9,9 +9,9 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.WorldVoxelCollisionCache.BuildStats; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.worldcollision.profiling.WorldCollisionProfilingResource.MissingSectionReason; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.MissingSectionReason; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java new file mode 100644 index 00000000..92b028ce --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java @@ -0,0 +1,19 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class PhysicsStoreOwnerLaneSnapshotBoundaryTest { + + @Test + void completedStepPublicationDoesNotReadBackendBodiesOnWorldThread() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java")); + + assertFalse(source.contains("backendRuntime.snapshotBodies")); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java index e59dee4a..e33fe67d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java @@ -21,7 +21,7 @@ void spaceMutationRuntimeCleanupDoesNotResolveRuntimeBindingsByUuid() throws IOE @Test void staleBodyCleanupDoesNotFallbackToRuntimeUuidLookups() throws IOException { String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/StaleBodyRemovalSystem.java")); + "src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java")); assertFalse(source.contains("runtime.getJointHandle(joint.jointUuid())")); assertFalse(source.contains("runtime.getJointSpaceHandle(joint.jointUuid())")); @@ -32,19 +32,19 @@ void staleBodyCleanupDoesNotFallbackToRuntimeUuidLookups() throws IOException { @Test void backendAccessDoesNotResolveRuntimeSpacesByUuid() throws IOException { String backendAccess = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreBackendAccess.java")); + "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java")); String diagnostics = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsStoreDiagnostics.java")); + "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java")); assertFalse(backendAccess.contains("runtime.getSpaceHandle(spaceUuid)")); assertFalse(backendAccess.contains("runtime.getSpaceBackendId(spaceUuid)")); - assertFalse(diagnostics.contains("PhysicsStoreBackendAccess.space(runtime, spaceUuid)")); + assertFalse(diagnostics.contains("PhysicsBackendAccess.space(runtime, spaceUuid)")); } @Test void completedStepPublicationIteratesRuntimeSpacesByRef() throws IOException { String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/CompletedStepPublicationSystem.java")); + "src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java")); assertFalse(source.contains("runtime.forEachSpaceBinding")); assertFalse(source.contains("runtime.hasTerrainBodyHandles(rowUuid)")); @@ -53,7 +53,7 @@ void completedStepPublicationIteratesRuntimeSpacesByRef() throws IOException { @Test void terrainNeighborStitchingDoesNotResolveRuntimeBindingsByUuid() throws IOException { String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/systems/TerrainColliderBindingSystem.java")); + "src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java")); assertFalse(source.contains("runtime.getTerrainVoxelBodyHandle(neighborUuid)")); assertFalse(source.contains("runtime.getTerrainSpaceHandle(neighborUuid)")); @@ -79,7 +79,7 @@ void legacyWorldResourceFacadeDoesNotIterateRuntimeSpacesByUuid() throws IOExcep @Test void runtimeResourceDoesNotExposeUuidRuntimeReadApis() throws IOException { String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsRuntimeResource.java")); + "src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java")); assertFalse(source.contains("getSpaceHandle(@Nonnull UUID")); assertFalse(source.contains("getSpaceBackendId(@Nonnull UUID")); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java index 80293f22..51d5a3b7 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.persistence; +package dev.hytalemodding.impulse.core.internal.persistence; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java new file mode 100644 index 00000000..db01d12a --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java @@ -0,0 +1,33 @@ +package dev.hytalemodding.impulse.core.internal.registration; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class PhysicsTypeRegistrationApiTest { + + @Test + void typeRegistriesExposeCentralRegistrationWithoutPublicSetters() throws NoSuchMethodException { + assertNotNull(PhysicsComponentTypes.class.getDeclaredMethod("registerComponentTypes", + ComponentRegistryProxy.class)); + assertNotNull(PhysicsResourceTypes.class.getDeclaredMethod("registerResourceTypes", + ComponentRegistryProxy.class)); + + assertFalse(hasPublicSetter(PhysicsComponentTypes.class)); + assertFalse(hasPublicSetter(PhysicsResourceTypes.class)); + } + + private static boolean hasPublicSetter(Class type) { + return Arrays.stream(type.getDeclaredMethods()) + .filter(method -> Modifier.isPublic(method.getModifiers())) + .map(Method::getName) + .anyMatch(name -> name.startsWith("set")); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java index 0c933339..b1afc6e3 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java @@ -8,13 +8,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.codec.ExtraInfo; -import dev.hytalemodding.impulse.core.internal.physicsstore.persistence.PersistentSpaceDto; -import dev.hytalemodding.impulse.core.plugin.modules.worldcollision.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java new file mode 100644 index 00000000..adf2dc58 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java @@ -0,0 +1,113 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class PhysicsStepSchedulerResourceTest { + + @Test + void submittedStepRunsAsynchronouslyAndSkipsWhilePending() throws Exception { + PhysicsStepSchedulerResource scheduler = new PhysicsStepSchedulerResource(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + PhysicsStepSchedulerResource.StepInput input = scheduler.acceptStepInput(0.05f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.10f); + assertTrue(scheduler.submitStep(input, () -> { + entered.countDown(); + awaitOrFail(release); + return new PhysicsStepSchedulerResource.CompletedStep(1, + 2, + 123L, + PhysicsStepPhaseStats.unavailable()); + }, 10L)); + + assertTrue(entered.await(5, TimeUnit.SECONDS)); + assertTrue(scheduler.isStepPending()); + + PhysicsStepSchedulerResource.TickDecision skipped = scheduler.beforeStoreTick(0.05f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.10f, + 20L); + assertFalse(skipped.shouldTick()); + assertEquals(10L, skipped.pendingStepAgeNanos()); + assertEquals(0.05f, skipped.backlogDtSeconds(), 0.0001f); + assertNull(scheduler.pollCompletedStep()); + + release.countDown(); + PhysicsStepSchedulerResource.TickDecision allowed = awaitAllowedTick(scheduler); + assertTrue(allowed.shouldTick()); + + PhysicsStepSchedulerResource.CompletedStep completed = scheduler.pollCompletedStep(); + assertNotNull(completed); + assertEquals(1, completed.spaces()); + assertEquals(2, completed.substeps()); + assertEquals(123L, completed.stepSubmitNanos()); + assertEquals(0.05f, completed.input().submittedDtSeconds(), 0.0001f); + assertFalse(scheduler.isStepPending()); + scheduler.close(); + } + + @Test + void whenIdleCompletesAfterPendingStepFinishes() throws Exception { + PhysicsStepSchedulerResource scheduler = new PhysicsStepSchedulerResource(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + PhysicsStepSchedulerResource.StepInput input = scheduler.acceptStepInput(0.05f, + PhysicsStepSchedulingMode.DROP_PENDING_DT, + 0.10f); + assertTrue(scheduler.submitStep(input, () -> { + entered.countDown(); + awaitOrFail(release); + return new PhysicsStepSchedulerResource.CompletedStep(1, + 1, + 10L, + PhysicsStepPhaseStats.unavailable()); + }, 10L)); + assertTrue(entered.await(5, TimeUnit.SECONDS)); + + CompletableFuture idle = scheduler.whenIdle().toCompletableFuture(); + assertFalse(idle.isDone()); + + release.countDown(); + idle.get(5, TimeUnit.SECONDS); + assertFalse(scheduler.isStepPending()); + scheduler.close(); + } + + private static PhysicsStepSchedulerResource.TickDecision awaitAllowedTick( + PhysicsStepSchedulerResource scheduler) throws InterruptedException { + for (int attempt = 0; attempt < 50; attempt++) { + PhysicsStepSchedulerResource.TickDecision decision = scheduler.beforeStoreTick(0.05f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.10f, + 30L + attempt); + if (decision.shouldTick()) { + return decision; + } + Thread.sleep(10L); + } + throw new AssertionError("Scheduler did not allow the tick after step completion"); + } + + private static void awaitOrFail(CountDownLatch latch) { + try { + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + throw new AssertionError("Interrupted while waiting for scheduler test latch", exception); + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java similarity index 91% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index 8ee4ced0..99f6fe6c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.resources; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -14,8 +14,11 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.physicsstore.snapshots.PhysicsStoreSnapshotFrame; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -123,7 +126,7 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000005"); UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000006"); - PhysicsStoreBodySnapshot body = new PhysicsStoreBodySnapshot(bodyUuid, + PhysicsBodySnapshot body = new PhysicsBodySnapshot(bodyUuid, spaceUuid, PhysicsBodyType.KINEMATIC, new Vector3f(1.0f, 2.0f, 3.0f), @@ -132,7 +135,7 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { new Vector3f(), 0.25f, false); - PhysicsStoreSnapshotFrame frame = new PhysicsStoreSnapshotFrame(11L, 0.05f, List.of(body)); + PhysicsSnapshotFrame frame = new PhysicsSnapshotFrame(11L, 0.05f, List.of(body)); resource.publish(frame); @@ -142,7 +145,7 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { resource.clear(); - assertEquals(PhysicsStoreSnapshotFrame.EMPTY, resource.getLatestFrame()); + assertEquals(PhysicsSnapshotFrame.EMPTY, resource.getLatestFrame()); assertNull(resource.getBody(bodyUuid)); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java index 5d721206..db37f872 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java @@ -12,7 +12,7 @@ import dev.hytalemodding.impulse.api.PhysicsContact; import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java index 0db92664..49c0077d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java @@ -5,9 +5,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.UUID; import org.joml.Quaterniond; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java index c69f0b04..036d49d5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.components; +package dev.hytalemodding.impulse.core.plugin.components; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; @@ -7,6 +7,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import org.joml.Vector3f; import org.junit.jupiter.api.Test; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java index cefdaf5d..2285eb19 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore.projection; +package dev.hytalemodding.impulse.core.plugin.projection; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.UUID; + +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisualsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisualsTest.java similarity index 96% rename from impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisualsTest.java rename to impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisualsTest.java index 14fa2fe2..c9d25497 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExampleBlockEntityVisualsTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisualsTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.examples.commands; +package dev.hytalemodding.impulse.examples.utils; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtilsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java similarity index 85% rename from impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtilsTest.java rename to impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java index e4b8c261..86a4a30f 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/ExamplePhysicsUtilsTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java @@ -1,9 +1,10 @@ -package dev.hytalemodding.impulse.examples.commands; +package dev.hytalemodding.impulse.examples.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.server.core.modules.entity.EntityModule; import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; @@ -11,14 +12,15 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; +import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import java.lang.reflect.Field; import javax.annotation.Nonnull; + import org.joml.Vector3d; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -28,15 +30,21 @@ class ExamplePhysicsUtilsTest { private ComponentRegistry registry; private Object previousEntityModule; - private Object previousImpulsePlugin; + private Object previousBodyAttachmentComponentType; @BeforeEach void registerComponentTypes() throws Exception { previousEntityModule = staticField(EntityModule.class, "instance").get(null); - previousImpulsePlugin = staticField(ImpulsePlugin.class, "instance").get(null); + Field bodyAttachmentTypeField = + staticField(PhysicsEntityTypes.class, "bodyAttachmentComponentType"); + previousBodyAttachmentComponentType = bodyAttachmentTypeField.get(null); registry = new ComponentRegistry<>(); registerEntityModuleTypes(); - registerImpulsePluginTypes(); + ComponentType bodyAttachmentType = + registry.registerComponent(BodyAttachmentComponent.class, + "BodyAttachment", + BodyAttachmentComponent.CODEC); + bodyAttachmentTypeField.set(null, bodyAttachmentType); ControlLifecycle.enable(); ImpulseControllableComponent.setComponentType(registry.registerComponent( ImpulseControllableComponent.class, @@ -53,7 +61,8 @@ void clearComponentTypes() throws Exception { ImpulseControllableComponent.clearComponentType(); PhysicsControlSessionComponent.clearComponentType(); staticField(EntityModule.class, "instance").set(null, previousEntityModule); - staticField(ImpulsePlugin.class, "instance").set(null, previousImpulsePlugin); + staticField(PhysicsEntityTypes.class, "bodyAttachmentComponentType") + .set(null, previousBodyAttachmentComponentType); registry.shutdown(); } @@ -75,16 +84,6 @@ private void registerEntityModuleTypes() throws Exception { staticField(EntityModule.class, "instance").set(null, entityModule); } - private void registerImpulsePluginTypes() throws Exception { - ImpulsePlugin plugin = allocate(ImpulsePlugin.class); - setField(plugin, - "bodyAttachmentComponentType", - registry.registerComponent(BodyAttachmentComponent.class, - "BodyAttachment", - BodyAttachmentComponent.CODEC)); - staticField(ImpulsePlugin.class, "instance").set(null, plugin); - } - @Test void physicsBodyCenterConvertsBackToVisualBasePosition() { Vector3d visualPosition = ExamplePhysicsOriginMath.visualPositionFromBodyCenter(new Vector3d(1.0, 2.5, 3.0), diff --git a/impulse-physics-chunk/build.gradle.kts b/impulse-physics-chunk/build.gradle.kts index bbede0b2..0584273d 100644 --- a/impulse-physics-chunk/build.gradle.kts +++ b/impulse-physics-chunk/build.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.api.tasks.testing.Test + plugins { id("com.azuredoom.hytale-tools") } @@ -15,7 +17,14 @@ dependencies { compileOnly(project(":impulse-api")) compileOnly(project(":impulse-core")) compileOnly(project(":impulse-early-plugin")) + testImplementation(project(":impulse-api")) testImplementation(project(":impulse-core")) + testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") + testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") +} + +tasks.withType().configureEach { + jvmArgs("-Djava.util.logging.manager=com.hypixel.hytale.logger.backend.HytaleLogManager") } hytaleTools { diff --git a/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java b/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java new file mode 100644 index 00000000..916a3c94 --- /dev/null +++ b/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java @@ -0,0 +1,50 @@ +package dev.hytalemodding.impulse.core.internal.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class PhysicsChunkCommandContributionRegistryTest { + + @AfterEach + void resetRegistry() { + ImpulseCommandContributionRegistry.resetForTests(); + } + + @Test + void worldCollisionContributesCommandsUnderImpulseRoot() { + WorldCollisionCommandContributions.register(); + + ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); + + AbstractCommand worldCollision = root.getSubCommands().get("worldcollision"); + assertTrue(root.getSubCommands().containsKey("worldcollision")); + assertTrue(worldCollision.getSubCommands().containsKey("settings")); + assertTrue(worldCollision.getSubCommands().containsKey("perf")); + assertTrue(settings(root).getSubCommands().containsKey("collision-lod")); + } + + @Test + void worldCollisionContributionsAreIdempotentAndRemovable() { + WorldCollisionCommandContributions.register(); + WorldCollisionCommandContributions.register(); + + ImpulseCommand contributed = ImpulseCommandContributionRegistry.createRootCommandForTests(); + assertTrue(contributed.getSubCommands().containsKey("worldcollision")); + assertTrue(settings(contributed).getSubCommands().containsKey("collision-lod")); + + WorldCollisionCommandContributions.unregister(); + + ImpulseCommand removed = ImpulseCommandContributionRegistry.createRootCommandForTests(); + assertFalse(removed.getSubCommands().containsKey("worldcollision")); + assertFalse(settings(removed).getSubCommands().containsKey("collision-lod")); + } + + private static AbstractCommand settings(ImpulseCommand root) { + return root.getSubCommands().get("settings"); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommandTest.java b/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java similarity index 97% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommandTest.java rename to impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java index 063a8253..41e6e31d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/worldcollision/commands/WorldCollisionPerfReportCommandTest.java +++ b/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.worldcollision.commands; +package dev.hytalemodding.impulse.physicschunk.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; From 7724f515514aa8627e23ff9efd65a2b07faddd49 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:43:20 +0200 Subject: [PATCH 315/534] refactor(core): expose physics chunk profiling helpers Signed-off-by: Blovien --- .../physicschunk/PhysicsWorldCollision.java | 18 +- .../PhysicsWorldCollisionProfiling.java | 353 ++++++++++++++++++ .../ImpulsePhysicsChunkPlugin.java | 6 +- .../WorldCollisionPerfReportCommand.java | 57 +-- .../WorldCollisionPerfResetCommand.java | 25 +- .../WorldCollisionPerfToggleCommand.java | 28 +- 6 files changed, 405 insertions(+), 82 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index c5b9493f..c8575891 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -29,6 +29,18 @@ public final class PhysicsWorldCollision { private PhysicsWorldCollision() { } + public static void enableModule() { + WorldCollisionLifecycle.enable(); + } + + public static void disableModule() { + WorldCollisionLifecycle.disable(); + } + + public static boolean isModuleEnabled() { + return WorldCollisionLifecycle.isEnabled(); + } + @Nonnull public static WorldCollisionBuildStats rebuildAround(@Nonnull World world, @Nonnull Store store, @@ -116,13 +128,13 @@ public static WorldCollisionStats stats(@Nonnull World world) { throw new IllegalStateException("Cannot read PhysicsChunk world-collision stats " + "outside the owning world thread"); } - return WorldCollisionLifecycle.isEnabled() + return isModuleEnabled() ? streaming(world).stats() : new WorldCollisionStats(0, 0, 0, 0); } private static void requireEnabled() { - if (!WorldCollisionLifecycle.isEnabled()) { + if (!isModuleEnabled()) { throw new IllegalStateException("Impulse physics chunk subplugin is disabled"); } } @@ -181,7 +193,7 @@ private static int clearSpaceRows(@Nonnull World world, @Nonnull Store store, @Nonnull UUID spaceUuid) { int removed = 0; - if (WorldCollisionLifecycle.isEnabled()) { + if (isModuleEnabled()) { removed = streaming(world).clearSpace(spaceUuid, store.getResource(PhysicsTerrainMutationQueueResource.getResourceType())); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java new file mode 100644 index 00000000..3fe95cf0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java @@ -0,0 +1,353 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import java.util.List; +import java.util.Locale; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Public PhysicsChunk profiling helpers for command and diagnostics surfaces. + */ +public final class PhysicsWorldCollisionProfiling { + + private PhysicsWorldCollisionProfiling() { + } + + public static boolean isRuntimeProfilingEnabled(@Nonnull Store store) { + PhysicsRuntimeProfilingResource runtimeProfiling = runtimeProfiling(store); + WorldCollisionProfilingResource worldCollisionProfiling = worldCollisionProfiling(store); + return runtimeProfiling.isEnabled() && worldCollisionProfiling.isEnabled(); + } + + public static void setRuntimeProfilingEnabled(@Nonnull World world, + @Nonnull Store store, + boolean enabled) { + runtimeProfiling(store).setEnabled(enabled); + worldCollisionProfiling(store).setEnabled(enabled); + Store physicsStore = physicsStoreOrNull(world); + if (physicsStore != null) { + physicsStore.getResource(PhysicsProfilingResource.getResourceType()) + .setEnabled(enabled); + } + } + + public static void resetRuntimeProfiling(@Nonnull World world, + @Nonnull Store store) { + runtimeProfiling(store).reset(); + worldCollisionProfiling(store).reset(); + Store physicsStore = physicsStoreOrNull(world); + if (physicsStore != null) { + physicsStore.getResource(PhysicsProfilingResource.getResourceType()).reset(); + } + } + + @Nonnull + public static Snapshots snapshots(@Nonnull Store store) { + WorldCollisionProfilingResource profiling = worldCollisionProfiling(store); + return new Snapshots(profiling.getCumulativeSnapshot(), + profiling.getLatestTickSnapshot(), + profiling.getWorstTickSnapshot(), + profiling.isEnabled()); + } + + @Nonnull + public static List missingSectionSamples( + @Nonnull SnapshotView snapshot) { + return snapshot.snapshot.getMissingSectionSamples() + .stream() + .map(PhysicsWorldCollisionProfiling::view) + .toList(); + } + + @Nonnull + private static MissingSectionSampleView view( + @Nonnull WorldCollisionProfilingResource.MissingSectionSample sample) { + WorldCollisionProfilingResource.StreamingTargetDiagnostic target = sample.target(); + return new MissingSectionSampleView(sample.chunkX(), + sample.sectionY(), + sample.chunkZ(), + sample.reason().name().toLowerCase(Locale.ROOT), + sample.retainedEnvelopeStatus().name().toLowerCase(Locale.ROOT), + target.targetType().name().toLowerCase(Locale.ROOT), + target.bodyUuid(), + target.snapshotPosition() != null ? target.snapshotPosition().compact() : null, + target.livePosition() != null ? target.livePosition().compact() : null); + } + + @Nonnull + private static PhysicsRuntimeProfilingResource runtimeProfiling( + @Nonnull Store store) { + return store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); + } + + @Nonnull + private static WorldCollisionProfilingResource worldCollisionProfiling( + @Nonnull Store store) { + return store.getResource(WorldCollisionProfilingResource.getResourceType()); + } + + @Nullable + private static Store physicsStoreOrNull(@Nonnull World world) { + if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { + return null; + } + Store store = physicsStoreWorld.getPhysicsStore().getStore(); + return store.isShutdown() ? null : store; + } + + public record Snapshots(@Nonnull SnapshotView cumulative, + @Nonnull SnapshotView latest, + @Nonnull SnapshotView worst, + boolean enabled) { + + private Snapshots(@Nonnull WorldCollisionProfilingResource.Snapshot cumulative, + @Nonnull WorldCollisionProfilingResource.Snapshot latest, + @Nonnull WorldCollisionProfilingResource.Snapshot worst, + boolean enabled) { + this(new SnapshotView(cumulative), new SnapshotView(latest), new SnapshotView(worst), + enabled); + } + } + + public static final class SnapshotView { + + @Nonnull + private final WorldCollisionProfilingResource.Snapshot snapshot; + + private SnapshotView(@Nonnull WorldCollisionProfilingResource.Snapshot snapshot) { + this.snapshot = snapshot; + } + + public int getTickSamples() { + return snapshot.getTickSamples(); + } + + public int getPlayerStreamingTargets() { + return snapshot.getPlayerStreamingTargets(); + } + + public int getBodyStreamingCandidates() { + return snapshot.getBodyStreamingCandidates(); + } + + public int getBodySpatialIndexCandidates() { + return snapshot.getBodySpatialIndexCandidates(); + } + + public int getBodyStreamingTargets() { + return snapshot.getBodyStreamingTargets(); + } + + public int getBodyTargetDedupeSkips() { + return snapshot.getBodyTargetDedupeSkips(); + } + + public int getBodyTargetCacheHits() { + return snapshot.getBodyTargetCacheHits(); + } + + public int getBodyTargetFirstSeen() { + return snapshot.getBodyTargetFirstSeen(); + } + + public int getBodyTargetBoundsChanged() { + return snapshot.getBodyTargetBoundsChanged(); + } + + public int getBodyTargetActiveRefreshes() { + return snapshot.getBodyTargetActiveRefreshes(); + } + + public int getBodyTargetSleepingRefreshes() { + return snapshot.getBodyTargetSleepingRefreshes(); + } + + public int getBodyTargetActiveStableSkips() { + return snapshot.getBodyTargetActiveStableSkips(); + } + + public int getBodyTargetSleepingStableSkips() { + return snapshot.getBodyTargetSleepingStableSkips(); + } + + public int getBodyTargetsPruned() { + return snapshot.getBodyTargetsPruned(); + } + + public int getPlayerSectionTargets() { + return snapshot.getPlayerSectionTargets(); + } + + public int getBodySectionTargets() { + return snapshot.getBodySectionTargets(); + } + + public int getStreamingSpaces() { + return snapshot.getStreamingSpaces(); + } + + public int getTerrainApplyQueued() { + return snapshot.getTerrainApplyQueued(); + } + + public int getTerrainApplySkippedPending() { + return snapshot.getTerrainApplySkippedPending(); + } + + public int getEnsureCalls() { + return snapshot.getEnsureCalls(); + } + + public int getSectionRequests() { + return snapshot.getSectionRequests(); + } + + public int getSectionCacheHits() { + return snapshot.getSectionCacheHits(); + } + + public int getMissingChunks() { + return snapshot.getMissingChunks(); + } + + public int getMissingBlockChunks() { + return snapshot.getMissingBlockChunks(); + } + + public int getMissingBlockSections() { + return snapshot.getMissingBlockSections(); + } + + public int getMissingReasonUnknown() { + return snapshot.getMissingReasonUnknown(); + } + + public int getMissingBackoffSkips() { + return snapshot.getMissingBackoffSkips(); + } + + public int getMissingBlockChunkBackoffSkips() { + return snapshot.getMissingBlockChunkBackoffSkips(); + } + + public int getMissingBlockSectionBackoffSkips() { + return snapshot.getMissingBlockSectionBackoffSkips(); + } + + public int getMissingInsideRetainedEnvelope() { + return snapshot.getMissingInsideRetainedEnvelope(); + } + + public int getMissingOutsideRetainedEnvelope() { + return snapshot.getMissingOutsideRetainedEnvelope(); + } + + public int getMissingUnconfiguredRetainedEnvelope() { + return snapshot.getMissingUnconfiguredRetainedEnvelope(); + } + + public int getSectionsBuilt() { + return snapshot.getSectionsBuilt(); + } + + public int getSectionsRebuilt() { + return snapshot.getSectionsRebuilt(); + } + + public int getVoxelBodies() { + return snapshot.getVoxelBodies(); + } + + public int getColliderBodiesAdded() { + return snapshot.getColliderBodiesAdded(); + } + + public int getBodiesRemovedFromRebuild() { + return snapshot.getBodiesRemovedFromRebuild(); + } + + public int getBodiesRemovedFromUnloadedPrune() { + return snapshot.getBodiesRemovedFromUnloadedPrune(); + } + + public int getBodiesRemovedFromTtlPrune() { + return snapshot.getBodiesRemovedFromTtlPrune(); + } + + public int getSectionsRemovedFromUnloadedPrune() { + return snapshot.getSectionsRemovedFromUnloadedPrune(); + } + + public int getSectionsRemovedFromTtlPrune() { + return snapshot.getSectionsRemovedFromTtlPrune(); + } + + public int getDuplicateSkips() { + return snapshot.getDuplicateSkips(); + } + + public int getScannedBlocks() { + return snapshot.getScannedBlocks(); + } + + public int getSolidBlocks() { + return snapshot.getSolidBlocks(); + } + + public int getCulledInteriorBlocks() { + return snapshot.getCulledInteriorBlocks(); + } + + public int getFullCubeRuns() { + return snapshot.getFullCubeRuns(); + } + + public int getDetailBoxes() { + return snapshot.getDetailBoxes(); + } + + public int getUniqueMissingSections() { + return snapshot.getUniqueMissingSections(); + } + + public long getTickNanos() { + return snapshot.getTickNanos(); + } + + public long getEnsureAroundNanos() { + return snapshot.getEnsureAroundNanos(); + } + + public long getEnsureSectionNanos() { + return snapshot.getEnsureSectionNanos(); + } + + public long getPruneUnloadedNanos() { + return snapshot.getPruneUnloadedNanos(); + } + + public long getPruneUnusedNanos() { + return snapshot.getPruneUnusedNanos(); + } + } + + public record MissingSectionSampleView(int chunkX, + int sectionY, + int chunkZ, + @Nonnull String reason, + @Nonnull String retainedEnvelopeStatus, + @Nonnull String targetType, + @Nullable UUID bodyUuid, + @Nullable String snapshotPosition, + @Nullable String livePosition) { + } +} diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java index dbb3d347..8f8ab52d 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -6,10 +6,10 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import java.util.function.Consumer; import java.util.logging.Level; @@ -41,13 +41,13 @@ protected void setup() { PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); WorldCollisionCommandContributions.register(); - WorldCollisionLifecycle.enable(); + PhysicsWorldCollision.enableModule(); LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore terrain producer enabled."); } @Override protected void shutdown() { - WorldCollisionLifecycle.disable(); + PhysicsWorldCollision.disableModule(); WorldCollisionCommandContributions.unregister(); PhysicsStoreHooks.unregisterShutdownHook(SHUTDOWN_CLEANUP); PhysicsChunkTypes.clearEntityStoreResourceTypes(); diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java index 30056c9f..9ae3a5bc 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -12,12 +12,11 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.VisualSnapshot; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -55,17 +54,17 @@ private static void sendReport(@Nonnull CommandContext ctx, VisualSnapshot cumulativeVisual = runtimeProfiling.getCumulativeVisual(); VisualSnapshot latestVisual = runtimeProfiling.getLatestVisual(); VisualSnapshot worstVisual = runtimeProfiling.getWorstVisual(); - WorldCollisionProfilingResource profiling = store.getResource( - WorldCollisionProfilingResource.getResourceType()); - Snapshot cumulative = profiling.getCumulativeSnapshot(); - Snapshot latest = profiling.getLatestTickSnapshot(); - Snapshot worst = profiling.getWorstTickSnapshot(); + PhysicsWorldCollisionProfiling.Snapshots profiling = + PhysicsWorldCollisionProfiling.snapshots(store); + var cumulative = profiling.cumulative(); + var latest = profiling.latest(); + var worst = profiling.worst(); PhysicsEntityDiagnostics.Snapshot entityDiagnostics = PhysicsEntityDiagnostics.collect(store); PhysicsWorldResource physicsWorld = store.getResource(PhysicsWorldResource.getResourceType()); RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(summaries); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling: " - + ((runtimeProfiling.isEnabled() || profiling.isEnabled()) ? "enabled" : "disabled"))); + + ((runtimeProfiling.isEnabled() || profiling.enabled()) ? "enabled" : "disabled"))); ctx.sender().sendMessage(Message.raw("Impulse runtime physics: " + runtimeFootprint.summary())); if (runtimeFootprint.hasRuntimeStats()) { @@ -226,22 +225,22 @@ private static void sendReport(@Nonnull CommandContext ctx, + "/" + latestVisual.getDematerialized())); } } else { - ctx.sender().sendMessage(Message.raw("No profiled physics step/sync/visual ticks recorded yet." - + (runtimeProfiling.isEnabled() + ctx.sender().sendMessage(Message.raw("No profiled physics step/sync/visual ticks recorded yet." + + (runtimeProfiling.isEnabled() ? "" : " Run /impulse worldcollision perf toggle, wait a few seconds, then run /impulse worldcollision perf report."))); } if (cumulative.getTickSamples() <= 0) { ctx.sender().sendMessage(Message.raw("No profiled world collision ticks recorded yet." - + (profiling.isEnabled() + + (profiling.enabled() ? "" : " Run /impulse worldcollision perf toggle, wait a few seconds, then run /impulse worldcollision perf report."))); return; } ctx.sender().sendMessage(Message.raw("World collision profiling: " - + (profiling.isEnabled() ? "enabled" : "disabled"))); + + (profiling.enabled() ? "enabled" : "disabled"))); ctx.sender().sendMessage(Message.raw("Since reset: ticks=" + cumulative.getTickSamples() + " playerTargets=" + cumulative.getPlayerStreamingTargets() @@ -284,9 +283,11 @@ private static void sendReport(@Nonnull CommandContext ctx, + cumulative.getMissingInsideRetainedEnvelope() + "/" + cumulative.getMissingOutsideRetainedEnvelope() + "/" + cumulative.getMissingUnconfiguredRetainedEnvelope())); - if (!cumulative.getMissingSectionSamples().isEmpty()) { + List missingSectionSamples = + PhysicsWorldCollisionProfiling.missingSectionSamples(cumulative); + if (!missingSectionSamples.isEmpty()) { ctx.sender().sendMessage(Message.raw("Missing section samples: " - + formatMissingSectionSamples(cumulative))); + + formatMissingSectionSamples(missingSectionSamples))); } ctx.sender().sendMessage(Message.raw("Since reset bodies+blocks: added=" + cumulative.getColliderBodiesAdded() @@ -356,11 +357,11 @@ private static void sendReport(@Nonnull CommandContext ctx, } @Nonnull - private static String formatMissingSectionSamples(@Nonnull Snapshot snapshot) { + private static String formatMissingSectionSamples( + @Nonnull List samples) { StringBuilder builder = new StringBuilder(); int emitted = 0; - for (WorldCollisionProfilingResource.MissingSectionSample sample - : snapshot.getMissingSectionSamples()) { + for (PhysicsWorldCollisionProfiling.MissingSectionSampleView sample : samples) { if (emitted > 0) { builder.append(" | "); } @@ -371,22 +372,22 @@ private static String formatMissingSectionSamples(@Nonnull Snapshot snapshot) { .append("/") .append(sample.chunkZ()) .append(" reason=") - .append(sample.reason().name().toLowerCase(Locale.ROOT)) + .append(sample.reason()) .append(" retained=") - .append(sample.retainedEnvelopeStatus().name().toLowerCase(Locale.ROOT)) + .append(sample.retainedEnvelopeStatus()) .append(" target=") - .append(sample.target().targetType().name().toLowerCase(Locale.ROOT)); - if (sample.target().bodyUuid() != null) { - builder.append(" body=").append(sample.target().bodyUuid()); + .append(sample.targetType()); + if (sample.bodyUuid() != null) { + builder.append(" body=").append(sample.bodyUuid()); } - if (sample.target().snapshotPosition() != null) { + if (sample.snapshotPosition() != null) { builder.append(" snapshot=(") - .append(sample.target().snapshotPosition().compact()) + .append(sample.snapshotPosition()) .append(")"); } - if (sample.target().livePosition() != null) { + if (sample.livePosition() != null) { builder.append(" live=(") - .append(sample.target().livePosition().compact()) + .append(sample.livePosition()) .append(")"); } emitted++; @@ -394,9 +395,9 @@ private static String formatMissingSectionSamples(@Nonnull Snapshot snapshot) { break; } } - if (snapshot.getMissingSectionSamples().size() > emitted) { + if (samples.size() > emitted) { builder.append(" | +") - .append(snapshot.getMissingSectionSamples().size() - emitted) + .append(samples.size() - emitted) .append(" more"); } return builder.toString(); diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java index f8135115..fb81afc1 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java @@ -6,11 +6,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; import javax.annotation.Nonnull; public class WorldCollisionPerfResetCommand extends AbstractWorldCommand { @@ -23,24 +19,7 @@ public WorldCollisionPerfResetCommand() { protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { - PhysicsRuntimeProfilingResource runtimeProfiling = store.getResource( - PhysicsRuntimeProfilingResource.getResourceType()); - WorldCollisionProfilingResource profiling = store.getResource( - WorldCollisionProfilingResource.getResourceType()); - runtimeProfiling.reset(); - profiling.reset(); - Store physicsStore = physicsStoreOrNull(world); - if (physicsStore != null) { - physicsStore.getResource(PhysicsProfilingResource.getResourceType()).reset(); - } + PhysicsWorldCollisionProfiling.resetRuntimeProfiling(world, store); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling counters reset")); } - - private static Store physicsStoreOrNull(@Nonnull World world) { - if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { - return null; - } - Store store = physicsStoreWorld.getPhysicsStore().getStore(); - return store.isShutdown() ? null : store; - } } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java index 310d15e1..278d4c74 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java @@ -6,11 +6,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; import javax.annotation.Nonnull; public class WorldCollisionPerfToggleCommand extends AbstractWorldCommand { @@ -23,27 +19,9 @@ public WorldCollisionPerfToggleCommand() { protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { - PhysicsRuntimeProfilingResource runtimeProfiling = store.getResource( - PhysicsRuntimeProfilingResource.getResourceType()); - WorldCollisionProfilingResource profiling = store.getResource( - WorldCollisionProfilingResource.getResourceType()); - boolean enabled = !runtimeProfiling.isEnabled() || !profiling.isEnabled(); - runtimeProfiling.setEnabled(enabled); - profiling.setEnabled(enabled); - Store physicsStore = physicsStoreOrNull(world); - if (physicsStore != null) { - physicsStore.getResource(PhysicsProfilingResource.getResourceType()) - .setEnabled(enabled); - } + boolean enabled = !PhysicsWorldCollisionProfiling.isRuntimeProfilingEnabled(store); + PhysicsWorldCollisionProfiling.setRuntimeProfilingEnabled(world, store, enabled); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling " + (enabled ? "enabled" : "disabled"))); } - - private static Store physicsStoreOrNull(@Nonnull World world) { - if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { - return null; - } - Store store = physicsStoreWorld.getPhysicsStore().getStore(); - return store.isShutdown() ? null : store; - } } From d911333d3f10d095ad781e20c3a5e6bdc3a6cf1d Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:54:49 +0200 Subject: [PATCH 316/534] refactor(core): narrow physics chunk command boundary Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkCommands.java | 36 ++++++++++++++ .../physicschunk/PhysicsChunkTypes.java | 10 ++++ .../ImpulsePhysicsChunkPlugin.java | 7 +-- .../commands/CollisionLodSettingsCommand.java | 17 +++---- .../WorldCollisionCommandContributions.java | 12 ++--- .../WorldCollisionSettingsCommand.java | 17 +++---- .../WorldCollisionSpaceSelection.java | 48 +++++++++++++++++++ 7 files changed, 110 insertions(+), 37 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java create mode 100644 impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java new file mode 100644 index 00000000..eceb6049 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java @@ -0,0 +1,36 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; +import java.util.Objects; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +/** + * Public command contribution endpoint for the physics chunk module. + */ +public final class PhysicsChunkCommands { + + private static final String WORLD_COLLISION_ROOT_COMMAND_ID = "worldcollision.root"; + private static final String COLLISION_LOD_SETTINGS_COMMAND_ID = + "worldcollision.settings.collision-lod"; + + private PhysicsChunkCommands() { + } + + public static void registerWorldCollisionCommands( + @Nonnull Supplier worldCollisionCommand, + @Nonnull Supplier collisionLodSettingsCommand) { + ImpulseCommandContributionRegistry.addRootAndSettingsSubCommands( + WORLD_COLLISION_ROOT_COMMAND_ID, + Objects.requireNonNull(worldCollisionCommand, "worldCollisionCommand"), + COLLISION_LOD_SETTINGS_COMMAND_ID, + Objects.requireNonNull(collisionLodSettingsCommand, "collisionLodSettingsCommand")); + } + + public static void unregisterWorldCollisionCommands() { + ImpulseCommandContributionRegistry.removeRootAndSettingsSubCommands( + WORLD_COLLISION_ROOT_COMMAND_ID, + COLLISION_LOD_SETTINGS_COMMAND_ID); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java index c4725928..af2689ac 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java @@ -5,11 +5,13 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; @@ -75,6 +77,14 @@ public static void registerSystems(@Nonnull ComponentRegistryProxy registry.registerSystem(new TerrainColliderBindingSystem()); } + public static void registerPhysicsStoreTypes(@Nonnull PluginBase plugin) { + ComponentRegistryProxy registry = + PhysicsStoreRegistration.physicsStoreRegistry(plugin); + registerComponentTypes(registry); + registerResourceTypes(registry); + registerSystems(registry); + } + public static void registerEntityStoreResourceTypes( @Nonnull ComponentRegistryProxy registry) { WorldCollisionProfilingResource.setResourceType(registry.registerResource( diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java index 8f8ab52d..6cf67bfe 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; @@ -30,11 +29,7 @@ public ImpulsePhysicsChunkPlugin(@Nonnull JavaPluginInit init) { @Override protected void setup() { - ComponentRegistryProxy physicsStoreRegistry = - PhysicsStoreRegistration.physicsStoreRegistry(this); - PhysicsChunkTypes.registerComponentTypes(physicsStoreRegistry); - PhysicsChunkTypes.registerResourceTypes(physicsStoreRegistry); - PhysicsChunkTypes.registerSystems(physicsStoreRegistry); + PhysicsChunkTypes.registerPhysicsStoreTypes(this); PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java index 845743b4..e2aeb057 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java @@ -11,10 +11,9 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -71,16 +70,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, - world, - spaceArg); - if (selectedSpace == null) { + SpaceId spaceId = WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, resource); + if (spaceId == null) { return CompletableFuture.completedFuture(null); } - SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings settings = new PhysicsSpaceSettings( - resource.getSpaceSettings(selectedSpace.spaceRef())); + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -138,7 +133,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getCollisionLodSettings().setCollisionLodHysteresis(hysteresis); settings.getCollisionLodSettings().setCollisionLodRefreshIntervalTicks(interval); settings.getCollisionLodSettings().setCollisionLodFarSleepEnabled(farSleep); - resource.setSpaceSettings(selectedSpace.spaceRef(), settings); + resource.setSpaceSettings(spaceId, settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java index 1b64a6d9..5e7d967d 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java @@ -1,28 +1,22 @@ package dev.hytalemodding.impulse.physicschunk.commands; -import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCommands; /** * Command contributions owned by the world-collision subplugin. */ public final class WorldCollisionCommandContributions { - private static final String ROOT_COMMAND_ID = "worldcollision.root"; - private static final String COLLISION_LOD_SETTINGS_ID = "worldcollision.settings.collision-lod"; - private WorldCollisionCommandContributions() { } public static void register() { - ImpulseCommandContributionRegistry.addRootAndSettingsSubCommands( - ROOT_COMMAND_ID, + PhysicsChunkCommands.registerWorldCollisionCommands( WorldCollisionCommand::new, - COLLISION_LOD_SETTINGS_ID, CollisionLodSettingsCommand::new); } public static void unregister() { - ImpulseCommandContributionRegistry.removeRootAndSettingsSubCommands(ROOT_COMMAND_ID, - COLLISION_LOD_SETTINGS_ID); + PhysicsChunkCommands.unregisterWorldCollisionCommands(); } } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java index 02051a61..f4a4de35 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java @@ -11,12 +11,11 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -71,16 +70,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, - world, - spaceArg); - if (selectedSpace == null) { + SpaceId spaceId = WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, resource); + if (spaceId == null) { return CompletableFuture.completedFuture(null); } - SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings settings = new PhysicsSpaceSettings( - resource.getSpaceSettings(selectedSpace.spaceRef())); + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -140,7 +135,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getWorldCollisionSettings().setWorldCollisionRadius(playerRadius); settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(bodyRadius); settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(ttl); - resource.setSpaceSettings(selectedSpace.spaceRef(), settings); + resource.setSpaceSettings(spaceId, settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java new file mode 100644 index 00000000..ed561451 --- /dev/null +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java @@ -0,0 +1,48 @@ +package dev.hytalemodding.impulse.physicschunk.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.universe.world.World; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import java.util.Comparator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class WorldCollisionSpaceSelection { + + private WorldCollisionSpaceSelection() { + } + + @Nullable + static SpaceId resolve(@Nonnull CommandContext context, + @Nonnull World world, + @Nonnull OptionalArg spaceArg, + @Nonnull PhysicsWorldResource resource) { + if (spaceArg.provided(context)) { + int rawSpaceId = spaceArg.get(context); + if (rawSpaceId <= 0) { + context.sendMessage(Message.raw("Space id must be a positive integer.")); + return null; + } + SpaceId spaceId = new SpaceId(rawSpaceId); + if (!resource.hasSpace(spaceId)) { + context.sendMessage(Message.raw("No physics space id=" + rawSpaceId + + " exists in world " + world.getName() + ".")); + return null; + } + return spaceId; + } + + SpaceId firstSpaceId = resource.getSpaceIds() + .stream() + .min(Comparator.comparingInt(SpaceId::value)) + .orElse(null); + if (firstSpaceId == null) { + context.sendMessage(Message.raw("No physics space exists. Run " + + "`/impulse space create --backend=` before targeting space settings.")); + } + return firstSpaceId; + } +} From e65d6e2a8688d213870ba8e0805d97eeb6ac2024 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:57:12 +0200 Subject: [PATCH 317/534] refactor(core): expose physics runtime profiling views Signed-off-by: Blovien --- .../PhysicsEntityDiagnostics.java | 68 ++++ .../PhysicsRuntimeProfiling.java | 351 ++++++++++++++++++ .../WorldCollisionPerfReportCommand.java | 43 ++- 3 files changed, 440 insertions(+), 22 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java new file mode 100644 index 00000000..687cdd6f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java @@ -0,0 +1,68 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +/** + * Public value diagnostics for the EntityStore side of Impulse physics. + */ +public final class PhysicsEntityDiagnostics { + + private PhysicsEntityDiagnostics() { + } + + @Nonnull + public static Snapshot collect(@Nonnull Store store) { + dev.hytalemodding.impulse.core.internal.diagnostics.PhysicsEntityDiagnostics.Snapshot + snapshot = + dev.hytalemodding.impulse.core.internal.diagnostics.PhysicsEntityDiagnostics.collect( + store); + return new Snapshot(snapshot.physicsBodyEntities(), + snapshot.persistentPhysicsBodyEntities(), + snapshot.physicsVisualEntities(), + snapshot.transformEntities(), + snapshot.networkIdEntities(), + snapshot.visibleEntities(), + snapshot.entityViewers(), + snapshot.physicsBodyWithTransform(), + snapshot.physicsBodyWithNetworkId(), + snapshot.physicsBodyWithVisible(), + snapshot.physicsBodyMaterialized(), + snapshot.physicsVisualMaterialized()); + } + + public record Snapshot(int physicsBodyEntities, + int persistentPhysicsBodyEntities, + int physicsVisualEntities, + int transformEntities, + int networkIdEntities, + int visibleEntities, + int entityViewers, + int physicsBodyWithTransform, + int physicsBodyWithNetworkId, + int physicsBodyWithVisible, + int physicsBodyMaterialized, + int physicsVisualMaterialized) { + + @Nonnull + public String hytaleSummary() { + return "transformEntities=" + transformEntities + + " networkIdEntities=" + networkIdEntities + + " visibleEntities=" + visibleEntities + + " entityViewers=" + entityViewers; + } + + @Nonnull + public String impulseSummary() { + return "physicsBodies=" + physicsBodyEntities + + " persistentBodies=" + persistentPhysicsBodyEntities + + " visualFollowers=" + physicsVisualEntities + + " bodyTransform=" + physicsBodyWithTransform + + " bodyNetworkId=" + physicsBodyWithNetworkId + + " bodyVisible=" + physicsBodyWithVisible + + " bodyMaterialized=" + physicsBodyMaterialized + + " visualMaterialized=" + physicsVisualMaterialized; + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java new file mode 100644 index 00000000..4e5cc42f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java @@ -0,0 +1,351 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Public value views for EntityStore-side physics runtime profiling. + */ +public final class PhysicsRuntimeProfiling { + + private PhysicsRuntimeProfiling() { + } + + @Nonnull + public static Snapshots snapshots(@Nonnull Store store) { + PhysicsRuntimeProfilingResource profiling = store.getResource( + PhysicsRuntimeProfilingResource.getResourceType()); + return new Snapshots(profiling.isEnabled(), + new StepSnapshotView(profiling.getCumulativeStep()), + new StepSnapshotView(profiling.getLatestStep()), + new StepSnapshotView(profiling.getLatestCompletedStep()), + new StepSnapshotView(profiling.getWorstStep()), + new SyncSnapshotView(profiling.getCumulativeSync()), + new SyncSnapshotView(profiling.getLatestSync()), + new SyncSnapshotView(profiling.getWorstSync()), + new VisualSnapshotView(profiling.getCumulativeVisual()), + new VisualSnapshotView(profiling.getLatestVisual()), + new VisualSnapshotView(profiling.getWorstVisual())); + } + + public record Snapshots(boolean enabled, + @Nonnull StepSnapshotView cumulativeStep, + @Nonnull StepSnapshotView latestStep, + @Nonnull StepSnapshotView latestCompletedStep, + @Nonnull StepSnapshotView worstStep, + @Nonnull SyncSnapshotView cumulativeSync, + @Nonnull SyncSnapshotView latestSync, + @Nonnull SyncSnapshotView worstSync, + @Nonnull VisualSnapshotView cumulativeVisual, + @Nonnull VisualSnapshotView latestVisual, + @Nonnull VisualSnapshotView worstVisual) { + } + + public static final class StepSnapshotView { + + @Nonnull + private final PhysicsRuntimeProfilingResource.StepSnapshot snapshot; + + private StepSnapshotView( + @Nonnull PhysicsRuntimeProfilingResource.StepSnapshot snapshot) { + this.snapshot = Objects.requireNonNull(snapshot, "snapshot"); + } + + public int getTickSamples() { + return snapshot.getTickSamples(); + } + + public int getSpaces() { + return snapshot.getSpaces(); + } + + public int getSubsteps() { + return snapshot.getSubsteps(); + } + + public int getBodySnapshots() { + return snapshot.getBodySnapshots(); + } + + public int getSpatialIndexCells() { + return snapshot.getSpatialIndexCells(); + } + + public long getTickNanos() { + return snapshot.getTickNanos(); + } + + public long getSnapshotNanos() { + return snapshot.getSnapshotNanos(); + } + + public long getStoreTickQueuedNanos() { + return snapshot.getStoreTickQueuedNanos(); + } + + public long getStoreTickRunNanos() { + return snapshot.getStoreTickRunNanos(); + } + + public int getPreStepDrainedMutations() { + return snapshot.getPreStepDrainedMutations(); + } + + public int getMaxPreStepDrainedMutations() { + return snapshot.getMaxPreStepDrainedMutations(); + } + + public long getPreStepDrainRunNanos() { + return snapshot.getPreStepDrainRunNanos(); + } + + public int getLateMutationBacklogAtStep() { + return snapshot.getLateMutationBacklogAtStep(); + } + + public int getMaxLateMutationBacklogAtStep() { + return snapshot.getMaxLateMutationBacklogAtStep(); + } + + public int getStoreTickStepRateSamples() { + return snapshot.getStoreTickStepRateSamples(); + } + + public long getStoreTickStepIntervalNanos() { + return snapshot.getStoreTickStepIntervalNanos(); + } + + public long getMaxStoreTickStepIntervalNanos() { + return snapshot.getMaxStoreTickStepIntervalNanos(); + } + + public int getSkippedPendingSteps() { + return snapshot.getSkippedPendingSteps(); + } + + public long getPendingStepAgeNanos() { + return snapshot.getPendingStepAgeNanos(); + } + + public long getMaxPendingStepAgeNanos() { + return snapshot.getMaxPendingStepAgeNanos(); + } + + public int getSchedulerSamples() { + return snapshot.getSchedulerSamples(); + } + + public long getSchedulerInputDtNanos() { + return snapshot.getSchedulerInputDtNanos(); + } + + public long getSchedulerSubmittedDtNanos() { + return snapshot.getSchedulerSubmittedDtNanos(); + } + + public long getSchedulerBacklogDtNanos() { + return snapshot.getSchedulerBacklogDtNanos(); + } + + public long getMaxSchedulerBacklogDtNanos() { + return snapshot.getMaxSchedulerBacklogDtNanos(); + } + + public long getDroppedBacklogDtNanos() { + return snapshot.getDroppedBacklogDtNanos(); + } + + public int getDroppedBacklogTicks() { + return snapshot.getDroppedBacklogTicks(); + } + + public int getDtCapHits() { + return snapshot.getDtCapHits(); + } + + public int getNativePhaseSamples() { + return snapshot.getNativePhaseSamples(); + } + + public long getNativeStepNanos() { + return snapshot.getNativeStepNanos(); + } + + public long getNativeBroadPhaseNanos() { + return snapshot.getNativeBroadPhaseNanos(); + } + + public long getNativeNarrowPhaseNanos() { + return snapshot.getNativeNarrowPhaseNanos(); + } + + public long getNativeSolverNanos() { + return snapshot.getNativeSolverNanos(); + } + + public long getNativeCcdNanos() { + return snapshot.getNativeCcdNanos(); + } + + public long getNativeSnapshotNanos() { + return snapshot.getNativeSnapshotNanos(); + } + } + + public static final class SyncSnapshotView { + + @Nonnull + private final PhysicsRuntimeProfilingResource.SyncSnapshot snapshot; + + private SyncSnapshotView( + @Nonnull PhysicsRuntimeProfilingResource.SyncSnapshot snapshot) { + this.snapshot = Objects.requireNonNull(snapshot, "snapshot"); + } + + public int getTickSamples() { + return snapshot.getTickSamples(); + } + + public int getBodiesInspected() { + return snapshot.getBodiesInspected(); + } + + public int getBodiesSynced() { + return snapshot.getBodiesSynced(); + } + + public int getTransitionSyncs() { + return snapshot.getTransitionSyncs(); + } + + public int getKeepaliveSyncs() { + return snapshot.getKeepaliveSyncs(); + } + + public int getSkippedSleeping() { + return snapshot.getSkippedSleeping(); + } + + public int getSkippedThreshold() { + return snapshot.getSkippedThreshold(); + } + + public int getSkippedVisualDeadzone() { + return snapshot.getSkippedVisualDeadzone(); + } + + public int getSkippedVisualRange() { + return snapshot.getSkippedVisualRange(); + } + + public int getSkippedStatic() { + return snapshot.getSkippedStatic(); + } + + public int getSkippedMissingSpace() { + return snapshot.getSkippedMissingSpace(); + } + + public long getTickNanos() { + return snapshot.getTickNanos(); + } + + public int getBodySnapshotMotionSamples() { + return snapshot.getBodySnapshotMotionSamples(); + } + + public double getBodySnapshotMotionDistance() { + return snapshot.getBodySnapshotMotionDistance(); + } + + public double getMaxBodySnapshotMotionDistance() { + return snapshot.getMaxBodySnapshotMotionDistance(); + } + + public int getVisualCorrectionSamples() { + return snapshot.getVisualCorrectionSamples(); + } + + public double getVisualCorrectionDistance() { + return snapshot.getVisualCorrectionDistance(); + } + + public double getMaxVisualCorrectionDistance() { + return snapshot.getMaxVisualCorrectionDistance(); + } + } + + public static final class VisualSnapshotView { + + @Nonnull + private final PhysicsRuntimeProfilingResource.VisualSnapshot snapshot; + + private VisualSnapshotView( + @Nonnull PhysicsRuntimeProfilingResource.VisualSnapshot snapshot) { + this.snapshot = Objects.requireNonNull(snapshot, "snapshot"); + } + + public int getTickSamples() { + return snapshot.getTickSamples(); + } + + public int getInterests() { + return snapshot.getInterests(); + } + + public int getMaterialized() { + return snapshot.getMaterialized(); + } + + public int getCandidates() { + return snapshot.getCandidates(); + } + + public int getSpawned() { + return snapshot.getSpawned(); + } + + public int getDematerialized() { + return snapshot.getDematerialized(); + } + + public int getNearQueries() { + return snapshot.getNearQueries(); + } + + public int getNearQueryCandidates() { + return snapshot.getNearQueryCandidates(); + } + + public int getRaycasts() { + return snapshot.getRaycasts(); + } + + public int getRaycastCacheHits() { + return snapshot.getRaycastCacheHits(); + } + + public int getCandidateRefreshes() { + return snapshot.getCandidateRefreshes(); + } + + public int getCandidateCacheUses() { + return snapshot.getCandidateCacheUses(); + } + + public int getVisibilityChecks() { + return snapshot.getVisibilityChecks(); + } + + public int getVisibilityCheckSkips() { + return snapshot.getVisibilityCheckSkips(); + } + + public long getTickNanos() { + return snapshot.getTickNanos(); + } + } +} diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java index 9ae3a5bc..1d88634f 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -6,12 +6,12 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.diagnostics.PhysicsEntityDiagnostics; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.VisualSnapshot; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityDiagnostics; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.StepSnapshotView; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.SyncSnapshotView; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.VisualSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; @@ -42,18 +42,17 @@ private static void sendReport(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store, @Nonnull List summaries) { - PhysicsRuntimeProfilingResource runtimeProfiling = store.getResource( - PhysicsRuntimeProfilingResource.getResourceType()); - StepSnapshot cumulativeStep = runtimeProfiling.getCumulativeStep(); - StepSnapshot latestStep = runtimeProfiling.getLatestStep(); - StepSnapshot latestCompletedStep = runtimeProfiling.getLatestCompletedStep(); - StepSnapshot worstStep = runtimeProfiling.getWorstStep(); - SyncSnapshot cumulativeSync = runtimeProfiling.getCumulativeSync(); - SyncSnapshot latestSync = runtimeProfiling.getLatestSync(); - SyncSnapshot worstSync = runtimeProfiling.getWorstSync(); - VisualSnapshot cumulativeVisual = runtimeProfiling.getCumulativeVisual(); - VisualSnapshot latestVisual = runtimeProfiling.getLatestVisual(); - VisualSnapshot worstVisual = runtimeProfiling.getWorstVisual(); + PhysicsRuntimeProfiling.Snapshots runtimeProfiling = PhysicsRuntimeProfiling.snapshots(store); + StepSnapshotView cumulativeStep = runtimeProfiling.cumulativeStep(); + StepSnapshotView latestStep = runtimeProfiling.latestStep(); + StepSnapshotView latestCompletedStep = runtimeProfiling.latestCompletedStep(); + StepSnapshotView worstStep = runtimeProfiling.worstStep(); + SyncSnapshotView cumulativeSync = runtimeProfiling.cumulativeSync(); + SyncSnapshotView latestSync = runtimeProfiling.latestSync(); + SyncSnapshotView worstSync = runtimeProfiling.worstSync(); + VisualSnapshotView cumulativeVisual = runtimeProfiling.cumulativeVisual(); + VisualSnapshotView latestVisual = runtimeProfiling.latestVisual(); + VisualSnapshotView worstVisual = runtimeProfiling.worstVisual(); PhysicsWorldCollisionProfiling.Snapshots profiling = PhysicsWorldCollisionProfiling.snapshots(store); var cumulative = profiling.cumulative(); @@ -64,7 +63,7 @@ private static void sendReport(@Nonnull CommandContext ctx, RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(summaries); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling: " - + ((runtimeProfiling.isEnabled() || profiling.enabled()) ? "enabled" : "disabled"))); + + ((runtimeProfiling.enabled() || profiling.enabled()) ? "enabled" : "disabled"))); ctx.sender().sendMessage(Message.raw("Impulse runtime physics: " + runtimeFootprint.summary())); if (runtimeFootprint.hasRuntimeStats()) { @@ -226,7 +225,7 @@ private static void sendReport(@Nonnull CommandContext ctx, } } else { ctx.sender().sendMessage(Message.raw("No profiled physics step/sync/visual ticks recorded yet." - + (runtimeProfiling.isEnabled() + + (runtimeProfiling.enabled() ? "" : " Run /impulse worldcollision perf toggle, wait a few seconds, then run /impulse worldcollision perf report."))); } @@ -437,8 +436,8 @@ static String formatEventFrameSummary(@Nonnull PhysicsEventFrame frame) { } @Nonnull - static String formatPreStepDrainSummary(@Nonnull StepSnapshot cumulativeStep, - @Nonnull StepSnapshot latestStep) { + static String formatPreStepDrainSummary(@Nonnull StepSnapshotView cumulativeStep, + @Nonnull StepSnapshotView latestStep) { return "Physics pre-step drain avg completedStep drained/runMs/lateBacklog=" + formatAverage(cumulativeStep.getPreStepDrainedMutations(), cumulativeStep.getTickSamples()) @@ -454,7 +453,7 @@ static String formatPreStepDrainSummary(@Nonnull StepSnapshot cumulativeStep, + "/" + cumulativeStep.getMaxLateMutationBacklogAtStep(); } - static boolean hasCompletedStepSamples(@Nonnull StepSnapshot cumulativeStep) { + static boolean hasCompletedStepSamples(@Nonnull StepSnapshotView cumulativeStep) { return cumulativeStep.getTickSamples() > 0; } From df1a2a249037fe0aa3e52faa5d5e695bad505e11 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:58:06 +0200 Subject: [PATCH 318/534] refactor(core): expose profiling drain snapshots Signed-off-by: Blovien --- .../physicsentity/PhysicsRuntimeProfiling.java | 17 ++++++++++++++++- .../WorldCollisionPerfReportCommand.java | 7 ++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java index 4e5cc42f..b82f387e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java @@ -44,7 +44,22 @@ public record Snapshots(boolean enabled, @Nonnull VisualSnapshotView worstVisual) { } - public static final class StepSnapshotView { + public interface StepDrainSnapshotView { + + int getTickSamples(); + + int getPreStepDrainedMutations(); + + int getMaxPreStepDrainedMutations(); + + long getPreStepDrainRunNanos(); + + int getLateMutationBacklogAtStep(); + + int getMaxLateMutationBacklogAtStep(); + } + + public static final class StepSnapshotView implements StepDrainSnapshotView { @Nonnull private final PhysicsRuntimeProfilingResource.StepSnapshot snapshot; diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java index 1d88634f..7c2d71a3 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityDiagnostics; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.StepDrainSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.StepSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.SyncSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.VisualSnapshotView; @@ -436,8 +437,8 @@ static String formatEventFrameSummary(@Nonnull PhysicsEventFrame frame) { } @Nonnull - static String formatPreStepDrainSummary(@Nonnull StepSnapshotView cumulativeStep, - @Nonnull StepSnapshotView latestStep) { + static String formatPreStepDrainSummary(@Nonnull StepDrainSnapshotView cumulativeStep, + @Nonnull StepDrainSnapshotView latestStep) { return "Physics pre-step drain avg completedStep drained/runMs/lateBacklog=" + formatAverage(cumulativeStep.getPreStepDrainedMutations(), cumulativeStep.getTickSamples()) @@ -453,7 +454,7 @@ static String formatPreStepDrainSummary(@Nonnull StepSnapshotView cumulativeStep + "/" + cumulativeStep.getMaxLateMutationBacklogAtStep(); } - static boolean hasCompletedStepSamples(@Nonnull StepSnapshotView cumulativeStep) { + static boolean hasCompletedStepSamples(@Nonnull StepDrainSnapshotView cumulativeStep) { return cumulativeStep.getTickSamples() > 0; } From 5095125125c003574716be1b70006e46d160a257 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 10:59:09 +0200 Subject: [PATCH 319/534] test(chunk): use profiling value views Signed-off-by: Blovien --- .../WorldCollisionPerfReportCommandTest.java | 125 +++++++++--------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java b/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java index 41e6e31d..def3a689 100644 --- a/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java +++ b/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java @@ -4,97 +4,96 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.StepDrainSnapshotView; import org.junit.jupiter.api.Test; class WorldCollisionPerfReportCommandTest { @Test void preStepDrainSummaryReportsAverageLatestAndMaxBackpressure() { - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - profiling.recordStep(1, - 1, - 20L, - 0, - 0, - 0L, - 0L, - 0L, - 1_000_000_000L, - PhysicsStepPhaseStats.unavailable(), - 2, - 4_000_000L, - 1); - profiling.recordStep(1, - 1, - 10L, - 0, - 0, - 0L, - 0L, - 0L, - 1_010_000_000L, - PhysicsStepPhaseStats.unavailable(), + StepDrainSample cumulative = new StepDrainSample(2, + 6, + 4, + 12_000_000L, + 4, + 3); + StepDrainSample latest = new StepDrainSample(1, + 4, 4, 8_000_000L, + 3, 3); assertEquals("Physics pre-step drain avg completedStep drained/runMs/lateBacklog=3.0/6.000/2.0 " + "latest drained/lateBacklog=4/3 max drained/lateBacklog=4/3", - WorldCollisionPerfReportCommand.formatPreStepDrainSummary(profiling.getCumulativeStep(), - profiling.getLatestStep())); + WorldCollisionPerfReportCommand.formatPreStepDrainSummary(cumulative, latest)); } @Test void preStepDrainSummaryRequiresCompletedStepSamples() { - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - - profiling.recordStepScheduling(0.05f, 0.05f, 0.0f, 0.0f, false); - assertFalse(WorldCollisionPerfReportCommand.hasCompletedStepSamples( - profiling.getCumulativeStep())); - - profiling.recordStep(1, - 1, - 20L, - 0, - 0, - 0L, - 0L, - 0L, - 1_000_000_000L, - PhysicsStepPhaseStats.unavailable(), - 1, - 2_000_000L, - 0); + new StepDrainSample(0, 0, 0, 0L, 0, 0))); assertTrue(WorldCollisionPerfReportCommand.hasCompletedStepSamples( - profiling.getCumulativeStep())); + new StepDrainSample(1, 1, 1, 2_000_000L, 0, 0))); } @Test void preStepDrainSummaryUsesLatestCompletedStepAfterSkippedPendingTick() { - PhysicsRuntimeProfilingResource profiling = new PhysicsRuntimeProfilingResource(); - profiling.recordStep(1, - 1, - 20L, - 0, - 0, - 0L, - 0L, - 0L, - 1_000_000_000L, - PhysicsStepPhaseStats.unavailable(), + StepDrainSample cumulative = new StepDrainSample(1, + 4, 4, 8_000_000L, + 3, + 3); + StepDrainSample latestCompleted = new StepDrainSample(1, + 4, + 4, + 8_000_000L, + 3, 3); - - profiling.recordStepSkippedPending(2_000_000L); assertEquals("Physics pre-step drain avg completedStep drained/runMs/lateBacklog=4.0/8.000/3.0 " + "latest drained/lateBacklog=4/3 max drained/lateBacklog=4/3", - WorldCollisionPerfReportCommand.formatPreStepDrainSummary(profiling.getCumulativeStep(), - profiling.getLatestCompletedStep())); + WorldCollisionPerfReportCommand.formatPreStepDrainSummary(cumulative, + latestCompleted)); + } + + private record StepDrainSample(int tickSamples, + int preStepDrainedMutations, + int maxPreStepDrainedMutations, + long preStepDrainRunNanos, + int lateMutationBacklogAtStep, + int maxLateMutationBacklogAtStep) implements StepDrainSnapshotView { + + @Override + public int getTickSamples() { + return tickSamples; + } + + @Override + public int getPreStepDrainedMutations() { + return preStepDrainedMutations; + } + + @Override + public int getMaxPreStepDrainedMutations() { + return maxPreStepDrainedMutations; + } + + @Override + public long getPreStepDrainRunNanos() { + return preStepDrainRunNanos; + } + + @Override + public int getLateMutationBacklogAtStep() { + return lateMutationBacklogAtStep; + } + + @Override + public int getMaxLateMutationBacklogAtStep() { + return maxLateMutationBacklogAtStep; + } } } From aeb115b4d71235390697a2deb109dbadea3e8ee5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:04:44 +0200 Subject: [PATCH 320/534] refactor(core): hide internal physics type handles Signed-off-by: Blovien --- .../GeneratedVisualProxyComponent.java | 11 +++- .../resources/PhysicsDebugResource.java | 12 ++++- .../PhysicsProjectionIndexResource.java | 11 +++- .../PhysicsTerrainMutationQueueResource.java | 11 +++- .../PhysicsTerrainPayloadResource.java | 10 +++- .../PhysicsWorldCollisionIndexResource.java | 10 +++- .../PhysicsRuntimeProfilingResource.java | 11 +++- .../physicschunk/PhysicsChunkTypes.java | 39 +++------------ .../physicsentity/PhysicsEntityTypes.java | 50 +++---------------- 9 files changed, 76 insertions(+), 89 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java index 99bcb5f7..1f274dc0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java @@ -4,14 +4,16 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Durable ownership marker for Impulse-generated visual proxy entities. */ public final class GeneratedVisualProxyComponent implements Component { + @Nullable + private static ComponentType componentType; @Nonnull public static final BuilderCodec CODEC = BuilderCodec.builder( GeneratedVisualProxyComponent.class, @@ -19,7 +21,12 @@ public final class GeneratedVisualProxyComponent implements Component getComponentType() { - return PhysicsEntityTypes.generatedVisualProxyComponentType(); + return componentType; + } + + public static void setComponentType( + @Nonnull ComponentType type) { + componentType = type; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index ad94e4e2..4f1e3b10 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -3,12 +3,12 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import lombok.Getter; import lombok.Setter; @@ -23,6 +23,9 @@ @Getter public class PhysicsDebugResource implements Resource { + @Nullable + private static ResourceType resourceType; + public static final float MIN_REFRESH_SECONDS = 0.05f; public static final float MAX_REFRESH_SECONDS = 2.0f; public static final float DEFAULT_OVERLAY_REFRESH_SECONDS = 0.10f; @@ -166,7 +169,12 @@ public PhysicsDebugResource clone() { } public static ResourceType getResourceType() { - return PhysicsEntityTypes.physicsDebugResourceType(); + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; } private static float clampRefresh(float value) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index cf87dea2..7d31df56 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -24,6 +23,9 @@ */ public final class PhysicsProjectionIndexResource implements Resource { + @Nullable + private static ResourceType resourceType; + private final Map>> bodyAttachments = new Object2ObjectOpenHashMap<>(); private final Int2ObjectOpenHashMap bodyAttachmentsByRowIndex = @@ -276,7 +278,12 @@ public PhysicsProjectionIndexResource clone() { } public static ResourceType getResourceType() { - return PhysicsEntityTypes.physicsProjectionIndexResourceType(); + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; } private void unregisterAttachmentRef(@Nonnull Ref bodyRef, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java index 0a536f12..f103b0e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -12,12 +11,15 @@ import java.util.Queue; import java.util.function.Predicate; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Copied terrain mutation queue drained by PhysicsStore.tick(). */ public final class PhysicsTerrainMutationQueueResource implements Resource { + @Nullable + private static ResourceType resourceType; @Nonnull private final Queue mutations = new ArrayDeque<>(); @@ -63,6 +65,11 @@ public synchronized PhysicsTerrainMutationQueueResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsChunkTypes.terrainMutationQueueResourceType(); + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java index e785f1e2..dc936c91 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import javax.annotation.Nonnull; @@ -15,6 +14,8 @@ */ public final class PhysicsTerrainPayloadResource implements Resource { + @Nullable + private static ResourceType resourceType; @Nonnull private final Map payloadsByKey = new Object2ObjectOpenHashMap<>(); @@ -49,6 +50,11 @@ public PhysicsTerrainPayloadResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsChunkTypes.terrainPayloadResourceType(); + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java index 22c1f838..5020bf01 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.TerrainColliderMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -20,6 +19,8 @@ */ public final class PhysicsWorldCollisionIndexResource implements Resource { + @Nullable + private static ResourceType resourceType; @Nonnull private final Map settingsBySpaceUuid = new Object2ObjectOpenHashMap<>(); @@ -58,7 +59,12 @@ public synchronized PhysicsWorldCollisionIndexResource clone() { @Nonnull public static ResourceType getResourceType() { - return PhysicsChunkTypes.worldCollisionIndexResourceType(); + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; } public record SpaceWorldCollisionSettings(@Nonnull UUID spaceUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java index e2dfb2c2..37cff850 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; @@ -19,6 +18,9 @@ */ public class PhysicsRuntimeProfilingResource implements Resource { + @Nullable + private static ResourceType resourceType; + private boolean enabled; private final StepSnapshot cumulativeStep = new StepSnapshot(); @@ -367,7 +369,12 @@ private static long secondsToNanos(float seconds) { } public static ResourceType getResourceType() { - return PhysicsEntityTypes.physicsRuntimeProfilingResourceType(); + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java index af2689ac..2cf6eea0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java @@ -33,16 +33,6 @@ public final class PhysicsChunkTypes { private static ComponentType terrainColliderComponentType; @Nullable private static ComponentType worldCollisionComponentType; - @Nullable - private static ResourceType - terrainMutationQueueResourceType; - @Nullable - private static ResourceType - terrainPayloadResourceType; - @Nullable - private static ResourceType - worldCollisionIndexResourceType; - private PhysicsChunkTypes() { } @@ -60,15 +50,15 @@ public static void registerComponentTypes( public static void registerResourceTypes( @Nonnull ComponentRegistryProxy registry) { - terrainMutationQueueResourceType = registry.registerResource( + PhysicsTerrainMutationQueueResource.setResourceType(registry.registerResource( PhysicsTerrainMutationQueueResource.class, - PhysicsTerrainMutationQueueResource::new); - terrainPayloadResourceType = registry.registerResource( + PhysicsTerrainMutationQueueResource::new)); + PhysicsTerrainPayloadResource.setResourceType(registry.registerResource( PhysicsTerrainPayloadResource.class, - PhysicsTerrainPayloadResource::new); - worldCollisionIndexResourceType = registry.registerResource( + PhysicsTerrainPayloadResource::new)); + PhysicsWorldCollisionIndexResource.setResourceType(registry.registerResource( PhysicsWorldCollisionIndexResource.class, - PhysicsWorldCollisionIndexResource::new); + PhysicsWorldCollisionIndexResource::new)); } public static void registerSystems(@Nonnull ComponentRegistryProxy registry) { @@ -140,21 +130,4 @@ private static > void cleanupResource( return worldCollisionComponentType; } - @Nonnull - public static ResourceType - terrainMutationQueueResourceType() { - return terrainMutationQueueResourceType; - } - - @Nonnull - public static ResourceType - terrainPayloadResourceType() { - return terrainPayloadResourceType; - } - - @Nonnull - public static ResourceType - worldCollisionIndexResourceType() { - return worldCollisionIndexResourceType; - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index 0dfbfcfd..cd582346 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -31,19 +31,8 @@ public final class PhysicsEntityTypes { @Nullable private static ComponentType bodyAttachmentComponentType; @Nullable - private static ComponentType - generatedVisualProxyComponentType; - @Nullable private static ResourceType physicsWorldResourceType; @Nullable - private static ResourceType physicsDebugResourceType; - @Nullable - private static ResourceType - physicsRuntimeProfilingResourceType; - @Nullable - private static ResourceType - physicsProjectionIndexResourceType; - @Nullable private static WorldEventType physicsEventFramePublishedEventType; @Nullable @@ -57,23 +46,23 @@ public static void registerComponentTypes(@Nonnull ComponentRegistryProxy registry) { physicsWorldResourceType = registry.registerResource(PhysicsWorldResource.class, PhysicsWorldRuntimeResource::new); - physicsDebugResourceType = registry.registerResource(PhysicsDebugResource.class, - PhysicsDebugResource::new); - physicsRuntimeProfilingResourceType = registry.registerResource( + PhysicsDebugResource.setResourceType(registry.registerResource(PhysicsDebugResource.class, + PhysicsDebugResource::new)); + PhysicsRuntimeProfilingResource.setResourceType(registry.registerResource( PhysicsRuntimeProfilingResource.class, - PhysicsRuntimeProfilingResource::new); - physicsProjectionIndexResourceType = registry.registerResource( + PhysicsRuntimeProfilingResource::new)); + PhysicsProjectionIndexResource.setResourceType(registry.registerResource( PhysicsProjectionIndexResource.class, - PhysicsProjectionIndexResource::new); + PhysicsProjectionIndexResource::new)); } public static void registerEventTypes(@Nonnull ComponentRegistryProxy registry) { @@ -99,34 +88,11 @@ public static ComponentType bodyAttachment return bodyAttachmentComponentType; } - @Nonnull - public static ComponentType - generatedVisualProxyComponentType() { - return generatedVisualProxyComponentType; - } - @Nonnull public static ResourceType physicsWorldResourceType() { return physicsWorldResourceType; } - @Nonnull - public static ResourceType physicsDebugResourceType() { - return physicsDebugResourceType; - } - - @Nonnull - public static ResourceType - physicsRuntimeProfilingResourceType() { - return physicsRuntimeProfilingResourceType; - } - - @Nonnull - public static ResourceType - physicsProjectionIndexResourceType() { - return physicsProjectionIndexResourceType; - } - @Nonnull public static WorldEventType physicsEventFramePublishedEventType() { From 2973af63e86f3067d6b6a1a21a859332c27fc725 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:11:35 +0200 Subject: [PATCH 321/534] refactor(core): move world collision off world resource Signed-off-by: Blovien --- .../PhysicsWorldRuntimeResource.java | 133 ------------------ .../resources/PhysicsWorldResource.java | 48 +------ .../commands/PhysicsStoreExampleCommands.java | 5 +- .../commands/WorldCollisionCommand.java | 26 ++-- .../commands/stress/StressBodiesCommand.java | 6 +- .../explosive/ExplosiveBlockRuntime.java | 11 +- 6 files changed, 33 insertions(+), 196 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index bbdde716..baf08610 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -32,7 +32,6 @@ import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsWorldCollisionRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; @@ -40,11 +39,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -84,7 +79,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; -import org.joml.Vector3d; import org.joml.Vector3f; /** @@ -1059,120 +1053,6 @@ public int getBodySnapshotCellCount() { return lifecycleState.bodySnapshotCellCount(); } - @Nonnull - @Override - public WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - if (isAuthoritativePhysicsStoreActive()) { - return PhysicsWorldCollision.rebuildAround(world, - authoritativePhysicsStore("rebuild world collision"), - spaceId, - center, - radius); - } - requireLegacyMutationAllowed("rebuild world collision"); - requireWorldCollisionLifecycleEnabled(); - return callDirectRuntime("rebuild world collision", () -> { - PhysicsSpaceBinding space = requireSpaceBinding(spaceId); - requireWorldCollisionSpaceEnabled(spaceId); - WorldCollisionBuildOptions buildOptions = - WorldCollisionBuildOptions.fromSettings(getLiveSpaceSettings(spaceId) - .getWorldCollisionSettings()); - return collisionRuntime.rebuildAround(world, - space, - center, - radius, - buildOptions); - }); - } - - @Nonnull - @Override - public WorldCollisionBuildStats refreshWorldCollisionAround(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - if (isAuthoritativePhysicsStoreActive()) { - return PhysicsWorldCollision.refreshAround(world, - authoritativePhysicsStore("refresh world collision"), - spaceId, - center, - radius); - } - requireLegacyMutationAllowed("refresh world collision"); - requireWorldCollisionLifecycleEnabled(); - return callDirectRuntime("refresh world collision", () -> { - PhysicsSpaceBinding space = requireSpaceBinding(spaceId); - requireWorldCollisionSpaceEnabled(spaceId); - WorldCollisionBuildOptions buildOptions = - WorldCollisionBuildOptions.fromSettings(getLiveSpaceSettings(spaceId) - .getWorldCollisionSettings()); - return collisionRuntime.refreshAround(world, - space, - center, - radius, - buildOptions); - }); - } - - @Nonnull - @Override - public WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Iterable centers, - int radius, - long tick) { - if (isAuthoritativePhysicsStoreActive()) { - return PhysicsWorldCollision.ensureAround(world, - authoritativePhysicsStore("ensure world collision"), - spaceId, - centers, - radius, - tick); - } - requireLegacyMutationAllowed("ensure world collision"); - Objects.requireNonNull(centers, "centers"); - requireWorldCollisionLifecycleEnabled(); - return callDirectRuntime("ensure world collision", () -> { - PhysicsSpaceBinding space = requireSpaceBinding(spaceId); - requireWorldCollisionSpaceEnabled(spaceId); - WorldCollisionBuildOptions buildOptions = - WorldCollisionBuildOptions.fromSettings(getLiveSpaceSettings(spaceId) - .getWorldCollisionSettings()); - return collisionRuntime.ensureAround(world, - space, - centers, - radius, - tick, - buildOptions); - }); - } - - @Override - public int clearWorldCollision(@Nonnull SpaceId spaceId) { - if (isAuthoritativePhysicsStoreActive()) { - return PhysicsWorldCollision.clearSpace(requireAuthoritativeWorld("clear world collision"), - authoritativePhysicsStore("clear world collision"), - spaceId); - } - requireLegacyMutationAllowed("clear world collision"); - return callDirectRuntime("clear world collision", () -> { - PhysicsSpaceBinding space = requireSpaceBinding(spaceId); - return collisionRuntime.clear(space); - }); - } - - @Nonnull - @Override - public WorldCollisionStats getWorldCollisionStats() { - if (isAuthoritativePhysicsStoreActive()) { - return PhysicsWorldCollision.stats(requireAuthoritativeWorld("read world collision stats")); - } - return callDirectRuntime("read world collision stats", collisionRuntime::getStats); - } - @Nonnull private PhysicsStoreWorldCollisionStreamingResource authoritativeWorldCollisionStreaming() { Store entityStore = owningStore; @@ -1242,19 +1122,6 @@ private void restoreCollisionLodFiltersDirect() { } } - private static void requireWorldCollisionLifecycleEnabled() { - if (!WorldCollisionLifecycle.isEnabled()) { - throw new IllegalStateException("Impulse world collision subplugin is disabled"); - } - } - - private void requireWorldCollisionSpaceEnabled(@Nonnull SpaceId spaceId) { - if (getLiveSpaceSettings(spaceId).getWorldCollisionSettings().getWorldCollisionMode() - == WorldCollisionMode.NONE) { - throw new IllegalStateException("World collision is disabled for space " + spaceId); - } - } - @Override public void forEachBodySnapshot(@Nonnull SpaceId spaceId, @Nonnull Consumer consumer) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 9edb0899..6bfef0e4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; @@ -13,9 +12,6 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; @@ -25,7 +21,6 @@ import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.joml.Vector3d; import org.joml.Vector3f; /** @@ -33,8 +28,7 @@ * *

    The concrete Impulse runtime lives in the internal package. Plugin-facing code should depend on * this facade for explicit space lifecycle, world settings, body lifetime by durable UUID, - * immutable snapshots, read-only registration views, public attachment/control hooks, and world - * collision operations.

    + * immutable snapshots, read-only registration views, and public attachment/control hooks.

    * *

    No physics space is created implicitly. Consumers choose which explicit {@link SpaceId} to * target for each operation.

    @@ -199,46 +193,6 @@ public abstract PhysicsMutationHandle createSpaceAsync( */ public abstract int getBodySnapshotCellCount(); - /** - * Rebuilds world collision around a center for the requested space. - */ - @Nonnull - public abstract WorldCollisionBuildStats rebuildWorldCollisionAround(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius); - - /** - * Rebuilds cached world-collision sections around a center for the requested space without - * clearing retained terrain outside that radius. - */ - @Nonnull - public abstract WorldCollisionBuildStats refreshWorldCollisionAround(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius); - - /** - * Ensures world collision exists around one or more centers for the requested space. - */ - @Nonnull - public abstract WorldCollisionPrewarmStats ensureWorldCollisionAround(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Iterable centers, - int radius, - long tick); - - /** - * Clears cached world collision for the requested space. - */ - public abstract int clearWorldCollision(@Nonnull SpaceId spaceId); - - /** - * Returns world-collision runtime statistics. - */ - @Nonnull - public abstract WorldCollisionStats getWorldCollisionStats(); - /** * Iterates published body snapshots for one space. */ diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 3a532ee1..095f91f6 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -19,6 +19,7 @@ import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; @@ -31,6 +32,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockPolicy; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -338,7 +340,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, + WorldCollisionPrewarmStats stats = PhysicsWorldCollision.ensureAround(world, + ((PhysicsStoreWorld) world).getPhysicsStore().getStore(), spaceId, List.of(spawn), Math.max(8, radius + 6), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java index b0bad454..60be54b5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java @@ -11,11 +11,13 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -36,6 +38,11 @@ public WorldCollisionCommand() { addSubCommand(new StatsCommand()); } + @Nonnull + private static Store physicsStore(@Nonnull World world) { + return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + } + private static final class BuildCommand extends AbstractAsyncPlayerCommand { private static final int DEFAULT_RADIUS = 8; @@ -68,8 +75,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - WorldCollisionBuildStats stats = resource.rebuildWorldCollisionAround(world, + Store physicsStore = physicsStore(world); + WorldCollisionBuildStats stats = PhysicsWorldCollision.rebuildAround(world, + physicsStore, spaceId, playerPos, radius); @@ -122,8 +130,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, + Store physicsStore = physicsStore(world); + WorldCollisionPrewarmStats stats = PhysicsWorldCollision.ensureAround(world, + physicsStore, spaceId, List.of(playerPos), radius, @@ -162,8 +171,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (spaceId == null) { return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - int removed = resource.clearWorldCollision(spaceId); + Store physicsStore = physicsStore(world); + int removed = PhysicsWorldCollision.clearSpace(world, physicsStore, spaceId); ctx.sender().sendMessage(Message.raw("Removed " + removed + " world voxel collision bodies.")); return CompletableFuture.completedFuture(null); @@ -183,8 +192,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - WorldCollisionStats stats = resource.getWorldCollisionStats(); + WorldCollisionStats stats = PhysicsWorldCollision.stats(world); ctx.sender().sendMessage(Message.raw("World voxel collision: " + stats.spaces() + " spaces, " + stats.sections() + " sections, " diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index a88c1064..0c88c8d0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; @@ -30,6 +31,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.Iterator; @@ -335,8 +337,8 @@ private static int prewarmStressWorldCollision(@Nonnull Store store return 0; } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - WorldCollisionPrewarmStats stats = resource.ensureWorldCollisionAround(world, + WorldCollisionPrewarmStats stats = PhysicsWorldCollision.ensureAround(world, + ((PhysicsStoreWorld) world).getPhysicsStore().getStore(), spaceId, layout.positions(count), worldCollisionSettings.getWorldCollisionBodyRadius(), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 31b8730c..b5f612f0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -22,9 +22,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; @@ -133,12 +134,14 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e } List groups = groupFragments(fragments, center, settings.getRadius()); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - resource.refreshWorldCollisionAround(world, + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + PhysicsWorldCollision.refreshAround(world, + physicsStore, spaceId, center, Math.max(8, settings.getRadius() + 4)); - resource.ensureWorldCollisionAround(world, + PhysicsWorldCollision.ensureAround(world, + physicsStore, spaceId, groupCenters(groups), Math.max(8, maxGroupCollisionRadius(groups) + 4), From 6eac3360ee79ce9bfd334cdb1e9d90cd4ec90eb3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:15:28 +0200 Subject: [PATCH 322/534] refactor(examples): read physics state through store helpers Signed-off-by: Blovien --- .../examples/commands/GrabCommand.java | 24 ++++++------- .../commands/PhysicsStoreExampleCommands.java | 24 +++++++------ .../commands/stress/StressBodiesCommand.java | 36 +++++++++++-------- .../systems/ExplosiveFuseContactSystem.java | 26 +++++++++----- 4 files changed, 65 insertions(+), 45 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index f663ddd0..452377f0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -28,12 +28,13 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.projection.PhysicsAttachments; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -91,7 +92,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); @@ -107,7 +108,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, world, store, ref, - resource, + physicsStore, targetSpaceId, controllableType, hits)); @@ -117,11 +118,11 @@ private static void finishGrab(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref, - @Nonnull PhysicsWorldResource resource, + @Nonnull Store physicsStore, @Nonnull SpaceId targetSpaceId, @Nonnull ComponentType controllableType, @Nonnull List hits) { - HitSelection selection = selectControllableHit(resource, + HitSelection selection = selectControllableHit(physicsStore, store, controllableType, hits); @@ -133,7 +134,7 @@ private static void finishGrab(@Nonnull CommandContext ctx, PhysicsControlSessions.releaseSession(store, ref); SpaceId selectedSpaceId = selection.spaceId() != null ? selection.spaceId() : targetSpaceId; - if (!resource.hasSpace(selectedSpaceId)) { + if (!PhysicsSpaces.hasSpace(physicsStore, selectedSpaceId)) { ctx.sender().sendMessage(Message.raw("Selected physics space no longer exists.")); return; } @@ -241,7 +242,7 @@ private static JointComponent controlJoint(@Nonnull Ref spaceRef, } @Nullable - private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource resource, + private static HitSelection selectControllableHit(@Nonnull Store physicsStore, @Nonnull Store store, @Nonnull ComponentType controllableType, @Nonnull List hits) { @@ -253,7 +254,7 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource continue; } PhysicsBodyRegistrationView registration = - PhysicsBodies.registrationView(hit.bodyRef().getStore(), hit.bodyRef()); + PhysicsBodies.registrationView(physicsStore, hit.bodyRef()); if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { continue; } @@ -266,7 +267,7 @@ private static HitSelection selectControllableHit(@Nonnull PhysicsWorldResource HitSelection best = null; for (HitCandidate candidate : candidates) { AttachmentSelection attachments = - inspectGameplayAttachments(resource, store, controllableType, candidate.bodyRef()); + inspectGameplayAttachments(store, controllableType, candidate.bodyRef()); if (attachments.controllableAttachment() == null && attachments.hasGameplayAttachment()) { continue; } @@ -291,12 +292,11 @@ private static PhysicsBodySnapshot bodyState(@Nonnull World world, } @Nonnull - private static AttachmentSelection inspectGameplayAttachments(@Nonnull PhysicsWorldResource resource, - @Nonnull Store store, + private static AttachmentSelection inspectGameplayAttachments(@Nonnull Store store, @Nonnull ComponentType controllableType, @Nonnull Ref bodyRef) { boolean hasGameplayAttachment = false; - for (Ref attachmentRef : resource.getBodyAttachments(bodyRef)) { + for (Ref attachmentRef : PhysicsAttachments.attachments(store, bodyRef)) { BodyAttachmentComponent attachment = store.getComponent(attachmentRef, ATTACHMENT_TYPE); if (attachment == null || attachment.getLifecycle() == BodyAttachmentComponent.AttachmentLifecycle.GENERATED_PROXY) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 095f91f6..bb1ed60c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -22,12 +22,13 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -257,9 +258,10 @@ private static void attachView(@Nonnull CommandContext ctx, @Nullable private static UUID physicsStoreBodyUuid(@Nonnull Ref bodyRef) { - UuidComponent uuid = bodyRef.getStore() - .getComponent(bodyRef, UuidComponent.getComponentType()); - return uuid != null ? uuid.getUuid() : null; + PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView( + bodyRef.getStore(), + bodyRef); + return registration != null ? registration.bodyUuid() : null; } } @@ -331,8 +333,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("No valid explosive block type is available.")); return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - boolean contactEventsEnabled = contactEventsEnabled(resource); + Store physicsStore = + ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + boolean contactEventsEnabled = contactEventsEnabled(physicsStore); Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); if (spaceRef == null) { @@ -341,7 +344,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } WorldCollisionPrewarmStats stats = PhysicsWorldCollision.ensureAround(world, - ((PhysicsStoreWorld) world).getPhysicsStore().getStore(), + physicsStore, spaceId, List.of(spawn), Math.max(8, radius + 6), @@ -395,8 +398,9 @@ private static BlockType blockType(@Nonnull String blockTypeId) { return BlockType.getAssetMap().getAsset(ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE); } - private static boolean contactEventsEnabled(@Nonnull PhysicsWorldResource resource) { - return resource.getWorldSettings().getEventCollectionMode() == PhysicsEventCollectionMode.CONTACTS; + private static boolean contactEventsEnabled(@Nonnull Store physicsStore) { + return PhysicsWorlds.settings(physicsStore).getEventCollectionMode() + == PhysicsEventCollectionMode.CONTACTS; } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 0c88c8d0..b8164b3f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -19,7 +19,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; @@ -155,19 +156,23 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound.")); return CompletableFuture.completedFuture(null); } - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - PhysicsSpaceSettings settings = configureStressRuntime(resource, - spaceId, + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + PhysicsSpaceSettings settings = configureStressRuntime(physicsStore, + spaceRef, mode, visibility, visualSettings, collisionLod); + if (settings == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + + " no longer exists.")); + return CompletableFuture.completedFuture(null); + } TimeResource time = store.getResource(TimeResource.getResourceType()); StressLayout layout = StressLayout.forCount(count, playerPos); long prewarmStartNanos = System.nanoTime(); - int prewarmedSections = prewarmStressWorldCollision(store, - world, + int prewarmedSections = prewarmStressWorldCollision(world, spaceId, settings, mode, @@ -230,7 +235,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getVisualMaterializationSettings(); PhysicsVisualSyncSettings visualSyncSettings = settings.getVisualSyncSettings(); PhysicsCollisionLodSettings collisionLodSettings = settings.getCollisionLodSettings(); - PhysicsWorldSettings worldSettings = resource.getWorldSettings(); + PhysicsWorldSettings worldSettings = PhysicsWorlds.settings(physicsStore); ctx.sender().sendMessage(Message.raw("Added " + count + " stress bodies: setupWallMs=" @@ -274,14 +279,18 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - @Nonnull - private static PhysicsSpaceSettings configureStressRuntime(@Nonnull PhysicsWorldResource resource, - @Nonnull SpaceId spaceId, + @Nullable + private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef, @Nonnull StressMode mode, @Nonnull StressVisibility visibility, @Nonnull StressVisualSettings visualSettings, @Nullable Boolean collisionLod) { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); + PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, spaceRef); + if (currentSettings == null) { + return null; + } + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); PhysicsSolverSettings solverSettings = settings.getSolverSettings(); solverSettings.setSolverIterations(1); solverSettings.setStabilizationIterations(1); @@ -319,12 +328,11 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull PhysicsWorld settings.getCollisionLodSettings().setCollisionLodEnabled(collisionLod); } } - resource.setSpaceSettings(spaceId, settings); + PhysicsSpaces.putSettings(physicsStore, spaceRef, settings); return settings; } - private static int prewarmStressWorldCollision(@Nonnull Store store, - @Nonnull World world, + private static int prewarmStressWorldCollision(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings, @Nonnull StressMode mode, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index ce3982c4..bf3d0910 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -6,14 +6,17 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.system.WorldEventSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.projection.PhysicsAttachments; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -40,19 +43,22 @@ public ExplosiveFuseContactSystem() { public void handle(@Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull PhysicsEventFramePublishedEvent event) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = + ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore().getStore(); long tick = Math.max(0L, store.getExternalData().getWorld().getTick()); for (PhysicsFrameEvent frameEvent : event.frame().physicsEvents()) { if (frameEvent instanceof PhysicsContactEvent contact && contact.phase() != PhysicsContactPhase.ENDED) { armIfExplosiveTouchesWorld(commandBuffer, - resource, + store, + physicsStore, tick, contact.bodyAUuid(), contact.bodyBUuid(), contactCenter(contact.pointOnB())); armIfExplosiveTouchesWorld(commandBuffer, - resource, + store, + physicsStore, tick, contact.bodyBUuid(), contact.bodyAUuid(), @@ -62,15 +68,16 @@ public void handle(@Nonnull Store store, } private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer commandBuffer, - @Nonnull PhysicsWorldResource resource, + @Nonnull Store store, + @Nonnull Store physicsStore, long tick, @Nonnull UUID explosiveBodyUuid, @Nonnull UUID otherBodyUuid, @Nonnull Vector3d explosionCenter) { - if (!isWorldCollision(resource, otherBodyUuid)) { + if (!isWorldCollision(physicsStore, otherBodyUuid)) { return; } - for (Ref ref : resource.getBodyAttachments(explosiveBodyUuid, null)) { + for (Ref ref : PhysicsAttachments.attachments(store, explosiveBodyUuid)) { BodyAttachmentComponent attachment = commandBuffer.getComponent(ref, ATTACHMENT_TYPE); ExplosiveBlockComponent explosive = commandBuffer.getComponent(ref, EXPLOSIVE_TYPE); ExplosiveFuseComponent fuse = commandBuffer.getComponent(ref, FUSE_TYPE); @@ -87,9 +94,10 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer physicsStore, @Nonnull UUID bodyUuid) { - PhysicsBodyRegistrationView registration = resource.getBodyRegistrationView(bodyUuid); + PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView(physicsStore, + bodyUuid); return registration != null && registration.kind() == PhysicsBodyKind.WORLD_COLLISION; } From 8e3323f2d810468957f41701d97805d2f0e73925 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:18:04 +0200 Subject: [PATCH 323/534] refactor(physicschunk): resolve settings spaces as refs Signed-off-by: Blovien --- .../commands/CollisionLodSettingsCommand.java | 26 +++++++++++------ .../WorldCollisionPerfReportCommand.java | 8 ++++-- .../WorldCollisionSettingsCommand.java | 26 +++++++++++------ .../WorldCollisionSpaceSelection.java | 28 ++++++++++++++----- 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java index e2aeb057..d97efa3c 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java @@ -10,10 +10,12 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -69,15 +71,23 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, resource); - if (spaceId == null) { + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + WorldCollisionSpaceSelection.Selection selection = + WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); + if (selection == null) { return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); + PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, + selection.spaceRef()); + if (currentSettings == null) { + ctx.sender().sendMessage(Message.raw("Physics space id=" + selection.spaceId().value() + + " no longer exists.")); + return CompletableFuture.completedFuture(null); + } + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { - sendSummary(ctx, spaceId, settings); + sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } @@ -133,8 +143,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getCollisionLodSettings().setCollisionLodHysteresis(hysteresis); settings.getCollisionLodSettings().setCollisionLodRefreshIntervalTicks(interval); settings.getCollisionLodSettings().setCollisionLodFarSleepEnabled(farSleep); - resource.setSpaceSettings(spaceId, settings); - sendSummary(ctx, spaceId, settings); + PhysicsSpaces.putSettings(physicsStore, selection.spaceRef(), settings); + sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java index 7c2d71a3..a06bef68 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityDiagnostics; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling; @@ -16,8 +17,9 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -60,7 +62,7 @@ private static void sendReport(@Nonnull CommandContext ctx, var latest = profiling.latest(); var worst = profiling.worst(); PhysicsEntityDiagnostics.Snapshot entityDiagnostics = PhysicsEntityDiagnostics.collect(store); - PhysicsWorldResource physicsWorld = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(summaries); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling: " @@ -72,7 +74,7 @@ private static void sendReport(@Nonnull CommandContext ctx, + runtimeFootprint.runtimeStatsSummary())); } ctx.sender().sendMessage(Message.raw("Physics event frame: " - + formatEventFrameSummary(physicsWorld.getLatestEventFrame()))); + + formatEventFrameSummary(PhysicsWorlds.latestEventFrame(physicsStore)))); ctx.sender().sendMessage(Message.raw("Hytale entity diagnostics: " + entityDiagnostics.hytaleSummary())); ctx.sender().sendMessage(Message.raw("Impulse entity diagnostics: " diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java index f4a4de35..898a64bd 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java @@ -10,12 +10,14 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -69,15 +71,23 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, resource); - if (spaceId == null) { + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + WorldCollisionSpaceSelection.Selection selection = + WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); + if (selection == null) { return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(resource.getSpaceSettings(spaceId)); + PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, + selection.spaceRef()); + if (currentSettings == null) { + ctx.sender().sendMessage(Message.raw("Physics space id=" + selection.spaceId().value() + + " no longer exists.")); + return CompletableFuture.completedFuture(null); + } + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { - sendSummary(ctx, spaceId, settings); + sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } @@ -135,8 +145,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getWorldCollisionSettings().setWorldCollisionRadius(playerRadius); settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(bodyRadius); settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(ttl); - resource.setSpaceSettings(spaceId, settings); - sendSummary(ctx, spaceId, settings); + PhysicsSpaces.putSettings(physicsStore, selection.spaceRef(), settings); + sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java index ed561451..c2054182 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java @@ -1,11 +1,14 @@ package dev.hytalemodding.impulse.physicschunk.commands; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import java.util.Comparator; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -16,10 +19,10 @@ private WorldCollisionSpaceSelection() { } @Nullable - static SpaceId resolve(@Nonnull CommandContext context, + static Selection resolve(@Nonnull CommandContext context, @Nonnull World world, @Nonnull OptionalArg spaceArg, - @Nonnull PhysicsWorldResource resource) { + @Nonnull Store physicsStore) { if (spaceArg.provided(context)) { int rawSpaceId = spaceArg.get(context); if (rawSpaceId <= 0) { @@ -27,22 +30,33 @@ static SpaceId resolve(@Nonnull CommandContext context, return null; } SpaceId spaceId = new SpaceId(rawSpaceId); - if (!resource.hasSpace(spaceId)) { + Ref spaceRef = PhysicsSpaces.resolveRef(physicsStore, spaceId); + if (spaceRef == null) { context.sendMessage(Message.raw("No physics space id=" + rawSpaceId + " exists in world " + world.getName() + ".")); return null; } - return spaceId; + return new Selection(spaceId, spaceRef); } - SpaceId firstSpaceId = resource.getSpaceIds() + SpaceId firstSpaceId = PhysicsSpaces.spaceIds(physicsStore) .stream() .min(Comparator.comparingInt(SpaceId::value)) .orElse(null); if (firstSpaceId == null) { context.sendMessage(Message.raw("No physics space exists. Run " + "`/impulse space create --backend=` before targeting space settings.")); + return null; } - return firstSpaceId; + Ref spaceRef = PhysicsSpaces.resolveRef(physicsStore, firstSpaceId); + if (spaceRef == null) { + context.sendMessage(Message.raw("No physics space id=" + firstSpaceId.value() + + " exists in world " + world.getName() + ".")); + return null; + } + return new Selection(firstSpaceId, spaceRef); + } + + record Selection(@Nonnull SpaceId spaceId, @Nonnull Ref spaceRef) { } } From 87d8a8bebd7226fe419bb58d46cee6bcf51ec32a Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:24:43 +0200 Subject: [PATCH 324/534] refactor(core): route settings commands through physics store Signed-off-by: Blovien --- .../EventCollectionSettingCommand.java | 13 ++++++----- .../settings/MaxStepDtSettingCommand.java | 13 ++++++----- .../SimulationStepsSettingCommand.java | 10 +++++---- .../settings/SolverSettingsCommand.java | 21 +++++++++++------- .../settings/StepModeSettingCommand.java | 22 ++++++++++--------- .../StepSchedulingSettingCommand.java | 13 ++++++----- .../VisualMaterializationSettingsCommand.java | 18 ++++++++++----- .../settings/VisualSyncSettingsCommand.java | 18 ++++++++++----- 8 files changed, 81 insertions(+), 47 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java index fef23a90..f885e68b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java @@ -10,9 +10,11 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -34,9 +36,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); if (!modeArg.provided(ctx)) { - PhysicsEventCollectionMode mode = resource.getWorldSettings().getEventCollectionMode(); + PhysicsEventCollectionMode mode = PhysicsWorlds.settings(physicsStore) + .getEventCollectionMode(); ctx.sender().sendMessage(Message.raw("Impulse event collection: " + mode.getSerializedName())); return CompletableFuture.completedFuture(null); @@ -51,9 +54,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsWorldSettings settings = resource.getWorldSettings(); + PhysicsWorldSettings settings = PhysicsWorlds.settings(physicsStore); settings.setEventCollectionMode(mode); - resource.setWorldSettings(settings); + PhysicsWorlds.putSettings(physicsStore, settings); ctx.sender().sendMessage(Message.raw("Impulse event collection set to " + mode.getSerializedName())); return CompletableFuture.completedFuture(null); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java index 0c286be8..009cc610 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java @@ -10,8 +10,10 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -33,10 +35,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); if (!dtArg.provided(ctx)) { ctx.sender().sendMessage(Message.raw("Impulse max step dt: " - + resource.getWorldSettings().getMaxStepDt() + " (used by adaptive step modes)")); + + PhysicsWorlds.settings(physicsStore).getMaxStepDt() + + " (used by adaptive step modes)")); return CompletableFuture.completedFuture(null); } @@ -46,9 +49,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsWorldSettings settings = resource.getWorldSettings(); + PhysicsWorldSettings settings = PhysicsWorlds.settings(physicsStore); settings.setMaxStepDt(maxStepDt); - resource.setWorldSettings(settings); + PhysicsWorlds.putSettings(physicsStore, settings); ctx.sender().sendMessage(Message.raw("Impulse max step dt set to " + maxStepDt)); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java index 10adadcb..60b2c627 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java @@ -10,9 +10,11 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -34,8 +36,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - PhysicsWorldSettings settings = resource.getWorldSettings(); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + PhysicsWorldSettings settings = PhysicsWorlds.settings(physicsStore); PhysicsStepMode stepMode = settings.getStepMode(); if (!stepsArg.provided(ctx)) { ctx.sender().sendMessage(Message.raw("Impulse simulation steps: " @@ -54,7 +56,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } settings.setSimulationSteps(steps); - resource.setWorldSettings(settings); + PhysicsWorlds.putSettings(physicsStore, settings); ctx.sender().sendMessage(Message.raw("Impulse simulation steps set to " + steps + " (" + stepMode.describeSimulationSteps() + " in " + stepMode.getSerializedName() + " mode)")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 640bf707..0ccee78d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -14,9 +14,10 @@ import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -55,8 +56,7 @@ public SolverSettingsCommand() { @Override protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { - Store store = world.getEntityStore().getStore(); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, world, spaceArg); @@ -66,16 +66,21 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, SpaceId spaceId = selectedSpace.spaceId(); return PhysicsAsync.acceptOnWorldThread(world, PhysicsDiagnostics.solverCapabilityAsync(world, selectedSpace.spaceRef()), - summary -> applySettings(ctx, resource, selectedSpace.spaceRef(), spaceId, summary)); + summary -> applySettings(ctx, physicsStore, selectedSpace.spaceRef(), spaceId, summary)); } private void applySettings(@Nonnull CommandContext ctx, - @Nonnull PhysicsWorldResource resource, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull SolverCapabilitySummary summary) { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings( - resource.getSpaceSettings(spaceRef)); + PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, spaceRef); + if (currentSettings == null) { + ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() + + " no longer exists.")); + return; + } + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, summary, settings); return; @@ -113,7 +118,7 @@ private void applySettings(@Nonnull CommandContext ctx, settings.getSolverSettings().setSolverIterations(solverIterations); settings.getSolverSettings().setStabilizationIterations(stabilizationIterations); settings.getSolverSettings().setDynamicSleepTuning(sleepLinearThreshold, sleepAngularThreshold, sleepTime); - resource.setSpaceSettings(spaceRef, settings); + PhysicsSpaces.putSettings(physicsStore, spaceRef, settings); sendSummary(ctx, spaceId, summary, settings); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java index fb3cb01f..95148359 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java @@ -10,12 +10,14 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -38,10 +40,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); if (!modeArg.provided(ctx)) { ctx.sender().sendMessage(Message.raw("Impulse step mode: " - + resource.getWorldSettings().getStepMode().getSerializedName())); + + PhysicsWorlds.settings(physicsStore).getStepMode().getSerializedName())); return CompletableFuture.completedFuture(null); } @@ -57,15 +59,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (stepMode == PhysicsStepMode.CCD) { return PhysicsAsync.acceptOnWorldThread(world, PhysicsDiagnostics.unsupportedCcdSpacesAsync(world), - summaries -> applyStepModeIfSupported(ctx, resource, stepMode, summaries)); + summaries -> applyStepModeIfSupported(ctx, physicsStore, stepMode, summaries)); } - applyStepMode(ctx, resource, stepMode); + applyStepMode(ctx, physicsStore, stepMode); return CompletableFuture.completedFuture(null); } private static void applyStepModeIfSupported(@Nonnull CommandContext ctx, - @Nonnull PhysicsWorldResource resource, + @Nonnull Store physicsStore, @Nonnull PhysicsStepMode stepMode, @Nonnull List unsupportedSummaries) { List unsupportedSpaces = unsupportedSummaries.stream() @@ -76,15 +78,15 @@ private static void applyStepModeIfSupported(@Nonnull CommandContext ctx, + String.join(", ", unsupportedSpaces))); return; } - applyStepMode(ctx, resource, stepMode); + applyStepMode(ctx, physicsStore, stepMode); } private static void applyStepMode(@Nonnull CommandContext ctx, - @Nonnull PhysicsWorldResource resource, + @Nonnull Store physicsStore, @Nonnull PhysicsStepMode stepMode) { - PhysicsWorldSettings settings = resource.getWorldSettings(); + PhysicsWorldSettings settings = PhysicsWorlds.settings(physicsStore); settings.setStepMode(stepMode); - resource.setWorldSettings(settings); + PhysicsWorlds.putSettings(physicsStore, settings); ctx.sender().sendMessage(Message.raw("Impulse step mode set to " + stepMode.getSerializedName())); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java index 35b34648..ff558a82 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java @@ -10,9 +10,11 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -34,9 +36,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); if (!modeArg.provided(ctx)) { - PhysicsStepSchedulingMode mode = resource.getWorldSettings().getStepSchedulingMode(); + PhysicsStepSchedulingMode mode = PhysicsWorlds.settings(physicsStore) + .getStepSchedulingMode(); ctx.sender().sendMessage(Message.raw("Impulse step scheduling: " + mode.getSerializedName() + " (" + mode.describePendingStepBehavior() + ")")); return CompletableFuture.completedFuture(null); @@ -51,9 +54,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsWorldSettings settings = resource.getWorldSettings(); + PhysicsWorldSettings settings = PhysicsWorlds.settings(physicsStore); settings.setStepSchedulingMode(mode); - resource.setWorldSettings(settings); + PhysicsWorlds.putSettings(physicsStore, settings); ctx.sender().sendMessage(Message.raw("Impulse step scheduling set to " + mode.getSerializedName() + " (" + mode.describePendingStepBehavior() + ")")); return CompletableFuture.completedFuture(null); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java index 849f4833..4afb4c32 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java @@ -10,11 +10,13 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -87,7 +89,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, world, spaceArg); @@ -96,8 +98,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings settings = new PhysicsSpaceSettings( - resource.getSpaceSettings(selectedSpace.spaceRef())); + PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, + selectedSpace.spaceRef()); + if (currentSettings == null) { + ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() + + " no longer exists.")); + return CompletableFuture.completedFuture(null); + } + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -146,7 +154,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - resource.setSpaceSettings(selectedSpace.spaceRef(), settings); + PhysicsSpaces.putSettings(physicsStore, selectedSpace.spaceRef(), settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java index c7934960..7b89cd9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java @@ -10,12 +10,14 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -110,7 +112,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, world, spaceArg); @@ -119,8 +121,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings settings = new PhysicsSpaceSettings( - resource.getSpaceSettings(selectedSpace.spaceRef())); + PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, + selectedSpace.spaceRef()); + if (currentSettings == null) { + ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() + + " no longer exists.")); + return CompletableFuture.completedFuture(null); + } + PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -259,7 +267,7 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), if (smoothingRateArg.provided(ctx)) { settings.getVisualSyncSettings().setVisualSnapshotSmoothingRate(smoothingRateArg.get(ctx)); } - resource.setSpaceSettings(selectedSpace.spaceRef(), settings); + PhysicsSpaces.putSettings(physicsStore, selectedSpace.spaceRef(), settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } From 59723babcbe4fe7959a071fd1cdf5da8393b0cd2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:26:54 +0200 Subject: [PATCH 325/534] refactor(core): route space commands through physics store Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 3a0763cc..69214eee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -10,6 +10,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.SpaceId; @@ -18,10 +19,13 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -75,11 +79,10 @@ protected void execute(@Nonnull CommandContext context, : PhysicsSpaceSettings.defaults(); settings.getWorldCollisionSettings().setWorldCollisionMode(worldCollisionMode); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); try { - SpaceId spaceId = resource.createSpace(backendId, - world.getName(), - settings); + Impulse.getRuntimeProvider(backendId); + SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId, settings); context.sendMessage(Message.raw("Created physics space id=" + spaceId.value() + " backend=" + backendId.value() @@ -102,25 +105,28 @@ private ListCommand() { @Override protected CompletableFuture executeAsync(@Nonnull CommandContext context, @Nonnull World world) { - Store store = world.getEntityStore().getStore(); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); return PhysicsAsync.acceptOnWorldThread(world, PhysicsDiagnostics.spaceSummariesAsync(world), - summaries -> sendSpaces(context, world, resource, summaries)); + summaries -> sendSpaces(context, world, physicsStore, summaries)); } private static void sendSpaces(@Nonnull CommandContext context, @Nonnull World world, - @Nonnull PhysicsWorldResource resource, + @Nonnull Store physicsStore, @Nonnull List summaries) { List spaces = summaries.stream() .map(summary -> { - PhysicsSpaceSettings settings = resource.getSpaceSettings(summary.spaceId()); + PhysicsSpaceSettings settings = PhysicsSpaces.settings(physicsStore, + summary.spaceId()); + WorldCollisionMode worldCollisionMode = settings != null + ? settings.getWorldCollisionSettings().getWorldCollisionMode() + : WorldCollisionMode.NONE; return new SpaceListEntry(summary.spaceId(), summary.backendId().value(), summary.bodyCount(), summary.jointCount(), - settings.getWorldCollisionSettings().getWorldCollisionMode()); + worldCollisionMode); }) .sorted(Comparator.comparingInt(entry -> entry.spaceId().value())) .toList(); @@ -163,8 +169,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, return CompletableFuture.completedFuture(null); } - Store store = world.getEntityStore().getStore(); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(context, world, spaceArg); @@ -180,12 +185,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, * UUID or live PhysicsStore entity ref, so they still require an explicit clean/destroy * before deleting the space. */ - int registeredBodies = countRegisteredBodies(resource, spaceId); + int registeredBodies = countRegisteredBodies(physicsStore, spaceId); return PhysicsAsync.acceptOnWorldThread(world, PhysicsDiagnostics.spaceSummariesAsync(world, selectedSpace.spaceRef()), summaries -> deleteIfEmpty(context, world, - resource, + physicsStore, spaceId, spaceId.value(), registeredBodies, @@ -194,7 +199,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, private static void deleteIfEmpty(@Nonnull CommandContext context, @Nonnull World world, - @Nonnull PhysicsWorldResource resource, + @Nonnull Store physicsStore, @Nonnull SpaceId spaceId, int rawSpaceId, int registeredBodies, @@ -210,7 +215,8 @@ private static void deleteIfEmpty(@Nonnull CommandContext context, return; } - resource.removeSpace(spaceId, world.getName()); + PhysicsWorldCollision.clearSpace(world, physicsStore, spaceId); + PhysicsSpaces.removeWithContents(physicsStore, spaceId); context.sendMessage(Message.raw("Deleted physics space id=" + rawSpaceId + " with " + backendBodies + " backend bodies and " + joints + " joints.")); } @@ -226,10 +232,10 @@ private static SpaceCounts countSpaceContents(@Nonnull List summar .orElseGet(() -> new SpaceCounts(0, 0)); } - private static int countRegisteredBodies(@Nonnull PhysicsWorldResource resource, + private static int countRegisteredBodies(@Nonnull Store physicsStore, @Nonnull SpaceId spaceId) { int count = 0; - for (PhysicsBodyRegistrationView registration : resource.getBodyRegistrationViews()) { + for (PhysicsBodyRegistrationView registration : PhysicsBodies.registrationViews(physicsStore)) { if (registration.spaceId().equals(spaceId)) { count++; } From 8d2424d01f7bf4919700592781465ecaa7e414f6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:39:51 +0200 Subject: [PATCH 326/534] docs(core): align physics store helper guidance Signed-off-by: Blovien --- impulse-core/README.md | 11 ++++++----- .../core/plugin/resources/PhysicsWorldResource.java | 10 +++++++--- .../core/plugin/settings/PhysicsSpaceSettings.java | 8 ++++---- .../commands/stress/StressBenchmarkCommand.java | 5 +++-- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/impulse-core/README.md b/impulse-core/README.md index fde8024a..c5c48d46 100644 --- a/impulse-core/README.md +++ b/impulse-core/README.md @@ -18,11 +18,12 @@ anywhere under the configured Hytale `mods` directories. ## Event frames -`PhysicsWorldResource.getLatestEventFrame()` exposes the latest value-only physics event frame for -diagnostics. When collection is enabled, backends emit bounded post-step `PhysicsBackendEvent` -batches; core translates them to stable UUID-primary `PhysicsFrameEvent` values and copied -PhysicsStore refs where available, then publishes one `PhysicsEventFramePublishedEvent` Hytale -world event for the completed frame. +`PhysicsWorlds.latestEventFrame(physicsStore)` exposes the latest value-only physics event frame +for diagnostics. The compatibility `PhysicsWorldResource.getLatestEventFrame()` facade returns the +same frame for callers still bound to the EntityStore resource. When collection is enabled, backends +emit bounded post-step `PhysicsBackendEvent` batches; core translates them to stable UUID-primary +`PhysicsFrameEvent` values and copied PhysicsStore refs where available, then publishes one +`PhysicsEventFramePublishedEvent` Hytale world event for the completed frame. Backend event collection is opt-in through `PhysicsWorldSettings.setEventCollectionMode(...)`. Worlds default to `PhysicsEventCollectionMode.DISABLED`; use diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 6bfef0e4..6c01ca2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -13,6 +13,8 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; @@ -26,9 +28,11 @@ /** * Public alpha facade for a world's physics runtime resource. * - *

    The concrete Impulse runtime lives in the internal package. Plugin-facing code should depend on - * this facade for explicit space lifecycle, world settings, body lifetime by durable UUID, - * immutable snapshots, read-only registration views, and public attachment/control hooks.

    + *

    The concrete Impulse runtime lives in the internal package. This facade remains for + * compatibility body lifetime by durable UUID, immutable snapshots, read-only registration views, + * and public attachment/control hooks. New code that already has the real PhysicsStore should use + * {@link PhysicsWorlds} for world settings/event-frame reads and {@link PhysicsSpaces} for space + * lifecycle and per-space settings.

    * *

    No physics space is created implicitly. Consumers choose which explicit {@link SpaceId} to * target for each operation.

    diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index e0f22e30..a93c27f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -1,16 +1,16 @@ package dev.hytalemodding.impulse.core.plugin.settings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import javax.annotation.Nonnull; /** * Per-space configuration aggregate for terrain collision, solver tuning, * collision LOD, visual sync, and detached visual materialization. * - *

    Settings are attached to a space at creation time via - * {@link PhysicsWorldResource#createSpace} and can be changed later with - * {@link PhysicsWorldResource#setSpaceSettings}.

    + *

    Settings are stored on PhysicsStore space entities. New plugin code should create spaces and + * change per-space settings through {@link PhysicsSpaces}; the world-resource space/settings + * methods remain compatibility facades.

    * *

    The grouped accessors expose the domain-owned settings objects. Internal * code should read and mutate the domain group directly instead of adding flat diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 4a7685fe..abee4ac0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -129,8 +129,9 @@ private static void spawnBenchmark(@Nonnull CommandContext ctx, + ". Body-count updates are visible after PhysicsStore binds the new entities" + ". This command measures raw setup/entity attachment; use /impulse-examples stress bodies" + " for detached/detached-view scalability scenarios" - + ". For clean comparisons run /impulse clean, /impulse-world-collision perf reset," - + " /impulse-world-collision perf toggle before spawning, then /impulse-world-collision perf report.")); + + ". For clean comparisons run /impulse clean, /impulse worldcollision perf reset," + + " /impulse worldcollision perf toggle before spawning," + + " then /impulse worldcollision perf report.")); } } From 5251ea2fcbcc333c7b18051428c013e019f47621 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 11:48:12 +0200 Subject: [PATCH 327/534] fix(core): idle-schedule physics body cleanup Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 101 ++++++++++++++---- .../PhysicsStoreTopologyMutations.java | 6 +- .../PhysicsWorldRuntimeResource.java | 15 ++- .../plugin/physicsstore/PhysicsBodies.java | 63 ++++++++++- .../resources/PhysicsWorldResource.java | 8 +- 5 files changed, 165 insertions(+), 28 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index b9b9f075..fbc22735 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -15,15 +15,18 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.UUID; @@ -202,10 +205,29 @@ private void cleanWithinRadius(@Nonnull CommandContext context, return; } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); - resource.refreshBodySnapshots(); - SelectedBodies selectedBodies = selectBodiesNear(resource, center, radius); + Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + SelectedBodies selectedBodies = selectBodiesNear(physicsStore, center, radius); double radiusSquared = (double) radius * radius; + CompletionStage clean = PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + "clean Impulse physics bodies within radius", + backendStore -> cleanSelectedBodies(store, + backendStore, + selectedBodies, + center, + radiusSquared)); + clean.whenComplete((result, failure) -> sendCleanRadiusResult(world, + context, + radius, + result, + failure)); + } + + @Nonnull + private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store store, + @Nonnull Store physicsStore, + @Nonnull SelectedBodies selectedBodies, + @Nonnull Vector3d center, + double radiusSquared) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); ComponentType generatedProxyType = @@ -269,18 +291,52 @@ private void cleanWithinRadius(@Nonnull CommandContext context, int removedBodies = 0; for (UUID bodyUuid : selectedBodies.bodyUuids()) { - resource.destroyBody(bodyUuid); + PhysicsBodies.destroy(physicsStore, bodyUuid); removedBodies++; } + return new RadiusCleanResult(removedEntities, removedBodies); + } + + private static void sendCleanRadiusResult(@Nonnull World world, + @Nonnull CommandContext context, + float radius, + @Nullable RadiusCleanResult result, + @Nullable Throwable failure) { + Runnable sender = () -> { + if (failure != null) { + Throwable cause = unwrap(failure); + String message = cause.getMessage() != null ? cause.getMessage() : cause.toString(); + context.sendMessage(Message.raw("Failed to clean Impulse physics bodies within radius: " + + message)); + return; + } + if (result == null) { + context.sendMessage(Message.raw("Failed to clean Impulse physics bodies within radius.")); + return; + } + sendCleanRadiusSuccess(context, result, radius, world.getName()); + }; + if (world.isInThread()) { + sender.run(); + return; + } + world.execute(sender); + } + + private static void sendCleanRadiusSuccess(@Nonnull CommandContext context, + @Nonnull RadiusCleanResult result, + float radius, + @Nonnull String worldName) { + AtomicIntegerArray removedEntities = result.removedEntities(); context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + " Impulse-owned attachment entities, " + removedEntities.get(DETACHED_EXTERNAL_ATTACHMENTS) + " detached external attachments, " - + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) - + " orphan visual proxy entities, " + removedBodies - + " runtime bodies, and " + removedEntities.get(REMOVED_SESSIONS) - + " control sessions within radius " + radius + " in world " + world.getName() + + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) + " orphan visual proxy entities, " + + result.removedBodies() + " runtime bodies, and " + + removedEntities.get(REMOVED_SESSIONS) + + " control sessions within radius " + radius + " in world " + worldName + ". Kept explicit physics spaces and world-collision cache.")); } @@ -295,18 +351,19 @@ private static Vector3d playerPosition(@Nonnull CommandContext context, } @Nonnull - private static SelectedBodies selectBodiesNear(@Nonnull PhysicsWorldRuntimeResource resource, + private static SelectedBodies selectBodiesNear(@Nonnull Store store, @Nonnull Vector3d center, float radius) { Set bodyUuids = new ObjectOpenHashSet<>(); - Vector3f centerF = new Vector3f((float) center.x, (float) center.y, (float) center.z); - for (SpaceId spaceId : resource.getSpaceIds()) { - resource.forEachIndexedBodySnapshotNear(spaceId, - centerF, - radius, - (bodyUuid, snapshot, bodySpaceId, kind, persistenceMode) -> { - bodyUuids.add(bodyUuid); - }); + double radiusSquared = (double) radius * radius; + for (PhysicsBodySnapshot snapshot : PhysicsBodies.snapshotFrame(store).bodies()) { + Vector3f position = snapshot.position(); + double dx = position.x - center.x; + double dy = position.y - center.y; + double dz = position.z - center.z; + if (dx * dx + dy * dy + dz * dz <= radiusSquared) { + bodyUuids.add(snapshot.bodyUuid()); + } } return new SelectedBodies(bodyUuids); } @@ -380,6 +437,10 @@ private static boolean containsBody(@Nonnull Set bodyUuids, private record SelectedBodies(@Nonnull Set bodyUuids) { } + private record RadiusCleanResult(@Nonnull AtomicIntegerArray removedEntities, + int removedBodies) { + } + @Nullable private static UUID rowUuid(@Nullable Ref bodyRef) { if (bodyRef == null || !bodyRef.isValid()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 44b30b01..5e2aad80 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -26,6 +26,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayList; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nonnull; @@ -41,12 +42,13 @@ private PhysicsStoreTopologyMutations() { public static void destroyBody(@Nonnull Store store, @Nonnull UUID bodyUuid) { + UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); PhysicsThreading.requireBackendIdle(store, "destroy a PhysicsStore body entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); - Ref bodyRef = identity.getByUuid(bodyUuid); - List removals = collectRows(store, null, null, bodyUuid, bodyRef); + Ref bodyRef = identity.getByUuid(checkedBodyUuid); + List removals = collectRows(store, null, null, checkedBodyUuid, bodyRef); removeRuntimeRows(runtime, identity, removals); removeRows(store, removals); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index baf08610..22d1b0d6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1485,14 +1485,15 @@ private void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { @Override public void destroyBody(@Nonnull UUID bodyUuid) { + UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { PhysicsStoreTopologyMutations.destroyBody( authoritativePhysicsStore("destroy physics body"), - bodyUuid); + checkedBodyUuid); return; } requireLegacyMutationAllowed("destroy physics body"); - destroyBody(bodyUuid, true); + destroyBody(checkedBodyUuid, true); } @Nonnull @@ -1500,9 +1501,15 @@ public void destroyBody(@Nonnull UUID bodyUuid) { public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid) { UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { - return enqueueAuthoritativePhysicsStoreMutation("destroy physics body", + World world = requireAuthoritativeWorld("destroy physics body"); + return PhysicsMutationHandle.fromCompletion("destroy physics body", checkedBodyUuid, - store -> PhysicsStoreTopologyMutations.destroyBody(store, checkedBodyUuid)); + PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + "destroy physics body", + store -> { + PhysicsStoreTopologyMutations.destroyBody(store, checkedBodyUuid); + return null; + })); } requireLegacyMutationAllowed("destroy physics body"); return enqueueDirectRuntimeMutation("destroy physics body", diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java index a5a09c17..16cac486 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java @@ -2,7 +2,9 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -13,11 +15,12 @@ import java.util.Collection; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * Public copied body reads for PhysicsStore entities. + * Public body helpers for PhysicsStore entities. */ public final class PhysicsBodies { @@ -114,6 +117,52 @@ public static int snapshotCount(@Nonnull Store store) { return snapshotFrame(store).bodies().size(); } + public static void destroy(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + Store checkedStore = requireWorldThread(store, + "destroy a PhysicsStore body entity"); + PhysicsStoreTopologyMutations.destroyBody(checkedStore, + Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + public static void destroy(@Nonnull Store store, + @Nonnull Ref bodyRef) { + Store checkedStore = requireWorldThread(store, + "destroy a PhysicsStore body entity"); + Ref checkedRef = requireSameValidStore(checkedStore, + bodyRef, + "bodyRef"); + destroy(checkedStore, PhysicsEntityRefs.entityUuid(checkedRef)); + } + + @Nonnull + public static CompletionStage destroyAsync(@Nonnull World world, + @Nonnull UUID bodyUuid) { + UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + "destroy a PhysicsStore body entity", + store -> { + destroy(store, checkedBodyUuid); + return checkedBodyUuid; + }); + } + + @Nonnull + public static CompletionStage destroyAsync(@Nonnull World world, + @Nonnull Ref bodyRef) { + Ref checkedRef = Objects.requireNonNull(bodyRef, "bodyRef"); + return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + "destroy a PhysicsStore body entity", + store -> { + Ref sameStoreRef = requireSameValidStore(store, + checkedRef, + "bodyRef"); + UUID bodyUuid = PhysicsEntityRefs.entityUuid(sameStoreRef); + destroy(store, bodyUuid); + return bodyUuid; + }); + } + @Nonnull private static Store requireWorldThread(@Nonnull Store store, @Nonnull String operation) { @@ -122,6 +171,18 @@ private static Store requireWorldThread(@Nonnull Store requireSameValidStore(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull String name) { + Ref checkedRef = Objects.requireNonNull(ref, name); + if (sameValidStore(store, checkedRef)) { + return checkedRef; + } + throw new IllegalArgumentException("PhysicsStore body ref is not valid for this store: " + + name); + } + private static boolean sameValidStore(@Nonnull Store store, @Nonnull Ref ref) { return ref.getStore() == store && ref.isValid(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 6c01ca2e..973c562a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -281,10 +281,16 @@ public abstract PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull Sp /** * Destroys a registered body by durable body UUID. * - *

    Prefer this overload when the caller is crossing a durable identity boundary.

    + *

    Prefer {@code PhysicsBodies.destroy(...)} for PhysicsStore-aware code. Keep this + * facade for compatibility callers crossing a durable identity boundary.

    */ public abstract void destroyBody(@Nonnull UUID bodyUuid); + /** + * Queues body destruction by durable body UUID. + * + *

    Prefer {@code PhysicsBodies.destroyAsync(...)} for PhysicsStore-aware code.

    + */ @Nonnull public abstract PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid); From e2bc4883e4e48309615ffeab47d3686118cf16fd Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 12:34:24 +0200 Subject: [PATCH 328/534] refactor(core): route control state through physics store Signed-off-by: Blovien --- .../modules/control/ControlLifecycle.java | 4 +- .../control/PhysicsControlRuntimeStates.java | 89 +++++++++++++++++++ .../systems/PhysicsControlSessionCleanup.java | 13 +-- .../PhysicsStoreTopologyMutations.java | 3 + .../control/PhysicsControlSessions.java | 29 +++--- .../plugin/physicsstore/PhysicsEntities.java | 12 +++ 6 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java index ea4de9db..dcdeadd2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java @@ -37,6 +37,7 @@ public final class ControlLifecycle { static { GATE.onDisable(ControlLifecycle::cleanupStores); + GATE.onDisable(PhysicsControlRuntimeStates::clearAll); GATE.onDisable(ControlLifecycle::cleanupResources); } @@ -147,7 +148,6 @@ private static void cleanupStoreOnWorldThread(@Nonnull Store store, @Nullable ComponentType controllableType, @Nullable ComponentType sessionType) { if (sessionType != null) { - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); ArrayList sessions = new ArrayList<>(); store.forEachEntityParallel(sessionType, (index, archetypeChunk, _) -> { @@ -163,7 +163,7 @@ private static void cleanupStoreOnWorldThread(@Nonnull Store store, } }); for (SessionCleanupTarget target : sessions) { - PhysicsControlSessionCleanup.cleanup(store, resource, target.session()); + PhysicsControlSessionCleanup.cleanup(store, target.session()); store.removeComponent(target.ref(), sessionType); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java new file mode 100644 index 00000000..fa7979c3 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java @@ -0,0 +1,89 @@ +package dev.hytalemodding.impulse.core.internal.modules.control; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * PhysicsStore-keyed runtime-only state for bodies currently driven by control sessions. + */ +public final class PhysicsControlRuntimeStates { + + @Nonnull + private static final Map, PhysicsControlRuntimeState> STATES_BY_STORE = + Collections.synchronizedMap(new WeakHashMap<>()); + + private PhysicsControlRuntimeStates() { + } + + public static void markControlled(@Nonnull Ref bodyRef) { + Store store = requireStore(bodyRef, "mark a PhysicsStore body controlled"); + stateFor(store).markBodyControlled(bodyRef); + } + + public static void clearControlled(@Nullable Ref bodyRef) { + Store store = storeOrNull(bodyRef); + if (store == null || bodyRef == null) { + return; + } + PhysicsThreading.requireWorldThread(store, "clear a controlled PhysicsStore body"); + stateFor(store).clearControlledBody(bodyRef); + } + + public static boolean isControlled(@Nullable Ref bodyRef) { + Store store = storeOrNull(bodyRef); + if (store == null || bodyRef == null) { + return false; + } + PhysicsThreading.requireWorldThread(store, "check a controlled PhysicsStore body"); + return stateFor(store).isBodyControlled(bodyRef); + } + + public static void clear(@Nonnull Store store) { + Store checkedStore = Objects.requireNonNull(store, "store"); + synchronized (STATES_BY_STORE) { + PhysicsControlRuntimeState state = STATES_BY_STORE.get(checkedStore); + if (state != null) { + state.clear(); + } + } + } + + public static void clearAll() { + synchronized (STATES_BY_STORE) { + for (PhysicsControlRuntimeState state : STATES_BY_STORE.values()) { + state.clear(); + } + } + } + + @Nonnull + private static PhysicsControlRuntimeState stateFor(@Nonnull Store store) { + synchronized (STATES_BY_STORE) { + return STATES_BY_STORE.computeIfAbsent(store, _ -> new PhysicsControlRuntimeState()); + } + } + + @Nonnull + private static Store requireStore(@Nonnull Ref ref, + @Nonnull String operation) { + Store store = Objects.requireNonNull(ref, "ref").getStore(); + if (store == null) { + throw new IllegalArgumentException("PhysicsStore ref has no owning store"); + } + PhysicsThreading.requireWorldThread(store, operation); + return store; + } + + @Nullable + private static Store storeOrNull(@Nullable Ref ref) { + return ref != null ? ref.getStore() : null; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java index f11c2cbc..790709c5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java @@ -4,8 +4,8 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import javax.annotation.Nonnull; public final class PhysicsControlSessionCleanup { @@ -15,17 +15,10 @@ private PhysicsControlSessionCleanup() { public static void cleanup(@Nonnull Store store, @Nonnull PhysicsControlSessionComponent session) { - cleanupInternal(store, PhysicsWorldRuntimeResource.require(store), session); - } - - public static void cleanup(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull PhysicsControlSessionComponent session) { - cleanupInternal(store, resource, session); + cleanupInternal(store, session); } private static void cleanupInternal(@Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull PhysicsControlSessionComponent session) { PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyRef()); if (!session.isActive()) { @@ -34,7 +27,7 @@ private static void cleanupInternal(@Nonnull Store store, Ref bodyRef = session.getBodyRef(); if (bodyRef != null) { - resource.clearControlledBody(bodyRef); + PhysicsControlRuntimeStates.clearControlled(bodyRef); } PhysicsStoreControlSessionMutations.applyRelease(store, session); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 5e2aad80..faf153e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; +import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; @@ -60,6 +61,7 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); + PhysicsControlRuntimeStates.clear(store); TopologyCounts removed = countBackendTopology(runtime); List removals = collectRows(store, null, null, null, null); removeRuntimeRows(runtime, identity, removals); @@ -344,6 +346,7 @@ private static void removeRows(@Nonnull Store store, terrainPayloads.remove(removal.payloadResourceKey()); } if (removal.kind() == RowKind.BODY) { + PhysicsControlRuntimeStates.clearControlled(removal.ref()); snapshots.removeBody(removal.rowUuid()); registrations.removeBody(removal.rowUuid()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 8b5c2f1a..12a12b26 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -6,14 +6,14 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -100,10 +100,9 @@ public static void startSession(@Nonnull Store store, requireAvailable(); ControlLifecycle.registerStore(store); validateControlRefs(bodyRef, anchorBodyRef, controlJointRef); - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); ComponentType sessionType = PhysicsControlSessionComponent.getComponentType(); - releaseSession(resource, store, controllerRef, sessionType); + releaseSession(store, controllerRef, sessionType); store.putComponent(controllerRef, sessionType, new PhysicsControlSessionComponent(bodyRef, @@ -114,7 +113,7 @@ public static void startSession(@Nonnull Store store, grabDistance, viewOffset, previousTarget)); - resource.markBodyControlled(bodyRef); + PhysicsControlRuntimeStates.markControlled(bodyRef); } /** @@ -127,14 +126,12 @@ public static boolean releaseSession(@Nonnull Store store, if (!isAvailable()) { return false; } - return releaseSession(PhysicsWorldRuntimeResource.require(store), - store, + return releaseSession(store, controllerRef, PhysicsControlSessionComponent.getComponentType()); } - private static boolean releaseSession(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull Store store, + private static boolean releaseSession(@Nonnull Store store, @Nonnull Ref controllerRef, @Nonnull ComponentType sessionType) { PhysicsControlSessionComponent session = @@ -143,19 +140,18 @@ private static boolean releaseSession(@Nonnull PhysicsWorldRuntimeResource resou return false; } - releaseSession(resource, store, controllerRef, sessionType, session); + releaseSession(store, controllerRef, sessionType, session); return true; } - private static void releaseSession(@Nonnull PhysicsWorldRuntimeResource resource, - @Nonnull Store store, + private static void releaseSession(@Nonnull Store store, @Nonnull Ref controllerRef, @Nonnull ComponentType sessionType, @Nonnull PhysicsControlSessionComponent session) { Ref bodyRef = session.getBodyRef(); PhysicsKinematicControlSystem.clearMutationState(store, session.getAnchorBodyRef()); if (bodyRef != null) { - resource.clearControlledBody(bodyRef); + PhysicsControlRuntimeStates.clearControlled(bodyRef); } PhysicsStoreControlSessionMutations.applyRelease(store, session); @@ -173,9 +169,8 @@ private static Store physicsStore(@Nonnull Store stor private static Ref requireRef(@Nonnull Store store, @Nonnull UUID uuid, @Nonnull String role) { - Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(uuid); - if (ref == null || !ref.isValid()) { + Ref ref = PhysicsEntities.resolveRef(store, uuid); + if (ref == null) { throw new IllegalArgumentException("PhysicsStore " + role + " entity is not loaded for uuid=" + uuid); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index cf415bc5..055a7bc5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -49,6 +50,17 @@ public static void addUuid(@Nonnull Holder holder, new UuidComponent(Objects.requireNonNull(entityUuid, "entityUuid"))); } + @Nullable + public static Ref resolveRef(@Nonnull Store store, + @Nonnull UUID entityUuid) { + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsThreading.requireWorldThread(checkedStore, "resolve a PhysicsStore entity ref"); + Ref ref = checkedStore + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(Objects.requireNonNull(entityUuid, "entityUuid")); + return ref != null && ref.getStore() == checkedStore && ref.isValid() ? ref : null; + } + @Nonnull public static Holder spaceHolder(@Nonnull Store store, @Nonnull UUID spaceUuid, From 34df97163b533f00301179ed56d06c918f310687 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 12:35:15 +0200 Subject: [PATCH 329/534] refactor(physicsentity): move attachment api into module Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 2 +- .../crucible/ImpulseLiveCrucibleTests.java | 6 ++--- ...pulseRapierBodyBenchmarkCrucibleTests.java | 2 +- .../diagnostics/PhysicsEntityDiagnostics.java | 4 ++-- .../systems/debug/PhysicsDebugRenderer.java | 2 +- .../systems/debug/PhysicsDebugSystem.java | 4 ++-- .../PhysicsBodyAttachmentIndexSystem.java | 4 ++-- .../systems/sync/PhysicsSyncSystem.java | 4 ++-- .../sync/PhysicsTransformAuthority.java | 4 ++-- .../visual/GeneratedProxyLifecycle.java | 4 ++-- .../PhysicsGeneratedProxyCleanupSystem.java | 4 ++-- .../PhysicsEntityAttachments.java} | 6 ++--- .../physicsentity/PhysicsEntityTypes.java | 2 +- .../components}/BodyAttachmentComponent.java | 2 +- .../examples/commands/GrabCommand.java | 10 ++++---- .../examples/commands/JointsCommand.java | 10 ++++---- .../commands/PhysicsStoreExampleCommands.java | 2 +- .../commands/stress/StressJointsCommand.java | 2 +- ...nchmarkEntityRemovalDiagnosticsSystem.java | 2 +- .../systems/ExplosiveFuseContactSystem.java | 6 ++--- .../systems/ExplosiveFuseTickSystem.java | 2 +- .../examples/utils/ExamplePhysicsUtils.java | 24 ++++++------------- 22 files changed, 49 insertions(+), 59 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{projection/PhysicsAttachments.java => modules/physicsentity/PhysicsEntityAttachments.java} (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{projection => modules/physicsentity/components}/BodyAttachmentComponent.java (99%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index fbc22735..44741a49 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -25,7 +25,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index d33ed143..e9535798 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -22,9 +22,9 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index b669e0f7..70aafc0c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java index 97402278..bf0fd26d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java @@ -7,8 +7,8 @@ import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems.Visible; import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerArray; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java index e5072913..47e44ee0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import java.util.Collection; import javax.annotation.Nonnull; import org.joml.Matrix4d; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index da0fd259..f93e7353 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -19,8 +19,8 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index c5c6d29c..54bc21a6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -9,8 +9,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index a13aa5e0..f9f23ab7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -28,8 +28,8 @@ import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java index 15a1c43f..f96bdf80 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.internal.systems.sync; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; import javax.annotation.Nonnull; final class PhysicsTransformAuthority { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index 775efcaa..cc0bdc96 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -10,8 +10,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java index 4e8bdee1..af8fbdd5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java @@ -10,8 +10,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import java.util.Collections; import java.util.Map; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/PhysicsAttachments.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/PhysicsAttachments.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java index bcd052ad..1623c6cb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/PhysicsAttachments.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.projection; +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -14,9 +14,9 @@ /** * Public EntityStore projection reads for PhysicsStore body attachments. */ -public final class PhysicsAttachments { +public final class PhysicsEntityAttachments { - private PhysicsAttachments() { + private PhysicsEntityAttachments() { } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index cd582346..d988a826 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java index e78123a5..66e281e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/projection/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.projection; +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 452377f0..9dce2ba9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; @@ -33,7 +33,7 @@ import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.projection.PhysicsAttachments; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -191,13 +191,13 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, if (!selectedBodyRef.isValid()) { return null; } - ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(selectedBodyRef.getStore(), + ExamplePhysicsUtils.appendBodyCommand(selectedBodyRef.getStore(), selectedBodyRef, BodyCommandComponent.wake()); try { Ref anchorBodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, anchorBodyEntity(spaceRef, anchorBodyUuid, hitPoint)); - Ref controlJointRef = ExamplePhysicsUtils.addPhysicsStoreJoint(world, + Ref controlJointRef = ExamplePhysicsUtils.addJoint(world, controlJointUuid, controlJoint(spaceRef, anchorBodyRef, selectedBodyRef, bodyLocalHit)); return new GrabPhysicsState(selectedState.bodyType(), @@ -296,7 +296,7 @@ private static AttachmentSelection inspectGameplayAttachments(@Nonnull Store controllableType, @Nonnull Ref bodyRef) { boolean hasGameplayAttachment = false; - for (Ref attachmentRef : PhysicsAttachments.attachments(store, bodyRef)) { + for (Ref attachmentRef : PhysicsEntityAttachments.attachments(store, bodyRef)) { BodyAttachmentComponent attachment = store.getComponent(attachmentRef, ATTACHMENT_TYPE); if (attachment == null || attachment.getLifecycle() == BodyAttachmentComponent.AttachmentLifecycle.GENERATED_PROXY) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index b535d159..a8e163f5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -111,7 +111,7 @@ private static void createFixed(@Nonnull List createdBodies, CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); CreatedBlockBody child = spawnBox(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, + ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint(spaceRef, anchor, @@ -135,7 +135,7 @@ private static void createPoint(@Nonnull List createdBodies, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f, new Vector3f(1.5f, 0.0f, 0.0f)); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, + ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint(spaceRef, anchor, @@ -166,7 +166,7 @@ private static void createHinge(@Nonnull List createdBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.5f); joint.setMotorMaxForce(3.0f); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); + ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint); } private static void createSlider(@Nonnull List createdBodies, @@ -189,7 +189,7 @@ private static void createSlider(@Nonnull List createdBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.0f); joint.setMotorMaxForce(4.0f); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); + ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint); } private static void createSpring(@Nonnull List createdBodies, @@ -215,7 +215,7 @@ private static void createSpring(@Nonnull List createdBodies, joint.setSpringRestLength(SPRING_REST_LENGTH); joint.setSpringStiffness(20.0f); joint.setSpringDamping(2.0f); - ExamplePhysicsUtils.addPhysicsStoreJoint(world, UUID.randomUUID(), joint); + ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint); } private static CreatedBlockBody spawnBox(@Nonnull List createdBodies, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index bb1ed60c..07d9b774 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -124,7 +124,7 @@ private void applyImpulse(@Nonnull CommandContext ctx, .getDirection()).mul(strength); Ref bodyRef = hit.bodyRef(); Store physicsStore = bodyRef.getStore(); - ExamplePhysicsUtils.appendPhysicsStoreBodyCommand(physicsStore, + ExamplePhysicsUtils.appendBodyCommand(physicsStore, bodyRef, BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, (float) impulse.x, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index f5a7296b..571d7d32 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -177,7 +177,7 @@ private static int appendRow(@Nonnull List createdBodies, createdBodies.add(created); } for (int i = 0; i < jointCount; i++) { - ExamplePhysicsUtils.addPhysicsStoreJoint(world, + ExamplePhysicsUtils.addJoint(world, new UUID(jointUuidRunId, i + 1L), joint(spaceRef, bodies[i], bodies[i + 1], jointType)); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java index 4d1ddb07..382a3e19 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index bf3d0910..d8f61868 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -10,8 +10,8 @@ import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.PhysicsAttachments; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; @@ -77,7 +77,7 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer ref : PhysicsAttachments.attachments(store, explosiveBodyUuid)) { + for (Ref ref : PhysicsEntityAttachments.attachments(store, explosiveBodyUuid)) { BodyAttachmentComponent attachment = commandBuffer.getComponent(ref, ATTACHMENT_TYPE); ExplosiveBlockComponent explosive = commandBuffer.getComponent(ref, EXPLOSIVE_TYPE); ExplosiveFuseComponent fuse = commandBuffer.getComponent(ref, FUSE_TYPE); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 342b6cd8..f50b39ef 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 0284fb3d..3b81b2f9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -27,7 +27,7 @@ import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -57,7 +57,7 @@ private ExamplePhysicsUtils() { @Nullable public static Ref resolveSpaceRef(@Nonnull World world, @Nonnull SpaceId spaceId) { - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + Store store = ((PhysicsStoreWorld) world) .getPhysicsStore() .getStore(); return PhysicsSpaces.resolveRef(store, spaceId); @@ -66,7 +66,7 @@ public static Ref resolveSpaceRef(@Nonnull World world, @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyEntityDescriptor descriptor) { - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + Store store = ((PhysicsStoreWorld) world) .getPhysicsStore() .getStore(); return addPhysicsStoreBody(store, descriptor); @@ -80,7 +80,7 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, .getPhysicsStore() .getStore(); Ref bodyRef = addPhysicsStoreBody(store, descriptor); - appendPhysicsStoreBodyCommand(store, bodyRef, command); + appendBodyCommand(store, bodyRef, command); return bodyRef; } @@ -113,7 +113,6 @@ public static void addPhysicsStoreBodies(@Nonnull World world, @Nonnull private static Ref addPhysicsStoreBody(@Nonnull Store store, @Nonnull BodyEntityDescriptor descriptor) { - Objects.requireNonNull(descriptor, "descriptor"); return addPhysicsStoreBody(store, descriptor, descriptor.dynamics(), @@ -135,7 +134,6 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store addPhysicsStoreBodyUnchecked(@Nonnull Store addPhysicsStoreJoint(@Nonnull World world, + public static Ref addJoint(@Nonnull World world, @Nonnull UUID jointUuid, @Nonnull JointComponent joint) { - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) + Store store = ((PhysicsStoreWorld) world) .getPhysicsStore() .getStore(); PhysicsThreading.requireWorldThread(store, "add a PhysicsStore joint entity"); @@ -160,12 +158,9 @@ public static Ref addPhysicsStoreJoint(@Nonnull World world, joint), AddReason.SPAWN); } - public static void appendPhysicsStoreBodyCommand(@Nonnull Store store, + public static void appendBodyCommand(@Nonnull Store store, @Nonnull Ref bodyRef, @Nonnull BodyCommandComponent command) { - Objects.requireNonNull(store, "store"); - Objects.requireNonNull(bodyRef, "bodyRef"); - Objects.requireNonNull(command, "command"); PhysicsThreading.requireWorldThread(store, "append a PhysicsStore body command"); BodyCommandComponent existing = store.getComponent(bodyRef, BodyCommandComponent.getComponentType()); @@ -264,11 +259,6 @@ private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store spaceRef; From abd271a75448758ecde29428f1bfbb04f68b8bed Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 12:35:57 +0200 Subject: [PATCH 330/534] refactor(core): simplify physics store helper surfaces Signed-off-by: Blovien --- .../PhysicsStoreSpaceMutations.java | 26 +++---------------- .../plugin/components/SpaceComponent.java | 2 +- .../PhysicsRuntimeProfiling.java | 1 + .../universe/world/storage/PhysicsStore.java | 4 ++- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 997d2312..8d4149bf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -42,11 +42,7 @@ public static Ref addSpace(@Nonnull Store store, @Nonnull SpaceId compatibilitySpaceId, @Nonnull BackendId backendId, @Nonnull PhysicsSpaceSettings settings) { - Objects.requireNonNull(store, "store"); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - Objects.requireNonNull(compatibilitySpaceId, "compatibilitySpaceId"); - Objects.requireNonNull(backendId, "backendId"); - Objects.requireNonNull(settings, "settings"); + PhysicsThreading.requireWorldThread(store, "add a PhysicsStore space entity"); if (backendId.value().isBlank()) { throw new IllegalArgumentException("PhysicsStore space backend id is blank: " @@ -74,6 +70,7 @@ public static Ref addSpace(@Nonnull Store store, new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), new ExtensionSettingsComponent(settings.getExtensionSettings())), AddReason.SPAWN); + assert ref != null; identity.putUuid(spaceUuid, ref); compatibility.putSpace(compatibilitySpaceId, spaceUuid); SpaceId.reserveAtLeast(compatibilitySpaceId.value()); @@ -87,14 +84,7 @@ public static void putSpaceSettings(@Nonnull Store store, @Nonnull PhysicsSpaceSettings settings) { UUID spaceUuid = requireSpaceUuid(store, spaceId); Ref ref = requireSpaceRef(store, spaceUuid); - putSpaceSettings(store, ref, spaceUuid, settings); - } - - public static void putSpaceSettings(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull PhysicsSpaceSettings settings) { - UUID spaceUuid = requireSpaceUuid(store, ref); - putSpaceSettings(store, ref, spaceUuid, settings); + putSpaceSettings(store, ref, settings); } public static void putSpaceGravity(@Nonnull Store store, @@ -109,10 +99,6 @@ public static void putSpaceGravity(@Nonnull Store store, @Nonnull Ref ref, @Nonnull UUID spaceUuid, @Nonnull Vector3f gravity) { - Objects.requireNonNull(store, "store"); - Objects.requireNonNull(ref, "ref"); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - Objects.requireNonNull(gravity, "gravity"); PhysicsThreading.requireWorldThread(store, "update PhysicsStore space gravity"); SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); if (space == null) { @@ -128,12 +114,8 @@ public static void putSpaceGravity(@Nonnull Store store, public static void putSpaceSettings(@Nonnull Store store, @Nonnull Ref ref, - @Nonnull UUID spaceUuid, @Nonnull PhysicsSpaceSettings settings) { - Objects.requireNonNull(store, "store"); - Objects.requireNonNull(ref, "ref"); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - Objects.requireNonNull(settings, "settings"); + requireSpaceUuid(store, ref); PhysicsThreading.requireWorldThread(store, "update a PhysicsStore space entity"); store.putComponent(ref, WorldCollisionComponent.getComponentType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java index 3f649c3b..071a4d2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SpaceComponent.java @@ -17,7 +17,7 @@ */ public final class SpaceComponent implements Component { - private static final Vector3f DEFAULT_GRAVITY = new Vector3f(0.0f, -9.81f, 0.0f); + public static final Vector3f DEFAULT_GRAVITY = new Vector3f(0.0f, -9.81f, 0.0f); @Nonnull public static final BuilderCodec CODEC = BuilderCodec.builder( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java index b82f387e..37388e32 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java @@ -16,6 +16,7 @@ private PhysicsRuntimeProfiling() { @Nonnull public static Snapshots snapshots(@Nonnull Store store) { + assert PhysicsRuntimeProfilingResource.getResourceType() != null; PhysicsRuntimeProfilingResource profiling = store.getResource( PhysicsRuntimeProfilingResource.getResourceType()); return new Snapshots(profiling.isEnabled(), diff --git a/impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java b/impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java index aa87f6ac..1a5cfc03 100644 --- a/impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java +++ b/impulse-early-plugin/src/main/java/com/hypixel/hytale/server/core/universe/world/storage/PhysicsStore.java @@ -8,6 +8,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldProvider; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -15,7 +16,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -public final class PhysicsStore { +public final class PhysicsStore implements WorldProvider { @Nonnull public static final ComponentRegistry REGISTRY = new ComponentRegistry<>(); @@ -63,6 +64,7 @@ public Store getStore() { } @Nonnull + @Override public World getWorld() { return world; } From 7ac49b4cd785f6e651f4a4bdf597be1b0b0fa274 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 12:45:36 +0200 Subject: [PATCH 331/534] refactor(physicsentity): own generated proxy component type Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 4 ++-- .../PhysicsGeneratedProxyCleanupSystem.java | 2 +- .../modules/physicsentity/PhysicsEntityTypes.java | 15 ++++++++++++--- .../components/GeneratedVisualProxyComponent.java | 13 +++---------- 4 files changed, 18 insertions(+), 16 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{internal => plugin/modules/physicsentity}/components/GeneratedVisualProxyComponent.java (69%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 44741a49..ecb4d8cc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -16,16 +16,16 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java index af8fbdd5..31faf888 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java @@ -8,10 +8,10 @@ import com.hypixel.hytale.component.dependency.SystemGroupDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import java.util.Collections; import java.util.Map; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index d988a826..df4d8f9b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.component.SystemGroup; import com.hypixel.hytale.component.event.WorldEventType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; @@ -19,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -31,6 +31,9 @@ public final class PhysicsEntityTypes { @Nullable private static ComponentType bodyAttachmentComponentType; @Nullable + private static ComponentType + generatedVisualProxyComponentType; + @Nullable private static ResourceType physicsWorldResourceType; @Nullable private static WorldEventType @@ -46,10 +49,10 @@ public static void registerComponentTypes(@Nonnull ComponentRegistryProxy registry) { @@ -88,6 +91,12 @@ public static ComponentType bodyAttachment return bodyAttachmentComponentType; } + @Nonnull + public static ComponentType + generatedVisualProxyComponentType() { + return generatedVisualProxyComponentType; + } + @Nonnull public static ResourceType physicsWorldResourceType() { return physicsWorldResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java similarity index 69% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java index 1f274dc0..f90189d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/components/GeneratedVisualProxyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java @@ -1,19 +1,17 @@ -package dev.hytalemodding.impulse.core.internal.components; +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** * Durable ownership marker for Impulse-generated visual proxy entities. */ public final class GeneratedVisualProxyComponent implements Component { - @Nullable - private static ComponentType componentType; @Nonnull public static final BuilderCodec CODEC = BuilderCodec.builder( GeneratedVisualProxyComponent.class, @@ -21,12 +19,7 @@ public final class GeneratedVisualProxyComponent implements Component getComponentType() { - return componentType; - } - - public static void setComponentType( - @Nonnull ComponentType type) { - componentType = type; + return PhysicsEntityTypes.generatedVisualProxyComponentType(); } @Nonnull From 076e6ad5d886d99ffa6d802dc7b4e45f8a1cf587 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 12:48:03 +0200 Subject: [PATCH 332/534] refactor(core): centralize physics store world access Signed-off-by: Blovien --- .../control/PhysicsControlSessions.java | 4 +- .../PhysicsWorldCollisionProfiling.java | 8 +--- .../persistence/PhysicsPersistence.java | 6 +-- .../plugin/physicsstore/PhysicsThreading.java | 12 +++++- .../examples/commands/GrabCommand.java | 6 +-- .../commands/PhysicsStoreExampleCommands.java | 4 +- .../commands/WorldCollisionCommand.java | 4 +- .../commands/stress/StressBodiesCommand.java | 6 +-- .../explosive/ExplosiveBlockRuntime.java | 4 +- .../systems/ExplosiveFuseContactSystem.java | 4 +- .../systems/ExplosiveFuseTickSystem.java | 8 ++-- .../examples/utils/ExamplePhysicsUtils.java | 41 ++++++------------- 12 files changed, 46 insertions(+), 61 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 12a12b26..61a5d5d2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; @@ -161,8 +160,7 @@ private static void releaseSession(@Nonnull Store store, @Nonnull private static Store physicsStore(@Nonnull Store store) { - return ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() - .getStore(); + return PhysicsThreading.store(store.getExternalData().getWorld()); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java index 3fe95cf0..510f886a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java @@ -7,7 +7,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Locale; import java.util.UUID; @@ -97,11 +97,7 @@ private static WorldCollisionProfilingResource worldCollisionProfiling( @Nullable private static Store physicsStoreOrNull(@Nonnull World world) { - if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { - return null; - } - Store store = physicsStoreWorld.getPhysicsStore().getStore(); - return store.isShutdown() ? null : store; + return PhysicsThreading.storeOrNull(world); } public record Snapshots(@Nonnull SnapshotView cumulative, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 6268fb26..6a9484ea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -3,13 +3,12 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.concurrent.CompletionStage; @@ -76,8 +75,7 @@ public static CompletionStage statusAsync(@Nonnull Store st @Nonnull private static Store physicsStore(@Nonnull Store store) { - return ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() - .getStore(); + return PhysicsThreading.store(store.getExternalData().getWorld()); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java index c61b58cb..f46b5049 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java @@ -13,6 +13,7 @@ import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Thread assertions for direct PhysicsStore entity and backend access. @@ -196,8 +197,17 @@ private static void enqueueRead(@Nonnull World world, } @Nonnull - private static Store store(@Nonnull World world) { + public static Store store(@Nonnull World world) { return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() .getStore(); } + + @Nullable + public static Store storeOrNull(@Nonnull World world) { + if (!(Objects.requireNonNull(world, "world") instanceof PhysicsStoreWorld physicsStoreWorld)) { + return null; + } + Store store = physicsStoreWorld.getPhysicsStore().getStore(); + return store.isShutdown() ? null : store; + } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 9dce2ba9..8a8b95a5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -14,7 +14,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.TargetUtil; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; @@ -92,7 +92,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); Transform look = TargetUtil.getLook(ref, store); Vector3d start = new Vector3d(look.getPosition()); @@ -287,7 +287,7 @@ private static HitSelection selectControllableHit(@Nonnull Store p @Nullable private static PhysicsBodySnapshot bodyState(@Nonnull World world, @Nonnull Ref bodyRef) { - Store store = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store store = PhysicsThreading.store(world); return PhysicsBodies.snapshot(store, bodyRef); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 07d9b774..a4de6639 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -33,7 +33,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockPolicy; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -334,7 +334,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Store physicsStore = - ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + PhysicsThreading.store(world); boolean contactEventsEnabled = contactEventsEnabled(physicsStore); Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java index 60be54b5..bfea232a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -40,7 +40,7 @@ public WorldCollisionCommand() { @Nonnull private static Store physicsStore(@Nonnull World world) { - return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + return PhysicsThreading.store(world); } private static final class BuildCommand extends AbstractAsyncPlayerCommand { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index b8164b3f..51ad6246 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -32,7 +32,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.Iterator; @@ -156,7 +156,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound.")); return CompletableFuture.completedFuture(null); } - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); PhysicsSpaceSettings settings = configureStressRuntime(physicsStore, spaceRef, mode, @@ -346,7 +346,7 @@ private static int prewarmStressWorldCollision(@Nonnull World world, } WorldCollisionPrewarmStats stats = PhysicsWorldCollision.ensureAround(world, - ((PhysicsStoreWorld) world).getPhysicsStore().getStore(), + PhysicsThreading.store(world), spaceId, layout.positions(count), worldCollisionSettings.getWorldCollisionBodyRadius(), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index b5f612f0..c7d6a8de 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -25,7 +25,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; @@ -134,7 +134,7 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e } List groups = groupFragments(fragments, center, settings.getRadius()); - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); PhysicsWorldCollision.refreshAround(world, physicsStore, spaceId, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index d8f61868..dc9207dc 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -44,7 +44,7 @@ public void handle(@Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull PhysicsEventFramePublishedEvent event) { Store physicsStore = - ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore().getStore(); + PhysicsThreading.store(store.getExternalData().getWorld()); long tick = Math.max(0L, store.getExternalData().getWorld().getTick()); for (PhysicsFrameEvent frameEvent : event.frame().physicsEvents()) { if (frameEvent instanceof PhysicsContactEvent contact diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index f50b39ef..a52a4267 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -13,7 +13,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; @@ -116,8 +116,7 @@ private static Vector3d explosionCenter(@Nullable BodyMotionSnapshot snapshot, @Nullable private static SpaceId attachmentSpaceId(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { - Store physics = ((PhysicsStoreWorld) store.getExternalData().getWorld()) - .getPhysicsStore().getStore(); + Store physics = PhysicsThreading.store(store.getExternalData().getWorld()); Ref bodyRef = attachment.getBodyRef(); PhysicsBodyRegistrationView registration = bodyRef != null && bodyRef.isValid() ? PhysicsBodies.registrationView(physics, bodyRef) @@ -132,8 +131,7 @@ private static SpaceId attachmentSpaceId(@Nonnull Store store, private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { UUID bodyUuid = attachment.getBodyUuid(); - Store physics = ((PhysicsStoreWorld) store.getExternalData().getWorld()) - .getPhysicsStore().getStore(); + Store physics = PhysicsThreading.store(store.getExternalData().getWorld()); Ref bodyRef = attachment.getBodyRef(); PhysicsBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() ? PhysicsBodies.snapshot(physics, bodyRef) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 3b81b2f9..a4846f95 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -14,21 +14,20 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -57,18 +56,14 @@ private ExamplePhysicsUtils() { @Nullable public static Ref resolveSpaceRef(@Nonnull World world, @Nonnull SpaceId spaceId) { - Store store = ((PhysicsStoreWorld) world) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); return PhysicsSpaces.resolveRef(store, spaceId); } @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyEntityDescriptor descriptor) { - Store store = ((PhysicsStoreWorld) world) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); return addPhysicsStoreBody(store, descriptor); } @@ -76,9 +71,7 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyEntityDescriptor descriptor, @Nonnull BodyCommandComponent command) { - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); Ref bodyRef = addPhysicsStoreBody(store, descriptor); appendBodyCommand(store, bodyRef, command); return bodyRef; @@ -89,18 +82,14 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyEntityDescriptor descriptor, @Nonnull DynamicsComponent dynamics, @Nullable TargetComponent target) { - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); return addPhysicsStoreBody(store, descriptor, dynamics, target); } public static void addPhysicsStoreBodies(@Nonnull World world, @Nonnull Iterable descriptors) { Objects.requireNonNull(descriptors, "descriptors"); - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(store, "add PhysicsStore body entities"); for (BodyEntityDescriptor descriptor : descriptors) { addPhysicsStoreBodyUnchecked(store, @@ -149,9 +138,7 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store addJoint(@Nonnull World world, @Nonnull UUID jointUuid, @Nonnull JointComponent joint) { - Store store = ((PhysicsStoreWorld) world) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(store, "add a PhysicsStore joint entity"); return store.addEntity(PhysicsEntities.jointHolder(store, Objects.requireNonNull(jointUuid, "jointUuid"), @@ -172,9 +159,7 @@ public static void appendBodyCommand(@Nonnull Store store, public static SpaceId spaceId(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull OptionalArg spaceArg) { - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); if (spaceArg.provided(ctx)) { int rawSpaceId = spaceArg.get(ctx); if (rawSpaceId <= 0) { From 8427a74148cf58785d34dd2f916dffc2ba3931f8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 12:54:40 +0200 Subject: [PATCH 333/534] refactor(commands): centralize physics store access Signed-off-by: Blovien --- .../impulse/core/internal/commands/CleanCommand.java | 3 +-- .../impulse/core/internal/commands/SpaceCommand.java | 8 ++++---- .../impulse/core/internal/commands/SpaceSelection.java | 5 +---- .../commands/settings/EventCollectionSettingCommand.java | 4 ++-- .../commands/settings/MaxStepDtSettingCommand.java | 4 ++-- .../commands/settings/SimulationStepsSettingCommand.java | 4 ++-- .../internal/commands/settings/SolverSettingsCommand.java | 4 ++-- .../commands/settings/StepModeSettingCommand.java | 4 ++-- .../commands/settings/StepSchedulingSettingCommand.java | 4 ++-- .../settings/VisualMaterializationSettingsCommand.java | 4 ++-- .../commands/settings/VisualSyncSettingsCommand.java | 4 ++-- .../commands/CollisionLodSettingsCommand.java | 4 ++-- .../commands/WorldCollisionPerfReportCommand.java | 4 ++-- .../commands/WorldCollisionSettingsCommand.java | 4 ++-- 14 files changed, 28 insertions(+), 32 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index ecb4d8cc..823a7378 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -15,7 +15,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; @@ -205,7 +204,7 @@ private void cleanWithinRadius(@Nonnull CommandContext context, return; } - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); SelectedBodies selectedBodies = selectBodiesNear(physicsStore, center, radius); double radiusSquared = (double) radius * radius; CompletionStage clean = PhysicsThreading.callWhenBackendIdleOnWorldThread(world, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 69214eee..628db601 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -21,11 +21,11 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -79,7 +79,7 @@ protected void execute(@Nonnull CommandContext context, : PhysicsSpaceSettings.defaults(); settings.getWorldCollisionSettings().setWorldCollisionMode(worldCollisionMode); - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); try { Impulse.getRuntimeProvider(backendId); SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId, settings); @@ -105,7 +105,7 @@ private ListCommand() { @Override protected CompletableFuture executeAsync(@Nonnull CommandContext context, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); return PhysicsAsync.acceptOnWorldThread(world, PhysicsDiagnostics.spaceSummariesAsync(world), summaries -> sendSpaces(context, world, physicsStore, summaries)); @@ -169,7 +169,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, return CompletableFuture.completedFuture(null); } - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(context, world, spaceArg); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java index 0714d9af..63a2fd87 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; @@ -103,9 +102,7 @@ static SpaceId firstRegisteredSpaceId( @Nonnull private static Store store(@Nonnull World world) { - Store store = ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")) - .getPhysicsStore() - .getStore(); + Store store = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(store, "select a PhysicsStore space"); return store; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java index f885e68b..a90cf87f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java @@ -12,9 +12,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -36,7 +36,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); if (!modeArg.provided(ctx)) { PhysicsEventCollectionMode mode = PhysicsWorlds.settings(physicsStore) .getEventCollectionMode(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java index 009cc610..5545026b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java @@ -12,8 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -35,7 +35,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); if (!dtArg.provided(ctx)) { ctx.sender().sendMessage(Message.raw("Impulse max step dt: " + PhysicsWorlds.settings(physicsStore).getMaxStepDt() diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java index 60b2c627..b83e6e3f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java @@ -12,9 +12,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -36,7 +36,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); PhysicsWorldSettings settings = PhysicsWorlds.settings(physicsStore); PhysicsStepMode stepMode = settings.getStepMode(); if (!stepsArg.provided(ctx)) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 0ccee78d..ec910cbb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -15,9 +15,9 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -56,7 +56,7 @@ public SolverSettingsCommand() { @Override protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, world, spaceArg); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java index 95148359..0fb296c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java @@ -14,10 +14,10 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -40,7 +40,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); if (!modeArg.provided(ctx)) { ctx.sender().sendMessage(Message.raw("Impulse step mode: " + PhysicsWorlds.settings(physicsStore).getStepMode().getSerializedName())); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java index ff558a82..f4d22f06 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java @@ -12,9 +12,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -36,7 +36,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); if (!modeArg.provided(ctx)) { PhysicsStepSchedulingMode mode = PhysicsWorlds.settings(physicsStore) .getStepSchedulingMode(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java index 4afb4c32..80a52474 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java @@ -14,9 +14,9 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -89,7 +89,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, world, spaceArg); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java index 7b89cd9d..6d57be7e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java @@ -14,10 +14,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -112,7 +112,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(ctx, world, spaceArg); diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java index d97efa3c..42f1f3aa 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java @@ -13,9 +13,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -71,7 +71,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); WorldCollisionSpaceSelection.Selection selection = WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); if (selection == null) { diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java index a06bef68..40a71b76 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -18,8 +18,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -62,7 +62,7 @@ private static void sendReport(@Nonnull CommandContext ctx, var latest = profiling.latest(); var worst = profiling.worst(); PhysicsEntityDiagnostics.Snapshot entityDiagnostics = PhysicsEntityDiagnostics.collect(store); - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); RuntimeFootprint runtimeFootprint = RuntimeFootprint.collect(summaries); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling: " diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java index 898a64bd..74720015 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java @@ -13,11 +13,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -71,7 +71,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - Store physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); WorldCollisionSpaceSelection.Selection selection = WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); if (selection == null) { From e195acef6636ca8e7aa63f417653c6a6873e3d42 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 12:58:36 +0200 Subject: [PATCH 334/534] refactor(core): route internal store access through physics threading Signed-off-by: Blovien --- .../control/systems/PhysicsKinematicControlSystem.java | 6 ++---- .../systems/PhysicsStoreControlSessionMutations.java | 6 ++---- .../systems/PhysicsStoreWorldCollisionProducerSystem.java | 4 +--- .../internal/resources/PhysicsWorldRuntimeResource.java | 4 +--- .../core/internal/systems/debug/PhysicsDebugSystem.java | 5 ++--- .../publication/PhysicsStoreEventPublicationSystem.java | 8 ++------ 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 75d5c734..e407be5a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -18,7 +18,6 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; @@ -164,9 +163,8 @@ private static PhysicsStoreControlTargets resolvePhysicsStoreTargets( @Nonnull Store store, @Nonnull Ref bodyRef, @Nonnull Ref anchorBodyRef) { - PhysicsStore physicsStore = - ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); - Store physics = physicsStore.getStore(); + Store physics = PhysicsThreading.store( + store.getExternalData().getWorld()); PhysicsThreading.requireWorldThread(physics, "resolve PhysicsStore kinematic control targets"); if (!validBodyRef(physics, bodyRef) || !validBodyRef(physics, anchorBodyRef)) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 641eee97..ad30ae99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; @@ -30,9 +29,8 @@ private PhysicsStoreControlSessionMutations() { public static void applyRelease(@Nonnull Store store, @Nonnull PhysicsControlSessionComponent session) { - Store physicsStore = - ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore() - .getStore(); + Store physicsStore = PhysicsThreading.store( + store.getExternalData().getWorld()); PhysicsThreading.requireWorldThread(physicsStore, "apply PhysicsStore control-session release mutations"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java index bfb352b5..9f44d3da 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -17,7 +17,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; @@ -77,8 +76,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { long tickStart = snapshot != null ? System.nanoTime() : 0L; try { World world = store.getExternalData().getWorld(); - PhysicsStore physicsStore = ((PhysicsStoreWorld) world).getPhysicsStore(); - Store physics = physicsStore.getStore(); + Store physics = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(physics, "produce PhysicsStore world-collision terrain mutations"); PhysicsTerrainMutationQueueResource queue = physics.getResource( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 22d1b0d6..343d402e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -14,7 +14,6 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; @@ -201,8 +200,7 @@ private Store authoritativePhysicsStore(@Nonnull String operation) @Nonnull private static Store physicsStore(@Nonnull World world) { - return ((PhysicsStoreWorld) Objects.requireNonNull(world, "world")).getPhysicsStore() - .getStore(); + return PhysicsThreading.store(world); } private static boolean sameRef(@Nullable Ref first, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index f93e7353..e007592a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -18,12 +18,12 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; @@ -108,8 +108,7 @@ public void tick(float dt, int index, @Nonnull Store store) { return; } - Store physicsStore = - ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + Store physicsStore = PhysicsThreading.store(world); float overlayLifetime = PhysicsDebugRenderer.lifetimeForRefresh( debug.getOverlayRefreshSeconds(), dt); float worldCollisionLifetime = PhysicsDebugRenderer.lifetimeForRefresh( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index a58b1d79..c84ce06e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -9,7 +9,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource.StepSample; @@ -41,11 +40,8 @@ public final class PhysicsStoreEventPublicationSystem extends TickingSystem store) { World world = store.getExternalData().getWorld(); - if (!(world instanceof PhysicsStoreWorld physicsStoreWorld)) { - return; - } - Store physics = physicsStoreWorld.getPhysicsStore().getStore(); - if (physics.isShutdown()) { + Store physics = PhysicsThreading.storeOrNull(world); + if (physics == null || physics.isShutdown()) { return; } PhysicsThreading.requireWorldThread(physics, "publish PhysicsStore event frame"); From 89fbf3bd69e30636cd5136b89fcc1d9911e9ad63 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 13:02:13 +0200 Subject: [PATCH 335/534] refactor(sync): centralize physics store access Signed-off-by: Blovien --- .../core/internal/systems/sync/PhysicsSyncSystem.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index f9f23ab7..1cad9256 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -17,7 +17,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; @@ -197,9 +196,7 @@ private static boolean sameRef(@Nullable Ref first, @Nonnull private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( @Nonnull Store store) { - PhysicsStore physicsStore = - ((PhysicsStoreWorld) store.getExternalData().getWorld()).getPhysicsStore(); - Store physics = physicsStore.getStore(); + Store physics = PhysicsThreading.store(store.getExternalData().getWorld()); PhysicsThreading.requireWorldThread(physics, "read copied PhysicsStore sync snapshots"); return physics.getResource( From dba624efa02356d01c1531f8e61d6cec262fe8e9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 13:06:04 +0200 Subject: [PATCH 336/534] refactor(crucible): use physics threading store access Signed-off-by: Blovien --- .../core/internal/crucible/ImpulseApiCrucibleTests.java | 3 +-- .../core/internal/crucible/ImpulseLiveCrucibleTests.java | 3 +-- .../core/internal/crucible/PhysicsStoreCrucibleSupport.java | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index aef3b412..5887c69e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; @@ -376,7 +375,7 @@ private static PhysicsWorldResource physicsResource(@Nonnull World world) { } private static Store physicsStore(@Nonnull World world) { - return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + return PhysicsStoreCrucibleSupport.physicsStore(world); } private static boolean stepSpaceDoesNotThrow() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index e9535798..affb9ca6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -13,7 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; @@ -167,7 +166,7 @@ private static void submitLiveBody(Store store, } private static Store physicsStore(World world) { - return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + return PhysicsThreading.store(world); } private static Ref spawnLiveBlockBody(Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index 74ebe445..f1aa9d05 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.early.PhysicsStoreWorld; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; @@ -33,7 +32,7 @@ private PhysicsStoreCrucibleSupport() { @Nonnull static Store physicsStore(@Nonnull World world) { - return ((PhysicsStoreWorld) world).getPhysicsStore().getStore(); + return PhysicsThreading.store(world); } static void clearAll(@Nonnull Store store) { From 70f886efa48fddc67d7555eb4f03020d00d0b748 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 13:07:33 +0200 Subject: [PATCH 337/534] test(physicsentity): update attachment component imports Signed-off-by: Blovien --- .../core/internal/systems/debug/PhysicsDebugSystemTest.java | 2 +- .../core/internal/systems/sync/PhysicsSyncSystemTest.java | 6 +++--- .../projection/BodyAttachmentComponentTest.java | 2 +- .../impulse/examples/utils/ExamplePhysicsUtilsTest.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java index db37f872..163b3829 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java @@ -12,7 +12,7 @@ import dev.hytalemodding.impulse.api.PhysicsContact; import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java index 49c0077d..5dae4cb7 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java @@ -5,9 +5,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.UUID; import org.joml.Quaterniond; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java index 2285eb19..df9edc19 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java @@ -8,7 +8,7 @@ import java.util.UUID; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java index 86a4a30f..b34cb150 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; -import dev.hytalemodding.impulse.core.plugin.projection.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import java.lang.reflect.Field; import javax.annotation.Nonnull; From 579e822ef00f32e6ac7c3dfb52ba213b6da06341 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 13:24:28 +0200 Subject: [PATCH 338/534] fix(core): collect physics contacts on owner lane Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 19 +- .../PhysicsStepSchedulerResource.java | 24 +++ .../internal/systems/BodyBindingSystem.java | 1 + .../systems/BodyCommandApplicationSystem.java | 1 + .../CompletedStepPublicationSystem.java | 129 +------------- .../systems/StepSubmissionSystem.java | 168 +++++++++++++++++- .../systems/TerrainColliderBindingSystem.java | 2 + 7 files changed, 209 insertions(+), 135 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index 402fd98f..ca1b1387 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -258,11 +258,24 @@ public List> bodyRefsForSpaceHandle( } public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, + @Nullable Ref bodyRef, + @Nonnull PhysicsBodyType bodyType, + @Nonnull ShapeType shapeType) { + BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.get(handle.value()); + putBodyHitMetadata(handle, + metadata != null ? metadata.bodyUuid() : new UUID(0L, 0L), + bodyRef, + bodyType, + shapeType); + } + + public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, + @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull ShapeType shapeType) { bodyHitMetadataByHandle.put(handle.value(), - new BodyHitMetadata(bodyRef, bodyType, shapeType)); + new BodyHitMetadata(bodyUuid, bodyRef, bodyType, shapeType)); } @Nullable @@ -741,11 +754,13 @@ void accept(@Nonnull Ref spaceRef, @Nonnull PhysicsBackendRuntime runtime); } - public record BodyHitMetadata(@Nullable Ref bodyRef, + public record BodyHitMetadata(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull ShapeType shapeType) { public BodyHitMetadata { + Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(bodyType, "bodyType"); Objects.requireNonNull(shapeType, "shapeType"); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java index db77d1aa..06bb001a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import java.util.List; @@ -253,6 +254,8 @@ public record CompletedStep(@Nullable StepInput input, long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, @Nonnull List bodySnapshots, + @Nonnull List physicsEvents, + int droppedBackendEventCount, @Nullable Throwable failure) { public CompletedStep(int spaces, @@ -268,6 +271,18 @@ public CompletedStep(int spaces, long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, @Nonnull List bodySnapshots) { + this(spaces, substeps, stepSubmitNanos, snapshotNanos, nativePhaseStats, bodySnapshots, + List.of(), 0); + } + + public CompletedStep(int spaces, + int substeps, + long stepSubmitNanos, + long snapshotNanos, + @Nonnull PhysicsStepPhaseStats nativePhaseStats, + @Nonnull List bodySnapshots, + @Nonnull List physicsEvents, + int droppedBackendEventCount) { this(null, spaces, substeps, @@ -275,6 +290,8 @@ public CompletedStep(int spaces, snapshotNanos, nativePhaseStats, bodySnapshots, + physicsEvents, + droppedBackendEventCount, null); } @@ -286,6 +303,9 @@ public CompletedStep(int spaces, Objects.requireNonNull(nativePhaseStats, "nativePhaseStats"); bodySnapshots = List.copyOf(Objects.requireNonNull(bodySnapshots, "bodySnapshots")); + physicsEvents = List.copyOf(Objects.requireNonNull(physicsEvents, + "physicsEvents")); + droppedBackendEventCount = Math.max(0, droppedBackendEventCount); } @Nonnull @@ -297,6 +317,8 @@ private CompletedStep withInput(@Nonnull StepInput input) { snapshotNanos, nativePhaseStats, bodySnapshots, + physicsEvents, + droppedBackendEventCount, failure); } @@ -309,6 +331,8 @@ private static CompletedStep failed(@Nonnull StepInput input, 0L, PhysicsStepPhaseStats.unavailable(), List.of(), + List.of(), + 0, Objects.requireNonNull(failure, "failure")); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java index 2c722522..08a944a3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java @@ -164,6 +164,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); runtime.putBodyHandle(bodyUuid, bodyRef, body.getSpaceUuid(), spaceHandle, bodyHandle); runtime.putBodyHitMetadata(bodyHandle, + bodyUuid, bodyRef, bodyType, shape.getShapeType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index 1dac9a79..7cbc263c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -241,6 +241,7 @@ private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtim PhysicsRuntimeResource.BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyHandle); if (metadata != null) { runtime.putBodyHitMetadata(bodyHandle, + metadata.bodyUuid(), metadata.bodyRef(), bodyType, metadata.shapeType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 29f74a94..4ba750ba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -10,7 +10,6 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource.BodyRegistrationPublication; @@ -18,18 +17,14 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; @@ -41,7 +36,6 @@ import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nonnull; -import org.joml.Vector3f; /** * Publishes the last completed backend state as a copied PhysicsStore snapshot frame. @@ -103,15 +97,14 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) compatibility, snapshotBodyUuids)); profiling.recordSnapshot(completed.snapshotNanos(), bodies.size()); - StepBackendEvents backendEvents = collectBackendEvents(store, runtime); store.getResource(PhysicsEventResource.getResourceType()) .publishStepFrame(frame.sequence(), Math.max(0L, store.getExternalData().getWorld().getTick()), bodies.size(), profiling.getStepSubmitNanos(), completed.snapshotNanos(), - backendEvents.physicsEvents, - backendEvents.droppedBackendEventCount); + completed.physicsEvents(), + completed.droppedBackendEventCount()); } @Nonnull @@ -169,104 +162,6 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run } } - @Nonnull - private static StepBackendEvents collectBackendEvents(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime) { - if (!store.getResource(PhysicsWorldSettingsResource.getResourceType()) - .getSettings() - .getEventCollectionMode() - .collectsBackendEvents()) { - return StepBackendEvents.EMPTY; - } - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - StepBackendEvents backendEvents = new StepBackendEvents(); - runtime.forEachRuntimeSpaceBinding((spaceRef, _, spaceHandle, backendRuntime) -> { - UUID spaceUuid = runtime.getSpaceUuid(spaceRef); - if (spaceUuid == null) { - backendEvents.droppedBackendEventCount += backendRuntime.contactCount(spaceHandle.value()); - return; - } - SpaceId spaceId = compatibility.getSpaceId(spaceUuid); - if (spaceId == null) { - backendEvents.droppedBackendEventCount += backendRuntime.contactCount(spaceHandle.value()); - return; - } - backendRuntime.contacts(spaceHandle.value(), (bodyAId, - bodyBId, - pointAX, - pointAY, - pointAZ, - pointBX, - pointBY, - pointBZ, - normalBX, - normalBY, - normalBZ, - distance, - impulse) -> collectContactEvent(runtime, - backendEvents, - spaceId, - bodyAId, - bodyBId, - pointAX, - pointAY, - pointAZ, - pointBX, - pointBY, - pointBZ, - normalBX, - normalBY, - normalBZ, - distance, - impulse)); - }); - return backendEvents; - } - - private static void collectContactEvent(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull StepBackendEvents backendEvents, - @Nonnull SpaceId spaceId, - long bodyAId, - long bodyBId, - float pointAX, - float pointAY, - float pointAZ, - float pointBX, - float pointBY, - float pointBZ, - float normalBX, - float normalBY, - float normalBZ, - float distance, - float impulse) { - BodyHitMetadata bodyA = runtime.getBodyHitMetadata(bodyAId); - BodyHitMetadata bodyB = runtime.getBodyHitMetadata(bodyBId); - if (bodyA == null - || bodyA.bodyRef() == null - || bodyB == null - || bodyB.bodyRef() == null) { - backendEvents.droppedBackendEventCount++; - return; - } - UUID bodyAUuid = PhysicsStoreSystemSupport.rowUuid(bodyA.bodyRef()); - UUID bodyBUuid = PhysicsStoreSystemSupport.rowUuid(bodyB.bodyRef()); - if (PhysicsStoreSystemSupport.isNil(bodyAUuid) - || PhysicsStoreSystemSupport.isNil(bodyBUuid)) { - backendEvents.droppedBackendEventCount++; - return; - } - backendEvents.physicsEvents.add(new PhysicsContactEvent(spaceId, - PhysicsContactPhase.OBSERVED, - bodyAUuid, - bodyBUuid, - new Vector3f(pointAX, pointAY, pointAZ), - new Vector3f(pointBX, pointBY, pointBZ), - new Vector3f(normalBX, normalBY, normalBZ), - distance, - impulse)); - } - @Nonnull @Override public Set> getDependencies() { @@ -278,24 +173,4 @@ public Set> getDependencies() { public Query getQuery() { return PhysicsStoreSystemSupport.uuidQuery(); } - - private static final class StepBackendEvents { - - @Nonnull - private static final StepBackendEvents EMPTY = new StepBackendEvents(List.of(), 0); - - @Nonnull - private final List physicsEvents; - private int droppedBackendEventCount; - - private StepBackendEvents() { - this(new ArrayList<>(), 0); - } - - private StepBackendEvents(@Nonnull List physicsEvents, - int droppedBackendEventCount) { - this.physicsEvents = physicsEvents; - this.droppedBackendEventCount = droppedBackendEventCount; - } - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index a7bbfb58..f3159c6a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.systems; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -7,8 +8,10 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; @@ -16,7 +19,9 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; @@ -24,12 +29,15 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -63,6 +71,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsWorldSettingsResource.getResourceType()); PhysicsWorldSettings settings = settingsResource.getSettings(); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsStepSchedulerResource scheduler = store.getResource( PhysicsStepSchedulerResource.getResourceType()); StepInput input = scheduler.acceptStepInput(safeDt, @@ -94,8 +104,15 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) resetStepPhaseStats(runtime); } List bindings = runtimeStepBindings(runtime); + boolean collectBackendEvents = settings.getEventCollectionMode().collectsBackendEvents(); boolean submitted = scheduler.submitStep(input, - () -> runOwnerStep(runtime, bindings, steps, stepDt, profilingEnabled), + () -> runOwnerStep(runtime, + compatibility, + bindings, + steps, + stepDt, + profilingEnabled, + collectBackendEvents), System.nanoTime()); if (!submitted) { throw new IllegalStateException("PhysicsStore owner-lane scheduler refused a submitted step"); @@ -104,10 +121,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) @Nonnull private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull List bindings, int steps, float stepDt, - boolean profilingEnabled) { + boolean profilingEnabled, + boolean collectBackendEvents) { long stepStartNanos = profilingEnabled ? System.nanoTime() : 0L; StepCounters counters = new StepCounters(); for (RuntimeStepBinding binding : bindings) { @@ -125,20 +144,26 @@ private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtim List bodySnapshots = collectOwnerLaneSnapshots(runtime, bindings); long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; + StepBackendEvents backendEvents = collectOwnerLaneBackendEvents(runtime, + compatibility, + bindings, + collectBackendEvents); return new CompletedStep(counters.spaceCount, counters.substeps, stepNanos, snapshotNanos, nativePhaseStats, - bodySnapshots); + bodySnapshots, + backendEvents.physicsEvents(), + backendEvents.droppedBackendEventCount()); } @Nonnull private static List runtimeStepBindings( @Nonnull PhysicsRuntimeResource runtime) { List bindings = new ArrayList<>(); - runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> - bindings.add(new RuntimeStepBinding(spaceHandle, backendRuntime))); + runtime.forEachRuntimeSpaceBinding((spaceRef, _, spaceHandle, backendRuntime) -> + bindings.add(new RuntimeStepBinding(spaceRef, spaceHandle, backendRuntime))); return bindings; } @@ -242,6 +267,99 @@ private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource run sleeping)); } + @Nonnull + private static StepBackendEvents collectOwnerLaneBackendEvents( + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull List bindings, + boolean collectBackendEvents) { + if (!collectBackendEvents) { + return StepBackendEvents.EMPTY; + } + StepBackendEvents backendEvents = new StepBackendEvents(); + for (RuntimeStepBinding binding : bindings) { + UUID spaceUuid = runtime.getSpaceUuid(binding.spaceRef()); + if (spaceUuid == null) { + backendEvents.addDropped( + binding.backendRuntime().contactCount(binding.spaceHandle().value())); + continue; + } + SpaceId spaceId = compatibility.getSpaceId(spaceUuid); + if (spaceId == null) { + backendEvents.addDropped( + binding.backendRuntime().contactCount(binding.spaceHandle().value())); + continue; + } + binding.backendRuntime().contacts(binding.spaceHandle().value(), (bodyAId, + bodyBId, + pointAX, + pointAY, + pointAZ, + pointBX, + pointBY, + pointBZ, + normalBX, + normalBY, + normalBZ, + distance, + impulse) -> collectOwnerLaneContactEvent(runtime, + backendEvents, + spaceId, + bodyAId, + bodyBId, + pointAX, + pointAY, + pointAZ, + pointBX, + pointBY, + pointBZ, + normalBX, + normalBY, + normalBZ, + distance, + impulse)); + } + return backendEvents; + } + + private static void collectOwnerLaneContactEvent(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull StepBackendEvents backendEvents, + @Nonnull SpaceId spaceId, + long bodyAId, + long bodyBId, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + BodyHitMetadata bodyA = runtime.getBodyHitMetadata(bodyAId); + BodyHitMetadata bodyB = runtime.getBodyHitMetadata(bodyBId); + if (bodyA == null + || bodyA.bodyRef() == null + || bodyB == null + || bodyB.bodyRef() == null + || PhysicsStoreSystemSupport.isNil(bodyA.bodyUuid()) + || PhysicsStoreSystemSupport.isNil(bodyB.bodyUuid())) { + backendEvents.addDropped(1); + return; + } + backendEvents.add(new PhysicsContactEvent(spaceId, + PhysicsContactPhase.OBSERVED, + bodyA.bodyUuid(), + bodyB.bodyUuid(), + new Vector3f(pointAX, pointAY, pointAZ), + new Vector3f(pointBX, pointBY, pointBZ), + new Vector3f(normalBX, normalBY, normalBZ), + distance, + impulse)); + } + private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runtime, float dt, int simulationSteps, @@ -513,10 +631,48 @@ private static final class StepCounters { private int substeps; } - private record RuntimeStepBinding(@Nonnull BackendSpaceHandle spaceHandle, + private record RuntimeStepBinding(@Nonnull Ref spaceRef, + @Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } + private static final class StepBackendEvents { + + @Nonnull + private static final StepBackendEvents EMPTY = new StepBackendEvents(List.of(), 0); + + @Nonnull + private final List physicsEvents; + private int droppedBackendEventCount; + + private StepBackendEvents() { + this(new ArrayList<>(), 0); + } + + private StepBackendEvents(@Nonnull List physicsEvents, + int droppedBackendEventCount) { + this.physicsEvents = physicsEvents; + this.droppedBackendEventCount = Math.max(0, droppedBackendEventCount); + } + + private void add(@Nonnull PhysicsFrameEvent event) { + physicsEvents.add(event); + } + + private void addDropped(int count) { + droppedBackendEventCount += Math.max(0, count); + } + + @Nonnull + private List physicsEvents() { + return physicsEvents; + } + + private int droppedBackendEventCount() { + return droppedBackendEventCount; + } + } + private static int requiredSteps(float travel, float safeTravel) { if (travel <= safeTravel) { return 1; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java index d2b735fd..5ca7cf04 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java @@ -196,6 +196,7 @@ private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); runtime.putTerrainBodyHandle(terrainRef, terrainUuid, spaceHandle, bodyHandle, true); runtime.putBodyHitMetadata(bodyHandle, + terrainUuid, terrainRef, PhysicsBodyType.STATIC, ShapeType.VOXELS); @@ -242,6 +243,7 @@ private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, bodyHandle, false); runtime.putBodyHitMetadata(bodyHandle, + terrainUuid, terrainRef, PhysicsBodyType.STATIC, ShapeType.BOX); From 36a2de4de64eb488497e36b9b3bdcc79a487e95e Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 13:32:29 +0200 Subject: [PATCH 339/534] perf(core): store physics body snapshots as scalars Signed-off-by: Blovien --- .../systems/StepSubmissionSystem.java | 20 +- .../plugin/snapshots/PhysicsBodySnapshot.java | 376 ++++++++++++++++-- 2 files changed, 364 insertions(+), 32 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index f3159c6a..f36a9e57 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -39,7 +39,6 @@ import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; -import org.joml.Quaternionf; import org.joml.Vector3f; /** @@ -255,14 +254,23 @@ private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource run if (metadata == null) { return; } - snapshots.add(new PhysicsBodySnapshot(metadata.bodyRef(), + snapshots.add(PhysicsBodySnapshot.of(metadata.bodyRef(), metadata.bodyUuid(), metadata.spaceUuid(), BackendRuntimeCodes.bodyType(bodyTypeCode), - new Vector3f(positionX, positionY, positionZ), - new Quaternionf(rotationX, rotationY, rotationZ, rotationW), - new Vector3f(linearVelocityX, linearVelocityY, linearVelocityZ), - new Vector3f(angularVelocityX, angularVelocityY, angularVelocityZ), + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, centerOfMassOffsetY, sleeping)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsBodySnapshot.java index cc4dfbf7..5d17dfa5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshots/PhysicsBodySnapshot.java @@ -13,16 +13,31 @@ /** * Copied body snapshot published out of PhysicsStore for projection and queries. */ -public record PhysicsBodySnapshot(@Nullable Ref bodyRef, - @Nonnull UUID bodyUuid, - @Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyType bodyType, - @Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - float centerOfMassOffsetY, - boolean sleeping) { +public final class PhysicsBodySnapshot { + + @Nullable + private final Ref bodyRef; + @Nonnull + private final UUID bodyUuid; + @Nonnull + private final UUID spaceUuid; + @Nonnull + private final PhysicsBodyType bodyType; + private final float positionX; + private final float positionY; + private final float positionZ; + private final float rotationX; + private final float rotationY; + private final float rotationZ; + private final float rotationW; + private final float linearVelocityX; + private final float linearVelocityY; + private final float linearVelocityZ; + private final float angularVelocityX; + private final float angularVelocityY; + private final float angularVelocityZ; + private final float centerOfMassOffsetY; + private final boolean sleeping; public PhysicsBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull UUID spaceUuid, @@ -45,37 +60,346 @@ public PhysicsBodySnapshot(@Nonnull UUID bodyUuid, sleeping); } - public PhysicsBodySnapshot { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - Objects.requireNonNull(bodyType, "bodyType"); - position = new Vector3f(Objects.requireNonNull(position, "position")); - rotation = new Quaternionf(Objects.requireNonNull(rotation, "rotation")); - linearVelocity = new Vector3f(Objects.requireNonNull(linearVelocity, "linearVelocity")); - angularVelocity = new Vector3f(Objects.requireNonNull(angularVelocity, "angularVelocity")); + public PhysicsBodySnapshot(@Nullable Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + @Nonnull Vector3f linearVelocity, + @Nonnull Vector3f angularVelocity, + float centerOfMassOffsetY, + boolean sleeping) { + this(bodyRef, + bodyUuid, + spaceUuid, + bodyType, + Objects.requireNonNull(position, "position").x, + position.y, + position.z, + Objects.requireNonNull(rotation, "rotation").x, + rotation.y, + rotation.z, + rotation.w, + Objects.requireNonNull(linearVelocity, "linearVelocity").x, + linearVelocity.y, + linearVelocity.z, + Objects.requireNonNull(angularVelocity, "angularVelocity").x, + angularVelocity.y, + angularVelocity.z, + centerOfMassOffsetY, + sleeping); + } + + @Nonnull + public static PhysicsBodySnapshot of(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + float centerOfMassOffsetY, + boolean sleeping) { + return of(null, + bodyUuid, + spaceUuid, + bodyType, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + centerOfMassOffsetY, + sleeping); + } + + @Nonnull + public static PhysicsBodySnapshot of(@Nullable Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + float centerOfMassOffsetY, + boolean sleeping) { + return new PhysicsBodySnapshot(bodyRef, + bodyUuid, + spaceUuid, + bodyType, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + centerOfMassOffsetY, + sleeping); + } + + private PhysicsBodySnapshot(@Nullable Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + float centerOfMassOffsetY, + boolean sleeping) { + this.bodyRef = bodyRef; + this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); + this.positionX = positionX; + this.positionY = positionY; + this.positionZ = positionZ; + this.rotationX = rotationX; + this.rotationY = rotationY; + this.rotationZ = rotationZ; + this.rotationW = rotationW; + this.linearVelocityX = linearVelocityX; + this.linearVelocityY = linearVelocityY; + this.linearVelocityZ = linearVelocityZ; + this.angularVelocityX = angularVelocityX; + this.angularVelocityY = angularVelocityY; + this.angularVelocityZ = angularVelocityZ; + this.centerOfMassOffsetY = centerOfMassOffsetY; + this.sleeping = sleeping; + } + + @Nullable + public Ref bodyRef() { + return bodyRef; + } + + @Nonnull + public UUID bodyUuid() { + return bodyUuid; + } + + @Nonnull + public UUID spaceUuid() { + return spaceUuid; + } + + @Nonnull + public PhysicsBodyType bodyType() { + return bodyType; + } + + public float positionX() { + return positionX; + } + + public float positionY() { + return positionY; + } + + public float positionZ() { + return positionZ; + } + + public float rotationX() { + return rotationX; + } + + public float rotationY() { + return rotationY; + } + + public float rotationZ() { + return rotationZ; + } + + public float rotationW() { + return rotationW; + } + + public float linearVelocityX() { + return linearVelocityX; + } + + public float linearVelocityY() { + return linearVelocityY; + } + + public float linearVelocityZ() { + return linearVelocityZ; + } + + public float angularVelocityX() { + return angularVelocityX; + } + + public float angularVelocityY() { + return angularVelocityY; + } + + public float angularVelocityZ() { + return angularVelocityZ; + } + + public float centerOfMassOffsetY() { + return centerOfMassOffsetY; + } + + public boolean sleeping() { + return sleeping; } @Nonnull - @Override public Vector3f position() { - return new Vector3f(position); + return copyPositionTo(new Vector3f()); } @Nonnull - @Override public Quaternionf rotation() { - return new Quaternionf(rotation); + return copyRotationTo(new Quaternionf()); } @Nonnull - @Override public Vector3f linearVelocity() { - return new Vector3f(linearVelocity); + return copyLinearVelocityTo(new Vector3f()); } @Nonnull - @Override public Vector3f angularVelocity() { - return new Vector3f(angularVelocity); + return copyAngularVelocityTo(new Vector3f()); + } + + @Nonnull + public Vector3f copyPositionTo(@Nonnull Vector3f target) { + return Objects.requireNonNull(target, "target").set(positionX, positionY, positionZ); + } + + @Nonnull + public Quaternionf copyRotationTo(@Nonnull Quaternionf target) { + return Objects.requireNonNull(target, "target") + .set(rotationX, rotationY, rotationZ, rotationW); + } + + @Nonnull + public Vector3f copyLinearVelocityTo(@Nonnull Vector3f target) { + return Objects.requireNonNull(target, "target") + .set(linearVelocityX, linearVelocityY, linearVelocityZ); + } + + @Nonnull + public Vector3f copyAngularVelocityTo(@Nonnull Vector3f target) { + return Objects.requireNonNull(target, "target") + .set(angularVelocityX, angularVelocityY, angularVelocityZ); + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof PhysicsBodySnapshot that)) { + return false; + } + return Float.compare(positionX, that.positionX) == 0 + && Float.compare(positionY, that.positionY) == 0 + && Float.compare(positionZ, that.positionZ) == 0 + && Float.compare(rotationX, that.rotationX) == 0 + && Float.compare(rotationY, that.rotationY) == 0 + && Float.compare(rotationZ, that.rotationZ) == 0 + && Float.compare(rotationW, that.rotationW) == 0 + && Float.compare(linearVelocityX, that.linearVelocityX) == 0 + && Float.compare(linearVelocityY, that.linearVelocityY) == 0 + && Float.compare(linearVelocityZ, that.linearVelocityZ) == 0 + && Float.compare(angularVelocityX, that.angularVelocityX) == 0 + && Float.compare(angularVelocityY, that.angularVelocityY) == 0 + && Float.compare(angularVelocityZ, that.angularVelocityZ) == 0 + && Float.compare(centerOfMassOffsetY, that.centerOfMassOffsetY) == 0 + && sleeping == that.sleeping + && Objects.equals(bodyRef, that.bodyRef) + && bodyUuid.equals(that.bodyUuid) + && spaceUuid.equals(that.spaceUuid) + && bodyType == that.bodyType; + } + + @Override + public int hashCode() { + int result = Objects.hash(bodyRef, bodyUuid, spaceUuid, bodyType, sleeping); + result = 31 * result + Float.hashCode(positionX); + result = 31 * result + Float.hashCode(positionY); + result = 31 * result + Float.hashCode(positionZ); + result = 31 * result + Float.hashCode(rotationX); + result = 31 * result + Float.hashCode(rotationY); + result = 31 * result + Float.hashCode(rotationZ); + result = 31 * result + Float.hashCode(rotationW); + result = 31 * result + Float.hashCode(linearVelocityX); + result = 31 * result + Float.hashCode(linearVelocityY); + result = 31 * result + Float.hashCode(linearVelocityZ); + result = 31 * result + Float.hashCode(angularVelocityX); + result = 31 * result + Float.hashCode(angularVelocityY); + result = 31 * result + Float.hashCode(angularVelocityZ); + result = 31 * result + Float.hashCode(centerOfMassOffsetY); + return result; + } + + @Nonnull + @Override + public String toString() { + return "PhysicsBodySnapshot[bodyRef=" + bodyRef + + ", bodyUuid=" + bodyUuid + + ", spaceUuid=" + spaceUuid + + ", bodyType=" + bodyType + + ", position=(" + positionX + ", " + positionY + ", " + positionZ + ')' + + ", rotation=(" + rotationX + ", " + rotationY + ", " + rotationZ + ", " + + rotationW + ')' + + ", linearVelocity=(" + linearVelocityX + ", " + linearVelocityY + ", " + + linearVelocityZ + ')' + + ", angularVelocity=(" + angularVelocityX + ", " + angularVelocityY + ", " + + angularVelocityZ + ')' + + ", centerOfMassOffsetY=" + centerOfMassOffsetY + + ", sleeping=" + sleeping + + ']'; } } From 06ef59291ffa103286e976c42423f2f3dd975158 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 13:41:46 +0200 Subject: [PATCH 340/534] perf(core): reduce physics snapshot publication allocation Signed-off-by: Blovien --- .../PhysicsBodyRegistrationResource.java | 6 +++--- .../resources/PhysicsRuntimeResource.java | 5 +++++ .../resources/PhysicsSnapshotResource.java | 16 +++++++++++----- .../CompletedStepPublicationSystem.java | 18 +++++++----------- .../internal/systems/StepSubmissionSystem.java | 12 +++++++++++- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java index ee0b3218..2bcdbeee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java @@ -78,7 +78,7 @@ public Collection getBodyRegistrationViews( public void publish(@Nonnull Collection publications) { Object2ObjectLinkedOpenHashMap publicationsByUuid = - new Object2ObjectLinkedOpenHashMap<>(); + new Object2ObjectLinkedOpenHashMap<>(publications.size()); for (BodyRegistrationPublication publication : publications) { BodyRegistrationPublication checkedPublication = Objects.requireNonNull(publication, "publication"); @@ -86,9 +86,9 @@ public void publish(@Nonnull Collection publication } Object2ObjectLinkedOpenHashMap viewsByUuid = - new Object2ObjectLinkedOpenHashMap<>(); + new Object2ObjectLinkedOpenHashMap<>(publicationsByUuid.size()); Int2ObjectOpenHashMap viewsByRowIndex = - new Int2ObjectOpenHashMap<>(); + new Int2ObjectOpenHashMap<>(publicationsByUuid.size()); for (BodyRegistrationPublication publication : publicationsByUuid.values()) { PhysicsBodyRegistrationView registration = publication.view(); viewsByUuid.put(registration.bodyUuid(), registration); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index ca1b1387..b6f163e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -548,6 +548,11 @@ public void forEachBodyHandle(@Nonnull BackendSpaceHandle spaceHandle, bodyHandles.forEach(consumer); } + public int bodyHandleCount(@Nonnull BackendSpaceHandle spaceHandle) { + LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); + return bodyHandles != null ? bodyHandles.size() : 0; + } + public void clear() { runtimesByBackend.clear(); spaceHandlesByUuid.clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java index e2d1f05f..aebf9f27 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java @@ -37,6 +37,10 @@ public PhysicsBodySnapshot getBody(@Nonnull UUID bodyUuid) { return snapshot.bodiesByUuid().get(bodyUuid); } + public boolean containsBody(@Nonnull UUID bodyUuid) { + return snapshot.bodiesByUuid().containsKey(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + @Nullable public PhysicsBodySnapshot getBody(@Nonnull Ref bodyRef) { PhysicsBodySnapshot body = snapshot.bodiesByRowIndex() @@ -45,9 +49,10 @@ public PhysicsBodySnapshot getBody(@Nonnull Ref bodyRef) { } public void publish(@Nonnull PhysicsSnapshotFrame frame) { - Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); + int bodyCount = frame.bodies().size(); + Map bodiesByUuid = new Object2ObjectOpenHashMap<>(bodyCount); Int2ObjectOpenHashMap bodiesByRowIndex = - new Int2ObjectOpenHashMap<>(); + new Int2ObjectOpenHashMap<>(bodyCount); for (PhysicsBodySnapshot body : frame.bodies()) { bodiesByUuid.put(body.bodyUuid(), body); Ref bodyRef = body.bodyRef(); @@ -75,10 +80,11 @@ public void clear() { @Nonnull private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, @Nonnull UUID bodyUuid) { - List bodies = new ArrayList<>(); - Map bodiesByUuid = new Object2ObjectOpenHashMap<>(); + int bodyCount = Math.max(0, current.frame().bodies().size() - 1); + List bodies = new ArrayList<>(bodyCount); + Map bodiesByUuid = new Object2ObjectOpenHashMap<>(bodyCount); Int2ObjectOpenHashMap bodiesByRowIndex = - new Int2ObjectOpenHashMap<>(); + new Int2ObjectOpenHashMap<>(bodyCount); for (PhysicsBodySnapshot body : current.frame().bodies()) { if (bodyUuid.equals(body.bodyUuid())) { continue; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 4ba750ba..62a7607a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -29,7 +29,6 @@ import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -80,10 +79,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) input.dtCapHit()); } List bodies = completed.bodySnapshots(); - Set snapshotBodyUuids = new ObjectOpenHashSet<>(); - for (PhysicsBodySnapshot body : bodies) { - snapshotBodyUuids.add(body.bodyUuid()); - } long nextSequence = snapshot.getLatestFrame().sequence() + 1L; float frameDt = input != null ? input.submittedDtSeconds() : dt; PhysicsSnapshotFrame frame = new PhysicsSnapshotFrame(nextSequence, @@ -95,7 +90,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) systemIndex, runtime, compatibility, - snapshotBodyUuids)); + snapshot)); profiling.recordSnapshot(completed.snapshotNanos(), bodies.size()); store.getResource(PhysicsEventResource.getResourceType()) .publishStepFrame(frame.sequence(), @@ -113,12 +108,13 @@ private static List collectRegistrationViews( int systemIndex, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull Set snapshotBodyUuids) { - List registrations = new ArrayList<>(); + @Nonnull PhysicsSnapshotResource snapshot) { + List registrations = + new ArrayList<>(snapshot.getLatestFrame().bodies().size()); BiConsumer, CommandBuffer> collector = (chunk, _) -> collectRegistrationViews(runtime, compatibility, - snapshotBodyUuids, + snapshot, registrations, chunk); store.forEachChunk(systemIndex, collector); @@ -127,7 +123,7 @@ private static List collectRegistrationViews( private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull Set snapshotBodyUuids, + @Nonnull PhysicsSnapshotResource snapshot, @Nonnull List registrations, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { @@ -137,7 +133,7 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run } var rowRef = chunk.getReferenceTo(index); BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); - if (body != null && snapshotBodyUuids.contains(rowUuid)) { + if (body != null && snapshot.containsBody(rowUuid)) { SpaceId spaceId = compatibility.getSpaceId(body.getSpaceUuid()); if (spaceId != null) { registrations.add(new BodyRegistrationPublication(rowRef, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index f36a9e57..a3c8e87b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -170,7 +170,8 @@ private static List runtimeStepBindings( private static List collectOwnerLaneSnapshots( @Nonnull PhysicsRuntimeResource runtime, @Nonnull List bindings) { - List snapshots = new ArrayList<>(); + List snapshots = new ArrayList<>(runtimeBodyHandleCount(runtime, + bindings)); for (RuntimeStepBinding binding : bindings) { binding.backendRuntime().snapshotBodies(binding.spaceHandle().value(), bodyIds -> runtime.forEachBodyHandle(binding.spaceHandle(), @@ -231,6 +232,15 @@ private static List collectOwnerLaneSnapshots( return snapshots; } + private static int runtimeBodyHandleCount(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull List bindings) { + int bodyCount = 0; + for (RuntimeStepBinding binding : bindings) { + bodyCount += runtime.bodyHandleCount(binding.spaceHandle()); + } + return bodyCount; + } + private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource runtime, @Nonnull List snapshots, long bodyId, From 740bbbd40d0d0ed6b4cad90e6cae0d1fde97e0c5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 13:52:02 +0200 Subject: [PATCH 341/534] perf(core): avoid duplicate snapshot index copies Signed-off-by: Blovien --- .../internal/resources/PhysicsBodyRegistrationResource.java | 4 ++-- .../core/internal/resources/PhysicsSnapshotResource.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java index 2bcdbeee..d9dee832 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java @@ -96,7 +96,7 @@ public void publish(@Nonnull Collection publication new RegistrationByRef(publication.bodyRef(), registration)); } registrations = new PublishedRegistrations(List.copyOf(viewsByUuid.values()), - Map.copyOf(viewsByUuid), + viewsByUuid, viewsByRowIndex); } @@ -114,7 +114,7 @@ public void removeBody(@Nonnull UUID bodyUuid) { viewsByRowIndex.int2ObjectEntrySet() .removeIf(entry -> entry.getValue().view().bodyUuid().equals(bodyUuid)); registrations = new PublishedRegistrations(List.copyOf(viewsByUuid.values()), - Map.copyOf(viewsByUuid), + viewsByUuid, viewsByRowIndex); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java index aebf9f27..c65897bc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java @@ -61,7 +61,7 @@ public void publish(@Nonnull PhysicsSnapshotFrame frame) { } } snapshot = new PublishedSnapshot(frame, - Map.copyOf(bodiesByUuid), + bodiesByUuid, bodiesByRowIndex); } @@ -100,7 +100,7 @@ private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, new PhysicsSnapshotFrame(current.frame().sequence(), current.frame().dt(), bodies), - Map.copyOf(bodiesByUuid), + bodiesByUuid, bodiesByRowIndex); } From 9d1660411657b45d3d85eea490f0d06f825c04e9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 14:04:03 +0200 Subject: [PATCH 342/534] perf(core): skip unchanged body registration publication Signed-off-by: Blovien --- ...tachedStreamingBenchmarkCrucibleTests.java | 27 ++++++++- ...pulseRapierBodyBenchmarkCrucibleTests.java | 30 +++++++++- .../PhysicsBodyRegistrationResource.java | 17 ++++-- .../resources/PhysicsProfilingResource.java | 37 ++++++++++++ .../resources/PhysicsRuntimeResource.java | 52 +++++++++++++---- .../PhysicsRuntimeProfilingResource.java | 57 +++++++++++++++++++ .../CompletedStepPublicationSystem.java | 40 +++++++++++-- .../PhysicsStoreEventPublicationSystem.java | 8 ++- .../PhysicsRuntimeProfiling.java | 12 ++++ .../WorldCollisionPerfReportCommand.java | 11 ++++ 10 files changed, 263 insertions(+), 28 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 10ae06fa..1a84fb24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -141,10 +142,12 @@ private static final class StageRunner { private final World world; private final PhysicsWorldRuntimeResource physics; private final Store physicsStore; + private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; private final WorldCollisionProfilingResource worldCollisionProfiling; private final PhysicsStoreWorldCollisionStreamingResource worldCollisionStreaming; private final PhysicsWorldSettings previousWorldSettings; + private final boolean previousPhysicsStoreProfilingEnabled; private final List retainedChunks = new ArrayList<>(); private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) @@ -155,12 +158,15 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) Store store = world.getEntityStore().getStore(); this.physics = PhysicsWorldRuntimeResource.require(store); this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); + this.physicsStoreProfiling = physicsStore.getResource( + PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.worldCollisionProfiling = store.getResource( WorldCollisionProfilingResource.getResourceType()); this.worldCollisionStreaming = store.getResource( PhysicsStoreWorldCollisionStreamingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); + this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); } private CompletionStage run() { @@ -186,8 +192,10 @@ private CompletionStage runStage(int stageIndex, int count = plan.counts().get(stageIndex); return startStageWhenReady(count, 1) .thenCompose(started -> contextWait(plan.warmupTicks()).thenCompose(_ -> { + physicsStoreProfiling.reset(); runtimeProfiling.reset(); worldCollisionProfiling.reset(); + physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); worldCollisionProfiling.setEnabled(true); long startedNanos = System.nanoTime(); @@ -254,8 +262,10 @@ private CompletionStage startStageWhenReady(int count, int attempt settings); PrewarmStats prewarm = prewarmWorldCollision(spaceId, count); spawnDetachedBodies(spaceId, count); + physicsStoreProfiling.reset(); runtimeProfiling.reset(); worldCollisionProfiling.reset(); + physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); worldCollisionProfiling.setEnabled(true); return CompletableFuture.completedFuture( @@ -282,10 +292,17 @@ private StageReport finishStage(int count, SpaceStats stats = SpaceStats.collect(physicsStore, worldCollisionStreaming, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); + double avgRegistrationPublicationMs = averageMillis( + step.getRegistrationPublicationNanos(), + step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); double avgWorldMs = averageMillis(worldCollision.getTickNanos(), worldCollision.getTickSamples()); - double totalMs = avgStepMs + avgSnapshotMs + avgSyncMs + avgWorldMs; + double totalMs = avgStepMs + + avgSnapshotMs + + avgRegistrationPublicationMs + + avgSyncMs + + avgWorldMs; StageHealth health = assessHealth(count, observedTickRate, stats, @@ -297,6 +314,7 @@ private StageReport finishStage(int count, observedTickRate, avgStepMs, avgSnapshotMs, + avgRegistrationPublicationMs, avgSyncMs, avgWorldMs, totalMs, @@ -339,6 +357,7 @@ private CompletionStage contextWait(int ticks) { private void clearStageState() { releaseRetainedChunks(); PhysicsStoreCrucibleSupport.clearAll(physicsStore); + physicsStoreProfiling.reset(); runtimeProfiling.reset(); worldCollisionProfiling.reset(); worldCollisionProfiling.clearDiagnosticRetainedSections(); @@ -346,6 +365,7 @@ private void clearStageState() { private void restoreStepSettings() { physics.setWorldSettings(previousWorldSettings); + physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); } private PrewarmStats prewarmWorldCollision(@Nonnull SpaceId spaceId, int count) { @@ -758,6 +778,7 @@ private record StageReport(int count, double observedTickRate, double avgStepMs, double avgSnapshotMs, + double avgRegistrationPublicationMs, double avgSyncMs, double avgWorldMs, double totalMs, @@ -797,6 +818,7 @@ private static StageReport failedPreflight(int count, @Nonnull String reason) { 0.0, 0.0, 0.0, + 0.0, 0, 0, 0, @@ -831,8 +853,9 @@ private String summary() { + " reason=" + health.reason() + " tps=" + format(observedTickRate) + " totalMs=" + format(totalMs) - + " step/snapshot/sync/worldMs=" + format(avgStepMs) + + " step/snapshot/registration/sync/worldMs=" + format(avgStepMs) + "/" + format(avgSnapshotMs) + + "/" + format(avgRegistrationPublicationMs) + "/" + format(avgSyncMs) + "/" + format(avgWorldMs) + " bodies dynamic/worldCollision=" + dynamicBodies diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 70aafc0c..f20ee9be 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -142,9 +143,11 @@ private static final class MatrixRunner { private final Store store; private final PhysicsWorldRuntimeResource physics; private final Store physicsStore; + private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; private final WorldCollisionProfilingResource worldCollisionProfiling; private final PhysicsWorldSettings previousWorldSettings; + private final boolean previousPhysicsStoreProfilingEnabled; private final boolean previousRuntimeProfilingEnabled; private final boolean previousWorldCollisionProfilingEnabled; @@ -156,10 +159,13 @@ private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) this.store = world.getEntityStore().getStore(); this.physics = PhysicsWorldRuntimeResource.require(store); this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); + this.physicsStoreProfiling = physicsStore.getResource( + PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.worldCollisionProfiling = store.getResource( WorldCollisionProfilingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); + this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); this.previousRuntimeProfilingEnabled = runtimeProfiling.isEnabled(); this.previousWorldCollisionProfilingEnabled = worldCollisionProfiling.isEnabled(); } @@ -188,8 +194,10 @@ private CompletionStage runCase(int index, MatrixCase matrixCase = new MatrixCase(plan.count(), plan.substeps().get(index)); return startCase(matrixCase) .thenCompose(started -> contextWait(plan.warmupTicks()).thenCompose(_ -> { + physicsStoreProfiling.reset(); runtimeProfiling.reset(); worldCollisionProfiling.reset(); + physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); worldCollisionProfiling.setEnabled(true); long startedNanos = System.nanoTime(); @@ -302,10 +310,17 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, SpaceStats stats = SpaceStats.collect(physicsStore, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); + double avgRegistrationPublicationMs = averageMillis( + step.getRegistrationPublicationNanos(), + step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); double avgWorldMs = averageMillis(worldCollision.getTickNanos(), worldCollision.getTickSamples()); - double totalMs = avgStepMs + avgSnapshotMs + avgSyncMs + avgWorldMs; + double totalMs = avgStepMs + + avgSnapshotMs + + avgRegistrationPublicationMs + + avgSyncMs + + avgWorldMs; MatrixHealth health = assessHealth(matrixCase, observedTickRate, step, @@ -316,6 +331,7 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, observedTickRate, avgStepMs, avgSnapshotMs, + avgRegistrationPublicationMs, avgSyncMs, avgWorldMs, totalMs, @@ -359,6 +375,7 @@ private void clearCaseState() { removeBenchmarkEntities(); physics.clearSyntheticVisualInterests(); PhysicsStoreCrucibleSupport.clearAll(physicsStore); + physicsStoreProfiling.reset(); runtimeProfiling.reset(); worldCollisionProfiling.reset(); worldCollisionProfiling.clearDiagnosticRetainedSections(); @@ -366,6 +383,7 @@ private void clearCaseState() { private void restoreSettings() { physics.setWorldSettings(previousWorldSettings); + physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); runtimeProfiling.setEnabled(previousRuntimeProfilingEnabled); worldCollisionProfiling.setEnabled(previousWorldCollisionProfilingEnabled); } @@ -472,13 +490,16 @@ private static void logComparison(@Nonnull List reports) { MatrixReport first = reports.get(0); MatrixReport second = reports.get(1); LOGGER.at(Level.INFO).log("Crucible Rapier body matrix comparison: %sx=%sms " - + "%sx=%sms stepRatio=%s snapshotRatio=%s totalRatio=%s worldCounters=%s/%s", + + "%sx=%sms stepRatio=%s snapshotRatio=%s registrationRatio=%s " + + "totalRatio=%s worldCounters=%s/%s", first.matrixCase().fixedSubsteps(), format(first.avgStepMs()), second.matrixCase().fixedSubsteps(), format(second.avgStepMs()), format(ratio(second.avgStepMs(), first.avgStepMs())), format(ratio(second.avgSnapshotMs(), first.avgSnapshotMs())), + format(ratio(second.avgRegistrationPublicationMs(), + first.avgRegistrationPublicationMs())), format(ratio(second.totalMs(), first.totalMs())), first.worldCounterSummary(), second.worldCounterSummary()); @@ -589,6 +610,7 @@ private record MatrixReport(@Nonnull MatrixCase matrixCase, double observedTickRate, double avgStepMs, double avgSnapshotMs, + double avgRegistrationPublicationMs, double avgSyncMs, double avgWorldMs, double totalMs, @@ -629,6 +651,7 @@ private static MatrixReport failedPreflight(@Nonnull MatrixCase matrixCase, 0.0, 0.0, 0.0, + 0.0, 0, 0, 0, @@ -664,8 +687,9 @@ private String summary() { + " reason=" + health.reason() + " tps=" + format(observedTickRate) + " totalMs=" + format(totalMs) - + " step/snapshot/sync/worldMs=" + format(avgStepMs) + + " step/snapshot/registration/sync/worldMs=" + format(avgStepMs) + "/" + format(avgSnapshotMs) + + "/" + format(avgRegistrationPublicationMs) + "/" + format(avgSyncMs) + "/" + format(avgWorldMs) + " step samples/substeps/bodySnapshots/spatialCells=" + stepSamples diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java index d9dee832..7d40b5dc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java @@ -52,6 +52,10 @@ public int getBodyRegistrationCount() { return registrations.views().size(); } + public boolean isCurrent(long registrationTopologyGeneration) { + return registrations.registrationTopologyGeneration() == registrationTopologyGeneration; + } + public int getBodyRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { Objects.requireNonNull(persistenceMode, "persistenceMode"); int count = 0; @@ -76,7 +80,8 @@ public Collection getBodyRegistrationViews( return views; } - public void publish(@Nonnull Collection publications) { + public void publish(long registrationTopologyGeneration, + @Nonnull Collection publications) { Object2ObjectLinkedOpenHashMap publicationsByUuid = new Object2ObjectLinkedOpenHashMap<>(publications.size()); for (BodyRegistrationPublication publication : publications) { @@ -95,7 +100,8 @@ public void publish(@Nonnull Collection publication viewsByRowIndex.put(publication.bodyRef().getIndex(), new RegistrationByRef(publication.bodyRef(), registration)); } - registrations = new PublishedRegistrations(List.copyOf(viewsByUuid.values()), + registrations = new PublishedRegistrations(registrationTopologyGeneration, + List.copyOf(viewsByUuid.values()), viewsByUuid, viewsByRowIndex); } @@ -113,7 +119,8 @@ public void removeBody(@Nonnull UUID bodyUuid) { new Int2ObjectOpenHashMap<>(current.viewsByRowIndex()); viewsByRowIndex.int2ObjectEntrySet() .removeIf(entry -> entry.getValue().view().bodyUuid().equals(bodyUuid)); - registrations = new PublishedRegistrations(List.copyOf(viewsByUuid.values()), + registrations = new PublishedRegistrations(current.registrationTopologyGeneration(), + List.copyOf(viewsByUuid.values()), viewsByUuid, viewsByRowIndex); } @@ -155,12 +162,14 @@ private record RegistrationByRef(@Nonnull Ref bodyRef, } private record PublishedRegistrations( + long registrationTopologyGeneration, @Nonnull List views, @Nonnull Map viewsByUuid, @Nonnull Int2ObjectOpenHashMap viewsByRowIndex) { private static final PublishedRegistrations EMPTY = - new PublishedRegistrations(List.of(), + new PublishedRegistrations(-1L, + List.of(), Map.of(), new Int2ObjectOpenHashMap<>()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java index aa5f0fda..25d9fa52 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java @@ -14,10 +14,13 @@ public final class PhysicsProfilingResource implements Resource { private boolean enabled; private long snapshotNanos; + private long registrationPublicationNanos; private long stepSubmitNanos; private int spaces; private int substeps; private int publishedBodies; + private int registrationPublicationRebuilds; + private int registrationPublicationSkips; private int schedulerSamples; private float schedulerInputDtSeconds; private float schedulerSubmittedDtSeconds; @@ -53,6 +56,13 @@ public void recordSnapshot(long snapshotNanos, int publishedBodies) { this.publishedBodies = Math.max(0, publishedBodies); } + public void recordRegistrationPublication(long registrationPublicationNanos, + boolean rebuilt) { + this.registrationPublicationNanos = Math.max(0L, registrationPublicationNanos); + registrationPublicationRebuilds = rebuilt ? 1 : 0; + registrationPublicationSkips = rebuilt ? 0 : 1; + } + public void recordStepScheduling(float inputDtSeconds, float submittedDtSeconds, float backlogDtSeconds, @@ -68,10 +78,13 @@ public void recordStepScheduling(float inputDtSeconds, public void reset() { snapshotNanos = 0L; + registrationPublicationNanos = 0L; stepSubmitNanos = 0L; spaces = 0; substeps = 0; publishedBodies = 0; + registrationPublicationRebuilds = 0; + registrationPublicationSkips = 0; schedulerSamples = 0; schedulerInputDtSeconds = 0.0f; schedulerSubmittedDtSeconds = 0.0f; @@ -87,7 +100,10 @@ public StepSample latestStepSample() { substeps, stepSubmitNanos, snapshotNanos, + registrationPublicationNanos, publishedBodies, + registrationPublicationRebuilds, + registrationPublicationSkips, schedulerSamples, schedulerInputDtSeconds, schedulerSubmittedDtSeconds, @@ -101,6 +117,10 @@ public long getSnapshotNanos() { return snapshotNanos; } + public long getRegistrationPublicationNanos() { + return registrationPublicationNanos; + } + public long getStepSubmitNanos() { return stepSubmitNanos; } @@ -117,6 +137,14 @@ public int getPublishedBodies() { return publishedBodies; } + public int getRegistrationPublicationRebuilds() { + return registrationPublicationRebuilds; + } + + public int getRegistrationPublicationSkips() { + return registrationPublicationSkips; + } + @Nonnull public PhysicsStepPhaseStats getNativePhaseStats() { return nativePhaseStats; @@ -128,10 +156,13 @@ public PhysicsProfilingResource clone() { PhysicsProfilingResource copy = new PhysicsProfilingResource(); copy.enabled = enabled; copy.snapshotNanos = snapshotNanos; + copy.registrationPublicationNanos = registrationPublicationNanos; copy.stepSubmitNanos = stepSubmitNanos; copy.spaces = spaces; copy.substeps = substeps; copy.publishedBodies = publishedBodies; + copy.registrationPublicationRebuilds = registrationPublicationRebuilds; + copy.registrationPublicationSkips = registrationPublicationSkips; copy.schedulerSamples = schedulerSamples; copy.schedulerInputDtSeconds = schedulerInputDtSeconds; copy.schedulerSubmittedDtSeconds = schedulerSubmittedDtSeconds; @@ -151,7 +182,10 @@ public record StepSample(int spaces, int substeps, long stepSubmitNanos, long snapshotNanos, + long registrationPublicationNanos, int publishedBodies, + int registrationPublicationRebuilds, + int registrationPublicationSkips, int schedulerSamples, float schedulerInputDtSeconds, float schedulerSubmittedDtSeconds, @@ -165,7 +199,10 @@ public record StepSample(int spaces, substeps = Math.max(0, substeps); stepSubmitNanos = Math.max(0L, stepSubmitNanos); snapshotNanos = Math.max(0L, snapshotNanos); + registrationPublicationNanos = Math.max(0L, registrationPublicationNanos); publishedBodies = Math.max(0, publishedBodies); + registrationPublicationRebuilds = Math.max(0, registrationPublicationRebuilds); + registrationPublicationSkips = Math.max(0, registrationPublicationSkips); schedulerSamples = Math.max(0, schedulerSamples); schedulerInputDtSeconds = safeDt(schedulerInputDtSeconds); schedulerSubmittedDtSeconds = safeDt(schedulerSubmittedDtSeconds); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index b6f163e2..0647d7fc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -123,6 +123,7 @@ public final class PhysicsRuntimeResource implements Resource { @Nonnull private final Int2ObjectOpenHashMap> pendingSpaceSettingsByRowIndex = new Int2ObjectOpenHashMap<>(); + private long registrationTopologyGeneration; @Setter @Getter private boolean started; @@ -200,6 +201,7 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { }); } removeTerrainHandlesForSpace(removed); + markRegistrationTopologyChanged(); } } @@ -218,6 +220,7 @@ public void putBodyHandle(@Nonnull UUID bodyUuid, .add(handle.value()); bodySnapshotMetadataByHandle.put(handle.value(), new BodySnapshotMetadata(bodyUuid, bodyRef, spaceUuid)); + markRegistrationTopologyChanged(); } @Nullable @@ -239,6 +242,7 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref BackendSpaceHandle spaceHandleByRef = bodySpaceHandlesByRowIndex.remove(rowIndex); removeBodyHandleIndexes(removed != null ? removed : removedByRef, spaceHandle != null ? spaceHandle : spaceHandleByRef); + markRegistrationTopologyChanged(); } @Nonnull @@ -431,6 +435,7 @@ public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, terrainVoxelBodyHandlesByRowIndex.put(rowIndex, handle); } } + markRegistrationTopologyChanged(); } public void putTerrainBodyHandle(@Nonnull Ref terrainRef, @@ -487,23 +492,30 @@ public void forEachTerrainBodyHandle(@Nonnull Ref terrainRef, } public void removeTerrainHandles(@Nonnull UUID terrainUuid) { + boolean changed = false; LongList bodyHandles = terrainBodyHandlesByUuid.remove(terrainUuid); if (bodyHandles != null) { bodyHandles.forEach(bodyHitMetadataByHandle::remove); + changed = true; } - terrainVoxelBodyHandlesByUuid.remove(terrainUuid); - terrainSpaceHandlesByUuid.remove(terrainUuid); - terrainPayloadKeysByUuid.remove(terrainUuid); + changed |= terrainVoxelBodyHandlesByUuid.remove(terrainUuid) != null; + changed |= terrainSpaceHandlesByUuid.remove(terrainUuid) != null; + changed |= terrainPayloadKeysByUuid.remove(terrainUuid) != null; Ref terrainRef = terrainRefsByUuid.remove(terrainUuid); if (terrainRef != null) { - removeTerrainRefMaps(terrainRef); + changed |= removeTerrainRefMaps(terrainRef); + } + if (changed) { + markRegistrationTopologyChanged(); } } public void removeTerrainHandles(@Nonnull UUID terrainUuid, @Nonnull Ref terrainRef) { removeTerrainHandles(terrainUuid); - removeTerrainRefMaps(terrainRef); + if (removeTerrainRefMaps(terrainRef)) { + markRegistrationTopologyChanged(); + } } public void removeTerrainHandles(@Nonnull Ref terrainRef, @@ -586,6 +598,7 @@ public void clear() { pendingBodyOperations.clear(); pendingSpaceSettingsByRowIndex.clear(); started = false; + markRegistrationTopologyChanged(); } public void clearTransientBodyOperations() { @@ -741,6 +754,7 @@ public PhysicsRuntimeResource clone() { copy.bodySnapshotMetadataByHandle.putAll(bodySnapshotMetadataByHandle); copy.pendingBodyOperations.addAll(pendingBodyOperations); copy.pendingSpaceSettingsByRowIndex.putAll(pendingSpaceSettingsByRowIndex); + copy.registrationTopologyGeneration = registrationTopologyGeneration; copy.started = started; return copy; } @@ -874,8 +888,16 @@ public enum Kind { } } + public long getRegistrationTopologyGeneration() { + return registrationTopologyGeneration; + } + + private void markRegistrationTopologyChanged() { + registrationTopologyGeneration++; + } + private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandle) { - terrainSpaceHandlesByUuid.entrySet().removeIf(entry -> { + boolean removedAny = terrainSpaceHandlesByUuid.entrySet().removeIf(entry -> { if (entry.getValue().value() != spaceHandle.value()) { return false; } @@ -893,6 +915,9 @@ private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandl } return true; }); + if (removedAny) { + markRegistrationTopologyChanged(); + } } private void bindTerrainRef(@Nonnull UUID terrainUuid, @@ -902,16 +927,19 @@ private void bindTerrainRef(@Nonnull UUID terrainUuid, } Ref previousRef = terrainRefsByUuid.put(terrainUuid, terrainRef); if (previousRef != null && !sameRef(previousRef, terrainRef)) { - removeTerrainRefMaps(previousRef); + if (removeTerrainRefMaps(previousRef)) { + markRegistrationTopologyChanged(); + } } } - private void removeTerrainRefMaps(@Nonnull Ref terrainRef) { + private boolean removeTerrainRefMaps(@Nonnull Ref terrainRef) { int rowIndex = terrainRef.getIndex(); - terrainBodyHandlesByRowIndex.remove(rowIndex); - terrainVoxelBodyHandlesByRowIndex.remove(rowIndex); - terrainSpaceHandlesByRowIndex.remove(rowIndex); - terrainPayloadKeysByRowIndex.remove(rowIndex); + boolean changed = terrainBodyHandlesByRowIndex.remove(rowIndex) != null; + changed |= terrainVoxelBodyHandlesByRowIndex.remove(rowIndex) != null; + changed |= terrainSpaceHandlesByRowIndex.remove(rowIndex) != null; + changed |= terrainPayloadKeysByRowIndex.remove(rowIndex) != null; + return changed; } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java index 37cff850..0c482705 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java @@ -203,6 +203,40 @@ public synchronized void recordStep(int spaces, int preStepDrainedMutations, long preStepDrainRunNanos, int lateMutationBacklogAtStep) { + recordStep(spaces, + substeps, + nanos, + bodySnapshots, + spatialIndexCells, + snapshotNanos, + storeTickQueuedNanos, + storeTickRunNanos, + storeTickCompletedNanos, + nativePhaseStats, + preStepDrainedMutations, + preStepDrainRunNanos, + lateMutationBacklogAtStep, + 0L, + 0, + 0); + } + + public synchronized void recordStep(int spaces, + int substeps, + long nanos, + int bodySnapshots, + int spatialIndexCells, + long snapshotNanos, + long storeTickQueuedNanos, + long storeTickRunNanos, + long storeTickCompletedNanos, + @Nonnull PhysicsStepPhaseStats nativePhaseStats, + int preStepDrainedMutations, + long preStepDrainRunNanos, + int lateMutationBacklogAtStep, + long registrationPublicationNanos, + int registrationPublicationRebuilds, + int registrationPublicationSkips) { StepSnapshot snapshot = new StepSnapshot(); snapshot.recordTickSample(); snapshot.setSpaces(spaces); @@ -215,6 +249,9 @@ public synchronized void recordStep(int spaces, snapshot.setStoreTickRunNanos(storeTickRunNanos); snapshot.recordStoreTickStepInterval(recordStoreTickStepInterval(storeTickCompletedNanos)); snapshot.setNativePhaseStats(nativePhaseStats); + snapshot.recordRegistrationPublication(registrationPublicationNanos, + registrationPublicationRebuilds, + registrationPublicationSkips); snapshot.recordPreStepDrain(Math.max(0, preStepDrainedMutations), Math.max(0L, preStepDrainRunNanos), Math.max(0, lateMutationBacklogAtStep)); @@ -414,6 +451,9 @@ public static final class StepSnapshot { private long tickNanos; @Setter private long snapshotNanos; + private long registrationPublicationNanos; + private int registrationPublicationRebuilds; + private int registrationPublicationSkips; @Setter private long storeTickQueuedNanos; @Setter @@ -461,6 +501,9 @@ public void copyFrom(@Nonnull StepSnapshot other) { spatialIndexCells = other.spatialIndexCells; tickNanos = other.tickNanos; snapshotNanos = other.snapshotNanos; + registrationPublicationNanos = other.registrationPublicationNanos; + registrationPublicationRebuilds = other.registrationPublicationRebuilds; + registrationPublicationSkips = other.registrationPublicationSkips; storeTickQueuedNanos = other.storeTickQueuedNanos; storeTickRunNanos = other.storeTickRunNanos; preStepDrainedMutations = other.preStepDrainedMutations; @@ -499,6 +542,9 @@ public void add(@Nonnull StepSnapshot other) { spatialIndexCells += other.spatialIndexCells; tickNanos += other.tickNanos; snapshotNanos += other.snapshotNanos; + registrationPublicationNanos += other.registrationPublicationNanos; + registrationPublicationRebuilds += other.registrationPublicationRebuilds; + registrationPublicationSkips += other.registrationPublicationSkips; storeTickQueuedNanos += other.storeTickQueuedNanos; storeTickRunNanos += other.storeTickRunNanos; preStepDrainedMutations += other.preStepDrainedMutations; @@ -542,6 +588,9 @@ public void reset() { spatialIndexCells = 0; tickNanos = 0L; snapshotNanos = 0L; + registrationPublicationNanos = 0L; + registrationPublicationRebuilds = 0; + registrationPublicationSkips = 0; storeTickQueuedNanos = 0L; storeTickRunNanos = 0L; preStepDrainedMutations = 0; @@ -594,6 +643,14 @@ public void recordPreStepDrain(int drainedMutations, maxLateMutationBacklogAtStep = lateMutationBacklogAtStep; } + public void recordRegistrationPublication(long nanos, + int rebuilds, + int skips) { + registrationPublicationNanos = Math.max(0L, nanos); + registrationPublicationRebuilds = Math.max(0, rebuilds); + registrationPublicationSkips = Math.max(0, skips); + } + private void retainPreStepDrainMaxima(@Nonnull StepSnapshot snapshot) { retainPreStepDrainMaxima(snapshot.maxPreStepDrainedMutations, snapshot.maxLateMutationBacklogAtStep); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 62a7607a..ebc51115 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -63,6 +63,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = store.getResource( + PhysicsBodyRegistrationResource.getResourceType()); PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); @@ -85,12 +87,13 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) frameDt, bodies); snapshot.publish(frame); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .publish(collectRegistrationViews(store, - systemIndex, - runtime, - compatibility, - snapshot)); + publishRegistrationViews(store, + systemIndex, + runtime, + compatibility, + snapshot, + registrations, + profiling); profiling.recordSnapshot(completed.snapshotNanos(), bodies.size()); store.getResource(PhysicsEventResource.getResourceType()) .publishStepFrame(frame.sequence(), @@ -102,6 +105,31 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) completed.droppedBackendEventCount()); } + private static void publishRegistrationViews(@Nonnull Store store, + int systemIndex, + @Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull PhysicsSnapshotResource snapshot, + @Nonnull PhysicsBodyRegistrationResource registrations, + @Nonnull PhysicsProfilingResource profiling) { + long generation = runtime.getRegistrationTopologyGeneration(); + if (registrations.isCurrent(generation)) { + profiling.recordRegistrationPublication(0L, false); + return; + } + long startNanos = profiling.isEnabled() ? System.nanoTime() : 0L; + registrations.publish(generation, + collectRegistrationViews(store, + systemIndex, + runtime, + compatibility, + snapshot)); + long publicationNanos = profiling.isEnabled() + ? System.nanoTime() - startNanos + : 0L; + profiling.recordRegistrationPublication(publicationNanos, true); + } + @Nonnull private static List collectRegistrationViews( @Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index c84ce06e..b5051d5c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -83,7 +83,13 @@ private static void recordProfiling(@Nonnull Store store, 0L, 0L, System.nanoTime(), - sample.nativePhaseStats()); + sample.nativePhaseStats(), + 0, + 0L, + 0, + sample.registrationPublicationNanos(), + sample.registrationPublicationRebuilds(), + sample.registrationPublicationSkips()); if (sample.schedulerSamples() > 0) { runtimeProfiling.recordStepScheduling(sample.schedulerInputDtSeconds(), sample.schedulerSubmittedDtSeconds(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java index 37388e32..62245d2f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java @@ -98,6 +98,18 @@ public long getSnapshotNanos() { return snapshot.getSnapshotNanos(); } + public long getRegistrationPublicationNanos() { + return snapshot.getRegistrationPublicationNanos(); + } + + public int getRegistrationPublicationRebuilds() { + return snapshot.getRegistrationPublicationRebuilds(); + } + + public int getRegistrationPublicationSkips() { + return snapshot.getRegistrationPublicationSkips(); + } + public long getStoreTickQueuedNanos() { return snapshot.getStoreTickQueuedNanos(); } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java index 40a71b76..a8979562 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java @@ -92,6 +92,11 @@ private static void sendReport(@Nonnull CommandContext ctx, + " indexCells=" + formatAverage(cumulativeStep.getSpatialIndexCells(), cumulativeStep.getTickSamples()))); ctx.sender().sendMessage(Message.raw("Physics snapshot avg ms/completedStep=" + formatAverageMillis(cumulativeStep.getSnapshotNanos(), cumulativeStep.getTickSamples()))); + ctx.sender().sendMessage(Message.raw("Physics registration publication avg ms/completedStep=" + + formatAverageMillis(cumulativeStep.getRegistrationPublicationNanos(), + cumulativeStep.getTickSamples()) + + " rebuilds/skips=" + cumulativeStep.getRegistrationPublicationRebuilds() + + "/" + cumulativeStep.getRegistrationPublicationSkips())); ctx.sender().sendMessage(Message.raw("Physics store tick avg queued/run/latency ms=" + formatAverageMillis(cumulativeStep.getStoreTickQueuedNanos(), cumulativeStep.getTickSamples()) + "/" + formatAverageMillis(cumulativeStep.getStoreTickRunNanos(), cumulativeStep.getTickSamples()) @@ -137,6 +142,12 @@ private static void sendReport(@Nonnull CommandContext ctx, + "/" + latestStep.getSpatialIndexCells() + " snapshot latest/worst ms=" + formatMillis(latestStep.getSnapshotNanos()) + "/" + formatMillis(worstStep.getSnapshotNanos()) + + " registration latest/worst ms=" + + formatMillis(latestStep.getRegistrationPublicationNanos()) + + "/" + formatMillis(worstStep.getRegistrationPublicationNanos()) + + " registration latest rebuilds/skips=" + + latestStep.getRegistrationPublicationRebuilds() + + "/" + latestStep.getRegistrationPublicationSkips() + " pendingAge latest/max ms=" + formatMillis(latestStep.getPendingStepAgeNanos()) + "/" + formatMillis(worstStep.getMaxPendingStepAgeNanos()))); if (cumulativeStep.getNativePhaseSamples() > 0) { From ff48e71411e909cc1862c4425105ea00a52df81d Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 14:14:14 +0200 Subject: [PATCH 343/534] refactor(core): own physics store terrain registration Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 21 ++++ .../resources/PhysicsResourceTypes.java | 9 ++ .../components/PhysicsComponentTypes.java | 24 +++++ .../components/TerrainColliderComponent.java | 3 +- .../components/WorldCollisionComponent.java | 3 +- .../physicschunk/PhysicsChunkTypes.java | 98 +------------------ .../ImpulsePhysicsChunkPlugin.java | 9 -- 7 files changed, 57 insertions(+), 110 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index d4e47b64..b28dac11 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -20,7 +20,10 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.TickDecision; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; import dev.hytalemodding.impulse.core.internal.systems.ColliderBindingSystem; @@ -35,6 +38,9 @@ import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; +import dev.hytalemodding.impulse.core.internal.systems.WorldCollisionIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; @@ -66,13 +72,16 @@ public static void register(@Nonnull ComponentRegistryProxy regist PhysicsResourceTypes.registerResourceTypes(registry); registry.registerSystem(new PersistenceHydrationSystem()); + registry.registerSystem(new TerrainMutationDrainSystem()); registry.registerSystem(new IdentityIndexSystem()); + registry.registerSystem(new WorldCollisionIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); registry.registerSystem(new BodyBindingSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); + registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); @@ -97,6 +106,18 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic () -> cleanupResource(store, PhysicsRuntimeResource.getResourceType(), PhysicsRuntimeResource::destroyBackendBindings)); + failure = runShutdownCleanup(failure, + () -> cleanupResource(store, + PhysicsTerrainMutationQueueResource.getResourceType(), + PhysicsTerrainMutationQueueResource::clear)); + failure = runShutdownCleanup(failure, + () -> cleanupResource(store, + PhysicsTerrainPayloadResource.getResourceType(), + PhysicsTerrainPayloadResource::clear)); + failure = runShutdownCleanup(failure, + () -> cleanupResource(store, + PhysicsWorldCollisionIndexResource.getResourceType(), + PhysicsWorldCollisionIndexResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsIdentityIndexResource.getResourceType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 59a0350a..fe0aaf58 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -87,6 +87,15 @@ public static void registerResourceTypes( debugResourceType = registry.registerResource( dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource.class, dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource::new); + PhysicsTerrainMutationQueueResource.setResourceType(registry.registerResource( + PhysicsTerrainMutationQueueResource.class, + PhysicsTerrainMutationQueueResource::new)); + PhysicsTerrainPayloadResource.setResourceType(registry.registerResource( + PhysicsTerrainPayloadResource.class, + PhysicsTerrainPayloadResource::new)); + PhysicsWorldCollisionIndexResource.setResourceType(registry.registerResource( + PhysicsWorldCollisionIndexResource.class, + PhysicsWorldCollisionIndexResource::new)); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 049265d9..668c81cf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -20,6 +20,10 @@ public final class PhysicsComponentTypes { @Nullable private static ComponentType bodyCommandComponentType; @Nullable + private static ComponentType terrainColliderComponentType; + @Nullable + private static ComponentType worldCollisionComponentType; + @Nullable private static ComponentType dynamicsComponentType; @Nullable private static ComponentType colliderComponentType; @@ -66,6 +70,14 @@ public static void registerComponentTypes( BodyCommandComponent.class, "BodyCommand", BodyCommandComponent.CODEC); + terrainColliderComponentType = registry.registerComponent( + TerrainColliderComponent.class, + "TerrainCollider", + TerrainColliderComponent.CODEC); + worldCollisionComponentType = registry.registerComponent( + WorldCollisionComponent.class, + "WorldCollision", + WorldCollisionComponent.CODEC); dynamicsComponentType = registry.registerComponent( DynamicsComponent.class, "Dynamics", @@ -136,6 +148,18 @@ public static ComponentType bodyCommandCompo return bodyCommandComponentType; } + @Nonnull + public static ComponentType + terrainColliderComponentType() { + return terrainColliderComponentType; + } + + @Nonnull + public static ComponentType + worldCollisionComponentType() { + return worldCollisionComponentType; + } + @Nonnull public static ComponentType dynamicsComponentType() { return dynamicsComponentType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java index 0ca25580..3c3fec72 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import lombok.Getter; import lombok.Setter; import java.util.Objects; @@ -133,7 +132,7 @@ public void setPayloadResourceKey(@Nonnull String payloadResourceKey) { @Nonnull public static ComponentType getComponentType() { - return PhysicsChunkTypes.terrainColliderComponentType(); + return PhysicsComponentTypes.terrainColliderComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java index 4bf09ec0..b877e74c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -219,7 +218,7 @@ public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { @Nonnull public static ComponentType getComponentType() { - return PhysicsChunkTypes.worldCollisionComponentType(); + return PhysicsComponentTypes.worldCollisionComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java index 2cf6eea0..c468917f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java @@ -1,80 +1,20 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; -import dev.hytalemodding.impulse.core.internal.systems.WorldCollisionIndexSystem; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; -import java.util.function.Consumer; import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** - * Registered PhysicsStore type handles owned by the PhysicsChunk integration module. + * Registered EntityStore type handles owned by the PhysicsChunk integration module. */ public final class PhysicsChunkTypes { - @Nullable - private static ComponentType terrainColliderComponentType; - @Nullable - private static ComponentType worldCollisionComponentType; private PhysicsChunkTypes() { } - public static void registerComponentTypes( - @Nonnull ComponentRegistryProxy registry) { - terrainColliderComponentType = registry.registerComponent( - TerrainColliderComponent.class, - "TerrainCollider", - TerrainColliderComponent.CODEC); - worldCollisionComponentType = registry.registerComponent( - WorldCollisionComponent.class, - "WorldCollision", - WorldCollisionComponent.CODEC); - } - - public static void registerResourceTypes( - @Nonnull ComponentRegistryProxy registry) { - PhysicsTerrainMutationQueueResource.setResourceType(registry.registerResource( - PhysicsTerrainMutationQueueResource.class, - PhysicsTerrainMutationQueueResource::new)); - PhysicsTerrainPayloadResource.setResourceType(registry.registerResource( - PhysicsTerrainPayloadResource.class, - PhysicsTerrainPayloadResource::new)); - PhysicsWorldCollisionIndexResource.setResourceType(registry.registerResource( - PhysicsWorldCollisionIndexResource.class, - PhysicsWorldCollisionIndexResource::new)); - } - - public static void registerSystems(@Nonnull ComponentRegistryProxy registry) { - registry.registerSystem(new TerrainMutationDrainSystem()); - registry.registerSystem(new WorldCollisionIndexSystem()); - registry.registerSystem(new TerrainColliderBindingSystem()); - } - - public static void registerPhysicsStoreTypes(@Nonnull PluginBase plugin) { - ComponentRegistryProxy registry = - PhysicsStoreRegistration.physicsStoreRegistry(plugin); - registerComponentTypes(registry); - registerResourceTypes(registry); - registerSystems(registry); - } - public static void registerEntityStoreResourceTypes( @Nonnull ComponentRegistryProxy registry) { WorldCollisionProfilingResource.setResourceType(registry.registerResource( @@ -94,40 +34,4 @@ public static void clearEntityStoreResourceTypes() { WorldCollisionProfilingResource.clearResourceType(); PhysicsStoreWorldCollisionStreamingResource.clearResourceType(); } - - public static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physicsStore) { - Store store = physicsStore.getStore(); - if (store.isShutdown()) { - return; - } - cleanupResource(store, - PhysicsTerrainMutationQueueResource.getResourceType(), - PhysicsTerrainMutationQueueResource::clear); - cleanupResource(store, - PhysicsTerrainPayloadResource.getResourceType(), - PhysicsTerrainPayloadResource::clear); - cleanupResource(store, - PhysicsWorldCollisionIndexResource.getResourceType(), - PhysicsWorldCollisionIndexResource::clear); - } - - private static > void cleanupResource( - @Nonnull Store store, - @Nonnull ResourceType type, - @Nonnull Consumer cleanup) { - cleanup.accept(store.getResource(type)); - } - - @Nonnull - public static ComponentType - terrainColliderComponentType() { - return terrainColliderComponentType; - } - - @Nonnull - public static ComponentType - worldCollisionComponentType() { - return worldCollisionComponentType; - } - } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java index 6cf67bfe..14cf162d 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java @@ -5,12 +5,9 @@ import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; -import dev.hytalemodding.impulse.early.PhysicsStoreHooks; -import java.util.function.Consumer; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -20,8 +17,6 @@ public final class ImpulsePhysicsChunkPlugin extends JavaPlugin { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final Consumer SHUTDOWN_CLEANUP = - PhysicsChunkTypes::clearRuntimeStateBeforeShutdown; public ImpulsePhysicsChunkPlugin(@Nonnull JavaPluginInit init) { super(init); @@ -29,9 +24,6 @@ public ImpulsePhysicsChunkPlugin(@Nonnull JavaPluginInit init) { @Override protected void setup() { - PhysicsChunkTypes.registerPhysicsStoreTypes(this); - PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); - ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); @@ -44,7 +36,6 @@ protected void setup() { protected void shutdown() { PhysicsWorldCollision.disableModule(); WorldCollisionCommandContributions.unregister(); - PhysicsStoreHooks.unregisterShutdownHook(SHUTDOWN_CLEANUP); PhysicsChunkTypes.clearEntityStoreResourceTypes(); } } From b67a7a78616f5001afe7080b058bfaaf4aef5499 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 14:30:47 +0200 Subject: [PATCH 344/534] refactor(core): harden physics entity module lifecycle Signed-off-by: Blovien --- .../crucible/ImpulseLiveCrucibleTests.java | 4 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 6 +-- .../diagnostics/PhysicsEntityDiagnostics.java | 42 +++++++++---------- .../resources/PhysicsDebugResource.java | 4 ++ .../PhysicsProjectionIndexResource.java | 4 ++ .../PhysicsRuntimeProfilingResource.java | 4 ++ .../systems/debug/PhysicsDebugSystem.java | 25 +++++++---- .../PhysicsBodyAttachmentIndexSystem.java | 22 +++++++--- .../systems/sync/PhysicsSyncSystem.java | 31 ++++++++++---- .../visual/GeneratedProxyLifecycle.java | 6 +-- .../modules/physicschunk/package-info.java | 4 ++ .../physicsentity/PhysicsEntityTypes.java | 11 +++++ .../components/package-info.java | 4 ++ .../modules/physicsentity/package-info.java | 4 ++ impulse-core/src/module-info/module-info.java | 2 +- .../ImpulsePhysicsEntityPlugin.java | 5 +++ 16 files changed, 123 insertions(+), 55 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index affb9ca6..5f282206 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -48,8 +48,6 @@ final class ImpulseLiveCrucibleTests { TransformComponent.getComponentType(); private static final ComponentType DESPAWN_TYPE = DespawnComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); private ImpulseLiveCrucibleTests() { } @@ -180,7 +178,7 @@ private static Ref spawnLiveBlockBody(Store store, DEFAULT_BLOCK_TYPE, new Vector3d(visualPosition)); holder.removeComponent(DESPAWN_TYPE); - holder.addComponent(ATTACHMENT_TYPE, + holder.addComponent(BodyAttachmentComponent.getComponentType(), new BodyAttachmentComponent(bodyUuid, TransformAuthority.BODY, AttachmentLifecycle.EXTERNAL_ENTITY)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index f20ee9be..14b0619e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -83,8 +83,6 @@ final class ImpulseRapierBodyBenchmarkCrucibleTests { private static final float BODY_VOID_Y = -128.0f; private static final double DETACHED_SPACING = 1.5; private static final Vector3d ORIGIN = new Vector3d(0.0, 128.0, 0.0); - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); private ImpulseRapierBodyBenchmarkCrucibleTests() { } @@ -389,7 +387,9 @@ private void restoreSettings() { } private void removeBenchmarkEntities() { - store.forEachEntityParallel(ATTACHMENT_TYPE, + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); + store.forEachEntityParallel(attachmentType, (index, archetypeChunk, commandBuffer) -> commandBuffer.removeEntity( archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java index bf0fd26d..3ce78218 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java @@ -18,16 +18,6 @@ */ public final class PhysicsEntityDiagnostics { - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); - private static final ComponentType NETWORK_ID_TYPE = - NetworkId.getComponentType(); - private static final ComponentType VISIBLE_TYPE = - Visible.getComponentType(); - private static final ComponentType ENTITY_VIEWER_TYPE = - EntityViewer.getComponentType(); private static final int BODY_PHYSICS_BODIES = 0; private static final int BODY_WITH_TRANSFORM = 1; private static final int BODY_WITH_NETWORK_ID = 2; @@ -43,23 +33,33 @@ private PhysicsEntityDiagnostics() { @Nonnull public static Snapshot collect(@Nonnull Store store) { + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); + ComponentType transformType = + TransformComponent.getComponentType(); + ComponentType networkIdType = + NetworkId.getComponentType(); + ComponentType visibleType = + Visible.getComponentType(); + ComponentType entityViewerType = + EntityViewer.getComponentType(); EntityFootprint bodyFootprint = collectBodyFootprint(store, - ATTACHMENT_TYPE, - TRANSFORM_TYPE, - NETWORK_ID_TYPE, - VISIBLE_TYPE); + attachmentType, + transformType, + networkIdType, + visibleType); VisualFootprint visualFootprint = collectVisualFootprint(store, - ATTACHMENT_TYPE, - TRANSFORM_TYPE, - NETWORK_ID_TYPE); + attachmentType, + transformType, + networkIdType); return new Snapshot(bodyFootprint.physicsBodies(), 0, visualFootprint.visuals(), - count(store, TRANSFORM_TYPE), - count(store, NETWORK_ID_TYPE), - count(store, VISIBLE_TYPE), - count(store, ENTITY_VIEWER_TYPE), + count(store, transformType), + count(store, networkIdType), + count(store, visibleType), + count(store, entityViewerType), bodyFootprint.withTransform(), bodyFootprint.withNetworkId(), bodyFootprint.withVisible(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index 4f1e3b10..c87a62e9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -177,6 +177,10 @@ public static void setResourceType( resourceType = type; } + public static void clearResourceType() { + resourceType = null; + } + private static float clampRefresh(float value) { return Math.clamp(value, MIN_REFRESH_SECONDS, MAX_REFRESH_SECONDS); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index 7d31df56..d08cdbef 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -286,6 +286,10 @@ public static void setResourceType( resourceType = type; } + public static void clearResourceType() { + resourceType = null; + } + private void unregisterAttachmentRef(@Nonnull Ref bodyRef, @Nonnull Ref attachment) { int rowIndex = bodyRef.getIndex(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java index 0c482705..41cd602f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java @@ -414,6 +414,10 @@ public static void setResourceType( resourceType = type; } + public static void clearResourceType() { + resourceType = null; + } + @Nonnull private static StepSnapshot copy(@Nonnull StepSnapshot source) { StepSnapshot copy = new StepSnapshot(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index e007592a..7e000fd6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -59,11 +59,10 @@ */ public class PhysicsDebugSystem extends TickingSystem { - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); - + @Nonnull + private final ComponentType attachmentType; + @Nonnull + private final ComponentType transformType; @Nonnull private final Map, DebugQueryCache> queryCachesByStore = Collections.synchronizedMap(new WeakHashMap<>()); @@ -72,6 +71,16 @@ public class PhysicsDebugSystem extends TickingSystem { new SystemDependency<>(Order.AFTER, UpdateLocationSystems.TickingSystem.class) ); + public PhysicsDebugSystem() { + this(BodyAttachmentComponent.getComponentType(), TransformComponent.getComponentType()); + } + + PhysicsDebugSystem(@Nonnull ComponentType attachmentType, + @Nonnull ComponentType transformType) { + this.attachmentType = Objects.requireNonNull(attachmentType, "attachmentType"); + this.transformType = Objects.requireNonNull(transformType, "transformType"); + } + @Override public Set> getDependencies() { return dependencies; @@ -206,7 +215,7 @@ private static List resolveSubscribers(@Nonnull World world, return viewers; } - private static int renderEntityBodies(@Nonnull Collection viewers, + private int renderEntityBodies(@Nonnull Collection viewers, @Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d viewerPosition, @@ -232,8 +241,8 @@ private static int renderEntityBodies(@Nonnull Collection viewers, continue; } BodyAttachmentComponent attachment = store.getComponent(attachmentRef, - ATTACHMENT_TYPE); - TransformComponent transform = store.getComponent(attachmentRef, TRANSFORM_TYPE); + attachmentType); + TransformComponent transform = store.getComponent(attachmentRef, transformType); if (attachment == null || attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY || transform == null) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java index 54bc21a6..fa7461e5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -11,6 +11,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -21,9 +22,20 @@ public class PhysicsBodyAttachmentIndexSystem extends RefChangeSystem { - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private static final Query QUERY = ATTACHMENT_TYPE; + @Nonnull + private final ComponentType attachmentType; + @Nonnull + private final Query query; + + public PhysicsBodyAttachmentIndexSystem() { + this(BodyAttachmentComponent.getComponentType()); + } + + PhysicsBodyAttachmentIndexSystem( + @Nonnull ComponentType attachmentType) { + this.attachmentType = Objects.requireNonNull(attachmentType, "attachmentType"); + this.query = attachmentType; + } @Override public void onComponentAdded(@Nonnull Ref ref, @@ -122,12 +134,12 @@ private static boolean sameRef(@Nullable Ref first, @Nonnull @Override public ComponentType componentType() { - return ATTACHMENT_TYPE; + return attachmentType; } @Nonnull @Override public Query getQuery() { - return QUERY; + return query; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 1cad9256..15595d79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -32,6 +32,7 @@ import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.List; +import java.util.Objects; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -51,12 +52,12 @@ */ public class PhysicsSyncSystem extends EntityTickingSystem { - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); - - private static final Query QUERY = Query.and(ATTACHMENT_TYPE, TRANSFORM_TYPE); + @Nonnull + private final ComponentType attachmentType; + @Nonnull + private final ComponentType transformType; + @Nonnull + private final Query query; private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, PhysicsEntityTypes.persistenceRestoreGroup()), new SystemDependency<>(Order.AFTER, PhysicsGeneratedProxyCleanupSystem.class), @@ -89,6 +90,18 @@ public class PhysicsSyncSystem extends EntityTickingSystem { */ private final ThreadLocal scratch = ThreadLocal.withInitial(Scratch::new); + public PhysicsSyncSystem() { + this(BodyAttachmentComponent.getComponentType(), TransformComponent.getComponentType()); + } + + PhysicsSyncSystem( + @Nonnull ComponentType attachmentType, + @Nonnull ComponentType transformType) { + this.attachmentType = Objects.requireNonNull(attachmentType, "attachmentType"); + this.transformType = Objects.requireNonNull(transformType, "transformType"); + this.query = Query.and(attachmentType, transformType); + } + @Override public boolean isParallel(int archetypeChunkSize, int taskCount) { // Backend bodies and per-body sync state are owned by the world tick thread. @@ -125,8 +138,8 @@ public void tick(float dt, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { Ref entityRef = chunk.getReferenceTo(index); - BodyAttachmentComponent attachment = chunk.getComponent(index, ATTACHMENT_TYPE); - TransformComponent transform = chunk.getComponent(index, TRANSFORM_TYPE); + BodyAttachmentComponent attachment = chunk.getComponent(index, attachmentType); + TransformComponent transform = chunk.getComponent(index, transformType); if (attachment == null || transform == null) { return; } @@ -401,7 +414,7 @@ private PhysicsRuntimeProfilingResource.SyncCollector getSyncCollector( @Nonnull @Override public Query getQuery() { - return QUERY; + return query; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index cc0bdc96..c6349adb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -2,7 +2,6 @@ import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; @@ -21,9 +20,6 @@ */ public final class GeneratedProxyLifecycle { - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private GeneratedProxyLifecycle() { } @@ -53,7 +49,7 @@ public static void clearMissingAttachment(@Nonnull Ref entityRef, } else if (attachment.shouldRemoveEntityWhenBodyMissing()) { removeEntity(commandBuffer, entityRef); } else { - commandBuffer.removeComponent(entityRef, ATTACHMENT_TYPE); + commandBuffer.removeComponent(entityRef, BodyAttachmentComponent.getComponentType()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java new file mode 100644 index 00000000..6857c839 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java @@ -0,0 +1,4 @@ +/** + * Optional ChunkStore world-collision integration for authoritative PhysicsStore terrain. + */ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index df4d8f9b..f11e7f99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -86,6 +86,17 @@ public static void registerSystems(@Nonnull ComponentRegistryProxy registry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); } + public static void clearEntityStoreTypes() { + bodyAttachmentComponentType = null; + generatedVisualProxyComponentType = null; + physicsWorldResourceType = null; + physicsEventFramePublishedEventType = null; + persistenceRestoreGroup = null; + PhysicsDebugResource.clearResourceType(); + PhysicsRuntimeProfilingResource.clearResourceType(); + PhysicsProjectionIndexResource.clearResourceType(); + } + @Nonnull public static ComponentType bodyAttachmentComponentType() { return bodyAttachmentComponentType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java new file mode 100644 index 00000000..aefad673 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java @@ -0,0 +1,4 @@ +/** + * EntityStore components that bind Hytale entities to authoritative PhysicsStore body entities. + */ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java new file mode 100644 index 00000000..99a0a407 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java @@ -0,0 +1,4 @@ +/** + * Optional EntityStore projection, sync, debug, and profiling integration for PhysicsStore bodies. + */ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index f35aa2c3..c824d94a 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -10,10 +10,10 @@ exports dev.hytalemodding.impulse.core.plugin.events; exports dev.hytalemodding.impulse.core.plugin.modules.control; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity; + exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk; exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physicsstore; - exports dev.hytalemodding.impulse.core.plugin.projection; exports dev.hytalemodding.impulse.core.plugin.resources; exports dev.hytalemodding.impulse.core.plugin.settings; exports dev.hytalemodding.impulse.core.plugin.simulation; diff --git a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java index 003671a1..e8c72e67 100644 --- a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java +++ b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java @@ -25,4 +25,9 @@ protected void setup() { PhysicsEntityTypes.registerSystemGroups(entityRegistry); PhysicsEntityTypes.registerSystems(entityRegistry); } + + @Override + protected void shutdown() { + PhysicsEntityTypes.clearEntityStoreTypes(); + } } From 18f9e5ea18c94457a13ccf0bee98d0e6345ad791 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 14:41:52 +0200 Subject: [PATCH 345/534] refactor(examples): avoid cached physics entity component handles Signed-off-by: Blovien --- .../impulse/examples/utils/ExamplePhysicsUtils.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index a4846f95..e3cb25e9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.examples.utils; import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -47,8 +46,6 @@ public final class ExamplePhysicsUtils { public static final String DEFAULT_BLOCK_TYPE = PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); private ExamplePhysicsUtils() { } @@ -820,7 +817,7 @@ public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store holder = blockEntityHolder(time, blockType, visualPosition); - holder.addComponent(ATTACHMENT_TYPE, + holder.addComponent(BodyAttachmentComponent.getComponentType(), BodyAttachmentComponent.externalEntity(bodyUuid)); return store.addEntity(holder, AddReason.SPAWN); } @@ -853,7 +850,7 @@ public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull float visualOriginOffsetY, boolean controllable) { Holder holder = blockEntityHolder(time, blockType, visualPosition); - holder.addComponent(ATTACHMENT_TYPE, + holder.addComponent(BodyAttachmentComponent.getComponentType(), BodyAttachmentComponent.impulseOwnedVisual(physicsBodyUuid, localPositionOffset, localRotationOffset, From b41bd2ef561721925446ded6b517633866413b3b Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 14:47:59 +0200 Subject: [PATCH 346/534] refactor(examples): avoid cached physics entity handles in systems Signed-off-by: Blovien --- .../examples/commands/GrabCommand.java | 6 ++--- ...nchmarkEntityRemovalDiagnosticsSystem.java | 24 ++++++++++++----- .../systems/ExplosiveFuseContactSystem.java | 9 ++++--- .../systems/ExplosiveFuseTickSystem.java | 27 ++++++++++++++----- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 8a8b95a5..e9832cd5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -55,8 +55,6 @@ public class GrabCommand extends AbstractAsyncPlayerCommand { private static final double RAY_LENGTH = 24.0; private static final float MIN_HOLD_DISTANCE = 4.0f; private static final Vector3f VIEW_OFFSET = new Vector3f(0.85f, -0.35f, 0.0f); - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); private final OptionalArg spaceArg = this.withOptionalArg( "space", "Physics space id to target", @@ -296,8 +294,10 @@ private static AttachmentSelection inspectGameplayAttachments(@Nonnull Store controllableType, @Nonnull Ref bodyRef) { boolean hasGameplayAttachment = false; + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); for (Ref attachmentRef : PhysicsEntityAttachments.attachments(store, bodyRef)) { - BodyAttachmentComponent attachment = store.getComponent(attachmentRef, ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = store.getComponent(attachmentRef, attachmentType); if (attachment == null || attachment.getLifecycle() == BodyAttachmentComponent.AttachmentLifecycle.GENERATED_PROXY) { continue; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java index 382a3e19..f98cf4e9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/BenchmarkEntityRemovalDiagnosticsSystem.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; @@ -20,13 +21,24 @@ public final class BenchmarkEntityRemovalDiagnosticsSystem extends RefSystem { - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); - private static final Query QUERY = ATTACHMENT_TYPE; - private static final AtomicInteger PHYSICS_ENTITY_REMOVALS = new AtomicInteger(); private static final ConcurrentMap REMOVALS_BY_REASON = new ConcurrentHashMap<>(); + @Nonnull + private final ComponentType attachmentType; + @Nonnull + private final Query query; + + public BenchmarkEntityRemovalDiagnosticsSystem() { + this(BodyAttachmentComponent.getComponentType()); + } + + BenchmarkEntityRemovalDiagnosticsSystem( + @Nonnull ComponentType attachmentType) { + this.attachmentType = Objects.requireNonNull(attachmentType, "attachmentType"); + this.query = attachmentType; + } + public static void reset() { PHYSICS_ENTITY_REMOVALS.set(0); REMOVALS_BY_REASON.clear(); @@ -55,7 +67,7 @@ public void onEntityRemove(@Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { - BodyAttachmentComponent component = store.getComponent(ref, ATTACHMENT_TYPE); + BodyAttachmentComponent component = store.getComponent(ref, attachmentType); if (component == null) { return; } @@ -68,6 +80,6 @@ public void onEntityRemove(@Nonnull Ref ref, @Nonnull @Override public Query getQuery() { - return QUERY; + return query; } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index dc9207dc..537a6bed 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -32,8 +32,6 @@ public final class ExplosiveFuseContactSystem ExplosiveBlockComponent.getComponentType(); private static final ComponentType FUSE_TYPE = ExplosiveFuseComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); public ExplosiveFuseContactSystem() { super(PhysicsEventFramePublishedEvent.class); @@ -46,6 +44,8 @@ public void handle(@Nonnull Store store, Store physicsStore = PhysicsThreading.store(store.getExternalData().getWorld()); long tick = Math.max(0L, store.getExternalData().getWorld().getTick()); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); for (PhysicsFrameEvent frameEvent : event.frame().physicsEvents()) { if (frameEvent instanceof PhysicsContactEvent contact && contact.phase() != PhysicsContactPhase.ENDED) { @@ -53,6 +53,7 @@ public void handle(@Nonnull Store store, store, physicsStore, tick, + attachmentType, contact.bodyAUuid(), contact.bodyBUuid(), contactCenter(contact.pointOnB())); @@ -60,6 +61,7 @@ public void handle(@Nonnull Store store, store, physicsStore, tick, + attachmentType, contact.bodyBUuid(), contact.bodyAUuid(), contactCenter(contact.pointOnA())); @@ -71,6 +73,7 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer store, @Nonnull Store physicsStore, long tick, + @Nonnull ComponentType attachmentType, @Nonnull UUID explosiveBodyUuid, @Nonnull UUID otherBodyUuid, @Nonnull Vector3d explosionCenter) { @@ -78,7 +81,7 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer ref : PhysicsEntityAttachments.attachments(store, explosiveBodyUuid)) { - BodyAttachmentComponent attachment = commandBuffer.getComponent(ref, ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = commandBuffer.getComponent(ref, attachmentType); ExplosiveBlockComponent explosive = commandBuffer.getComponent(ref, EXPLOSIVE_TYPE); ExplosiveFuseComponent fuse = commandBuffer.getComponent(ref, FUSE_TYPE); if (attachment == null diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index a52a4267..23cee10d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -22,6 +22,7 @@ import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -35,12 +36,26 @@ public final class ExplosiveFuseTickSystem extends EntityTickingSystem FUSE_TYPE = ExplosiveFuseComponent.getComponentType(); - private static final ComponentType ATTACHMENT_TYPE = - BodyAttachmentComponent.getComponentType(); private static final ComponentType TRANSFORM_TYPE = TransformComponent.getComponentType(); - private static final Query QUERY = - Query.and(EXPLOSIVE_TYPE, FUSE_TYPE, ATTACHMENT_TYPE, TRANSFORM_TYPE); + + @Nonnull + private final ComponentType attachmentType; + @Nonnull + private final Query query; + + public ExplosiveFuseTickSystem() { + this(BodyAttachmentComponent.getComponentType()); + } + + ExplosiveFuseTickSystem( + @Nonnull ComponentType attachmentType) { + this.attachmentType = Objects.requireNonNull(attachmentType, "attachmentType"); + this.query = Query.and(EXPLOSIVE_TYPE, + FUSE_TYPE, + this.attachmentType, + TRANSFORM_TYPE); + } @Override public boolean isParallel(int archetypeChunkSize, int taskCount) { @@ -59,7 +74,7 @@ public void tick(float dt, return; } ExplosiveBlockComponent explosive = chunk.getComponent(index, EXPLOSIVE_TYPE); - BodyAttachmentComponent attachment = chunk.getComponent(index, ATTACHMENT_TYPE); + BodyAttachmentComponent attachment = chunk.getComponent(index, attachmentType); TransformComponent transform = chunk.getComponent(index, TRANSFORM_TYPE); SpaceId spaceId = attachment != null ? attachmentSpaceId(store, attachment) : null; if (explosive == null || attachment == null || transform == null || spaceId == null) { @@ -95,7 +110,7 @@ public void tick(float dt, @Nonnull @Override public Query getQuery() { - return QUERY; + return query; } private static long currentTick(@Nonnull Store store) { From a5407ac4bca9e6e90a8fec941938234f3575665f Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 14:58:12 +0200 Subject: [PATCH 347/534] refactor(core): guard physics entity availability Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 162 ++++++++++-------- .../physicsentity/PhysicsEntityLifecycle.java | 39 +++++ .../PhysicsEntityAttachments.java | 27 +++ .../physicsentity/PhysicsEntityTypes.java | 43 ++++- .../components/BodyAttachmentComponent.java | 5 + .../GeneratedVisualProxyComponent.java | 5 + .../examples/commands/GrabCommand.java | 12 ++ .../examples/utils/ExamplePhysicsUtils.java | 11 ++ .../ImpulsePhysicsEntityPlugin.java | 3 + 9 files changed, 234 insertions(+), 73 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 823a7378..e62f5675 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -21,6 +21,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; @@ -82,37 +83,41 @@ protected void execute(@Nonnull CommandContext context, private static void cleanAll(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - ComponentType generatedProxyType = - GeneratedVisualProxyComponent.getComponentType(); - ComponentType controllableType = - controllableTypeOrNull(); AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, commandBuffer) -> { - BodyAttachmentComponent attachment = - archetypeChunk.getComponent(index, attachmentType); - if (attachment == null) { - return; - } - cleanAttachedEntity(removedEntities, - commandBuffer, - archetypeChunk.getReferenceTo(index), - attachmentType, - controllableType, - attachment); - }); - - store.forEachEntityParallel(generatedProxyType, - (index, archetypeChunk, commandBuffer) -> { - if (archetypeChunk.getComponent(index, attachmentType) != null) { - return; - } - - removedEntities.incrementAndGet(REMOVED_ORPHAN_VISUAL_ENTITIES); - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); - }); + boolean skippedProjectionCleanup = !PhysicsEntityAttachments.isAvailable(); + if (!skippedProjectionCleanup) { + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); + ComponentType generatedProxyType = + GeneratedVisualProxyComponent.getComponentType(); + ComponentType controllableType = + controllableTypeOrNull(); + store.forEachEntityParallel(attachmentType, + (index, archetypeChunk, commandBuffer) -> { + BodyAttachmentComponent attachment = + archetypeChunk.getComponent(index, attachmentType); + if (attachment == null) { + return; + } + cleanAttachedEntity(removedEntities, + commandBuffer, + archetypeChunk.getReferenceTo(index), + attachmentType, + controllableType, + attachment); + }); + + store.forEachEntityParallel(generatedProxyType, + (index, archetypeChunk, commandBuffer) -> { + if (archetypeChunk.getComponent(index, attachmentType) != null) { + return; + } + + removedEntities.incrementAndGet(REMOVED_ORPHAN_VISUAL_ENTITIES); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), + RemoveReason.REMOVE); + }); + } ComponentType controlSessionType = controlSessionTypeOrNull(); @@ -131,6 +136,7 @@ private static void cleanAll(@Nonnull CommandContext context, reset.whenComplete((result, failure) -> sendCleanAllResult(world, context, removedEntities, + skippedProjectionCleanup, result, failure)); } @@ -138,6 +144,7 @@ private static void cleanAll(@Nonnull CommandContext context, private static void sendCleanAllResult(@Nonnull World world, @Nonnull CommandContext context, @Nonnull AtomicIntegerArray removedEntities, + boolean skippedProjectionCleanup, @Nullable PhysicsRuntimeResetResult reset, @Nullable Throwable failure) { Runnable sender = () -> { @@ -152,7 +159,11 @@ private static void sendCleanAllResult(@Nonnull World world, context.sendMessage(Message.raw("Failed to clean Impulse physics runtime state.")); return; } - sendCleanAllSuccess(context, removedEntities, reset, world.getName()); + sendCleanAllSuccess(context, + removedEntities, + skippedProjectionCleanup, + reset, + world.getName()); }; if (world.isInThread()) { sender.run(); @@ -163,9 +174,13 @@ private static void sendCleanAllResult(@Nonnull World world, private static void sendCleanAllSuccess(@Nonnull CommandContext context, @Nonnull AtomicIntegerArray removedEntities, + boolean skippedProjectionCleanup, @Nonnull PhysicsRuntimeResetResult reset, @Nonnull String worldName) { - context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + String prefix = skippedProjectionCleanup + ? "Impulse PhysicsEntity integration is not available; skipped EntityStore attachment/proxy cleanup. " + : ""; + context.sendMessage(Message.raw(prefix + "Removed " + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + " Impulse-owned attachment entities, " + removedEntities.get(DETACHED_EXTERNAL_ATTACHMENTS) + " detached external attachments, " @@ -227,41 +242,45 @@ private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store @Nonnull SelectedBodies selectedBodies, @Nonnull Vector3d center, double radiusSquared) { - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - ComponentType generatedProxyType = - GeneratedVisualProxyComponent.getComponentType(); - ComponentType controllableType = - controllableTypeOrNull(); - AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, commandBuffer) -> { - BodyAttachmentComponent attachment = - archetypeChunk.getComponent(index, attachmentType); - assert attachment != null; - if (!selectedBodies.bodyUuids().contains(attachment.getBodyUuid())) { - return; - } - - cleanAttachedEntity(removedEntities, - commandBuffer, - archetypeChunk.getReferenceTo(index), - attachmentType, - controllableType, - attachment); - }); - - store.forEachEntityParallel(generatedProxyType, - (index, archetypeChunk, commandBuffer) -> { - if (archetypeChunk.getComponent(index, attachmentType) != null - || !entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { - return; - } - - removedEntities.incrementAndGet(REMOVED_ORPHAN_VISUAL_ENTITIES); - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); - }); + boolean skippedProjectionCleanup = !PhysicsEntityAttachments.isAvailable(); + if (!skippedProjectionCleanup) { + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); + ComponentType generatedProxyType = + GeneratedVisualProxyComponent.getComponentType(); + ComponentType controllableType = + controllableTypeOrNull(); + + store.forEachEntityParallel(attachmentType, + (index, archetypeChunk, commandBuffer) -> { + BodyAttachmentComponent attachment = + archetypeChunk.getComponent(index, attachmentType); + assert attachment != null; + if (!selectedBodies.bodyUuids().contains(attachment.getBodyUuid())) { + return; + } + + cleanAttachedEntity(removedEntities, + commandBuffer, + archetypeChunk.getReferenceTo(index), + attachmentType, + controllableType, + attachment); + }); + + store.forEachEntityParallel(generatedProxyType, + (index, archetypeChunk, commandBuffer) -> { + if (archetypeChunk.getComponent(index, attachmentType) != null + || !entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { + return; + } + + removedEntities.incrementAndGet(REMOVED_ORPHAN_VISUAL_ENTITIES); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), + RemoveReason.REMOVE); + }); + } ComponentType controlSessionType = controlSessionTypeOrNull(); @@ -294,7 +313,9 @@ private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store removedBodies++; } - return new RadiusCleanResult(removedEntities, removedBodies); + return new RadiusCleanResult(removedEntities, + skippedProjectionCleanup, + removedBodies); } private static void sendCleanRadiusResult(@Nonnull World world, @@ -328,7 +349,11 @@ private static void sendCleanRadiusSuccess(@Nonnull CommandContext context, float radius, @Nonnull String worldName) { AtomicIntegerArray removedEntities = result.removedEntities(); - context.sendMessage(Message.raw("Removed " + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + String prefix = result.skippedProjectionCleanup() + ? "Impulse PhysicsEntity integration is not available; skipped EntityStore attachment/proxy cleanup. " + : ""; + context.sendMessage(Message.raw(prefix + "Removed " + + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + " Impulse-owned attachment entities, " + removedEntities.get(DETACHED_EXTERNAL_ATTACHMENTS) + " detached external attachments, " @@ -437,6 +462,7 @@ private record SelectedBodies(@Nonnull Set bodyUuids) { } private record RadiusCleanResult(@Nonnull AtomicIntegerArray removedEntities, + boolean skippedProjectionCleanup, int removedBodies) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java new file mode 100644 index 00000000..b4f39722 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java @@ -0,0 +1,39 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicsentity; + +import dev.hytalemodding.impulse.core.internal.modules.SubPluginLifecycleGate; + +/** + * Server-level lifecycle controlled by the Impulse PhysicsEntity subplugin. + */ +public final class PhysicsEntityLifecycle { + + public static final String DISABLED_MESSAGE = + "Impulse PhysicsEntity integration is not available. " + + "Enable HytaleModding:ImpulsePhysicsEntity to use EntityStore physics projections."; + + private static final SubPluginLifecycleGate GATE = + new SubPluginLifecycleGate(DISABLED_MESSAGE); + + private PhysicsEntityLifecycle() { + } + + public static void enable() { + GATE.enable(); + } + + public static void disable() { + GATE.disable(); + } + + public static boolean isEnabled() { + return GATE.isEnabled(); + } + + public static long generation() { + return GATE.generation(); + } + + public static void requireEnabled() { + GATE.requireEnabled(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java index 1623c6cb..0f7609e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import java.util.Collection; import java.util.Objects; @@ -19,9 +20,28 @@ public final class PhysicsEntityAttachments { private PhysicsEntityAttachments() { } + /** + * Returns whether the PhysicsEntity subplugin is loaded and its EntityStore projection types + * are registered. + */ + public static boolean isAvailable() { + return PhysicsEntityLifecycle.isEnabled() + && PhysicsEntityTypes.areEntityStoreTypesRegistered(); + } + + /** + * Requires the PhysicsEntity subplugin to be loaded before reading projection attachments. + */ + public static void requireAvailable() { + if (!isAvailable()) { + throw new IllegalStateException(PhysicsEntityLifecycle.DISABLED_MESSAGE); + } + } + @Nonnull public static Collection> attachments(@Nonnull Store store, @Nonnull UUID bodyUuid) { + requireAvailable(); return requireWorldThread(store, "read PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); @@ -30,6 +50,7 @@ public static Collection> attachments(@Nonnull Store> attachments(@Nonnull Store store, @Nonnull Ref bodyRef) { + requireAvailable(); return requireWorldThread(store, "read PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getAttachments(Objects.requireNonNull(bodyRef, "bodyRef")); @@ -39,6 +60,7 @@ public static Collection> attachments(@Nonnull Store> attachments(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { + requireAvailable(); PhysicsProjectionIndexResource projection = requireWorldThread(store, "read PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()); @@ -49,6 +71,7 @@ public static Collection> attachments(@Nonnull Store store, @Nonnull UUID bodyUuid) { + requireAvailable(); return requireWorldThread(store, "check PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .hasAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); @@ -56,6 +79,7 @@ public static boolean hasAttachments(@Nonnull Store store, public static boolean hasAttachments(@Nonnull Store store, @Nonnull Ref bodyRef) { + requireAvailable(); return requireWorldThread(store, "check PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .hasAttachments(Objects.requireNonNull(bodyRef, "bodyRef")); @@ -64,6 +88,7 @@ public static boolean hasAttachments(@Nonnull Store store, public static boolean hasAttachments(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { + requireAvailable(); PhysicsProjectionIndexResource projection = requireWorldThread(store, "check PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()); @@ -75,6 +100,7 @@ public static boolean hasAttachments(@Nonnull Store store, @Nullable public static Ref generatedVisualProxy(@Nonnull Store store, @Nonnull UUID bodyUuid) { + requireAvailable(); return requireWorldThread(store, "read PhysicsStore generated visual proxy") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getGeneratedVisualProxy(Objects.requireNonNull(bodyUuid, "bodyUuid")); @@ -83,6 +109,7 @@ public static Ref generatedVisualProxy(@Nonnull Store @Nullable public static Ref generatedVisualProxy(@Nonnull Store store, @Nonnull Ref bodyRef) { + requireAvailable(); return requireWorldThread(store, "read PhysicsStore generated visual proxy") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getGeneratedVisualProxy(Objects.requireNonNull(bodyRef, "bodyRef")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index f11e7f99..43fc2e95 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -20,6 +20,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -97,30 +98,62 @@ public static void clearEntityStoreTypes() { PhysicsProjectionIndexResource.clearResourceType(); } + public static boolean areEntityStoreTypesRegistered() { + return bodyAttachmentComponentType != null + && generatedVisualProxyComponentType != null + && physicsWorldResourceType != null + && physicsEventFramePublishedEventType != null + && persistenceRestoreGroup != null + && PhysicsDebugResource.getResourceType() != null + && PhysicsRuntimeProfilingResource.getResourceType() != null + && PhysicsProjectionIndexResource.getResourceType() != null; + } + + public static boolean isBodyAttachmentComponentTypeRegistered() { + return bodyAttachmentComponentType != null; + } + + public static boolean isGeneratedVisualProxyComponentTypeRegistered() { + return generatedVisualProxyComponentType != null; + } + @Nonnull public static ComponentType bodyAttachmentComponentType() { - return bodyAttachmentComponentType; + return requireRegistered(bodyAttachmentComponentType, + "Impulse BodyAttachment component type is not registered"); } @Nonnull public static ComponentType generatedVisualProxyComponentType() { - return generatedVisualProxyComponentType; + return requireRegistered(generatedVisualProxyComponentType, + "Impulse GeneratedVisualProxy component type is not registered"); } @Nonnull public static ResourceType physicsWorldResourceType() { - return physicsWorldResourceType; + return requireRegistered(physicsWorldResourceType, + "Impulse PhysicsWorld resource type is not registered"); } @Nonnull public static WorldEventType physicsEventFramePublishedEventType() { - return physicsEventFramePublishedEventType; + return requireRegistered(physicsEventFramePublishedEventType, + "Impulse physics event-frame world event type is not registered"); } @Nonnull public static SystemGroup persistenceRestoreGroup() { - return persistenceRestoreGroup; + return requireRegistered(persistenceRestoreGroup, + "Impulse PhysicsEntity persistence restore group is not registered"); + } + + @Nonnull + private static T requireRegistered(@Nullable T value, @Nonnull String message) { + if (value == null) { + throw new IllegalStateException(Objects.requireNonNull(message, "message")); + } + return value; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java index 66e281e6..626a5b88 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java @@ -224,6 +224,11 @@ public boolean shouldRemoveEntityWhenBodyMissing() { || lifecycle == AttachmentLifecycle.GENERATED_PROXY; } + public static boolean isComponentTypeRegistered() { + return PhysicsEntityTypes.isBodyAttachmentComponentTypeRegistered(); + } + + @Nonnull public static ComponentType getComponentType() { return PhysicsEntityTypes.bodyAttachmentComponentType(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java index f90189d1..e61313df 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java @@ -18,6 +18,11 @@ public final class GeneratedVisualProxyComponent implements Component getComponentType() { return PhysicsEntityTypes.generatedVisualProxyComponentType(); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index e9832cd5..76f057ce 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -55,6 +55,9 @@ public class GrabCommand extends AbstractAsyncPlayerCommand { private static final double RAY_LENGTH = 24.0; private static final float MIN_HOLD_DISTANCE = 4.0f; private static final Vector3f VIEW_OFFSET = new Vector3f(0.85f, -0.35f, 0.0f); + private static final String PHYSICS_ENTITY_UNAVAILABLE_MESSAGE = + "Impulse PhysicsEntity integration is not available. " + + "Enable HytaleModding:ImpulsePhysicsEntity to grab entity-backed physics bodies."; private final OptionalArg spaceArg = this.withOptionalArg( "space", "Physics space id to target", @@ -76,6 +79,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, "Impulse control is disabled. Enable HytaleModding:ImpulseControl to use grab.")); return CompletableFuture.completedFuture(null); } + if (!PhysicsEntityAttachments.isAvailable()) { + ctx.sender().sendMessage(Message.raw(PHYSICS_ENTITY_UNAVAILABLE_MESSAGE)); + return CompletableFuture.completedFuture(null); + } ComponentType controllableType = ImpulseControllableComponent.getComponentType(); @@ -120,6 +127,10 @@ private static void finishGrab(@Nonnull CommandContext ctx, @Nonnull SpaceId targetSpaceId, @Nonnull ComponentType controllableType, @Nonnull List hits) { + if (!PhysicsEntityAttachments.isAvailable()) { + ctx.sender().sendMessage(Message.raw(PHYSICS_ENTITY_UNAVAILABLE_MESSAGE)); + return; + } HitSelection selection = selectControllableHit(physicsStore, store, controllableType, @@ -294,6 +305,7 @@ private static AttachmentSelection inspectGameplayAttachments(@Nonnull Store controllableType, @Nonnull Ref bodyRef) { boolean hasGameplayAttachment = false; + PhysicsEntityAttachments.requireAvailable(); ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); for (Ref attachmentRef : PhysicsEntityAttachments.attachments(store, bodyRef)) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index e3cb25e9..76ea01d1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -21,6 +21,7 @@ import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; @@ -816,6 +817,7 @@ public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(BodyAttachmentComponent.getComponentType(), BodyAttachmentComponent.externalEntity(bodyUuid)); @@ -849,6 +851,7 @@ public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull @Nonnull Quaternionf localRotationOffset, float visualOriginOffsetY, boolean controllable) { + requirePhysicsEntityVisuals(); Holder holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(BodyAttachmentComponent.getComponentType(), BodyAttachmentComponent.impulseOwnedVisual(physicsBodyUuid, @@ -869,6 +872,14 @@ private static Holder blockEntityHolder(@Nonnull TimeResource time, return ExampleBlockEntityVisuals.impulseOwnedBlockVisual(time, blockType, visualPosition); } + private static void requirePhysicsEntityVisuals() { + if (!PhysicsEntityAttachments.isAvailable()) { + throw new IllegalStateException( + "Impulse PhysicsEntity integration is not available. " + + "Enable HytaleModding:ImpulsePhysicsEntity to spawn entity-backed example visuals."); + } + } + public static int optionalInt(@Nonnull CommandContext ctx, @Nonnull OptionalArg arg, int defaultValue, diff --git a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java index e8c72e67..116e5751 100644 --- a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java +++ b/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; @@ -24,10 +25,12 @@ protected void setup() { PhysicsEntityTypes.registerEventTypes(entityRegistry); PhysicsEntityTypes.registerSystemGroups(entityRegistry); PhysicsEntityTypes.registerSystems(entityRegistry); + PhysicsEntityLifecycle.enable(); } @Override protected void shutdown() { + PhysicsEntityLifecycle.disable(); PhysicsEntityTypes.clearEntityStoreTypes(); } } From 025d7dfdf8687afad95278e56d65427599f435b8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 15:08:25 +0200 Subject: [PATCH 348/534] test(core): add physics entity subplugin smoke Signed-off-by: Blovien --- .../crucible/ImpulseApiCrucibleTests.java | 3 + ...PhysicsEntitySubPluginCrucibleSupport.java | 112 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 5887c69e..eb50d599 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -82,6 +82,9 @@ private static CrucibleSuite smokeSuite() { CrucibleTestCase.asyncResult("world collision subplugin load/unload/reload", ignored -> WorldCollisionSubPluginCrucibleSupport.loadUnloadReloadSmokeAsync(), "World collision subplugin lifecycle smoke failed"), + CrucibleTestCase.asyncResult("physics entity subplugin load/unload/reload", + ignored -> PhysicsEntitySubPluginCrucibleSupport.loadUnloadReloadSmokeAsync(), + "PhysicsEntity subplugin lifecycle smoke failed"), CrucibleTestCase.asyncResult("control subplugin load/unload/reload", ControlSubPluginCrucibleSupport::loadUnloadReloadSmokeAsync, "Control subplugin lifecycle smoke failed"), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java new file mode 100644 index 00000000..af2d3c84 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java @@ -0,0 +1,112 @@ +package dev.hytalemodding.impulse.core.internal.crucible; + +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +/** + * Runtime-only helpers for exercising the PhysicsEntity subplugin through Hytale. + */ +final class PhysicsEntitySubPluginCrucibleSupport { + + private static final PluginIdentifier PLUGIN_ID = + new PluginIdentifier("HytaleModding", "ImpulsePhysicsEntity"); + private static final long SMOKE_TIMEOUT_SECONDS = 30L; + + private PhysicsEntitySubPluginCrucibleSupport() { + } + + @Nonnull + static CompletionStage loadUnloadReloadSmokeAsync() { + return CompletableFuture.supplyAsync(PhysicsEntitySubPluginCrucibleSupport::loadUnloadReloadSmoke) + .orTimeout(SMOKE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .exceptionally(failure -> CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin lifecycle smoke failed: " + failure.getMessage())); + } + + private static CrucibleTestCase.TestOutcome loadUnloadReloadSmoke() { + PluginManager pluginManager = PluginManager.get(); + if (!pluginManager.getAvailablePlugins().containsKey(PLUGIN_ID) + && pluginManager.getPlugin(PLUGIN_ID) == null) { + return CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin is not available: " + PLUGIN_ID); + } + + if (!ensureLoaded(pluginManager)) { + return CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin load did not enable the lifecycle"); + } + if (!pluginManager.unload(PLUGIN_ID)) { + return CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin unload returned false"); + } + if (pluginManager.getPlugin(PLUGIN_ID) != null) { + return CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin remained loaded after unload"); + } + if (isAvailable()) { + return CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin unload did not disable availability: " + + availabilityState()); + } + boolean loadResult = pluginManager.load(PLUGIN_ID); + PluginBase loadedPlugin = pluginManager.getPlugin(PLUGIN_ID); + if (!loadResult || !isAvailable()) { + return CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin reload load did not enable availability: " + + "loadResult=" + loadResult + + ", pluginState=" + stateOf(loadedPlugin) + + ", " + availabilityState()); + } + boolean reloadResult = pluginManager.reload(PLUGIN_ID); + PluginBase reloadedPlugin = pluginManager.getPlugin(PLUGIN_ID); + if (!reloadResult || !isAvailable()) { + return CrucibleTestCase.TestOutcome.fail( + "PhysicsEntity subplugin reload did not leave availability enabled: " + + "reloadResult=" + reloadResult + + ", pluginState=" + stateOf(reloadedPlugin) + + ", " + availabilityState()); + } + return CrucibleTestCase.TestOutcome.pass(); + } + + private static boolean ensureLoaded(@Nonnull PluginManager pluginManager) { + if (pluginManager.getPlugin(PLUGIN_ID) != null && isAvailable()) { + return true; + } + return pluginManager.load(PLUGIN_ID) && isAvailable(); + } + + private static boolean isAvailable() { + return PhysicsEntityLifecycle.isEnabled() + && PhysicsEntityTypes.areEntityStoreTypesRegistered() + && BodyAttachmentComponent.isComponentTypeRegistered() + && GeneratedVisualProxyComponent.isComponentTypeRegistered() + && PhysicsEntityAttachments.isAvailable(); + } + + @Nonnull + private static String availabilityState() { + return "lifecycleEnabled=" + PhysicsEntityLifecycle.isEnabled() + + ", typesRegistered=" + PhysicsEntityTypes.areEntityStoreTypesRegistered() + + ", bodyAttachmentRegistered=" + + BodyAttachmentComponent.isComponentTypeRegistered() + + ", generatedVisualProxyRegistered=" + + GeneratedVisualProxyComponent.isComponentTypeRegistered() + + ", attachmentsAvailable=" + PhysicsEntityAttachments.isAvailable(); + } + + @Nonnull + private static String stateOf(PluginBase plugin) { + return plugin == null ? "missing" : plugin.getState().name(); + } +} From bf17d559c1f5dd30358be1ac9ac53721d881df51 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 17:58:41 +0200 Subject: [PATCH 349/534] refactor(core): bundle physics subplugins in core Signed-off-by: Blovien --- build.gradle.kts | 10 +--- .../build.gradle.kts | 6 +++ .../hytalemodding/impulse/api/BackendId.java | 0 .../hytalemodding/impulse/api/Impulse.java | 0 .../impulse/api/PhysicsAxis.java | 0 .../impulse/api/PhysicsBackend.java | 0 .../PhysicsBackendBodyActivationEvent.java | 0 .../api/PhysicsBackendContactEvent.java | 0 .../impulse/api/PhysicsBackendEvent.java | 0 .../impulse/api/PhysicsBackendEventBatch.java | 0 .../api/PhysicsBackendEventBuffer.java | 0 .../impulse/api/PhysicsBackendEventKind.java | 0 .../impulse/api/PhysicsBackendEventSink.java | 0 .../api/PhysicsBackendJointBreakEvent.java | 0 .../impulse/api/PhysicsBody.java | 0 .../api/PhysicsBodyActivationPhase.java | 0 .../impulse/api/PhysicsBodySnapshot.java | 0 .../impulse/api/PhysicsBodyType.java | 0 .../impulse/api/PhysicsCollisionFilters.java | 0 .../impulse/api/PhysicsContact.java | 0 .../impulse/api/PhysicsContactPhase.java | 0 .../impulse/api/PhysicsJoint.java | 0 .../impulse/api/PhysicsJointType.java | 0 .../impulse/api/PhysicsRayHit.java | 0 .../impulse/api/PhysicsRuntimeStats.java | 0 .../impulse/api/PhysicsSpace.java | 0 .../impulse/api/PhysicsStepPhaseStats.java | 0 .../hytalemodding/impulse/api/ShapeType.java | 0 .../hytalemodding/impulse/api/SpaceId.java | 0 .../capability/PhysicsActivationTuning.java | 0 .../PhysicsActivationTuningCapability.java | 0 .../PhysicsBackendEventsCapability.java | 0 .../api/capability/PhysicsCapability.java | 0 .../PhysicsCapabilityDescriptor.java | 0 .../api/capability/PhysicsCapabilityId.java | 0 .../PhysicsContinuousCollisionCapability.java | 0 .../PhysicsExtensionSettingsCapability.java | 0 .../api/capability/PhysicsSolverTuning.java | 0 .../PhysicsSolverTuningCapability.java | 0 .../PhysicsVoxelTerrainCapability.java | 0 .../api/runtime/BackendBodyIdSource.java | 0 .../api/runtime/BackendBodySnapshotSink.java | 0 .../api/runtime/BackendContactSink.java | 0 .../BackendExtensionSettingsSource.java | 0 .../impulse/api/runtime/BackendJointType.java | 0 .../impulse/api/runtime/BackendQuatSink.java | 0 .../api/runtime/BackendRayHitSink.java | 0 .../api/runtime/BackendRuntimeCodes.java | 0 .../api/runtime/BackendRuntimeStatsSink.java | 0 .../runtime/BackendStepPhaseStatsSink.java | 0 .../impulse/api/runtime/BackendVec3Sink.java | 0 .../api/runtime/PhysicsBackendRuntime.java | 0 .../PhysicsBackendRuntimeProvider.java | 0 .../legacy/LegacyPhysicsBackendRuntime.java | 0 .../LegacyPhysicsBackendRuntimeProvider.java | 0 .../impulse/api/BackendIdTest.java | 0 .../impulse/api/ImpulseRegistryTest.java | 0 .../impulse/api/PhysicsAxisTest.java | 0 .../api/PhysicsBackendEventBufferTest.java | 0 .../impulse/api/PhysicsBodySnapshotTest.java | 0 .../PhysicsBackendEventsCapabilityTest.java | 0 .../capability/PhysicsCapabilityIdTest.java | 0 .../PhysicsCapabilitySettingsTest.java | 0 .../LegacyPhysicsBackendRuntimeTest.java | 0 .../FakePhysicsBackendCapabilityTest.java | 0 .../api/testsupport/FakePhysicsBackend.java | 0 .../FakePhysicsBackendRuntimeProvider.java | 0 impulse-bullet/build.gradle.kts | 4 +- impulse-core/build.gradle.kts | 36 +++++++++---- .../impulse/core/ImpulsePlugin.java | 2 +- .../PhysicsStoreEarlyPluginProbe.java | 2 +- .../commands/debug/DebugFlagCommand.java | 1 + .../commands/debug/DebugToggleCommand.java | 2 + .../settings/MaxStepDtSettingCommand.java | 1 + .../settings/VisualSettingsCommand.java | 1 + .../settings/VisualSyncSettingsCommand.java | 1 + .../BenchmarkSpaceStatsView.java | 2 +- .../crucible/ImpulseApiCrucibleTests.java | 6 +-- ...tachedStreamingBenchmarkCrucibleTests.java | 5 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 1 - ...PhysicsChunkSubPluginCrucibleSupport.java} | 40 +++++++-------- .../PhysicsStoreBenchmarkQueries.java | 1 - ...ecycle.java => PhysicsChunkLifecycle.java} | 12 ++--- .../physicschunk/PhysicsChunkModule.java | 12 ++--- ...he.java => PhysicsChunkMutationCache.java} | 20 ++++---- ...sStoreWorldCollisionStreamingResource.java | 6 +-- .../PhysicsWorldCollisionRuntime.java | 14 +++--- .../physicschunk/SectionBlockReader.java | 4 +- .../physicschunk/SectionColliderBuilder.java | 4 +- ...ionCache.java => VoxelCollisionCache.java} | 4 +- .../commands/CollisionLodSettingsCommand.java | 6 +-- .../commands/PhysicsChunkCommand.java | 12 +++++ .../PhysicsChunkCommandContributions.java | 22 ++++++++ .../commands/PhysicsChunkPerfCommand.java | 13 +++++ .../PhysicsChunkPerfReportCommand.java | 16 +++--- .../PhysicsChunkPerfResetCommand.java | 6 +-- .../PhysicsChunkPerfToggleCommand.java | 6 +-- .../commands/PhysicsChunkSettingsCommand.java | 18 +++---- .../commands/PhysicsChunkSpaceSelection.java | 6 +-- .../WorldCollisionProfilingResource.java | 2 +- ...sicsStoreWorldCollisionProducerSystem.java | 6 +-- .../PhysicsEntityDiagnostics.java | 2 +- .../physicsentity/PhysicsEntityModule.java | 7 ++- .../PhysicsWorldRuntimeResource.java | 19 ++++--- .../PhysicsWorldSettingsResource.java | 12 ++--- .../debug}/PhysicsDebugContactView.java | 2 +- .../debug}/PhysicsDebugJointView.java | 2 +- .../systems/debug/PhysicsDebugRenderer.java | 2 - .../systems/debug/PhysicsDebugSystem.java | 3 -- ...PhysicsDebugWorldCollisionSectionView.java | 2 +- .../debug/PhysicsStoreDebugQueries.java | 3 -- .../core/plugin/body/PhysicsBodyKind.java | 2 + .../physicschunk/PhysicsChunkCommands.java | 16 +++--- .../physicschunk/PhysicsWorldCollision.java | 8 +-- .../PhysicsEntityDiagnostics.java | 4 +- .../impulse/core/plugin/package-info.java | 2 +- .../plugin/physicsstore/PhysicsAsync.java | 2 + ...mpulseCommandContributionRegistryTest.java | 4 +- ...sChunkCommandContributionRegistryTest.java | 26 +++++----- ...st.java => PhysicsChunkLifecycleTest.java} | 16 +++--- .../WorldVoxelCollisionCacheTest.java | 50 +++++++++---------- .../PhysicsChunkPerfReportCommandTest.java | 12 ++--- .../WorldCollisionProfilingResourceTest.java | 3 +- .../systems/debug/PhysicsDebugSystemTest.java | 2 - impulse-examples/build.gradle.kts | 4 +- .../examples/commands/ImpulseCommand.java | 2 +- ...nCommand.java => PhysicsChunkCommand.java} | 6 +-- .../stress/StressBenchmarkCommand.java | 6 +-- impulse-physics-chunk/.gitignore | 1 - impulse-physics-chunk/build.gradle.kts | 38 -------------- .../commands/WorldCollisionCommand.java | 12 ----- .../WorldCollisionCommandContributions.java | 22 -------- .../commands/WorldCollisionPerfCommand.java | 13 ----- impulse-physics-entity/.gitignore | 1 - impulse-physics-entity/build.gradle.kts | 28 ----------- impulse-rapier/build.gradle.kts | 4 +- settings.gradle.kts | 4 +- 137 files changed, 292 insertions(+), 360 deletions(-) rename {impulse-api => impulse-backend-api}/build.gradle.kts (72%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/BackendId.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/Impulse.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java (100%) rename {impulse-api => impulse-backend-api}/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java (100%) rename {impulse-api => impulse-backend-api}/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java (100%) rename {impulse-api => impulse-backend-api}/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java (100%) rename {impulse-api => impulse-backend-api}/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java (100%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{store/integration => }/PhysicsStoreEarlyPluginProbe.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{simulation/view => crucible}/BenchmarkSpaceStatsView.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/{WorldCollisionSubPluginCrucibleSupport.java => PhysicsChunkSubPluginCrucibleSupport.java} (62%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{WorldCollisionLifecycle.java => PhysicsChunkLifecycle.java} (78%) rename impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java (76%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{PhysicsStoreTerrainMutationCache.java => PhysicsChunkMutationCache.java} (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{WorldVoxelCollisionCache.java => VoxelCollisionCache.java} (99%) rename {impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules}/physicschunk/commands/CollisionLodSettingsCommand.java (97%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java rename impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java (97%) rename impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java (82%) rename impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java (84%) rename impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java (93%) rename impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java (93%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{diagnostics => modules/physicsentity}/PhysicsEntityDiagnostics.java (99%) rename impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java => impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java (80%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{simulation/view => systems/debug}/PhysicsDebugContactView.java (87%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{simulation/view => systems/debug}/PhysicsDebugJointView.java (90%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{simulation/view => systems/debug}/PhysicsDebugWorldCollisionSectionView.java (92%) rename {impulse-physics-chunk => impulse-core}/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java (57%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{WorldCollisionLifecycleTest.java => PhysicsChunkLifecycleTest.java} (54%) rename impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java => impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java (86%) rename impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/{WorldCollisionCommand.java => PhysicsChunkCommand.java} (97%) delete mode 100644 impulse-physics-chunk/.gitignore delete mode 100644 impulse-physics-chunk/build.gradle.kts delete mode 100644 impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommand.java delete mode 100644 impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java delete mode 100644 impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfCommand.java delete mode 100644 impulse-physics-entity/.gitignore delete mode 100644 impulse-physics-entity/build.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts index 697d9fd3..5019114a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,11 +17,7 @@ version = property("version") as String val coreOnlyWorkspace = providers.gradleProperty("impulse.coreOnlyWorkspace") .map(String::toBoolean) .orElse(false) -val coreModProjects = listOf( - ":impulse-core", - ":impulse-physics-entity", - ":impulse-physics-chunk" -) +val coreModProjects = listOf(":impulse-core") val workspaceModProjects = if (coreOnlyWorkspace.get()) { coreModProjects } else { @@ -150,13 +146,11 @@ tasks.register("headlessTest") { group = "verification" description = "Runs automated headless/serverless tests without booting the Hytale server" dependsOn( - ":impulse-api:test", + ":impulse-backend-api:test", ":impulse-native-loader:test", ":impulse-bullet:test", ":impulse-rapier:test", ":impulse-core:test", - ":impulse-physics-entity:test", - ":impulse-physics-chunk:test", ":impulse-early-plugin:test" ) } diff --git a/impulse-api/build.gradle.kts b/impulse-backend-api/build.gradle.kts similarity index 72% rename from impulse-api/build.gradle.kts rename to impulse-backend-api/build.gradle.kts index 25894570..14b63948 100644 --- a/impulse-api/build.gradle.kts +++ b/impulse-backend-api/build.gradle.kts @@ -12,3 +12,9 @@ dependencies { testFixturesImplementation(platform(libs.junit.bom)) testFixturesApi(libs.junit.jupiter.api) } + +tasks.named("jar") { + manifest { + attributes["Automatic-Module-Name"] = "impulse.api" + } +} diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java diff --git a/impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java similarity index 100% rename from impulse-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java diff --git a/impulse-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java similarity index 100% rename from impulse-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java diff --git a/impulse-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java similarity index 100% rename from impulse-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java rename to impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java diff --git a/impulse-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java similarity index 100% rename from impulse-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java rename to impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java diff --git a/impulse-bullet/build.gradle.kts b/impulse-bullet/build.gradle.kts index 2fecc8d1..78259c7f 100644 --- a/impulse-bullet/build.gradle.kts +++ b/impulse-bullet/build.gradle.kts @@ -23,7 +23,7 @@ val impulseLicenseFile = rootProject.layout.projectDirectory.file("LICENSE") val libbulletjmeLicenseFile = rootProject.layout.projectDirectory.file("licenses/LIBBULLETJME_LICENSE") dependencies { - api(project(":impulse-api")) + api(project(":impulse-backend-api")) implementation(project(":impulse-native-loader")) implementation(libs.libbulletjme) @@ -49,7 +49,7 @@ bulletBackendPlatforms.forEach { platform -> fun runtimeClasspathWithoutBundledApi(): FileCollection { return configurations.runtimeClasspath.get() - .filter { file -> !file.name.startsWith("impulse-api-") } + .filter { file -> !file.name.startsWith("impulse-backend-api-") } } fun runtimeClasspathWithoutBundledApiAndHostNative(): FileCollection { diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index 132af1c5..c28b0d0b 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -25,27 +25,29 @@ val moduleInfoModulePath by configurations.creating { } dependencies { - implementation(project(":impulse-api")) + implementation(project(":impulse-backend-api")) compileOnly(project(":impulse-early-plugin")) - testImplementation(testFixtures(project(":impulse-api"))) - testImplementation(libs.objenesis) - testCompileOnly(project(":impulse-early-plugin")) - testRuntimeOnly(project(":impulse-early-plugin")) - testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") - testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") compileOnly(libs.crucible) + compileOnly(libs.lombok) moduleInfoModulePath(libs.joml) moduleInfoModulePath(libs.jsr305) moduleInfoModulePath(libs.crucible) - compileOnly(libs.lombok) annotationProcessor(libs.lombok) + + testImplementation(testFixtures(project(":impulse-backend-api"))) + testImplementation(libs.objenesis) + testCompileOnly(project(":impulse-early-plugin")) + testRuntimeOnly(project(":impulse-early-plugin")) + testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") + testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") + } -val impulseApiJar = project(":impulse-api").tasks.named("jar") +val impulseApiJar = project(":impulse-backend-api").tasks.named("jar") -tasks.named("compileJava") { +tasks.named("compileJava") { doFirst { destinationDirectory.file("module-info.class").get().asFile.delete() } @@ -108,4 +110,18 @@ hytaleTools { false, /* disabledByDefault */ false /* includeAssetPack */ ) + + subPlugin ( + "ImpulsePhysicsEntity", + "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityModule", + false, /* disabledByDefault */ + false /* includeAssetPack */ + ) + + subPlugin ( + "ImpulsePhysicsChunk", + "dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkModule", + false, /* disabledByDefault */ + false /* includeAssetPack */ + ) } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index b67bfeee..656c0395 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; -import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; +import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import java.nio.file.Path; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsStoreEarlyPluginProbe.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsStoreEarlyPluginProbe.java index a0579ec5..c43cd1b7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/store/integration/PhysicsStoreEarlyPluginProbe.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsStoreEarlyPluginProbe.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.store.integration; +package dev.hytalemodding.impulse.core.internal; import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.universe.system.WorldConfigSaveSystem; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java index b0d08e87..75cb8668 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java @@ -37,6 +37,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { + assert PhysicsDebugResource.getResourceType() != null; PhysicsDebugResource resource = store.getResource(PhysicsDebugResource.getResourceType()); boolean enabled = !getter.apply(resource); setter.accept(resource, enabled); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java index ebf57c0f..ee99d279 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java @@ -26,10 +26,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { + assert PhysicsDebugResource.getResourceType() != null; PhysicsDebugResource debug = store.getResource(PhysicsDebugResource.getResourceType()); boolean enabled; if (debug.removeSubscriber(playerRef.getUuid())) { enabled = false; + // FIXME: maybe don't clear up all shapes bust just ours playerRef.getPacketHandler().write(new ClearDebugShapes()); } else { enabled = true; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java index 5545026b..88ef205a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java @@ -17,6 +17,7 @@ import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +// NOTE: probably move this to just an optional target in stepmode public class MaxStepDtSettingCommand extends AbstractAsyncPlayerCommand { private final OptionalArg dtArg = this.withOptionalArg( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSettingsCommand.java index 71df9875..832f44cd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSettingsCommand.java @@ -2,6 +2,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +// TODO: move to physicsentity module public class VisualSettingsCommand extends AbstractCommandCollection { public VisualSettingsCommand() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java index 6d57be7e..5e8d1bee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java @@ -22,6 +22,7 @@ import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +// TODO: move to physicsentity module public class VisualSyncSettingsCommand extends AbstractAsyncPlayerCommand { private final OptionalArg fullRadiusArg = this.withOptionalArg( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/BenchmarkSpaceStatsView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/BenchmarkSpaceStatsView.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java index fb711d11..4ec2aff7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/simulation/view/BenchmarkSpaceStatsView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.simulation.view; +package dev.hytalemodding.impulse.core.internal.crucible; /** * Copied store tick lane counters used by stress and fall-envelope diagnostics. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index eb50d599..26f9ddd3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -79,9 +79,9 @@ private static CrucibleSuite smokeSuite() { return true; }, "No backend id is available for Crucible tests"), - CrucibleTestCase.asyncResult("world collision subplugin load/unload/reload", - ignored -> WorldCollisionSubPluginCrucibleSupport.loadUnloadReloadSmokeAsync(), - "World collision subplugin lifecycle smoke failed"), + CrucibleTestCase.asyncResult("PhysicsChunk subplugin load/unload/reload", + ignored -> PhysicsChunkSubPluginCrucibleSupport.loadUnloadReloadSmokeAsync(), + "PhysicsChunk subplugin lifecycle smoke failed"), CrucibleTestCase.asyncResult("physics entity subplugin load/unload/reload", ignored -> PhysicsEntitySubPluginCrucibleSupport.loadUnloadReloadSmokeAsync(), "PhysicsEntity subplugin lifecycle smoke failed"), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 1a84fb24..bb791c13 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -26,7 +26,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; @@ -216,9 +215,9 @@ private CompletionStage runStage(int stageIndex, private CompletionStage startStageWhenReady(int count, int attempt) { clearStageState(); - if (!WorldCollisionSubPluginCrucibleSupport.ensureLoaded()) { + if (!PhysicsChunkSubPluginCrucibleSupport.ensureLoaded()) { return CompletableFuture.completedFuture(StartedStage.failed(count, - "world collision subplugin did not load")); + "PhysicsChunk subplugin did not load")); } PhysicsWorldSettings worldSettings = physics.getWorldSettings(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 14b0619e..2441e098 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -30,7 +30,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/WorldCollisionSubPluginCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsChunkSubPluginCrucibleSupport.java similarity index 62% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/WorldCollisionSubPluginCrucibleSupport.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsChunkSubPluginCrucibleSupport.java index 5c1c7c24..e4534599 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/WorldCollisionSubPluginCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsChunkSubPluginCrucibleSupport.java @@ -3,37 +3,37 @@ import com.hypixel.hytale.common.plugin.PluginIdentifier; import com.hypixel.hytale.server.core.plugin.PluginManager; import com.hypixel.hytale.server.core.plugin.PluginBase; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; /** - * Runtime-only helpers for exercising the world-collision subplugin through Hytale. + * Runtime-only helpers for exercising the PhysicsChunk subplugin through Hytale. */ -final class WorldCollisionSubPluginCrucibleSupport { +final class PhysicsChunkSubPluginCrucibleSupport { private static final PluginIdentifier PLUGIN_ID = new PluginIdentifier("HytaleModding", "ImpulsePhysicsChunk"); - private WorldCollisionSubPluginCrucibleSupport() { + private PhysicsChunkSubPluginCrucibleSupport() { } static boolean ensureLoaded() { PluginManager pluginManager = PluginManager.get(); - if (pluginManager.getPlugin(PLUGIN_ID) != null && WorldCollisionLifecycle.isEnabled()) { + if (pluginManager.getPlugin(PLUGIN_ID) != null && PhysicsChunkLifecycle.isEnabled()) { return true; } - return pluginManager.load(PLUGIN_ID) && WorldCollisionLifecycle.isEnabled(); + return pluginManager.load(PLUGIN_ID) && PhysicsChunkLifecycle.isEnabled(); } @Nonnull static CompletionStage loadUnloadReloadSmokeAsync() { - return CompletableFuture.supplyAsync(WorldCollisionSubPluginCrucibleSupport::loadUnloadReloadSmoke) + return CompletableFuture.supplyAsync(PhysicsChunkSubPluginCrucibleSupport::loadUnloadReloadSmoke) .orTimeout(30L, TimeUnit.SECONDS) .exceptionally(failure -> CrucibleTestCase.TestOutcome.fail( - "World collision subplugin lifecycle smoke failed: " + failure.getMessage())); + "PhysicsChunk subplugin lifecycle smoke failed: " + failure.getMessage())); } private static CrucibleTestCase.TestOutcome loadUnloadReloadSmoke() { @@ -41,42 +41,42 @@ private static CrucibleTestCase.TestOutcome loadUnloadReloadSmoke() { if (!pluginManager.getAvailablePlugins().containsKey(PLUGIN_ID) && pluginManager.getPlugin(PLUGIN_ID) == null) { return CrucibleTestCase.TestOutcome.fail( - "World collision subplugin is not available: " + PLUGIN_ID); + "PhysicsChunk subplugin is not available: " + PLUGIN_ID); } if (!ensureLoaded()) { return CrucibleTestCase.TestOutcome.fail( - "World collision subplugin load did not enable the lifecycle"); + "PhysicsChunk subplugin load did not enable the lifecycle"); } if (!pluginManager.unload(PLUGIN_ID)) { return CrucibleTestCase.TestOutcome.fail( - "World collision subplugin unload returned false"); + "PhysicsChunk subplugin unload returned false"); } if (pluginManager.getPlugin(PLUGIN_ID) != null) { return CrucibleTestCase.TestOutcome.fail( - "World collision subplugin remained loaded after unload"); + "PhysicsChunk subplugin remained loaded after unload"); } - if (WorldCollisionLifecycle.isEnabled()) { + if (PhysicsChunkLifecycle.isEnabled()) { return CrucibleTestCase.TestOutcome.fail( - "World collision subplugin unload did not disable the lifecycle"); + "PhysicsChunk subplugin unload did not disable the lifecycle"); } boolean loadResult = pluginManager.load(PLUGIN_ID); PluginBase loadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!loadResult || !WorldCollisionLifecycle.isEnabled()) { + if (!loadResult || !PhysicsChunkLifecycle.isEnabled()) { return CrucibleTestCase.TestOutcome.fail( - "World collision subplugin reload load did not enable the lifecycle: " + "PhysicsChunk subplugin reload load did not enable the lifecycle: " + "loadResult=" + loadResult + ", pluginState=" + stateOf(loadedPlugin) - + ", lifecycleEnabled=" + WorldCollisionLifecycle.isEnabled()); + + ", lifecycleEnabled=" + PhysicsChunkLifecycle.isEnabled()); } boolean reloadResult = pluginManager.reload(PLUGIN_ID); PluginBase reloadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!reloadResult || !WorldCollisionLifecycle.isEnabled()) { + if (!reloadResult || !PhysicsChunkLifecycle.isEnabled()) { return CrucibleTestCase.TestOutcome.fail( - "World collision subplugin reload did not leave the lifecycle enabled: " + "PhysicsChunk subplugin reload did not leave the lifecycle enabled: " + "reloadResult=" + reloadResult + ", pluginState=" + stateOf(reloadedPlugin) - + ", lifecycleEnabled=" + WorldCollisionLifecycle.isEnabled()); + + ", lifecycleEnabled=" + PhysicsChunkLifecycle.isEnabled()); } return CrucibleTestCase.TestOutcome.pass(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 5a6a20a9..d46249c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -10,7 +10,6 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.simulation.view.BenchmarkSpaceStatsView; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycle.java similarity index 78% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycle.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycle.java index 39319528..e4fa3e10 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycle.java @@ -9,20 +9,20 @@ import javax.annotation.Nonnull; /** - * Server-level lifecycle controlled by the Impulse world-collision subplugin. + * Server-level lifecycle controlled by the Impulse PhysicsChunk subplugin. */ -public final class WorldCollisionLifecycle { +public final class PhysicsChunkLifecycle { private static final SubPluginLifecycleGate GATE = - new SubPluginLifecycleGate("Impulse world collision subplugin is disabled"); + new SubPluginLifecycleGate("Impulse PhysicsChunk subplugin is disabled"); private static final Set RESOURCES = Collections.newSetFromMap(new WeakHashMap<>()); static { - GATE.onDisable(WorldCollisionLifecycle::cleanupResources); + GATE.onDisable(PhysicsChunkLifecycle::cleanupResources); } - private WorldCollisionLifecycle() { + private PhysicsChunkLifecycle() { } public static void enable() { @@ -53,7 +53,7 @@ private static void cleanupResources() { resources = new ArrayList<>(RESOURCES); } for (PhysicsWorldRuntimeResource resource : resources) { - resource.disableWorldCollisionLifecycle(); + resource.disablePhysicsChunkLifecycle(); } } } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java similarity index 76% rename from impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java index 14cf162d..3f1da5ca 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/ImpulsePhysicsChunkPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java @@ -1,11 +1,11 @@ -package dev.hytalemodding.impulse.physicschunk; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; import java.util.logging.Level; @@ -14,11 +14,11 @@ /** * Plugin module that enables Impulse ChunkStore world-collision integration. */ -public final class ImpulsePhysicsChunkPlugin extends JavaPlugin { +public final class PhysicsChunkModule extends JavaPlugin { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - public ImpulsePhysicsChunkPlugin(@Nonnull JavaPluginInit init) { + public PhysicsChunkModule(@Nonnull JavaPluginInit init) { super(init); } @@ -27,7 +27,7 @@ protected void setup() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); - WorldCollisionCommandContributions.register(); + PhysicsChunkCommandContributions.register(); PhysicsWorldCollision.enableModule(); LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore terrain producer enabled."); } @@ -35,7 +35,7 @@ protected void setup() { @Override protected void shutdown() { PhysicsWorldCollision.disableModule(); - WorldCollisionCommandContributions.unregister(); + PhysicsChunkCommandContributions.unregister(); PhysicsChunkTypes.clearEntityStoreResourceTypes(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutationCache.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java index a98a5333..9335f0dc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java @@ -32,7 +32,7 @@ /** * Section cache for PhysicsStore terrain mutation producers. */ -public final class PhysicsStoreTerrainMutationCache { +public final class PhysicsChunkMutationCache { private static final int ACTIVE_BODY_STREAMING_INTERVAL_TICKS = 4; private static final int SLEEPING_BODY_STREAMING_INTERVAL_TICKS = 20; @@ -49,7 +49,7 @@ public final class PhysicsStoreTerrainMutationCache { private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); @Nonnull - public synchronized WorldVoxelCollisionCache.BuildStats ensureAround(@Nonnull World world, + public synchronized VoxelCollisionCache.BuildStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull Vector3d center, @@ -78,7 +78,7 @@ public synchronized WorldVoxelCollisionCache.BuildStats ensureAround(@Nonnull Wo int minChunkZ = ChunkUtil.chunkCoordinate(minZ); int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); - WorldVoxelCollisionCache.BuildStats total = WorldVoxelCollisionCache.BuildStats.empty(); + VoxelCollisionCache.BuildStats total = VoxelCollisionCache.BuildStats.empty(); for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { @@ -465,7 +465,7 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, } @Nonnull - private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, + private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsTerrainMutationQueueResource queue, int chunkX, @@ -491,7 +491,7 @@ private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return WorldVoxelCollisionCache.BuildStats.empty(); + return VoxelCollisionCache.BuildStats.empty(); } if (blockChunk(world, chunkX, chunkZ) == null) { cache.missingBlockChunkBackoffs.put(chunkKey, tick + MISSING_BLOCK_CHUNK_RETRY_TICKS); @@ -502,7 +502,7 @@ private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return WorldVoxelCollisionCache.BuildStats.empty(); + return VoxelCollisionCache.BuildStats.empty(); } cache.missingBlockChunkBackoffs.remove(chunkKey); @@ -514,7 +514,7 @@ private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return WorldVoxelCollisionCache.BuildStats.empty(); + return VoxelCollisionCache.BuildStats.empty(); } BlockSection section = ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); if (section == null) { @@ -527,7 +527,7 @@ private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return WorldVoxelCollisionCache.BuildStats.empty(); + return VoxelCollisionCache.BuildStats.empty(); } cache.missingBlockSectionBackoffs.remove(sectionKey); @@ -545,7 +545,7 @@ private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, profiling.incrementSectionCacheHits(); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return WorldVoxelCollisionCache.BuildStats.empty(); + return VoxelCollisionCache.BuildStats.empty(); } SectionCollisionGeometry geometry = sectionBuilder.build(world, @@ -572,7 +572,7 @@ private WorldVoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, buildOptions)); } cache.sections.put(sectionKey, built); - WorldVoxelCollisionCache.BuildStats stats = new WorldVoxelCollisionCache.BuildStats( + VoxelCollisionCache.BuildStats stats = new VoxelCollisionCache.BuildStats( geometry.scannedBlocks(), geometry.solidBlocks(), geometry.culledInteriorBlocks(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java index 11a3bb50..ea33cd8f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java @@ -6,8 +6,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelCollisionCache.BuildStats; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; @@ -29,7 +29,7 @@ public final class PhysicsStoreWorldCollisionStreamingResource implements Resource { @Nonnull - private final PhysicsStoreTerrainMutationCache cache = new PhysicsStoreTerrainMutationCache(); + private final PhysicsChunkMutationCache cache = new PhysicsChunkMutationCache(); private long tick; @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java index 556dec9d..6e035757 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java @@ -21,11 +21,11 @@ */ public final class PhysicsWorldCollisionRuntime { - private final WorldVoxelCollisionCache worldVoxelCollisionCache = new WorldVoxelCollisionCache(); + private final VoxelCollisionCache worldVoxelCollisionCache = new VoxelCollisionCache(); private final Int2LongMap streamingRevisions = new Int2LongOpenHashMap(); @Nonnull - public WorldVoxelCollisionCache worldVoxelCollisionCache() { + public VoxelCollisionCache worldVoxelCollisionCache() { return worldVoxelCollisionCache; } @@ -79,7 +79,7 @@ public WorldCollisionBuildStats refreshAround(@Nonnull World world, @Nonnull Vector3d center, int radius, @Nonnull WorldCollisionBuildOptions buildOptions) { - WorldVoxelCollisionCache.BuildStats stats = worldVoxelCollisionCache.refreshAround(world, + VoxelCollisionCache.BuildStats stats = worldVoxelCollisionCache.refreshAround(world, space, center, radius, @@ -114,7 +114,7 @@ public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, @Nonnull WorldCollisionBuildOptions buildOptions) { Objects.requireNonNull(centers, "centers"); LongSet visitedSections = new LongOpenHashSet(); - WorldVoxelCollisionCache.BuildStats total = WorldVoxelCollisionCache.BuildStats.empty(); + VoxelCollisionCache.BuildStats total = VoxelCollisionCache.BuildStats.empty(); for (Vector3d center : centers) { total = total.plus(worldVoxelCollisionCache.ensureAround(world, space, @@ -142,7 +142,7 @@ public void clear(@Nonnull SpaceId spaceId, @Nullable PhysicsSpaceBinding space) } public synchronized void clearAll() { - worldVoxelCollisionCache.copyFrom(new WorldVoxelCollisionCache()); + worldVoxelCollisionCache.copyFrom(new VoxelCollisionCache()); for (int spaceId : streamingRevisions.keySet().toIntArray()) { streamingRevisions.put(spaceId, streamingRevisions.get(spaceId) + 1L); } @@ -156,7 +156,7 @@ public void clearRetainedTerrain(@Nonnull Iterable spaces) } public synchronized void clearAllAndUnregisterSpaces() { - worldVoxelCollisionCache.copyFrom(new WorldVoxelCollisionCache()); + worldVoxelCollisionCache.copyFrom(new VoxelCollisionCache()); streamingRevisions.clear(); } @@ -170,7 +170,7 @@ public WorldCollisionStats getStats() { @Nonnull private static WorldCollisionBuildStats worldCollisionStats( - @Nonnull WorldVoxelCollisionCache.BuildStats stats) { + @Nonnull VoxelCollisionCache.BuildStats stats) { return new WorldCollisionBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java index 08f6303b..951a7b8c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java @@ -25,7 +25,7 @@ final class SectionBlockReader { private final int baseZ; private final Long2ObjectMap sectionCache = new Long2ObjectOpenHashMap<>(); @Nullable - private final WorldVoxelCollisionCache.SectionAccessCache accessCache; + private final VoxelCollisionCache.SectionAccessCache accessCache; SectionBlockReader(@Nonnull World world, @Nonnull ShapeTemplateCache templates, @@ -42,7 +42,7 @@ final class SectionBlockReader { int currentChunkX, int currentSectionY, int currentChunkZ, - @Nullable WorldVoxelCollisionCache.SectionAccessCache accessCache) { + @Nullable VoxelCollisionCache.SectionAccessCache accessCache) { this.world = world; this.templates = templates; this.currentSection = currentSection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java index ed1de1fe..4e579c75 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java @@ -39,7 +39,7 @@ long neighborhoodSignature(@Nonnull World world, int chunkX, int sectionY, int chunkZ, - @Nullable WorldVoxelCollisionCache.SectionAccessCache accessCache) { + @Nullable VoxelCollisionCache.SectionAccessCache accessCache) { return new SectionBlockReader(world, templates, section, chunkX, sectionY, chunkZ, accessCache) .neighborhoodSignature(); } @@ -59,7 +59,7 @@ SectionCollisionGeometry build(@Nonnull World world, int chunkX, int sectionY, int chunkZ, - @Nullable WorldVoxelCollisionCache.SectionAccessCache accessCache) { + @Nullable VoxelCollisionCache.SectionAccessCache accessCache) { SectionBlockReader reader = new SectionBlockReader(world, templates, section, chunkX, sectionY, chunkZ, accessCache); BitSet fullCubes = new BitSet(SECTION_VOLUME); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelCollisionCache.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCache.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelCollisionCache.java index a1358100..1ff4af24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelCollisionCache.java @@ -42,7 +42,7 @@ * policies. Each cached section is rebuilt when Hytale's section change counter changes, * and removed when it falls out of the streaming radius or when its chunk unloads.

    */ -public final class WorldVoxelCollisionCache { +public final class VoxelCollisionCache { private static final int ACTIVE_BODY_STREAMING_INTERVAL_TICKS = 4; private static final int SLEEPING_BODY_STREAMING_INTERVAL_TICKS = 20; @@ -57,7 +57,7 @@ public final class WorldVoxelCollisionCache { private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); private final AtomicBoolean streamingApplyPending = new AtomicBoolean(); - public synchronized void copyFrom(@Nonnull WorldVoxelCollisionCache other) { + public synchronized void copyFrom(@Nonnull VoxelCollisionCache other) { if (other == this) { streamingApplyPending.set(false); return; diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java similarity index 97% rename from impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java index 42f1f3aa..02df11b9 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.physicschunk.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -72,8 +72,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { Store physicsStore = PhysicsThreading.store(world); - WorldCollisionSpaceSelection.Selection selection = - WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); + PhysicsChunkSpaceSelection.Selection selection = + PhysicsChunkSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); if (selection == null) { return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java new file mode 100644 index 00000000..ffdd472f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java @@ -0,0 +1,12 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public final class PhysicsChunkCommand extends AbstractCommandCollection { + + public PhysicsChunkCommand() { + super("physicschunk", "Impulse PhysicsChunk module commands"); + addSubCommand(new PhysicsChunkSettingsCommand()); + addSubCommand(new PhysicsChunkPerfCommand()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java new file mode 100644 index 00000000..bb3d4ffd --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java @@ -0,0 +1,22 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; + +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCommands; + +/** + * Command contributions owned by the PhysicsChunk subplugin. + */ +public final class PhysicsChunkCommandContributions { + + private PhysicsChunkCommandContributions() { + } + + public static void register() { + PhysicsChunkCommands.registerPhysicsChunkCommands( + PhysicsChunkCommand::new, + CollisionLodSettingsCommand::new); + } + + public static void unregister() { + PhysicsChunkCommands.unregisterPhysicsChunkCommands(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java new file mode 100644 index 00000000..29ffd0a5 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java @@ -0,0 +1,13 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public final class PhysicsChunkPerfCommand extends AbstractCommandCollection { + + public PhysicsChunkPerfCommand() { + super("perf", "Impulse PhysicsChunk profiling commands"); + addSubCommand(new PhysicsChunkPerfToggleCommand()); + addSubCommand(new PhysicsChunkPerfReportCommand()); + addSubCommand(new PhysicsChunkPerfResetCommand()); + } +} diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java similarity index 97% rename from impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java index a8979562..52dbfbc9 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.physicschunk.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -25,10 +25,10 @@ import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; -public class WorldCollisionPerfReportCommand extends AbstractAsyncWorldCommand { +public class PhysicsChunkPerfReportCommand extends AbstractAsyncWorldCommand { - public WorldCollisionPerfReportCommand() { - super("report", "Report Impulse world collision profiling metrics"); + public PhysicsChunkPerfReportCommand() { + super("report", "Report Impulse PhysicsChunk profiling metrics"); } @Nonnull @@ -241,18 +241,18 @@ private static void sendReport(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("No profiled physics step/sync/visual ticks recorded yet." + (runtimeProfiling.enabled() ? "" - : " Run /impulse worldcollision perf toggle, wait a few seconds, then run /impulse worldcollision perf report."))); + : " Run /impulse physicschunk perf toggle, wait a few seconds, then run /impulse physicschunk perf report."))); } if (cumulative.getTickSamples() <= 0) { - ctx.sender().sendMessage(Message.raw("No profiled world collision ticks recorded yet." + ctx.sender().sendMessage(Message.raw("No profiled PhysicsChunk ticks recorded yet." + (profiling.enabled() ? "" - : " Run /impulse worldcollision perf toggle, wait a few seconds, then run /impulse worldcollision perf report."))); + : " Run /impulse physicschunk perf toggle, wait a few seconds, then run /impulse physicschunk perf report."))); return; } - ctx.sender().sendMessage(Message.raw("World collision profiling: " + ctx.sender().sendMessage(Message.raw("PhysicsChunk profiling: " + (profiling.enabled() ? "enabled" : "disabled"))); ctx.sender().sendMessage(Message.raw("Since reset: ticks=" + cumulative.getTickSamples() diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java similarity index 82% rename from impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java index fb81afc1..5195095e 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfResetCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.physicschunk.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -9,9 +9,9 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; import javax.annotation.Nonnull; -public class WorldCollisionPerfResetCommand extends AbstractWorldCommand { +public class PhysicsChunkPerfResetCommand extends AbstractWorldCommand { - public WorldCollisionPerfResetCommand() { + public PhysicsChunkPerfResetCommand() { super("reset", "Reset Impulse runtime profiling counters"); } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java similarity index 84% rename from impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java index 278d4c74..0a053758 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.physicschunk.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -9,9 +9,9 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; import javax.annotation.Nonnull; -public class WorldCollisionPerfToggleCommand extends AbstractWorldCommand { +public class PhysicsChunkPerfToggleCommand extends AbstractWorldCommand { - public WorldCollisionPerfToggleCommand() { + public PhysicsChunkPerfToggleCommand() { super("toggle", "Toggle Impulse runtime profiling"); } diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java similarity index 93% rename from impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index 74720015..c21d28a3 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.physicschunk.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -23,11 +23,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class WorldCollisionSettingsCommand extends AbstractAsyncPlayerCommand { +public class PhysicsChunkSettingsCommand extends AbstractAsyncPlayerCommand { private final OptionalArg modeArg = this.withOptionalArg( "mode", - "World collision mode: none, manual, or streaming", + "PhysicsChunk mode: none, manual, or streaming", ArgTypes.STRING); private final OptionalArg playerRadiusArg = this.withOptionalArg( "playerRadius", @@ -53,15 +53,15 @@ public class WorldCollisionSettingsCommand extends AbstractAsyncPlayerCommand { ArgTypes.STRING); private final OptionalArg terrainArg = this.withOptionalArg( "terrain", - "World collision terrain collider: boxes or native_voxels", + "PhysicsChunk terrain collider: boxes or native_voxels", ArgTypes.STRING); private final OptionalArg spaceArg = this.withOptionalArg( "space", "Physics space id to target", ArgTypes.INTEGER); - public WorldCollisionSettingsCommand() { - super("settings", "Get or set world collision streaming settings for a physics space"); + public PhysicsChunkSettingsCommand() { + super("settings", "Get or set PhysicsChunk streaming settings for a physics space"); } @Nonnull @@ -72,8 +72,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { Store physicsStore = PhysicsThreading.store(world); - WorldCollisionSpaceSelection.Selection selection = - WorldCollisionSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); + PhysicsChunkSpaceSelection.Selection selection = + PhysicsChunkSpaceSelection.resolve(ctx, world, spaceArg, physicsStore); if (selection == null) { return CompletableFuture.completedFuture(null); } @@ -166,7 +166,7 @@ private static boolean outOfRange(int value, int maxValue) { private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { - ctx.sender().sendMessage(Message.raw("Impulse world collision settings for space " + ctx.sender().sendMessage(Message.raw("Impulse PhysicsChunk settings for space " + spaceId.value() + ": mode=" + settings.getWorldCollisionSettings().getWorldCollisionMode().name().toLowerCase(Locale.ROOT) + " playerRadius=" + settings.getWorldCollisionSettings().getWorldCollisionRadius() diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java similarity index 93% rename from impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java index c2054182..fdcdcd07 100644 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionSpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.physicschunk.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -13,9 +13,9 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -final class WorldCollisionSpaceSelection { +final class PhysicsChunkSpaceSelection { - private WorldCollisionSpaceSelection() { + private PhysicsChunkSpaceSelection() { } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java index 1d54a534..cd45a1ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelCollisionCache.BuildStats; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java index 9f44d3da..6a595418 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java @@ -17,9 +17,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreTerrainMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionStreamingBounds; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; @@ -66,7 +66,7 @@ public final class PhysicsStoreWorldCollisionProducerSystem extends TickingSyste @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { - if (!WorldCollisionLifecycle.isEnabled()) { + if (!PhysicsChunkLifecycle.isEnabled()) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityDiagnostics.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityDiagnostics.java index 3ce78218..2f6d3f91 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/diagnostics/PhysicsEntityDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityDiagnostics.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.diagnostics; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Store; diff --git a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java similarity index 80% rename from impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java index 116e5751..f935af54 100644 --- a/impulse-physics-entity/src/main/java/dev/hytalemodding/impulse/physicsentity/ImpulsePhysicsEntityPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java @@ -1,19 +1,18 @@ -package dev.hytalemodding.impulse.physicsentity; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; /** * Plugin module that integrates authoritative PhysicsStore bodies with EntityStore entities. */ -public final class ImpulsePhysicsEntityPlugin extends JavaPlugin { +public final class PhysicsEntityModule extends JavaPlugin { - public ImpulsePhysicsEntityPlugin(@Nonnull JavaPluginInit init) { + public PhysicsEntityModule(@Nonnull JavaPluginInit init) { super(init); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 343d402e..ffea63d2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -19,7 +19,6 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; @@ -32,8 +31,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsWorldCollisionRuntime; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; -import dev.hytalemodding.impulse.core.internal.store.integration.PhysicsStoreEarlyPluginProbe; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; +import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; @@ -117,7 +116,7 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { public PhysicsWorldRuntimeResource() { ControlLifecycle.registerResource(this); - WorldCollisionLifecycle.registerResource(this); + PhysicsChunkLifecycle.registerResource(this); } @Nonnull @@ -1062,7 +1061,7 @@ private PhysicsStoreWorldCollisionStreamingResource authoritativeWorldCollisionS } private void clearAuthoritativeWorldCollisionStreaming(@Nonnull Store store) { - if (!WorldCollisionLifecycle.isEnabled() || owningStore == null) { + if (!PhysicsChunkLifecycle.isEnabled() || owningStore == null) { return; } PhysicsTerrainMutationQueueResource queue = @@ -1074,7 +1073,7 @@ private void clearAuthoritativeWorldCollisionStreaming(@Nonnull Store store, @Nonnull UUID spaceUuid) { int removed = 0; - if (WorldCollisionLifecycle.isEnabled() && owningStore != null) { + if (PhysicsChunkLifecycle.isEnabled() && owningStore != null) { PhysicsTerrainMutationQueueResource queue = store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); removed = authoritativeWorldCollisionStreaming().clearSpace(spaceUuid, queue); @@ -1084,21 +1083,21 @@ private int clearAuthoritativeWorldCollisionSpace(@Nonnull Store s return removed != 0 ? removed : directlyRemoved; } - public void disableWorldCollisionLifecycle() { + public void disablePhysicsChunkLifecycle() { if (isAuthoritativePhysicsStoreActive()) { return; } try { - runDirectRuntimeMutation("disable world collision lifecycle", this::disableWorldCollisionLifecycleDirect); + runDirectRuntimeMutation("disable PhysicsChunk lifecycle", this::disablePhysicsChunkLifecycleDirect); } catch (RejectedExecutionException ignored) { // The server can unload the subplugin after the store tick lane has already closed. } catch (RuntimeException exception) { - LOGGER.at(Level.WARNING).log("Failed to disable world collision lifecycle: %s", + LOGGER.at(Level.WARNING).log("Failed to disable PhysicsChunk lifecycle: %s", exception.getMessage()); } } - private void disableWorldCollisionLifecycleDirect() { + private void disablePhysicsChunkLifecycleDirect() { collisionRuntime.clearRetainedTerrain(spaceRuntime.getBindings()); restoreCollisionLodFiltersDirect(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsResource.java index ca356ad4..1ae0b2e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsResource.java @@ -4,6 +4,8 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import lombok.Getter; +import lombok.Setter; import javax.annotation.Nonnull; /** @@ -13,6 +15,8 @@ public final class PhysicsWorldSettingsResource implements Resource worldCollisionCommand, + public static void registerPhysicsChunkCommands( + @Nonnull Supplier physicsChunkCommand, @Nonnull Supplier collisionLodSettingsCommand) { ImpulseCommandContributionRegistry.addRootAndSettingsSubCommands( - WORLD_COLLISION_ROOT_COMMAND_ID, - Objects.requireNonNull(worldCollisionCommand, "worldCollisionCommand"), + PHYSICS_CHUNK_ROOT_COMMAND_ID, + Objects.requireNonNull(physicsChunkCommand, "physicsChunkCommand"), COLLISION_LOD_SETTINGS_COMMAND_ID, Objects.requireNonNull(collisionLodSettingsCommand, "collisionLodSettingsCommand")); } - public static void unregisterWorldCollisionCommands() { + public static void unregisterPhysicsChunkCommands() { ImpulseCommandContributionRegistry.removeRootAndSettingsSubCommands( - WORLD_COLLISION_ROOT_COMMAND_ID, + PHYSICS_CHUNK_ROOT_COMMAND_ID, COLLISION_LOD_SETTINGS_COMMAND_ID); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index c8575891..dd80b34a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; @@ -30,15 +30,15 @@ private PhysicsWorldCollision() { } public static void enableModule() { - WorldCollisionLifecycle.enable(); + PhysicsChunkLifecycle.enable(); } public static void disableModule() { - WorldCollisionLifecycle.disable(); + PhysicsChunkLifecycle.disable(); } public static boolean isModuleEnabled() { - return WorldCollisionLifecycle.isEnabled(); + return PhysicsChunkLifecycle.isEnabled(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java index 687cdd6f..32bc3971 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java @@ -14,9 +14,9 @@ private PhysicsEntityDiagnostics() { @Nonnull public static Snapshot collect(@Nonnull Store store) { - dev.hytalemodding.impulse.core.internal.diagnostics.PhysicsEntityDiagnostics.Snapshot + dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityDiagnostics.Snapshot snapshot = - dev.hytalemodding.impulse.core.internal.diagnostics.PhysicsEntityDiagnostics.collect( + dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityDiagnostics.collect( store); return new Snapshot(snapshot.physicsBodyEntities(), snapshot.persistentPhysicsBodyEntities(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java index 8edbc8a1..0bdc7178 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java @@ -3,6 +3,6 @@ * *

    Types under this package tree are the preferred import surface for * third-party Hytale plugins. Backend-neutral physics contracts remain in the - * {@code impulse-api} module.

    + * {@code impulse-backend-api} module.

    */ package dev.hytalemodding.impulse.core.plugin; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java index 8f7a912d..f84df80e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java @@ -20,9 +20,11 @@ private PhysicsAsync() { public static CompletableFuture acceptOnWorldThread(@Nonnull World world, @Nonnull CompletionStage stage, @Nonnull Consumer consumer) { + Objects.requireNonNull(world, "world"); Objects.requireNonNull(stage, "stage"); Objects.requireNonNull(consumer, "consumer"); + CompletableFuture completion = new CompletableFuture<>(); stage.whenComplete((value, failure) -> { if (failure != null) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java index 94f54bb9..b3b0f2de 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java @@ -14,10 +14,10 @@ void resetRegistry() { } @Test - void coreRootDoesNotOwnWorldCollisionCommandsByDefault() { + void coreRootDoesNotOwnPhysicsChunkCommandsByDefault() { ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); - assertFalse(root.getSubCommands().containsKey("worldcollision")); + assertFalse(root.getSubCommands().containsKey("physicschunk")); assertFalse(settings(root).getSubCommands().containsKey("collision-lod")); } diff --git a/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java similarity index 57% rename from impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java index 916a3c94..b195b8ee 100644 --- a/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.server.core.command.system.AbstractCommand; -import dev.hytalemodding.impulse.physicschunk.commands.WorldCollisionCommandContributions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -16,31 +16,31 @@ void resetRegistry() { } @Test - void worldCollisionContributesCommandsUnderImpulseRoot() { - WorldCollisionCommandContributions.register(); + void physicsChunkContributesCommandsUnderImpulseRoot() { + PhysicsChunkCommandContributions.register(); ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); - AbstractCommand worldCollision = root.getSubCommands().get("worldcollision"); - assertTrue(root.getSubCommands().containsKey("worldcollision")); - assertTrue(worldCollision.getSubCommands().containsKey("settings")); - assertTrue(worldCollision.getSubCommands().containsKey("perf")); + AbstractCommand physicsChunk = root.getSubCommands().get("physicschunk"); + assertTrue(root.getSubCommands().containsKey("physicschunk")); + assertTrue(physicsChunk.getSubCommands().containsKey("settings")); + assertTrue(physicsChunk.getSubCommands().containsKey("perf")); assertTrue(settings(root).getSubCommands().containsKey("collision-lod")); } @Test - void worldCollisionContributionsAreIdempotentAndRemovable() { - WorldCollisionCommandContributions.register(); - WorldCollisionCommandContributions.register(); + void physicsChunkContributionsAreIdempotentAndRemovable() { + PhysicsChunkCommandContributions.register(); + PhysicsChunkCommandContributions.register(); ImpulseCommand contributed = ImpulseCommandContributionRegistry.createRootCommandForTests(); - assertTrue(contributed.getSubCommands().containsKey("worldcollision")); + assertTrue(contributed.getSubCommands().containsKey("physicschunk")); assertTrue(settings(contributed).getSubCommands().containsKey("collision-lod")); - WorldCollisionCommandContributions.unregister(); + PhysicsChunkCommandContributions.unregister(); ImpulseCommand removed = ImpulseCommandContributionRegistry.createRootCommandForTests(); - assertFalse(removed.getSubCommands().containsKey("worldcollision")); + assertFalse(removed.getSubCommands().containsKey("physicschunk")); assertFalse(settings(removed).getSubCommands().containsKey("collision-lod")); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycleTest.java similarity index 54% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycleTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycleTest.java index b5800bfc..3319a531 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycleTest.java @@ -7,27 +7,27 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class WorldCollisionLifecycleTest { +class PhysicsChunkLifecycleTest { @BeforeEach @AfterEach void disableLifecycle() { - WorldCollisionLifecycle.disable(); + PhysicsChunkLifecycle.disable(); } @Test void lifecycleStartsDisabled() { - assertFalse(WorldCollisionLifecycle.isEnabled()); + assertFalse(PhysicsChunkLifecycle.isEnabled()); } @Test void lifecycleGenerationChangesWhenLifecycleIsDisabled() { - WorldCollisionLifecycle.enable(); - long enabledGeneration = WorldCollisionLifecycle.generation(); + PhysicsChunkLifecycle.enable(); + long enabledGeneration = PhysicsChunkLifecycle.generation(); - WorldCollisionLifecycle.disable(); + PhysicsChunkLifecycle.disable(); - assertFalse(WorldCollisionLifecycle.isEnabled()); - assertTrue(WorldCollisionLifecycle.generation() > enabledGeneration); + assertFalse(PhysicsChunkLifecycle.isEnabled()); + assertTrue(PhysicsChunkLifecycle.generation() > enabledGeneration); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java index 3f9f5fe4..ac4da634 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java @@ -15,10 +15,6 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.CombineCall; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.VoxelTerrainCall; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionStreamingBounds; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; @@ -42,7 +38,7 @@ class WorldVoxelCollisionCacheTest { @Test void streamingApplyGateAllowsOnlyOnePendingMutation() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); + VoxelCollisionCache cache = new VoxelCollisionCache(); assertFalse(cache.isStreamingApplyPending()); assertTrue(cache.tryBeginStreamingApply()); @@ -57,8 +53,8 @@ void streamingApplyGateAllowsOnlyOnePendingMutation() { @Test void copyFromDoesNotInheritPendingStreamingApply() { - WorldVoxelCollisionCache source = new WorldVoxelCollisionCache(); - WorldVoxelCollisionCache target = new WorldVoxelCollisionCache(); + VoxelCollisionCache source = new VoxelCollisionCache(); + VoxelCollisionCache target = new VoxelCollisionCache(); assertTrue(source.tryBeginStreamingApply()); @@ -70,8 +66,8 @@ void copyFromDoesNotInheritPendingStreamingApply() { @Test void copyFromDeepCopiesCachedSectionBodyIds() throws Exception { RuntimeFixture fixture = runtimeFixture("test:copy-section-isolation", true); - WorldVoxelCollisionCache source = new WorldVoxelCollisionCache(); - WorldVoxelCollisionCache target = new WorldVoxelCollisionCache(); + VoxelCollisionCache source = new VoxelCollisionCache(); + VoxelCollisionCache target = new VoxelCollisionCache(); Object spaceCache = newSpaceCollisionCache(); Object sourceSection = newCachedSection(1, 2, 3); long copiedBodyId = createVoxelTerrain(fixture); @@ -91,7 +87,7 @@ void copyFromDeepCopiesCachedSectionBodyIds() throws Exception { @Test void bodyTargetCacheRefreshesActiveBodiesEveryFourTicks() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); + VoxelCollisionCache cache = new VoxelCollisionCache(); WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1001); @@ -119,7 +115,7 @@ void bodyTargetCacheRefreshesActiveBodiesEveryFourTicks() { @Test void bodyTargetCacheRefreshesSleepingBodiesOnTtlBoundedInterval() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); + VoxelCollisionCache cache = new VoxelCollisionCache(); WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1002); @@ -143,7 +139,7 @@ void bodyTargetCacheRefreshesSleepingBodiesOnTtlBoundedInterval() { @Test void bodyTargetCacheRefreshesImmediatelyWhenBoundsChange() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); + VoxelCollisionCache cache = new VoxelCollisionCache(); WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1003); @@ -181,7 +177,7 @@ void bodyTargetCacheRefreshesImmediatelyWhenBoundsChange() { @Test void bodyTargetRefreshIsNotConsumedUntilTerrainApplyRecordsIt() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); + VoxelCollisionCache cache = new VoxelCollisionCache(); SpaceId spaceId = new SpaceId(1005); UUID bodyId = bodyId(5); WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); @@ -199,7 +195,7 @@ void bodyTargetRefreshIsNotConsumedUntilTerrainApplyRecordsIt() { @Test void bodyTargetCachePrunesBodiesThatDisappearPastDoubleTtl() { - WorldVoxelCollisionCache cache = new WorldVoxelCollisionCache(); + VoxelCollisionCache cache = new VoxelCollisionCache(); WorldCollisionProfilingResource.Snapshot snapshot = new WorldCollisionProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1004); @@ -237,7 +233,7 @@ void absentVoxelCapabilityFallsBackToMergedFullCubeBoxes() throws Exception { 0, 0)); - WorldVoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); + VoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); assertEquals(2, fixture.runtime().bodyCount(fixture.backendSpaceId())); assertEquals(0, fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()).size()); assertFalse(debugSection.voxelTerrain()); @@ -260,7 +256,7 @@ void supportedRuntimeCreatesVoxelTerrainAndKeepsDebugBoxes() throws Throwable { addGeometryBodies(fixture.binding(), cachedSection, geometry, 2, 3, 4); List calls = fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()); - WorldVoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); + VoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); assertEquals(1, calls.size()); assertArrayEquals(new int[] {0, 0, 0, 1, 0, 0}, calls.getFirst().voxelCoordinates()); assertEquals((float) (2 << ChunkUtil.BITS), calls.getFirst().positionX()); @@ -338,7 +334,7 @@ void disabledNativeVoxelTerrainFallsBackToMergedFullCubeBoxes() throws Throwable addGeometryBodies(fixture.binding(), cachedSection, geometry, 0, 0, 0, false); - WorldVoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); + VoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); assertEquals(1, fixture.runtime().bodyCount(fixture.backendSpaceId())); assertEquals(0, fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()).size()); assertFalse(debugSection.voxelTerrain()); @@ -396,7 +392,7 @@ void stitchesVoxelTerrainToSixAdjacentSections() throws Throwable { @Test void clearSectionsAroundKeepsDistantCachedTerrain() throws Exception { RuntimeFixture fixture = runtimeFixture("test:section-radius-clear", true); - WorldVoxelCollisionCache worldCache = new WorldVoxelCollisionCache(); + VoxelCollisionCache worldCache = new VoxelCollisionCache(); Object spaceCache = newSpaceCollisionCache(); Object near = newCachedSection(0, 2, 0); long nearBody = createVoxelTerrain(fixture); @@ -475,7 +471,7 @@ private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, int sectionY, int chunkZ, @Nonnull WorldCollisionBuildOptions buildOptions) throws Throwable { - Method method = Arrays.stream(WorldVoxelCollisionCache.class.getDeclaredMethods()) + Method method = Arrays.stream(VoxelCollisionCache.class.getDeclaredMethods()) .filter(candidate -> candidate.getName().equals("addGeometryBodies")) .findFirst() .orElseThrow(); @@ -534,7 +530,7 @@ private static Object[] addGeometryBodiesArguments(@Nonnull Method method, private static void stitchAdjacentVoxelTerrains(@Nonnull PhysicsSpaceBinding space, @Nonnull Object cache, @Nonnull Object built) throws Throwable { - Method method = Arrays.stream(WorldVoxelCollisionCache.class.getDeclaredMethods()) + Method method = Arrays.stream(VoxelCollisionCache.class.getDeclaredMethods()) .filter(candidate -> candidate.getName().equals("stitchAdjacentVoxelTerrains")) .findFirst() .orElseThrow(); @@ -549,7 +545,7 @@ private static void stitchAdjacentVoxelTerrains(@Nonnull PhysicsSpaceBinding spa private static void removeBuiltSectionAfterFailure(@Nonnull PhysicsSpaceBinding space, @Nonnull Object built, @Nonnull RuntimeException failure) throws Throwable { - Method method = WorldVoxelCollisionCache.class.getDeclaredMethod("removeBuiltSectionAfterFailure", + Method method = VoxelCollisionCache.class.getDeclaredMethod("removeBuiltSectionAfterFailure", PhysicsSpaceBinding.class, nestedClass("CachedSection"), RuntimeException.class); @@ -570,7 +566,7 @@ private static Object newSpaceCollisionCache() throws Exception { @SuppressWarnings("unchecked") private static void putCachedSection(@Nonnull Object cache, @Nonnull Object section) throws Exception { - Method keyMethod = WorldVoxelCollisionCache.class.getDeclaredMethod("packSectionKey", + Method keyMethod = VoxelCollisionCache.class.getDeclaredMethod("packSectionKey", int.class, int.class, int.class); @@ -585,10 +581,10 @@ private static void putCachedSection(@Nonnull Object cache, @Nonnull Object sect } @SuppressWarnings("unchecked") - private static void putSpaceCache(@Nonnull WorldVoxelCollisionCache worldCache, + private static void putSpaceCache(@Nonnull VoxelCollisionCache worldCache, @Nonnull SpaceId spaceId, @Nonnull Object cache) throws Exception { - Field spacesField = WorldVoxelCollisionCache.class.getDeclaredField("spaces"); + Field spacesField = VoxelCollisionCache.class.getDeclaredField("spaces"); spacesField.setAccessible(true); ((Map) spacesField.get(worldCache)).put(spaceId.value(), cache); } @@ -646,10 +642,10 @@ private static long voxelTerrainBodyId(@Nonnull Object section) throws Exception return field.getLong(section); } - private static WorldVoxelCollisionCache.DebugSection debugSection(@Nonnull Object section) throws Exception { + private static VoxelCollisionCache.DebugSection debugSection(@Nonnull Object section) throws Exception { Method method = section.getClass().getDeclaredMethod("debugSection"); method.setAccessible(true); - return (WorldVoxelCollisionCache.DebugSection) method.invoke(section); + return (VoxelCollisionCache.DebugSection) method.invoke(section); } private static int intField(@Nonnull Object target, @Nonnull String name) throws Exception { @@ -676,7 +672,7 @@ private static void setLongField(@Nonnull Object target, @Nonnull private static Class nestedClass(@Nonnull String simpleName) { - return Arrays.stream(WorldVoxelCollisionCache.class.getDeclaredClasses()) + return Arrays.stream(VoxelCollisionCache.class.getDeclaredClasses()) .filter(candidate -> candidate.getSimpleName().equals(simpleName)) .findFirst() .orElseThrow(); diff --git a/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java similarity index 86% rename from impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java index def3a689..3a0edae3 100644 --- a/impulse-physics-chunk/src/test/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfReportCommandTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.physicschunk.commands; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -7,7 +7,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.StepDrainSnapshotView; import org.junit.jupiter.api.Test; -class WorldCollisionPerfReportCommandTest { +class PhysicsChunkPerfReportCommandTest { @Test void preStepDrainSummaryReportsAverageLatestAndMaxBackpressure() { @@ -26,15 +26,15 @@ void preStepDrainSummaryReportsAverageLatestAndMaxBackpressure() { assertEquals("Physics pre-step drain avg completedStep drained/runMs/lateBacklog=3.0/6.000/2.0 " + "latest drained/lateBacklog=4/3 max drained/lateBacklog=4/3", - WorldCollisionPerfReportCommand.formatPreStepDrainSummary(cumulative, latest)); + PhysicsChunkPerfReportCommand.formatPreStepDrainSummary(cumulative, latest)); } @Test void preStepDrainSummaryRequiresCompletedStepSamples() { - assertFalse(WorldCollisionPerfReportCommand.hasCompletedStepSamples( + assertFalse(PhysicsChunkPerfReportCommand.hasCompletedStepSamples( new StepDrainSample(0, 0, 0, 0L, 0, 0))); - assertTrue(WorldCollisionPerfReportCommand.hasCompletedStepSamples( + assertTrue(PhysicsChunkPerfReportCommand.hasCompletedStepSamples( new StepDrainSample(1, 1, 1, 2_000_000L, 0, 0))); } @@ -55,7 +55,7 @@ void preStepDrainSummaryUsesLatestCompletedStepAfterSkippedPendingTick() { assertEquals("Physics pre-step drain avg completedStep drained/runMs/lateBacklog=4.0/8.000/3.0 " + "latest drained/lateBacklog=4/3 max drained/lateBacklog=4/3", - WorldCollisionPerfReportCommand.formatPreStepDrainSummary(cumulative, + PhysicsChunkPerfReportCommand.formatPreStepDrainSummary(cumulative, latestCompleted)); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java index f49e2e61..c1364792 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java @@ -9,8 +9,7 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldVoxelCollisionCache.BuildStats; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelCollisionCache.BuildStats; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.MissingSectionReason; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.junit.jupiter.api.AfterEach; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java index 163b3829..156cff50 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java @@ -16,8 +16,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugContactView; -import dev.hytalemodding.impulse.core.internal.simulation.view.PhysicsDebugJointView; import java.util.concurrent.CompletableFuture; import java.util.List; import java.util.UUID; diff --git a/impulse-examples/build.gradle.kts b/impulse-examples/build.gradle.kts index b5d6bf10..d5194595 100644 --- a/impulse-examples/build.gradle.kts +++ b/impulse-examples/build.gradle.kts @@ -7,7 +7,7 @@ plugins { version = rootProject.version dependencies { - implementation(project(":impulse-api")) + implementation(project(":impulse-backend-api")) compileOnly(project(":impulse-core")) compileOnly(project(":impulse-early-plugin")) testImplementation(project(":impulse-core")) @@ -38,5 +38,5 @@ hytaleTools { modUrl = property("mod_website") as String modDescription = "Example plugins for Impulse" manifestServerVersion = property("hytale_version") as String - manifestDependencies = "HytaleModding:Impulse=*,HytaleModding:ImpulsePhysicsEntity=*,HytaleModding:ImpulsePhysicsChunk=*" + manifestDependencies = "HytaleModding:Impulse=*" } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java index 797d175f..6266d091 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java @@ -21,7 +21,7 @@ public ImpulseCommand() { addSubCommand(new GrabCommand()); addSubCommand(new ReleaseCommand()); addSubCommand(new PersistenceCommand()); - addSubCommand(new WorldCollisionCommand()); + addSubCommand(new PhysicsChunkCommand()); addSubCommand(new StressCommand()); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkCommand.java similarity index 97% rename from impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java rename to impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkCommand.java index bfea232a..df206910 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/WorldCollisionCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkCommand.java @@ -28,10 +28,10 @@ /** * Debug commands for manually building/clearing world voxel collision. */ -public class WorldCollisionCommand extends AbstractCommandCollection { +public class PhysicsChunkCommand extends AbstractCommandCollection { - public WorldCollisionCommand() { - super("world-collision", "Build static Impulse voxel collision from nearby world blocks"); + public PhysicsChunkCommand() { + super("physicschunk", "Build static Impulse chunk collision from nearby world blocks"); addSubCommand(new BuildCommand()); addSubCommand(new EnsureCommand()); addSubCommand(new ClearCommand()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index abee4ac0..3a47e1b8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -129,9 +129,9 @@ private static void spawnBenchmark(@Nonnull CommandContext ctx, + ". Body-count updates are visible after PhysicsStore binds the new entities" + ". This command measures raw setup/entity attachment; use /impulse-examples stress bodies" + " for detached/detached-view scalability scenarios" - + ". For clean comparisons run /impulse clean, /impulse worldcollision perf reset," - + " /impulse worldcollision perf toggle before spawning," - + " then /impulse worldcollision perf report.")); + + ". For clean comparisons run /impulse clean, /impulse physicschunk perf reset," + + " /impulse physicschunk perf toggle before spawning," + + " then /impulse physicschunk perf report.")); } } diff --git a/impulse-physics-chunk/.gitignore b/impulse-physics-chunk/.gitignore deleted file mode 100644 index 8c25fdb9..00000000 --- a/impulse-physics-chunk/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/src/main/resources/manifest.json diff --git a/impulse-physics-chunk/build.gradle.kts b/impulse-physics-chunk/build.gradle.kts deleted file mode 100644 index 0584273d..00000000 --- a/impulse-physics-chunk/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -import org.gradle.api.tasks.testing.Test - -plugins { - id("com.azuredoom.hytale-tools") -} - -version = rootProject.version -val impulsePhysicsChunkDependencies = listOf( - "HytaleModding:Impulse=*", - "Hytale:AssetModule=*", - "Hytale:BlockTypeModule=*", - "Hytale:EntityModule=*", - "Hytale:LegacyModule=*" -).joinToString(",") - -dependencies { - compileOnly(project(":impulse-api")) - compileOnly(project(":impulse-core")) - compileOnly(project(":impulse-early-plugin")) - testImplementation(project(":impulse-api")) - testImplementation(project(":impulse-core")) - testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") - testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") -} - -tasks.withType().configureEach { - jvmArgs("-Djava.util.logging.manager=com.hypixel.hytale.logger.backend.HytaleLogManager") -} - -hytaleTools { - modId = "ImpulsePhysicsChunk" - mainClass = "dev.hytalemodding.impulse.physicschunk.ImpulsePhysicsChunkPlugin" - modCredits = property("mod_credits") as String - modUrl = property("mod_website") as String - modDescription = "Impulse ChunkStore world-collision integration" - manifestServerVersion = property("hytale_version") as String - manifestDependencies = impulsePhysicsChunkDependencies -} diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommand.java deleted file mode 100644 index 516b1081..00000000 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommand.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.hytalemodding.impulse.physicschunk.commands; - -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; - -public final class WorldCollisionCommand extends AbstractCommandCollection { - - public WorldCollisionCommand() { - super("worldcollision", "Impulse world-collision module commands"); - addSubCommand(new WorldCollisionSettingsCommand()); - addSubCommand(new WorldCollisionPerfCommand()); - } -} diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java deleted file mode 100644 index 5e7d967d..00000000 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionCommandContributions.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.physicschunk.commands; - -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCommands; - -/** - * Command contributions owned by the world-collision subplugin. - */ -public final class WorldCollisionCommandContributions { - - private WorldCollisionCommandContributions() { - } - - public static void register() { - PhysicsChunkCommands.registerWorldCollisionCommands( - WorldCollisionCommand::new, - CollisionLodSettingsCommand::new); - } - - public static void unregister() { - PhysicsChunkCommands.unregisterWorldCollisionCommands(); - } -} diff --git a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfCommand.java b/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfCommand.java deleted file mode 100644 index 16e9d313..00000000 --- a/impulse-physics-chunk/src/main/java/dev/hytalemodding/impulse/physicschunk/commands/WorldCollisionPerfCommand.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.hytalemodding.impulse.physicschunk.commands; - -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; - -public final class WorldCollisionPerfCommand extends AbstractCommandCollection { - - public WorldCollisionPerfCommand() { - super("perf", "Impulse world-collision profiling commands"); - addSubCommand(new WorldCollisionPerfToggleCommand()); - addSubCommand(new WorldCollisionPerfReportCommand()); - addSubCommand(new WorldCollisionPerfResetCommand()); - } -} diff --git a/impulse-physics-entity/.gitignore b/impulse-physics-entity/.gitignore deleted file mode 100644 index 8c25fdb9..00000000 --- a/impulse-physics-entity/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/src/main/resources/manifest.json diff --git a/impulse-physics-entity/build.gradle.kts b/impulse-physics-entity/build.gradle.kts deleted file mode 100644 index d1ffba10..00000000 --- a/impulse-physics-entity/build.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - id("com.azuredoom.hytale-tools") -} - -version = rootProject.version -val impulsePhysicsEntityDependencies = listOf( - "HytaleModding:Impulse=*", - "Hytale:AssetModule=*", - "Hytale:BlockTypeModule=*", - "Hytale:EntityModule=*", - "Hytale:LegacyModule=*" -).joinToString(",") - -dependencies { - compileOnly(project(":impulse-core")) - compileOnly(project(":impulse-early-plugin")) - testImplementation(project(":impulse-core")) -} - -hytaleTools { - modId = "ImpulsePhysicsEntity" - mainClass = "dev.hytalemodding.impulse.physicsentity.ImpulsePhysicsEntityPlugin" - modCredits = property("mod_credits") as String - modUrl = property("mod_website") as String - modDescription = "Impulse EntityStore projection integration" - manifestServerVersion = property("hytale_version") as String - manifestDependencies = impulsePhysicsEntityDependencies -} diff --git a/impulse-rapier/build.gradle.kts b/impulse-rapier/build.gradle.kts index 934e17c7..b8af2a16 100644 --- a/impulse-rapier/build.gradle.kts +++ b/impulse-rapier/build.gradle.kts @@ -246,7 +246,7 @@ tasks.processResources { } dependencies { - api(project(":impulse-api")) + api(project(":impulse-backend-api")) implementation(project(":impulse-native-loader")) @@ -254,7 +254,7 @@ dependencies { fun runtimeClasspathWithoutBundledApi(): FileCollection { return configurations.runtimeClasspath.get() - .filter { file -> !file.name.startsWith("impulse-api-") } + .filter { file -> !file.name.startsWith("impulse-backend-api-") } } fun Jar.expandRuntimeClasspath(runtimeClasspath: FileCollection) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 923c87fa..57e1feed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,12 +26,10 @@ dependencyResolutionManagement { rootProject.name = "impulse" -include("impulse-api") +include("impulse-backend-api") include("impulse-native-loader") include("impulse-bullet") include("impulse-rapier") include("impulse-core") -include("impulse-physics-entity") -include("impulse-physics-chunk") include("impulse-examples") include("impulse-early-plugin") From 23a87e0b5fe89ca3e2f1b3dc8fee0234cdb1f3db Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 18:50:50 +0200 Subject: [PATCH 350/534] refactor(core): align physics subplugin ownership Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 10 +-- .../core/internal/commands/SpaceCommand.java | 2 +- .../internal/commands/debug/DebugCommand.java | 6 +- .../commands/settings/SettingsCommand.java | 1 - ...tachedStreamingBenchmarkCrucibleTests.java | 24 ++--- ...pulseRapierBodyBenchmarkCrucibleTests.java | 8 +- .../PhysicsStoreBenchmarkQueries.java | 4 +- ...ons.java => PhysicsChunkBuildOptions.java} | 16 ++-- .../physicschunk/PhysicsChunkModule.java | 4 +- .../PhysicsChunkMutationCache.java | 46 +++++----- ....java => PhysicsChunkStreamingBounds.java} | 10 +-- ...e.java => PhysicsChunkTerrainRuntime.java} | 56 ++++++------ ...PhysicsChunkTerrainStreamingResource.java} | 44 ++++----- .../PhysicsStoreTerrainMutations.java | 8 +- .../physicschunk/SectionBlockReader.java | 4 +- .../physicschunk/SectionColliderBuilder.java | 4 +- ...e.java => VoxelTerrainCollisionCache.java} | 48 +++++----- .../commands/PhysicsChunkPerfCommand.java | 2 +- ...ava => PhysicsChunkProfilingResource.java} | 20 ++--- ...=> PhysicsChunkTerrainProducerSystem.java} | 56 ++++++------ .../physicsentity/PhysicsEntityModule.java | 3 + .../PhysicsEntityCommandContributions.java | 20 +++++ .../VisualMaterializationSettingsCommand.java | 2 +- .../commands}/VisualSettingsCommand.java | 3 +- .../commands}/VisualSyncSettingsCommand.java | 3 +- .../impulse/core/internal/package-info.java | 2 +- .../PersistentPhysicsStorePreflight.java | 6 +- .../PhysicsStoreRuntimeCleaner.java | 4 +- .../PhysicsStoreRegistration.java | 10 +-- ...=> PhysicsChunkSettingsIndexResource.java} | 32 +++---- .../resources/PhysicsDebugResource.java | 50 +++++------ .../resources/PhysicsResourceTypes.java | 6 +- .../resources/PhysicsSpaceRuntime.java | 2 +- .../PhysicsWorldRuntimeResource.java | 30 +++---- .../body/PhysicsBodySpatialIndex.java | 2 +- ...a => PhysicsChunkSettingsIndexSystem.java} | 16 ++-- .../internal/systems/SpaceBindingSystem.java | 2 +- .../debug/PhysicsChunkDebugSectionView.java | 21 +++++ .../systems/debug/PhysicsDebugSystem.java | 84 ++++++++--------- ...PhysicsDebugWorldCollisionSectionView.java | 21 ----- .../debug/PhysicsStoreDebugQueries.java | 24 ++--- .../terrain/TerrainColliderMutation.java | 2 +- .../components/WorldCollisionComponent.java | 2 +- .../physicschunk/PhysicsChunkTypes.java | 24 ++--- .../physicschunk/PhysicsWorldCollision.java | 30 +++---- .../PhysicsWorldCollisionProfiling.java | 24 ++--- .../WorldCollisionBuildStats.java | 2 +- .../physicschunk/WorldCollisionMode.java | 4 +- .../WorldCollisionPrewarmStats.java | 2 +- .../physicschunk/WorldCollisionStats.java | 2 +- .../modules/physicschunk/package-info.java | 2 +- .../physicsentity/PhysicsEntityCommands.java | 29 ++++++ .../plugin/settings/PhysicsSpaceSettings.java | 4 +- .../PhysicsWorldCollisionSettings.java | 6 +- ...mpulseCommandContributionRegistryTest.java | 1 + ...EntityCommandContributionRegistryTest.java | 47 ++++++++++ ...a => PhysicsChunkStreamingBoundsTest.java} | 18 ++-- ...va => VoxelTerrainCollisionCacheTest.java} | 90 +++++++++---------- ...=> PhysicsChunkProfilingResourceTest.java} | 54 +++++------ .../examples/commands/ImpulseCommand.java | 2 +- ...d.java => PhysicsChunkExampleCommand.java} | 4 +- .../commands/PhysicsStoreExampleCommands.java | 2 +- 62 files changed, 582 insertions(+), 485 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{WorldCollisionBuildOptions.java => PhysicsChunkBuildOptions.java} (69%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{WorldCollisionStreamingBounds.java => PhysicsChunkStreamingBounds.java} (80%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{PhysicsWorldCollisionRuntime.java => PhysicsChunkTerrainRuntime.java} (74%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{PhysicsStoreWorldCollisionStreamingResource.java => PhysicsChunkTerrainStreamingResource.java} (83%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{VoxelCollisionCache.java => VoxelTerrainCollisionCache.java} (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/{WorldCollisionProfilingResource.java => PhysicsChunkProfilingResource.java} (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/{PhysicsStoreWorldCollisionProducerSystem.java => PhysicsChunkTerrainProducerSystem.java} (86%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{commands/settings => modules/physicsentity/commands}/VisualMaterializationSettingsCommand.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{commands/settings => modules/physicsentity/commands}/VisualSettingsCommand.java (80%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{commands/settings => modules/physicsentity/commands}/VisualSyncSettingsCommand.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/{PhysicsWorldCollisionIndexResource.java => PhysicsChunkSettingsIndexResource.java} (65%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{WorldCollisionIndexSystem.java => PhysicsChunkSettingsIndexSystem.java} (84%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsChunkDebugSectionView.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugWorldCollisionSectionView.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandContributionRegistryTest.java rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{WorldCollisionStreamingBoundsTest.java => PhysicsChunkStreamingBoundsTest.java} (69%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{WorldVoxelCollisionCacheTest.java => VoxelTerrainCollisionCacheTest.java} (88%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/{WorldCollisionProfilingResourceTest.java => PhysicsChunkProfilingResourceTest.java} (81%) rename impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/{PhysicsChunkCommand.java => PhysicsChunkExampleCommand.java} (98%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index e62f5675..2596e934 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -42,14 +42,14 @@ * Clears Impulse-owned runtime state from the target world. * *

    This removes Impulse-owned visual entities, detaches external physics attachments, - * clears runtime bodies, joints, and current world-collision cache bodies. Explicit - * physics spaces are kept, including their world-collision settings. Spaces with streaming - * world collision enabled may build fresh backend terrain bodies again on the next + * clears runtime bodies, joints, and current PhysicsChunk terrain cache bodies. Explicit + * physics spaces are kept, including their PhysicsChunk terrain settings. Spaces with streaming + * PhysicsChunk terrain enabled may build fresh backend terrain bodies again on the next * streaming tick.

    * *

    When a radius is provided, cleanup is intentionally narrower: it selects * registered body snapshots near the player, removes those bodies and their - * attachments/proxies, and leaves spaces plus the world-collision cache intact.

    + * attachments/proxies, and leaves spaces plus the PhysicsChunk terrain cache intact.

    */ public class CleanCommand extends AbstractWorldCommand { @@ -361,7 +361,7 @@ private static void sendCleanRadiusSuccess(@Nonnull CommandContext context, + result.removedBodies() + " runtime bodies, and " + removedEntities.get(REMOVED_SESSIONS) + " control sessions within radius " + radius + " in world " + worldName - + ". Kept explicit physics spaces and world-collision cache.")); + + ". Kept explicit physics spaces and PhysicsChunk terrain cache.")); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 628db601..f833dea1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -179,7 +179,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, SpaceId spaceId = selectedSpace.spaceId(); /* - * Backend-only bodies can be generated by systems such as streaming world collision. + * Backend-only bodies can be generated by systems such as streaming PhysicsChunk terrain. * Those bodies belong to the space/cache lifecycle and are removed when the space is * deleted. Registered bodies are gameplay/runtime resources addressed by durable body * UUID or live PhysicsStore entity ref, so they still require an explicit clean/destroy diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java index 64137c71..a63cc1cf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java @@ -20,8 +20,8 @@ public DebugCommand() { addSubCommand(new DebugFlagCommand("joints", "joint", PhysicsDebugResource::isDebugJointsEnabled, PhysicsDebugResource::setDebugJointsEnabled)); - addSubCommand(new DebugFlagCommand("world-collision", "world collision", - PhysicsDebugResource::isDebugWorldCollisionEnabled, - PhysicsDebugResource::setDebugWorldCollisionEnabled)); + addSubCommand(new DebugFlagCommand("physicschunk", "PhysicsChunk terrain", + PhysicsDebugResource::isDebugPhysicsChunkTerrainEnabled, + PhysicsDebugResource::setDebugPhysicsChunkTerrainEnabled)); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java index 6c6b4a6a..86b065c7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java @@ -10,7 +10,6 @@ public SettingsCommand() { super("settings", "Impulse runtime settings commands"); addSubCommand(new SimulationSettingsCommand()); addSubCommand(new SolverSettingsCommand()); - addSubCommand(new VisualSettingsCommand()); } public void addContribution(@Nonnull AbstractCommand command) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index bb791c13..7b1fdd99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -18,10 +18,10 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; @@ -55,7 +55,7 @@ import org.joml.Vector3f; /** - * Benchmark-oriented Crucible scenario for detached bodies using streamed world collision. + * Benchmark-oriented Crucible scenario for detached bodies using streamed PhysicsChunk terrain. */ @SuppressWarnings("SameParameterValue") final class ImpulseDetachedStreamingBenchmarkCrucibleTests { @@ -143,8 +143,8 @@ private static final class StageRunner { private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final WorldCollisionProfilingResource worldCollisionProfiling; - private final PhysicsStoreWorldCollisionStreamingResource worldCollisionStreaming; + private final PhysicsChunkProfilingResource worldCollisionProfiling; + private final PhysicsChunkTerrainStreamingResource worldCollisionStreaming; private final PhysicsWorldSettings previousWorldSettings; private final boolean previousPhysicsStoreProfilingEnabled; private final List retainedChunks = new ArrayList<>(); @@ -161,9 +161,9 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.worldCollisionProfiling = store.getResource( - WorldCollisionProfilingResource.getResourceType()); + PhysicsChunkProfilingResource.getResourceType()); this.worldCollisionStreaming = store.getResource( - PhysicsStoreWorldCollisionStreamingResource.getResourceType()); + PhysicsChunkTerrainStreamingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); } @@ -372,7 +372,7 @@ private PrewarmStats prewarmWorldCollision(@Nonnull SpaceId spaceId, int count) UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(physicsStore, spaceId); PhysicsTerrainMutationQueueResource queue = physicsStore.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); - WorldCollisionBuildOptions buildOptions = WorldCollisionBuildOptions.fromSettings( + PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( physics.getSpaceSettings(spaceId).getWorldCollisionSettings()); WorldCollisionPrewarmStats stats = worldCollisionStreaming.ensureAround(world, spaceUuid, @@ -434,7 +434,7 @@ private static void addPrewarmEnvelopeCentersAt(@Nonnull List centers, private void configureMissingSectionDiagnostics(@Nonnull BenchmarkChunks chunks) { LongSet sectionKeys = new LongOpenHashSet(); for (ChunkSection section : chunks.sections()) { - sectionKeys.add(WorldCollisionProfilingResource.packDiagnosticSectionKey( + sectionKeys.add(PhysicsChunkProfilingResource.packDiagnosticSectionKey( section.x(), section.y(), section.z())); @@ -951,7 +951,7 @@ private static final class SpaceStats { private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; private static SpaceStats collect(@Nonnull Store physicsStore, - @Nonnull PhysicsStoreWorldCollisionStreamingResource worldCollisionStreaming, + @Nonnull PhysicsChunkTerrainStreamingResource worldCollisionStreaming, @Nonnull SpaceId spaceId) { BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( physicsStore, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 2441e098..a1439c7e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; @@ -142,7 +142,7 @@ private static final class MatrixRunner { private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final WorldCollisionProfilingResource worldCollisionProfiling; + private final PhysicsChunkProfilingResource worldCollisionProfiling; private final PhysicsWorldSettings previousWorldSettings; private final boolean previousPhysicsStoreProfilingEnabled; private final boolean previousRuntimeProfilingEnabled; @@ -160,7 +160,7 @@ private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.worldCollisionProfiling = store.getResource( - WorldCollisionProfilingResource.getResourceType()); + PhysicsChunkProfilingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); this.previousRuntimeProfilingEnabled = runtimeProfiling.isEnabled(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index d46249c9..b3c2d575 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -7,7 +7,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -32,7 +32,7 @@ private PhysicsStoreBenchmarkQueries() { @Nonnull static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store store, - @Nullable PhysicsStoreWorldCollisionStreamingResource streaming, + @Nullable PhysicsChunkTerrainStreamingResource streaming, @Nonnull BenchmarkSpaceStatsRequest query) { PhysicsThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, query.spaceId()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java similarity index 69% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionBuildOptions.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index ecb71926..ef42e6b7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -5,16 +5,16 @@ import javax.annotation.Nonnull; /** - * Options that control generated world-collision backend geometry. + * Options that control generated PhysicsChunk terrain backend geometry. */ -public record WorldCollisionBuildOptions(@Nonnull TerrainColliderMode terrainColliderMode, +public record PhysicsChunkBuildOptions(@Nonnull TerrainColliderMode terrainColliderMode, float terrainFriction, float terrainRestitution) { - public static final WorldCollisionBuildOptions DEFAULT = + public static final PhysicsChunkBuildOptions DEFAULT = fromNativeVoxelTerrainEnabled(PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); - public WorldCollisionBuildOptions { + public PhysicsChunkBuildOptions { Objects.requireNonNull(terrainColliderMode, "terrainColliderMode"); if (!Float.isFinite(terrainFriction) || terrainFriction < 0.0f) { throw new IllegalArgumentException("terrainFriction must be finite and >= 0"); @@ -25,16 +25,16 @@ public record WorldCollisionBuildOptions(@Nonnull TerrainColliderMode terrainCol } @Nonnull - public static WorldCollisionBuildOptions fromSettings(@Nonnull PhysicsWorldCollisionSettings settings) { - return new WorldCollisionBuildOptions( + public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsWorldCollisionSettings settings) { + return new PhysicsChunkBuildOptions( TerrainColliderMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), settings.getTerrainFriction(), settings.getTerrainRestitution()); } @Nonnull - public static WorldCollisionBuildOptions fromNativeVoxelTerrainEnabled(boolean enabled) { - return new WorldCollisionBuildOptions(TerrainColliderMode.fromNativeVoxelTerrainEnabled(enabled), + public static PhysicsChunkBuildOptions fromNativeVoxelTerrainEnabled(boolean enabled) { + return new PhysicsChunkBuildOptions(TerrainColliderMode.fromNativeVoxelTerrainEnabled(enabled), PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java index 3f1da5ca..c089f3a7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java @@ -12,7 +12,7 @@ import javax.annotation.Nonnull; /** - * Plugin module that enables Impulse ChunkStore world-collision integration. + * Plugin module that enables Impulse PhysicsChunk terrain integration. */ public final class PhysicsChunkModule extends JavaPlugin { @@ -29,7 +29,7 @@ protected void setup() { PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); PhysicsChunkCommandContributions.register(); PhysicsWorldCollision.enableModule(); - LOGGER.at(Level.INFO).log("Impulse world-collision PhysicsStore terrain producer enabled."); + LOGGER.at(Level.INFO).log("Impulse PhysicsChunk terrain producer enabled."); } @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java index 9335f0dc..75a8db2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java @@ -8,9 +8,9 @@ import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.MissingSectionReason; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.MissingSectionReason; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -49,7 +49,7 @@ public final class PhysicsChunkMutationCache { private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); @Nonnull - public synchronized VoxelCollisionCache.BuildStats ensureAround(@Nonnull World world, + public synchronized VoxelTerrainCollisionCache.BuildStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull Vector3d center, @@ -58,7 +58,7 @@ public synchronized VoxelCollisionCache.BuildStats ensureAround(@Nonnull World w @Nullable Snapshot profiling, @Nullable LongSet visitedSections, @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { profiling.incrementEnsureCalls(); @@ -78,7 +78,7 @@ public synchronized VoxelCollisionCache.BuildStats ensureAround(@Nonnull World w int minChunkZ = ChunkUtil.chunkCoordinate(minZ); int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); - VoxelCollisionCache.BuildStats total = VoxelCollisionCache.BuildStats.empty(); + VoxelTerrainCollisionCache.BuildStats total = VoxelTerrainCollisionCache.BuildStats.empty(); for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { @@ -259,7 +259,7 @@ public synchronized int clearSectionsAround(@Nonnull UUID spaceUuid, @Nonnull public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick, int ttlTicks, @@ -322,7 +322,7 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID @Nonnull public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, @Nonnull Ref bodyRef, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick, int ttlTicks, @@ -386,7 +386,7 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick) { SpaceCollisionCache cache = spaces.computeIfAbsent(spaceUuid, _ -> new SpaceCollisionCache()); @@ -406,7 +406,7 @@ public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, @Nonnull Ref bodyRef, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick) { SpaceCollisionCache cache = spaces.computeIfAbsent(spaceUuid, _ -> new SpaceCollisionCache()); @@ -465,7 +465,7 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, } @Nonnull - private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, + private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsTerrainMutationQueueResource queue, int chunkX, @@ -474,7 +474,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, long tick, @Nullable Snapshot profiling, @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { profiling.incrementSectionRequests(); @@ -491,7 +491,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return VoxelCollisionCache.BuildStats.empty(); + return VoxelTerrainCollisionCache.BuildStats.empty(); } if (blockChunk(world, chunkX, chunkZ) == null) { cache.missingBlockChunkBackoffs.put(chunkKey, tick + MISSING_BLOCK_CHUNK_RETRY_TICKS); @@ -502,7 +502,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return VoxelCollisionCache.BuildStats.empty(); + return VoxelTerrainCollisionCache.BuildStats.empty(); } cache.missingBlockChunkBackoffs.remove(chunkKey); @@ -514,7 +514,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return VoxelCollisionCache.BuildStats.empty(); + return VoxelTerrainCollisionCache.BuildStats.empty(); } BlockSection section = ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); if (section == null) { @@ -527,7 +527,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, chunkZ, targetDiagnostic, start); - return VoxelCollisionCache.BuildStats.empty(); + return VoxelTerrainCollisionCache.BuildStats.empty(); } cache.missingBlockSectionBackoffs.remove(sectionKey); @@ -545,7 +545,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, profiling.incrementSectionCacheHits(); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return VoxelCollisionCache.BuildStats.empty(); + return VoxelTerrainCollisionCache.BuildStats.empty(); } SectionCollisionGeometry geometry = sectionBuilder.build(world, @@ -572,7 +572,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, buildOptions)); } cache.sections.put(sectionKey, built); - VoxelCollisionCache.BuildStats stats = new VoxelCollisionCache.BuildStats( + VoxelTerrainCollisionCache.BuildStats stats = new VoxelTerrainCollisionCache.BuildStats( geometry.scannedBlocks(), geometry.solidBlocks(), geometry.culledInteriorBlocks(), @@ -591,7 +591,7 @@ private VoxelCollisionCache.BuildStats ensureSection(@Nonnull World world, } private static int bodyCount(@Nonnull SectionCollisionGeometry geometry, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { int fullCubeBodyCount = buildOptions.nativeVoxelTerrainEnabled() && geometry.hasFullCubeVoxels() ? 1 @@ -772,7 +772,7 @@ private static final class CachedSection { private final int chunkZ; private final long neighborhoodSignature; @Nonnull - private final WorldCollisionBuildOptions buildOptions; + private final PhysicsChunkBuildOptions buildOptions; private final int bodyCount; private final boolean voxelTerrain; private long lastUsedTick; @@ -782,7 +782,7 @@ private CachedSection(int chunkX, int chunkZ, long lastUsedTick, long neighborhoodSignature, - @Nonnull WorldCollisionBuildOptions buildOptions, + @Nonnull PhysicsChunkBuildOptions buildOptions, int bodyCount, boolean voxelTerrain) { this.chunkX = chunkX; @@ -799,12 +799,12 @@ private CachedSection(int chunkX, private static final class CachedBodyStreamingTarget { @Nonnull - private WorldCollisionStreamingBounds bounds; + private PhysicsChunkStreamingBounds bounds; private boolean sleeping; private long lastSeenTick; private long lastRefreshTick; - private CachedBodyStreamingTarget(@Nonnull WorldCollisionStreamingBounds bounds, + private CachedBodyStreamingTarget(@Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long lastSeenTick, long lastRefreshTick) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBounds.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBounds.java similarity index 80% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBounds.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBounds.java index 5a838e5a..b2404221 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBounds.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBounds.java @@ -5,12 +5,12 @@ import org.joml.Vector3f; /** - * Chunk/section neighborhood covered by a streamed world-collision target. + * Chunk/section neighborhood covered by a streamed PhysicsChunk terrain target. * *

    Two body targets with the same bounds would trigger the same section * collision requests, so the streaming system can deduplicate them.

    */ -public record WorldCollisionStreamingBounds(int minChunkX, +public record PhysicsChunkStreamingBounds(int minChunkX, int maxChunkX, int minSectionY, int maxSectionY, @@ -18,12 +18,12 @@ public record WorldCollisionStreamingBounds(int minChunkX, int maxChunkZ) { @Nonnull - public static WorldCollisionStreamingBounds from(@Nonnull Vector3f center, int radius) { + public static PhysicsChunkStreamingBounds from(@Nonnull Vector3f center, int radius) { return from(center.x, center.y, center.z, radius); } @Nonnull - public static WorldCollisionStreamingBounds from(float centerX, + public static PhysicsChunkStreamingBounds from(float centerX, float centerY, float centerZ, int radius) { @@ -33,7 +33,7 @@ public static WorldCollisionStreamingBounds from(float centerX, int maxY = Math.clamp((int) Math.floor(centerY) + radius, 0, ChunkUtil.HEIGHT_MINUS_1); int minZ = (int) Math.floor(centerZ) - radius; int maxZ = (int) Math.floor(centerZ) + radius; - return new WorldCollisionStreamingBounds( + return new PhysicsChunkStreamingBounds( ChunkUtil.chunkCoordinate(minX), ChunkUtil.chunkCoordinate(maxX), ChunkUtil.indexSection(minY), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java similarity index 74% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java index 6e035757..e821f6ba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsWorldCollisionRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java @@ -17,16 +17,16 @@ import org.joml.Vector3d; /** - * World-collision runtime state for one physics world. + * PhysicsChunk terrain runtime state for one physics world. */ -public final class PhysicsWorldCollisionRuntime { +public final class PhysicsChunkTerrainRuntime { - private final VoxelCollisionCache worldVoxelCollisionCache = new VoxelCollisionCache(); + private final VoxelTerrainCollisionCache voxelTerrainCache = new VoxelTerrainCollisionCache(); private final Int2LongMap streamingRevisions = new Int2LongOpenHashMap(); @Nonnull - public VoxelCollisionCache worldVoxelCollisionCache() { - return worldVoxelCollisionCache; + public VoxelTerrainCollisionCache voxelTerrainCache() { + return voxelTerrainCache; } public synchronized void registerSpace(@Nonnull SpaceId spaceId) { @@ -56,7 +56,7 @@ public WorldCollisionBuildStats rebuildAround(@Nonnull World world, space, center, radius, - WorldCollisionBuildOptions.fromNativeVoxelTerrainEnabled( + PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled( PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); } @@ -65,12 +65,12 @@ public WorldCollisionBuildStats rebuildAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, - @Nonnull WorldCollisionBuildOptions buildOptions) { - return worldCollisionStats(worldVoxelCollisionCache.rebuildAround(world, + @Nonnull PhysicsChunkBuildOptions buildOptions) { + return terrainStats(voxelTerrainCache.rebuildAround(world, space, center, radius, - buildOptions)); + buildOptions)); } @Nonnull @@ -78,8 +78,8 @@ public WorldCollisionBuildStats refreshAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, - @Nonnull WorldCollisionBuildOptions buildOptions) { - VoxelCollisionCache.BuildStats stats = worldVoxelCollisionCache.refreshAround(world, + @Nonnull PhysicsChunkBuildOptions buildOptions) { + VoxelTerrainCollisionCache.BuildStats stats = voxelTerrainCache.refreshAround(world, space, center, radius, @@ -87,7 +87,7 @@ public WorldCollisionBuildStats refreshAround(@Nonnull World world, if (stats.removedBodies() > 0) { incrementStreamingRevision(space.spaceId()); } - return worldCollisionStats(stats); + return terrainStats(stats); } @Nonnull @@ -101,7 +101,7 @@ public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, centers, radius, tick, - WorldCollisionBuildOptions.fromNativeVoxelTerrainEnabled( + PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled( PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); } @@ -111,12 +111,12 @@ public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, @Nonnull Iterable centers, int radius, long tick, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { Objects.requireNonNull(centers, "centers"); LongSet visitedSections = new LongOpenHashSet(); - VoxelCollisionCache.BuildStats total = VoxelCollisionCache.BuildStats.empty(); + VoxelTerrainCollisionCache.BuildStats total = VoxelTerrainCollisionCache.BuildStats.empty(); for (Vector3d center : centers) { - total = total.plus(worldVoxelCollisionCache.ensureAround(world, + total = total.plus(voxelTerrainCache.ensureAround(world, space, center, radius, @@ -128,21 +128,21 @@ public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, buildOptions)); } return new WorldCollisionPrewarmStats(visitedSections.size(), - worldCollisionStats(total)); + terrainStats(total)); } public int clear(@Nonnull PhysicsSpaceBinding space) { incrementStreamingRevision(space.spaceId()); - return worldVoxelCollisionCache.clear(space); + return voxelTerrainCache.clear(space); } public void clear(@Nonnull SpaceId spaceId, @Nullable PhysicsSpaceBinding space) { - worldVoxelCollisionCache.clear(spaceId, space); + voxelTerrainCache.clear(spaceId, space); unregisterSpace(spaceId); } public synchronized void clearAll() { - worldVoxelCollisionCache.copyFrom(new VoxelCollisionCache()); + voxelTerrainCache.copyFrom(new VoxelTerrainCollisionCache()); for (int spaceId : streamingRevisions.keySet().toIntArray()) { streamingRevisions.put(spaceId, streamingRevisions.get(spaceId) + 1L); } @@ -152,25 +152,25 @@ public void clearRetainedTerrain(@Nonnull Iterable spaces) for (PhysicsSpaceBinding space : spaces) { clear(space); } - worldVoxelCollisionCache.finishStreamingApply(); + voxelTerrainCache.finishStreamingApply(); } public synchronized void clearAllAndUnregisterSpaces() { - worldVoxelCollisionCache.copyFrom(new VoxelCollisionCache()); + voxelTerrainCache.copyFrom(new VoxelTerrainCollisionCache()); streamingRevisions.clear(); } @Nonnull public WorldCollisionStats getStats() { - return new WorldCollisionStats(worldVoxelCollisionCache.spaceCount(), - worldVoxelCollisionCache.sectionCount(), - worldVoxelCollisionCache.bodyCount(), - worldVoxelCollisionCache.shapeTemplateCount()); + return new WorldCollisionStats(voxelTerrainCache.spaceCount(), + voxelTerrainCache.sectionCount(), + voxelTerrainCache.bodyCount(), + voxelTerrainCache.shapeTemplateCount()); } @Nonnull - private static WorldCollisionBuildStats worldCollisionStats( - @Nonnull VoxelCollisionCache.BuildStats stats) { + private static WorldCollisionBuildStats terrainStats( + @Nonnull VoxelTerrainCollisionCache.BuildStats stats) { return new WorldCollisionBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java similarity index 83% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java index ea33cd8f..dc7a9906 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreWorldCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java @@ -7,9 +7,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelCollisionCache.BuildStats; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelTerrainCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; @@ -26,20 +26,20 @@ /** * Shared EntityStore-side producer state for copied PhysicsStore terrain mutations. */ -public final class PhysicsStoreWorldCollisionStreamingResource implements Resource { +public final class PhysicsChunkTerrainStreamingResource implements Resource { @Nonnull private final PhysicsChunkMutationCache cache = new PhysicsChunkMutationCache(); private long tick; @Nullable - private static ResourceType resourceType; + private static ResourceType resourceType; - public PhysicsStoreWorldCollisionStreamingResource() { + public PhysicsChunkTerrainStreamingResource() { } public static void setResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { resourceType = Objects.requireNonNull(type, "type"); } @@ -48,9 +48,9 @@ public static void clearResourceType() { } @Nonnull - public static ResourceType getResourceType() { + public static ResourceType getResourceType() { if (resourceType == null) { - throw new IllegalStateException("PhysicsStore world-collision streaming resource is not registered"); + throw new IllegalStateException("PhysicsStore PhysicsChunk terrain streaming resource is not registered"); } return resourceType; } @@ -72,7 +72,7 @@ public synchronized WorldCollisionPrewarmStats ensureAround(@Nonnull World world int radius, long tick, @Nullable Snapshot profiling, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { LongSet visitedSections = new LongOpenHashSet(); BuildStats total = BuildStats.empty(); for (Vector3d center : centers) { @@ -87,7 +87,7 @@ public synchronized WorldCollisionPrewarmStats ensureAround(@Nonnull World world null, buildOptions)); } - return new WorldCollisionPrewarmStats(visitedSections.size(), worldCollisionStats(total)); + return new WorldCollisionPrewarmStats(visitedSections.size(), terrainStats(total)); } @Nonnull @@ -98,7 +98,7 @@ public synchronized WorldCollisionBuildStats refreshAround(@Nonnull World world, int radius, long tick, @Nullable Snapshot profiling, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { int removed = cache.clearSectionsAround(spaceUuid, queue, center, radius); BuildStats stats = ensureAround(world, spaceUuid, @@ -110,7 +110,7 @@ public synchronized WorldCollisionBuildStats refreshAround(@Nonnull World world, null, null, buildOptions); - return worldCollisionStats(withRemovedBodies(stats, stats.removedBodies() + removed)); + return terrainStats(withRemovedBodies(stats, stats.removedBodies() + removed)); } @Nonnull @@ -123,7 +123,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, @Nullable Snapshot profiling, @Nullable LongSet visitedSections, @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { return cache.ensureAround(world, spaceUuid, queue, @@ -139,7 +139,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, @Nonnull public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick, int ttlTicks, @@ -156,7 +156,7 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID @Nonnull public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID spaceUuid, @Nonnull Ref bodyRef, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick, int ttlTicks, @@ -172,7 +172,7 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull UUID public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick) { cache.recordBodyTargetRefresh(spaceUuid, bodyUuid, bounds, sleeping, currentTick); @@ -180,7 +180,7 @@ public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, public synchronized void recordBodyTargetRefresh(@Nonnull UUID spaceUuid, @Nonnull Ref bodyRef, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick) { cache.recordBodyTargetRefresh(spaceUuid, bodyRef, bounds, sleeping, currentTick); @@ -227,15 +227,15 @@ public synchronized int bodyCount(@Nonnull UUID spaceUuid) { @Nonnull @Override - public synchronized PhysicsStoreWorldCollisionStreamingResource clone() { - PhysicsStoreWorldCollisionStreamingResource copy = - new PhysicsStoreWorldCollisionStreamingResource(); + public synchronized PhysicsChunkTerrainStreamingResource clone() { + PhysicsChunkTerrainStreamingResource copy = + new PhysicsChunkTerrainStreamingResource(); copy.tick = tick; return copy; } @Nonnull - private static WorldCollisionBuildStats worldCollisionStats(@Nonnull BuildStats stats) { + private static WorldCollisionBuildStats terrainStats(@Nonnull BuildStats stats) { return new WorldCollisionBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java index 76d25bd6..d0daa6f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java @@ -11,7 +11,7 @@ import javax.annotation.Nonnull; /** - * Converts generated world-collision sections into copied PhysicsStore terrain mutations. + * Converts generated PhysicsChunk terrain sections into copied PhysicsStore terrain mutations. */ public final class PhysicsStoreTerrainMutations { @@ -27,7 +27,7 @@ public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, int chunkZ, long neighborhoodSignature, @Nonnull SectionCollisionGeometry geometry, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { String sourceKey = sourceKey(chunkX, sectionY, chunkZ); return TerrainColliderMutation.upsert(spaceUuid, sourceKey, @@ -58,7 +58,7 @@ public static String sourceKey(int chunkX, int sectionY, int chunkZ) { @Nonnull private static String payloadKey(@Nonnull String sourceKey, long neighborhoodSignature, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { return sourceKey + ":" + Long.toUnsignedString(neighborhoodSignature) + ":" @@ -67,7 +67,7 @@ private static String payloadKey(@Nonnull String sourceKey, @Nonnull private static TerrainColliderPayload payload(@Nonnull SectionCollisionGeometry geometry, - @Nonnull WorldCollisionBuildOptions buildOptions, + @Nonnull PhysicsChunkBuildOptions buildOptions, @Nonnull List neighbors) { return new TerrainColliderPayload(1.0f, 1.0f, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java index 951a7b8c..b656b341 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java @@ -25,7 +25,7 @@ final class SectionBlockReader { private final int baseZ; private final Long2ObjectMap sectionCache = new Long2ObjectOpenHashMap<>(); @Nullable - private final VoxelCollisionCache.SectionAccessCache accessCache; + private final VoxelTerrainCollisionCache.SectionAccessCache accessCache; SectionBlockReader(@Nonnull World world, @Nonnull ShapeTemplateCache templates, @@ -42,7 +42,7 @@ final class SectionBlockReader { int currentChunkX, int currentSectionY, int currentChunkZ, - @Nullable VoxelCollisionCache.SectionAccessCache accessCache) { + @Nullable VoxelTerrainCollisionCache.SectionAccessCache accessCache) { this.world = world; this.templates = templates; this.currentSection = currentSection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java index 4e579c75..abdab659 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java @@ -39,7 +39,7 @@ long neighborhoodSignature(@Nonnull World world, int chunkX, int sectionY, int chunkZ, - @Nullable VoxelCollisionCache.SectionAccessCache accessCache) { + @Nullable VoxelTerrainCollisionCache.SectionAccessCache accessCache) { return new SectionBlockReader(world, templates, section, chunkX, sectionY, chunkZ, accessCache) .neighborhoodSignature(); } @@ -59,7 +59,7 @@ SectionCollisionGeometry build(@Nonnull World world, int chunkX, int sectionY, int chunkZ, - @Nullable VoxelCollisionCache.SectionAccessCache accessCache) { + @Nullable VoxelTerrainCollisionCache.SectionAccessCache accessCache) { SectionBlockReader reader = new SectionBlockReader(world, templates, section, chunkX, sectionY, chunkZ, accessCache); BitSet fullCubes = new BitSet(SECTION_VOLUME); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelCollisionCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelCollisionCache.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java index 1ff4af24..2097b950 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelCollisionCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java @@ -12,9 +12,9 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.MissingSectionReason; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.MissingSectionReason; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -38,11 +38,11 @@ /** * Section-keyed cache that generates static physics collision from Hytale world blocks. * - *

    The cache is split per physics space so spaces can choose different world-collision + *

    The cache is split per physics space so spaces can choose different PhysicsChunk terrain * policies. Each cached section is rebuilt when Hytale's section change counter changes, * and removed when it falls out of the streaming radius or when its chunk unloads.

    */ -public final class VoxelCollisionCache { +public final class VoxelTerrainCollisionCache { private static final int ACTIVE_BODY_STREAMING_INTERVAL_TICKS = 4; private static final int SLEEPING_BODY_STREAMING_INTERVAL_TICKS = 20; @@ -57,7 +57,7 @@ public final class VoxelCollisionCache { private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); private final AtomicBoolean streamingApplyPending = new AtomicBoolean(); - public synchronized void copyFrom(@Nonnull VoxelCollisionCache other) { + public synchronized void copyFrom(@Nonnull VoxelTerrainCollisionCache other) { if (other == this) { streamingApplyPending.set(false); return; @@ -90,13 +90,13 @@ public SectionAccessCache newSectionAccessCache() { /** * Returns whether a body target needs terrain work. Call - * {@link #recordBodyTargetRefresh(SpaceId, UUID, WorldCollisionStreamingBounds, boolean, long)} + * {@link #recordBodyTargetRefresh(SpaceId, UUID, PhysicsChunkStreamingBounds, boolean, long)} * only after the terrain apply path has actually attempted that work. */ @Nonnull public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull SpaceId spaceId, @Nonnull UUID bodyUuid, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick, int ttlTicks, @@ -161,7 +161,7 @@ public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull Space */ public synchronized void recordBodyTargetRefresh(@Nonnull SpaceId spaceId, @Nonnull UUID bodyUuid, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long currentTick) { SpaceCollisionCache cache = spaces.computeIfAbsent(spaceId.value(), ignored -> new SpaceCollisionCache()); @@ -222,7 +222,7 @@ public synchronized BuildStats rebuildAround(@Nonnull World world, space, center, radius, - WorldCollisionBuildOptions.DEFAULT); + PhysicsChunkBuildOptions.DEFAULT); } /** @@ -233,7 +233,7 @@ public synchronized BuildStats rebuildAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { int removed = clear(space); BuildStats stats = ensureAround(world, space, @@ -256,7 +256,7 @@ public synchronized BuildStats refreshAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { int removed = clearSectionsAround(space.spaceId(), space, center, radius); BuildStats stats = ensureAround(world, space, @@ -355,7 +355,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, visitedSections, targetDiagnostic, accessCache, - WorldCollisionBuildOptions.DEFAULT); + PhysicsChunkBuildOptions.DEFAULT); } /** @@ -371,7 +371,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, @Nullable LongSet visitedSections, @Nullable StreamingTargetDiagnostic targetDiagnostic, @Nullable SectionAccessCache accessCache, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { profiling.incrementEnsureCalls(); @@ -734,7 +734,7 @@ public synchronized boolean containsBody(@Nonnull SpaceId spaceId, long backendB } /** - * Probes the highest cached world-collision surface under a body footprint. + * Probes the highest cached PhysicsChunk terrain surface under a body footprint. *

    * This is intended for diagnostics, not simulation. It uses the collision geometry * already built for a physics space so benchmark health checks can compare bodies to @@ -818,7 +818,7 @@ private BuildStats ensureSection(@Nonnull World world, @Nullable Snapshot profiling, @Nullable StreamingTargetDiagnostic targetDiagnostic, @Nullable SectionAccessCache accessCache, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { profiling.incrementSectionRequests(); @@ -973,7 +973,7 @@ private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, int chunkX, int sectionY, int chunkZ, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { target.fullCubeBoxes.addAll(geometry.mergedFullCubeBoxes()); target.detailBoxes.addAll(geometry.detailBoxes()); if (buildOptions.nativeVoxelTerrainEnabled() @@ -997,7 +997,7 @@ private static void addVoxelTerrain(@Nonnull PhysicsSpaceBinding space, int chunkX, int sectionY, int chunkZ, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { long backendBodyId = space.runtime().createVoxelTerrain(space.backendSpaceHandle().value(), 1.0f, 1.0f, @@ -1018,7 +1018,7 @@ private static void addVoxelTerrain(@Nonnull PhysicsSpaceBinding space, private static void addStaticBox(@Nonnull PhysicsSpaceBinding space, @Nonnull CachedSection section, @Nonnull BoxCollider box, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { if (box.halfX() <= 0.0 || box.halfY() <= 0.0 || box.halfZ() <= 0.0) { return; } @@ -1052,7 +1052,7 @@ private static void addStaticBox(@Nonnull PhysicsSpaceBinding space, private static void applyTerrainMaterial(@Nonnull PhysicsSpaceBinding space, long backendBodyId, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { // TODO: Replace coarse terrain settings with real per-block material lookup. space.runtime().setBodyFriction(space.backendSpaceHandle().value(), backendBodyId, @@ -1249,12 +1249,12 @@ private boolean isEmpty() { private static final class CachedBodyStreamingTarget { - private WorldCollisionStreamingBounds bounds; + private PhysicsChunkStreamingBounds bounds; private boolean sleeping; private long lastSeenTick; private long lastRefreshTick; - private CachedBodyStreamingTarget(@Nonnull WorldCollisionStreamingBounds bounds, + private CachedBodyStreamingTarget(@Nonnull PhysicsChunkStreamingBounds bounds, boolean sleeping, long lastSeenTick, long lastRefreshTick) { @@ -1282,7 +1282,7 @@ private static final class CachedSection { private final List detailBoxes = new ArrayList<>(); private final long neighborhoodSignature; @Nonnull - private WorldCollisionBuildOptions buildOptions = WorldCollisionBuildOptions.DEFAULT; + private PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.DEFAULT; private boolean voxelTerrain; private long voxelTerrainBodyId; private long lastUsedTick; @@ -1341,7 +1341,7 @@ private DebugSection debugSection() { } /** - * Immutable debug snapshot for one cached world-collision section. + * Immutable debug snapshot for one cached PhysicsChunk terrain section. */ public record DebugSection(int chunkX, int sectionY, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java index 29ffd0a5..7da80ecd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfCommand.java @@ -5,7 +5,7 @@ public final class PhysicsChunkPerfCommand extends AbstractCommandCollection { public PhysicsChunkPerfCommand() { - super("perf", "Impulse PhysicsChunk profiling commands"); + super("perf", "Impulse runtime and PhysicsChunk profiling commands"); addSubCommand(new PhysicsChunkPerfToggleCommand()); addSubCommand(new PhysicsChunkPerfReportCommand()); addSubCommand(new PhysicsChunkPerfResetCommand()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java index cd45a1ff..22c60c38 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelTerrainCollisionCache.BuildStats; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.ArrayList; @@ -20,7 +20,7 @@ import org.joml.Vector3f; /** - * Runtime-only profiling state for world collision streaming. + * Runtime-only profiling state for PhysicsChunk terrain streaming. * *

    This resource collects targeted metrics for the streamed voxel-collision * path so performance work can be driven by section/build/prune behavior rather @@ -28,7 +28,7 @@ * part of persisted physics world state.

    */ @Getter -public class WorldCollisionProfilingResource implements Resource { +public class PhysicsChunkProfilingResource implements Resource { @Setter private boolean enabled; @@ -42,13 +42,13 @@ public class WorldCollisionProfilingResource implements Resource { private RetainedSectionEnvelope diagnosticRetainedEnvelope; @Nullable - private static ResourceType resourceType; + private static ResourceType resourceType; - public WorldCollisionProfilingResource() { + public PhysicsChunkProfilingResource() { } public static void setResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { resourceType = Objects.requireNonNull(type, "type"); } @@ -113,8 +113,8 @@ public synchronized Snapshot getWorstTickSnapshot() { @Nonnull @Override - public synchronized WorldCollisionProfilingResource clone() { - WorldCollisionProfilingResource copy = new WorldCollisionProfilingResource(); + public synchronized PhysicsChunkProfilingResource clone() { + PhysicsChunkProfilingResource copy = new PhysicsChunkProfilingResource(); copy.enabled = enabled; copy.cumulative.copyFrom(cumulative); copy.latestTick.copyFrom(latestTick); @@ -124,9 +124,9 @@ public synchronized WorldCollisionProfilingResource clone() { } @Nonnull - public static ResourceType getResourceType() { + public static ResourceType getResourceType() { if (resourceType == null) { - throw new IllegalStateException("World collision profiling resource is not registered"); + throw new IllegalStateException("PhysicsChunk profiling resource is not registered"); } return resourceType; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java similarity index 86% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java index 6a595418..36ab75c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsStoreWorldCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java @@ -18,16 +18,16 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionStreamingBounds; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.StreamingTargetDiagnostic; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStreamingBounds; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; @@ -50,7 +50,7 @@ /** * Produces copied PhysicsStore terrain mutations from EntityStore and ChunkStore state. */ -public final class PhysicsStoreWorldCollisionProducerSystem extends TickingSystem +public final class PhysicsChunkTerrainProducerSystem extends TickingSystem implements QuerySystem { @Nullable @@ -70,25 +70,25 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { return; } - WorldCollisionProfilingResource profiling = store.getResource( - WorldCollisionProfilingResource.getResourceType()); + PhysicsChunkProfilingResource profiling = store.getResource( + PhysicsChunkProfilingResource.getResourceType()); Snapshot snapshot = profiling.isEnabled() ? profiling.beginTick() : null; long tickStart = snapshot != null ? System.nanoTime() : 0L; try { World world = store.getExternalData().getWorld(); Store physics = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(physics, - "produce PhysicsStore world-collision terrain mutations"); + "produce PhysicsStore PhysicsChunk terrain mutations"); PhysicsTerrainMutationQueueResource queue = physics.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); - PhysicsWorldCollisionIndexResource worldCollisionIndex = physics.getResource( - PhysicsWorldCollisionIndexResource.getResourceType()); + PhysicsChunkSettingsIndexResource worldCollisionIndex = physics.getResource( + PhysicsChunkSettingsIndexResource.getResourceType()); PhysicsSnapshotResource snapshotResource = physics.getResource( PhysicsSnapshotResource.getResourceType()); - PhysicsStoreWorldCollisionStreamingResource streaming = store.getResource( - PhysicsStoreWorldCollisionStreamingResource.getResourceType()); + PhysicsChunkTerrainStreamingResource streaming = store.getResource( + PhysicsChunkTerrainStreamingResource.getResourceType()); - List spaces = worldCollisionIndex.streamingSpaces(); + List spaces = worldCollisionIndex.streamingSpaces(); if (spaces.isEmpty()) { streaming.retainSpaces(Set.of(), queue); return; @@ -100,13 +100,13 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } long currentTick = streaming.nextTick(); Set retainedSpaces = new ObjectOpenHashSet<>(); - for (SpaceWorldCollisionSettings settings : spaces) { + for (PhysicsChunkSpaceSettings settings : spaces) { retainedSpaces.add(settings.spaceUuid()); } streaming.retainSpaces(retainedSpaces, queue); PhysicsSnapshotFrame physicsFrame = snapshotResource.getLatestFrame(); - for (SpaceWorldCollisionSettings settings : spaces) { + for (PhysicsChunkSpaceSettings settings : spaces) { if (snapshot != null) { snapshot.incrementStreamingSpaces(); } @@ -128,9 +128,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } private static void processSpace(@Nonnull World world, - @Nonnull PhysicsStoreWorldCollisionStreamingResource streaming, + @Nonnull PhysicsChunkTerrainStreamingResource streaming, @Nonnull PhysicsTerrainMutationQueueResource queue, - @Nonnull SpaceWorldCollisionSettings settings, + @Nonnull PhysicsChunkSpaceSettings settings, @Nonnull List playerPositions, @Nonnull PhysicsSnapshotFrame physicsFrame, long currentTick, @@ -191,12 +191,12 @@ private static void processSpace(@Nonnull World world, @Nonnull private static List collectDynamicBodyTargets( - @Nonnull PhysicsStoreWorldCollisionStreamingResource streaming, - @Nonnull SpaceWorldCollisionSettings settings, + @Nonnull PhysicsChunkTerrainStreamingResource streaming, + @Nonnull PhysicsChunkSpaceSettings settings, @Nonnull PhysicsSnapshotFrame physicsFrame, long currentTick, @Nullable Snapshot snapshot) { - Map uniqueTargets = + Map uniqueTargets = new Object2ObjectOpenHashMap<>(); int spatialCandidates = 0; int dynamicCandidates = 0; @@ -214,7 +214,7 @@ private static List collectDynamicBodyTargets( } dynamicCandidates++; Vector3f position = body.position(); - WorldCollisionStreamingBounds bounds = WorldCollisionStreamingBounds.from(position.x, + PhysicsChunkStreamingBounds bounds = PhysicsChunkStreamingBounds.from(position.x, position.y, position.z, settings.bodyRadius()); @@ -287,7 +287,7 @@ private static Query query() { if (resolved != null) { return resolved; } - synchronized (PhysicsStoreWorldCollisionProducerSystem.class) { + synchronized (PhysicsChunkTerrainProducerSystem.class) { resolved = query; if (resolved == null) { resolved = Query.and(playerType(), transformType()); @@ -303,7 +303,7 @@ private static ComponentType playerType() { if (resolved != null) { return resolved; } - synchronized (PhysicsStoreWorldCollisionProducerSystem.class) { + synchronized (PhysicsChunkTerrainProducerSystem.class) { resolved = playerType; if (resolved == null) { resolved = Player.getComponentType(); @@ -319,7 +319,7 @@ private static ComponentType transformType() { if (resolved != null) { return resolved; } - synchronized (PhysicsStoreWorldCollisionProducerSystem.class) { + synchronized (PhysicsChunkTerrainProducerSystem.class) { resolved = transformType; if (resolved == null) { resolved = TransformComponent.getComponentType(); @@ -330,7 +330,7 @@ private static ComponentType transformType() { } private record BodyStreamingTarget(@Nonnull Vector3d position, - @Nonnull WorldCollisionStreamingBounds bounds, + @Nonnull PhysicsChunkStreamingBounds bounds, @Nonnull List refreshes) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java index f935af54..cdcabe48 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands.PhysicsEntityCommandContributions; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; @@ -24,12 +25,14 @@ protected void setup() { PhysicsEntityTypes.registerEventTypes(entityRegistry); PhysicsEntityTypes.registerSystemGroups(entityRegistry); PhysicsEntityTypes.registerSystems(entityRegistry); + PhysicsEntityCommandContributions.register(); PhysicsEntityLifecycle.enable(); } @Override protected void shutdown() { PhysicsEntityLifecycle.disable(); + PhysicsEntityCommandContributions.unregister(); PhysicsEntityTypes.clearEntityStoreTypes(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java new file mode 100644 index 00000000..5d488af6 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java @@ -0,0 +1,20 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands; + +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityCommands; + +/** + * Command contributions owned by the PhysicsEntity subplugin. + */ +public final class PhysicsEntityCommandContributions { + + private PhysicsEntityCommandContributions() { + } + + public static void register() { + PhysicsEntityCommands.registerPhysicsEntityCommands(VisualSettingsCommand::new); + } + + public static void unregister() { + PhysicsEntityCommands.unregisterPhysicsEntityCommands(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java index 80a52474..cb41a7af 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.commands.settings; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSettingsCommand.java similarity index 80% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSettingsCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSettingsCommand.java index 832f44cd..718d9f23 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSettingsCommand.java @@ -1,8 +1,7 @@ -package dev.hytalemodding.impulse.core.internal.commands.settings; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; -// TODO: move to physicsentity module public class VisualSettingsCommand extends AbstractCommandCollection { public VisualSettingsCommand() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java index 5e8d1bee..7ffb45ad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.commands.settings; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -22,7 +22,6 @@ import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; -// TODO: move to physicsentity module public class VisualSyncSettingsCommand extends AbstractAsyncPlayerCommand { private final OptionalArg fullRadiusArg = this.withOptionalArg( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java index 08e3483f..00252c41 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java @@ -2,7 +2,7 @@ * Internal Impulse Core implementation packages. * *

    Types under this package tree support the Hytale runtime integration, - * commands, persistence, diagnostics, world collision, and system execution. + * commands, persistence, diagnostics, PhysicsChunk terrain, and system execution. * They are not the supported third-party plugin API.

    */ package dev.hytalemodding.impulse.core.internal; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index 76c7f1c1..1838894c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -69,19 +69,19 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, || space.getWorldCollisionRadius() > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS) { errors.add("PhysicsStore space " + uuid - + " has invalid world collision radius"); + + " has invalid PhysicsChunk terrain radius"); } if (space.getWorldCollisionBodyRadius() < 1 || space.getWorldCollisionBodyRadius() > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS) { errors.add("PhysicsStore space " + uuid - + " has invalid world collision body radius"); + + " has invalid PhysicsChunk terrain body radius"); } if (space.getWorldCollisionTtlTicks() < 1 || space.getWorldCollisionTtlTicks() > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS) { errors.add("PhysicsStore space " + uuid - + " has invalid world collision TTL"); + + " has invalid PhysicsChunk terrain TTL"); } if (!Float.isFinite(space.getTerrainFriction()) || space.getTerrainFriction() < 0.0f) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index 7bd9ed59..c5dcb091 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import javax.annotation.Nonnull; @@ -42,6 +42,6 @@ public static void clearAll(@Nonnull Store store) { store.getResource(PhysicsProfilingResource.getResourceType()).reset(); store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); - store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()).clear(); + store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()).clear(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index b28dac11..2c6a5044 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; import dev.hytalemodding.impulse.core.internal.systems.ColliderBindingSystem; @@ -40,7 +40,7 @@ import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; -import dev.hytalemodding.impulse.core.internal.systems.WorldCollisionIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; @@ -74,7 +74,7 @@ public static void register(@Nonnull ComponentRegistryProxy regist registry.registerSystem(new PersistenceHydrationSystem()); registry.registerSystem(new TerrainMutationDrainSystem()); registry.registerSystem(new IdentityIndexSystem()); - registry.registerSystem(new WorldCollisionIndexSystem()); + registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); registry.registerSystem(new BodyBindingSystem()); @@ -116,8 +116,8 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic PhysicsTerrainPayloadResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, - PhysicsWorldCollisionIndexResource.getResourceType(), - PhysicsWorldCollisionIndexResource::clear)); + PhysicsChunkSettingsIndexResource.getResourceType(), + PhysicsChunkSettingsIndexResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsIdentityIndexResource.getResourceType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java similarity index 65% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index 5020bf01..24cab36b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldCollisionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionBuildOptions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.TerrainColliderMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; @@ -15,33 +15,33 @@ import javax.annotation.Nullable; /** - * Runtime-only copied world-collision settings indexed by PhysicsStore space UUID. + * Runtime-only copied PhysicsChunk terrain settings indexed by PhysicsStore space UUID. */ -public final class PhysicsWorldCollisionIndexResource implements Resource { +public final class PhysicsChunkSettingsIndexResource implements Resource { @Nullable - private static ResourceType resourceType; + private static ResourceType resourceType; @Nonnull - private final Map settingsBySpaceUuid = + private final Map settingsBySpaceUuid = new Object2ObjectOpenHashMap<>(); - public PhysicsWorldCollisionIndexResource() { + public PhysicsChunkSettingsIndexResource() { } - public synchronized void replaceAll(@Nonnull Map settings) { + public synchronized void replaceAll(@Nonnull Map settings) { settingsBySpaceUuid.clear(); settingsBySpaceUuid.putAll(settings); } @Nonnull - public synchronized List streamingSpaces() { + public synchronized List streamingSpaces() { return settingsBySpaceUuid.values().stream() .filter(settings -> settings.mode() == WorldCollisionMode.STREAMING) .toList(); } @Nullable - public synchronized SpaceWorldCollisionSettings settings(@Nonnull UUID spaceUuid) { + public synchronized PhysicsChunkSpaceSettings settings(@Nonnull UUID spaceUuid) { return settingsBySpaceUuid.get(spaceUuid); } @@ -51,23 +51,23 @@ public synchronized void clear() { @Nonnull @Override - public synchronized PhysicsWorldCollisionIndexResource clone() { - PhysicsWorldCollisionIndexResource copy = new PhysicsWorldCollisionIndexResource(); + public synchronized PhysicsChunkSettingsIndexResource clone() { + PhysicsChunkSettingsIndexResource copy = new PhysicsChunkSettingsIndexResource(); copy.settingsBySpaceUuid.putAll(settingsBySpaceUuid); return copy; } @Nonnull - public static ResourceType getResourceType() { + public static ResourceType getResourceType() { return resourceType; } public static void setResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { resourceType = type; } - public record SpaceWorldCollisionSettings(@Nonnull UUID spaceUuid, + public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, @Nonnull WorldCollisionMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, @@ -78,8 +78,8 @@ public record SpaceWorldCollisionSettings(@Nonnull UUID spaceUuid, float terrainRestitution) { @Nonnull - public WorldCollisionBuildOptions buildOptions() { - return new WorldCollisionBuildOptions( + public PhysicsChunkBuildOptions buildOptions() { + return new PhysicsChunkBuildOptions( TerrainColliderMode.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled), terrainFriction, terrainRestitution); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index c87a62e9..5ff2af97 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -29,14 +29,14 @@ public class PhysicsDebugResource implements Resource { public static final float MIN_REFRESH_SECONDS = 0.05f; public static final float MAX_REFRESH_SECONDS = 2.0f; public static final float DEFAULT_OVERLAY_REFRESH_SECONDS = 0.10f; - public static final float DEFAULT_WORLD_COLLISION_REFRESH_SECONDS = 0.25f; + public static final float DEFAULT_PHYSICS_CHUNK_REFRESH_SECONDS = 0.25f; public static final double DEFAULT_VIEW_RADIUS = 96.0; public static final int DEFAULT_MAX_BODIES = 512; public static final int DEFAULT_MAX_CONTACTS = 384; public static final int DEFAULT_MAX_JOINTS = 384; - public static final int DEFAULT_MAX_WORLD_COLLISION_SECTIONS = 192; - public static final int DEFAULT_MAX_WORLD_COLLISION_BOXES = 768; + public static final int DEFAULT_MAX_PHYSICS_CHUNK_SECTIONS = 192; + public static final int DEFAULT_MAX_PHYSICS_CHUNK_BOXES = 768; private final Set subscriberUuids = new ObjectOpenHashSet<>(); @@ -49,19 +49,19 @@ public class PhysicsDebugResource implements Resource { @Setter private boolean debugJointsEnabled = true; @Setter - private boolean debugWorldCollisionEnabled; + private boolean debugPhysicsChunkTerrainEnabled; private float overlayRefreshSeconds = DEFAULT_OVERLAY_REFRESH_SECONDS; - private float worldCollisionRefreshSeconds = DEFAULT_WORLD_COLLISION_REFRESH_SECONDS; + private float physicsChunkRefreshSeconds = DEFAULT_PHYSICS_CHUNK_REFRESH_SECONDS; private float overlayTimeUntilRefresh; - private float worldCollisionTimeUntilRefresh; + private float physicsChunkTimeUntilRefresh; private double viewRadius = DEFAULT_VIEW_RADIUS; private int maxBodies = DEFAULT_MAX_BODIES; private int maxContacts = DEFAULT_MAX_CONTACTS; private int maxJoints = DEFAULT_MAX_JOINTS; - private int maxWorldCollisionSections = DEFAULT_MAX_WORLD_COLLISION_SECTIONS; - private int maxWorldCollisionBoxes = DEFAULT_MAX_WORLD_COLLISION_BOXES; + private int maxPhysicsChunkSections = DEFAULT_MAX_PHYSICS_CHUNK_SECTIONS; + private int maxPhysicsChunkBoxes = DEFAULT_MAX_PHYSICS_CHUNK_BOXES; public PhysicsDebugResource() { } @@ -91,8 +91,8 @@ public void setOverlayRefreshSeconds(float overlayRefreshSeconds) { this.overlayRefreshSeconds = clampRefresh(overlayRefreshSeconds); } - public void setWorldCollisionRefreshSeconds(float worldCollisionRefreshSeconds) { - this.worldCollisionRefreshSeconds = clampRefresh(worldCollisionRefreshSeconds); + public void setPhysicsChunkRefreshSeconds(float physicsChunkRefreshSeconds) { + this.physicsChunkRefreshSeconds = clampRefresh(physicsChunkRefreshSeconds); } public void setViewRadius(double viewRadius) { @@ -111,12 +111,12 @@ public void setMaxJoints(int maxJoints) { this.maxJoints = Math.max(1, maxJoints); } - public void setMaxWorldCollisionSections(int maxWorldCollisionSections) { - this.maxWorldCollisionSections = Math.max(1, maxWorldCollisionSections); + public void setMaxPhysicsChunkSections(int maxPhysicsChunkSections) { + this.maxPhysicsChunkSections = Math.max(1, maxPhysicsChunkSections); } - public void setMaxWorldCollisionBoxes(int maxWorldCollisionBoxes) { - this.maxWorldCollisionBoxes = Math.max(1, maxWorldCollisionBoxes); + public void setMaxPhysicsChunkBoxes(int maxPhysicsChunkBoxes) { + this.maxPhysicsChunkBoxes = Math.max(1, maxPhysicsChunkBoxes); } public boolean tickOverlayBudget(float dt) { @@ -132,15 +132,15 @@ public boolean tickOverlayBudget(float dt) { return true; } - public boolean tickWorldCollisionBudget(float dt) { - worldCollisionTimeUntilRefresh -= dt; - if (worldCollisionTimeUntilRefresh > 0.0f) { + public boolean tickPhysicsChunkBudget(float dt) { + physicsChunkTimeUntilRefresh -= dt; + if (physicsChunkTimeUntilRefresh > 0.0f) { return false; } - worldCollisionTimeUntilRefresh += worldCollisionRefreshSeconds; - if (worldCollisionTimeUntilRefresh <= 0.0f) { - worldCollisionTimeUntilRefresh = worldCollisionRefreshSeconds; + physicsChunkTimeUntilRefresh += physicsChunkRefreshSeconds; + if (physicsChunkTimeUntilRefresh <= 0.0f) { + physicsChunkTimeUntilRefresh = physicsChunkRefreshSeconds; } return true; } @@ -154,17 +154,17 @@ public PhysicsDebugResource clone() { copy.debugMotionEnabled = debugMotionEnabled; copy.debugContactsEnabled = debugContactsEnabled; copy.debugJointsEnabled = debugJointsEnabled; - copy.debugWorldCollisionEnabled = debugWorldCollisionEnabled; + copy.debugPhysicsChunkTerrainEnabled = debugPhysicsChunkTerrainEnabled; copy.overlayRefreshSeconds = overlayRefreshSeconds; - copy.worldCollisionRefreshSeconds = worldCollisionRefreshSeconds; + copy.physicsChunkRefreshSeconds = physicsChunkRefreshSeconds; copy.overlayTimeUntilRefresh = overlayTimeUntilRefresh; - copy.worldCollisionTimeUntilRefresh = worldCollisionTimeUntilRefresh; + copy.physicsChunkTimeUntilRefresh = physicsChunkTimeUntilRefresh; copy.viewRadius = viewRadius; copy.maxBodies = maxBodies; copy.maxContacts = maxContacts; copy.maxJoints = maxJoints; - copy.maxWorldCollisionSections = maxWorldCollisionSections; - copy.maxWorldCollisionBoxes = maxWorldCollisionBoxes; + copy.maxPhysicsChunkSections = maxPhysicsChunkSections; + copy.maxPhysicsChunkBoxes = maxPhysicsChunkBoxes; return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index fe0aaf58..ebf7cac8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -93,9 +93,9 @@ public static void registerResourceTypes( PhysicsTerrainPayloadResource.setResourceType(registry.registerResource( PhysicsTerrainPayloadResource.class, PhysicsTerrainPayloadResource::new)); - PhysicsWorldCollisionIndexResource.setResourceType(registry.registerResource( - PhysicsWorldCollisionIndexResource.class, - PhysicsWorldCollisionIndexResource::new)); + PhysicsChunkSettingsIndexResource.setResourceType(registry.registerResource( + PhysicsChunkSettingsIndexResource.class, + PhysicsChunkSettingsIndexResource::new)); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java index 884c1c33..bde8c693 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java @@ -31,7 +31,7 @@ public final class PhysicsSpaceRuntime { private final Int2ObjectMap spaces = new Int2ObjectOpenHashMap<>(); /** - * Per-space settings (world collision mode, radius, TTL, etc.). Keyed by space id value. + * Per-space settings (PhysicsChunk terrain mode, radius, TTL, etc.). Keyed by space id value. */ private final Int2ObjectMap spaceSettings = new Int2ObjectOpenHashMap<>(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index ffea63d2..73a3c26b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -26,11 +26,11 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotRefVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsWorldCollisionRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -90,8 +90,8 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { private final PhysicsBodyRegistry bodyRegistry = new PhysicsBodyRegistry(); - private final PhysicsWorldCollisionRuntime collisionRuntime = - new PhysicsWorldCollisionRuntime(); + private final PhysicsChunkTerrainRuntime terrainRuntime = + new PhysicsChunkTerrainRuntime(); @Nonnull private final PhysicsSimulationRuntime simulationRuntime = new PhysicsSimulationRuntime(); @@ -519,7 +519,7 @@ private PhysicsSpaceBinding createSpaceDirect(@Nonnull BackendId backendId, worldName, settings, simulationRuntime.getWorldSettings().getStepMode()); - collisionRuntime.registerSpace(spaceId); + terrainRuntime.registerSpace(spaceId); markWorldChanged(); return binding; } @@ -1051,13 +1051,13 @@ public int getBodySnapshotCellCount() { } @Nonnull - private PhysicsStoreWorldCollisionStreamingResource authoritativeWorldCollisionStreaming() { + private PhysicsChunkTerrainStreamingResource authoritativeWorldCollisionStreaming() { Store entityStore = owningStore; if (entityStore == null) { - throw new IllegalStateException("Cannot access PhysicsStore world-collision streaming " + throw new IllegalStateException("Cannot access PhysicsStore PhysicsChunk terrain streaming " + "before this resource is attached to an EntityStore"); } - return entityStore.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()); + return entityStore.getResource(PhysicsChunkTerrainStreamingResource.getResourceType()); } private void clearAuthoritativeWorldCollisionStreaming(@Nonnull Store store) { @@ -1098,7 +1098,7 @@ public void disablePhysicsChunkLifecycle() { } private void disablePhysicsChunkLifecycleDirect() { - collisionRuntime.clearRetainedTerrain(spaceRuntime.getBindings()); + terrainRuntime.clearRetainedTerrain(spaceRuntime.getBindings()); restoreCollisionLodFiltersDirect(); } @@ -1234,12 +1234,12 @@ public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldName) { PhysicsSpaceBinding removed = spaceRuntime.removeSpace(spaceId); if (removed == null) { - collisionRuntime.clear(spaceId, null); + terrainRuntime.clear(spaceId, null); return; } try { - collisionRuntime.clear(spaceId, removed); + terrainRuntime.clear(spaceId, removed); jointRegistry.unregisterSpace(spaceId); for (PhysicsBodyRegistration registration : new ArrayList<>(bodyRegistry.getRegistrations())) { if (registration.spaceId().equals(spaceId)) { @@ -1352,7 +1352,7 @@ public CompletionStage resetRuntimeStateKeepingSpaces private PhysicsRuntimeResetResult resetRuntimeStateKeepingSpacesDirect(@Nonnull String worldName) { PhysicsRuntimeResetResult reset = spaceRuntime.resetKeepingSpaces(worldName, simulationRuntime.getWorldSettings().getStepMode()); - collisionRuntime.clearAll(); + terrainRuntime.clearAll(); clearRuntimeTopologyDirect(false); markWorldChanged(); return reset; @@ -1460,9 +1460,9 @@ private void setSpaceSettingsDirect(@Nonnull SpaceId spaceId, && previousCollisionSettings.getWorldCollisionMode() != WorldCollisionMode.NONE; spaceRuntime.setSpaceSettings(spaceId, settings); if (worldCollisionDisabled || terrainRepresentationChanged || terrainMaterialChanged) { - collisionRuntime.clear(requireSpaceBinding(spaceId)); + terrainRuntime.clear(requireSpaceBinding(spaceId)); } else if (worldCollisionSettingsChanged) { - collisionRuntime.incrementStreamingRevision(spaceId); + terrainRuntime.incrementStreamingRevision(spaceId); } } @@ -1773,7 +1773,7 @@ private void copyFromDirect(@Nonnull PhysicsWorldResource other) { private void clearRuntimeTopologyDirect(boolean clearCollision) { bodyRuntime.clearBodyStateWithoutMarkingWorldChanged(); if (clearCollision) { - collisionRuntime.clearAllAndUnregisterSpaces(); + terrainRuntime.clearAllAndUnregisterSpaces(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java index b56ff858..f667354a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java @@ -26,7 +26,7 @@ * position.

    * *

    Callers use it for area queries that need body identity and pose data, such - * as visual materialization, world-collision streaming hints, diagnostics, and + * as visual materialization, PhysicsChunk terrain streaming hints, diagnostics, and * other nearby-body discovery. Query freshness follows the snapshot publishing * policy for each body.

    */ diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/WorldCollisionIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java similarity index 84% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/WorldCollisionIndexSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index faa5bb25..7fc2f388 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/WorldCollisionIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -10,8 +10,8 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -22,9 +22,9 @@ import javax.annotation.Nonnull; /** - * Publishes copied world-collision settings for PhysicsStore space entities. + * Publishes copied PhysicsChunk terrain settings for PhysicsStore space entities. */ -public final class WorldCollisionIndexSystem extends TickingSystem +public final class PhysicsChunkSettingsIndexSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( @@ -33,17 +33,17 @@ public final class WorldCollisionIndexSystem extends TickingSystem @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { - Map settingsBySpaceUuid = + Map settingsBySpaceUuid = new Object2ObjectOpenHashMap<>(); BiConsumer, CommandBuffer> collector = (chunk, _) -> collectChunk(settingsBySpaceUuid, chunk); store.forEachChunk(systemIndex, collector); - store.getResource(PhysicsWorldCollisionIndexResource.getResourceType()) + store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()) .replaceAll(settingsBySpaceUuid); } private static void collectChunk( - @Nonnull Map settingsBySpaceUuid, + @Nonnull Map settingsBySpaceUuid, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); @@ -59,7 +59,7 @@ private static void collectChunk( WorldCollisionComponent settings = worldCollision != null ? worldCollision : new WorldCollisionComponent(); - settingsBySpaceUuid.put(spaceUuid, new SpaceWorldCollisionSettings(spaceUuid, + settingsBySpaceUuid.put(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, settings.getMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelTerrainEnabled(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java index 86f25ded..6226543b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java @@ -40,7 +40,7 @@ public final class SpaceBindingSystem extends TickingSystem private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), - new SystemDependency<>(Order.AFTER, WorldCollisionIndexSystem.class) + new SystemDependency<>(Order.AFTER, PhysicsChunkSettingsIndexSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsChunkDebugSectionView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsChunkDebugSectionView.java new file mode 100644 index 00000000..0ae7acf0 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsChunkDebugSectionView.java @@ -0,0 +1,21 @@ +package dev.hytalemodding.impulse.core.internal.systems.debug; + +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Copied terrain debug section used by the internal debug renderer. + */ +public record PhysicsChunkDebugSectionView(int chunkX, + int sectionY, + int chunkZ, + boolean voxelTerrain, + @Nonnull List fullCubeBoxes, + @Nonnull List detailBoxes) { + + public PhysicsChunkDebugSectionView { + fullCubeBoxes = List.copyOf(fullCubeBoxes); + detailBoxes = List.copyOf(detailBoxes); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index c226ae53..5917d913 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -99,7 +99,7 @@ public void tick(float dt, int index, @Nonnull Store store) { } boolean overlayDue = debug.tickOverlayBudget(dt); - boolean worldCollisionDue = debug.tickWorldCollisionBudget(dt); + boolean worldCollisionDue = debug.tickPhysicsChunkBudget(dt); if (!overlayDue && !worldCollisionDue) { return; } @@ -108,7 +108,7 @@ public void tick(float dt, int index, @Nonnull Store store) { boolean debugMotion = debug.isDebugMotionEnabled(); boolean debugContacts = debug.isDebugContactsEnabled(); boolean debugJoints = debug.isDebugJointsEnabled(); - boolean debugWorldCollision = debug.isDebugWorldCollisionEnabled(); + boolean debugWorldCollision = debug.isDebugPhysicsChunkTerrainEnabled(); if (!debugShapes && !debugMotion && !debugContacts && !debugJoints && !debugWorldCollision) { return; @@ -118,7 +118,7 @@ public void tick(float dt, int index, @Nonnull Store store) { float overlayLifetime = PhysicsDebugRenderer.lifetimeForRefresh( debug.getOverlayRefreshSeconds(), dt); float worldCollisionLifetime = PhysicsDebugRenderer.lifetimeForRefresh( - debug.getWorldCollisionRefreshSeconds(), dt); + debug.getPhysicsChunkRefreshSeconds(), dt); DebugQueryCache queryCache = queryCacheFor(store); for (PlayerRef viewer : viewers) { @@ -180,8 +180,8 @@ public void tick(float dt, int index, @Nonnull Store store) { queryCache, viewerPosition, debug.getViewRadius(), - debug.getMaxWorldCollisionSections(), - debug.getMaxWorldCollisionBoxes(), + debug.getMaxPhysicsChunkSections(), + debug.getMaxPhysicsChunkBoxes(), worldCollisionLifetime); } } @@ -426,10 +426,10 @@ private static void renderWorldCollision(@Nonnull Collection viewers, int maxSections, int maxBoxes, float time) { - DebugQueryKey key = DebugQueryKey.worldCollision(spaceId, viewerUuid); + DebugQueryKey key = DebugQueryKey.physicsChunk(spaceId, viewerUuid); try { - queryCache.requestWorldCollisionIfIdle(key, - () -> PhysicsStoreDebugQueries.worldCollisionSectionsAsync(physicsStore, + queryCache.requestPhysicsChunkIfIdle(key, + () -> PhysicsStoreDebugQueries.physicsChunkSectionsAsync(physicsStore, spaceId, viewerPosition, viewRadius)); @@ -438,15 +438,15 @@ private static void renderWorldCollision(@Nonnull Collection viewers, } double maxDistanceSquared = viewRadius * viewRadius; - List visibleSections = collectVisibleWorldCollisionSections( - queryCache.worldCollisionSectionsOrEmpty(key), + List visibleSections = collectVisiblePhysicsChunkSections( + queryCache.physicsChunkSectionsOrEmpty(key), viewerPosition, maxDistanceSquared); visibleSections.sort(Comparator.comparingDouble(VisibleDebugSection::distanceSquared)); int sectionLimit = Math.min(maxSections, visibleSections.size()); for (int i = 0; i < sectionLimit; i++) { - PhysicsDebugWorldCollisionSectionView section = visibleSections.get(i).section(); + PhysicsChunkDebugSectionView section = visibleSections.get(i).section(); PhysicsDebugRenderer.renderWorldCollisionSection(viewers, section.chunkX(), section.sectionY(), @@ -455,7 +455,7 @@ private static void renderWorldCollision(@Nonnull Collection viewers, time); } - List visibleBoxes = collectVisibleWorldCollisionBoxes( + List visibleBoxes = collectVisiblePhysicsChunkBoxes( visibleSections, viewerPosition, maxDistanceSquared); visibleBoxes.sort(Comparator.comparingDouble(VisibleDebugBox::distanceSquared)); @@ -470,12 +470,12 @@ private static void renderWorldCollision(@Nonnull Collection viewers, } @Nonnull - private static List collectVisibleWorldCollisionSections( - @Nonnull Iterable sections, + private static List collectVisiblePhysicsChunkSections( + @Nonnull Iterable sections, @Nonnull Vector3d viewerPosition, double maxDistanceSquared) { List visibleSections = new ArrayList<>(); - for (PhysicsDebugWorldCollisionSectionView section : sections) { + for (PhysicsChunkDebugSectionView section : sections) { double distanceSquared = distanceSquaredToSection(viewerPosition, section); if (distanceSquared > maxDistanceSquared) { continue; @@ -487,19 +487,19 @@ private static List collectVisibleWorldCollisionSections( } @Nonnull - private static List collectVisibleWorldCollisionBoxes( + private static List collectVisiblePhysicsChunkBoxes( @Nonnull List visibleSections, @Nonnull Vector3d viewerPosition, double maxDistanceSquared) { List visibleBoxes = new ArrayList<>(); for (VisibleDebugSection visibleSection : visibleSections) { - PhysicsDebugWorldCollisionSectionView section = visibleSection.section(); - collectVisibleWorldCollisionBoxes(viewerPosition, + PhysicsChunkDebugSectionView section = visibleSection.section(); + collectVisiblePhysicsChunkBoxes(viewerPosition, maxDistanceSquared, section.fullCubeBoxes(), DebugUtils.COLOR_CYAN, visibleBoxes); - collectVisibleWorldCollisionBoxes(viewerPosition, + collectVisiblePhysicsChunkBoxes(viewerPosition, maxDistanceSquared, section.detailBoxes(), DebugUtils.COLOR_MAGENTA, @@ -508,7 +508,7 @@ private static List collectVisibleWorldCollisionBoxes( return visibleBoxes; } - private static void collectVisibleWorldCollisionBoxes(@Nonnull Vector3d viewerPosition, + private static void collectVisiblePhysicsChunkBoxes(@Nonnull Vector3d viewerPosition, double maxDistanceSquared, @Nonnull Iterable boxes, @Nonnull Vector3f color, @@ -524,7 +524,7 @@ private static void collectVisibleWorldCollisionBoxes(@Nonnull Vector3d viewerPo } private static double distanceSquaredToSection(@Nonnull Vector3d viewerPosition, - @Nonnull PhysicsDebugWorldCollisionSectionView section) { + @Nonnull PhysicsChunkDebugSectionView section) { double minX = section.chunkX() << ChunkUtil.BITS; double minY = section.sectionY() << ChunkUtil.BITS; double minZ = section.chunkZ() << ChunkUtil.BITS; @@ -591,11 +591,11 @@ static final class DebugQueryCache { private final Map> completedJoints = new Object2ObjectOpenHashMap<>(); @Nonnull - private final Map>> - pendingWorldCollisionSections = new Object2ObjectOpenHashMap<>(); + private final Map>> + pendingPhysicsChunkSections = new Object2ObjectOpenHashMap<>(); @Nonnull - private final Map> - completedWorldCollisionSections = new Object2ObjectOpenHashMap<>(); + private final Map> + completedPhysicsChunkSections = new Object2ObjectOpenHashMap<>(); synchronized boolean requestContactsIfIdle(@Nonnull DebugQueryKey key, @Nonnull Supplier>> completionSupplier) { @@ -629,22 +629,22 @@ synchronized List jointsOrEmpty(@Nonnull DebugQueryKey ke return completedJoints.getOrDefault(key, List.of()); } - synchronized boolean requestWorldCollisionIfIdle(@Nonnull DebugQueryKey key, - @Nonnull Supplier>> + synchronized boolean requestPhysicsChunkIfIdle(@Nonnull DebugQueryKey key, + @Nonnull Supplier>> completionSupplier) { - pollWorldCollision(key); - if (pendingWorldCollisionSections.containsKey(key)) { + pollPhysicsChunk(key); + if (pendingPhysicsChunkSections.containsKey(key)) { return false; } - pendingWorldCollisionSections.put(key, completionSupplier.get().toCompletableFuture()); + pendingPhysicsChunkSections.put(key, completionSupplier.get().toCompletableFuture()); return true; } @Nonnull - synchronized List worldCollisionSectionsOrEmpty( + synchronized List physicsChunkSectionsOrEmpty( @Nonnull DebugQueryKey key) { - pollWorldCollision(key); - return completedWorldCollisionSections.getOrDefault(key, List.of()); + pollPhysicsChunk(key); + return completedPhysicsChunkSections.getOrDefault(key, List.of()); } private void pollContacts(@Nonnull DebugQueryKey key) { @@ -665,14 +665,14 @@ private void pollJoints(@Nonnull DebugQueryKey key) { completedJoints.put(key, completedList(pending)); } - private void pollWorldCollision(@Nonnull DebugQueryKey key) { - CompletableFuture> pending = - pendingWorldCollisionSections.get(key); + private void pollPhysicsChunk(@Nonnull DebugQueryKey key) { + CompletableFuture> pending = + pendingPhysicsChunkSections.get(key); if (pending == null || !pending.isDone()) { return; } - pendingWorldCollisionSections.remove(key); - completedWorldCollisionSections.put(key, completedList(pending)); + pendingPhysicsChunkSections.remove(key); + completedPhysicsChunkSections.put(key, completedList(pending)); } @Nonnull @@ -706,18 +706,18 @@ static DebugQueryKey joints(@Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid) } @Nonnull - static DebugQueryKey worldCollision(@Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid) { - return new DebugQueryKey(QueryKind.WORLD_COLLISION, spaceId, viewerUuid); + static DebugQueryKey physicsChunk(@Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid) { + return new DebugQueryKey(QueryKind.PHYSICS_CHUNK, spaceId, viewerUuid); } } enum QueryKind { CONTACTS, JOINTS, - WORLD_COLLISION + PHYSICS_CHUNK } - private record VisibleDebugSection(@Nonnull PhysicsDebugWorldCollisionSectionView section, + private record VisibleDebugSection(@Nonnull PhysicsChunkDebugSectionView section, double distanceSquared) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugWorldCollisionSectionView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugWorldCollisionSectionView.java deleted file mode 100644 index e127485e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugWorldCollisionSectionView.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.debug; - -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; -import java.util.List; -import javax.annotation.Nonnull; - -/** - * Copied terrain debug section used by the internal debug renderer. - */ -public record PhysicsDebugWorldCollisionSectionView(int chunkX, - int sectionY, - int chunkZ, - boolean voxelTerrain, - @Nonnull List fullCubeBoxes, - @Nonnull List detailBoxes) { - - public PhysicsDebugWorldCollisionSectionView { - fullCubeBoxes = List.copyOf(fullCubeBoxes); - detailBoxes = List.copyOf(detailBoxes); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index e735585f..49055602 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -86,7 +86,7 @@ static CompletionStage> jointsAsync( } @Nonnull - static CompletionStage> worldCollisionSectionsAsync( + static CompletionStage> physicsChunkSectionsAsync( @Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3d viewerPosition, @@ -95,8 +95,8 @@ static CompletionStage> worldCollisi double viewerY = viewerPosition.y; double viewerZ = viewerPosition.z; return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore world-collision debug read", - physics -> worldCollisionSections(physics, + "queue PhysicsStore PhysicsChunk terrain debug read", + physics -> physicsChunkSections(physics, spaceId, viewerX, viewerY, @@ -197,7 +197,7 @@ private static List joints(@Nonnull Store s } @Nonnull - private static List worldCollisionSections( + private static List physicsChunkSections( @Nonnull Store store, @Nonnull SpaceId spaceId, double viewerX, @@ -205,7 +205,7 @@ private static List worldCollisionSection double viewerZ, double viewRadius) { PhysicsThreading.requireWorldThread(store, - "read PhysicsStore world-collision debug sections"); + "read PhysicsStore PhysicsChunk terrain debug sections"); SpaceContext spaceContext = space(store, spaceId); if (spaceContext == null) { return List.of(); @@ -222,9 +222,9 @@ private static List worldCollisionSection PhysicsTerrainPayloadResource payloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); double maxDistanceSquared = viewRadius * viewRadius; - List visible = new ArrayList<>(); + List visible = new ArrayList<>(); BiConsumer, CommandBuffer> collector = - (chunk, _) -> collectWorldCollisionChunk(chunk, + (chunk, _) -> collectPhysicsChunkTerrainChunk(chunk, payloads, spaceContext, spaceRef, @@ -238,7 +238,7 @@ private static List worldCollisionSection return List.copyOf(visible); } - private static void collectWorldCollisionChunk(@Nonnull ArchetypeChunk chunk, + private static void collectPhysicsChunkTerrainChunk(@Nonnull ArchetypeChunk chunk, @Nonnull PhysicsTerrainPayloadResource payloads, @Nonnull SpaceContext spaceContext, @Nullable Ref spaceRef, @@ -247,7 +247,7 @@ private static void collectWorldCollisionChunk(@Nonnull ArchetypeChunk visible) { + @Nonnull List visible) { for (int index = 0; index < chunk.size(); index++) { TerrainColliderComponent terrain = chunk.getComponent(index, TerrainColliderComponent.getComponentType()); @@ -263,12 +263,12 @@ private static void collectWorldCollisionChunk(@Nonnull ArchetypeChunk { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java index c468917f..c42a990b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java @@ -2,9 +2,9 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsStoreWorldCollisionProducerSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsChunkTerrainProducerSystem; import javax.annotation.Nonnull; /** @@ -17,21 +17,21 @@ private PhysicsChunkTypes() { public static void registerEntityStoreResourceTypes( @Nonnull ComponentRegistryProxy registry) { - WorldCollisionProfilingResource.setResourceType(registry.registerResource( - WorldCollisionProfilingResource.class, - WorldCollisionProfilingResource::new)); - PhysicsStoreWorldCollisionStreamingResource.setResourceType(registry.registerResource( - PhysicsStoreWorldCollisionStreamingResource.class, - PhysicsStoreWorldCollisionStreamingResource::new)); + PhysicsChunkProfilingResource.setResourceType(registry.registerResource( + PhysicsChunkProfilingResource.class, + PhysicsChunkProfilingResource::new)); + PhysicsChunkTerrainStreamingResource.setResourceType(registry.registerResource( + PhysicsChunkTerrainStreamingResource.class, + PhysicsChunkTerrainStreamingResource::new)); } public static void registerEntityStoreSystems( @Nonnull ComponentRegistryProxy registry) { - registry.registerSystem(new PhysicsStoreWorldCollisionProducerSystem()); + registry.registerSystem(new PhysicsChunkTerrainProducerSystem()); } public static void clearEntityStoreResourceTypes() { - WorldCollisionProfilingResource.clearResourceType(); - PhysicsStoreWorldCollisionStreamingResource.clearResourceType(); + PhysicsChunkProfilingResource.clearResourceType(); + PhysicsChunkTerrainStreamingResource.clearResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index dd80b34a..f2b43667 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -6,13 +6,13 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsStoreWorldCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldCollisionIndexResource.SpaceWorldCollisionSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; @@ -22,7 +22,7 @@ import org.joml.Vector3d; /** - * Public PhysicsChunk operations for terrain-backed world collision. + * Public PhysicsChunk operations for terrain-backed collision. */ public final class PhysicsWorldCollision { @@ -50,8 +50,8 @@ public static WorldCollisionBuildStats rebuildAround(@Nonnull World world, requireEnabled(); Store checkedStore = requireMatchingWorldThread(world, store, - "rebuild PhysicsStore world collision"); - SpaceWorldCollisionSettings settings = requireSettings(checkedStore, spaceId); + "rebuild PhysicsChunk terrain"); + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); PhysicsTerrainMutationQueueResource queue = checkedStore.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); @@ -76,8 +76,8 @@ public static WorldCollisionBuildStats refreshAround(@Nonnull World world, requireEnabled(); Store checkedStore = requireMatchingWorldThread(world, store, - "refresh PhysicsStore world collision"); - SpaceWorldCollisionSettings settings = requireSettings(checkedStore, spaceId); + "refresh PhysicsChunk terrain"); + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); return streaming(world).refreshAround(world, settings.spaceUuid(), checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), @@ -98,8 +98,8 @@ public static WorldCollisionPrewarmStats ensureAround(@Nonnull World world, requireEnabled(); Store checkedStore = requireMatchingWorldThread(world, store, - "ensure PhysicsStore world collision"); - SpaceWorldCollisionSettings settings = requireSettings(checkedStore, spaceId); + "ensure PhysicsChunk terrain"); + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); return streaming(world).ensureAround(world, settings.spaceUuid(), checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), @@ -115,7 +115,7 @@ public static int clearSpace(@Nonnull World world, @Nonnull SpaceId spaceId) { Store checkedStore = requireMatchingWorldThread(world, store, - "clear PhysicsStore world collision"); + "clear PhysicsChunk terrain"); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, Objects.requireNonNull(spaceId, "spaceId")); return clearSpaceRows(world, checkedStore, spaceUuid); @@ -125,7 +125,7 @@ public static int clearSpace(@Nonnull World world, public static WorldCollisionStats stats(@Nonnull World world) { Objects.requireNonNull(world, "world"); if (!world.isInThread()) { - throw new IllegalStateException("Cannot read PhysicsChunk world-collision stats " + throw new IllegalStateException("Cannot read PhysicsChunk terrain stats " + "outside the owning world thread"); } return isModuleEnabled() @@ -153,7 +153,7 @@ private static Store requireMatchingWorldThread(@Nonnull World wor } @Nonnull - private static SpaceWorldCollisionSettings requireSettings( + private static PhysicsChunkSpaceSettings requireSettings( @Nonnull Store store, @Nonnull SpaceId spaceId) { UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, @@ -170,7 +170,7 @@ private static SpaceWorldCollisionSettings requireSettings( if (settings.getMode() == WorldCollisionMode.NONE) { throw new IllegalStateException("World collision is disabled for space " + spaceId); } - return new SpaceWorldCollisionSettings(spaceUuid, + return new PhysicsChunkSpaceSettings(spaceUuid, settings.getMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelTerrainEnabled(), @@ -182,11 +182,11 @@ private static SpaceWorldCollisionSettings requireSettings( } @Nonnull - private static PhysicsStoreWorldCollisionStreamingResource streaming(@Nonnull World world) { + private static PhysicsChunkTerrainStreamingResource streaming(@Nonnull World world) { Store entityStore = Objects.requireNonNull(world, "world") .getEntityStore() .getStore(); - return entityStore.getResource(PhysicsStoreWorldCollisionStreamingResource.getResourceType()); + return entityStore.getResource(PhysicsChunkTerrainStreamingResource.getResourceType()); } private static int clearSpaceRows(@Nonnull World world, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java index 510f886a..e6c03b9a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; @@ -24,7 +24,7 @@ private PhysicsWorldCollisionProfiling() { public static boolean isRuntimeProfilingEnabled(@Nonnull Store store) { PhysicsRuntimeProfilingResource runtimeProfiling = runtimeProfiling(store); - WorldCollisionProfilingResource worldCollisionProfiling = worldCollisionProfiling(store); + PhysicsChunkProfilingResource worldCollisionProfiling = worldCollisionProfiling(store); return runtimeProfiling.isEnabled() && worldCollisionProfiling.isEnabled(); } @@ -52,7 +52,7 @@ public static void resetRuntimeProfiling(@Nonnull World world, @Nonnull public static Snapshots snapshots(@Nonnull Store store) { - WorldCollisionProfilingResource profiling = worldCollisionProfiling(store); + PhysicsChunkProfilingResource profiling = worldCollisionProfiling(store); return new Snapshots(profiling.getCumulativeSnapshot(), profiling.getLatestTickSnapshot(), profiling.getWorstTickSnapshot(), @@ -70,8 +70,8 @@ public static List missingSectionSamples( @Nonnull private static MissingSectionSampleView view( - @Nonnull WorldCollisionProfilingResource.MissingSectionSample sample) { - WorldCollisionProfilingResource.StreamingTargetDiagnostic target = sample.target(); + @Nonnull PhysicsChunkProfilingResource.MissingSectionSample sample) { + PhysicsChunkProfilingResource.StreamingTargetDiagnostic target = sample.target(); return new MissingSectionSampleView(sample.chunkX(), sample.sectionY(), sample.chunkZ(), @@ -90,9 +90,9 @@ private static PhysicsRuntimeProfilingResource runtimeProfiling( } @Nonnull - private static WorldCollisionProfilingResource worldCollisionProfiling( + private static PhysicsChunkProfilingResource worldCollisionProfiling( @Nonnull Store store) { - return store.getResource(WorldCollisionProfilingResource.getResourceType()); + return store.getResource(PhysicsChunkProfilingResource.getResourceType()); } @Nullable @@ -105,9 +105,9 @@ public record Snapshots(@Nonnull SnapshotView cumulative, @Nonnull SnapshotView worst, boolean enabled) { - private Snapshots(@Nonnull WorldCollisionProfilingResource.Snapshot cumulative, - @Nonnull WorldCollisionProfilingResource.Snapshot latest, - @Nonnull WorldCollisionProfilingResource.Snapshot worst, + private Snapshots(@Nonnull PhysicsChunkProfilingResource.Snapshot cumulative, + @Nonnull PhysicsChunkProfilingResource.Snapshot latest, + @Nonnull PhysicsChunkProfilingResource.Snapshot worst, boolean enabled) { this(new SnapshotView(cumulative), new SnapshotView(latest), new SnapshotView(worst), enabled); @@ -117,9 +117,9 @@ private Snapshots(@Nonnull WorldCollisionProfilingResource.Snapshot cumulative, public static final class SnapshotView { @Nonnull - private final WorldCollisionProfilingResource.Snapshot snapshot; + private final PhysicsChunkProfilingResource.Snapshot snapshot; - private SnapshotView(@Nonnull WorldCollisionProfilingResource.Snapshot snapshot) { + private SnapshotView(@Nonnull PhysicsChunkProfilingResource.Snapshot snapshot) { this.snapshot = snapshot; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java index 61a63717..1ceb7bb3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Aggregate statistics from building or rebuilding streamed world-collision geometry. + * Aggregate statistics from building or rebuilding streamed PhysicsChunk terrain geometry. */ public record WorldCollisionBuildStats(int scannedBlocks, int solidBlocks, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java index df4993b8..a2e1e78d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java @@ -3,11 +3,11 @@ /** * Controls how a physics space interacts with Hytale world voxel collision. * - *

    This is an opt-in per-space policy. Impulse does not impose world collision + *

    This is an opt-in per-space policy. Impulse does not impose PhysicsChunk terrain * on any space by default; the integrator chooses the level of intrusion.

    * *
      - *
    • {@link #NONE} - No world collision. The space is pure physics with no terrain.
    • + *
    • {@link #NONE} - No PhysicsChunk terrain. The space is pure physics with no terrain.
    • *
    • {@link #MANUAL} - World collision exists but must be built/cleared explicitly * by the integrator (e.g. via commands or a custom system).
    • *
    • {@link #STREAMING} - Impulse automatically streams section collision around diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java index 82c2bb2a..6e745a55 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Statistics from ensuring world collision around multiple target positions. + * Statistics from ensuring PhysicsChunk terrain around multiple target positions. */ public record WorldCollisionPrewarmStats(int sectionTargets, WorldCollisionBuildStats buildStats) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java index 45eab549..2c0f3fb6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Current size of the generated world-collision cache. + * Current size of the generated PhysicsChunk terrain cache. */ public record WorldCollisionStats(int spaces, int sections, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java index 6857c839..d4ce310b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java @@ -1,4 +1,4 @@ /** - * Optional ChunkStore world-collision integration for authoritative PhysicsStore terrain. + * Optional ChunkStore PhysicsChunk terrain integration for authoritative PhysicsStore terrain. */ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java new file mode 100644 index 00000000..2894b219 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java @@ -0,0 +1,29 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; + +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; +import java.util.Objects; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +/** + * Public command contribution endpoint for the PhysicsEntity module. + */ +public final class PhysicsEntityCommands { + + private static final String VISUAL_SETTINGS_COMMAND_ID = "physicsentity.settings.visual"; + + private PhysicsEntityCommands() { + } + + public static void registerPhysicsEntityCommands( + @Nonnull Supplier visualSettingsCommand) { + ImpulseCommandContributionRegistry.addSettingsSubCommand( + VISUAL_SETTINGS_COMMAND_ID, + Objects.requireNonNull(visualSettingsCommand, "visualSettingsCommand")); + } + + public static void unregisterPhysicsEntityCommands() { + ImpulseCommandContributionRegistry.removeSettingsSubCommand(VISUAL_SETTINGS_COMMAND_ID); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index a93c27f7..f89b35a7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -16,7 +16,7 @@ * code should read and mutate the domain group directly instead of adding flat * shortcut state here.

      * - *

      Default settings have world collision disabled ({@link WorldCollisionMode#NONE}), + *

      Default settings have PhysicsChunk terrain disabled ({@link WorldCollisionMode#NONE}), * which keeps Impulse fully opt-in: no terrain bodies are created unless the integrator * explicitly opts in.

      */ @@ -112,7 +112,7 @@ public static PhysicsSpaceSettings defaults() { } /** - * Convenience factory for a space with streaming world collision enabled. + * Convenience factory for a space with streaming PhysicsChunk terrain enabled. */ @Nonnull public static PhysicsSpaceSettings streamingWorldCollision() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java index 68397c94..31d00577 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java @@ -16,7 +16,7 @@ public class PhysicsWorldCollisionSettings { public static final int DEFAULT_WORLD_COLLISION_RADIUS = 8; /** - * Hard block-radius cap for player-centered world collision streaming. + * Hard block-radius cap for player-centered PhysicsChunk terrain streaming. */ public static final int MAX_WORLD_COLLISION_RADIUS = 128; @@ -28,7 +28,7 @@ public class PhysicsWorldCollisionSettings { public static final int DEFAULT_WORLD_COLLISION_BODY_RADIUS = 4; /** - * Hard block-radius cap for dynamic-body world collision streaming. + * Hard block-radius cap for dynamic-body PhysicsChunk terrain streaming. */ public static final int MAX_WORLD_COLLISION_BODY_RADIUS = 64; @@ -78,7 +78,7 @@ public class PhysicsWorldCollisionSettings { private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; /** - * Enables native backend voxel terrain for full-cube world collision. + * Enables native backend voxel terrain for full-cube PhysicsChunk terrain. */ @Setter @Getter diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java index b3b0f2de..8ee6e62a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java @@ -19,6 +19,7 @@ void coreRootDoesNotOwnPhysicsChunkCommandsByDefault() { assertFalse(root.getSubCommands().containsKey("physicschunk")); assertFalse(settings(root).getSubCommands().containsKey("collision-lod")); + assertFalse(settings(root).getSubCommands().containsKey("visual")); } private static AbstractCommand settings(ImpulseCommand root) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandContributionRegistryTest.java new file mode 100644 index 00000000..46f06049 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandContributionRegistryTest.java @@ -0,0 +1,47 @@ +package dev.hytalemodding.impulse.core.internal.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands.PhysicsEntityCommandContributions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class PhysicsEntityCommandContributionRegistryTest { + + @AfterEach + void resetRegistry() { + ImpulseCommandContributionRegistry.resetForTests(); + } + + @Test + void physicsEntityContributesVisualSettingsUnderImpulseSettings() { + PhysicsEntityCommandContributions.register(); + + ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); + + AbstractCommand visual = settings(root).getSubCommands().get("visual"); + assertTrue(settings(root).getSubCommands().containsKey("visual")); + assertTrue(visual.getSubCommands().containsKey("sync")); + assertTrue(visual.getSubCommands().containsKey("materialization")); + } + + @Test + void physicsEntityContributionsAreIdempotentAndRemovable() { + PhysicsEntityCommandContributions.register(); + PhysicsEntityCommandContributions.register(); + + ImpulseCommand contributed = ImpulseCommandContributionRegistry.createRootCommandForTests(); + assertTrue(settings(contributed).getSubCommands().containsKey("visual")); + + PhysicsEntityCommandContributions.unregister(); + + ImpulseCommand removed = ImpulseCommandContributionRegistry.createRootCommandForTests(); + assertFalse(settings(removed).getSubCommands().containsKey("visual")); + } + + private static AbstractCommand settings(ImpulseCommand root) { + return root.getSubCommands().get("settings"); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBoundsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBoundsTest.java similarity index 69% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBoundsTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBoundsTest.java index c68c20b1..8395735c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldCollisionStreamingBoundsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBoundsTest.java @@ -3,18 +3,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.WorldCollisionStreamingBounds; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStreamingBounds; import org.joml.Vector3f; import org.junit.jupiter.api.Test; -class WorldCollisionStreamingBoundsTest { +class PhysicsChunkStreamingBoundsTest { @Test void dedupesBodiesThatShareTheSameChunkAndSectionNeighborhood() { - WorldCollisionStreamingBounds first = WorldCollisionStreamingBounds.from( + PhysicsChunkStreamingBounds first = PhysicsChunkStreamingBounds.from( new Vector3f(10.2f, 65.4f, -3.7f), 4); - WorldCollisionStreamingBounds second = WorldCollisionStreamingBounds.from( + PhysicsChunkStreamingBounds second = PhysicsChunkStreamingBounds.from( new Vector3f(10.8f, 65.1f, -3.2f), 4); @@ -24,10 +24,10 @@ void dedupesBodiesThatShareTheSameChunkAndSectionNeighborhood() { @Test void keepsBodiesDistinctWhenTheirChunkNeighborhoodChanges() { - WorldCollisionStreamingBounds first = WorldCollisionStreamingBounds.from( + PhysicsChunkStreamingBounds first = PhysicsChunkStreamingBounds.from( new Vector3f(15.5f, 70.0f, 15.5f), 4); - WorldCollisionStreamingBounds second = WorldCollisionStreamingBounds.from( + PhysicsChunkStreamingBounds second = PhysicsChunkStreamingBounds.from( new Vector3f(31.5f, 70.0f, 15.5f), 4); @@ -37,7 +37,7 @@ void keepsBodiesDistinctWhenTheirChunkNeighborhoodChanges() { @Test void clampsVerticalBoundsToValidSectionRange() { - WorldCollisionStreamingBounds bounds = WorldCollisionStreamingBounds.from( + PhysicsChunkStreamingBounds bounds = PhysicsChunkStreamingBounds.from( new Vector3f(0.0f, -20.0f, 0.0f), 12); @@ -48,10 +48,10 @@ void clampsVerticalBoundsToValidSectionRange() { @Test void scalarFactoryMatchesVectorFactory() { - WorldCollisionStreamingBounds vectorBounds = WorldCollisionStreamingBounds.from( + PhysicsChunkStreamingBounds vectorBounds = PhysicsChunkStreamingBounds.from( new Vector3f(10.2f, 65.4f, -3.7f), 4); - WorldCollisionStreamingBounds scalarBounds = WorldCollisionStreamingBounds.from( + PhysicsChunkStreamingBounds scalarBounds = PhysicsChunkStreamingBounds.from( 10.2f, 65.4f, -3.7f, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java similarity index 88% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java index ac4da634..13323a89 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/WorldVoxelCollisionCacheTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.VoxelTerrainCall; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; @@ -34,11 +34,11 @@ import org.joml.Vector3f; import org.junit.jupiter.api.Test; -class WorldVoxelCollisionCacheTest { +class VoxelTerrainCollisionCacheTest { @Test void streamingApplyGateAllowsOnlyOnePendingMutation() { - VoxelCollisionCache cache = new VoxelCollisionCache(); + VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); assertFalse(cache.isStreamingApplyPending()); assertTrue(cache.tryBeginStreamingApply()); @@ -53,8 +53,8 @@ void streamingApplyGateAllowsOnlyOnePendingMutation() { @Test void copyFromDoesNotInheritPendingStreamingApply() { - VoxelCollisionCache source = new VoxelCollisionCache(); - VoxelCollisionCache target = new VoxelCollisionCache(); + VoxelTerrainCollisionCache source = new VoxelTerrainCollisionCache(); + VoxelTerrainCollisionCache target = new VoxelTerrainCollisionCache(); assertTrue(source.tryBeginStreamingApply()); @@ -66,8 +66,8 @@ void copyFromDoesNotInheritPendingStreamingApply() { @Test void copyFromDeepCopiesCachedSectionBodyIds() throws Exception { RuntimeFixture fixture = runtimeFixture("test:copy-section-isolation", true); - VoxelCollisionCache source = new VoxelCollisionCache(); - VoxelCollisionCache target = new VoxelCollisionCache(); + VoxelTerrainCollisionCache source = new VoxelTerrainCollisionCache(); + VoxelTerrainCollisionCache target = new VoxelTerrainCollisionCache(); Object spaceCache = newSpaceCollisionCache(); Object sourceSection = newCachedSection(1, 2, 3); long copiedBodyId = createVoxelTerrain(fixture); @@ -87,12 +87,12 @@ void copyFromDeepCopiesCachedSectionBodyIds() throws Exception { @Test void bodyTargetCacheRefreshesActiveBodiesEveryFourTicks() { - VoxelCollisionCache cache = new VoxelCollisionCache(); - WorldCollisionProfilingResource.Snapshot snapshot = - new WorldCollisionProfilingResource.Snapshot(); + VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); + PhysicsChunkProfilingResource.Snapshot snapshot = + new PhysicsChunkProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1001); UUID bodyId = bodyId(1); - WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); + PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, snapshot) .refresh()); @@ -115,12 +115,12 @@ void bodyTargetCacheRefreshesActiveBodiesEveryFourTicks() { @Test void bodyTargetCacheRefreshesSleepingBodiesOnTtlBoundedInterval() { - VoxelCollisionCache cache = new VoxelCollisionCache(); - WorldCollisionProfilingResource.Snapshot snapshot = - new WorldCollisionProfilingResource.Snapshot(); + VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); + PhysicsChunkProfilingResource.Snapshot snapshot = + new PhysicsChunkProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1002); UUID bodyId = bodyId(2); - WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); + PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, true, 1L, 100, snapshot) .refresh()); @@ -139,9 +139,9 @@ void bodyTargetCacheRefreshesSleepingBodiesOnTtlBoundedInterval() { @Test void bodyTargetCacheRefreshesImmediatelyWhenBoundsChange() { - VoxelCollisionCache cache = new VoxelCollisionCache(); - WorldCollisionProfilingResource.Snapshot snapshot = - new WorldCollisionProfilingResource.Snapshot(); + VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); + PhysicsChunkProfilingResource.Snapshot snapshot = + new PhysicsChunkProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1003); UUID bodyId = bodyId(3); @@ -177,10 +177,10 @@ void bodyTargetCacheRefreshesImmediatelyWhenBoundsChange() { @Test void bodyTargetRefreshIsNotConsumedUntilTerrainApplyRecordsIt() { - VoxelCollisionCache cache = new VoxelCollisionCache(); + VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); SpaceId spaceId = new SpaceId(1005); UUID bodyId = bodyId(5); - WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); + PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, null) .refresh()); @@ -195,12 +195,12 @@ void bodyTargetRefreshIsNotConsumedUntilTerrainApplyRecordsIt() { @Test void bodyTargetCachePrunesBodiesThatDisappearPastDoubleTtl() { - VoxelCollisionCache cache = new VoxelCollisionCache(); - WorldCollisionProfilingResource.Snapshot snapshot = - new WorldCollisionProfilingResource.Snapshot(); + VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); + PhysicsChunkProfilingResource.Snapshot snapshot = + new PhysicsChunkProfilingResource.Snapshot(); SpaceId spaceId = new SpaceId(1004); UUID bodyId = bodyId(4); - WorldCollisionStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); + PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, snapshot) .refresh()); @@ -233,7 +233,7 @@ void absentVoxelCapabilityFallsBackToMergedFullCubeBoxes() throws Exception { 0, 0)); - VoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); + VoxelTerrainCollisionCache.DebugSection debugSection = debugSection(cachedSection); assertEquals(2, fixture.runtime().bodyCount(fixture.backendSpaceId())); assertEquals(0, fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()).size()); assertFalse(debugSection.voxelTerrain()); @@ -256,7 +256,7 @@ void supportedRuntimeCreatesVoxelTerrainAndKeepsDebugBoxes() throws Throwable { addGeometryBodies(fixture.binding(), cachedSection, geometry, 2, 3, 4); List calls = fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()); - VoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); + VoxelTerrainCollisionCache.DebugSection debugSection = debugSection(cachedSection); assertEquals(1, calls.size()); assertArrayEquals(new int[] {0, 0, 0, 1, 0, 0}, calls.getFirst().voxelCoordinates()); assertEquals((float) (2 << ChunkUtil.BITS), calls.getFirst().positionX()); @@ -293,7 +293,7 @@ void terrainMaterialSettingsApplyToNativeVoxelAndFallbackBoxes() throws Throwabl 0, 0, 0, - WorldCollisionBuildOptions.fromSettings(nativeSettings)); + PhysicsChunkBuildOptions.fromSettings(nativeSettings)); List calls = nativeFixture.runtime().voxelTerrainCalls(nativeFixture.backendSpaceId()); @@ -312,7 +312,7 @@ void terrainMaterialSettingsApplyToNativeVoxelAndFallbackBoxes() throws Throwabl 0, 0, 0, - WorldCollisionBuildOptions.fromSettings(fallbackSettings)); + PhysicsChunkBuildOptions.fromSettings(fallbackSettings)); long fallbackBodyId = firstBackendBodyId(fallbackSection); var snapshot = PhysicsBodySnapshots.read(fallbackFixture.binding(), fallbackBodyId); @@ -334,7 +334,7 @@ void disabledNativeVoxelTerrainFallsBackToMergedFullCubeBoxes() throws Throwable addGeometryBodies(fixture.binding(), cachedSection, geometry, 0, 0, 0, false); - VoxelCollisionCache.DebugSection debugSection = debugSection(cachedSection); + VoxelTerrainCollisionCache.DebugSection debugSection = debugSection(cachedSection); assertEquals(1, fixture.runtime().bodyCount(fixture.backendSpaceId())); assertEquals(0, fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()).size()); assertFalse(debugSection.voxelTerrain()); @@ -392,7 +392,7 @@ void stitchesVoxelTerrainToSixAdjacentSections() throws Throwable { @Test void clearSectionsAroundKeepsDistantCachedTerrain() throws Exception { RuntimeFixture fixture = runtimeFixture("test:section-radius-clear", true); - VoxelCollisionCache worldCache = new VoxelCollisionCache(); + VoxelTerrainCollisionCache worldCache = new VoxelTerrainCollisionCache(); Object spaceCache = newSpaceCollisionCache(); Object near = newCachedSection(0, 2, 0); long nearBody = createVoxelTerrain(fixture); @@ -420,8 +420,8 @@ private static UUID bodyId(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); } - private static WorldCollisionStreamingBounds boundsAt(float x, float y, float z) { - return WorldCollisionStreamingBounds.from(new Vector3f(x, y, z), 4); + private static PhysicsChunkStreamingBounds boundsAt(float x, float y, float z) { + return PhysicsChunkStreamingBounds.from(new Vector3f(x, y, z), 4); } private static Object newCachedSection() throws Exception { @@ -461,7 +461,7 @@ private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, chunkX, sectionY, chunkZ, - WorldCollisionBuildOptions.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled)); + PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled)); } private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, @@ -470,8 +470,8 @@ private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, int chunkX, int sectionY, int chunkZ, - @Nonnull WorldCollisionBuildOptions buildOptions) throws Throwable { - Method method = Arrays.stream(VoxelCollisionCache.class.getDeclaredMethods()) + @Nonnull PhysicsChunkBuildOptions buildOptions) throws Throwable { + Method method = Arrays.stream(VoxelTerrainCollisionCache.class.getDeclaredMethods()) .filter(candidate -> candidate.getName().equals("addGeometryBodies")) .findFirst() .orElseThrow(); @@ -498,7 +498,7 @@ private static Object[] addGeometryBodiesArguments(@Nonnull Method method, int chunkX, int sectionY, int chunkZ, - @Nonnull WorldCollisionBuildOptions buildOptions) { + @Nonnull PhysicsChunkBuildOptions buildOptions) { Object[] arguments = new Object[method.getParameterCount()]; int integerIndex = 0; for (int index = 0; index < arguments.length; index++) { @@ -518,7 +518,7 @@ private static Object[] addGeometryBodiesArguments(@Nonnull Method method, }; } else if (type == boolean.class) { arguments[index] = buildOptions.nativeVoxelTerrainEnabled(); - } else if (type == WorldCollisionBuildOptions.class) { + } else if (type == PhysicsChunkBuildOptions.class) { arguments[index] = buildOptions; } else { arguments[index] = null; @@ -530,7 +530,7 @@ private static Object[] addGeometryBodiesArguments(@Nonnull Method method, private static void stitchAdjacentVoxelTerrains(@Nonnull PhysicsSpaceBinding space, @Nonnull Object cache, @Nonnull Object built) throws Throwable { - Method method = Arrays.stream(VoxelCollisionCache.class.getDeclaredMethods()) + Method method = Arrays.stream(VoxelTerrainCollisionCache.class.getDeclaredMethods()) .filter(candidate -> candidate.getName().equals("stitchAdjacentVoxelTerrains")) .findFirst() .orElseThrow(); @@ -545,7 +545,7 @@ private static void stitchAdjacentVoxelTerrains(@Nonnull PhysicsSpaceBinding spa private static void removeBuiltSectionAfterFailure(@Nonnull PhysicsSpaceBinding space, @Nonnull Object built, @Nonnull RuntimeException failure) throws Throwable { - Method method = VoxelCollisionCache.class.getDeclaredMethod("removeBuiltSectionAfterFailure", + Method method = VoxelTerrainCollisionCache.class.getDeclaredMethod("removeBuiltSectionAfterFailure", PhysicsSpaceBinding.class, nestedClass("CachedSection"), RuntimeException.class); @@ -566,7 +566,7 @@ private static Object newSpaceCollisionCache() throws Exception { @SuppressWarnings("unchecked") private static void putCachedSection(@Nonnull Object cache, @Nonnull Object section) throws Exception { - Method keyMethod = VoxelCollisionCache.class.getDeclaredMethod("packSectionKey", + Method keyMethod = VoxelTerrainCollisionCache.class.getDeclaredMethod("packSectionKey", int.class, int.class, int.class); @@ -581,10 +581,10 @@ private static void putCachedSection(@Nonnull Object cache, @Nonnull Object sect } @SuppressWarnings("unchecked") - private static void putSpaceCache(@Nonnull VoxelCollisionCache worldCache, + private static void putSpaceCache(@Nonnull VoxelTerrainCollisionCache worldCache, @Nonnull SpaceId spaceId, @Nonnull Object cache) throws Exception { - Field spacesField = VoxelCollisionCache.class.getDeclaredField("spaces"); + Field spacesField = VoxelTerrainCollisionCache.class.getDeclaredField("spaces"); spacesField.setAccessible(true); ((Map) spacesField.get(worldCache)).put(spaceId.value(), cache); } @@ -642,10 +642,10 @@ private static long voxelTerrainBodyId(@Nonnull Object section) throws Exception return field.getLong(section); } - private static VoxelCollisionCache.DebugSection debugSection(@Nonnull Object section) throws Exception { + private static VoxelTerrainCollisionCache.DebugSection debugSection(@Nonnull Object section) throws Exception { Method method = section.getClass().getDeclaredMethod("debugSection"); method.setAccessible(true); - return (VoxelCollisionCache.DebugSection) method.invoke(section); + return (VoxelTerrainCollisionCache.DebugSection) method.invoke(section); } private static int intField(@Nonnull Object target, @Nonnull String name) throws Exception { @@ -672,7 +672,7 @@ private static void setLongField(@Nonnull Object target, @Nonnull private static Class nestedClass(@Nonnull String simpleName) { - return Arrays.stream(VoxelCollisionCache.class.getDeclaredClasses()) + return Arrays.stream(VoxelTerrainCollisionCache.class.getDeclaredClasses()) .filter(candidate -> candidate.getSimpleName().equals(simpleName)) .findFirst() .orElseThrow(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java similarity index 81% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java index c1364792..8e0b0ebb 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/WorldCollisionProfilingResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java @@ -9,44 +9,44 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelCollisionCache.BuildStats; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.WorldCollisionProfilingResource.MissingSectionReason; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelTerrainCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.MissingSectionReason; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -class WorldCollisionProfilingResourceTest { +class PhysicsChunkProfilingResourceTest { @AfterEach void clearRegistration() { - WorldCollisionProfilingResource.clearResourceType(); + PhysicsChunkProfilingResource.clearResourceType(); } @Test - void resourceTypeIsOwnedByWorldCollisionModuleRegistration() { - assertFalse(WorldCollisionProfilingResource.isResourceTypeRegistered()); - assertThrows(IllegalStateException.class, WorldCollisionProfilingResource::getResourceType); + void resourceTypeIsOwnedByPhysicsChunkModuleRegistration() { + assertFalse(PhysicsChunkProfilingResource.isResourceTypeRegistered()); + assertThrows(IllegalStateException.class, PhysicsChunkProfilingResource::getResourceType); ComponentRegistry registry = new ComponentRegistry<>(); - ResourceType type = - registry.registerResource(WorldCollisionProfilingResource.class, - WorldCollisionProfilingResource::new); + ResourceType type = + registry.registerResource(PhysicsChunkProfilingResource.class, + PhysicsChunkProfilingResource::new); - WorldCollisionProfilingResource.setResourceType(type); + PhysicsChunkProfilingResource.setResourceType(type); - assertTrue(WorldCollisionProfilingResource.isResourceTypeRegistered()); - assertSame(type, WorldCollisionProfilingResource.getResourceType()); + assertTrue(PhysicsChunkProfilingResource.isResourceTypeRegistered()); + assertSame(type, PhysicsChunkProfilingResource.getResourceType()); - WorldCollisionProfilingResource.clearResourceType(); + PhysicsChunkProfilingResource.clearResourceType(); - assertFalse(WorldCollisionProfilingResource.isResourceTypeRegistered()); + assertFalse(PhysicsChunkProfilingResource.isResourceTypeRegistered()); } @Test void finishTickTracksLatestCumulativeAndWorstSnapshots() { - WorldCollisionProfilingResource resource = new WorldCollisionProfilingResource(); + PhysicsChunkProfilingResource resource = new PhysicsChunkProfilingResource(); - WorldCollisionProfilingResource.Snapshot first = resource.beginTick(); + PhysicsChunkProfilingResource.Snapshot first = resource.beginTick(); first.setPlayerStreamingTargets(2); first.addBodyStreamingCandidates(5); first.addBodyStreamingTargets(3); @@ -81,7 +81,7 @@ void finishTickTracksLatestCumulativeAndWorstSnapshots() { first.setTickNanos(120L); resource.finishTick(first); - WorldCollisionProfilingResource.Snapshot second = resource.beginTick(); + PhysicsChunkProfilingResource.Snapshot second = resource.beginTick(); second.setPlayerStreamingTargets(1); second.addBodyStreamingCandidates(2); second.addBodyStreamingTargets(1); @@ -147,13 +147,13 @@ void finishTickTracksLatestCumulativeAndWorstSnapshots() { @Test void cloneAndResetPreserveExpectedMetrics() { - WorldCollisionProfilingResource resource = new WorldCollisionProfilingResource(); + PhysicsChunkProfilingResource resource = new PhysicsChunkProfilingResource(); resource.setEnabled(true); - WorldCollisionProfilingResource.Snapshot snapshot = resource.beginTick(); + PhysicsChunkProfilingResource.Snapshot snapshot = resource.beginTick(); snapshot.setTickNanos(25L); resource.finishTick(snapshot); - WorldCollisionProfilingResource copy = resource.clone(); + PhysicsChunkProfilingResource copy = resource.clone(); assertTrue(copy.isEnabled()); assertEquals(25L, copy.getLatestTick().getTickNanos()); assertEquals(25L, copy.getWorstTick().getTickNanos()); @@ -166,17 +166,17 @@ void cloneAndResetPreserveExpectedMetrics() { @Test void missingSectionDiagnosticsTrackRetainedEnvelopeStatus() { - WorldCollisionProfilingResource resource = new WorldCollisionProfilingResource(); + PhysicsChunkProfilingResource resource = new PhysicsChunkProfilingResource(); LongOpenHashSet retained = new LongOpenHashSet(); - retained.add(WorldCollisionProfilingResource.packDiagnosticSectionKey(1, 2, 3)); + retained.add(PhysicsChunkProfilingResource.packDiagnosticSectionKey(1, 2, 3)); resource.setDiagnosticRetainedSections(retained); - WorldCollisionProfilingResource.Snapshot snapshot = resource.beginTick(); + PhysicsChunkProfilingResource.Snapshot snapshot = resource.beginTick(); snapshot.recordMissingSection(MissingSectionReason.BLOCK_CHUNK, 1, 2, 3, null); snapshot.recordMissingSection(MissingSectionReason.BLOCK_SECTION, 4, 5, 6, null); resource.finishTick(snapshot); - WorldCollisionProfilingResource.Snapshot cumulative = resource.getCumulativeSnapshot(); + PhysicsChunkProfilingResource.Snapshot cumulative = resource.getCumulativeSnapshot(); assertEquals(2, cumulative.getMissingChunks()); assertEquals(1, cumulative.getMissingBlockChunks()); assertEquals(1, cumulative.getMissingBlockSections()); @@ -185,9 +185,9 @@ void missingSectionDiagnosticsTrackRetainedEnvelopeStatus() { assertEquals(1, cumulative.getMissingOutsideRetainedEnvelope()); assertEquals(0, cumulative.getMissingUnconfiguredRetainedEnvelope()); assertEquals(2, cumulative.getMissingSectionSamples().size()); - assertEquals(WorldCollisionProfilingResource.RetainedEnvelopeStatus.INSIDE, + assertEquals(PhysicsChunkProfilingResource.RetainedEnvelopeStatus.INSIDE, cumulative.getMissingSectionSamples().get(0).retainedEnvelopeStatus()); - assertEquals(WorldCollisionProfilingResource.RetainedEnvelopeStatus.OUTSIDE, + assertEquals(PhysicsChunkProfilingResource.RetainedEnvelopeStatus.OUTSIDE, cumulative.getMissingSectionSamples().get(1).retainedEnvelopeStatus()); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java index 6266d091..60cd6185 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java @@ -21,7 +21,7 @@ public ImpulseCommand() { addSubCommand(new GrabCommand()); addSubCommand(new ReleaseCommand()); addSubCommand(new PersistenceCommand()); - addSubCommand(new PhysicsChunkCommand()); + addSubCommand(new PhysicsChunkExampleCommand()); addSubCommand(new StressCommand()); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java similarity index 98% rename from impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkCommand.java rename to impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index df206910..6aa9d290 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -28,9 +28,9 @@ /** * Debug commands for manually building/clearing world voxel collision. */ -public class PhysicsChunkCommand extends AbstractCommandCollection { +public class PhysicsChunkExampleCommand extends AbstractCommandCollection { - public PhysicsChunkCommand() { + public PhysicsChunkExampleCommand() { super("physicschunk", "Build static Impulse chunk collision from nearby world blocks"); addSubCommand(new BuildCommand()); addSubCommand(new EnsureCommand()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index a4de6639..c604f018 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -281,7 +281,7 @@ static final class ExplosiveCommand extends PhysicsStorePlayerCommand { ArgTypes.STRING); private final OptionalArg radiusArg = withOptionalArg( "radius", - "Block radius fragmented when the explosive block hits world collision", + "Block radius fragmented when the explosive block hits PhysicsChunk terrain", ArgTypes.INTEGER); private final OptionalArg maxFragmentsArg = withOptionalArg( "maxFragments", From 34809899c937a62cff7d9ad9bb7d2b551316d664 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 19:13:26 +0200 Subject: [PATCH 351/534] fix(core): order physics subplugin startup Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 34 +++++++++++++++++++ .../core/internal/commands/SpaceCommand.java | 34 +++++++++---------- .../crucible/ImpulseApiCrucibleTests.java | 2 +- ...tachedStreamingBenchmarkCrucibleTests.java | 2 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 2 +- .../modules/ImpulseSubPluginRegistration.java | 23 +++++++++++-- .../persistence/PersistentSpaceDto.java | 4 +-- .../PhysicsStoreSpaceMutations.java | 4 +-- .../PhysicsWorldRuntimeResource.java | 4 +-- .../systems/PersistenceCaptureSystem.java | 4 +-- .../systems/PersistenceHydrationSystem.java | 4 +-- .../PhysicsChunkSettingsIndexSystem.java | 2 +- .../components/PhysicsComponentTypes.java | 2 ++ .../physicschunk/PhysicsWorldCollision.java | 4 +-- .../physicschunk/WorldCollisionMode.java | 2 +- .../CollisionLodSettingsComponent.java | 3 +- .../components/WorldCollisionComponent.java | 3 +- .../plugin/physicsstore/PhysicsEntities.java | 4 +-- .../plugin/physicsstore/PhysicsSpaces.java | 4 +-- .../plugin/settings/PhysicsSpaceSettings.java | 11 +++++- .../PhysicsWorldCollisionSettings.java | 8 ++--- impulse-core/src/module-info/module-info.java | 1 + .../ImpulseSubPluginRegistrationTest.java | 22 ++++++++++-- .../resources/PhysicsSpaceSettingsTest.java | 12 +++---- .../components/BodyCommandComponentTest.java | 0 .../BodyAttachmentComponentTest.java | 3 +- impulse-examples/build.gradle.kts | 6 +++- .../commands/stress/StressBodiesCommand.java | 2 +- 28 files changed, 146 insertions(+), 60 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{ => modules/physicschunk}/components/CollisionLodSettingsComponent.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{ => modules/physicschunk}/components/WorldCollisionComponent.java (98%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => }/components/BodyCommandComponentTest.java (100%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/{physicsstore/projection => modules/physicsentity/components}/BodyAttachmentComponentTest.java (94%) diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index c28b0d0b..d838f619 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -125,3 +125,37 @@ hytaleTools { false /* includeAssetPack */ ) } + +tasks.named("updatePluginManifest") { + doLast { + val manifestFile = file("src/main/resources/manifest.json") + + @Suppress("UNCHECKED_CAST") + val manifestJson = groovy.json.JsonSlurper().parse(manifestFile) + as MutableMap + + @Suppress("UNCHECKED_CAST") + val subPlugins = manifestJson["SubPlugins"] as? List> + ?: return@doLast + + fun MutableMap.mergeDependencies(dependencies: Map) { + @Suppress("UNCHECKED_CAST") + val existingDependencies = (this["Dependencies"] as? Map) + ?.toMutableMap() + ?: linkedMapOf() + existingDependencies.putAll(dependencies) + this["Dependencies"] = existingDependencies + } + + val physicsEntityDependency = mapOf("HytaleModding:ImpulsePhysicsEntity" to "*") + subPlugins.firstOrNull { it["Name"] == "ImpulseControl" } + ?.mergeDependencies(physicsEntityDependency) + subPlugins.firstOrNull { it["Name"] == "ImpulsePhysicsChunk" } + ?.mergeDependencies(physicsEntityDependency) + + manifestFile.writeText( + groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(manifestJson)) + + System.lineSeparator() + ) + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index f833dea1..ba7516e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -49,9 +49,9 @@ private static final class CreateCommand extends AbstractWorldCommand { "backend", "Backend id, for example impulse:rapier", ArgTypes.STRING); - private final OptionalArg worldCollisionArg = withOptionalArg( - "worldCollision", - "World collision mode: none, manual, or streaming", + private final OptionalArg physicsChunkArg = withOptionalArg( + "physicsChunk", + "PhysicsChunk terrain mode: none, manual, or streaming", ArgTypes.STRING); private CreateCommand() { super("create", "Create an explicit physics space", false); @@ -66,18 +66,18 @@ protected void execute(@Nonnull CommandContext context, return; } - WorldCollisionMode worldCollisionMode = worldCollisionArg.provided(context) - ? parseWorldCollisionMode(worldCollisionArg.get(context)) + WorldCollisionMode physicsChunkMode = physicsChunkArg.provided(context) + ? parsePhysicsChunkMode(physicsChunkArg.get(context)) : WorldCollisionMode.STREAMING; - if (worldCollisionMode == null) { - context.sendMessage(Message.raw("worldCollision must be none, manual, or streaming.")); + if (physicsChunkMode == null) { + context.sendMessage(Message.raw("physicsChunk must be none, manual, or streaming.")); return; } - PhysicsSpaceSettings settings = worldCollisionMode == WorldCollisionMode.STREAMING - ? PhysicsSpaceSettings.streamingWorldCollision() + PhysicsSpaceSettings settings = physicsChunkMode == WorldCollisionMode.STREAMING + ? PhysicsSpaceSettings.streamingPhysicsChunk() : PhysicsSpaceSettings.defaults(); - settings.getWorldCollisionSettings().setWorldCollisionMode(worldCollisionMode); + settings.getWorldCollisionSettings().setWorldCollisionMode(physicsChunkMode); Store physicsStore = PhysicsThreading.store(world); try { @@ -86,7 +86,7 @@ protected void execute(@Nonnull CommandContext context, context.sendMessage(Message.raw("Created physics space id=" + spaceId.value() + " backend=" + backendId.value() - + " worldCollision=" + worldCollisionMode.name().toLowerCase(Locale.ROOT) + + " physicsChunk=" + physicsChunkMode.name().toLowerCase(Locale.ROOT) + ".")); } catch (RuntimeException exception) { context.sendMessage(Message.raw("Failed to create physics space: " @@ -119,14 +119,14 @@ private static void sendSpaces(@Nonnull CommandContext context, .map(summary -> { PhysicsSpaceSettings settings = PhysicsSpaces.settings(physicsStore, summary.spaceId()); - WorldCollisionMode worldCollisionMode = settings != null + WorldCollisionMode physicsChunkMode = settings != null ? settings.getWorldCollisionSettings().getWorldCollisionMode() : WorldCollisionMode.NONE; return new SpaceListEntry(summary.spaceId(), summary.backendId().value(), summary.bodyCount(), summary.jointCount(), - worldCollisionMode); + physicsChunkMode); }) .sorted(Comparator.comparingInt(entry -> entry.spaceId().value())) .toList(); @@ -142,8 +142,8 @@ private static void sendSpaces(@Nonnull CommandContext context, + " backend=" + space.backendId() + " bodies=" + space.bodies() + " joints=" + space.joints() - + " worldCollision=" - + space.worldCollisionMode().name().toLowerCase(Locale.ROOT))); + + " physicsChunk=" + + space.physicsChunkMode().name().toLowerCase(Locale.ROOT))); } } } @@ -272,7 +272,7 @@ private static BackendId parseBackendId(@Nonnull CommandContext context, } @Nullable - private static WorldCollisionMode parseWorldCollisionMode(@Nonnull String value) { + private static WorldCollisionMode parsePhysicsChunkMode(@Nonnull String value) { return switch (value.toLowerCase(Locale.ROOT)) { case "none", "off", "disabled" -> WorldCollisionMode.NONE; case "manual" -> WorldCollisionMode.MANUAL; @@ -298,6 +298,6 @@ private record SpaceListEntry(@Nonnull SpaceId spaceId, @Nonnull String backendId, int bodies, int joints, - @Nonnull WorldCollisionMode worldCollisionMode) { + @Nonnull WorldCollisionMode physicsChunkMode) { } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 26f9ddd3..5a17a282 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -205,7 +205,7 @@ private static CompletionStage createdExplicitSpaceLifecycleWorks( Store store = physicsStore(world); SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), "crucible", - PhysicsSpaceSettings.streamingWorldCollision()); + PhysicsSpaceSettings.streamingPhysicsChunk()); boolean registered = resource.hasSpace(spaceId) && resource.getSpaceSettings(spaceId).getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 7b1fdd99..02928bc2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -857,7 +857,7 @@ private String summary() { + "/" + format(avgRegistrationPublicationMs) + "/" + format(avgSyncMs) + "/" + format(avgWorldMs) - + " bodies dynamic/worldCollision=" + dynamicBodies + + " bodies dynamic/physicsChunk=" + dynamicBodies + "/" + worldCollisionBodies + " belowPlane/terrain/worldMin/void=" + belowPlaneBodies + "/" + belowTerrainBodies diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index a1439c7e..e822967a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -705,7 +705,7 @@ private String summary() { + "/" + worldSectionRequests + "/" + worldSectionsBuilt + "/" + worldBodyTargets - + " bodies total/dynamic/detached/raw/worldCollision=" + bodies + + " bodies total/dynamic/detached/raw/physicsChunk=" + bodies + "/" + dynamicBodies + "/" + detachedBodies + "/" + rawBodies diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistration.java index 4e9733ef..f9b73acc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistration.java @@ -13,6 +13,7 @@ import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -54,12 +55,30 @@ public static void register(@Nonnull JavaPlugin parentPlugin) { static List prepareSubPluginManifests(@Nonnull PluginManifest parentManifest) { List prepared = new ArrayList<>(); for (PluginManifest subPluginManifest : parentManifest.getSubPlugins()) { - subPluginManifest.inherit(parentManifest); - prepared.add(subPluginManifest); + PluginManifest mutableSubPluginManifest = mutableCopy(subPluginManifest); + mutableSubPluginManifest.inherit(parentManifest); + prepared.add(mutableSubPluginManifest); } return prepared; } + @Nonnull + private static PluginManifest mutableCopy(@Nonnull PluginManifest manifest) { + return new PluginManifest(manifest.getGroup(), + manifest.getName(), + manifest.getVersion(), + manifest.getDescription(), + new ArrayList<>(manifest.getAuthors()), + manifest.getWebsite(), + manifest.getMain(), + manifest.getServerVersion(), + new LinkedHashMap<>(manifest.getDependencies()), + new LinkedHashMap<>(manifest.getOptionalDependencies()), + new LinkedHashMap<>(manifest.getLoadBefore()), + new ArrayList<>(manifest.getSubPlugins()), + manifest.isDisabledByDefault()); + } + private static boolean hasPendingPlugin(@Nonnull List pendingPlugins, @Nonnull PluginIdentifier pluginId) { for (PendingLoadPlugin pendingPlugin : pendingPlugins) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index e8f09d56..e4eecd78 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -7,12 +7,12 @@ import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 8d4149bf..25030ec6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -14,14 +14,14 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 73a3c26b..621faf70 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -42,7 +42,7 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; @@ -51,7 +51,7 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index 0cf0e7c6..29569509 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -24,7 +24,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; @@ -37,7 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index d2d77c39..f060fd85 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; @@ -35,7 +35,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index 7fc2f388..2e3f1210 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 668c81cf..6e8a8d84 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -3,6 +3,8 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index f2b43667..5143da67 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Objects; @@ -168,7 +168,7 @@ private static PhysicsChunkSpaceSettings requireSettings( store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); if (settings.getMode() == WorldCollisionMode.NONE) { - throw new IllegalStateException("World collision is disabled for space " + spaceId); + throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); } return new PhysicsChunkSpaceSettings(spaceUuid, settings.getMode(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java index a2e1e78d..a8910290 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java @@ -8,7 +8,7 @@ * *
        *
      • {@link #NONE} - No PhysicsChunk terrain. The space is pure physics with no terrain.
      • - *
      • {@link #MANUAL} - World collision exists but must be built/cleared explicitly + *
      • {@link #MANUAL} - PhysicsChunk terrain exists but must be built/cleared explicitly * by the integrator (e.g. via commands or a custom system).
      • *
      • {@link #STREAMING} - Impulse automatically streams section collision around * tracked players/bodies and prunes unused sections after a TTL.
      • diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java index c5c38935..cf58daa1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.components; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -6,6 +6,7 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java index 6cf4bbc9..baa91b87 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.components; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -7,6 +7,7 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index 055a7bc5..c4964b50 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -8,7 +8,7 @@ import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index a323d253..6c5dcc83 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -9,13 +9,13 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Collection; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index f89b35a7..504ffced 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -115,9 +115,18 @@ public static PhysicsSpaceSettings defaults() { * Convenience factory for a space with streaming PhysicsChunk terrain enabled. */ @Nonnull - public static PhysicsSpaceSettings streamingWorldCollision() { + public static PhysicsSpaceSettings streamingPhysicsChunk() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); return settings; } + + /** + * @deprecated Use {@link #streamingPhysicsChunk()}. + */ + @Deprecated + @Nonnull + public static PhysicsSpaceSettings streamingWorldCollision() { + return streamingPhysicsChunk(); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java index 31d00577..e7b4eff7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java @@ -65,7 +65,7 @@ public class PhysicsWorldCollisionSettings { public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; /** - * World collision mode for this space. Defaults to NONE so Impulse + * PhysicsChunk terrain mode for this space. Defaults to NONE so Impulse * is fully opt-in: no terrain collision is created unless explicitly requested. */ @Nonnull @@ -149,21 +149,21 @@ public void setEntityChunkBoundaryMode( public void setWorldCollisionRadius(int worldCollisionRadius) { this.worldCollisionRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "World collision radius", + "PhysicsChunk terrain radius", worldCollisionRadius, MAX_WORLD_COLLISION_RADIUS); } public void setWorldCollisionBodyRadius(int worldCollisionBodyRadius) { this.worldCollisionBodyRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "World collision body radius", + "PhysicsChunk terrain body radius", worldCollisionBodyRadius, MAX_WORLD_COLLISION_BODY_RADIUS); } public void setWorldCollisionTtlTicks(int worldCollisionTtlTicks) { this.worldCollisionTtlTicks = PhysicsSettingsValidation.requirePositiveAtMost( - "World collision TTL", + "PhysicsChunk terrain TTL", worldCollisionTtlTicks, MAX_WORLD_COLLISION_TTL_TICKS); } diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index c824d94a..a8262ade 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -12,6 +12,7 @@ exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physicsstore; exports dev.hytalemodding.impulse.core.plugin.resources; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java index 220f6c7d..38e14261 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -18,6 +17,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import javax.annotation.Nonnull; import org.junit.jupiter.api.Test; class ImpulseSubPluginRegistrationTest { @@ -27,10 +27,12 @@ void generatedManifestSubPluginsSupportHytalePendingLoadInheritance() throws IOE PluginManifest parent = decodeGeneratedManifest(); PluginIdentifier parentId = new PluginIdentifier(parent); - for (PluginManifest subPlugin : parent.getSubPlugins()) { - assertDoesNotThrow(() -> subPlugin.inherit(parent), subPlugin.getName()); + List prepared = ImpulseSubPluginRegistration.prepareSubPluginManifests(parent); + for (PluginManifest subPlugin : prepared) { assertTrue(subPlugin.getDependencies().containsKey(parentId)); } + assertSubPluginDependsOn(prepared, "ImpulseControl", "ImpulsePhysicsEntity"); + assertSubPluginDependsOn(prepared, "ImpulsePhysicsChunk", "ImpulsePhysicsEntity"); } @Test @@ -64,6 +66,20 @@ private static void assertPreparedSubPlugin(PluginManifest manifest, assertTrue(manifest.getDependencies().containsKey(parentId)); } + private static void assertSubPluginDependsOn(@Nonnull List subPlugins, + @Nonnull String subPluginName, + @Nonnull String dependencyName) { + PluginIdentifier dependencyId = new PluginIdentifier("HytaleModding", dependencyName); + for (PluginManifest subPlugin : subPlugins) { + if (subPluginName.equals(subPlugin.getName())) { + assertTrue(subPlugin.getDependencies().containsKey(dependencyId), + subPluginName + " should depend on " + dependencyName); + return; + } + } + throw new AssertionError("Missing subplugin " + subPluginName); + } + private static PluginManifest manifest(String group, String name, String main, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java index b1afc6e3..c91174d3 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java @@ -10,7 +10,7 @@ import com.hypixel.hytale.codec.ExtraInfo; import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; @@ -98,15 +98,15 @@ void acceptsUpdatedVisualSyncRadiiWhenOrderingStaysValid() { void rejectsNonPositiveWorldCollisionValues() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - assertEquals("World collision radius must be between 1 and " + assertEquals("PhysicsChunk terrain radius must be between 1 and " + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS, assertThrows(IllegalArgumentException.class, () -> settings.getWorldCollisionSettings().setWorldCollisionRadius(0)).getMessage()); - assertEquals("World collision body radius must be between 1 and " + assertEquals("PhysicsChunk terrain body radius must be between 1 and " + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS, assertThrows(IllegalArgumentException.class, () -> settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(0)).getMessage()); - assertEquals("World collision TTL must be between 1 and " + assertEquals("PhysicsChunk terrain TTL must be between 1 and " + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS, assertThrows(IllegalArgumentException.class, () -> settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(0)).getMessage()); @@ -257,8 +257,8 @@ void defaultsDoNotCarryBackendExtensionValues() { } @Test - void streamingWorldCollisionFactoryEnablesStreamingMode() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingWorldCollision(); + void streamingPhysicsChunkFactoryEnablesStreamingMode() { + PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingPhysicsChunk(); assertEquals(WorldCollisionMode.STREAMING, settings.getWorldCollisionSettings().getWorldCollisionMode()); assertEquals(PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponentTest.java similarity index 100% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/components/BodyCommandComponentTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponentTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponentTest.java similarity index 94% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponentTest.java index df9edc19..5c25f833 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/projection/BodyAttachmentComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponentTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.projection; +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -8,7 +8,6 @@ import java.util.UUID; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; diff --git a/impulse-examples/build.gradle.kts b/impulse-examples/build.gradle.kts index d5194595..c0134068 100644 --- a/impulse-examples/build.gradle.kts +++ b/impulse-examples/build.gradle.kts @@ -38,5 +38,9 @@ hytaleTools { modUrl = property("mod_website") as String modDescription = "Example plugins for Impulse" manifestServerVersion = property("hytale_version") as String - manifestDependencies = "HytaleModding:Impulse=*" + manifestDependencies = listOf( + "HytaleModding:Impulse=*", + "HytaleModding:ImpulsePhysicsEntity=*", + "HytaleModding:ImpulsePhysicsChunk=*" + ).joinToString(",") } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 51ad6246..0d018ce1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -247,7 +247,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, : "") + ": mode=" + mode.serialized() + " space=" + spaceId.value() - + " worldCollision=streaming" + + " physicsChunk=streaming" + " bodyCollisionRadius=" + worldCollisionSettings.getWorldCollisionBodyRadius() + " prewarmedSections=" + prewarmedSections + " step=" + worldSettings.getStepMode().getSerializedName() From bec4e2d268cfd266cf0e3e0b2c817279754d001e Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 19:20:14 +0200 Subject: [PATCH 352/534] fix(core): use loader-safe subplugin ordering Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 18 +++++++++--------- .../ImpulseSubPluginRegistrationTest.java | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index d838f619..26f1d353 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -138,20 +138,20 @@ tasks.named("updatePluginManifest") { val subPlugins = manifestJson["SubPlugins"] as? List> ?: return@doLast - fun MutableMap.mergeDependencies(dependencies: Map) { + fun MutableMap.mergeLoadBefore(loadBefore: Map) { @Suppress("UNCHECKED_CAST") - val existingDependencies = (this["Dependencies"] as? Map) + val existingLoadBefore = (this["LoadBefore"] as? Map) ?.toMutableMap() ?: linkedMapOf() - existingDependencies.putAll(dependencies) - this["Dependencies"] = existingDependencies + existingLoadBefore.putAll(loadBefore) + this["LoadBefore"] = existingLoadBefore } - val physicsEntityDependency = mapOf("HytaleModding:ImpulsePhysicsEntity" to "*") - subPlugins.firstOrNull { it["Name"] == "ImpulseControl" } - ?.mergeDependencies(physicsEntityDependency) - subPlugins.firstOrNull { it["Name"] == "ImpulsePhysicsChunk" } - ?.mergeDependencies(physicsEntityDependency) + subPlugins.firstOrNull { it["Name"] == "ImpulsePhysicsEntity" } + ?.mergeLoadBefore(mapOf( + "HytaleModding:ImpulseControl" to "*", + "HytaleModding:ImpulsePhysicsChunk" to "*" + )) manifestFile.writeText( groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(manifestJson)) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java index 38e14261..bc1fda3c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java @@ -31,8 +31,8 @@ void generatedManifestSubPluginsSupportHytalePendingLoadInheritance() throws IOE for (PluginManifest subPlugin : prepared) { assertTrue(subPlugin.getDependencies().containsKey(parentId)); } - assertSubPluginDependsOn(prepared, "ImpulseControl", "ImpulsePhysicsEntity"); - assertSubPluginDependsOn(prepared, "ImpulsePhysicsChunk", "ImpulsePhysicsEntity"); + assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulseControl"); + assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulsePhysicsChunk"); } @Test @@ -66,14 +66,14 @@ private static void assertPreparedSubPlugin(PluginManifest manifest, assertTrue(manifest.getDependencies().containsKey(parentId)); } - private static void assertSubPluginDependsOn(@Nonnull List subPlugins, + private static void assertSubPluginLoadsBefore(@Nonnull PluginManifest parent, @Nonnull String subPluginName, @Nonnull String dependencyName) { PluginIdentifier dependencyId = new PluginIdentifier("HytaleModding", dependencyName); - for (PluginManifest subPlugin : subPlugins) { + for (PluginManifest subPlugin : parent.getSubPlugins()) { if (subPluginName.equals(subPlugin.getName())) { - assertTrue(subPlugin.getDependencies().containsKey(dependencyId), - subPluginName + " should depend on " + dependencyName); + assertTrue(subPlugin.getLoadBefore().containsKey(dependencyId), + subPluginName + " should load before " + dependencyName); return; } } From ca0867afefe35649f973bf8a7629bcc13bff52d7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 19:34:56 +0200 Subject: [PATCH 353/534] fix(core): stabilize crucible physics runtime checks Signed-off-by: Blovien --- .../crucible/ImpulseApiCrucibleTests.java | 193 +++++++++++------- ...pulseRapierBodyBenchmarkCrucibleTests.java | 10 +- 2 files changed, 123 insertions(+), 80 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 5a17a282..d19eb0fc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -22,6 +22,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; @@ -35,6 +36,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.function.Function; import javax.annotation.Nonnull; import org.joml.Vector3f; @@ -181,8 +183,7 @@ private static boolean createSpaceAndBody() { } private static CompletionStage spaceCountRoundTrip(@Nonnull CrucibleContext context) { - try { - World world = context.world(); + return callWhenPhysicsStoreIdle(context, "run Crucible space count round trip", world -> { PhysicsWorldResource resource = physicsResource(world); Store store = physicsStore(world); int previousCount = resource.getSpaceCount(); @@ -190,17 +191,13 @@ private static CompletionStage spaceCountRoundTrip(@Nonnull CrucibleCon "crucible", PhysicsSpaceSettings.defaults()); PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); - return CompletableFuture.completedFuture(resource.getSpaceCount() == previousCount - && !resource.hasSpace(spaceId)); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } + return resource.getSpaceCount() == previousCount && !resource.hasSpace(spaceId); + }); } private static CompletionStage createdExplicitSpaceLifecycleWorks( @Nonnull CrucibleContext context) { - try { - World world = context.world(); + return callWhenPhysicsStoreIdle(context, "run Crucible explicit space lifecycle", world -> { PhysicsWorldResource resource = physicsResource(world); Store store = physicsStore(world); SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), @@ -210,10 +207,8 @@ private static CompletionStage createdExplicitSpaceLifecycleWorks( && resource.getSpaceSettings(spaceId).getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING; PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); - return CompletableFuture.completedFuture(registered && !resource.hasSpace(spaceId)); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } + return registered && !resource.hasSpace(spaceId); + }); } private static CompletionStage clearPopulatedSpaces( @@ -229,26 +224,51 @@ private static CompletionStage detachedUnregisterRemovesBackendBody( private static CompletionStage populatedBodyCleanup( @Nonnull CrucibleContext context, boolean checkSpaceRemoval) { - try { - World world = context.world(); + return createPopulatedBodyCleanupState(context) + .thenCompose(state -> waitApproxTicksOnWorld(context, 4) + .thenCompose(_ -> removeBodyEntityAndWait(context, state.store(), state.bodyRef())) + .thenCompose(_ -> PhysicsDiagnostics.bodyCountAsync(state.store(), state.spaceId())) + .thenCompose(bodyCount -> PhysicsThreading.callWhenBackendIdleOnWorldThread( + state.world(), + "check Crucible body cleanup", + _ -> { + PhysicsWorldResource resource = physicsResource(state.world()); + boolean spaceEmpty = bodyCount == 0; + boolean noRegistrations = resource.getBodyRegistrationViews().isEmpty(); + boolean removedSpace = true; + if (checkSpaceRemoval || spaceEmpty) { + PhysicsStoreSpaceMutations.removeEmptySpace( + state.store(), + state.spaceId()); + removedSpace = !resource.hasSpace(state.spaceId()); + } + return spaceEmpty && noRegistrations && removedSpace; + }))); + } + + private static CompletionStage createPopulatedBodyCleanupState( + @Nonnull CrucibleContext context) { + return callWhenPhysicsStoreIdle(context, "create Crucible body cleanup state", world -> { PhysicsWorldResource resource = physicsResource(world); Store store = physicsStore(world); SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), "crucible", PhysicsSpaceSettings.defaults()); Ref bodyRef = addCrucibleBox(store, spaceId, UUID.randomUUID()); - return context.waitApproxTicksOnWorld(4) - .thenCompose(_ -> removeBodyEntityAndWait(context, store, bodyRef)) - .thenApply(_ -> { - boolean spaceEmpty = PhysicsDiagnostics.bodyCount(store, spaceId) == 0; - boolean noRegistrations = resource.getBodyRegistrationViews().isEmpty(); - boolean removedSpace = true; - if (checkSpaceRemoval || spaceEmpty) { - PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); - removedSpace = !resource.hasSpace(spaceId); - } - return spaceEmpty && noRegistrations && removedSpace; - }); + return new PopulatedBodyCleanupState(world, store, spaceId, bodyRef); + }); + } + + @Nonnull + private static CompletionStage callWhenPhysicsStoreIdle( + @Nonnull CrucibleContext context, + @Nonnull String operation, + @Nonnull Function action) { + try { + World world = context.world(); + return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + operation, + _ -> action.apply(world)); } catch (ReflectiveOperationException e) { return CompletableFuture.failedFuture(e); } @@ -257,11 +277,20 @@ private static CompletionStage populatedBodyCleanup( private static CompletionStage removeBodyEntityAndWait(@Nonnull CrucibleContext context, @Nonnull Store store, @Nonnull Ref bodyRef) { - if (bodyRef.isValid()) { - store.removeEntity(bodyRef, store.getRegistry().newHolder(), RemoveReason.REMOVE); - } + return PhysicsThreading.executeOnWorldThread(PhysicsThreading.world(store), + "remove Crucible body entity", + _ -> { + if (bodyRef.isValid()) { + store.removeEntity(bodyRef, store.getRegistry().newHolder(), RemoveReason.REMOVE); + } + }) + .thenCompose(_ -> waitApproxTicksOnWorld(context, 4)); + } + + private static CompletionStage waitApproxTicksOnWorld(@Nonnull CrucibleContext context, + int ticks) { try { - return context.waitApproxTicksOnWorld(4); + return context.waitApproxTicksOnWorld(ticks); } catch (ReflectiveOperationException e) { return CompletableFuture.failedFuture(e); } @@ -292,51 +321,17 @@ private static Ref addCrucibleBox(@Nonnull Store sto } private static CompletionStage settingsRoundTrip(@Nonnull CrucibleContext context) { - World world; - try { - world = context.world(); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - PhysicsWorldResource resource = physicsResource(world); - Store store = physicsStore(world); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); - settings.getWorldCollisionSettings().setWorldCollisionRadius(9); - settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(5); - settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(77); - settings.getVisualSyncSettings().setVisualMaxSyncRadius(96); - settings.getVisualSyncSettings().setVisualFullSyncRadius(48); - settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(false); - settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(3); - settings.getVisualSyncSettings().setVisualFarSyncIntervalTicks(17); - settings.getVisualSyncSettings().setVisualOcclusionMode(VisualOcclusionMode.PRIORITY); - settings.getVisualSyncSettings().setVisualOcclusionRaycastsPerTick(31); - settings.getVisualSyncSettings().setVisualOcclusionCacheTicks(7); - settings.getSolverSettings().setSolverIterations(5); - settings.getSolverSettings().setStabilizationIterations(1); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_INTERNAL_PGS_ITERATIONS, - 2); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_MIN_ISLAND_SIZE, - 64); - settings.getVisualSyncSettings().setEntityVisualSyncCullingEnabled(true); - settings.getVisualSyncSettings().setVisualVisibilityCullingEnabled(true); - settings.getVisualMaterializationSettings().setDetachedVisualMaterializationEnabled(true); - settings.getVisualMaterializationSettings().setDetachedVisualDematerializationRadius(72); - settings.getVisualMaterializationSettings().setDetachedVisualMaterializationRadius(48); - settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(33); - settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(444); - settings.getVisualMaterializationSettings().setDetachedVisualBlockType("Rock_Stone"); + PhysicsSpaceSettings settings = populatedSettings(); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", - settings); - try { - PhysicsSpaceSettings copy = resource.getSpaceSettings(spaceId); - boolean roundTrip = - copy.getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING + return callWhenPhysicsStoreIdle(context, "run Crucible settings round trip", world -> { + PhysicsWorldResource resource = physicsResource(world); + Store store = physicsStore(world); + SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), + "crucible", + settings); + try { + PhysicsSpaceSettings copy = resource.getSpaceSettings(spaceId); + return copy.getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING && copy.getWorldCollisionSettings().getWorldCollisionRadius() == 9 && copy.getWorldCollisionSettings().getWorldCollisionBodyRadius() == 5 && copy.getWorldCollisionSettings().getWorldCollisionTtlTicks() == 77 @@ -366,10 +361,44 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte && copy.getVisualMaterializationSettings().getDetachedVisualMaxSpawnsPerTick() == 33 && copy.getVisualMaterializationSettings().getDetachedVisualMaxMaterialized() == 444 && "Rock_Stone".equals(copy.getVisualMaterializationSettings().getDetachedVisualBlockType()); - return CompletableFuture.completedFuture(roundTrip); - } finally { - PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); - } + } finally { + PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); + } + }); + } + + @Nonnull + private static PhysicsSpaceSettings populatedSettings() { + PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); + settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); + settings.getWorldCollisionSettings().setWorldCollisionRadius(9); + settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(5); + settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(77); + settings.getVisualSyncSettings().setVisualMaxSyncRadius(96); + settings.getVisualSyncSettings().setVisualFullSyncRadius(48); + settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(false); + settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(3); + settings.getVisualSyncSettings().setVisualFarSyncIntervalTicks(17); + settings.getVisualSyncSettings().setVisualOcclusionMode(VisualOcclusionMode.PRIORITY); + settings.getVisualSyncSettings().setVisualOcclusionRaycastsPerTick(31); + settings.getVisualSyncSettings().setVisualOcclusionCacheTicks(7); + settings.getSolverSettings().setSolverIterations(5); + settings.getSolverSettings().setStabilizationIterations(1); + settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, + RAPIER_INTERNAL_PGS_ITERATIONS, + 2); + settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, + RAPIER_MIN_ISLAND_SIZE, + 64); + settings.getVisualSyncSettings().setEntityVisualSyncCullingEnabled(true); + settings.getVisualSyncSettings().setVisualVisibilityCullingEnabled(true); + settings.getVisualMaterializationSettings().setDetachedVisualMaterializationEnabled(true); + settings.getVisualMaterializationSettings().setDetachedVisualDematerializationRadius(72); + settings.getVisualMaterializationSettings().setDetachedVisualMaterializationRadius(48); + settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(33); + settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(444); + settings.getVisualMaterializationSettings().setDetachedVisualBlockType("Rock_Stone"); + return settings; } private static PhysicsWorldResource physicsResource(@Nonnull World world) { @@ -381,6 +410,12 @@ private static Store physicsStore(@Nonnull World world) { return PhysicsStoreCrucibleSupport.physicsStore(world); } + private record PopulatedBodyCleanupState(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Ref bodyRef) { + } + private static boolean stepSpaceDoesNotThrow() { PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); int spaceId = runtime.createSpace(SpaceId.next()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index e822967a..3a095c84 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -432,11 +432,15 @@ private static MatrixHealth assessHealth(@Nonnull MatrixCase matrixCase, if (stats.detachedBodies != matrixCase.count()) { stops.add("detachedBodies=" + stats.detachedBodies + "!=" + matrixCase.count()); } + int expectedBodies = expectedBenchmarkBodies(matrixCase); + if (stats.bodies != expectedBodies) { + stops.add("bodies=" + stats.bodies + "!=" + expectedBodies); + } int expectedSubsteps = step.getTickSamples() * matrixCase.fixedSubsteps(); if (step.getTickSamples() > 0 && step.getSubsteps() != expectedSubsteps) { stops.add("substeps=" + step.getSubsteps() + "!=" + expectedSubsteps); } - int expectedSnapshots = step.getTickSamples() * matrixCase.count(); + int expectedSnapshots = step.getTickSamples() * expectedBodies; if (step.getTickSamples() > 0 && step.getBodySnapshots() != expectedSnapshots) { stops.add("bodySnapshots=" + step.getBodySnapshots() + "!=" + expectedSnapshots); } @@ -482,6 +486,10 @@ private static MatrixHealth assessHealth(@Nonnull MatrixCase matrixCase, return new MatrixHealth(MatrixStatus.PASS, "within gates"); } + private static int expectedBenchmarkBodies(@Nonnull MatrixCase matrixCase) { + return matrixCase.count() + 1; + } + private static void logComparison(@Nonnull List reports) { if (reports.size() < 2) { return; From 79022688ce308e0772262bef26c530d592623e70 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 19:48:32 +0200 Subject: [PATCH 354/534] fix(api): avoid legacy snapshot body-list scans Signed-off-by: Blovien --- .../api/runtime/legacy/LegacyPhysicsBackendRuntime.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java index c147b5dc..24b93073 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java @@ -211,9 +211,7 @@ public int bodyCount(int spaceId) { @Override public boolean containsBody(int spaceId, long bodyId) { - SpaceState state = requireSpace(spaceId); - PhysicsBody body = state.bodiesById.get(bodyId); - return body != null && state.space.getBodies().contains(body); + return requireSpace(spaceId).bodiesById.containsKey(bodyId); } @Override @@ -222,7 +220,7 @@ public boolean bodySnapshot(int spaceId, @Nonnull BackendBodySnapshotSink sink) { SpaceState state = requireSpace(spaceId); PhysicsBody body = state.bodiesById.get(bodyId); - if (body == null || !state.space.getBodies().contains(body)) { + if (body == null) { return false; } emitBodySnapshot(bodyId, PhysicsBodySnapshot.from(body), sink); @@ -236,7 +234,7 @@ public void snapshotBodies(int spaceId, SpaceState state = requireSpace(spaceId); bodyIds.forEachBodyId(bodyId -> { PhysicsBody body = state.bodiesById.get(bodyId); - if (body != null && state.space.getBodies().contains(body)) { + if (body != null) { emitBodySnapshot(bodyId, PhysicsBodySnapshot.from(body), sink); } }); From 234df25f5fa0213b638b73e48a6d6f4a0ab1aef3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 20:02:37 +0200 Subject: [PATCH 355/534] refactor(core): expose physics chunk terrain names Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 30 +- .../crucible/ImpulseApiCrucibleTests.java | 22 +- ...tachedStreamingBenchmarkCrucibleTests.java | 8 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 4 +- .../PhysicsStoreBenchmarkQueries.java | 2 +- .../PhysicsChunkBuildOptions.java | 10 +- .../physicschunk/PhysicsChunkModule.java | 6 +- .../PhysicsChunkTerrainRuntime.java | 6 +- .../commands/PhysicsChunkSettingsCommand.java | 66 ++-- .../PersistentPhysicsStorePreflight.java | 20 +- .../persistence/PersistentSpaceDto.java | 74 +++++ .../PhysicsStoreSpaceMutations.java | 4 +- .../PhysicsChunkSettingsIndexResource.java | 6 +- .../resources/PhysicsSpaceRuntime.java | 4 +- .../PhysicsWorldRuntimeResource.java | 32 +- .../CompletedStepPublicationSystem.java | 4 +- .../PhysicsChunkSettingsIndexSystem.java | 2 +- .../core/plugin/body/PhysicsBodyKind.java | 14 +- .../physicschunk/PhysicsChunkTerrain.java | 86 ++++++ .../PhysicsChunkTerrainBuildStats.java | 47 +++ .../physicschunk/PhysicsChunkTerrainMode.java | 34 +++ .../PhysicsChunkTerrainPrewarmStats.java | 23 ++ .../PhysicsChunkTerrainStats.java | 29 ++ .../physicschunk/PhysicsWorldCollision.java | 7 +- .../WorldCollisionBuildStats.java | 3 +- .../physicschunk/WorldCollisionMode.java | 29 +- .../WorldCollisionPrewarmStats.java | 3 +- .../physicschunk/WorldCollisionStats.java | 3 +- .../components/WorldCollisionComponent.java | 143 ++++++--- .../settings/PhysicsChunkTerrainSettings.java | 286 ++++++++++++++++++ .../plugin/settings/PhysicsSpaceSettings.java | 28 +- .../PhysicsWorldCollisionSettings.java | 190 +----------- .../VoxelTerrainCollisionCacheTest.java | 6 +- .../resources/PhysicsSpaceSettingsTest.java | 108 +++---- .../commands/PhysicsChunkExampleCommand.java | 16 +- .../commands/PhysicsStoreExampleCommands.java | 6 +- .../commands/stress/StressBodiesCommand.java | 42 +-- .../explosive/ExplosiveBlockRuntime.java | 6 +- .../systems/ExplosiveFuseContactSystem.java | 6 +- 39 files changed, 957 insertions(+), 458 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index ba7516e8..8a694af0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -22,8 +22,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.ArrayList; @@ -66,18 +66,18 @@ protected void execute(@Nonnull CommandContext context, return; } - WorldCollisionMode physicsChunkMode = physicsChunkArg.provided(context) + PhysicsChunkTerrainMode physicsChunkMode = physicsChunkArg.provided(context) ? parsePhysicsChunkMode(physicsChunkArg.get(context)) - : WorldCollisionMode.STREAMING; + : PhysicsChunkTerrainMode.STREAMING; if (physicsChunkMode == null) { context.sendMessage(Message.raw("physicsChunk must be none, manual, or streaming.")); return; } - PhysicsSpaceSettings settings = physicsChunkMode == WorldCollisionMode.STREAMING + PhysicsSpaceSettings settings = physicsChunkMode == PhysicsChunkTerrainMode.STREAMING ? PhysicsSpaceSettings.streamingPhysicsChunk() : PhysicsSpaceSettings.defaults(); - settings.getWorldCollisionSettings().setWorldCollisionMode(physicsChunkMode); + settings.getPhysicsChunkTerrainSettings().setTerrainMode(physicsChunkMode); Store physicsStore = PhysicsThreading.store(world); try { @@ -119,9 +119,9 @@ private static void sendSpaces(@Nonnull CommandContext context, .map(summary -> { PhysicsSpaceSettings settings = PhysicsSpaces.settings(physicsStore, summary.spaceId()); - WorldCollisionMode physicsChunkMode = settings != null - ? settings.getWorldCollisionSettings().getWorldCollisionMode() - : WorldCollisionMode.NONE; + PhysicsChunkTerrainMode physicsChunkMode = settings != null + ? settings.getPhysicsChunkTerrainSettings().getTerrainMode() + : PhysicsChunkTerrainMode.NONE; return new SpaceListEntry(summary.spaceId(), summary.backendId().value(), summary.bodyCount(), @@ -215,7 +215,7 @@ private static void deleteIfEmpty(@Nonnull CommandContext context, return; } - PhysicsWorldCollision.clearSpace(world, physicsStore, spaceId); + PhysicsChunkTerrain.clearSpace(world, physicsStore, spaceId); PhysicsSpaces.removeWithContents(physicsStore, spaceId); context.sendMessage(Message.raw("Deleted physics space id=" + rawSpaceId + " with " + backendBodies + " backend bodies and " + joints + " joints.")); @@ -272,11 +272,11 @@ private static BackendId parseBackendId(@Nonnull CommandContext context, } @Nullable - private static WorldCollisionMode parsePhysicsChunkMode(@Nonnull String value) { + private static PhysicsChunkTerrainMode parsePhysicsChunkMode(@Nonnull String value) { return switch (value.toLowerCase(Locale.ROOT)) { - case "none", "off", "disabled" -> WorldCollisionMode.NONE; - case "manual" -> WorldCollisionMode.MANUAL; - case "streaming", "stream", "on", "enabled" -> WorldCollisionMode.STREAMING; + case "none", "off", "disabled" -> PhysicsChunkTerrainMode.NONE; + case "manual" -> PhysicsChunkTerrainMode.MANUAL; + case "streaming", "stream", "on", "enabled" -> PhysicsChunkTerrainMode.STREAMING; default -> null; }; } @@ -298,6 +298,6 @@ private record SpaceListEntry(@Nonnull SpaceId spaceId, @Nonnull String backendId, int bodies, int joints, - @Nonnull WorldCollisionMode physicsChunkMode) { + @Nonnull PhysicsChunkTerrainMode physicsChunkMode) { } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index d19eb0fc..f1d3fa34 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -29,7 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import java.util.UUID; import java.util.Collection; import java.util.List; @@ -204,8 +204,8 @@ private static CompletionStage createdExplicitSpaceLifecycleWorks( "crucible", PhysicsSpaceSettings.streamingPhysicsChunk()); boolean registered = resource.hasSpace(spaceId) - && resource.getSpaceSettings(spaceId).getWorldCollisionSettings().getWorldCollisionMode() - == WorldCollisionMode.STREAMING; + && resource.getSpaceSettings(spaceId).getPhysicsChunkTerrainSettings().getTerrainMode() + == PhysicsChunkTerrainMode.STREAMING; PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); return registered && !resource.hasSpace(spaceId); }); @@ -331,10 +331,10 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte settings); try { PhysicsSpaceSettings copy = resource.getSpaceSettings(spaceId); - return copy.getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.STREAMING - && copy.getWorldCollisionSettings().getWorldCollisionRadius() == 9 - && copy.getWorldCollisionSettings().getWorldCollisionBodyRadius() == 5 - && copy.getWorldCollisionSettings().getWorldCollisionTtlTicks() == 77 + return copy.getPhysicsChunkTerrainSettings().getTerrainMode() == PhysicsChunkTerrainMode.STREAMING + && copy.getPhysicsChunkTerrainSettings().getTerrainRadius() == 9 + && copy.getPhysicsChunkTerrainSettings().getBodyTerrainRadius() == 5 + && copy.getPhysicsChunkTerrainSettings().getTerrainTtlTicks() == 77 && copy.getVisualSyncSettings().getVisualFullSyncRadius() == 48 && copy.getVisualSyncSettings().getVisualMaxSyncRadius() == 96 && !copy.getVisualSyncSettings().isVisualFarSyncCutoffEnabled() @@ -370,10 +370,10 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte @Nonnull private static PhysicsSpaceSettings populatedSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); - settings.getWorldCollisionSettings().setWorldCollisionRadius(9); - settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(5); - settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(77); + settings.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.STREAMING); + settings.getPhysicsChunkTerrainSettings().setTerrainRadius(9); + settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(5); + settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(77); settings.getVisualSyncSettings().setVisualMaxSyncRadius(96); settings.getVisualSyncSettings().setVisualFullSyncRadius(48); settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(false); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 02928bc2..a5ab2c3f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -34,7 +34,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -245,8 +245,8 @@ private CompletionStage startStageWhenReady(int count, int attempt int retained = retainChunks(chunks); configureMissingSectionDiagnostics(chunks); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); - settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(BODY_STREAMING_RADIUS); + settings.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.STREAMING); + settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(BODY_STREAMING_RADIUS); settings.getSolverSettings().setSolverIterations(4); settings.getSolverSettings().setStabilizationIterations(1); settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, @@ -373,7 +373,7 @@ private PrewarmStats prewarmWorldCollision(@Nonnull SpaceId spaceId, int count) PhysicsTerrainMutationQueueResource queue = physicsStore.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( - physics.getSpaceSettings(spaceId).getWorldCollisionSettings()); + physics.getSpaceSettings(spaceId).getPhysicsChunkTerrainSettings()); WorldCollisionPrewarmStats stats = worldCollisionStreaming.ensureAround(world, spaceUuid, queue, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 3a095c84..92cc90c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -29,7 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.ArrayList; @@ -224,7 +224,7 @@ private CompletionStage startCase(@Nonnull MatrixCase matrixCase) { physics.clearSyntheticVisualInterests(); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.NONE); + settings.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.NONE); settings.getSolverSettings().setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); settings.getSolverSettings().setStabilizationIterations( PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index b3c2d575..9df51459 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -107,7 +107,7 @@ private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, if (shape != null && shape.getShapeType() == ShapeType.PLANE) { return; } - if (body.getKind() == PhysicsBodyKind.WORLD_COLLISION) { + if (body.getKind().isTerrainCollider()) { stats.worldCollisionBodies++; return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index ef42e6b7..308bb1a4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -12,7 +12,7 @@ public record PhysicsChunkBuildOptions(@Nonnull TerrainColliderMode terrainColli float terrainRestitution) { public static final PhysicsChunkBuildOptions DEFAULT = - fromNativeVoxelTerrainEnabled(PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); + fromNativeVoxelTerrainEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); public PhysicsChunkBuildOptions { Objects.requireNonNull(terrainColliderMode, "terrainColliderMode"); @@ -25,7 +25,7 @@ public record PhysicsChunkBuildOptions(@Nonnull TerrainColliderMode terrainColli } @Nonnull - public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsWorldCollisionSettings settings) { + public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrainSettings settings) { return new PhysicsChunkBuildOptions( TerrainColliderMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), settings.getTerrainFriction(), @@ -35,8 +35,8 @@ public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsWorldCollisi @Nonnull public static PhysicsChunkBuildOptions fromNativeVoxelTerrainEnabled(boolean enabled) { return new PhysicsChunkBuildOptions(TerrainColliderMode.fromNativeVoxelTerrainEnabled(enabled), - PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION); + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); } public boolean nativeVoxelTerrainEnabled() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java index c089f3a7..339c4ea6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -28,13 +28,13 @@ protected void setup() { PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); PhysicsChunkCommandContributions.register(); - PhysicsWorldCollision.enableModule(); + PhysicsChunkTerrain.enableModule(); LOGGER.at(Level.INFO).log("Impulse PhysicsChunk terrain producer enabled."); } @Override protected void shutdown() { - PhysicsWorldCollision.disableModule(); + PhysicsChunkTerrain.disableModule(); PhysicsChunkCommandContributions.unregister(); PhysicsChunkTypes.clearEntityStoreResourceTypes(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java index e821f6ba..f5dfe54f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java @@ -6,7 +6,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import it.unimi.dsi.fastutil.ints.Int2LongMap; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -57,7 +57,7 @@ public WorldCollisionBuildStats rebuildAround(@Nonnull World world, center, radius, PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled( - PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); } @Nonnull @@ -102,7 +102,7 @@ public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, radius, tick, PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled( - PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index c21d28a3..e4dc71ba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -14,10 +14,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -32,19 +32,19 @@ public class PhysicsChunkSettingsCommand extends AbstractAsyncPlayerCommand { private final OptionalArg playerRadiusArg = this.withOptionalArg( "playerRadius", "Block radius streamed around players (1-" - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS + + PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS + ")", ArgTypes.INTEGER); private final OptionalArg bodyRadiusArg = this.withOptionalArg( "bodyRadius", "Block radius streamed around awake dynamic bodies (1-" - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS + + PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS + ")", ArgTypes.INTEGER); private final OptionalArg ttlArg = this.withOptionalArg( "ttl", "Ticks before unused streamed sections are pruned (1-" - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS + + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS + ")", ArgTypes.INTEGER); private final OptionalArg chunkBoundaryArg = this.withOptionalArg( @@ -91,7 +91,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - WorldCollisionMode mode = settings.getWorldCollisionSettings().getWorldCollisionMode(); + PhysicsChunkTerrainMode mode = settings.getPhysicsChunkTerrainSettings().getTerrainMode(); if (modeArg.provided(ctx)) { mode = parseMode(modeArg.get(ctx)); if (mode == null) { @@ -100,7 +100,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } } - EntityChunkBoundaryMode chunkBoundaryMode = settings.getWorldCollisionSettings().getEntityChunkBoundaryMode(); + EntityChunkBoundaryMode chunkBoundaryMode = settings.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode(); if (chunkBoundaryArg.provided(ctx)) { chunkBoundaryMode = parseChunkBoundaryMode(chunkBoundaryArg.get(ctx)); if (chunkBoundaryMode == null) { @@ -109,7 +109,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } } - boolean nativeVoxelTerrainEnabled = settings.getWorldCollisionSettings().isNativeVoxelTerrainEnabled(); + boolean nativeVoxelTerrainEnabled = settings.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled(); if (terrainArg.provided(ctx)) { Boolean parsedTerrain = parseTerrain(terrainArg.get(ctx)); if (parsedTerrain == null) { @@ -121,30 +121,30 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int playerRadius = playerRadiusArg.provided(ctx) ? playerRadiusArg.get(ctx) - : settings.getWorldCollisionSettings().getWorldCollisionRadius(); + : settings.getPhysicsChunkTerrainSettings().getTerrainRadius(); int bodyRadius = bodyRadiusArg.provided(ctx) ? bodyRadiusArg.get(ctx) - : settings.getWorldCollisionSettings().getWorldCollisionBodyRadius(); - int ttl = ttlArg.provided(ctx) ? ttlArg.get(ctx) : settings.getWorldCollisionSettings().getWorldCollisionTtlTicks(); - if (outOfRange(playerRadius, PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS) - || outOfRange(bodyRadius, PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS) - || outOfRange(ttl, PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS)) { + : settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius(); + int ttl = ttlArg.provided(ctx) ? ttlArg.get(ctx) : settings.getPhysicsChunkTerrainSettings().getTerrainTtlTicks(); + if (outOfRange(playerRadius, PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS) + || outOfRange(bodyRadius, PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS) + || outOfRange(ttl, PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS)) { ctx.sender().sendMessage(Message.raw( - "playerRadius must be 1-" + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS + "playerRadius must be 1-" + PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS + ", bodyRadius must be 1-" - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS + + PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS + ", and ttl must be 1-" - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS + + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS + ".")); return CompletableFuture.completedFuture(null); } - settings.getWorldCollisionSettings().setWorldCollisionMode(mode); - settings.getWorldCollisionSettings().setEntityChunkBoundaryMode(chunkBoundaryMode); - settings.getWorldCollisionSettings().setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); - settings.getWorldCollisionSettings().setWorldCollisionRadius(playerRadius); - settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(bodyRadius); - settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(ttl); + settings.getPhysicsChunkTerrainSettings().setTerrainMode(mode); + settings.getPhysicsChunkTerrainSettings().setEntityChunkBoundaryMode(chunkBoundaryMode); + settings.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); + settings.getPhysicsChunkTerrainSettings().setTerrainRadius(playerRadius); + settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(bodyRadius); + settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(ttl); PhysicsSpaces.putSettings(physicsStore, selection.spaceRef(), settings); sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); @@ -168,24 +168,24 @@ private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull PhysicsSpaceSettings settings) { ctx.sender().sendMessage(Message.raw("Impulse PhysicsChunk settings for space " + spaceId.value() - + ": mode=" + settings.getWorldCollisionSettings().getWorldCollisionMode().name().toLowerCase(Locale.ROOT) - + " playerRadius=" + settings.getWorldCollisionSettings().getWorldCollisionRadius() - + " bodyRadius=" + settings.getWorldCollisionSettings().getWorldCollisionBodyRadius() - + " ttl=" + settings.getWorldCollisionSettings().getWorldCollisionTtlTicks() + + ": mode=" + settings.getPhysicsChunkTerrainSettings().getTerrainMode().name().toLowerCase(Locale.ROOT) + + " playerRadius=" + settings.getPhysicsChunkTerrainSettings().getTerrainRadius() + + " bodyRadius=" + settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius() + + " ttl=" + settings.getPhysicsChunkTerrainSettings().getTerrainTtlTicks() + " chunkBoundary=" - + settings.getWorldCollisionSettings().getEntityChunkBoundaryMode().name().toLowerCase(Locale.ROOT) + + settings.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode().name().toLowerCase(Locale.ROOT) + " terrain=" - + (settings.getWorldCollisionSettings().isNativeVoxelTerrainEnabled() + + (settings.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled() ? "native_voxels" : "boxes"))); } @Nullable - private static WorldCollisionMode parseMode(@Nonnull String value) { + private static PhysicsChunkTerrainMode parseMode(@Nonnull String value) { return switch (value.toLowerCase(Locale.ROOT)) { - case "none", "off", "disabled" -> WorldCollisionMode.NONE; - case "manual" -> WorldCollisionMode.MANUAL; - case "streaming", "stream", "on", "enabled" -> WorldCollisionMode.STREAMING; + case "none", "off", "disabled" -> PhysicsChunkTerrainMode.NONE; + case "manual" -> PhysicsChunkTerrainMode.MANUAL; + case "streaming", "stream", "on", "enabled" -> PhysicsChunkTerrainMode.STREAMING; default -> null; }; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index 1838894c..42776efb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -2,7 +2,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -65,21 +65,21 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, if (!PhysicsStorePersistenceValidation.isFinite(space.getGravity())) { errors.add("PhysicsStore space " + uuid + " has non-finite gravity"); } - if (space.getWorldCollisionRadius() < 1 - || space.getWorldCollisionRadius() - > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS) { + if (space.getTerrainRadius() < 1 + || space.getTerrainRadius() + > PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS) { errors.add("PhysicsStore space " + uuid + " has invalid PhysicsChunk terrain radius"); } - if (space.getWorldCollisionBodyRadius() < 1 - || space.getWorldCollisionBodyRadius() - > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS) { + if (space.getBodyTerrainRadius() < 1 + || space.getBodyTerrainRadius() + > PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS) { errors.add("PhysicsStore space " + uuid + " has invalid PhysicsChunk terrain body radius"); } - if (space.getWorldCollisionTtlTicks() < 1 - || space.getWorldCollisionTtlTicks() - > PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS) { + if (space.getTerrainTtlTicks() < 1 + || space.getTerrainTtlTicks() + > PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS) { errors.add("PhysicsStore space " + uuid + " has invalid PhysicsChunk terrain TTL"); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index e4eecd78..e7828960 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; @@ -217,6 +218,28 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, new ExtensionSettingsComponent()); } + public PersistentSpaceDto(@Nonnull UUID spaceUuid, + @Nonnull String backendId, + @Nonnull Vector3f gravity, + @Nonnull PhysicsChunkTerrainMode terrainMode, + boolean nativeVoxelTerrainEnabled, + int terrainRadius, + int bodyTerrainRadius, + int terrainTtlTicks, + float terrainFriction, + float terrainRestitution) { + this(spaceUuid, + backendId, + gravity, + terrainMode.toWorldCollisionMode(), + nativeVoxelTerrainEnabled, + terrainRadius, + bodyTerrainRadius, + terrainTtlTicks, + terrainFriction, + terrainRestitution); + } + public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, @@ -257,6 +280,40 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, "extensionSettings").clone(); } + public PersistentSpaceDto(@Nonnull UUID spaceUuid, + @Nonnull String backendId, + @Nonnull Vector3f gravity, + @Nonnull PhysicsChunkTerrainMode terrainMode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, + boolean nativeVoxelTerrainEnabled, + int terrainRadius, + int bodyTerrainRadius, + int terrainTtlTicks, + float terrainFriction, + float terrainRestitution, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { + this(spaceUuid, + backendId, + gravity, + terrainMode.toWorldCollisionMode(), + entityChunkBoundaryMode, + nativeVoxelTerrainEnabled, + terrainRadius, + bodyTerrainRadius, + terrainTtlTicks, + terrainFriction, + terrainRestitution, + solverSettings, + visualSyncSettings, + visualMaterializationSettings, + collisionLodSettings, + extensionSettings); + } + @Nonnull public UUID getSpaceUuid() { return spaceUuid; @@ -277,6 +334,11 @@ public WorldCollisionMode getWorldCollisionMode() { return worldCollisionMode; } + @Nonnull + public PhysicsChunkTerrainMode getTerrainMode() { + return worldCollisionMode.toPhysicsChunkTerrainMode(); + } + @Nonnull public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { return entityChunkBoundaryMode; @@ -290,14 +352,26 @@ public int getWorldCollisionRadius() { return worldCollisionRadius; } + public int getTerrainRadius() { + return worldCollisionRadius; + } + public int getWorldCollisionBodyRadius() { return worldCollisionBodyRadius; } + public int getBodyTerrainRadius() { + return worldCollisionBodyRadius; + } + public int getWorldCollisionTtlTicks() { return worldCollisionTtlTicks; } + public int getTerrainTtlTicks() { + return worldCollisionTtlTicks; + } + public float getTerrainFriction() { return terrainFriction; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 25030ec6..d4283157 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -64,7 +64,7 @@ public static Ref addSpace(@Nonnull Store store, Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, spaceUuid, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), - new WorldCollisionComponent(settings.getWorldCollisionSettings()), + new WorldCollisionComponent(settings.getPhysicsChunkTerrainSettings()), new SolverSettingsComponent(settings.getSolverSettings()), new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), @@ -119,7 +119,7 @@ public static void putSpaceSettings(@Nonnull Store store, PhysicsThreading.requireWorldThread(store, "update a PhysicsStore space entity"); store.putComponent(ref, WorldCollisionComponent.getComponentType(), - new WorldCollisionComponent(settings.getWorldCollisionSettings())); + new WorldCollisionComponent(settings.getPhysicsChunkTerrainSettings())); PhysicsEntities.putSpaceSettingsComponents(store, ref, new SolverSettingsComponent(settings.getSolverSettings()), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index 24cab36b..1533db05 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -5,7 +5,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.TerrainColliderMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; @@ -36,7 +36,7 @@ public synchronized void replaceAll(@Nonnull Map streamingSpaces() { return settingsBySpaceUuid.values().stream() - .filter(settings -> settings.mode() == WorldCollisionMode.STREAMING) + .filter(settings -> settings.mode() == PhysicsChunkTerrainMode.STREAMING) .toList(); } @@ -68,7 +68,7 @@ public static void setResourceType( } public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, - @Nonnull WorldCollisionMode mode, + @Nonnull PhysicsChunkTerrainMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, int radius, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java index bde8c693..c781b0ce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java @@ -51,7 +51,7 @@ public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId "World %s creating physics space using backend %s collision=%s", worldName, backendId, - settings.getWorldCollisionSettings().getWorldCollisionMode()); + settings.getPhysicsChunkTerrainSettings().getTerrainMode()); PhysicsBackendRuntime runtime = Impulse.createRuntime(backendId); BackendSpaceHandle backendSpaceHandle = new BackendSpaceHandle(runtime.createSpace(spaceId)); @@ -72,7 +72,7 @@ public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId worldName, spaceId, backendId, - settings.getWorldCollisionSettings().getWorldCollisionMode()); + settings.getPhysicsChunkTerrainSettings().getTerrainMode()); return binding; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 621faf70..fdd883c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -37,7 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -58,7 +58,7 @@ import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; @@ -1442,22 +1442,22 @@ public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spa private void setSpaceSettingsDirect(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { - PhysicsWorldCollisionSettings previousCollisionSettings = - spaceRuntime.getLiveSpaceSettings(spaceId).getWorldCollisionSettings(); + PhysicsChunkTerrainSettings previousCollisionSettings = + spaceRuntime.getLiveSpaceSettings(spaceId).getPhysicsChunkTerrainSettings(); boolean worldCollisionSettingsChanged = worldCollisionStreamingSettingsChanged(previousCollisionSettings, - settings.getWorldCollisionSettings()); + settings.getPhysicsChunkTerrainSettings()); boolean terrainRepresentationChanged = previousCollisionSettings.isNativeVoxelTerrainEnabled() - != settings.getWorldCollisionSettings().isNativeVoxelTerrainEnabled(); + != settings.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled(); boolean terrainMaterialChanged = Float.compare(previousCollisionSettings.getTerrainFriction(), - settings.getWorldCollisionSettings().getTerrainFriction()) != 0 + settings.getPhysicsChunkTerrainSettings().getTerrainFriction()) != 0 || Float.compare(previousCollisionSettings.getTerrainRestitution(), - settings.getWorldCollisionSettings().getTerrainRestitution()) != 0; + settings.getPhysicsChunkTerrainSettings().getTerrainRestitution()) != 0; boolean worldCollisionDisabled = - settings.getWorldCollisionSettings().getWorldCollisionMode() == WorldCollisionMode.NONE - && previousCollisionSettings.getWorldCollisionMode() != WorldCollisionMode.NONE; + settings.getPhysicsChunkTerrainSettings().getTerrainMode() == PhysicsChunkTerrainMode.NONE + && previousCollisionSettings.getTerrainMode() != PhysicsChunkTerrainMode.NONE; spaceRuntime.setSpaceSettings(spaceId, settings); if (worldCollisionDisabled || terrainRepresentationChanged || terrainMaterialChanged) { terrainRuntime.clear(requireSpaceBinding(spaceId)); @@ -1467,12 +1467,12 @@ private void setSpaceSettingsDirect(@Nonnull SpaceId spaceId, } private static boolean worldCollisionStreamingSettingsChanged( - @Nonnull PhysicsWorldCollisionSettings previous, - @Nonnull PhysicsWorldCollisionSettings next) { - return previous.getWorldCollisionMode() != next.getWorldCollisionMode() - || previous.getWorldCollisionRadius() != next.getWorldCollisionRadius() - || previous.getWorldCollisionBodyRadius() != next.getWorldCollisionBodyRadius() - || previous.getWorldCollisionTtlTicks() != next.getWorldCollisionTtlTicks() + @Nonnull PhysicsChunkTerrainSettings previous, + @Nonnull PhysicsChunkTerrainSettings next) { + return previous.getTerrainMode() != next.getTerrainMode() + || previous.getTerrainRadius() != next.getTerrainRadius() + || previous.getBodyTerrainRadius() != next.getBodyTerrainRadius() + || previous.getTerrainTtlTicks() != next.getTerrainTtlTicks() || previous.isNativeVoxelTerrainEnabled() != next.isNativeVoxelTerrainEnabled(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index ebc51115..b3e2be83 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -177,9 +177,9 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run SpaceId spaceId = compatibility.getSpaceId(terrain.getSpaceUuid()); if (spaceId != null) { registrations.add(new BodyRegistrationPublication(rowRef, - new PhysicsBodyRegistrationView(rowUuid, + new PhysicsBodyRegistrationView(rowUuid, spaceId, - PhysicsBodyKind.WORLD_COLLISION, + PhysicsBodyKind.TERRAIN, PhysicsBodyPersistenceMode.RUNTIME_ONLY))); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index 2e3f1210..7c706dc2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -60,7 +60,7 @@ private static void collectChunk( ? worldCollision : new WorldCollisionComponent(); settingsBySpaceUuid.put(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, - settings.getMode(), + settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelTerrainEnabled(), settings.getRadius(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java index fbfbb693..eabae795 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java @@ -3,11 +3,19 @@ /** * Classifies why a body exists in the runtime registry. - * - * FIXME: this should be removed */ public enum PhysicsBodyKind { BODY, + + /** + * @deprecated Use {@link #TERRAIN}. + */ + @Deprecated(forRemoval = false) WORLD_COLLISION, - TEMPORARY + TEMPORARY, + TERRAIN; + + public boolean isTerrainCollider() { + return this == TERRAIN || this == WORLD_COLLISION; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java new file mode 100644 index 00000000..14fe91d5 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -0,0 +1,86 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import java.util.Objects; +import javax.annotation.Nonnull; +import org.joml.Vector3d; + +/** + * Public PhysicsChunk operations for terrain-backed collision. + */ +@SuppressWarnings("deprecation") +public final class PhysicsChunkTerrain { + + private PhysicsChunkTerrain() { + } + + public static void enableModule() { + PhysicsWorldCollision.enableModule(); + } + + public static void disableModule() { + PhysicsWorldCollision.disableModule(); + } + + public static boolean isModuleEnabled() { + return PhysicsWorldCollision.isModuleEnabled(); + } + + @Nonnull + public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d center, + int radius) { + return PhysicsChunkTerrainBuildStats.fromWorldCollisionStats( + PhysicsWorldCollision.rebuildAround(world, + store, + spaceId, + center, + radius)); + } + + @Nonnull + public static PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d center, + int radius) { + return PhysicsChunkTerrainBuildStats.fromWorldCollisionStats( + PhysicsWorldCollision.refreshAround(world, + store, + spaceId, + center, + radius)); + } + + @Nonnull + public static PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull Iterable centers, + int radius, + long tick) { + return PhysicsChunkTerrainPrewarmStats.fromWorldCollisionStats( + PhysicsWorldCollision.ensureAround(world, + store, + spaceId, + Objects.requireNonNull(centers, "centers"), + radius, + tick)); + } + + public static int clearSpace(@Nonnull World world, + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + return PhysicsWorldCollision.clearSpace(world, store, spaceId); + } + + @Nonnull + public static PhysicsChunkTerrainStats stats(@Nonnull World world) { + return PhysicsChunkTerrainStats.fromWorldCollisionStats(PhysicsWorldCollision.stats(world)); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java new file mode 100644 index 00000000..0cc18932 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java @@ -0,0 +1,47 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import javax.annotation.Nonnull; + +/** + * Aggregate statistics from building or rebuilding streamed PhysicsChunk terrain geometry. + */ +public record PhysicsChunkTerrainBuildStats(int scannedBlocks, + int solidBlocks, + int culledInteriorBlocks, + int fullCubeRuns, + int detailBoxes, + int colliderBodies, + int removedBodies, + int sectionsBuilt, + int sectionsRebuilt, + int voxelBodies) { + + @Nonnull + public static PhysicsChunkTerrainBuildStats fromWorldCollisionStats( + @Nonnull WorldCollisionBuildStats stats) { + return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), + stats.solidBlocks(), + stats.culledInteriorBlocks(), + stats.fullCubeRuns(), + stats.detailBoxes(), + stats.colliderBodies(), + stats.removedBodies(), + stats.sectionsBuilt(), + stats.sectionsRebuilt(), + stats.voxelBodies()); + } + + @Nonnull + public WorldCollisionBuildStats toWorldCollisionStats() { + return new WorldCollisionBuildStats(scannedBlocks, + solidBlocks, + culledInteriorBlocks, + fullCubeRuns, + detailBoxes, + colliderBodies, + removedBodies, + sectionsBuilt, + sectionsRebuilt, + voxelBodies); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java new file mode 100644 index 00000000..46c4f1a5 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java @@ -0,0 +1,34 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import javax.annotation.Nonnull; + +/** + * Controls PhysicsChunk terrain collider generation for a PhysicsStore space. + */ +public enum PhysicsChunkTerrainMode { + /** + * Terrain colliders are disabled. + */ + NONE, + + /** + * Terrain colliders are only built when explicitly requested. + */ + MANUAL, + + /** + * Terrain colliders stream around players and configured physics bodies. + */ + STREAMING; + + @Nonnull + public WorldCollisionMode toWorldCollisionMode() { + return WorldCollisionMode.valueOf(name()); + } + + @Nonnull + public static PhysicsChunkTerrainMode fromWorldCollisionMode( + @Nonnull WorldCollisionMode mode) { + return valueOf(mode.name()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java new file mode 100644 index 00000000..e6dee545 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java @@ -0,0 +1,23 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import javax.annotation.Nonnull; + +/** + * Statistics from ensuring PhysicsChunk terrain around multiple target positions. + */ +public record PhysicsChunkTerrainPrewarmStats(int sectionTargets, + @Nonnull PhysicsChunkTerrainBuildStats buildStats) { + + @Nonnull + public static PhysicsChunkTerrainPrewarmStats fromWorldCollisionStats( + @Nonnull WorldCollisionPrewarmStats stats) { + return new PhysicsChunkTerrainPrewarmStats(stats.sectionTargets(), + PhysicsChunkTerrainBuildStats.fromWorldCollisionStats(stats.buildStats())); + } + + @Nonnull + public WorldCollisionPrewarmStats toWorldCollisionStats() { + return new WorldCollisionPrewarmStats(sectionTargets, + buildStats.toWorldCollisionStats()); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java new file mode 100644 index 00000000..f685d8a6 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java @@ -0,0 +1,29 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import javax.annotation.Nonnull; + +/** + * Current size of the generated PhysicsChunk terrain cache. + */ +public record PhysicsChunkTerrainStats(int spaces, + int sections, + int bodies, + int shapeTemplates) { + + @Nonnull + public static PhysicsChunkTerrainStats fromWorldCollisionStats( + @Nonnull WorldCollisionStats stats) { + return new PhysicsChunkTerrainStats(stats.spaces(), + stats.sections(), + stats.bodies(), + stats.shapeTemplates()); + } + + @Nonnull + public WorldCollisionStats toWorldCollisionStats() { + return new WorldCollisionStats(spaces, + sections, + bodies, + shapeTemplates); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index 5143da67..d9f513f2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -22,8 +22,9 @@ import org.joml.Vector3d; /** - * Public PhysicsChunk operations for terrain-backed collision. + * @deprecated Use {@link PhysicsChunkTerrain}. */ +@Deprecated(forRemoval = false) public final class PhysicsWorldCollision { private PhysicsWorldCollision() { @@ -167,11 +168,11 @@ private static PhysicsChunkSpaceSettings requireSettings( WorldCollisionComponent component = store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); - if (settings.getMode() == WorldCollisionMode.NONE) { + if (settings.getTerrainMode() == PhysicsChunkTerrainMode.NONE) { throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); } return new PhysicsChunkSpaceSettings(spaceUuid, - settings.getMode(), + settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelTerrainEnabled(), settings.getRadius(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java index 1ceb7bb3..dd893e22 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java @@ -1,8 +1,9 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Aggregate statistics from building or rebuilding streamed PhysicsChunk terrain geometry. + * @deprecated Use {@link PhysicsChunkTerrainBuildStats}. */ +@Deprecated(forRemoval = false) public record WorldCollisionBuildStats(int scannedBlocks, int solidBlocks, int culledInteriorBlocks, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java index a8910290..04caf731 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java @@ -1,21 +1,24 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; +import javax.annotation.Nonnull; + /** - * Controls how a physics space interacts with Hytale world voxel collision. - * - *

        This is an opt-in per-space policy. Impulse does not impose PhysicsChunk terrain - * on any space by default; the integrator chooses the level of intrusion.

        - * - *
          - *
        • {@link #NONE} - No PhysicsChunk terrain. The space is pure physics with no terrain.
        • - *
        • {@link #MANUAL} - PhysicsChunk terrain exists but must be built/cleared explicitly - * by the integrator (e.g. via commands or a custom system).
        • - *
        • {@link #STREAMING} - Impulse automatically streams section collision around - * tracked players/bodies and prunes unused sections after a TTL.
        • - *
        + * @deprecated Use {@link PhysicsChunkTerrainMode}. */ +@Deprecated(forRemoval = false) public enum WorldCollisionMode { NONE, MANUAL, - STREAMING + STREAMING; + + @Nonnull + public PhysicsChunkTerrainMode toPhysicsChunkTerrainMode() { + return PhysicsChunkTerrainMode.valueOf(name()); + } + + @Nonnull + public static WorldCollisionMode fromPhysicsChunkTerrainMode( + @Nonnull PhysicsChunkTerrainMode mode) { + return valueOf(mode.name()); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java index 6e745a55..a0fa180c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java @@ -1,8 +1,9 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Statistics from ensuring PhysicsChunk terrain around multiple target positions. + * @deprecated Use {@link PhysicsChunkTerrainPrewarmStats}. */ +@Deprecated(forRemoval = false) public record WorldCollisionPrewarmStats(int sectionTargets, WorldCollisionBuildStats buildStats) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java index 2c0f3fb6..2c273b3b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java @@ -1,8 +1,9 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Current size of the generated PhysicsChunk terrain cache. + * @deprecated Use {@link PhysicsChunkTerrainStats}. */ +@Deprecated(forRemoval = false) public record WorldCollisionStats(int spaces, int sections, int bodies, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java index baa91b87..9c242481 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java @@ -8,8 +8,10 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; @@ -24,9 +26,11 @@ public final class WorldCollisionComponent implements Component { public static final BuilderCodec CODEC = BuilderCodec.builder( WorldCollisionComponent.class, WorldCollisionComponent::new) - .append(new KeyedCodec<>("Mode", new EnumCodec<>(WorldCollisionMode.class), false), - (component, value) -> component.mode = value != null ? value : WorldCollisionMode.NONE, - WorldCollisionComponent::getMode) + .append(new KeyedCodec<>("Mode", new EnumCodec<>(PhysicsChunkTerrainMode.class), false), + (component, value) -> component.terrainMode = value != null + ? value + : PhysicsChunkTerrainMode.NONE, + WorldCollisionComponent::getTerrainMode) .add() .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), (component, value) -> component.nativeVoxelTerrainEnabled = value != null && value, @@ -37,77 +41,81 @@ public final class WorldCollisionComponent implements Component { false), (component, value) -> component.entityChunkBoundaryMode = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, WorldCollisionComponent::getEntityChunkBoundaryMode) .add() .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), (component, value) -> component.radius = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, WorldCollisionComponent::getRadius) .add() .append(new KeyedCodec<>("BodyRadius", Codec.INTEGER, false), (component, value) -> component.bodyRadius = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS, + : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, WorldCollisionComponent::getBodyRadius) .add() .append(new KeyedCodec<>("TtlTicks", Codec.INTEGER, false), (component, value) -> component.ttlTicks = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, WorldCollisionComponent::getTtlTicks) .add() .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), (component, value) -> component.terrainFriction = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, WorldCollisionComponent::getTerrainFriction) .add() .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), (component, value) -> component.terrainRestitution = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, WorldCollisionComponent::getTerrainRestitution) .add() .build(); @Nonnull - private WorldCollisionMode mode = WorldCollisionMode.NONE; + private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = - PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelTerrainEnabled = - PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; - private int radius = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS; - private int bodyRadius = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS; - private int ttlTicks = PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS; - private float terrainFriction = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION; + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private int radius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; + private int bodyRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; + private int ttlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; + private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; + private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; public WorldCollisionComponent() { } - public WorldCollisionComponent(@Nonnull PhysicsWorldCollisionSettings settings) { - this(settings.getWorldCollisionMode(), + public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainSettings settings) { + this(settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelTerrainEnabled(), - settings.getWorldCollisionRadius(), - settings.getWorldCollisionBodyRadius(), - settings.getWorldCollisionTtlTicks(), + settings.getTerrainRadius(), + settings.getBodyTerrainRadius(), + settings.getTerrainTtlTicks(), settings.getTerrainFriction(), settings.getTerrainRestitution()); } - public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, + public WorldCollisionComponent(@Nonnull PhysicsWorldCollisionSettings settings) { + this((PhysicsChunkTerrainSettings) settings); + } + + public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, boolean nativeVoxelTerrainEnabled, int radius, int bodyRadius, int ttlTicks, float terrainFriction, float terrainRestitution) { - this(mode, - PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + this(terrainMode, + PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, nativeVoxelTerrainEnabled, radius, bodyRadius, @@ -116,7 +124,7 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, terrainRestitution); } - public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, + public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, int radius, @@ -124,7 +132,7 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, int ttlTicks, float terrainFriction, float terrainRestitution) { - this.mode = Objects.requireNonNull(mode, "mode"); + this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, "entityChunkBoundaryMode"); this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; @@ -135,13 +143,72 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, this.terrainRestitution = terrainRestitution; } + /** + * @deprecated Use {@link #WorldCollisionComponent(PhysicsChunkTerrainMode, boolean, int, int, int, float, float)}. + */ + @Deprecated(forRemoval = false) + public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this(mode.toPhysicsChunkTerrainMode(), + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } + + /** + * @deprecated Use {@link #WorldCollisionComponent(PhysicsChunkTerrainMode, EntityChunkBoundaryMode, boolean, int, int, int, float, float)}. + */ + @Deprecated(forRemoval = false) + public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this(mode.toPhysicsChunkTerrainMode(), + entityChunkBoundaryMode, + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } + + @Nonnull + public PhysicsChunkTerrainMode getTerrainMode() { + return terrainMode; + } + + public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { + this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + } + + /** + * @deprecated Use {@link #getTerrainMode()}. + */ + @Deprecated(forRemoval = false) @Nonnull public WorldCollisionMode getMode() { - return mode; + return terrainMode.toWorldCollisionMode(); } + /** + * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. + */ + @Deprecated(forRemoval = false) public void setMode(@Nonnull WorldCollisionMode mode) { - this.mode = Objects.requireNonNull(mode, "mode"); + setTerrainMode(mode.toPhysicsChunkTerrainMode()); } @Nonnull @@ -204,19 +271,23 @@ public void setTerrainRestitution(float terrainRestitution) { } public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getWorldCollisionSettings()); + copyTo(settings.getPhysicsChunkTerrainSettings()); } - public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { - settings.setWorldCollisionMode(mode); + public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { + settings.setTerrainMode(terrainMode); settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); settings.setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); - settings.setWorldCollisionRadius(radius); - settings.setWorldCollisionBodyRadius(bodyRadius); - settings.setWorldCollisionTtlTicks(ttlTicks); + settings.setTerrainRadius(radius); + settings.setBodyTerrainRadius(bodyRadius); + settings.setTerrainTtlTicks(ttlTicks); settings.setTerrainMaterial(terrainFriction, terrainRestitution); } + public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { + copyTo((PhysicsChunkTerrainSettings) settings); + } + @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.worldCollisionComponentType(); @@ -225,7 +296,7 @@ public static ComponentType getComponentT @Nonnull @Override public WorldCollisionComponent clone() { - return new WorldCollisionComponent(mode, + return new WorldCollisionComponent(terrainMode, entityChunkBoundaryMode, nativeVoxelTerrainEnabled, radius, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java new file mode 100644 index 00000000..cab29576 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java @@ -0,0 +1,286 @@ +package dev.hytalemodding.impulse.core.plugin.settings; + +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Terrain collider streaming settings for a PhysicsStore space. + */ +public class PhysicsChunkTerrainSettings { + + /** + * Block radius around each tracked player for streaming terrain colliders. + */ + public static final int DEFAULT_TERRAIN_RADIUS = 8; + + /** + * Hard block-radius cap for player-centered PhysicsChunk terrain streaming. + */ + public static final int MAX_TERRAIN_RADIUS = 128; + + /** + * Block radius around each active dynamic physics body for streaming terrain colliders. + */ + public static final int DEFAULT_BODY_TERRAIN_RADIUS = 4; + + /** + * Hard block-radius cap for dynamic-body PhysicsChunk terrain streaming. + */ + public static final int MAX_BODY_TERRAIN_RADIUS = 64; + + /** + * Ticks before an unused section's terrain colliders are pruned. + */ + public static final int DEFAULT_TERRAIN_TTL_TICKS = 100; + + /** + * Hard tick cap for retaining unused streamed terrain sections. + */ + public static final int MAX_TERRAIN_TTL_TICKS = 12_000; + + /** + * Default behavior when an entity-backed body reaches an unloaded chunk border. + */ + @Nonnull + public static final EntityChunkBoundaryMode DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE = + EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED; + + /** + * Whether full-cube world sections should use native backend voxel terrain when available. + */ + public static final boolean DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED = false; + + /** + * Default friction applied to generated terrain collider bodies. + */ + public static final float DEFAULT_TERRAIN_FRICTION = 0.75f; + + /** + * Default restitution applied to generated terrain collider bodies. + */ + public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; + + /** + * @deprecated Use {@link #DEFAULT_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int DEFAULT_WORLD_COLLISION_RADIUS = DEFAULT_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #MAX_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int MAX_WORLD_COLLISION_RADIUS = MAX_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #DEFAULT_BODY_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int DEFAULT_WORLD_COLLISION_BODY_RADIUS = DEFAULT_BODY_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #MAX_BODY_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int MAX_WORLD_COLLISION_BODY_RADIUS = MAX_BODY_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #DEFAULT_TERRAIN_TTL_TICKS}. + */ + @Deprecated(forRemoval = false) + public static final int DEFAULT_WORLD_COLLISION_TTL_TICKS = DEFAULT_TERRAIN_TTL_TICKS; + + /** + * @deprecated Use {@link #MAX_TERRAIN_TTL_TICKS}. + */ + @Deprecated(forRemoval = false) + public static final int MAX_WORLD_COLLISION_TTL_TICKS = MAX_TERRAIN_TTL_TICKS; + + @Nonnull + private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; + @Nonnull + private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + private boolean nativeVoxelTerrainEnabled = DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private int terrainRadius = DEFAULT_TERRAIN_RADIUS; + private int bodyTerrainRadius = DEFAULT_BODY_TERRAIN_RADIUS; + private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; + private float terrainFriction = DEFAULT_TERRAIN_FRICTION; + private float terrainRestitution = DEFAULT_TERRAIN_RESTITUTION; + + public PhysicsChunkTerrainSettings() { + } + + public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings) { + terrainMode = settings.terrainMode; + entityChunkBoundaryMode = settings.entityChunkBoundaryMode; + nativeVoxelTerrainEnabled = settings.nativeVoxelTerrainEnabled; + terrainRadius = settings.terrainRadius; + bodyTerrainRadius = settings.bodyTerrainRadius; + terrainTtlTicks = settings.terrainTtlTicks; + terrainFriction = settings.terrainFriction; + terrainRestitution = settings.terrainRestitution; + } + + @Nonnull + public PhysicsChunkTerrainMode getTerrainMode() { + return terrainMode; + } + + public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { + this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + } + + @Nonnull + public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { + return entityChunkBoundaryMode; + } + + public void setEntityChunkBoundaryMode( + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); + } + + public boolean isNativeVoxelTerrainEnabled() { + return nativeVoxelTerrainEnabled; + } + + public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { + this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + } + + public int getTerrainRadius() { + return terrainRadius; + } + + public void setTerrainRadius(int terrainRadius) { + this.terrainRadius = PhysicsSettingsValidation.requirePositiveAtMost( + "PhysicsChunk terrain radius", + terrainRadius, + MAX_TERRAIN_RADIUS); + } + + public int getBodyTerrainRadius() { + return bodyTerrainRadius; + } + + public void setBodyTerrainRadius(int bodyTerrainRadius) { + this.bodyTerrainRadius = PhysicsSettingsValidation.requirePositiveAtMost( + "PhysicsChunk terrain body radius", + bodyTerrainRadius, + MAX_BODY_TERRAIN_RADIUS); + } + + public int getTerrainTtlTicks() { + return terrainTtlTicks; + } + + public void setTerrainTtlTicks(int terrainTtlTicks) { + this.terrainTtlTicks = PhysicsSettingsValidation.requirePositiveAtMost( + "PhysicsChunk terrain TTL", + terrainTtlTicks, + MAX_TERRAIN_TTL_TICKS); + } + + public float getTerrainFriction() { + return terrainFriction; + } + + public void setTerrainFriction(float terrainFriction) { + this.terrainFriction = PhysicsSettingsValidation.requireFiniteAtLeast( + "Terrain friction", + terrainFriction, + 0.0f); + } + + public float getTerrainRestitution() { + return terrainRestitution; + } + + public void setTerrainRestitution(float terrainRestitution) { + this.terrainRestitution = PhysicsSettingsValidation.requireFiniteAtLeast( + "Terrain restitution", + terrainRestitution, + 0.0f); + } + + public void setTerrainMaterial(float terrainFriction, float terrainRestitution) { + float validatedFriction = PhysicsSettingsValidation.requireFiniteAtLeast( + "Terrain friction", + terrainFriction, + 0.0f); + float validatedRestitution = PhysicsSettingsValidation.requireFiniteAtLeast( + "Terrain restitution", + terrainRestitution, + 0.0f); + this.terrainFriction = validatedFriction; + this.terrainRestitution = validatedRestitution; + } + + /** + * @deprecated Use {@link #getTerrainMode()}. + */ + @Deprecated(forRemoval = false) + @Nonnull + public WorldCollisionMode getWorldCollisionMode() { + return terrainMode.toWorldCollisionMode(); + } + + /** + * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionMode(@Nonnull WorldCollisionMode worldCollisionMode) { + setTerrainMode(worldCollisionMode.toPhysicsChunkTerrainMode()); + } + + /** + * @deprecated Use {@link #getTerrainRadius()}. + */ + @Deprecated(forRemoval = false) + public int getWorldCollisionRadius() { + return getTerrainRadius(); + } + + /** + * @deprecated Use {@link #setTerrainRadius(int)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionRadius(int worldCollisionRadius) { + setTerrainRadius(worldCollisionRadius); + } + + /** + * @deprecated Use {@link #getBodyTerrainRadius()}. + */ + @Deprecated(forRemoval = false) + public int getWorldCollisionBodyRadius() { + return getBodyTerrainRadius(); + } + + /** + * @deprecated Use {@link #setBodyTerrainRadius(int)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionBodyRadius(int worldCollisionBodyRadius) { + setBodyTerrainRadius(worldCollisionBodyRadius); + } + + /** + * @deprecated Use {@link #getTerrainTtlTicks()}. + */ + @Deprecated(forRemoval = false) + public int getWorldCollisionTtlTicks() { + return getTerrainTtlTicks(); + } + + /** + * @deprecated Use {@link #setTerrainTtlTicks(int)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionTtlTicks(int worldCollisionTtlTicks) { + setTerrainTtlTicks(worldCollisionTtlTicks); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index 504ffced..6d74e573 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import javax.annotation.Nonnull; @@ -16,14 +16,14 @@ * code should read and mutate the domain group directly instead of adding flat * shortcut state here.

        * - *

        Default settings have PhysicsChunk terrain disabled ({@link WorldCollisionMode#NONE}), + *

        Default settings have PhysicsChunk terrain disabled ({@link PhysicsChunkTerrainMode#NONE}), * which keeps Impulse fully opt-in: no terrain bodies are created unless the integrator * explicitly opts in.

        */ public class PhysicsSpaceSettings { @Nonnull - private final PhysicsWorldCollisionSettings worldCollisionSettings; + private final PhysicsWorldCollisionSettings physicsChunkTerrainSettings; @Nonnull private final PhysicsVisualSyncSettings visualSyncSettings; @Nonnull @@ -36,7 +36,7 @@ public class PhysicsSpaceSettings { private final PhysicsExtensionSettings extensionSettings; public PhysicsSpaceSettings() { - worldCollisionSettings = new PhysicsWorldCollisionSettings(); + physicsChunkTerrainSettings = new PhysicsWorldCollisionSettings(); visualSyncSettings = new PhysicsVisualSyncSettings(); solverSettings = new PhysicsSolverSettings(); visualMaterializationSettings = new PhysicsVisualMaterializationSettings(); @@ -45,8 +45,8 @@ public PhysicsSpaceSettings() { } public PhysicsSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { - worldCollisionSettings = - new PhysicsWorldCollisionSettings(settings.worldCollisionSettings); + physicsChunkTerrainSettings = + new PhysicsWorldCollisionSettings(settings.physicsChunkTerrainSettings); visualSyncSettings = new PhysicsVisualSyncSettings(settings.visualSyncSettings); solverSettings = @@ -59,11 +59,20 @@ public PhysicsSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { } /** - * Terrain collision streaming and chunk-boundary behavior. + * Terrain collider streaming and chunk-boundary behavior. */ @Nonnull + public PhysicsChunkTerrainSettings getPhysicsChunkTerrainSettings() { + return physicsChunkTerrainSettings; + } + + /** + * @deprecated Use {@link #getPhysicsChunkTerrainSettings()}. + */ + @Deprecated(forRemoval = false) + @Nonnull public PhysicsWorldCollisionSettings getWorldCollisionSettings() { - return worldCollisionSettings; + return physicsChunkTerrainSettings; } /** @@ -117,7 +126,8 @@ public static PhysicsSpaceSettings defaults() { @Nonnull public static PhysicsSpaceSettings streamingPhysicsChunk() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); + settings.getPhysicsChunkTerrainSettings() + .setTerrainMode(PhysicsChunkTerrainMode.STREAMING); return settings; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java index e7b4eff7..77af5db6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java @@ -1,197 +1,21 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import lombok.Getter; -import lombok.Setter; import javax.annotation.Nonnull; /** - * Terrain collision streaming settings for a physics space. + * @deprecated Use {@link PhysicsChunkTerrainSettings}. */ -public class PhysicsWorldCollisionSettings { - - /** - * Block radius around each tracked player for streaming collision. - */ - public static final int DEFAULT_WORLD_COLLISION_RADIUS = 8; - - /** - * Hard block-radius cap for player-centered PhysicsChunk terrain streaming. - */ - public static final int MAX_WORLD_COLLISION_RADIUS = 128; - - /** - * Block radius around each active dynamic physics body for streaming collision. - * Smaller than the player radius because bodies should not pull collision - * as far as players, but still need terrain to land on. - */ - public static final int DEFAULT_WORLD_COLLISION_BODY_RADIUS = 4; - - /** - * Hard block-radius cap for dynamic-body PhysicsChunk terrain streaming. - */ - public static final int MAX_WORLD_COLLISION_BODY_RADIUS = 64; - - /** - * Ticks before an unused section's collision bodies are pruned. - */ - public static final int DEFAULT_WORLD_COLLISION_TTL_TICKS = 100; - - /** - * Hard tick cap for retaining unused streamed collision sections. - */ - public static final int MAX_WORLD_COLLISION_TTL_TICKS = 12_000; - - /** - * Default behavior when an entity-backed body reaches an unloaded chunk border. - */ - @Nonnull - public static final EntityChunkBoundaryMode DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE = - EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED; - - /** - * Whether full-cube world sections should use native backend voxel terrain when available. - */ - public static final boolean DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED = false; - - /** - * Default friction applied to generated terrain collision bodies. - */ - public static final float DEFAULT_TERRAIN_FRICTION = 0.75f; - - /** - * Default restitution applied to generated terrain collision bodies. - */ - public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; - - /** - * PhysicsChunk terrain mode for this space. Defaults to NONE so Impulse - * is fully opt-in: no terrain collision is created unless explicitly requested. - */ - @Nonnull - private WorldCollisionMode worldCollisionMode = WorldCollisionMode.NONE; - - /** - * How entity-backed bodies behave when they reach an unloaded chunk border. - */ - @Nonnull - private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - - /** - * Enables native backend voxel terrain for full-cube PhysicsChunk terrain. - */ - @Setter - @Getter - private boolean nativeVoxelTerrainEnabled = DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; - - /** - * Block radius around tracked player positions for streaming or manual build. - */ - @Getter - private int worldCollisionRadius = DEFAULT_WORLD_COLLISION_RADIUS; - - /** - * Block radius around active dynamic physics bodies for streaming collision. - */ - @Getter - private int worldCollisionBodyRadius = DEFAULT_WORLD_COLLISION_BODY_RADIUS; - - /** - * How long a section stays loaded after its last use, in server ticks. - */ - @Getter - private int worldCollisionTtlTicks = DEFAULT_WORLD_COLLISION_TTL_TICKS; - - /** - * Friction applied to generated terrain bodies for this space. - */ - @Getter - private float terrainFriction = DEFAULT_TERRAIN_FRICTION; - - /** - * Restitution applied to generated terrain bodies for this space. - */ - @Getter - private float terrainRestitution = DEFAULT_TERRAIN_RESTITUTION; +@Deprecated(forRemoval = false) +public class PhysicsWorldCollisionSettings extends PhysicsChunkTerrainSettings { public PhysicsWorldCollisionSettings() { } - public PhysicsWorldCollisionSettings(@Nonnull PhysicsWorldCollisionSettings settings) { - worldCollisionMode = settings.worldCollisionMode; - entityChunkBoundaryMode = settings.entityChunkBoundaryMode; - nativeVoxelTerrainEnabled = settings.nativeVoxelTerrainEnabled; - worldCollisionRadius = settings.worldCollisionRadius; - worldCollisionBodyRadius = settings.worldCollisionBodyRadius; - worldCollisionTtlTicks = settings.worldCollisionTtlTicks; - terrainFriction = settings.terrainFriction; - terrainRestitution = settings.terrainRestitution; - } - - @Nonnull - public WorldCollisionMode getWorldCollisionMode() { - return worldCollisionMode; - } - - public void setWorldCollisionMode(@Nonnull WorldCollisionMode worldCollisionMode) { - this.worldCollisionMode = worldCollisionMode; - } - - @Nonnull - public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { - return entityChunkBoundaryMode; - } - - public void setEntityChunkBoundaryMode( - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { - this.entityChunkBoundaryMode = entityChunkBoundaryMode; - } - - public void setWorldCollisionRadius(int worldCollisionRadius) { - this.worldCollisionRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain radius", - worldCollisionRadius, - MAX_WORLD_COLLISION_RADIUS); + public PhysicsWorldCollisionSettings(@Nonnull PhysicsChunkTerrainSettings settings) { + super(settings); } - public void setWorldCollisionBodyRadius(int worldCollisionBodyRadius) { - this.worldCollisionBodyRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain body radius", - worldCollisionBodyRadius, - MAX_WORLD_COLLISION_BODY_RADIUS); - } - - public void setWorldCollisionTtlTicks(int worldCollisionTtlTicks) { - this.worldCollisionTtlTicks = PhysicsSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain TTL", - worldCollisionTtlTicks, - MAX_WORLD_COLLISION_TTL_TICKS); - } - - public void setTerrainFriction(float terrainFriction) { - this.terrainFriction = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain friction", - terrainFriction, - 0.0f); - } - - public void setTerrainRestitution(float terrainRestitution) { - this.terrainRestitution = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain restitution", - terrainRestitution, - 0.0f); - } - - public void setTerrainMaterial(float terrainFriction, float terrainRestitution) { - float validatedFriction = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain friction", - terrainFriction, - 0.0f); - float validatedRestitution = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain restitution", - terrainRestitution, - 0.0f); - this.terrainFriction = validatedFriction; - this.terrainRestitution = validatedRestitution; + public PhysicsWorldCollisionSettings(@Nonnull PhysicsWorldCollisionSettings settings) { + super(settings); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java index 13323a89..f4eec231 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -274,7 +274,7 @@ void supportedRuntimeCreatesVoxelTerrainAndKeepsDebugBoxes() throws Throwable { @Test void terrainMaterialSettingsApplyToNativeVoxelAndFallbackBoxes() throws Throwable { - PhysicsWorldCollisionSettings nativeSettings = new PhysicsWorldCollisionSettings(); + PhysicsChunkTerrainSettings nativeSettings = new PhysicsChunkTerrainSettings(); nativeSettings.setNativeVoxelTerrainEnabled(true); nativeSettings.setTerrainMaterial(0.9f, 0.25f); RuntimeFixture nativeFixture = runtimeFixture("test:voxel-runtime-custom-material", true); @@ -301,7 +301,7 @@ void terrainMaterialSettingsApplyToNativeVoxelAndFallbackBoxes() throws Throwabl assertEquals(0.9f, calls.getFirst().friction(), 0.0001f); assertEquals(0.25f, calls.getFirst().restitution(), 0.0001f); - PhysicsWorldCollisionSettings fallbackSettings = new PhysicsWorldCollisionSettings(); + PhysicsChunkTerrainSettings fallbackSettings = new PhysicsChunkTerrainSettings(); fallbackSettings.setTerrainMaterial(0.8f, 0.1f); RuntimeFixture fallbackFixture = runtimeFixture("test:box-runtime-custom-material", false); Object fallbackSection = newCachedSection(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java index c91174d3..c4c7fcb6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.codec.ExtraInfo; import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import java.util.Objects; import java.util.UUID; import org.bson.BsonDocument; @@ -95,27 +95,27 @@ void acceptsUpdatedVisualSyncRadiiWhenOrderingStaysValid() { } @Test - void rejectsNonPositiveWorldCollisionValues() { + void rejectsNonPositivePhysicsChunkTerrainValues() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); assertEquals("PhysicsChunk terrain radius must be between 1 and " - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_RADIUS, + + PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS, assertThrows(IllegalArgumentException.class, - () -> settings.getWorldCollisionSettings().setWorldCollisionRadius(0)).getMessage()); + () -> settings.getPhysicsChunkTerrainSettings().setTerrainRadius(0)).getMessage()); assertEquals("PhysicsChunk terrain body radius must be between 1 and " - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_BODY_RADIUS, + + PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS, assertThrows(IllegalArgumentException.class, - () -> settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(0)).getMessage()); + () -> settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(0)).getMessage()); assertEquals("PhysicsChunk terrain TTL must be between 1 and " - + PhysicsWorldCollisionSettings.MAX_WORLD_COLLISION_TTL_TICKS, + + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS, assertThrows(IllegalArgumentException.class, - () -> settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(0)).getMessage()); + () -> settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(0)).getMessage()); assertEquals("Terrain friction must be finite and >= 0.0", assertThrows(IllegalArgumentException.class, - () -> settings.getWorldCollisionSettings().setTerrainFriction(-0.1f)).getMessage()); + () -> settings.getPhysicsChunkTerrainSettings().setTerrainFriction(-0.1f)).getMessage()); assertEquals("Terrain restitution must be finite and >= 0.0", assertThrows(IllegalArgumentException.class, - () -> settings.getWorldCollisionSettings().setTerrainRestitution(Float.NaN)).getMessage()); + () -> settings.getPhysicsChunkTerrainSettings().setTerrainRestitution(Float.NaN)).getMessage()); assertEquals("Visual full sync radius must be between 1 and " + PhysicsVisualSyncSettings.MAX_VISUAL_FULL_SYNC_RADIUS, assertThrows(IllegalArgumentException.class, @@ -183,15 +183,15 @@ void defaultsFactoryReturnsFreshDefaultSettings() { PhysicsSpaceSettings second = PhysicsSpaceSettings.defaults(); assertNotSame(first, second); - assertEquals(WorldCollisionMode.NONE, first.getWorldCollisionSettings().getWorldCollisionMode()); - assertSame(PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - first.getWorldCollisionSettings().getEntityChunkBoundaryMode()); - assertFalse(first.getWorldCollisionSettings().isNativeVoxelTerrainEnabled()); - assertEquals(PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, - first.getWorldCollisionSettings().getTerrainFriction(), + assertEquals(PhysicsChunkTerrainMode.NONE, first.getPhysicsChunkTerrainSettings().getTerrainMode()); + assertSame(PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + first.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode()); + assertFalse(first.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); + assertEquals(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + first.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); - assertEquals(PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, - first.getWorldCollisionSettings().getTerrainRestitution(), + assertEquals(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + first.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); } @@ -199,13 +199,13 @@ void defaultsFactoryReturnsFreshDefaultSettings() { void groupedAccessorsExposeIndependentDomainState() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - settings.getWorldCollisionSettings().setWorldCollisionRadius(14); + settings.getPhysicsChunkTerrainSettings().setTerrainRadius(14); settings.getVisualSyncSettings().setVisualSyncRadii(36, 144); settings.getSolverSettings().setSolverIterations(6); settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(96); settings.getCollisionLodSettings().setCollisionLodRadii(24, 72); - assertEquals(14, settings.getWorldCollisionSettings().getWorldCollisionRadius()); + assertEquals(14, settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); assertEquals(36, settings.getVisualSyncSettings().getVisualFullSyncRadius()); assertEquals(144, settings.getVisualSyncSettings().getVisualMaxSyncRadius()); assertEquals(6, settings.getSolverSettings().getSolverIterations()); @@ -213,13 +213,13 @@ void groupedAccessorsExposeIndependentDomainState() { assertEquals(24, settings.getCollisionLodSettings().getCollisionLodNearRadius()); assertEquals(72, settings.getCollisionLodSettings().getCollisionLodMidRadius()); - settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(5); + settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(5); settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(3); settings.getSolverSettings().setDynamicSleepLinearThreshold(0.45f); settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(16); settings.getCollisionLodSettings().setCollisionLodHysteresis(4); - assertEquals(5, settings.getWorldCollisionSettings().getWorldCollisionBodyRadius()); + assertEquals(5, settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); assertEquals(3, settings.getVisualSyncSettings().getVisualMidSyncIntervalTicks()); assertEquals(0.45f, settings.getSolverSettings().getDynamicSleepLinearThreshold(), 0.0001f); assertEquals(16, @@ -260,20 +260,20 @@ void defaultsDoNotCarryBackendExtensionValues() { void streamingPhysicsChunkFactoryEnablesStreamingMode() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingPhysicsChunk(); - assertEquals(WorldCollisionMode.STREAMING, settings.getWorldCollisionSettings().getWorldCollisionMode()); - assertEquals(PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, - settings.getWorldCollisionSettings().getWorldCollisionRadius()); + assertEquals(PhysicsChunkTerrainMode.STREAMING, settings.getPhysicsChunkTerrainSettings().getTerrainMode()); + assertEquals(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, + settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); } @Test void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { PhysicsSpaceSettings original = new PhysicsSpaceSettings(); - original.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.STREAMING); - original.getWorldCollisionSettings().setWorldCollisionRadius(12); - original.getWorldCollisionSettings().setWorldCollisionBodyRadius(6); - original.getWorldCollisionSettings().setWorldCollisionTtlTicks(180); - original.getWorldCollisionSettings().setNativeVoxelTerrainEnabled(true); - original.getWorldCollisionSettings().setTerrainMaterial(0.9f, 0.15f); + original.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.STREAMING); + original.getPhysicsChunkTerrainSettings().setTerrainRadius(12); + original.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(6); + original.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(180); + original.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(true); + original.getPhysicsChunkTerrainSettings().setTerrainMaterial(0.9f, 0.15f); original.getVisualSyncSettings().setVisualMaxSyncRadius(160); original.getVisualSyncSettings().setVisualFullSyncRadius(80); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(2); @@ -290,24 +290,24 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { original.getCollisionLodSettings().setCollisionLodFarSleepEnabled(false); PhysicsSpaceSettings copy = new PhysicsSpaceSettings(original); - original.getWorldCollisionSettings().setWorldCollisionRadius(20); + original.getPhysicsChunkTerrainSettings().setTerrainRadius(20); original.getVisualSyncSettings().setVisualSyncRadii(96, 192); original.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(128); original.getCollisionLodSettings().setCollisionLodRadii(48, 112); - assertNotSame(original.getWorldCollisionSettings(), copy.getWorldCollisionSettings()); + assertNotSame(original.getPhysicsChunkTerrainSettings(), copy.getPhysicsChunkTerrainSettings()); assertNotSame(original.getVisualSyncSettings(), copy.getVisualSyncSettings()); assertNotSame(original.getSolverSettings(), copy.getSolverSettings()); assertNotSame(original.getVisualMaterializationSettings(), copy.getVisualMaterializationSettings()); assertNotSame(original.getCollisionLodSettings(), copy.getCollisionLodSettings()); - assertEquals(WorldCollisionMode.STREAMING, copy.getWorldCollisionSettings().getWorldCollisionMode()); - assertEquals(12, copy.getWorldCollisionSettings().getWorldCollisionRadius()); - assertEquals(6, copy.getWorldCollisionSettings().getWorldCollisionBodyRadius()); - assertEquals(180, copy.getWorldCollisionSettings().getWorldCollisionTtlTicks()); - assertTrue(copy.getWorldCollisionSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.9f, copy.getWorldCollisionSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.15f, copy.getWorldCollisionSettings().getTerrainRestitution(), 0.0001f); + assertEquals(PhysicsChunkTerrainMode.STREAMING, copy.getPhysicsChunkTerrainSettings().getTerrainMode()); + assertEquals(12, copy.getPhysicsChunkTerrainSettings().getTerrainRadius()); + assertEquals(6, copy.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); + assertEquals(180, copy.getPhysicsChunkTerrainSettings().getTerrainTtlTicks()); + assertTrue(copy.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); + assertEquals(0.9f, copy.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); + assertEquals(0.15f, copy.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); assertEquals(160, copy.getVisualSyncSettings().getVisualMaxSyncRadius()); assertEquals(80, copy.getVisualSyncSettings().getVisualFullSyncRadius()); assertEquals(2, copy.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); @@ -328,22 +328,22 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { @Test void persistentSpaceDtoRoundTripPreservesDetachedVisualCadenceSettings() { PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); - original.getWorldCollisionSettings().setNativeVoxelTerrainEnabled(true); - original.getWorldCollisionSettings().setTerrainMaterial(0.85f, 0.2f); + original.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(true); + original.getPhysicsChunkTerrainSettings().setTerrainMaterial(0.85f, 0.2f); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); - PhysicsWorldCollisionSettings collision = original.getWorldCollisionSettings(); + PhysicsChunkTerrainSettings collision = original.getPhysicsChunkTerrainSettings(); PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), "test:settings-persistence", new Vector3f(0.0f, -9.81f, 0.0f), - collision.getWorldCollisionMode(), + collision.getTerrainMode(), collision.getEntityChunkBoundaryMode(), collision.isNativeVoxelTerrainEnabled(), - collision.getWorldCollisionRadius(), - collision.getWorldCollisionBodyRadius(), - collision.getWorldCollisionTtlTicks(), + collision.getTerrainRadius(), + collision.getBodyTerrainRadius(), + collision.getTerrainTtlTicks(), collision.getTerrainFriction(), collision.getTerrainRestitution(), new SolverSettingsComponent(original.getSolverSettings()), @@ -360,17 +360,17 @@ void persistentSpaceDtoRoundTripPreservesDetachedVisualCadenceSettings() { assertTrue(encoded.containsKey("VisualMaterializationSettings")); PhysicsSpaceSettings decoded = Objects.requireNonNull( PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())).toSettings(); - assertTrue(decoded.getWorldCollisionSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.85f, decoded.getWorldCollisionSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.2f, decoded.getWorldCollisionSettings().getTerrainRestitution(), 0.0001f); + assertTrue(decoded.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); + assertEquals(0.85f, decoded.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); + assertEquals(0.2f, decoded.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); assertDetachedVisualCadence(decoded, 7, 9, 11); PhysicsSpaceSettings copied = state.copy().toSettings(); - assertTrue(copied.getWorldCollisionSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.85f, copied.getWorldCollisionSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.2f, copied.getWorldCollisionSettings().getTerrainRestitution(), 0.0001f); + assertTrue(copied.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); + assertEquals(0.85f, copied.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); + assertEquals(0.2f, copied.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); assertDetachedVisualCadence(copied, 7, 9, 11); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index 6aa9d290..1c02e561 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -13,10 +13,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -76,7 +76,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Store physicsStore = physicsStore(world); - WorldCollisionBuildStats stats = PhysicsWorldCollision.rebuildAround(world, + PhysicsChunkTerrainBuildStats stats = PhysicsChunkTerrain.rebuildAround(world, physicsStore, spaceId, playerPos, @@ -131,7 +131,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } Store physicsStore = physicsStore(world); - WorldCollisionPrewarmStats stats = PhysicsWorldCollision.ensureAround(world, + PhysicsChunkTerrainPrewarmStats stats = PhysicsChunkTerrain.ensureAround(world, physicsStore, spaceId, List.of(playerPos), @@ -172,7 +172,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Store physicsStore = physicsStore(world); - int removed = PhysicsWorldCollision.clearSpace(world, physicsStore, spaceId); + int removed = PhysicsChunkTerrain.clearSpace(world, physicsStore, spaceId); ctx.sender().sendMessage(Message.raw("Removed " + removed + " world voxel collision bodies.")); return CompletableFuture.completedFuture(null); @@ -192,7 +192,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - WorldCollisionStats stats = PhysicsWorldCollision.stats(world); + PhysicsChunkTerrainStats stats = PhysicsChunkTerrain.stats(world); ctx.sender().sendMessage(Message.raw("World voxel collision: " + stats.spaces() + " spaces, " + stats.sections() + " sections, " diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index c604f018..9eb11b72 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -19,8 +19,8 @@ import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; @@ -343,7 +343,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - WorldCollisionPrewarmStats stats = PhysicsWorldCollision.ensureAround(world, + PhysicsChunkTerrainPrewarmStats stats = PhysicsChunkTerrain.ensureAround(world, physicsStore, spaceId, List.of(spawn), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 0d018ce1..ac106792 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -16,9 +16,9 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; @@ -27,7 +27,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -54,7 +54,7 @@ public class StressBodiesCommand extends AbstractAsyncPlayerCommand { DETACHED_VISUAL_DEMATERIALIZATION_RADIUS - DETACHED_VISUAL_MATERIALIZATION_RADIUS; private static final int DETACHED_VISUAL_MAX_MATERIALIZED = 10_000; private static final int DETACHED_VISUAL_MAX_SPAWNS_PER_TICK = 128; - private static final int STRESS_BODY_WORLD_COLLISION_RADIUS = 8; + private static final int STRESS_BODY_TERRAIN_RADIUS = 8; private static final PhysicsBackendExtensionId RAPIER_SOLVER_EXTENSION_ID = new PhysicsBackendExtensionId("impulse:rapier_solver"); private static final String RAPIER_INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; @@ -172,7 +172,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, StressLayout layout = StressLayout.forCount(count, playerPos); long prewarmStartNanos = System.nanoTime(); - int prewarmedSections = prewarmStressWorldCollision(world, + int prewarmedSections = prewarmStressTerrain(world, spaceId, settings, mode, @@ -229,8 +229,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, batchTiming.entityApplyNanos(), 0L); } - PhysicsWorldCollisionSettings worldCollisionSettings = - settings.getWorldCollisionSettings(); + PhysicsChunkTerrainSettings terrainSettings = + settings.getPhysicsChunkTerrainSettings(); PhysicsVisualMaterializationSettings visualMaterializationSettings = settings.getVisualMaterializationSettings(); PhysicsVisualSyncSettings visualSyncSettings = settings.getVisualSyncSettings(); @@ -248,7 +248,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + ": mode=" + mode.serialized() + " space=" + spaceId.value() + " physicsChunk=streaming" - + " bodyCollisionRadius=" + worldCollisionSettings.getWorldCollisionBodyRadius() + + " bodyCollisionRadius=" + terrainSettings.getBodyTerrainRadius() + " prewarmedSections=" + prewarmedSections + " step=" + worldSettings.getStepMode().getSerializedName() + "/" + worldSettings.getSimulationSteps() @@ -301,12 +301,12 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store e List groups = groupFragments(fragments, center, settings.getRadius()); Store physicsStore = PhysicsThreading.store(world); - PhysicsWorldCollision.refreshAround(world, + PhysicsChunkTerrain.refreshAround(world, physicsStore, spaceId, center, Math.max(8, settings.getRadius() + 4)); - PhysicsWorldCollision.ensureAround(world, + PhysicsChunkTerrain.ensureAround(world, physicsStore, spaceId, groupCenters(groups), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index 537a6bed..75a491d1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -77,7 +77,7 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer ref : PhysicsEntityAttachments.attachments(store, explosiveBodyUuid)) { @@ -97,11 +97,11 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer physicsStore, + private static boolean isTerrainCollider(@Nonnull Store physicsStore, @Nonnull UUID bodyUuid) { PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView(physicsStore, bodyUuid); - return registration != null && registration.kind() == PhysicsBodyKind.WORLD_COLLISION; + return registration != null && registration.kind().isTerrainCollider(); } @Nonnull From 1ad6a27ffd9b6b35f3b040d389c0a32b8fe6336f Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 20:23:13 +0200 Subject: [PATCH 356/534] refactor(core): make physics chunk terrain facade authoritative Signed-off-by: Blovien --- .../commands/debug/DebugToggleCommand.java | 3 +- .../crucible/BenchmarkSpaceStatsView.java | 2 +- ...tachedStreamingBenchmarkCrucibleTests.java | 96 ++--- ...pulseRapierBodyBenchmarkCrucibleTests.java | 128 +++---- .../PhysicsStoreBenchmarkQueries.java | 12 +- .../PhysicsChunkTerrainRuntime.java | 26 +- .../PhysicsChunkTerrainStreamingResource.java | 20 +- .../PhysicsChunkPerfReportCommand.java | 14 +- .../PhysicsChunkPerfResetCommand.java | 4 +- .../PhysicsChunkPerfToggleCommand.java | 6 +- .../PhysicsChunkTerrainProducerSystem.java | 4 +- .../PhysicsWorldRuntimeResource.java | 50 +-- .../systems/debug/PhysicsDebugRenderer.java | 10 +- .../systems/debug/PhysicsDebugSystem.java | 22 +- .../physicschunk/PhysicsChunkTerrain.java | 187 +++++++-- .../PhysicsChunkTerrainProfiling.java | 354 ++++++++++++++++++ .../physicschunk/PhysicsWorldCollision.java | 175 +-------- .../PhysicsWorldCollisionProfiling.java | 315 ++-------------- .../plugin/physicsstore/PhysicsEntities.java | 12 +- .../plugin/physicsstore/PhysicsSpaces.java | 6 +- .../PhysicsVisualMaterializationSettings.java | 9 +- .../PhysicsChunkNamingSourceGuardTest.java | 55 +++ 22 files changed, 833 insertions(+), 677 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java index ee99d279..3a76670b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java @@ -31,7 +31,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, boolean enabled; if (debug.removeSubscriber(playerRef.getUuid())) { enabled = false; - // FIXME: maybe don't clear up all shapes bust just ours + // The Hytale packet clears all debug shapes visible to the player; Impulse-owned + // shape tracking can narrow this once the server API exposes scoped removal. playerRef.getPacketHandler().write(new ClearDebugShapes()); } else { enabled = true; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java index 4ec2aff7..8b3076e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java @@ -9,7 +9,7 @@ public record BenchmarkSpaceStatsView(int bodies, int sleepingDynamicBodies, int detachedBodies, int rawBodies, - int worldCollisionBodies, + int terrainBodies, int belowPlaneBodies, int belowTerrainBodies, int belowWorldMinBodies, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index a5ab2c3f..718d1deb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -28,7 +28,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; @@ -143,8 +143,8 @@ private static final class StageRunner { private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final PhysicsChunkProfilingResource worldCollisionProfiling; - private final PhysicsChunkTerrainStreamingResource worldCollisionStreaming; + private final PhysicsChunkProfilingResource terrainProfiling; + private final PhysicsChunkTerrainStreamingResource terrainStreaming; private final PhysicsWorldSettings previousWorldSettings; private final boolean previousPhysicsStoreProfilingEnabled; private final List retainedChunks = new ArrayList<>(); @@ -160,9 +160,9 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) this.physicsStoreProfiling = physicsStore.getResource( PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); - this.worldCollisionProfiling = store.getResource( + this.terrainProfiling = store.getResource( PhysicsChunkProfilingResource.getResourceType()); - this.worldCollisionStreaming = store.getResource( + this.terrainStreaming = store.getResource( PhysicsChunkTerrainStreamingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); @@ -193,10 +193,10 @@ private CompletionStage runStage(int stageIndex, .thenCompose(started -> contextWait(plan.warmupTicks()).thenCompose(_ -> { physicsStoreProfiling.reset(); runtimeProfiling.reset(); - worldCollisionProfiling.reset(); + terrainProfiling.reset(); physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); - worldCollisionProfiling.setEnabled(true); + terrainProfiling.setEnabled(true); long startedNanos = System.nanoTime(); return contextWait(plan.sampleTicks()).thenApply( _ -> finishStage(count, started, startedNanos)); @@ -259,14 +259,14 @@ private CompletionStage startStageWhenReady(int count, int attempt SpaceId spaceId = physics.createSpace(CrucibleBackends.requireBackendId(), world.getName(), settings); - PrewarmStats prewarm = prewarmWorldCollision(spaceId, count); + PrewarmStats prewarm = prewarmPhysicsChunkTerrain(spaceId, count); spawnDetachedBodies(spaceId, count); physicsStoreProfiling.reset(); runtimeProfiling.reset(); - worldCollisionProfiling.reset(); + terrainProfiling.reset(); physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); - worldCollisionProfiling.setEnabled(true); + terrainProfiling.setEnabled(true); return CompletableFuture.completedFuture( StartedStage.started(spaceId, chunks, retained, prewarm)); } @@ -284,28 +284,28 @@ private StageReport finishStage(int count, StepSnapshot step = runtimeProfiling.getCumulativeStep(); SyncSnapshot sync = runtimeProfiling.getCumulativeSync(); - Snapshot worldCollision = worldCollisionProfiling.getCumulativeSnapshot(); + Snapshot terrainProfilingSnapshot = terrainProfiling.getCumulativeSnapshot(); double elapsedSeconds = Math.max(0.001, (System.nanoTime() - startedNanos) / 1_000_000_000.0); double observedTickRate = step.getTickSamples() / elapsedSeconds; - SpaceStats stats = SpaceStats.collect(physicsStore, worldCollisionStreaming, spaceId); + SpaceStats stats = SpaceStats.collect(physicsStore, terrainStreaming, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); double avgRegistrationPublicationMs = averageMillis( step.getRegistrationPublicationNanos(), step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); - double avgWorldMs = averageMillis(worldCollision.getTickNanos(), - worldCollision.getTickSamples()); + double avgTerrainMs = averageMillis(terrainProfilingSnapshot.getTickNanos(), + terrainProfilingSnapshot.getTickSamples()); double totalMs = avgStepMs + avgSnapshotMs + avgRegistrationPublicationMs + avgSyncMs - + avgWorldMs; + + avgTerrainMs; StageHealth health = assessHealth(count, observedTickRate, stats, - worldCollision.getMissingChunks()); + terrainProfilingSnapshot.getMissingChunks()); assert started.chunks() != null; assert started.prewarm() != null; @@ -315,7 +315,7 @@ private StageReport finishStage(int count, avgSnapshotMs, avgRegistrationPublicationMs, avgSyncMs, - avgWorldMs, + avgTerrainMs, totalMs, started.retainedColumns(), started.chunks().size(), @@ -323,7 +323,7 @@ private StageReport finishStage(int count, started.prewarm().sectionsBuilt(), stats.bodies, stats.dynamicBodies, - stats.worldCollisionBodies, + stats.terrainBodies, stats.belowPlaneBodies, stats.belowTerrainBodies, stats.belowWorldMinBodies, @@ -331,17 +331,17 @@ private StageReport finishStage(int count, stats.terrainBaselineBodies, stats.missingTerrainBaselineBodies, stats.minTerrainBottomClearance(), - worldCollision.getTickSamples(), - worldCollision.getEnsureCalls(), - worldCollision.getSectionRequests(), - worldCollision.getSectionCacheHits(), - worldCollision.getSectionsBuilt(), - worldCollision.getMissingChunks(), - worldCollision.getMissingBlockChunks(), - worldCollision.getMissingBlockSections(), - worldCollision.getUniqueMissingSections(), - worldCollision.getMissingOutsideRetainedEnvelope(), - worldCollision.getBodyStreamingTargets(), + terrainProfilingSnapshot.getTickSamples(), + terrainProfilingSnapshot.getEnsureCalls(), + terrainProfilingSnapshot.getSectionRequests(), + terrainProfilingSnapshot.getSectionCacheHits(), + terrainProfilingSnapshot.getSectionsBuilt(), + terrainProfilingSnapshot.getMissingChunks(), + terrainProfilingSnapshot.getMissingBlockChunks(), + terrainProfilingSnapshot.getMissingBlockSections(), + terrainProfilingSnapshot.getUniqueMissingSections(), + terrainProfilingSnapshot.getMissingOutsideRetainedEnvelope(), + terrainProfilingSnapshot.getBodyStreamingTargets(), health); } @@ -358,8 +358,8 @@ private void clearStageState() { PhysicsStoreCrucibleSupport.clearAll(physicsStore); physicsStoreProfiling.reset(); runtimeProfiling.reset(); - worldCollisionProfiling.reset(); - worldCollisionProfiling.clearDiagnosticRetainedSections(); + terrainProfiling.reset(); + terrainProfiling.clearDiagnosticRetainedSections(); } private void restoreStepSettings() { @@ -367,14 +367,14 @@ private void restoreStepSettings() { physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); } - private PrewarmStats prewarmWorldCollision(@Nonnull SpaceId spaceId, int count) { + private PrewarmStats prewarmPhysicsChunkTerrain(@Nonnull SpaceId spaceId, int count) { BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(physicsStore, spaceId); PhysicsTerrainMutationQueueResource queue = physicsStore.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( physics.getSpaceSettings(spaceId).getPhysicsChunkTerrainSettings()); - WorldCollisionPrewarmStats stats = worldCollisionStreaming.ensureAround(world, + PhysicsChunkTerrainPrewarmStats stats = terrainStreaming.ensureAround(world, spaceUuid, queue, prewarmCenters(layout, count), @@ -439,7 +439,7 @@ private void configureMissingSectionDiagnostics(@Nonnull BenchmarkChunks chunks) section.y(), section.z())); } - worldCollisionProfiling.setDiagnosticRetainedSections(sectionKeys); + terrainProfiling.setDiagnosticRetainedSections(sectionKeys); } private void spawnDetachedBodies(@Nonnull SpaceId spaceId, int count) { @@ -629,8 +629,8 @@ private static StageHealth assessHealth(int count, if (observedTickRate < configuredDouble(MIN_TPS_PROPERTY, 8.0)) { stops.add("observedTPS=" + format(observedTickRate) + "<8"); } - if (stats.worldCollisionBodies == 0) { - stops.add("worldCollisionBodies=0"); + if (stats.terrainBodies == 0) { + stops.add("terrainBodies=0"); } if (stats.belowWorldMinBodies > 0) { stops.add("belowWorldMinBodies=" + stats.belowWorldMinBodies); @@ -779,7 +779,7 @@ private record StageReport(int count, double avgSnapshotMs, double avgRegistrationPublicationMs, double avgSyncMs, - double avgWorldMs, + double avgTerrainMs, double totalMs, int retainedColumns, int chunkRefs, @@ -787,7 +787,7 @@ private record StageReport(int count, int prewarmSectionsBuilt, int bodies, int dynamicBodies, - int worldCollisionBodies, + int terrainBodies, int belowPlaneBodies, int belowTerrainBodies, int belowWorldMinBodies, @@ -795,7 +795,7 @@ private record StageReport(int count, int terrainBaselineBodies, int missingTerrainBaselineBodies, double minTerrainBottomClearance, - int worldCollisionSamples, + int terrainSamples, int ensureCalls, int sectionRequests, int sectionCacheHits, @@ -852,13 +852,13 @@ private String summary() { + " reason=" + health.reason() + " tps=" + format(observedTickRate) + " totalMs=" + format(totalMs) - + " step/snapshot/registration/sync/worldMs=" + format(avgStepMs) + + " step/snapshot/registration/sync/terrainMs=" + format(avgStepMs) + "/" + format(avgSnapshotMs) + "/" + format(avgRegistrationPublicationMs) + "/" + format(avgSyncMs) - + "/" + format(avgWorldMs) + + "/" + format(avgTerrainMs) + " bodies dynamic/physicsChunk=" + dynamicBodies - + "/" + worldCollisionBodies + + "/" + terrainBodies + " belowPlane/terrain/worldMin/void=" + belowPlaneBodies + "/" + belowTerrainBodies + "/" + belowWorldMinBodies @@ -872,8 +872,8 @@ private String summary() { + "/" + prewarmSectionsBuilt + " streamingFallMinY=" + format(STREAMING_FALL_ENVELOPE_MIN_Y) + " streamingHorizontalHalo=" + format(STREAMING_HORIZONTAL_DRIFT_HALO_BLOCKS) - + " world samples/ensure/req/hit/build/miss/bodyTargets=" - + worldCollisionSamples + + " terrain samples/ensure/req/hit/build/miss/bodyTargets=" + + terrainSamples + "/" + ensureCalls + "/" + sectionRequests + "/" + sectionCacheHits @@ -941,7 +941,7 @@ private static final class SpaceStats { private int bodies; private int dynamicBodies; - private int worldCollisionBodies; + private int terrainBodies; private int belowPlaneBodies; private int belowTerrainBodies; private int belowWorldMinBodies; @@ -951,11 +951,11 @@ private static final class SpaceStats { private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; private static SpaceStats collect(@Nonnull Store physicsStore, - @Nonnull PhysicsChunkTerrainStreamingResource worldCollisionStreaming, + @Nonnull PhysicsChunkTerrainStreamingResource terrainStreaming, @Nonnull SpaceId spaceId) { BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( physicsStore, - worldCollisionStreaming, + terrainStreaming, new PhysicsStoreBenchmarkQueries.BenchmarkSpaceStatsRequest(spaceId, GROUND_Y, BELOW_PLANE_TOLERANCE, @@ -965,7 +965,7 @@ private static SpaceStats collect(@Nonnull Store physicsStore, SpaceStats stats = new SpaceStats(); stats.bodies = view.bodies(); stats.dynamicBodies = view.dynamicBodies(); - stats.worldCollisionBodies = view.worldCollisionBodies(); + stats.terrainBodies = view.terrainBodies(); stats.belowPlaneBodies = view.belowPlaneBodies(); stats.belowTerrainBodies = view.belowTerrainBodies(); stats.belowWorldMinBodies = view.belowWorldMinBodies(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 92cc90c8..730393c0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -142,11 +142,11 @@ private static final class MatrixRunner { private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final PhysicsChunkProfilingResource worldCollisionProfiling; + private final PhysicsChunkProfilingResource terrainProfiling; private final PhysicsWorldSettings previousWorldSettings; private final boolean previousPhysicsStoreProfilingEnabled; private final boolean previousRuntimeProfilingEnabled; - private final boolean previousWorldCollisionProfilingEnabled; + private final boolean previousTerrainProfilingEnabled; private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) throws ReflectiveOperationException { @@ -159,12 +159,12 @@ private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) this.physicsStoreProfiling = physicsStore.getResource( PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); - this.worldCollisionProfiling = store.getResource( + this.terrainProfiling = store.getResource( PhysicsChunkProfilingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); this.previousRuntimeProfilingEnabled = runtimeProfiling.isEnabled(); - this.previousWorldCollisionProfilingEnabled = worldCollisionProfiling.isEnabled(); + this.previousTerrainProfilingEnabled = terrainProfiling.isEnabled(); } private CompletionStage run() { @@ -193,10 +193,10 @@ private CompletionStage runCase(int index, .thenCompose(started -> contextWait(plan.warmupTicks()).thenCompose(_ -> { physicsStoreProfiling.reset(); runtimeProfiling.reset(); - worldCollisionProfiling.reset(); + terrainProfiling.reset(); physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); - worldCollisionProfiling.setEnabled(true); + terrainProfiling.setEnabled(true); long startedNanos = System.nanoTime(); return contextWait(plan.sampleTicks()).thenApply( _ -> finishCase(matrixCase, started, startedNanos)); @@ -300,7 +300,7 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, StepSnapshot step = runtimeProfiling.getCumulativeStep(); SyncSnapshot sync = runtimeProfiling.getCumulativeSync(); - Snapshot worldCollision = worldCollisionProfiling.getCumulativeSnapshot(); + Snapshot terrainProfilingSnapshot = terrainProfiling.getCumulativeSnapshot(); double elapsedSeconds = Math.max(0.001, (System.nanoTime() - startedNanos) / 1_000_000_000.0); double observedTickRate = step.getTickSamples() / elapsedSeconds; @@ -311,17 +311,17 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, step.getRegistrationPublicationNanos(), step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); - double avgWorldMs = averageMillis(worldCollision.getTickNanos(), - worldCollision.getTickSamples()); + double avgTerrainMs = averageMillis(terrainProfilingSnapshot.getTickNanos(), + terrainProfilingSnapshot.getTickSamples()); double totalMs = avgStepMs + avgSnapshotMs + avgRegistrationPublicationMs + avgSyncMs - + avgWorldMs; + + avgTerrainMs; MatrixHealth health = assessHealth(matrixCase, observedTickRate, step, - worldCollision, + terrainProfilingSnapshot, stats); return new MatrixReport(matrixCase, @@ -330,7 +330,7 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, avgSnapshotMs, avgRegistrationPublicationMs, avgSyncMs, - avgWorldMs, + avgTerrainMs, totalMs, step.getTickSamples(), step.getSubsteps(), @@ -339,17 +339,17 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, sync.getTickSamples(), sync.getBodiesInspected(), sync.getBodiesSynced(), - worldCollision.getTickSamples(), - worldCollision.getStreamingSpaces(), - worldCollision.getEnsureCalls(), - worldCollision.getSectionRequests(), - worldCollision.getSectionsBuilt(), - worldCollision.getBodyStreamingTargets(), + terrainProfilingSnapshot.getTickSamples(), + terrainProfilingSnapshot.getStreamingSpaces(), + terrainProfilingSnapshot.getEnsureCalls(), + terrainProfilingSnapshot.getSectionRequests(), + terrainProfilingSnapshot.getSectionsBuilt(), + terrainProfilingSnapshot.getBodyStreamingTargets(), stats.bodies, stats.dynamicBodies, stats.detachedBodies, stats.rawBodies, - stats.worldCollisionBodies, + stats.terrainBodies, stats.awakeDynamicBodies, stats.sleepingDynamicBodies, stats.belowPlaneBodies, @@ -374,15 +374,15 @@ private void clearCaseState() { PhysicsStoreCrucibleSupport.clearAll(physicsStore); physicsStoreProfiling.reset(); runtimeProfiling.reset(); - worldCollisionProfiling.reset(); - worldCollisionProfiling.clearDiagnosticRetainedSections(); + terrainProfiling.reset(); + terrainProfiling.clearDiagnosticRetainedSections(); } private void restoreSettings() { physics.setWorldSettings(previousWorldSettings); physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); runtimeProfiling.setEnabled(previousRuntimeProfilingEnabled); - worldCollisionProfiling.setEnabled(previousWorldCollisionProfilingEnabled); + terrainProfiling.setEnabled(previousTerrainProfilingEnabled); } private void removeBenchmarkEntities() { @@ -416,15 +416,15 @@ private static CrucibleTestCase.TestOutcome outcome(@Nonnull List private static MatrixHealth assessHealth(@Nonnull MatrixCase matrixCase, double observedTickRate, @Nonnull StepSnapshot step, - @Nonnull Snapshot worldCollision, + @Nonnull Snapshot terrainProfilingSnapshot, @Nonnull SpaceStats stats) { List stops = new ArrayList<>(); List warnings = new ArrayList<>(); if (step.getTickSamples() <= 0) { stops.add("stepSamples=0"); } - if (worldCollision.getTickSamples() <= 0) { - stops.add("worldCollisionSamples=0"); + if (terrainProfilingSnapshot.getTickSamples() <= 0) { + stops.add("terrainSamples=0"); } if (stats.dynamicBodies != matrixCase.count()) { stops.add("dynamicBodies=" + stats.dynamicBodies + "!=" + matrixCase.count()); @@ -444,20 +444,20 @@ private static MatrixHealth assessHealth(@Nonnull MatrixCase matrixCase, if (step.getTickSamples() > 0 && step.getBodySnapshots() != expectedSnapshots) { stops.add("bodySnapshots=" + step.getBodySnapshots() + "!=" + expectedSnapshots); } - if (worldCollision.getStreamingSpaces() > 0) { - stops.add("worldStreamingSpaces=" + worldCollision.getStreamingSpaces()); + if (terrainProfilingSnapshot.getStreamingSpaces() > 0) { + stops.add("terrainStreamingSpaces=" + terrainProfilingSnapshot.getStreamingSpaces()); } - if (worldCollision.getEnsureCalls() > 0) { - stops.add("worldEnsureCalls=" + worldCollision.getEnsureCalls()); + if (terrainProfilingSnapshot.getEnsureCalls() > 0) { + stops.add("terrainEnsureCalls=" + terrainProfilingSnapshot.getEnsureCalls()); } - if (worldCollision.getSectionsBuilt() > 0) { - stops.add("worldSectionsBuilt=" + worldCollision.getSectionsBuilt()); + if (terrainProfilingSnapshot.getSectionsBuilt() > 0) { + stops.add("terrainSectionsBuilt=" + terrainProfilingSnapshot.getSectionsBuilt()); } - if (worldCollision.getBodyStreamingTargets() > 0) { - stops.add("worldBodyTargets=" + worldCollision.getBodyStreamingTargets()); + if (terrainProfilingSnapshot.getBodyStreamingTargets() > 0) { + stops.add("terrainBodyTargets=" + terrainProfilingSnapshot.getBodyStreamingTargets()); } - if (stats.worldCollisionBodies > 0) { - stops.add("worldCollisionBodies=" + stats.worldCollisionBodies); + if (stats.terrainBodies > 0) { + stops.add("terrainBodies=" + stats.terrainBodies); } if (stats.belowWorldMinBodies > 0) { stops.add("belowWorldMinBodies=" + stats.belowWorldMinBodies); @@ -498,7 +498,7 @@ private static void logComparison(@Nonnull List reports) { MatrixReport second = reports.get(1); LOGGER.at(Level.INFO).log("Crucible Rapier body matrix comparison: %sx=%sms " + "%sx=%sms stepRatio=%s snapshotRatio=%s registrationRatio=%s " - + "totalRatio=%s worldCounters=%s/%s", + + "totalRatio=%s terrainCounters=%s/%s", first.matrixCase().fixedSubsteps(), format(first.avgStepMs()), second.matrixCase().fixedSubsteps(), @@ -508,8 +508,8 @@ private static void logComparison(@Nonnull List reports) { format(ratio(second.avgRegistrationPublicationMs(), first.avgRegistrationPublicationMs())), format(ratio(second.totalMs(), first.totalMs())), - first.worldCounterSummary(), - second.worldCounterSummary()); + first.terrainCounterSummary(), + second.terrainCounterSummary()); } private static double ratio(double numerator, double denominator) { @@ -619,7 +619,7 @@ private record MatrixReport(@Nonnull MatrixCase matrixCase, double avgSnapshotMs, double avgRegistrationPublicationMs, double avgSyncMs, - double avgWorldMs, + double avgTerrainMs, double totalMs, int stepSamples, int substeps, @@ -628,17 +628,17 @@ private record MatrixReport(@Nonnull MatrixCase matrixCase, int syncSamples, int syncInspected, int syncSynced, - int worldSamples, - int worldStreamingSpaces, - int worldEnsureCalls, - int worldSectionRequests, - int worldSectionsBuilt, - int worldBodyTargets, + int terrainSamples, + int terrainStreamingSpaces, + int terrainEnsureCalls, + int terrainSectionRequests, + int terrainSectionsBuilt, + int terrainBodyTargets, int bodies, int dynamicBodies, int detachedBodies, int rawBodies, - int worldCollisionBodies, + int terrainBodies, int awakeDynamicBodies, int sleepingDynamicBodies, int belowPlaneBodies, @@ -694,11 +694,11 @@ private String summary() { + " reason=" + health.reason() + " tps=" + format(observedTickRate) + " totalMs=" + format(totalMs) - + " step/snapshot/registration/sync/worldMs=" + format(avgStepMs) + + " step/snapshot/registration/sync/terrainMs=" + format(avgStepMs) + "/" + format(avgSnapshotMs) + "/" + format(avgRegistrationPublicationMs) + "/" + format(avgSyncMs) - + "/" + format(avgWorldMs) + + "/" + format(avgTerrainMs) + " step samples/substeps/bodySnapshots/spatialCells=" + stepSamples + "/" + substeps + "/" + bodySnapshots @@ -706,18 +706,18 @@ private String summary() { + " sync samples/inspected/synced=" + syncSamples + "/" + syncInspected + "/" + syncSynced - + " world samples/streaming/ensure/req/build/bodyTargets=" - + worldSamples - + "/" + worldStreamingSpaces - + "/" + worldEnsureCalls - + "/" + worldSectionRequests - + "/" + worldSectionsBuilt - + "/" + worldBodyTargets + + " terrain samples/streaming/ensure/req/build/bodyTargets=" + + terrainSamples + + "/" + terrainStreamingSpaces + + "/" + terrainEnsureCalls + + "/" + terrainSectionRequests + + "/" + terrainSectionsBuilt + + "/" + terrainBodyTargets + " bodies total/dynamic/detached/raw/physicsChunk=" + bodies + "/" + dynamicBodies + "/" + detachedBodies + "/" + rawBodies - + "/" + worldCollisionBodies + + "/" + terrainBodies + " awake/sleeping=" + awakeDynamicBodies + "/" + sleepingDynamicBodies + " belowPlane/worldMin/void=" + belowPlaneBodies @@ -727,12 +727,12 @@ private String summary() { + "/" + formatOptional(maxDynamicBodyY); } - private String worldCounterSummary() { - return worldSamples - + "/" + worldStreamingSpaces - + "/" + worldEnsureCalls - + "/" + worldSectionsBuilt - + "/" + worldBodyTargets; + private String terrainCounterSummary() { + return terrainSamples + + "/" + terrainStreamingSpaces + + "/" + terrainEnsureCalls + + "/" + terrainSectionsBuilt + + "/" + terrainBodyTargets; } } @@ -777,7 +777,7 @@ private static final class SpaceStats { private int sleepingDynamicBodies; private int detachedBodies; private int rawBodies; - private int worldCollisionBodies; + private int terrainBodies; private int belowPlaneBodies; private int belowWorldMinBodies; private int belowVoidBodies; @@ -802,7 +802,7 @@ private static SpaceStats collect(@Nonnull Store physicsStore, stats.sleepingDynamicBodies = view.sleepingDynamicBodies(); stats.detachedBodies = view.detachedBodies(); stats.rawBodies = view.rawBodies(); - stats.worldCollisionBodies = view.worldCollisionBodies(); + stats.terrainBodies = view.terrainBodies(); stats.belowPlaneBodies = view.belowPlaneBodies(); stats.belowWorldMinBodies = view.belowWorldMinBodies(); stats.belowVoidBodies = view.belowVoidBodies(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 9df51459..88807d64 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -41,9 +41,9 @@ static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store BiConsumer, CommandBuffer> collector = (chunk, _) -> collectBodyRows(chunk, snapshots, spaceUuid, query, stats); store.forEachChunk(BodyComponent.getComponentType(), collector); - int worldCollisionBodies = streaming != null ? streaming.bodyCount(spaceUuid) : 0; - stats.bodies += worldCollisionBodies; - stats.worldCollisionBodies += worldCollisionBodies; + int terrainBodies = streaming != null ? streaming.bodyCount(spaceUuid) : 0; + stats.bodies += terrainBodies; + stats.terrainBodies += terrainBodies; return stats.toView(); } @@ -108,7 +108,7 @@ private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, return; } if (body.getKind().isTerrainCollider()) { - stats.worldCollisionBodies++; + stats.terrainBodies++; return; } stats.rawBodies++; @@ -122,7 +122,7 @@ private static final class BenchmarkSpaceStatsAccumulator { private int sleepingDynamicBodies; private int detachedBodies; private int rawBodies; - private int worldCollisionBodies; + private int terrainBodies; private int belowPlaneBodies; private int belowTerrainBodies; private int belowWorldMinBodies; @@ -141,7 +141,7 @@ private BenchmarkSpaceStatsView toView() { sleepingDynamicBodies, detachedBodies, rawBodies, - worldCollisionBodies, + terrainBodies, belowPlaneBodies, belowTerrainBodies, belowWorldMinBodies, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java index f5dfe54f..058286c1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java @@ -3,9 +3,9 @@ import com.hypixel.hytale.server.core.universe.world.World; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import it.unimi.dsi.fastutil.ints.Int2LongMap; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; @@ -48,7 +48,7 @@ public synchronized long incrementStreamingRevision(@Nonnull SpaceId spaceId) { } @Nonnull - public WorldCollisionBuildStats rebuildAround(@Nonnull World world, + public PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius) { @@ -61,7 +61,7 @@ public WorldCollisionBuildStats rebuildAround(@Nonnull World world, } @Nonnull - public WorldCollisionBuildStats rebuildAround(@Nonnull World world, + public PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -74,7 +74,7 @@ public WorldCollisionBuildStats rebuildAround(@Nonnull World world, } @Nonnull - public WorldCollisionBuildStats refreshAround(@Nonnull World world, + public PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -91,7 +91,7 @@ public WorldCollisionBuildStats refreshAround(@Nonnull World world, } @Nonnull - public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, + public PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Iterable centers, int radius, @@ -106,7 +106,7 @@ public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, } @Nonnull - public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, + public PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Iterable centers, int radius, @@ -127,7 +127,7 @@ public WorldCollisionPrewarmStats ensureAround(@Nonnull World world, null, buildOptions)); } - return new WorldCollisionPrewarmStats(visitedSections.size(), + return new PhysicsChunkTerrainPrewarmStats(visitedSections.size(), terrainStats(total)); } @@ -161,17 +161,17 @@ public synchronized void clearAllAndUnregisterSpaces() { } @Nonnull - public WorldCollisionStats getStats() { - return new WorldCollisionStats(voxelTerrainCache.spaceCount(), + public PhysicsChunkTerrainStats getStats() { + return new PhysicsChunkTerrainStats(voxelTerrainCache.spaceCount(), voxelTerrainCache.sectionCount(), voxelTerrainCache.bodyCount(), voxelTerrainCache.shapeTemplateCount()); } @Nonnull - private static WorldCollisionBuildStats terrainStats( + private static PhysicsChunkTerrainBuildStats terrainStats( @Nonnull VoxelTerrainCollisionCache.BuildStats stats) { - return new WorldCollisionBuildStats(stats.scannedBlocks(), + return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), stats.fullCubeRuns(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java index dc7a9906..d9ab8098 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java @@ -11,9 +11,9 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.Objects; @@ -65,7 +65,7 @@ public synchronized void retainSpaces(@Nonnull Set retainedSpaces, } @Nonnull - public synchronized WorldCollisionPrewarmStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull Iterable centers, @@ -87,11 +87,11 @@ public synchronized WorldCollisionPrewarmStats ensureAround(@Nonnull World world null, buildOptions)); } - return new WorldCollisionPrewarmStats(visitedSections.size(), terrainStats(total)); + return new PhysicsChunkTerrainPrewarmStats(visitedSections.size(), terrainStats(total)); } @Nonnull - public synchronized WorldCollisionBuildStats refreshAround(@Nonnull World world, + public synchronized PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsTerrainMutationQueueResource queue, @Nonnull Vector3d center, @@ -214,8 +214,8 @@ public synchronized int clearSpace(@Nonnull UUID spaceUuid, } @Nonnull - public synchronized WorldCollisionStats stats() { - return new WorldCollisionStats(cache.spaceCount(), + public synchronized PhysicsChunkTerrainStats stats() { + return new PhysicsChunkTerrainStats(cache.spaceCount(), cache.sectionCount(), cache.bodyCount(), cache.shapeTemplateCount()); @@ -235,8 +235,8 @@ public synchronized PhysicsChunkTerrainStreamingResource clone() { } @Nonnull - private static WorldCollisionBuildStats terrainStats(@Nonnull BuildStats stats) { - return new WorldCollisionBuildStats(stats.scannedBlocks(), + private static PhysicsChunkTerrainBuildStats terrainStats(@Nonnull BuildStats stats) { + return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), stats.fullCubeRuns(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java index 52dbfbc9..e34335d0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.StepSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.SyncSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.VisualSnapshotView; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainProfiling; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; @@ -56,8 +56,8 @@ private static void sendReport(@Nonnull CommandContext ctx, VisualSnapshotView cumulativeVisual = runtimeProfiling.cumulativeVisual(); VisualSnapshotView latestVisual = runtimeProfiling.latestVisual(); VisualSnapshotView worstVisual = runtimeProfiling.worstVisual(); - PhysicsWorldCollisionProfiling.Snapshots profiling = - PhysicsWorldCollisionProfiling.snapshots(store); + PhysicsChunkTerrainProfiling.Snapshots profiling = + PhysicsChunkTerrainProfiling.snapshots(store); var cumulative = profiling.cumulative(); var latest = profiling.latest(); var worst = profiling.worst(); @@ -296,8 +296,8 @@ private static void sendReport(@Nonnull CommandContext ctx, + cumulative.getMissingInsideRetainedEnvelope() + "/" + cumulative.getMissingOutsideRetainedEnvelope() + "/" + cumulative.getMissingUnconfiguredRetainedEnvelope())); - List missingSectionSamples = - PhysicsWorldCollisionProfiling.missingSectionSamples(cumulative); + List missingSectionSamples = + PhysicsChunkTerrainProfiling.missingSectionSamples(cumulative); if (!missingSectionSamples.isEmpty()) { ctx.sender().sendMessage(Message.raw("Missing section samples: " + formatMissingSectionSamples(missingSectionSamples))); @@ -371,10 +371,10 @@ private static void sendReport(@Nonnull CommandContext ctx, @Nonnull private static String formatMissingSectionSamples( - @Nonnull List samples) { + @Nonnull List samples) { StringBuilder builder = new StringBuilder(); int emitted = 0; - for (PhysicsWorldCollisionProfiling.MissingSectionSampleView sample : samples) { + for (PhysicsChunkTerrainProfiling.MissingSectionSampleView sample : samples) { if (emitted > 0) { builder.append(" | "); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java index 5195095e..f98f9acf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainProfiling; import javax.annotation.Nonnull; public class PhysicsChunkPerfResetCommand extends AbstractWorldCommand { @@ -19,7 +19,7 @@ public PhysicsChunkPerfResetCommand() { protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { - PhysicsWorldCollisionProfiling.resetRuntimeProfiling(world, store); + PhysicsChunkTerrainProfiling.resetRuntimeProfiling(world, store); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling counters reset")); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java index 0a053758..3d5b500c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollisionProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainProfiling; import javax.annotation.Nonnull; public class PhysicsChunkPerfToggleCommand extends AbstractWorldCommand { @@ -19,8 +19,8 @@ public PhysicsChunkPerfToggleCommand() { protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { - boolean enabled = !PhysicsWorldCollisionProfiling.isRuntimeProfilingEnabled(store); - PhysicsWorldCollisionProfiling.setRuntimeProfilingEnabled(world, store, enabled); + boolean enabled = !PhysicsChunkTerrainProfiling.isRuntimeProfilingEnabled(store); + PhysicsChunkTerrainProfiling.setRuntimeProfilingEnabled(world, store, enabled); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling " + (enabled ? "enabled" : "disabled"))); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java index 36ab75c8..8df20074 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java @@ -81,14 +81,14 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { "produce PhysicsStore PhysicsChunk terrain mutations"); PhysicsTerrainMutationQueueResource queue = physics.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); - PhysicsChunkSettingsIndexResource worldCollisionIndex = physics.getResource( + PhysicsChunkSettingsIndexResource terrainSettingsIndex = physics.getResource( PhysicsChunkSettingsIndexResource.getResourceType()); PhysicsSnapshotResource snapshotResource = physics.getResource( PhysicsSnapshotResource.getResourceType()); PhysicsChunkTerrainStreamingResource streaming = store.getResource( PhysicsChunkTerrainStreamingResource.getResourceType()); - List spaces = worldCollisionIndex.streamingSpaces(); + List spaces = terrainSettingsIndex.streamingSpaces(); if (spaces.isEmpty()) { streaming.retainSpaces(Set.of(), queue); return; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index fdd883c8..12937651 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -259,7 +259,7 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( if (space == null) { return null; } - WorldCollisionComponent worldCollision = store.getComponent(ref, + WorldCollisionComponent terrainSettings = store.getComponent(ref, WorldCollisionComponent.getComponentType()); SolverSettingsComponent solverSettings = store.getComponent(ref, SolverSettingsComponent.getComponentType()); @@ -272,8 +272,8 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( ExtensionSettingsComponent extensionSettings = store.getComponent(ref, ExtensionSettingsComponent.getComponentType()); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - if (worldCollision != null) { - worldCollision.copyTo(settings); + if (terrainSettings != null) { + terrainSettings.copyTo(settings); } if (solverSettings != null) { solverSettings.copyTo(settings); @@ -1051,7 +1051,7 @@ public int getBodySnapshotCellCount() { } @Nonnull - private PhysicsChunkTerrainStreamingResource authoritativeWorldCollisionStreaming() { + private PhysicsChunkTerrainStreamingResource authoritativePhysicsChunkTerrainStreaming() { Store entityStore = owningStore; if (entityStore == null) { throw new IllegalStateException("Cannot access PhysicsStore PhysicsChunk terrain streaming " @@ -1060,23 +1060,23 @@ private PhysicsChunkTerrainStreamingResource authoritativeWorldCollisionStreamin return entityStore.getResource(PhysicsChunkTerrainStreamingResource.getResourceType()); } - private void clearAuthoritativeWorldCollisionStreaming(@Nonnull Store store) { + private void clearAuthoritativePhysicsChunkTerrainStreaming(@Nonnull Store store) { if (!PhysicsChunkLifecycle.isEnabled() || owningStore == null) { return; } PhysicsTerrainMutationQueueResource queue = store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); - authoritativeWorldCollisionStreaming().retainSpaces(Set.of(), queue); + authoritativePhysicsChunkTerrainStreaming().retainSpaces(Set.of(), queue); queue.clear(); } - private int clearAuthoritativeWorldCollisionSpace(@Nonnull Store store, + private int clearAuthoritativePhysicsChunkTerrainSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { int removed = 0; if (PhysicsChunkLifecycle.isEnabled() && owningStore != null) { PhysicsTerrainMutationQueueResource queue = store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()); - removed = authoritativeWorldCollisionStreaming().clearSpace(spaceUuid, queue); + removed = authoritativePhysicsChunkTerrainStreaming().clearSpace(spaceUuid, queue); } int directlyRemoved = PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); @@ -1204,7 +1204,7 @@ public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("remove physics space"); UUID spaceUuid = requireSpaceUuid(store, spaceId); - clearAuthoritativeWorldCollisionSpace(store, spaceUuid); + clearAuthoritativePhysicsChunkTerrainSpace(store, spaceUuid); PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); return; } @@ -1221,7 +1221,7 @@ public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, spaceId, store -> { UUID spaceUuid = requireSpaceUuid(store, spaceId); - clearAuthoritativeWorldCollisionSpace(store, spaceUuid); + clearAuthoritativePhysicsChunkTerrainSpace(store, spaceUuid); PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); }); } @@ -1261,7 +1261,7 @@ private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldNa public void clearAllSpaces(@Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("clear physics spaces"); - clearAuthoritativeWorldCollisionStreaming(store); + clearAuthoritativePhysicsChunkTerrainStreaming(store); PhysicsStoreRuntimeCleaner.clearAll(store); return; } @@ -1276,7 +1276,7 @@ public PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName return enqueueAuthoritativePhysicsStoreMutation("clear physics spaces", null, store -> { - clearAuthoritativeWorldCollisionStreaming(store); + clearAuthoritativePhysicsChunkTerrainStreaming(store); PhysicsStoreRuntimeCleaner.clearAll(store); }); } @@ -1317,7 +1317,7 @@ private static RuntimeException collectFailure(@Nullable RuntimeException failur public PhysicsRuntimeResetResult resetRuntimeStateKeepingSpaces(@Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("reset physics runtime state"); - clearAuthoritativeWorldCollisionStreaming(store); + clearAuthoritativePhysicsChunkTerrainStreaming(store); return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); } requireLegacyMutationAllowed("reset physics runtime state"); @@ -1334,7 +1334,7 @@ public CompletionStage resetRuntimeStateKeepingSpaces return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, "reset physics runtime state", store -> { - clearAuthoritativeWorldCollisionStreaming(store); + clearAuthoritativePhysicsChunkTerrainStreaming(store); return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); }); } @@ -1442,31 +1442,31 @@ public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spa private void setSpaceSettingsDirect(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { - PhysicsChunkTerrainSettings previousCollisionSettings = + PhysicsChunkTerrainSettings previousTerrainSettings = spaceRuntime.getLiveSpaceSettings(spaceId).getPhysicsChunkTerrainSettings(); - boolean worldCollisionSettingsChanged = - worldCollisionStreamingSettingsChanged(previousCollisionSettings, + boolean terrainStreamingSettingsChanged = + terrainStreamingSettingsChanged(previousTerrainSettings, settings.getPhysicsChunkTerrainSettings()); boolean terrainRepresentationChanged = - previousCollisionSettings.isNativeVoxelTerrainEnabled() + previousTerrainSettings.isNativeVoxelTerrainEnabled() != settings.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled(); boolean terrainMaterialChanged = - Float.compare(previousCollisionSettings.getTerrainFriction(), + Float.compare(previousTerrainSettings.getTerrainFriction(), settings.getPhysicsChunkTerrainSettings().getTerrainFriction()) != 0 - || Float.compare(previousCollisionSettings.getTerrainRestitution(), + || Float.compare(previousTerrainSettings.getTerrainRestitution(), settings.getPhysicsChunkTerrainSettings().getTerrainRestitution()) != 0; - boolean worldCollisionDisabled = + boolean terrainDisabled = settings.getPhysicsChunkTerrainSettings().getTerrainMode() == PhysicsChunkTerrainMode.NONE - && previousCollisionSettings.getTerrainMode() != PhysicsChunkTerrainMode.NONE; + && previousTerrainSettings.getTerrainMode() != PhysicsChunkTerrainMode.NONE; spaceRuntime.setSpaceSettings(spaceId, settings); - if (worldCollisionDisabled || terrainRepresentationChanged || terrainMaterialChanged) { + if (terrainDisabled || terrainRepresentationChanged || terrainMaterialChanged) { terrainRuntime.clear(requireSpaceBinding(spaceId)); - } else if (worldCollisionSettingsChanged) { + } else if (terrainStreamingSettingsChanged) { terrainRuntime.incrementStreamingRevision(spaceId); } } - private static boolean worldCollisionStreamingSettingsChanged( + private static boolean terrainStreamingSettingsChanged( @Nonnull PhysicsChunkTerrainSettings previous, @Nonnull PhysicsChunkTerrainSettings next) { return previous.getTerrainMode() != next.getTerrainMode() diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java index 7f822837..a0f7956e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java @@ -20,7 +20,7 @@ final class PhysicsDebugRenderer { private static final double SHAPE_INFLATION = 1.025; - private static final double WORLD_COLLISION_EDGE_PADDING = 0.0125; + private static final double TERRAIN_EDGE_PADDING = 0.0125; private static final float MIN_DEBUG_LIFETIME = 0.08f; private static final double MIN_ARROW_LENGTH = 0.05; private static final double MAX_ARROW_LENGTH = 4.0; @@ -228,7 +228,7 @@ static void renderRay(@Nonnull Collection viewers, renderArrow(viewers, start, direction, color, time); } - static void renderWorldCollisionSection(@Nonnull Collection viewers, + static void renderPhysicsChunkTerrainSection(@Nonnull Collection viewers, int chunkX, int sectionY, int chunkZ, @@ -243,10 +243,10 @@ static void renderWorldCollisionSection(@Nonnull Collection viewers, new Vector3d(halfSection, halfSection, halfSection), color, time, - WORLD_COLLISION_EDGE_PADDING); + TERRAIN_EDGE_PADDING); } - static void renderWorldCollisionBox(@Nonnull Collection viewers, + static void renderPhysicsChunkTerrainBox(@Nonnull Collection viewers, @Nonnull BoxCollider box, @Nonnull Vector3f color, float time) { @@ -255,7 +255,7 @@ static void renderWorldCollisionBox(@Nonnull Collection viewers, new Vector3d(box.halfX(), box.halfY(), box.halfZ()), color, time, - WORLD_COLLISION_EDGE_PADDING); + TERRAIN_EDGE_PADDING); } static Vector3d centerFromSyncedTransform(@Nonnull PhysicsBodySnapshot snapshot, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 5917d913..fbdabe3d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -99,8 +99,8 @@ public void tick(float dt, int index, @Nonnull Store store) { } boolean overlayDue = debug.tickOverlayBudget(dt); - boolean worldCollisionDue = debug.tickPhysicsChunkBudget(dt); - if (!overlayDue && !worldCollisionDue) { + boolean terrainDue = debug.tickPhysicsChunkBudget(dt); + if (!overlayDue && !terrainDue) { return; } @@ -108,16 +108,16 @@ public void tick(float dt, int index, @Nonnull Store store) { boolean debugMotion = debug.isDebugMotionEnabled(); boolean debugContacts = debug.isDebugContactsEnabled(); boolean debugJoints = debug.isDebugJointsEnabled(); - boolean debugWorldCollision = debug.isDebugPhysicsChunkTerrainEnabled(); + boolean debugTerrain = debug.isDebugPhysicsChunkTerrainEnabled(); if (!debugShapes && !debugMotion && !debugContacts && !debugJoints - && !debugWorldCollision) { + && !debugTerrain) { return; } Store physicsStore = PhysicsThreading.store(world); float overlayLifetime = PhysicsDebugRenderer.lifetimeForRefresh( debug.getOverlayRefreshSeconds(), dt); - float worldCollisionLifetime = PhysicsDebugRenderer.lifetimeForRefresh( + float terrainLifetime = PhysicsDebugRenderer.lifetimeForRefresh( debug.getPhysicsChunkRefreshSeconds(), dt); DebugQueryCache queryCache = queryCacheFor(store); @@ -172,8 +172,8 @@ public void tick(float dt, int index, @Nonnull Store store) { debug.getMaxJoints(), overlayLifetime); } - if (worldCollisionDue && debugWorldCollision) { - renderWorldCollision(target, + if (terrainDue && debugTerrain) { + renderPhysicsChunkTerrain(target, physicsStore, spaceId, viewerUuid, @@ -182,7 +182,7 @@ public void tick(float dt, int index, @Nonnull Store store) { debug.getViewRadius(), debug.getMaxPhysicsChunkSections(), debug.getMaxPhysicsChunkBoxes(), - worldCollisionLifetime); + terrainLifetime); } } } @@ -416,7 +416,7 @@ private static void renderJoints(@Nonnull Collection viewers, } } - private static void renderWorldCollision(@Nonnull Collection viewers, + private static void renderPhysicsChunkTerrain(@Nonnull Collection viewers, @Nonnull Store physicsStore, @Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid, @@ -447,7 +447,7 @@ private static void renderWorldCollision(@Nonnull Collection viewers, int sectionLimit = Math.min(maxSections, visibleSections.size()); for (int i = 0; i < sectionLimit; i++) { PhysicsChunkDebugSectionView section = visibleSections.get(i).section(); - PhysicsDebugRenderer.renderWorldCollisionSection(viewers, + PhysicsDebugRenderer.renderPhysicsChunkTerrainSection(viewers, section.chunkX(), section.sectionY(), section.chunkZ(), @@ -462,7 +462,7 @@ private static void renderWorldCollision(@Nonnull Collection viewers, int boxLimit = Math.min(maxBoxes, visibleBoxes.size()); for (int i = 0; i < boxLimit; i++) { VisibleDebugBox visibleBox = visibleBoxes.get(i); - PhysicsDebugRenderer.renderWorldCollisionBox(viewers, + PhysicsDebugRenderer.renderPhysicsChunkTerrainBox(viewers, visibleBox.box(), visibleBox.color(), time); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 14fe91d5..ae89b937 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -1,32 +1,45 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Vector3d; /** * Public PhysicsChunk operations for terrain-backed collision. */ -@SuppressWarnings("deprecation") public final class PhysicsChunkTerrain { private PhysicsChunkTerrain() { } public static void enableModule() { - PhysicsWorldCollision.enableModule(); + PhysicsChunkLifecycle.enable(); } public static void disableModule() { - PhysicsWorldCollision.disableModule(); + PhysicsChunkLifecycle.disable(); } public static boolean isModuleEnabled() { - return PhysicsWorldCollision.isModuleEnabled(); + return PhysicsChunkLifecycle.isEnabled(); } @Nonnull @@ -35,12 +48,24 @@ public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { - return PhysicsChunkTerrainBuildStats.fromWorldCollisionStats( - PhysicsWorldCollision.rebuildAround(world, - store, - spaceId, - center, - radius)); + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "rebuild PhysicsChunk terrain"); + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); + PhysicsTerrainMutationQueueResource queue = checkedStore.getResource( + PhysicsTerrainMutationQueueResource.getResourceType()); + int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); + PhysicsChunkTerrainPrewarmStats stats = streaming(world).ensureAround(world, + settings.spaceUuid(), + queue, + List.of(Objects.requireNonNull(center, "center")), + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + return withRemovedBodies(stats.buildStats(), + stats.buildStats().removedBodies() + removed); } @Nonnull @@ -49,12 +74,19 @@ public static PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { - return PhysicsChunkTerrainBuildStats.fromWorldCollisionStats( - PhysicsWorldCollision.refreshAround(world, - store, - spaceId, - center, - radius)); + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "refresh PhysicsChunk terrain"); + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); + return streaming(world).refreshAround(world, + settings.spaceUuid(), + checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + Objects.requireNonNull(center, "center"), + radius, + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); } @Nonnull @@ -64,23 +96,128 @@ public static PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, @Nonnull Iterable centers, int radius, long tick) { - return PhysicsChunkTerrainPrewarmStats.fromWorldCollisionStats( - PhysicsWorldCollision.ensureAround(world, - store, - spaceId, - Objects.requireNonNull(centers, "centers"), - radius, - tick)); + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "ensure PhysicsChunk terrain"); + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); + return streaming(world).ensureAround(world, + settings.spaceUuid(), + checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + Objects.requireNonNull(centers, "centers"), + radius, + tick, + null, + settings.buildOptions()); } public static int clearSpace(@Nonnull World world, @Nonnull Store store, @Nonnull SpaceId spaceId) { - return PhysicsWorldCollision.clearSpace(world, store, spaceId); + Store checkedStore = requireMatchingWorldThread(world, + store, + "clear PhysicsChunk terrain"); + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, + Objects.requireNonNull(spaceId, "spaceId")); + return clearSpaceRows(world, checkedStore, spaceUuid); } @Nonnull public static PhysicsChunkTerrainStats stats(@Nonnull World world) { - return PhysicsChunkTerrainStats.fromWorldCollisionStats(PhysicsWorldCollision.stats(world)); + Objects.requireNonNull(world, "world"); + if (!world.isInThread()) { + throw new IllegalStateException("Cannot read PhysicsChunk terrain stats " + + "outside the owning world thread"); + } + return isModuleEnabled() + ? streaming(world).stats() + : new PhysicsChunkTerrainStats(0, 0, 0, 0); + } + + private static void requireEnabled() { + if (!isModuleEnabled()) { + throw new IllegalStateException("Impulse physics chunk subplugin is disabled"); + } + } + + @Nonnull + private static Store requireMatchingWorldThread(@Nonnull World world, + @Nonnull Store store, + @Nonnull String operation) { + World checkedWorld = Objects.requireNonNull(world, "world"); + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsThreading.requireWorldThread(checkedStore, operation); + if (PhysicsThreading.world(checkedStore) != checkedWorld) { + throw new IllegalArgumentException("PhysicsStore does not belong to the supplied world"); + } + return checkedStore; + } + + @Nonnull + private static PhysicsChunkSpaceSettings requireSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, + Objects.requireNonNull(spaceId, "spaceId")); + Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + if (spaceRef == null || !spaceRef.isValid()) { + throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() + + " is not bound yet"); + } + WorldCollisionComponent component = + store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); + WorldCollisionComponent settings = + component != null ? component : new WorldCollisionComponent(); + if (settings.getTerrainMode() == PhysicsChunkTerrainMode.NONE) { + throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + + spaceId); + } + return new PhysicsChunkSpaceSettings(spaceUuid, + settings.getTerrainMode(), + settings.getEntityChunkBoundaryMode(), + settings.isNativeVoxelTerrainEnabled(), + settings.getRadius(), + settings.getBodyRadius(), + settings.getTtlTicks(), + settings.getTerrainFriction(), + settings.getTerrainRestitution()); + } + + @Nonnull + private static PhysicsChunkTerrainStreamingResource streaming(@Nonnull World world) { + Store entityStore = Objects.requireNonNull(world, "world") + .getEntityStore() + .getStore(); + return entityStore.getResource(PhysicsChunkTerrainStreamingResource.getResourceType()); + } + + private static int clearSpaceRows(@Nonnull World world, + @Nonnull Store store, + @Nonnull UUID spaceUuid) { + int removed = 0; + if (isModuleEnabled()) { + removed = streaming(world).clearSpace(spaceUuid, + store.getResource(PhysicsTerrainMutationQueueResource.getResourceType())); + } + int directlyRemoved = + PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); + return removed != 0 ? removed : directlyRemoved; + } + + @Nonnull + private static PhysicsChunkTerrainBuildStats withRemovedBodies( + @Nonnull PhysicsChunkTerrainBuildStats stats, + int removedBodies) { + return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), + stats.solidBlocks(), + stats.culledInteriorBlocks(), + stats.fullCubeRuns(), + stats.detailBoxes(), + stats.colliderBodies(), + removedBodies, + stats.sectionsBuilt(), + stats.sectionsRebuilt(), + stats.voxelBodies()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java new file mode 100644 index 00000000..9e6cdf25 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java @@ -0,0 +1,354 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import java.util.List; +import java.util.Locale; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Public PhysicsChunk profiling helpers for command and diagnostics surfaces. + */ +public final class PhysicsChunkTerrainProfiling { + + private PhysicsChunkTerrainProfiling() { + } + + public static boolean isRuntimeProfilingEnabled(@Nonnull Store store) { + PhysicsRuntimeProfilingResource runtimeProfiling = runtimeProfiling(store); + PhysicsChunkProfilingResource terrainProfiling = terrainProfiling(store); + return runtimeProfiling.isEnabled() && terrainProfiling.isEnabled(); + } + + public static void setRuntimeProfilingEnabled(@Nonnull World world, + @Nonnull Store store, + boolean enabled) { + runtimeProfiling(store).setEnabled(enabled); + terrainProfiling(store).setEnabled(enabled); + Store physicsStore = physicsStoreOrNull(world); + if (physicsStore != null) { + physicsStore.getResource(PhysicsProfilingResource.getResourceType()) + .setEnabled(enabled); + } + } + + public static void resetRuntimeProfiling(@Nonnull World world, + @Nonnull Store store) { + runtimeProfiling(store).reset(); + terrainProfiling(store).reset(); + Store physicsStore = physicsStoreOrNull(world); + if (physicsStore != null) { + physicsStore.getResource(PhysicsProfilingResource.getResourceType()).reset(); + } + } + + @Nonnull + public static Snapshots snapshots(@Nonnull Store store) { + PhysicsChunkProfilingResource profiling = terrainProfiling(store); + return new Snapshots(profiling.getCumulativeSnapshot(), + profiling.getLatestTickSnapshot(), + profiling.getWorstTickSnapshot(), + profiling.isEnabled()); + } + + @Nonnull + public static List missingSectionSamples( + @Nonnull SnapshotView snapshot) { + return snapshot.snapshot.getMissingSectionSamples() + .stream() + .map(PhysicsChunkTerrainProfiling::view) + .toList(); + } + + @Nonnull + private static MissingSectionSampleView view( + @Nonnull PhysicsChunkProfilingResource.MissingSectionSample sample) { + PhysicsChunkProfilingResource.StreamingTargetDiagnostic target = sample.target(); + return new MissingSectionSampleView(sample.chunkX(), + sample.sectionY(), + sample.chunkZ(), + sample.reason().name().toLowerCase(Locale.ROOT), + sample.retainedEnvelopeStatus().name().toLowerCase(Locale.ROOT), + target.targetType().name().toLowerCase(Locale.ROOT), + target.bodyUuid(), + target.snapshotPosition() != null ? target.snapshotPosition().compact() : null, + target.livePosition() != null ? target.livePosition().compact() : null); + } + + @Nonnull + private static PhysicsRuntimeProfilingResource runtimeProfiling( + @Nonnull Store store) { + return store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); + } + + @Nonnull + private static PhysicsChunkProfilingResource terrainProfiling( + @Nonnull Store store) { + return store.getResource(PhysicsChunkProfilingResource.getResourceType()); + } + + @Nullable + private static Store physicsStoreOrNull(@Nonnull World world) { + return PhysicsThreading.storeOrNull(world); + } + + public record Snapshots(@Nonnull SnapshotView cumulative, + @Nonnull SnapshotView latest, + @Nonnull SnapshotView worst, + boolean enabled) { + + private Snapshots(@Nonnull PhysicsChunkProfilingResource.Snapshot cumulative, + @Nonnull PhysicsChunkProfilingResource.Snapshot latest, + @Nonnull PhysicsChunkProfilingResource.Snapshot worst, + boolean enabled) { + this(new SnapshotView(cumulative), new SnapshotView(latest), new SnapshotView(worst), + enabled); + } + } + + public static class SnapshotView { + + @Nonnull + private final PhysicsChunkProfilingResource.Snapshot snapshot; + + SnapshotView(@Nonnull PhysicsChunkProfilingResource.Snapshot snapshot) { + this.snapshot = snapshot; + } + + @Nonnull + final PhysicsChunkProfilingResource.Snapshot rawSnapshot() { + return snapshot; + } + + public int getTickSamples() { + return snapshot.getTickSamples(); + } + + public int getPlayerStreamingTargets() { + return snapshot.getPlayerStreamingTargets(); + } + + public int getBodyStreamingCandidates() { + return snapshot.getBodyStreamingCandidates(); + } + + public int getBodySpatialIndexCandidates() { + return snapshot.getBodySpatialIndexCandidates(); + } + + public int getBodyStreamingTargets() { + return snapshot.getBodyStreamingTargets(); + } + + public int getBodyTargetDedupeSkips() { + return snapshot.getBodyTargetDedupeSkips(); + } + + public int getBodyTargetCacheHits() { + return snapshot.getBodyTargetCacheHits(); + } + + public int getBodyTargetFirstSeen() { + return snapshot.getBodyTargetFirstSeen(); + } + + public int getBodyTargetBoundsChanged() { + return snapshot.getBodyTargetBoundsChanged(); + } + + public int getBodyTargetActiveRefreshes() { + return snapshot.getBodyTargetActiveRefreshes(); + } + + public int getBodyTargetSleepingRefreshes() { + return snapshot.getBodyTargetSleepingRefreshes(); + } + + public int getBodyTargetActiveStableSkips() { + return snapshot.getBodyTargetActiveStableSkips(); + } + + public int getBodyTargetSleepingStableSkips() { + return snapshot.getBodyTargetSleepingStableSkips(); + } + + public int getBodyTargetsPruned() { + return snapshot.getBodyTargetsPruned(); + } + + public int getPlayerSectionTargets() { + return snapshot.getPlayerSectionTargets(); + } + + public int getBodySectionTargets() { + return snapshot.getBodySectionTargets(); + } + + public int getStreamingSpaces() { + return snapshot.getStreamingSpaces(); + } + + public int getTerrainApplyQueued() { + return snapshot.getTerrainApplyQueued(); + } + + public int getTerrainApplySkippedPending() { + return snapshot.getTerrainApplySkippedPending(); + } + + public int getEnsureCalls() { + return snapshot.getEnsureCalls(); + } + + public int getSectionRequests() { + return snapshot.getSectionRequests(); + } + + public int getSectionCacheHits() { + return snapshot.getSectionCacheHits(); + } + + public int getMissingChunks() { + return snapshot.getMissingChunks(); + } + + public int getMissingBlockChunks() { + return snapshot.getMissingBlockChunks(); + } + + public int getMissingBlockSections() { + return snapshot.getMissingBlockSections(); + } + + public int getMissingReasonUnknown() { + return snapshot.getMissingReasonUnknown(); + } + + public int getMissingBackoffSkips() { + return snapshot.getMissingBackoffSkips(); + } + + public int getMissingBlockChunkBackoffSkips() { + return snapshot.getMissingBlockChunkBackoffSkips(); + } + + public int getMissingBlockSectionBackoffSkips() { + return snapshot.getMissingBlockSectionBackoffSkips(); + } + + public int getMissingInsideRetainedEnvelope() { + return snapshot.getMissingInsideRetainedEnvelope(); + } + + public int getMissingOutsideRetainedEnvelope() { + return snapshot.getMissingOutsideRetainedEnvelope(); + } + + public int getMissingUnconfiguredRetainedEnvelope() { + return snapshot.getMissingUnconfiguredRetainedEnvelope(); + } + + public int getSectionsBuilt() { + return snapshot.getSectionsBuilt(); + } + + public int getSectionsRebuilt() { + return snapshot.getSectionsRebuilt(); + } + + public int getVoxelBodies() { + return snapshot.getVoxelBodies(); + } + + public int getColliderBodiesAdded() { + return snapshot.getColliderBodiesAdded(); + } + + public int getBodiesRemovedFromRebuild() { + return snapshot.getBodiesRemovedFromRebuild(); + } + + public int getBodiesRemovedFromUnloadedPrune() { + return snapshot.getBodiesRemovedFromUnloadedPrune(); + } + + public int getBodiesRemovedFromTtlPrune() { + return snapshot.getBodiesRemovedFromTtlPrune(); + } + + public int getSectionsRemovedFromUnloadedPrune() { + return snapshot.getSectionsRemovedFromUnloadedPrune(); + } + + public int getSectionsRemovedFromTtlPrune() { + return snapshot.getSectionsRemovedFromTtlPrune(); + } + + public int getDuplicateSkips() { + return snapshot.getDuplicateSkips(); + } + + public int getScannedBlocks() { + return snapshot.getScannedBlocks(); + } + + public int getSolidBlocks() { + return snapshot.getSolidBlocks(); + } + + public int getCulledInteriorBlocks() { + return snapshot.getCulledInteriorBlocks(); + } + + public int getFullCubeRuns() { + return snapshot.getFullCubeRuns(); + } + + public int getDetailBoxes() { + return snapshot.getDetailBoxes(); + } + + public int getUniqueMissingSections() { + return snapshot.getUniqueMissingSections(); + } + + public long getTickNanos() { + return snapshot.getTickNanos(); + } + + public long getEnsureAroundNanos() { + return snapshot.getEnsureAroundNanos(); + } + + public long getEnsureSectionNanos() { + return snapshot.getEnsureSectionNanos(); + } + + public long getPruneUnloadedNanos() { + return snapshot.getPruneUnloadedNanos(); + } + + public long getPruneUnusedNanos() { + return snapshot.getPruneUnusedNanos(); + } + } + + public record MissingSectionSampleView(int chunkX, + int sectionY, + int chunkZ, + @Nonnull String reason, + @Nonnull String retainedEnvelopeStatus, + @Nonnull String targetType, + @Nullable UUID bodyUuid, + @Nullable String snapshotPosition, + @Nullable String livePosition) { + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index d9f513f2..4c2815ca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -1,23 +1,9 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; -import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import java.util.List; -import java.util.Objects; -import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -31,15 +17,15 @@ private PhysicsWorldCollision() { } public static void enableModule() { - PhysicsChunkLifecycle.enable(); + PhysicsChunkTerrain.enableModule(); } public static void disableModule() { - PhysicsChunkLifecycle.disable(); + PhysicsChunkTerrain.disableModule(); } public static boolean isModuleEnabled() { - return PhysicsChunkLifecycle.isEnabled(); + return PhysicsChunkTerrain.isModuleEnabled(); } @Nonnull @@ -48,24 +34,12 @@ public static WorldCollisionBuildStats rebuildAround(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { - requireEnabled(); - Store checkedStore = requireMatchingWorldThread(world, + return PhysicsChunkTerrain.rebuildAround(world, store, - "rebuild PhysicsChunk terrain"); - PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); - PhysicsTerrainMutationQueueResource queue = checkedStore.getResource( - PhysicsTerrainMutationQueueResource.getResourceType()); - int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); - WorldCollisionPrewarmStats stats = streaming(world).ensureAround(world, - settings.spaceUuid(), - queue, - List.of(Objects.requireNonNull(center, "center")), - radius, - Math.max(0L, world.getTick()), - null, - settings.buildOptions()); - return withRemovedBodies(stats.buildStats(), - stats.buildStats().removedBodies() + removed); + spaceId, + center, + radius) + .toWorldCollisionStats(); } @Nonnull @@ -74,19 +48,12 @@ public static WorldCollisionBuildStats refreshAround(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, int radius) { - requireEnabled(); - Store checkedStore = requireMatchingWorldThread(world, + return PhysicsChunkTerrain.refreshAround(world, store, - "refresh PhysicsChunk terrain"); - PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); - return streaming(world).refreshAround(world, - settings.spaceUuid(), - checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), - Objects.requireNonNull(center, "center"), - radius, - Math.max(0L, world.getTick()), - null, - settings.buildOptions()); + spaceId, + center, + radius) + .toWorldCollisionStats(); } @Nonnull @@ -96,125 +63,23 @@ public static WorldCollisionPrewarmStats ensureAround(@Nonnull World world, @Nonnull Iterable centers, int radius, long tick) { - requireEnabled(); - Store checkedStore = requireMatchingWorldThread(world, + return PhysicsChunkTerrain.ensureAround(world, store, - "ensure PhysicsChunk terrain"); - PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); - return streaming(world).ensureAround(world, - settings.spaceUuid(), - checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), - Objects.requireNonNull(centers, "centers"), + spaceId, + centers, radius, - tick, - null, - settings.buildOptions()); + tick) + .toWorldCollisionStats(); } public static int clearSpace(@Nonnull World world, @Nonnull Store store, @Nonnull SpaceId spaceId) { - Store checkedStore = requireMatchingWorldThread(world, - store, - "clear PhysicsChunk terrain"); - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, - Objects.requireNonNull(spaceId, "spaceId")); - return clearSpaceRows(world, checkedStore, spaceUuid); + return PhysicsChunkTerrain.clearSpace(world, store, spaceId); } @Nonnull public static WorldCollisionStats stats(@Nonnull World world) { - Objects.requireNonNull(world, "world"); - if (!world.isInThread()) { - throw new IllegalStateException("Cannot read PhysicsChunk terrain stats " - + "outside the owning world thread"); - } - return isModuleEnabled() - ? streaming(world).stats() - : new WorldCollisionStats(0, 0, 0, 0); - } - - private static void requireEnabled() { - if (!isModuleEnabled()) { - throw new IllegalStateException("Impulse physics chunk subplugin is disabled"); - } - } - - @Nonnull - private static Store requireMatchingWorldThread(@Nonnull World world, - @Nonnull Store store, - @Nonnull String operation) { - World checkedWorld = Objects.requireNonNull(world, "world"); - Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsThreading.requireWorldThread(checkedStore, operation); - if (PhysicsThreading.world(checkedStore) != checkedWorld) { - throw new IllegalArgumentException("PhysicsStore does not belong to the supplied world"); - } - return checkedStore; - } - - @Nonnull - private static PhysicsChunkSpaceSettings requireSettings( - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, - Objects.requireNonNull(spaceId, "spaceId")); - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - if (spaceRef == null || !spaceRef.isValid()) { - throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() - + " is not bound yet"); - } - WorldCollisionComponent component = - store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); - WorldCollisionComponent settings = component != null ? component : new WorldCollisionComponent(); - if (settings.getTerrainMode() == PhysicsChunkTerrainMode.NONE) { - throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); - } - return new PhysicsChunkSpaceSettings(spaceUuid, - settings.getTerrainMode(), - settings.getEntityChunkBoundaryMode(), - settings.isNativeVoxelTerrainEnabled(), - settings.getRadius(), - settings.getBodyRadius(), - settings.getTtlTicks(), - settings.getTerrainFriction(), - settings.getTerrainRestitution()); - } - - @Nonnull - private static PhysicsChunkTerrainStreamingResource streaming(@Nonnull World world) { - Store entityStore = Objects.requireNonNull(world, "world") - .getEntityStore() - .getStore(); - return entityStore.getResource(PhysicsChunkTerrainStreamingResource.getResourceType()); - } - - private static int clearSpaceRows(@Nonnull World world, - @Nonnull Store store, - @Nonnull UUID spaceUuid) { - int removed = 0; - if (isModuleEnabled()) { - removed = streaming(world).clearSpace(spaceUuid, - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType())); - } - int directlyRemoved = PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); - return removed != 0 ? removed : directlyRemoved; - } - - @Nonnull - private static WorldCollisionBuildStats withRemovedBodies( - @Nonnull WorldCollisionBuildStats stats, - int removedBodies) { - return new WorldCollisionBuildStats(stats.scannedBlocks(), - stats.solidBlocks(), - stats.culledInteriorBlocks(), - stats.fullCubeRuns(), - stats.detailBoxes(), - stats.colliderBodies(), - removedBodies, - stats.sectionsBuilt(), - stats.sectionsRebuilt(), - stats.voxelBodies()); + return PhysicsChunkTerrain.stats(world).toWorldCollisionStats(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java index e6c03b9a..fcb091e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java @@ -3,336 +3,66 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; -import java.util.Locale; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * Public PhysicsChunk profiling helpers for command and diagnostics surfaces. + * @deprecated Use {@link PhysicsChunkTerrainProfiling}. */ +@Deprecated(forRemoval = false) public final class PhysicsWorldCollisionProfiling { private PhysicsWorldCollisionProfiling() { } public static boolean isRuntimeProfilingEnabled(@Nonnull Store store) { - PhysicsRuntimeProfilingResource runtimeProfiling = runtimeProfiling(store); - PhysicsChunkProfilingResource worldCollisionProfiling = worldCollisionProfiling(store); - return runtimeProfiling.isEnabled() && worldCollisionProfiling.isEnabled(); + return PhysicsChunkTerrainProfiling.isRuntimeProfilingEnabled(store); } public static void setRuntimeProfilingEnabled(@Nonnull World world, @Nonnull Store store, boolean enabled) { - runtimeProfiling(store).setEnabled(enabled); - worldCollisionProfiling(store).setEnabled(enabled); - Store physicsStore = physicsStoreOrNull(world); - if (physicsStore != null) { - physicsStore.getResource(PhysicsProfilingResource.getResourceType()) - .setEnabled(enabled); - } + PhysicsChunkTerrainProfiling.setRuntimeProfilingEnabled(world, store, enabled); } public static void resetRuntimeProfiling(@Nonnull World world, @Nonnull Store store) { - runtimeProfiling(store).reset(); - worldCollisionProfiling(store).reset(); - Store physicsStore = physicsStoreOrNull(world); - if (physicsStore != null) { - physicsStore.getResource(PhysicsProfilingResource.getResourceType()).reset(); - } + PhysicsChunkTerrainProfiling.resetRuntimeProfiling(world, store); } @Nonnull public static Snapshots snapshots(@Nonnull Store store) { - PhysicsChunkProfilingResource profiling = worldCollisionProfiling(store); - return new Snapshots(profiling.getCumulativeSnapshot(), - profiling.getLatestTickSnapshot(), - profiling.getWorstTickSnapshot(), - profiling.isEnabled()); + return new Snapshots(PhysicsChunkTerrainProfiling.snapshots(store)); } @Nonnull public static List missingSectionSamples( @Nonnull SnapshotView snapshot) { - return snapshot.snapshot.getMissingSectionSamples() + return PhysicsChunkTerrainProfiling.missingSectionSamples(snapshot) .stream() - .map(PhysicsWorldCollisionProfiling::view) + .map(MissingSectionSampleView::new) .toList(); } - @Nonnull - private static MissingSectionSampleView view( - @Nonnull PhysicsChunkProfilingResource.MissingSectionSample sample) { - PhysicsChunkProfilingResource.StreamingTargetDiagnostic target = sample.target(); - return new MissingSectionSampleView(sample.chunkX(), - sample.sectionY(), - sample.chunkZ(), - sample.reason().name().toLowerCase(Locale.ROOT), - sample.retainedEnvelopeStatus().name().toLowerCase(Locale.ROOT), - target.targetType().name().toLowerCase(Locale.ROOT), - target.bodyUuid(), - target.snapshotPosition() != null ? target.snapshotPosition().compact() : null, - target.livePosition() != null ? target.livePosition().compact() : null); - } - - @Nonnull - private static PhysicsRuntimeProfilingResource runtimeProfiling( - @Nonnull Store store) { - return store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); - } - - @Nonnull - private static PhysicsChunkProfilingResource worldCollisionProfiling( - @Nonnull Store store) { - return store.getResource(PhysicsChunkProfilingResource.getResourceType()); - } - - @Nullable - private static Store physicsStoreOrNull(@Nonnull World world) { - return PhysicsThreading.storeOrNull(world); - } - public record Snapshots(@Nonnull SnapshotView cumulative, @Nonnull SnapshotView latest, @Nonnull SnapshotView worst, boolean enabled) { - private Snapshots(@Nonnull PhysicsChunkProfilingResource.Snapshot cumulative, - @Nonnull PhysicsChunkProfilingResource.Snapshot latest, - @Nonnull PhysicsChunkProfilingResource.Snapshot worst, - boolean enabled) { - this(new SnapshotView(cumulative), new SnapshotView(latest), new SnapshotView(worst), - enabled); + private Snapshots(@Nonnull PhysicsChunkTerrainProfiling.Snapshots snapshots) { + this(new SnapshotView(snapshots.cumulative()), + new SnapshotView(snapshots.latest()), + new SnapshotView(snapshots.worst()), + snapshots.enabled()); } } - public static final class SnapshotView { - - @Nonnull - private final PhysicsChunkProfilingResource.Snapshot snapshot; - - private SnapshotView(@Nonnull PhysicsChunkProfilingResource.Snapshot snapshot) { - this.snapshot = snapshot; - } - - public int getTickSamples() { - return snapshot.getTickSamples(); - } - - public int getPlayerStreamingTargets() { - return snapshot.getPlayerStreamingTargets(); - } - - public int getBodyStreamingCandidates() { - return snapshot.getBodyStreamingCandidates(); - } - - public int getBodySpatialIndexCandidates() { - return snapshot.getBodySpatialIndexCandidates(); - } - - public int getBodyStreamingTargets() { - return snapshot.getBodyStreamingTargets(); - } - - public int getBodyTargetDedupeSkips() { - return snapshot.getBodyTargetDedupeSkips(); - } - - public int getBodyTargetCacheHits() { - return snapshot.getBodyTargetCacheHits(); - } - - public int getBodyTargetFirstSeen() { - return snapshot.getBodyTargetFirstSeen(); - } - - public int getBodyTargetBoundsChanged() { - return snapshot.getBodyTargetBoundsChanged(); - } - - public int getBodyTargetActiveRefreshes() { - return snapshot.getBodyTargetActiveRefreshes(); - } - - public int getBodyTargetSleepingRefreshes() { - return snapshot.getBodyTargetSleepingRefreshes(); - } - - public int getBodyTargetActiveStableSkips() { - return snapshot.getBodyTargetActiveStableSkips(); - } - - public int getBodyTargetSleepingStableSkips() { - return snapshot.getBodyTargetSleepingStableSkips(); - } - - public int getBodyTargetsPruned() { - return snapshot.getBodyTargetsPruned(); - } - - public int getPlayerSectionTargets() { - return snapshot.getPlayerSectionTargets(); - } - - public int getBodySectionTargets() { - return snapshot.getBodySectionTargets(); - } - - public int getStreamingSpaces() { - return snapshot.getStreamingSpaces(); - } - - public int getTerrainApplyQueued() { - return snapshot.getTerrainApplyQueued(); - } - - public int getTerrainApplySkippedPending() { - return snapshot.getTerrainApplySkippedPending(); - } - - public int getEnsureCalls() { - return snapshot.getEnsureCalls(); - } - - public int getSectionRequests() { - return snapshot.getSectionRequests(); - } - - public int getSectionCacheHits() { - return snapshot.getSectionCacheHits(); - } - - public int getMissingChunks() { - return snapshot.getMissingChunks(); - } - - public int getMissingBlockChunks() { - return snapshot.getMissingBlockChunks(); - } - - public int getMissingBlockSections() { - return snapshot.getMissingBlockSections(); - } - - public int getMissingReasonUnknown() { - return snapshot.getMissingReasonUnknown(); - } - - public int getMissingBackoffSkips() { - return snapshot.getMissingBackoffSkips(); - } - - public int getMissingBlockChunkBackoffSkips() { - return snapshot.getMissingBlockChunkBackoffSkips(); - } - - public int getMissingBlockSectionBackoffSkips() { - return snapshot.getMissingBlockSectionBackoffSkips(); - } - - public int getMissingInsideRetainedEnvelope() { - return snapshot.getMissingInsideRetainedEnvelope(); - } - - public int getMissingOutsideRetainedEnvelope() { - return snapshot.getMissingOutsideRetainedEnvelope(); - } - - public int getMissingUnconfiguredRetainedEnvelope() { - return snapshot.getMissingUnconfiguredRetainedEnvelope(); - } - - public int getSectionsBuilt() { - return snapshot.getSectionsBuilt(); - } - - public int getSectionsRebuilt() { - return snapshot.getSectionsRebuilt(); - } - - public int getVoxelBodies() { - return snapshot.getVoxelBodies(); - } - - public int getColliderBodiesAdded() { - return snapshot.getColliderBodiesAdded(); - } - - public int getBodiesRemovedFromRebuild() { - return snapshot.getBodiesRemovedFromRebuild(); - } - - public int getBodiesRemovedFromUnloadedPrune() { - return snapshot.getBodiesRemovedFromUnloadedPrune(); - } - - public int getBodiesRemovedFromTtlPrune() { - return snapshot.getBodiesRemovedFromTtlPrune(); - } - - public int getSectionsRemovedFromUnloadedPrune() { - return snapshot.getSectionsRemovedFromUnloadedPrune(); - } - - public int getSectionsRemovedFromTtlPrune() { - return snapshot.getSectionsRemovedFromTtlPrune(); - } - - public int getDuplicateSkips() { - return snapshot.getDuplicateSkips(); - } - - public int getScannedBlocks() { - return snapshot.getScannedBlocks(); - } - - public int getSolidBlocks() { - return snapshot.getSolidBlocks(); - } - - public int getCulledInteriorBlocks() { - return snapshot.getCulledInteriorBlocks(); - } + public static final class SnapshotView extends PhysicsChunkTerrainProfiling.SnapshotView { - public int getFullCubeRuns() { - return snapshot.getFullCubeRuns(); - } - - public int getDetailBoxes() { - return snapshot.getDetailBoxes(); - } - - public int getUniqueMissingSections() { - return snapshot.getUniqueMissingSections(); - } - - public long getTickNanos() { - return snapshot.getTickNanos(); - } - - public long getEnsureAroundNanos() { - return snapshot.getEnsureAroundNanos(); - } - - public long getEnsureSectionNanos() { - return snapshot.getEnsureSectionNanos(); - } - - public long getPruneUnloadedNanos() { - return snapshot.getPruneUnloadedNanos(); - } - - public long getPruneUnusedNanos() { - return snapshot.getPruneUnusedNanos(); + private SnapshotView(@Nonnull PhysicsChunkTerrainProfiling.SnapshotView view) { + super(view.rawSnapshot()); } } @@ -345,5 +75,18 @@ public record MissingSectionSampleView(int chunkX, @Nullable UUID bodyUuid, @Nullable String snapshotPosition, @Nullable String livePosition) { + + private MissingSectionSampleView( + @Nonnull PhysicsChunkTerrainProfiling.MissingSectionSampleView sample) { + this(sample.chunkX(), + sample.sectionY(), + sample.chunkZ(), + sample.reason(), + sample.retainedEnvelopeStatus(), + sample.targetType(), + sample.bodyUuid(), + sample.snapshotPosition(), + sample.livePosition()); + } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index c4964b50..08b8dd82 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -65,7 +65,7 @@ public static Ref resolveRef(@Nonnull Store store, public static Holder spaceHolder(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent worldCollision, + @Nonnull WorldCollisionComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -74,7 +74,7 @@ public static Holder spaceHolder(@Nonnull Store stor Holder holder = entityHolder(store, spaceUuid); addSpaceComponents(holder, space, - worldCollision, + terrainSettings, solverSettings, visualSyncSettings, visualMaterializationSettings, @@ -120,7 +120,7 @@ public static Holder terrainColliderHolder(@Nonnull Store holder, @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent worldCollision, + @Nonnull WorldCollisionComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -130,7 +130,7 @@ public static void addSpaceComponents(@Nonnull Holder holder, .addComponent(SpaceComponent.getComponentType(), Objects.requireNonNull(space, "space").clone()); holder.addComponent(WorldCollisionComponent.getComponentType(), - Objects.requireNonNull(worldCollision, "worldCollision").clone()); + Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); addSpaceSettingsComponents(holder, solverSettings, visualSyncSettings, @@ -188,7 +188,7 @@ public static void addBodyComponents(@Nonnull Holder holder, public static void putSpaceComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent worldCollision, + @Nonnull WorldCollisionComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -202,7 +202,7 @@ public static void putSpaceComponents(@Nonnull Store store, Objects.requireNonNull(space, "space").clone()); checkedStore.putComponent(ref, WorldCollisionComponent.getComponentType(), - Objects.requireNonNull(worldCollision, "worldCollision").clone()); + Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); putSpaceSettingsComponents(checkedStore, ref, solverSettings, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 6c5dcc83..b762cd24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -119,10 +119,10 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, return null; } PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - WorldCollisionComponent worldCollision = checkedStore.getComponent(checkedRef, + WorldCollisionComponent terrainSettings = checkedStore.getComponent(checkedRef, WorldCollisionComponent.getComponentType()); - if (worldCollision != null) { - worldCollision.copyTo(settings); + if (terrainSettings != null) { + terrainSettings.copyTo(settings); } SolverSettingsComponent solverSettings = checkedStore.getComponent(checkedRef, SolverSettingsComponent.getComponentType()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java index 9c1407c7..442f5141 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java @@ -139,10 +139,11 @@ public class PhysicsVisualMaterializationSettings { DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS; /** - * Hytale block type used for default detached visual proxies. - *

        - * FIXME: this is temporary we cannot assume a specific blocktype since a physics body could be - * composed by any general mix of blocks and entities + * Fallback Hytale block type for generated detached visual proxies. + * + *

        This is only a default materialization hint for simple demos and stress tests. Integrators + * that need body-specific visuals should store their own visual description on the physics body + * or disable generated visual materialization.

        */ @Nonnull private String detachedVisualBlockType = DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java new file mode 100644 index 00000000..590df597 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java @@ -0,0 +1,55 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +class PhysicsChunkNamingSourceGuardTest { + + @Test + void physicsChunkTerrainFacadeDoesNotDelegateThroughDeprecatedWorldCollision() + throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java")); + + assertFalse(source.contains("PhysicsWorldCollision."), + "new PhysicsChunk terrain facade must own the implementation path"); + } + + @Test + void physicsChunkCommandsUseTerrainNamedProfilingApi() throws IOException { + try (Stream files = Files.walk(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands"))) { + for (Path file : files.filter(path -> path.toString().endsWith(".java")).toList()) { + String source = Files.readString(file); + assertFalse(source.contains("PhysicsWorldCollisionProfiling"), + file + " should use PhysicsChunkTerrainProfiling"); + } + } + } + + @Test + void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { + Path examples = Path.of("../impulse-examples/src/main/java"); + if (!Files.exists(examples)) { + return; + } + try (Stream files = Files.walk(examples)) { + for (Path file : files.filter(path -> path.toString().endsWith(".java")).toList()) { + String source = Files.readString(file); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.internal."), + file + " should use exported plugin APIs"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision"), + file + " should use PhysicsChunkTerrain"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollision"), + file + " should use PhysicsChunkTerrain names"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings"), + file + " should use PhysicsChunkTerrainSettings"); + } + } + } +} From bc16fda3301ccd289642094057947bea6b37157a Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 20:35:44 +0200 Subject: [PATCH 357/534] perf(api): batch legacy body snapshots Signed-off-by: Blovien --- .../legacy/LegacyPhysicsBackendRuntime.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java index 24b93073..2182c411 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java @@ -33,8 +33,10 @@ import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; import dev.hytalemodding.impulse.api.runtime.BackendVec3Sink; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import javax.annotation.Nonnull; @@ -232,12 +234,29 @@ public void snapshotBodies(int spaceId, @Nonnull BackendBodyIdSource bodyIds, @Nonnull BackendBodySnapshotSink sink) { SpaceState state = requireSpace(spaceId); + List selectedBodies = state.selectedSnapshotBodies; + selectedBodies.clear(); bodyIds.forEachBodyId(bodyId -> { PhysicsBody body = state.bodiesById.get(bodyId); if (body != null) { - emitBodySnapshot(bodyId, PhysicsBodySnapshot.from(body), sink); + selectedBodies.add(body); } }); + if (selectedBodies.isEmpty()) { + return; + } + try { + state.space.snapshotBodies(selectedBodies, + body -> null, + (body, snapshot) -> { + Long bodyId = state.bodyIdsByBody.get(body); + if (bodyId != null) { + emitBodySnapshot(bodyId, snapshot, sink); + } + }); + } finally { + selectedBodies.clear(); + } } @Override @@ -817,6 +836,7 @@ private static final class SpaceState { private final Map bodiesById = new HashMap<>(); private final Map bodyIdsByBody = new IdentityHashMap<>(); private final Map jointsById = new HashMap<>(); + private final List selectedSnapshotBodies = new ArrayList<>(); private SpaceState(@Nonnull PhysicsSpace space) { this.space = Objects.requireNonNull(space, "space"); From a2858b4c11567177902e2687f9d7f3d5d3d61832 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 20:43:26 +0200 Subject: [PATCH 358/534] fix(core): settle physics benchmark warmup Signed-off-by: Blovien --- ...pulseRapierBodyBenchmarkCrucibleTests.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 730393c0..34a18f75 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -190,17 +191,20 @@ private CompletionStage runCase(int index, MatrixCase matrixCase = new MatrixCase(plan.count(), plan.substeps().get(index)); return startCase(matrixCase) - .thenCompose(started -> contextWait(plan.warmupTicks()).thenCompose(_ -> { - physicsStoreProfiling.reset(); - runtimeProfiling.reset(); - terrainProfiling.reset(); - physicsStoreProfiling.setEnabled(true); - runtimeProfiling.setEnabled(true); - terrainProfiling.setEnabled(true); - long startedNanos = System.nanoTime(); - return contextWait(plan.sampleTicks()).thenApply( - _ -> finishCase(matrixCase, started, startedNanos)); - })) + .thenCompose(started -> contextWait(plan.warmupTicks()) + .thenCompose(_ -> waitForPhysicsStoreIdle()) + .thenCompose(_ -> contextWait(1)) + .thenCompose(_ -> { + physicsStoreProfiling.reset(); + runtimeProfiling.reset(); + terrainProfiling.reset(); + physicsStoreProfiling.setEnabled(true); + runtimeProfiling.setEnabled(true); + terrainProfiling.setEnabled(true); + long startedNanos = System.nanoTime(); + return contextWait(plan.sampleTicks()).thenApply( + _ -> finishCase(matrixCase, started, startedNanos)); + })) .thenCompose(report -> { reports.add(report); LOGGER.at(Level.INFO).log("Crucible Rapier body matrix case: %s", @@ -368,6 +372,11 @@ private CompletionStage contextWait(int ticks) { } } + private CompletionStage waitForPhysicsStoreIdle() { + return physicsStore.getResource(PhysicsStepSchedulerResource.getResourceType()) + .whenIdle(); + } + private void clearCaseState() { removeBenchmarkEntities(); physics.clearSyntheticVisualInterests(); From 1f34d7dd7671af0ff08a35d9366efe7e82098d79 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 20:53:49 +0200 Subject: [PATCH 359/534] refactor(core): canonicalize physics chunk terrain component Signed-off-by: Blovien --- README.md | 2 +- build.gradle.kts | 1 + .../persistence/PersistentSpaceDto.java | 62 ++-- .../PhysicsStoreSpaceMutations.java | 8 +- .../PhysicsWorldRuntimeResource.java | 6 +- .../systems/PersistenceCaptureSystem.java | 28 +- .../systems/PersistenceHydrationSystem.java | 6 +- .../PhysicsChunkSettingsIndexSystem.java | 12 +- .../components/PhysicsComponentTypes.java | 16 +- .../physicschunk/PhysicsChunkTerrain.java | 10 +- .../PhysicsChunkTerrainComponent.java | 308 ++++++++++++++++++ .../components/WorldCollisionComponent.java | 250 ++------------ .../plugin/physicsstore/PhysicsEntities.java | 12 +- .../plugin/physicsstore/PhysicsSpaces.java | 6 +- 14 files changed, 423 insertions(+), 304 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java diff --git a/README.md b/README.md index 1613e6ad..9db690ae 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ flowchart TB Plugin["Plugin API package"] - Modules["Internal modules\n- Hytale modules substitution (WIP)\n- World collision module\n- Control session module"] + Modules["Internal modules\n- Hytale modules substitution (WIP)\n- PhysicsChunk terrain module\n- Control session module"] StoreSystems["PhysicsStore systems + resources"] Ordering["row mutation + backend step ordering"] diff --git a/build.gradle.kts b/build.gradle.kts index 5019114a..2afda4ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -151,6 +151,7 @@ tasks.register("headlessTest") { ":impulse-bullet:test", ":impulse-rapier:test", ":impulse-core:test", + ":impulse-examples:test", ":impulse-early-plugin:test" ) } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index e7828960..bc3ae7c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -13,15 +13,16 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Vector3f; +@SuppressWarnings("deprecation") public final class PersistentSpaceDto { @Nonnull @@ -57,7 +58,7 @@ public final class PersistentSpaceDto { false), (dto, value) -> dto.entityChunkBoundaryMode = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, PersistentSpaceDto::getEntityChunkBoundaryMode) .add() .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), @@ -67,31 +68,31 @@ public final class PersistentSpaceDto { .append(new KeyedCodec<>("WorldCollisionRadius", Codec.INTEGER, false), (dto, value) -> dto.worldCollisionRadius = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, PersistentSpaceDto::getWorldCollisionRadius) .add() .append(new KeyedCodec<>("WorldCollisionBodyRadius", Codec.INTEGER, false), (dto, value) -> dto.worldCollisionBodyRadius = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS, + : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, PersistentSpaceDto::getWorldCollisionBodyRadius) .add() .append(new KeyedCodec<>("WorldCollisionTtlTicks", Codec.INTEGER, false), (dto, value) -> dto.worldCollisionTtlTicks = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, PersistentSpaceDto::getWorldCollisionTtlTicks) .add() .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), (dto, value) -> dto.terrainFriction = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, PersistentSpaceDto::getTerrainFriction) .add() .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), (dto, value) -> dto.terrainRestitution = value != null ? value - : PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, PersistentSpaceDto::getTerrainRestitution) .add() .append(new KeyedCodec<>("SolverSettings", SolverSettingsComponent.CODEC, false), @@ -142,17 +143,17 @@ public final class PersistentSpaceDto { private WorldCollisionMode worldCollisionMode = WorldCollisionMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = - PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelTerrainEnabled = - PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; private int worldCollisionRadius = - PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS; + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; private int worldCollisionBodyRadius = - PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS; + PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; private int worldCollisionTtlTicks = - PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS; - private float terrainFriction = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION; + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; + private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; + private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; @Nonnull private SolverSettingsComponent solverSettings = new SolverSettingsComponent(); @Nonnull @@ -176,13 +177,13 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, backendId, gravity, WorldCollisionMode.NONE, - PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - PhysicsWorldCollisionSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED, - PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_RADIUS, - PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_BODY_RADIUS, - PhysicsWorldCollisionSettings.DEFAULT_WORLD_COLLISION_TTL_TICKS, - PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsWorldCollisionSettings.DEFAULT_TERRAIN_RESTITUTION, + PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, + PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, new SolverSettingsComponent(), new VisualSyncSettingsComponent(), new VisualMaterializationSettingsComponent(), @@ -204,7 +205,7 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, backendId, gravity, worldCollisionMode, - PhysicsWorldCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, nativeVoxelTerrainEnabled, worldCollisionRadius, worldCollisionBodyRadius, @@ -381,8 +382,8 @@ public float getTerrainRestitution() { } @Nonnull - public WorldCollisionComponent getWorldCollision() { - return new WorldCollisionComponent(worldCollisionMode, + public PhysicsChunkTerrainComponent getPhysicsChunkTerrain() { + return new PhysicsChunkTerrainComponent(getTerrainMode(), entityChunkBoundaryMode, nativeVoxelTerrainEnabled, worldCollisionRadius, @@ -392,6 +393,15 @@ public WorldCollisionComponent getWorldCollision() { terrainRestitution); } + /** + * @deprecated Use {@link #getPhysicsChunkTerrain()}. + */ + @Deprecated(forRemoval = false) + @Nonnull + public PhysicsChunkTerrainComponent getWorldCollision() { + return getPhysicsChunkTerrain(); + } + @Nonnull public SolverSettingsComponent getSolverSettings() { return solverSettings.clone(); @@ -420,7 +430,7 @@ public ExtensionSettingsComponent getExtensionSettings() { @Nonnull public PhysicsSpaceSettings toSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - getWorldCollision().copyTo(settings); + getPhysicsChunkTerrain().copyTo(settings); solverSettings.copyTo(settings); visualSyncSettings.copyTo(settings); visualMaterializationSettings.copyTo(settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index d4283157..ee69c9f4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; @@ -64,7 +64,7 @@ public static Ref addSpace(@Nonnull Store store, Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, spaceUuid, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), - new WorldCollisionComponent(settings.getPhysicsChunkTerrainSettings()), + new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings()), new SolverSettingsComponent(settings.getSolverSettings()), new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), @@ -118,8 +118,8 @@ public static void putSpaceSettings(@Nonnull Store store, requireSpaceUuid(store, ref); PhysicsThreading.requireWorldThread(store, "update a PhysicsStore space entity"); store.putComponent(ref, - WorldCollisionComponent.getComponentType(), - new WorldCollisionComponent(settings.getPhysicsChunkTerrainSettings())); + PhysicsChunkTerrainComponent.getComponentType(), + new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings())); PhysicsEntities.putSpaceSettingsComponents(store, ref, new SolverSettingsComponent(settings.getSolverSettings()), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 12937651..4d7f3f8a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -51,7 +51,7 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; @@ -259,8 +259,8 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( if (space == null) { return null; } - WorldCollisionComponent terrainSettings = store.getComponent(ref, - WorldCollisionComponent.getComponentType()); + PhysicsChunkTerrainComponent terrainSettings = store.getComponent(ref, + PhysicsChunkTerrainComponent.getComponentType()); SolverSettingsComponent solverSettings = store.getComponent(ref, SolverSettingsComponent.getComponentType()); VisualSyncSettingsComponent visualSyncSettings = store.getComponent(ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index 29569509..980e11f1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -37,7 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -134,7 +134,7 @@ private void collectRow(@Nonnull UUID uuid, if (space != null) { spaceRows.add(new SpaceRow(uuid, space, - chunk.getComponent(index, WorldCollisionComponent.getComponentType()), + chunk.getComponent(index, PhysicsChunkTerrainComponent.getComponentType()), chunk.getComponent(index, SolverSettingsComponent.getComponentType()), chunk.getComponent(index, VisualSyncSettingsComponent.getComponentType()), chunk.getComponent(index, @@ -198,20 +198,20 @@ private PersistentSpaceDto[] spaceDtos() { @Nonnull private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { - WorldCollisionComponent worldCollision = row.worldCollision() != null - ? row.worldCollision() - : new WorldCollisionComponent(); + PhysicsChunkTerrainComponent terrain = row.physicsChunkTerrain() != null + ? row.physicsChunkTerrain() + : new PhysicsChunkTerrainComponent(); return new PersistentSpaceDto(row.uuid(), row.space().getBackendIdValue(), row.space().getGravity(), - worldCollision.getMode(), - worldCollision.getEntityChunkBoundaryMode(), - worldCollision.isNativeVoxelTerrainEnabled(), - worldCollision.getRadius(), - worldCollision.getBodyRadius(), - worldCollision.getTtlTicks(), - worldCollision.getTerrainFriction(), - worldCollision.getTerrainRestitution(), + terrain.getTerrainMode(), + terrain.getEntityChunkBoundaryMode(), + terrain.isNativeVoxelTerrainEnabled(), + terrain.getRadius(), + terrain.getBodyRadius(), + terrain.getTtlTicks(), + terrain.getTerrainFriction(), + terrain.getTerrainRestitution(), row.solverSettings() != null ? row.solverSettings() : new SolverSettingsComponent(), @@ -384,7 +384,7 @@ private PersistentTerrainColliderDto[] terrainDtos() { private record SpaceRow(@Nonnull UUID uuid, @Nonnull SpaceComponent space, - @Nullable WorldCollisionComponent worldCollision, + @Nullable PhysicsChunkTerrainComponent physicsChunkTerrain, @Nullable SolverSettingsComponent solverSettings, @Nullable VisualSyncSettingsComponent visualSyncSettings, @Nullable VisualMaterializationSettingsComponent visualMaterializationSettings, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index f060fd85..3febe707 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -35,7 +35,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; @@ -87,8 +87,8 @@ private static void addSpace(@Nonnull Store store, Holder holder = row(store, dto.getSpaceUuid()); holder.addComponent(SpaceComponent.getComponentType(), new SpaceComponent(new BackendId(dto.getBackendId()), dto.getGravity())); - holder.addComponent(WorldCollisionComponent.getComponentType(), - dto.getWorldCollision()); + holder.addComponent(PhysicsChunkTerrainComponent.getComponentType(), + dto.getPhysicsChunkTerrain()); holder.addComponent(SolverSettingsComponent.getComponentType(), dto.getSolverSettings()); holder.addComponent(VisualSyncSettingsComponent.getComponentType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index 7c706dc2..d45321cc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; @@ -54,11 +54,11 @@ private static void collectChunk( if (PhysicsStoreSystemSupport.isNil(spaceUuid)) { continue; } - WorldCollisionComponent worldCollision = chunk.getComponent(index, - WorldCollisionComponent.getComponentType()); - WorldCollisionComponent settings = worldCollision != null - ? worldCollision - : new WorldCollisionComponent(); + PhysicsChunkTerrainComponent terrain = chunk.getComponent(index, + PhysicsChunkTerrainComponent.getComponentType()); + PhysicsChunkTerrainComponent settings = terrain != null + ? terrain + : new PhysicsChunkTerrainComponent(); settingsBySpaceUuid.put(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 6e8a8d84..904cfbff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -24,7 +24,7 @@ public final class PhysicsComponentTypes { @Nullable private static ComponentType terrainColliderComponentType; @Nullable - private static ComponentType worldCollisionComponentType; + private static ComponentType physicsChunkTerrainComponentType; @Nullable private static ComponentType dynamicsComponentType; @Nullable @@ -76,10 +76,10 @@ public static void registerComponentTypes( TerrainColliderComponent.class, "TerrainCollider", TerrainColliderComponent.CODEC); - worldCollisionComponentType = registry.registerComponent( - WorldCollisionComponent.class, + physicsChunkTerrainComponentType = registry.registerComponent( + PhysicsChunkTerrainComponent.class, "WorldCollision", - WorldCollisionComponent.CODEC); + PhysicsChunkTerrainComponent.CODEC); dynamicsComponentType = registry.registerComponent( DynamicsComponent.class, "Dynamics", @@ -157,9 +157,9 @@ public static ComponentType bodyCommandCompo } @Nonnull - public static ComponentType - worldCollisionComponentType() { - return worldCollisionComponentType; + public static ComponentType + physicsChunkTerrainComponentType() { + return physicsChunkTerrainComponentType; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index ae89b937..5a9ca6e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Objects; @@ -165,10 +165,10 @@ private static PhysicsChunkSpaceSettings requireSettings( throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() + " is not bound yet"); } - WorldCollisionComponent component = - store.getComponent(spaceRef, WorldCollisionComponent.getComponentType()); - WorldCollisionComponent settings = - component != null ? component : new WorldCollisionComponent(); + PhysicsChunkTerrainComponent component = + store.getComponent(spaceRef, PhysicsChunkTerrainComponent.getComponentType()); + PhysicsChunkTerrainComponent settings = + component != null ? component : new PhysicsChunkTerrainComponent(); if (settings.getTerrainMode() == PhysicsChunkTerrainMode.NONE) { throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java new file mode 100644 index 00000000..5d5104fa --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java @@ -0,0 +1,308 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Authored PhysicsChunk terrain streaming settings for one PhysicsStore space entity. + */ +public class PhysicsChunkTerrainComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PhysicsChunkTerrainComponent.class, + PhysicsChunkTerrainComponent::new) + .append(new KeyedCodec<>("Mode", new EnumCodec<>(PhysicsChunkTerrainMode.class), false), + (component, value) -> component.terrainMode = value != null + ? value + : PhysicsChunkTerrainMode.NONE, + PhysicsChunkTerrainComponent::getTerrainMode) + .add() + .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), + (component, value) -> component.nativeVoxelTerrainEnabled = value != null && value, + PhysicsChunkTerrainComponent::isNativeVoxelTerrainEnabled) + .add() + .append(new KeyedCodec<>("EntityChunkBoundaryMode", + new EnumCodec<>(EntityChunkBoundaryMode.class), + false), + (component, value) -> component.entityChunkBoundaryMode = value != null + ? value + : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + PhysicsChunkTerrainComponent::getEntityChunkBoundaryMode) + .add() + .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), + (component, value) -> component.radius = value != null + ? value + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, + PhysicsChunkTerrainComponent::getRadius) + .add() + .append(new KeyedCodec<>("BodyRadius", Codec.INTEGER, false), + (component, value) -> component.bodyRadius = value != null + ? value + : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, + PhysicsChunkTerrainComponent::getBodyRadius) + .add() + .append(new KeyedCodec<>("TtlTicks", Codec.INTEGER, false), + (component, value) -> component.ttlTicks = value != null + ? value + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, + PhysicsChunkTerrainComponent::getTtlTicks) + .add() + .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), + (component, value) -> component.terrainFriction = value != null + ? value + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + PhysicsChunkTerrainComponent::getTerrainFriction) + .add() + .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), + (component, value) -> component.terrainRestitution = value != null + ? value + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + PhysicsChunkTerrainComponent::getTerrainRestitution) + .add() + .build(); + + @Nonnull + private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; + @Nonnull + private EntityChunkBoundaryMode entityChunkBoundaryMode = + PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + private boolean nativeVoxelTerrainEnabled = + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private int radius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; + private int bodyRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; + private int ttlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; + private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; + private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; + + public PhysicsChunkTerrainComponent() { + } + + public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainSettings settings) { + this(settings.getTerrainMode(), + settings.getEntityChunkBoundaryMode(), + settings.isNativeVoxelTerrainEnabled(), + settings.getTerrainRadius(), + settings.getBodyTerrainRadius(), + settings.getTerrainTtlTicks(), + settings.getTerrainFriction(), + settings.getTerrainRestitution()); + } + + public PhysicsChunkTerrainComponent(@Nonnull PhysicsWorldCollisionSettings settings) { + this((PhysicsChunkTerrainSettings) settings); + } + + public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this(terrainMode, + PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } + + public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); + this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + this.radius = radius; + this.bodyRadius = bodyRadius; + this.ttlTicks = ttlTicks; + this.terrainFriction = terrainFriction; + this.terrainRestitution = terrainRestitution; + } + + /** + * @deprecated Use {@link #PhysicsChunkTerrainComponent(PhysicsChunkTerrainMode, boolean, int, int, int, float, float)}. + */ + @Deprecated(forRemoval = false) + public PhysicsChunkTerrainComponent(@Nonnull WorldCollisionMode mode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this(mode.toPhysicsChunkTerrainMode(), + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } + + /** + * @deprecated Use {@link #PhysicsChunkTerrainComponent(PhysicsChunkTerrainMode, EntityChunkBoundaryMode, boolean, int, int, int, float, float)}. + */ + @Deprecated(forRemoval = false) + public PhysicsChunkTerrainComponent(@Nonnull WorldCollisionMode mode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, + boolean nativeVoxelTerrainEnabled, + int radius, + int bodyRadius, + int ttlTicks, + float terrainFriction, + float terrainRestitution) { + this(mode.toPhysicsChunkTerrainMode(), + entityChunkBoundaryMode, + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } + + @Nonnull + public PhysicsChunkTerrainMode getTerrainMode() { + return terrainMode; + } + + public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { + this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + } + + /** + * @deprecated Use {@link #getTerrainMode()}. + */ + @Deprecated(forRemoval = false) + @Nonnull + public WorldCollisionMode getMode() { + return terrainMode.toWorldCollisionMode(); + } + + /** + * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. + */ + @Deprecated(forRemoval = false) + public void setMode(@Nonnull WorldCollisionMode mode) { + setTerrainMode(mode.toPhysicsChunkTerrainMode()); + } + + @Nonnull + public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { + return entityChunkBoundaryMode; + } + + public void setEntityChunkBoundaryMode( + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); + } + + public boolean isNativeVoxelTerrainEnabled() { + return nativeVoxelTerrainEnabled; + } + + public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { + this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + } + + public int getRadius() { + return radius; + } + + public void setRadius(int radius) { + this.radius = radius; + } + + public int getBodyRadius() { + return bodyRadius; + } + + public void setBodyRadius(int bodyRadius) { + this.bodyRadius = bodyRadius; + } + + public int getTtlTicks() { + return ttlTicks; + } + + public void setTtlTicks(int ttlTicks) { + this.ttlTicks = ttlTicks; + } + + public float getTerrainFriction() { + return terrainFriction; + } + + public void setTerrainFriction(float terrainFriction) { + this.terrainFriction = terrainFriction; + } + + public float getTerrainRestitution() { + return terrainRestitution; + } + + public void setTerrainRestitution(float terrainRestitution) { + this.terrainRestitution = terrainRestitution; + } + + public void copyTo(@Nonnull PhysicsSpaceSettings settings) { + copyTo(settings.getPhysicsChunkTerrainSettings()); + } + + public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { + settings.setTerrainMode(terrainMode); + settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); + settings.setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); + settings.setTerrainRadius(radius); + settings.setBodyTerrainRadius(bodyRadius); + settings.setTerrainTtlTicks(ttlTicks); + settings.setTerrainMaterial(terrainFriction, terrainRestitution); + } + + public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { + copyTo((PhysicsChunkTerrainSettings) settings); + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsComponentTypes.physicsChunkTerrainComponentType(); + } + + @Nonnull + @Override + public PhysicsChunkTerrainComponent clone() { + return new PhysicsChunkTerrainComponent(terrainMode, + entityChunkBoundaryMode, + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java index 9c242481..422e8ef4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java @@ -1,110 +1,28 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; -import java.util.Objects; import javax.annotation.Nonnull; /** - * Authored PhysicsChunk terrain streaming settings for one PhysicsStore space entity. + * @deprecated Use {@link PhysicsChunkTerrainComponent}. The serialized component name remains + * {@code WorldCollision} for saved-world compatibility. */ -public final class WorldCollisionComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - WorldCollisionComponent.class, - WorldCollisionComponent::new) - .append(new KeyedCodec<>("Mode", new EnumCodec<>(PhysicsChunkTerrainMode.class), false), - (component, value) -> component.terrainMode = value != null - ? value - : PhysicsChunkTerrainMode.NONE, - WorldCollisionComponent::getTerrainMode) - .add() - .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), - (component, value) -> component.nativeVoxelTerrainEnabled = value != null && value, - WorldCollisionComponent::isNativeVoxelTerrainEnabled) - .add() - .append(new KeyedCodec<>("EntityChunkBoundaryMode", - new EnumCodec<>(EntityChunkBoundaryMode.class), - false), - (component, value) -> component.entityChunkBoundaryMode = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - WorldCollisionComponent::getEntityChunkBoundaryMode) - .add() - .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), - (component, value) -> component.radius = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, - WorldCollisionComponent::getRadius) - .add() - .append(new KeyedCodec<>("BodyRadius", Codec.INTEGER, false), - (component, value) -> component.bodyRadius = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, - WorldCollisionComponent::getBodyRadius) - .add() - .append(new KeyedCodec<>("TtlTicks", Codec.INTEGER, false), - (component, value) -> component.ttlTicks = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - WorldCollisionComponent::getTtlTicks) - .add() - .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), - (component, value) -> component.terrainFriction = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - WorldCollisionComponent::getTerrainFriction) - .add() - .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), - (component, value) -> component.terrainRestitution = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, - WorldCollisionComponent::getTerrainRestitution) - .add() - .build(); - - @Nonnull - private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; - @Nonnull - private EntityChunkBoundaryMode entityChunkBoundaryMode = - PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - private boolean nativeVoxelTerrainEnabled = - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; - private int radius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; - private int bodyRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; - private int ttlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; - private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; +@Deprecated(forRemoval = false) +public class WorldCollisionComponent extends PhysicsChunkTerrainComponent { public WorldCollisionComponent() { } public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainSettings settings) { - this(settings.getTerrainMode(), - settings.getEntityChunkBoundaryMode(), - settings.isNativeVoxelTerrainEnabled(), - settings.getTerrainRadius(), - settings.getBodyTerrainRadius(), - settings.getTerrainTtlTicks(), - settings.getTerrainFriction(), - settings.getTerrainRestitution()); + super(settings); } public WorldCollisionComponent(@Nonnull PhysicsWorldCollisionSettings settings) { - this((PhysicsChunkTerrainSettings) settings); + super(settings); } public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, @@ -114,8 +32,7 @@ public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, int ttlTicks, float terrainFriction, float terrainRestitution) { - this(terrainMode, - PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + super(terrainMode, nativeVoxelTerrainEnabled, radius, bodyRadius, @@ -132,21 +49,16 @@ public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, int ttlTicks, float terrainFriction, float terrainRestitution) { - this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); - this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, - "entityChunkBoundaryMode"); - this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; - this.radius = radius; - this.bodyRadius = bodyRadius; - this.ttlTicks = ttlTicks; - this.terrainFriction = terrainFriction; - this.terrainRestitution = terrainRestitution; + super(terrainMode, + entityChunkBoundaryMode, + nativeVoxelTerrainEnabled, + radius, + bodyRadius, + ttlTicks, + terrainFriction, + terrainRestitution); } - /** - * @deprecated Use {@link #WorldCollisionComponent(PhysicsChunkTerrainMode, boolean, int, int, int, float, float)}. - */ - @Deprecated(forRemoval = false) public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, boolean nativeVoxelTerrainEnabled, int radius, @@ -154,7 +66,7 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, int ttlTicks, float terrainFriction, float terrainRestitution) { - this(mode.toPhysicsChunkTerrainMode(), + super(mode, nativeVoxelTerrainEnabled, radius, bodyRadius, @@ -163,10 +75,6 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, terrainRestitution); } - /** - * @deprecated Use {@link #WorldCollisionComponent(PhysicsChunkTerrainMode, EntityChunkBoundaryMode, boolean, int, int, int, float, float)}. - */ - @Deprecated(forRemoval = false) public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, @@ -175,7 +83,7 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, int ttlTicks, float terrainFriction, float terrainRestitution) { - this(mode.toPhysicsChunkTerrainMode(), + super(mode, entityChunkBoundaryMode, nativeVoxelTerrainEnabled, radius, @@ -185,124 +93,16 @@ public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, terrainRestitution); } - @Nonnull - public PhysicsChunkTerrainMode getTerrainMode() { - return terrainMode; - } - - public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { - this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); - } - - /** - * @deprecated Use {@link #getTerrainMode()}. - */ - @Deprecated(forRemoval = false) - @Nonnull - public WorldCollisionMode getMode() { - return terrainMode.toWorldCollisionMode(); - } - - /** - * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. - */ - @Deprecated(forRemoval = false) - public void setMode(@Nonnull WorldCollisionMode mode) { - setTerrainMode(mode.toPhysicsChunkTerrainMode()); - } - - @Nonnull - public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { - return entityChunkBoundaryMode; - } - - public void setEntityChunkBoundaryMode( - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { - this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, - "entityChunkBoundaryMode"); - } - - public boolean isNativeVoxelTerrainEnabled() { - return nativeVoxelTerrainEnabled; - } - - public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { - this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; - } - - public int getRadius() { - return radius; - } - - public void setRadius(int radius) { - this.radius = radius; - } - - public int getBodyRadius() { - return bodyRadius; - } - - public void setBodyRadius(int bodyRadius) { - this.bodyRadius = bodyRadius; - } - - public int getTtlTicks() { - return ttlTicks; - } - - public void setTtlTicks(int ttlTicks) { - this.ttlTicks = ttlTicks; - } - - public float getTerrainFriction() { - return terrainFriction; - } - - public void setTerrainFriction(float terrainFriction) { - this.terrainFriction = terrainFriction; - } - - public float getTerrainRestitution() { - return terrainRestitution; - } - - public void setTerrainRestitution(float terrainRestitution) { - this.terrainRestitution = terrainRestitution; - } - - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getPhysicsChunkTerrainSettings()); - } - - public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { - settings.setTerrainMode(terrainMode); - settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); - settings.setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); - settings.setTerrainRadius(radius); - settings.setBodyTerrainRadius(bodyRadius); - settings.setTerrainTtlTicks(ttlTicks); - settings.setTerrainMaterial(terrainFriction, terrainRestitution); - } - - public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { - copyTo((PhysicsChunkTerrainSettings) settings); - } - - @Nonnull - public static ComponentType getComponentType() { - return PhysicsComponentTypes.worldCollisionComponentType(); - } - @Nonnull @Override public WorldCollisionComponent clone() { - return new WorldCollisionComponent(terrainMode, - entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, - radius, - bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); + return new WorldCollisionComponent(getTerrainMode(), + getEntityChunkBoundaryMode(), + isNativeVoxelTerrainEnabled(), + getRadius(), + getBodyRadius(), + getTtlTicks(), + getTerrainFriction(), + getTerrainRestitution()); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index 08b8dd82..2eaf1a17 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -65,7 +65,7 @@ public static Ref resolveRef(@Nonnull Store store, public static Holder spaceHolder(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent terrainSettings, + @Nonnull PhysicsChunkTerrainComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -120,7 +120,7 @@ public static Holder terrainColliderHolder(@Nonnull Store holder, @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent terrainSettings, + @Nonnull PhysicsChunkTerrainComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -129,7 +129,7 @@ public static void addSpaceComponents(@Nonnull Holder holder, Objects.requireNonNull(holder, "holder") .addComponent(SpaceComponent.getComponentType(), Objects.requireNonNull(space, "space").clone()); - holder.addComponent(WorldCollisionComponent.getComponentType(), + holder.addComponent(PhysicsChunkTerrainComponent.getComponentType(), Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); addSpaceSettingsComponents(holder, solverSettings, @@ -188,7 +188,7 @@ public static void addBodyComponents(@Nonnull Holder holder, public static void putSpaceComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull SpaceComponent space, - @Nonnull WorldCollisionComponent terrainSettings, + @Nonnull PhysicsChunkTerrainComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -201,7 +201,7 @@ public static void putSpaceComponents(@Nonnull Store store, SpaceComponent.getComponentType(), Objects.requireNonNull(space, "space").clone()); checkedStore.putComponent(ref, - WorldCollisionComponent.getComponentType(), + PhysicsChunkTerrainComponent.getComponentType(), Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); putSpaceSettingsComponents(checkedStore, ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index b762cd24..a08ae1e5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.WorldCollisionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Collection; import java.util.List; @@ -119,8 +119,8 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, return null; } PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - WorldCollisionComponent terrainSettings = checkedStore.getComponent(checkedRef, - WorldCollisionComponent.getComponentType()); + PhysicsChunkTerrainComponent terrainSettings = checkedStore.getComponent(checkedRef, + PhysicsChunkTerrainComponent.getComponentType()); if (terrainSettings != null) { terrainSettings.copyTo(settings); } From 3f6f481f962a51df428981c73575c9285f8971e4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 20:55:25 +0200 Subject: [PATCH 360/534] refactor(core): keep physics chunk lifecycle internal Signed-off-by: Blovien --- .../internal/modules/physicschunk/PhysicsChunkModule.java | 5 ++--- .../plugin/modules/physicschunk/PhysicsChunkTerrain.java | 8 -------- .../modules/physicschunk/PhysicsWorldCollision.java | 8 -------- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java index 339c4ea6..6b63dd86 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -28,13 +27,13 @@ protected void setup() { PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); PhysicsChunkCommandContributions.register(); - PhysicsChunkTerrain.enableModule(); + PhysicsChunkLifecycle.enable(); LOGGER.at(Level.INFO).log("Impulse PhysicsChunk terrain producer enabled."); } @Override protected void shutdown() { - PhysicsChunkTerrain.disableModule(); + PhysicsChunkLifecycle.disable(); PhysicsChunkCommandContributions.unregister(); PhysicsChunkTypes.clearEntityStoreResourceTypes(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 5a9ca6e2..dc005c03 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -30,14 +30,6 @@ public final class PhysicsChunkTerrain { private PhysicsChunkTerrain() { } - public static void enableModule() { - PhysicsChunkLifecycle.enable(); - } - - public static void disableModule() { - PhysicsChunkLifecycle.disable(); - } - public static boolean isModuleEnabled() { return PhysicsChunkLifecycle.isEnabled(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index 4c2815ca..c6f1c902 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -16,14 +16,6 @@ public final class PhysicsWorldCollision { private PhysicsWorldCollision() { } - public static void enableModule() { - PhysicsChunkTerrain.enableModule(); - } - - public static void disableModule() { - PhysicsChunkTerrain.disableModule(); - } - public static boolean isModuleEnabled() { return PhysicsChunkTerrain.isModuleEnabled(); } From 011eb185ed96187ad94e5775f44604e08154b472 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 20:56:54 +0200 Subject: [PATCH 361/534] refactor(core): internalize physics chunk type registration Signed-off-by: Blovien --- .../internal/modules/physicschunk/PhysicsChunkModule.java | 1 - .../modules/physicschunk/PhysicsChunkTypes.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{plugin => internal}/modules/physicschunk/PhysicsChunkTypes.java (93%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java index 6b63dd86..94956898 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTypes; import java.util.logging.Level; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java index c42a990b..c40b60b7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; @@ -10,7 +10,7 @@ /** * Registered EntityStore type handles owned by the PhysicsChunk integration module. */ -public final class PhysicsChunkTypes { +final class PhysicsChunkTypes { private PhysicsChunkTypes() { } From b1547094788098901d1c28c939e5bc720b36b0dc Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 21:01:48 +0200 Subject: [PATCH 362/534] refactor(core): internalize physics entity type registration Signed-off-by: Blovien --- .../physicsentity/PhysicsEntityModule.java | 13 +- .../PhysicsEntityTypeRegistry.java | 159 ++++++++++++++++++ .../physicsentity/PhysicsEntityTypes.java | 118 ++----------- 3 files changed, 175 insertions(+), 115 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java index cdcabe48..26902045 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands.PhysicsEntityCommandContributions; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import javax.annotation.Nonnull; /** @@ -20,11 +19,11 @@ public PhysicsEntityModule(@Nonnull JavaPluginInit init) { @Override protected void setup() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - PhysicsEntityTypes.registerComponentTypes(entityRegistry); - PhysicsEntityTypes.registerResourceTypes(entityRegistry); - PhysicsEntityTypes.registerEventTypes(entityRegistry); - PhysicsEntityTypes.registerSystemGroups(entityRegistry); - PhysicsEntityTypes.registerSystems(entityRegistry); + PhysicsEntityTypeRegistry.registerComponentTypes(entityRegistry); + PhysicsEntityTypeRegistry.registerResourceTypes(entityRegistry); + PhysicsEntityTypeRegistry.registerEventTypes(entityRegistry); + PhysicsEntityTypeRegistry.registerSystemGroups(entityRegistry); + PhysicsEntityTypeRegistry.registerSystems(entityRegistry); PhysicsEntityCommandContributions.register(); PhysicsEntityLifecycle.enable(); } @@ -33,6 +32,6 @@ protected void setup() { protected void shutdown() { PhysicsEntityLifecycle.disable(); PhysicsEntityCommandContributions.unregister(); - PhysicsEntityTypes.clearEntityStoreTypes(); + PhysicsEntityTypeRegistry.clearEntityStoreTypes(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java new file mode 100644 index 00000000..4da0d8af --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java @@ -0,0 +1,159 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicsentity; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; +import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; +import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Registered EntityStore type handles owned by the PhysicsEntity integration module. + */ +public final class PhysicsEntityTypeRegistry { + + @Nullable + private static ComponentType bodyAttachmentComponentType; + @Nullable + private static ComponentType + generatedVisualProxyComponentType; + @Nullable + private static ResourceType physicsWorldResourceType; + @Nullable + private static WorldEventType + physicsEventFramePublishedEventType; + @Nullable + private static SystemGroup persistenceRestoreGroup; + + private PhysicsEntityTypeRegistry() { + } + + public static void registerComponentTypes(@Nonnull ComponentRegistryProxy registry) { + bodyAttachmentComponentType = registry.registerComponent( + BodyAttachmentComponent.class, + "BodyAttachment", + BodyAttachmentComponent.CODEC); + generatedVisualProxyComponentType = registry.registerComponent( + GeneratedVisualProxyComponent.class, + "GeneratedVisualProxy", + GeneratedVisualProxyComponent.CODEC); + } + + public static void registerResourceTypes(@Nonnull ComponentRegistryProxy registry) { + physicsWorldResourceType = registry.registerResource(PhysicsWorldResource.class, + PhysicsWorldRuntimeResource::new); + PhysicsDebugResource.setResourceType(registry.registerResource(PhysicsDebugResource.class, + PhysicsDebugResource::new)); + PhysicsRuntimeProfilingResource.setResourceType(registry.registerResource( + PhysicsRuntimeProfilingResource.class, + PhysicsRuntimeProfilingResource::new)); + PhysicsProjectionIndexResource.setResourceType(registry.registerResource( + PhysicsProjectionIndexResource.class, + PhysicsProjectionIndexResource::new)); + } + + public static void registerEventTypes(@Nonnull ComponentRegistryProxy registry) { + physicsEventFramePublishedEventType = + registry.registerWorldEventType(PhysicsEventFramePublishedEvent.class); + } + + public static void registerSystemGroups(@Nonnull ComponentRegistryProxy registry) { + persistenceRestoreGroup = registry.registerSystemGroup(); + } + + public static void registerSystems(@Nonnull ComponentRegistryProxy registry) { + registry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); + registry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); + registry.registerSystem(new PhysicsSyncSystem()); + registry.registerSystem(new PhysicsDebugSystem()); + registry.registerSystem(new PhysicsStoreEventPublicationSystem()); + registry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); + } + + public static void clearEntityStoreTypes() { + bodyAttachmentComponentType = null; + generatedVisualProxyComponentType = null; + physicsWorldResourceType = null; + physicsEventFramePublishedEventType = null; + persistenceRestoreGroup = null; + PhysicsDebugResource.clearResourceType(); + PhysicsRuntimeProfilingResource.clearResourceType(); + PhysicsProjectionIndexResource.clearResourceType(); + } + + public static boolean areEntityStoreTypesRegistered() { + return bodyAttachmentComponentType != null + && generatedVisualProxyComponentType != null + && physicsWorldResourceType != null + && physicsEventFramePublishedEventType != null + && persistenceRestoreGroup != null + && PhysicsDebugResource.getResourceType() != null + && PhysicsRuntimeProfilingResource.getResourceType() != null + && PhysicsProjectionIndexResource.getResourceType() != null; + } + + public static boolean isBodyAttachmentComponentTypeRegistered() { + return bodyAttachmentComponentType != null; + } + + public static boolean isGeneratedVisualProxyComponentTypeRegistered() { + return generatedVisualProxyComponentType != null; + } + + @Nonnull + public static ComponentType bodyAttachmentComponentType() { + return requireRegistered(bodyAttachmentComponentType, + "Impulse BodyAttachment component type is not registered"); + } + + @Nonnull + public static ComponentType + generatedVisualProxyComponentType() { + return requireRegistered(generatedVisualProxyComponentType, + "Impulse GeneratedVisualProxy component type is not registered"); + } + + @Nonnull + public static ResourceType physicsWorldResourceType() { + return requireRegistered(physicsWorldResourceType, + "Impulse PhysicsWorld resource type is not registered"); + } + + @Nonnull + public static WorldEventType + physicsEventFramePublishedEventType() { + return requireRegistered(physicsEventFramePublishedEventType, + "Impulse physics event-frame world event type is not registered"); + } + + @Nonnull + public static SystemGroup persistenceRestoreGroup() { + return requireRegistered(persistenceRestoreGroup, + "Impulse PhysicsEntity persistence restore group is not registered"); + } + + @Nonnull + private static T requireRegistered(@Nullable T value, @Nonnull String message) { + if (value == null) { + throw new IllegalStateException(Objects.requireNonNull(message, "message")); + } + return value; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index 43fc2e95..797b869e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -1,159 +1,61 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; -import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.SystemGroup; import com.hypixel.hytale.component.event.WorldEventType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; -import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import java.util.Objects; import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** - * Registered EntityStore type handles for the PhysicsEntity integration module. + * Public EntityStore type handles for the PhysicsEntity integration module. */ public final class PhysicsEntityTypes { - @Nullable - private static ComponentType bodyAttachmentComponentType; - @Nullable - private static ComponentType - generatedVisualProxyComponentType; - @Nullable - private static ResourceType physicsWorldResourceType; - @Nullable - private static WorldEventType - physicsEventFramePublishedEventType; - @Nullable - private static SystemGroup persistenceRestoreGroup; - private PhysicsEntityTypes() { } - public static void registerComponentTypes(@Nonnull ComponentRegistryProxy registry) { - bodyAttachmentComponentType = registry.registerComponent( - BodyAttachmentComponent.class, - "BodyAttachment", - BodyAttachmentComponent.CODEC); - generatedVisualProxyComponentType = registry.registerComponent( - GeneratedVisualProxyComponent.class, - "GeneratedVisualProxy", - GeneratedVisualProxyComponent.CODEC); - } - - public static void registerResourceTypes(@Nonnull ComponentRegistryProxy registry) { - physicsWorldResourceType = registry.registerResource(PhysicsWorldResource.class, - PhysicsWorldRuntimeResource::new); - PhysicsDebugResource.setResourceType(registry.registerResource(PhysicsDebugResource.class, - PhysicsDebugResource::new)); - PhysicsRuntimeProfilingResource.setResourceType(registry.registerResource( - PhysicsRuntimeProfilingResource.class, - PhysicsRuntimeProfilingResource::new)); - PhysicsProjectionIndexResource.setResourceType(registry.registerResource( - PhysicsProjectionIndexResource.class, - PhysicsProjectionIndexResource::new)); - } - - public static void registerEventTypes(@Nonnull ComponentRegistryProxy registry) { - physicsEventFramePublishedEventType = - registry.registerWorldEventType(PhysicsEventFramePublishedEvent.class); - } - - public static void registerSystemGroups(@Nonnull ComponentRegistryProxy registry) { - persistenceRestoreGroup = registry.registerSystemGroup(); - } - - public static void registerSystems(@Nonnull ComponentRegistryProxy registry) { - registry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); - registry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); - registry.registerSystem(new PhysicsSyncSystem()); - registry.registerSystem(new PhysicsDebugSystem()); - registry.registerSystem(new PhysicsStoreEventPublicationSystem()); - registry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); - } - - public static void clearEntityStoreTypes() { - bodyAttachmentComponentType = null; - generatedVisualProxyComponentType = null; - physicsWorldResourceType = null; - physicsEventFramePublishedEventType = null; - persistenceRestoreGroup = null; - PhysicsDebugResource.clearResourceType(); - PhysicsRuntimeProfilingResource.clearResourceType(); - PhysicsProjectionIndexResource.clearResourceType(); - } - public static boolean areEntityStoreTypesRegistered() { - return bodyAttachmentComponentType != null - && generatedVisualProxyComponentType != null - && physicsWorldResourceType != null - && physicsEventFramePublishedEventType != null - && persistenceRestoreGroup != null - && PhysicsDebugResource.getResourceType() != null - && PhysicsRuntimeProfilingResource.getResourceType() != null - && PhysicsProjectionIndexResource.getResourceType() != null; + return PhysicsEntityTypeRegistry.areEntityStoreTypesRegistered(); } public static boolean isBodyAttachmentComponentTypeRegistered() { - return bodyAttachmentComponentType != null; + return PhysicsEntityTypeRegistry.isBodyAttachmentComponentTypeRegistered(); } public static boolean isGeneratedVisualProxyComponentTypeRegistered() { - return generatedVisualProxyComponentType != null; + return PhysicsEntityTypeRegistry.isGeneratedVisualProxyComponentTypeRegistered(); } @Nonnull public static ComponentType bodyAttachmentComponentType() { - return requireRegistered(bodyAttachmentComponentType, - "Impulse BodyAttachment component type is not registered"); + return PhysicsEntityTypeRegistry.bodyAttachmentComponentType(); } @Nonnull public static ComponentType generatedVisualProxyComponentType() { - return requireRegistered(generatedVisualProxyComponentType, - "Impulse GeneratedVisualProxy component type is not registered"); + return PhysicsEntityTypeRegistry.generatedVisualProxyComponentType(); } @Nonnull public static ResourceType physicsWorldResourceType() { - return requireRegistered(physicsWorldResourceType, - "Impulse PhysicsWorld resource type is not registered"); + return PhysicsEntityTypeRegistry.physicsWorldResourceType(); } @Nonnull public static WorldEventType physicsEventFramePublishedEventType() { - return requireRegistered(physicsEventFramePublishedEventType, - "Impulse physics event-frame world event type is not registered"); + return PhysicsEntityTypeRegistry.physicsEventFramePublishedEventType(); } @Nonnull public static SystemGroup persistenceRestoreGroup() { - return requireRegistered(persistenceRestoreGroup, - "Impulse PhysicsEntity persistence restore group is not registered"); - } - - @Nonnull - private static T requireRegistered(@Nullable T value, @Nonnull String message) { - if (value == null) { - throw new IllegalStateException(Objects.requireNonNull(message, "message")); - } - return value; + return PhysicsEntityTypeRegistry.persistenceRestoreGroup(); } } From 64940ffa3afc5cfaaa956cb9d0227bc7a7497f8d Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 21:04:28 +0200 Subject: [PATCH 363/534] fix(core): preserve physics entity type facade compatibility Signed-off-by: Blovien --- .../modules/physicsentity/PhysicsEntityTypes.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index 797b869e..c1bebca7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -11,12 +11,16 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Public EntityStore type handles for the PhysicsEntity integration module. */ public final class PhysicsEntityTypes { + @Nullable + private static ComponentType bodyAttachmentComponentType; + private PhysicsEntityTypes() { } @@ -25,7 +29,8 @@ public static boolean areEntityStoreTypesRegistered() { } public static boolean isBodyAttachmentComponentTypeRegistered() { - return PhysicsEntityTypeRegistry.isBodyAttachmentComponentTypeRegistered(); + return bodyAttachmentComponentType != null + || PhysicsEntityTypeRegistry.isBodyAttachmentComponentTypeRegistered(); } public static boolean isGeneratedVisualProxyComponentTypeRegistered() { @@ -34,6 +39,11 @@ public static boolean isGeneratedVisualProxyComponentTypeRegistered() { @Nonnull public static ComponentType bodyAttachmentComponentType() { + ComponentType compatibilityType = + bodyAttachmentComponentType; + if (compatibilityType != null) { + return compatibilityType; + } return PhysicsEntityTypeRegistry.bodyAttachmentComponentType(); } From 5ac8ac2935552e6ca0946765b53150fa88a09e72 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 21:16:35 +0200 Subject: [PATCH 364/534] refactor(core): internalize physics registration boundaries Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 4 +- .../PhysicsChunkCommandContributions.java | 14 +- .../PhysicsEntityCommandContributions.java | 10 +- .../persistence/PersistentSpaceDto.java | 166 ++++-------- .../PhysicsComponentTypeRegistry.java | 244 ++++++++++++++++++ .../components/PhysicsComponentTypes.java | 155 ++--------- .../physicschunk/PhysicsChunkCommands.java | 36 --- .../PhysicsChunkTerrainComponent.java | 8 + .../physicsentity/PhysicsEntityCommands.java | 29 --- .../plugin/settings/PhysicsSpaceSettings.java | 5 +- .../PhysicsTypeRegistrationApiTest.java | 26 +- 11 files changed, 363 insertions(+), 334 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 656c0395..ff3d8578 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -17,9 +17,9 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; @@ -55,7 +55,7 @@ protected void setup() { PhysicsStoreEarlyPluginProbe.requireAvailable(); ComponentRegistryProxy physicsStoreRegistry = PhysicsStoreRegistration.physicsStoreRegistry(this); - PhysicsComponentTypes.registerComponentTypes(physicsStoreRegistry); + PhysicsComponentTypeRegistry.registerComponentTypes(physicsStoreRegistry); PhysicsStoreRegistration.register(physicsStoreRegistry); ImpulseSubPluginRegistration.register(this); discoverBackends(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java index bb3d4ffd..65fa86d7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java @@ -1,22 +1,30 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCommands; +import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; /** * Command contributions owned by the PhysicsChunk subplugin. */ public final class PhysicsChunkCommandContributions { + private static final String PHYSICS_CHUNK_ROOT_COMMAND_ID = "physicschunk.root"; + private static final String COLLISION_LOD_SETTINGS_COMMAND_ID = + "physicschunk.settings.collision-lod"; + private PhysicsChunkCommandContributions() { } public static void register() { - PhysicsChunkCommands.registerPhysicsChunkCommands( + ImpulseCommandContributionRegistry.addRootAndSettingsSubCommands( + PHYSICS_CHUNK_ROOT_COMMAND_ID, PhysicsChunkCommand::new, + COLLISION_LOD_SETTINGS_COMMAND_ID, CollisionLodSettingsCommand::new); } public static void unregister() { - PhysicsChunkCommands.unregisterPhysicsChunkCommands(); + ImpulseCommandContributionRegistry.removeRootAndSettingsSubCommands( + PHYSICS_CHUNK_ROOT_COMMAND_ID, + COLLISION_LOD_SETTINGS_COMMAND_ID); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java index 5d488af6..a7e439d5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java @@ -1,20 +1,24 @@ package dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityCommands; +import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; /** * Command contributions owned by the PhysicsEntity subplugin. */ public final class PhysicsEntityCommandContributions { + private static final String VISUAL_SETTINGS_COMMAND_ID = "physicsentity.settings.visual"; + private PhysicsEntityCommandContributions() { } public static void register() { - PhysicsEntityCommands.registerPhysicsEntityCommands(VisualSettingsCommand::new); + ImpulseCommandContributionRegistry.addSettingsSubCommand( + VISUAL_SETTINGS_COMMAND_ID, + VisualSettingsCommand::new); } public static void unregister() { - PhysicsEntityCommands.unregisterPhysicsEntityCommands(); + ImpulseCommandContributionRegistry.removeSettingsSubCommand(VISUAL_SETTINGS_COMMAND_ID); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index bc3ae7c8..80c869d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -48,10 +48,10 @@ public final class PersistentSpaceDto { .append(new KeyedCodec<>("WorldCollisionMode", new EnumCodec<>(WorldCollisionMode.class), false), - (dto, value) -> dto.worldCollisionMode = value != null - ? value - : WorldCollisionMode.NONE, - PersistentSpaceDto::getWorldCollisionMode) + (dto, value) -> dto.terrainMode = value != null + ? value.toPhysicsChunkTerrainMode() + : PhysicsChunkTerrainMode.NONE, + PersistentSpaceDto::getPersistedWorldCollisionMode) .add() .append(new KeyedCodec<>("EntityChunkBoundaryMode", new EnumCodec<>(EntityChunkBoundaryMode.class), @@ -66,22 +66,22 @@ public final class PersistentSpaceDto { PersistentSpaceDto::isNativeVoxelTerrainEnabled) .add() .append(new KeyedCodec<>("WorldCollisionRadius", Codec.INTEGER, false), - (dto, value) -> dto.worldCollisionRadius = value != null + (dto, value) -> dto.terrainRadius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, - PersistentSpaceDto::getWorldCollisionRadius) + PersistentSpaceDto::getTerrainRadius) .add() .append(new KeyedCodec<>("WorldCollisionBodyRadius", Codec.INTEGER, false), - (dto, value) -> dto.worldCollisionBodyRadius = value != null + (dto, value) -> dto.bodyTerrainRadius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, - PersistentSpaceDto::getWorldCollisionBodyRadius) + PersistentSpaceDto::getBodyTerrainRadius) .add() .append(new KeyedCodec<>("WorldCollisionTtlTicks", Codec.INTEGER, false), - (dto, value) -> dto.worldCollisionTtlTicks = value != null + (dto, value) -> dto.terrainTtlTicks = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - PersistentSpaceDto::getWorldCollisionTtlTicks) + PersistentSpaceDto::getTerrainTtlTicks) .add() .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), (dto, value) -> dto.terrainFriction = value != null @@ -140,17 +140,17 @@ public final class PersistentSpaceDto { @Nonnull private final Vector3f gravity = new Vector3f(0.0f, -9.81f, 0.0f); @Nonnull - private WorldCollisionMode worldCollisionMode = WorldCollisionMode.NONE; + private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelTerrainEnabled = PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; - private int worldCollisionRadius = + private int terrainRadius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; - private int worldCollisionBodyRadius = + private int bodyTerrainRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; - private int worldCollisionTtlTicks = + private int terrainTtlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; @@ -176,7 +176,7 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this(spaceUuid, backendId, gravity, - WorldCollisionMode.NONE, + PhysicsChunkTerrainMode.NONE, PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, @@ -194,22 +194,22 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, - @Nonnull WorldCollisionMode worldCollisionMode, + @Nonnull PhysicsChunkTerrainMode terrainMode, boolean nativeVoxelTerrainEnabled, - int worldCollisionRadius, - int worldCollisionBodyRadius, - int worldCollisionTtlTicks, + int terrainRadius, + int bodyTerrainRadius, + int terrainTtlTicks, float terrainFriction, float terrainRestitution) { this(spaceUuid, backendId, gravity, - worldCollisionMode, + terrainMode, PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, nativeVoxelTerrainEnabled, - worldCollisionRadius, - worldCollisionBodyRadius, - worldCollisionTtlTicks, + terrainRadius, + bodyTerrainRadius, + terrainTtlTicks, terrainFriction, terrainRestitution, new SolverSettingsComponent(), @@ -223,34 +223,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, @Nonnull PhysicsChunkTerrainMode terrainMode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, int terrainRadius, int bodyTerrainRadius, int terrainTtlTicks, float terrainFriction, - float terrainRestitution) { - this(spaceUuid, - backendId, - gravity, - terrainMode.toWorldCollisionMode(), - nativeVoxelTerrainEnabled, - terrainRadius, - bodyTerrainRadius, - terrainTtlTicks, - terrainFriction, - terrainRestitution); - } - - public PersistentSpaceDto(@Nonnull UUID spaceUuid, - @Nonnull String backendId, - @Nonnull Vector3f gravity, - @Nonnull WorldCollisionMode worldCollisionMode, - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, - int worldCollisionRadius, - int worldCollisionBodyRadius, - int worldCollisionTtlTicks, - float terrainFriction, float terrainRestitution, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @@ -260,14 +238,13 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); this.backendId = Objects.requireNonNull(backendId, "backendId"); this.gravity.set(Objects.requireNonNull(gravity, "gravity")); - this.worldCollisionMode = Objects.requireNonNull(worldCollisionMode, - "worldCollisionMode"); + this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, "entityChunkBoundaryMode"); this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; - this.worldCollisionRadius = worldCollisionRadius; - this.worldCollisionBodyRadius = worldCollisionBodyRadius; - this.worldCollisionTtlTicks = worldCollisionTtlTicks; + this.terrainRadius = terrainRadius; + this.bodyTerrainRadius = bodyTerrainRadius; + this.terrainTtlTicks = terrainTtlTicks; this.terrainFriction = terrainFriction; this.terrainRestitution = terrainRestitution; this.solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); @@ -281,40 +258,6 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, "extensionSettings").clone(); } - public PersistentSpaceDto(@Nonnull UUID spaceUuid, - @Nonnull String backendId, - @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkTerrainMode terrainMode, - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, - int terrainRadius, - int bodyTerrainRadius, - int terrainTtlTicks, - float terrainFriction, - float terrainRestitution, - @Nonnull SolverSettingsComponent solverSettings, - @Nonnull VisualSyncSettingsComponent visualSyncSettings, - @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, - @Nonnull CollisionLodSettingsComponent collisionLodSettings, - @Nonnull ExtensionSettingsComponent extensionSettings) { - this(spaceUuid, - backendId, - gravity, - terrainMode.toWorldCollisionMode(), - entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, - terrainRadius, - bodyTerrainRadius, - terrainTtlTicks, - terrainFriction, - terrainRestitution, - solverSettings, - visualSyncSettings, - visualMaterializationSettings, - collisionLodSettings, - extensionSettings); - } - @Nonnull public UUID getSpaceUuid() { return spaceUuid; @@ -330,14 +273,9 @@ public Vector3f getGravity() { return new Vector3f(gravity); } - @Nonnull - public WorldCollisionMode getWorldCollisionMode() { - return worldCollisionMode; - } - @Nonnull public PhysicsChunkTerrainMode getTerrainMode() { - return worldCollisionMode.toPhysicsChunkTerrainMode(); + return terrainMode; } @Nonnull @@ -349,28 +287,16 @@ public boolean isNativeVoxelTerrainEnabled() { return nativeVoxelTerrainEnabled; } - public int getWorldCollisionRadius() { - return worldCollisionRadius; - } - public int getTerrainRadius() { - return worldCollisionRadius; - } - - public int getWorldCollisionBodyRadius() { - return worldCollisionBodyRadius; + return terrainRadius; } public int getBodyTerrainRadius() { - return worldCollisionBodyRadius; - } - - public int getWorldCollisionTtlTicks() { - return worldCollisionTtlTicks; + return bodyTerrainRadius; } public int getTerrainTtlTicks() { - return worldCollisionTtlTicks; + return terrainTtlTicks; } public float getTerrainFriction() { @@ -386,22 +312,13 @@ public PhysicsChunkTerrainComponent getPhysicsChunkTerrain() { return new PhysicsChunkTerrainComponent(getTerrainMode(), entityChunkBoundaryMode, nativeVoxelTerrainEnabled, - worldCollisionRadius, - worldCollisionBodyRadius, - worldCollisionTtlTicks, + terrainRadius, + bodyTerrainRadius, + terrainTtlTicks, terrainFriction, terrainRestitution); } - /** - * @deprecated Use {@link #getPhysicsChunkTerrain()}. - */ - @Deprecated(forRemoval = false) - @Nonnull - public PhysicsChunkTerrainComponent getWorldCollision() { - return getPhysicsChunkTerrain(); - } - @Nonnull public SolverSettingsComponent getSolverSettings() { return solverSettings.clone(); @@ -444,12 +361,12 @@ public PersistentSpaceDto copy() { return new PersistentSpaceDto(spaceUuid, backendId, gravity, - worldCollisionMode, + terrainMode, entityChunkBoundaryMode, nativeVoxelTerrainEnabled, - worldCollisionRadius, - worldCollisionBodyRadius, - worldCollisionTtlTicks, + terrainRadius, + bodyTerrainRadius, + terrainTtlTicks, terrainFriction, terrainRestitution, solverSettings, @@ -458,4 +375,9 @@ public PersistentSpaceDto copy() { collisionLodSettings, extensionSettings); } + + @Nonnull + private WorldCollisionMode getPersistedWorldCollisionMode() { + return terrainMode.toWorldCollisionMode(); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java new file mode 100644 index 00000000..0184d337 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -0,0 +1,244 @@ +package dev.hytalemodding.impulse.core.internal.registration; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Registered Hytale ECS component type handles for PhysicsStore entities. + */ +public final class PhysicsComponentTypeRegistry { + + @Nullable + private static ComponentType uuidComponentType; + @Nullable + private static ComponentType spaceComponentType; + @Nullable + private static ComponentType bodyComponentType; + @Nullable + private static ComponentType bodyCommandComponentType; + @Nullable + private static ComponentType terrainColliderComponentType; + @Nullable + private static ComponentType physicsChunkTerrainComponentType; + @Nullable + private static ComponentType dynamicsComponentType; + @Nullable + private static ComponentType colliderComponentType; + @Nullable + private static ComponentType shapeComponentType; + @Nullable + private static ComponentType materialComponentType; + @Nullable + private static ComponentType collisionFilterComponentType; + @Nullable + private static ComponentType jointComponentType; + @Nullable + private static ComponentType targetComponentType; + @Nullable + private static ComponentType solverSettingsComponentType; + @Nullable + private static ComponentType visualSyncSettingsComponentType; + @Nullable + private static ComponentType + visualMaterializationSettingsComponentType; + @Nullable + private static ComponentType collisionLodSettingsComponentType; + @Nullable + private static ComponentType extensionSettingsComponentType; + + private PhysicsComponentTypeRegistry() { + } + + public static void registerComponentTypes( + @Nonnull ComponentRegistryProxy registry) { + uuidComponentType = registry.registerComponent( + UuidComponent.class, + "Uuid", + UuidComponent.CODEC); + spaceComponentType = registry.registerComponent( + SpaceComponent.class, + "Space", + SpaceComponent.CODEC); + bodyComponentType = registry.registerComponent( + BodyComponent.class, + "Body", + BodyComponent.CODEC); + bodyCommandComponentType = registry.registerComponent( + BodyCommandComponent.class, + "BodyCommand", + BodyCommandComponent.CODEC); + terrainColliderComponentType = registry.registerComponent( + TerrainColliderComponent.class, + "TerrainCollider", + TerrainColliderComponent.CODEC); + physicsChunkTerrainComponentType = registry.registerComponent( + PhysicsChunkTerrainComponent.class, + "WorldCollision", + PhysicsChunkTerrainComponent.CODEC); + dynamicsComponentType = registry.registerComponent( + DynamicsComponent.class, + "Dynamics", + DynamicsComponent.CODEC); + colliderComponentType = registry.registerComponent( + ColliderComponent.class, + "Collider", + ColliderComponent.CODEC); + shapeComponentType = registry.registerComponent( + ShapeComponent.class, + "Shape", + ShapeComponent.CODEC); + materialComponentType = registry.registerComponent( + MaterialComponent.class, + "Material", + MaterialComponent.CODEC); + collisionFilterComponentType = registry.registerComponent( + CollisionFilterComponent.class, + "CollisionFilter", + CollisionFilterComponent.CODEC); + jointComponentType = registry.registerComponent( + JointComponent.class, + "Joint", + JointComponent.CODEC); + targetComponentType = registry.registerComponent( + TargetComponent.class, + "Target", + TargetComponent.CODEC); + solverSettingsComponentType = registry.registerComponent( + SolverSettingsComponent.class, + "SolverSettings", + SolverSettingsComponent.CODEC); + visualSyncSettingsComponentType = registry.registerComponent( + VisualSyncSettingsComponent.class, + "VisualSyncSettings", + VisualSyncSettingsComponent.CODEC); + visualMaterializationSettingsComponentType = registry.registerComponent( + VisualMaterializationSettingsComponent.class, + "VisualMaterializationSettings", + VisualMaterializationSettingsComponent.CODEC); + collisionLodSettingsComponentType = registry.registerComponent( + CollisionLodSettingsComponent.class, + "CollisionLodSettings", + CollisionLodSettingsComponent.CODEC); + extensionSettingsComponentType = registry.registerComponent( + ExtensionSettingsComponent.class, + "ExtensionSettings", + ExtensionSettingsComponent.CODEC); + } + + @Nonnull + public static ComponentType uuidComponentType() { + return uuidComponentType; + } + + @Nonnull + public static ComponentType spaceComponentType() { + return spaceComponentType; + } + + @Nonnull + public static ComponentType bodyComponentType() { + return bodyComponentType; + } + + @Nonnull + public static ComponentType bodyCommandComponentType() { + return bodyCommandComponentType; + } + + @Nonnull + public static ComponentType + terrainColliderComponentType() { + return terrainColliderComponentType; + } + + @Nonnull + public static ComponentType + physicsChunkTerrainComponentType() { + return physicsChunkTerrainComponentType; + } + + @Nonnull + public static ComponentType dynamicsComponentType() { + return dynamicsComponentType; + } + + @Nonnull + public static ComponentType colliderComponentType() { + return colliderComponentType; + } + + @Nonnull + public static ComponentType shapeComponentType() { + return shapeComponentType; + } + + @Nonnull + public static ComponentType materialComponentType() { + return materialComponentType; + } + + @Nonnull + public static ComponentType collisionFilterComponentType() { + return collisionFilterComponentType; + } + + @Nonnull + public static ComponentType jointComponentType() { + return jointComponentType; + } + + @Nonnull + public static ComponentType targetComponentType() { + return targetComponentType; + } + + @Nonnull + public static ComponentType solverSettingsComponentType() { + return solverSettingsComponentType; + } + + @Nonnull + public static ComponentType + visualSyncSettingsComponentType() { + return visualSyncSettingsComponentType; + } + + @Nonnull + public static ComponentType + visualMaterializationSettingsComponentType() { + return visualMaterializationSettingsComponentType; + } + + @Nonnull + public static ComponentType + collisionLodSettingsComponentType() { + return collisionLodSettingsComponentType; + } + + @Nonnull + public static ComponentType + extensionSettingsComponentType() { + return extensionSettingsComponentType; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 904cfbff..346b3452 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -1,228 +1,113 @@ package dev.hytalemodding.impulse.core.plugin.components; -import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** - * Registered Hytale ECS component type handles for PhysicsStore entities. + * Public Hytale ECS component type handles for PhysicsStore entities. */ public final class PhysicsComponentTypes { - @Nullable - private static ComponentType uuidComponentType; - @Nullable - private static ComponentType spaceComponentType; - @Nullable - private static ComponentType bodyComponentType; - @Nullable - private static ComponentType bodyCommandComponentType; - @Nullable - private static ComponentType terrainColliderComponentType; - @Nullable - private static ComponentType physicsChunkTerrainComponentType; - @Nullable - private static ComponentType dynamicsComponentType; - @Nullable - private static ComponentType colliderComponentType; - @Nullable - private static ComponentType shapeComponentType; - @Nullable - private static ComponentType materialComponentType; - @Nullable - private static ComponentType collisionFilterComponentType; - @Nullable - private static ComponentType jointComponentType; - @Nullable - private static ComponentType targetComponentType; - @Nullable - private static ComponentType solverSettingsComponentType; - @Nullable - private static ComponentType visualSyncSettingsComponentType; - @Nullable - private static ComponentType - visualMaterializationSettingsComponentType; - @Nullable - private static ComponentType collisionLodSettingsComponentType; - @Nullable - private static ComponentType extensionSettingsComponentType; - private PhysicsComponentTypes() { } - public static void registerComponentTypes( - @Nonnull ComponentRegistryProxy registry) { - uuidComponentType = registry.registerComponent( - UuidComponent.class, - "Uuid", - UuidComponent.CODEC); - spaceComponentType = registry.registerComponent( - SpaceComponent.class, - "Space", - SpaceComponent.CODEC); - bodyComponentType = registry.registerComponent( - BodyComponent.class, - "Body", - BodyComponent.CODEC); - bodyCommandComponentType = registry.registerComponent( - BodyCommandComponent.class, - "BodyCommand", - BodyCommandComponent.CODEC); - terrainColliderComponentType = registry.registerComponent( - TerrainColliderComponent.class, - "TerrainCollider", - TerrainColliderComponent.CODEC); - physicsChunkTerrainComponentType = registry.registerComponent( - PhysicsChunkTerrainComponent.class, - "WorldCollision", - PhysicsChunkTerrainComponent.CODEC); - dynamicsComponentType = registry.registerComponent( - DynamicsComponent.class, - "Dynamics", - DynamicsComponent.CODEC); - colliderComponentType = registry.registerComponent( - ColliderComponent.class, - "Collider", - ColliderComponent.CODEC); - shapeComponentType = registry.registerComponent( - ShapeComponent.class, - "Shape", - ShapeComponent.CODEC); - materialComponentType = registry.registerComponent( - MaterialComponent.class, - "Material", - MaterialComponent.CODEC); - collisionFilterComponentType = registry.registerComponent( - CollisionFilterComponent.class, - "CollisionFilter", - CollisionFilterComponent.CODEC); - jointComponentType = registry.registerComponent( - JointComponent.class, - "Joint", - JointComponent.CODEC); - targetComponentType = registry.registerComponent( - TargetComponent.class, - "Target", - TargetComponent.CODEC); - solverSettingsComponentType = registry.registerComponent( - SolverSettingsComponent.class, - "SolverSettings", - SolverSettingsComponent.CODEC); - visualSyncSettingsComponentType = registry.registerComponent( - VisualSyncSettingsComponent.class, - "VisualSyncSettings", - VisualSyncSettingsComponent.CODEC); - visualMaterializationSettingsComponentType = registry.registerComponent( - VisualMaterializationSettingsComponent.class, - "VisualMaterializationSettings", - VisualMaterializationSettingsComponent.CODEC); - collisionLodSettingsComponentType = registry.registerComponent( - CollisionLodSettingsComponent.class, - "CollisionLodSettings", - CollisionLodSettingsComponent.CODEC); - extensionSettingsComponentType = registry.registerComponent( - ExtensionSettingsComponent.class, - "ExtensionSettings", - ExtensionSettingsComponent.CODEC); - } - @Nonnull public static ComponentType uuidComponentType() { - return uuidComponentType; + return PhysicsComponentTypeRegistry.uuidComponentType(); } @Nonnull public static ComponentType spaceComponentType() { - return spaceComponentType; + return PhysicsComponentTypeRegistry.spaceComponentType(); } @Nonnull public static ComponentType bodyComponentType() { - return bodyComponentType; + return PhysicsComponentTypeRegistry.bodyComponentType(); } @Nonnull public static ComponentType bodyCommandComponentType() { - return bodyCommandComponentType; + return PhysicsComponentTypeRegistry.bodyCommandComponentType(); } @Nonnull public static ComponentType terrainColliderComponentType() { - return terrainColliderComponentType; + return PhysicsComponentTypeRegistry.terrainColliderComponentType(); } @Nonnull public static ComponentType physicsChunkTerrainComponentType() { - return physicsChunkTerrainComponentType; + return PhysicsComponentTypeRegistry.physicsChunkTerrainComponentType(); } @Nonnull public static ComponentType dynamicsComponentType() { - return dynamicsComponentType; + return PhysicsComponentTypeRegistry.dynamicsComponentType(); } @Nonnull public static ComponentType colliderComponentType() { - return colliderComponentType; + return PhysicsComponentTypeRegistry.colliderComponentType(); } @Nonnull public static ComponentType shapeComponentType() { - return shapeComponentType; + return PhysicsComponentTypeRegistry.shapeComponentType(); } @Nonnull public static ComponentType materialComponentType() { - return materialComponentType; + return PhysicsComponentTypeRegistry.materialComponentType(); } @Nonnull public static ComponentType collisionFilterComponentType() { - return collisionFilterComponentType; + return PhysicsComponentTypeRegistry.collisionFilterComponentType(); } @Nonnull public static ComponentType jointComponentType() { - return jointComponentType; + return PhysicsComponentTypeRegistry.jointComponentType(); } @Nonnull public static ComponentType targetComponentType() { - return targetComponentType; + return PhysicsComponentTypeRegistry.targetComponentType(); } @Nonnull public static ComponentType solverSettingsComponentType() { - return solverSettingsComponentType; + return PhysicsComponentTypeRegistry.solverSettingsComponentType(); } @Nonnull public static ComponentType visualSyncSettingsComponentType() { - return visualSyncSettingsComponentType; + return PhysicsComponentTypeRegistry.visualSyncSettingsComponentType(); } @Nonnull public static ComponentType visualMaterializationSettingsComponentType() { - return visualMaterializationSettingsComponentType; + return PhysicsComponentTypeRegistry.visualMaterializationSettingsComponentType(); } @Nonnull public static ComponentType collisionLodSettingsComponentType() { - return collisionLodSettingsComponentType; + return PhysicsComponentTypeRegistry.collisionLodSettingsComponentType(); } @Nonnull public static ComponentType extensionSettingsComponentType() { - return extensionSettingsComponentType; + return PhysicsComponentTypeRegistry.extensionSettingsComponentType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java deleted file mode 100644 index f23c573e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCommands.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -import com.hypixel.hytale.server.core.command.system.AbstractCommand; -import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; -import java.util.Objects; -import java.util.function.Supplier; -import javax.annotation.Nonnull; - -/** - * Public command contribution endpoint for the physics chunk module. - */ -public final class PhysicsChunkCommands { - - private static final String PHYSICS_CHUNK_ROOT_COMMAND_ID = "physicschunk.root"; - private static final String COLLISION_LOD_SETTINGS_COMMAND_ID = - "physicschunk.settings.collision-lod"; - - private PhysicsChunkCommands() { - } - - public static void registerPhysicsChunkCommands( - @Nonnull Supplier physicsChunkCommand, - @Nonnull Supplier collisionLodSettingsCommand) { - ImpulseCommandContributionRegistry.addRootAndSettingsSubCommands( - PHYSICS_CHUNK_ROOT_COMMAND_ID, - Objects.requireNonNull(physicsChunkCommand, "physicsChunkCommand"), - COLLISION_LOD_SETTINGS_COMMAND_ID, - Objects.requireNonNull(collisionLodSettingsCommand, "collisionLodSettingsCommand")); - } - - public static void unregisterPhysicsChunkCommands() { - ImpulseCommandContributionRegistry.removeRootAndSettingsSubCommands( - PHYSICS_CHUNK_ROOT_COMMAND_ID, - COLLISION_LOD_SETTINGS_COMMAND_ID); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java index 5d5104fa..42f4a5c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java @@ -103,6 +103,10 @@ public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainSettings setting settings.getTerrainRestitution()); } + /** + * @deprecated Use {@link #PhysicsChunkTerrainComponent(PhysicsChunkTerrainSettings)}. + */ + @Deprecated(forRemoval = false) public PhysicsChunkTerrainComponent(@Nonnull PhysicsWorldCollisionSettings settings) { this((PhysicsChunkTerrainSettings) settings); } @@ -284,6 +288,10 @@ public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { settings.setTerrainMaterial(terrainFriction, terrainRestitution); } + /** + * @deprecated Use {@link #copyTo(PhysicsChunkTerrainSettings)}. + */ + @Deprecated(forRemoval = false) public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { copyTo((PhysicsChunkTerrainSettings) settings); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java deleted file mode 100644 index 2894b219..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityCommands.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; - -import com.hypixel.hytale.server.core.command.system.AbstractCommand; -import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; -import java.util.Objects; -import java.util.function.Supplier; -import javax.annotation.Nonnull; - -/** - * Public command contribution endpoint for the PhysicsEntity module. - */ -public final class PhysicsEntityCommands { - - private static final String VISUAL_SETTINGS_COMMAND_ID = "physicsentity.settings.visual"; - - private PhysicsEntityCommands() { - } - - public static void registerPhysicsEntityCommands( - @Nonnull Supplier visualSettingsCommand) { - ImpulseCommandContributionRegistry.addSettingsSubCommand( - VISUAL_SETTINGS_COMMAND_ID, - Objects.requireNonNull(visualSettingsCommand, "visualSettingsCommand")); - } - - public static void unregisterPhysicsEntityCommands() { - ImpulseCommandContributionRegistry.removeSettingsSubCommand(VISUAL_SETTINGS_COMMAND_ID); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index 6d74e573..bfd3517e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -20,10 +20,11 @@ * which keeps Impulse fully opt-in: no terrain bodies are created unless the integrator * explicitly opts in.

        */ +@SuppressWarnings("deprecation") public class PhysicsSpaceSettings { @Nonnull - private final PhysicsWorldCollisionSettings physicsChunkTerrainSettings; + private final PhysicsChunkTerrainSettings physicsChunkTerrainSettings; @Nonnull private final PhysicsVisualSyncSettings visualSyncSettings; @Nonnull @@ -72,7 +73,7 @@ public PhysicsChunkTerrainSettings getPhysicsChunkTerrainSettings() { @Deprecated(forRemoval = false) @Nonnull public PhysicsWorldCollisionSettings getWorldCollisionSettings() { - return physicsChunkTerrainSettings; + return (PhysicsWorldCollisionSettings) physicsChunkTerrainSettings; } /** diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java index db01d12a..7df0adb9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java @@ -6,6 +6,8 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; @@ -14,14 +16,18 @@ class PhysicsTypeRegistrationApiTest { @Test - void typeRegistriesExposeCentralRegistrationWithoutPublicSetters() throws NoSuchMethodException { - assertNotNull(PhysicsComponentTypes.class.getDeclaredMethod("registerComponentTypes", + void internalRegistriesOwnRegistrationWithoutPublicTypeSetters() throws NoSuchMethodException { + assertNotNull(PhysicsComponentTypeRegistry.class.getDeclaredMethod("registerComponentTypes", ComponentRegistryProxy.class)); assertNotNull(PhysicsResourceTypes.class.getDeclaredMethod("registerResourceTypes", ComponentRegistryProxy.class)); assertFalse(hasPublicSetter(PhysicsComponentTypes.class)); assertFalse(hasPublicSetter(PhysicsResourceTypes.class)); + assertFalse(hasPublicRegistrationMethod(PhysicsComponentTypes.class)); + assertFalse(hasPublicRegistrationMethod(PhysicsEntityTypes.class)); + assertFalse(hasPublicMethodNamed(PhysicsChunkTerrain.class, "enableModule")); + assertFalse(hasPublicMethodNamed(PhysicsChunkTerrain.class, "disableModule")); } private static boolean hasPublicSetter(Class type) { @@ -30,4 +36,20 @@ private static boolean hasPublicSetter(Class type) { .map(Method::getName) .anyMatch(name -> name.startsWith("set")); } + + private static boolean hasPublicRegistrationMethod(Class type) { + return Arrays.stream(type.getDeclaredMethods()) + .filter(method -> Modifier.isPublic(method.getModifiers())) + .map(Method::getName) + .anyMatch(name -> name.startsWith("register") + || name.startsWith("unregister") + || name.startsWith("clear")); + } + + private static boolean hasPublicMethodNamed(Class type, String name) { + return Arrays.stream(type.getDeclaredMethods()) + .filter(method -> Modifier.isPublic(method.getModifiers())) + .map(Method::getName) + .anyMatch(name::equals); + } } From 6a95077ccc0814cf8814911ac8b38b03e28f9c4e Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 21:36:45 +0200 Subject: [PATCH 365/534] refactor(core): internalize control module registration Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 2 +- .../modules/control/ControlLifecycle.java | 47 +++++++++++--- .../modules/control/ControlModule.java} | 18 ++---- .../modules/control/ControlTypeRegistry.java | 62 +++++++++++++++++++ .../PhysicsControlSessionComponent.java | 20 +----- .../control/ImpulseControllableComponent.java | 22 +------ 6 files changed, 111 insertions(+), 60 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{plugin/modules/control/ImpulseControlPlugin.java => internal/modules/control/ControlModule.java} (60%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlTypeRegistry.java diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index 26f1d353..33e685fe 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -106,7 +106,7 @@ hytaleTools { subPlugin ( "ImpulseControl", - "dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControlPlugin", + "dev.hytalemodding.impulse.core.internal.modules.control.ControlModule", false, /* disabledByDefault */ false /* includeAssetPack */ ) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java index dcdeadd2..4b6cecf6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.control; +import com.hypixel.hytale.assetstore.AssetRegistry; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -17,6 +18,8 @@ import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -114,23 +117,47 @@ private static void cleanupStore(@Nonnull Store store, return; } - CompletableFuture cleanup = new CompletableFuture<>(); + // PluginManager.unload holds the asset write lock while world ticks drain queued + // tasks under the read lock, so waiting here would stall until the timeout. + boolean waitForCleanup = !isAssetWriteLockHeldByCurrentThread(); + CompletableFuture cleanup = waitForCleanup ? new CompletableFuture<>() : null; try { - world.execute(() -> { - try { - cleanupStoreOnWorldThread(store, controllableType, sessionType); - cleanup.complete(null); - } catch (Throwable throwable) { - cleanup.completeExceptionally(throwable); - } - }); + world.execute(() -> cleanupStoreSafely(store, controllableType, sessionType, cleanup)); } catch (RuntimeException exception) { if (isWorldTaskRejection(exception)) { return; } throw exception; } - cleanup.orTimeout(CLEANUP_TIMEOUT_SECONDS, TimeUnit.SECONDS).join(); + + if (cleanup != null) { + cleanup.orTimeout(CLEANUP_TIMEOUT_SECONDS, TimeUnit.SECONDS).join(); + } + } + + private static boolean isAssetWriteLockHeldByCurrentThread() { + ReadWriteLock lock = AssetRegistry.ASSET_LOCK; + return lock instanceof ReentrantReadWriteLock reentrantLock + && reentrantLock.isWriteLockedByCurrentThread(); + } + + private static void cleanupStoreSafely(@Nonnull Store store, + @Nullable ComponentType controllableType, + @Nullable ComponentType sessionType, + @Nullable CompletableFuture completion) { + try { + cleanupStoreOnWorldThread(store, controllableType, sessionType); + if (completion != null) { + completion.complete(null); + } + } catch (RuntimeException exception) { + if (completion != null) { + completion.completeExceptionally(exception); + } else { + LOGGER.at(Level.WARNING).log("Failed to clean Impulse control components: %s", + exception.getMessage()); + } + } } private static boolean isWorldTaskRejection(@Nonnull RuntimeException exception) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControlPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlModule.java similarity index 60% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControlPlugin.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlModule.java index 638e1c8c..64cb4a12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControlPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlModule.java @@ -1,11 +1,9 @@ -package dev.hytalemodding.impulse.core.plugin.modules.control; +package dev.hytalemodding.impulse.core.internal.modules.control; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControllableLifecycleSystem; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlRuntimeHolderSystem; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanupSystem; @@ -15,21 +13,16 @@ /** * Subplugin that enables Impulse kinematic control sessions. */ -public final class ImpulseControlPlugin extends JavaPlugin { +public final class ControlModule extends JavaPlugin { - public ImpulseControlPlugin(@Nonnull JavaPluginInit init) { + public ControlModule(@Nonnull JavaPluginInit init) { super(init); } @Override protected void setup() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - ImpulseControllableComponent.setComponentType(entityRegistry.registerComponent( - ImpulseControllableComponent.class, - "ImpulseControllable", - ImpulseControllableComponent.CODEC)); - PhysicsControlSessionComponent.setComponentType(entityRegistry.registerComponent( - PhysicsControlSessionComponent.class, PhysicsControlSessionComponent::new)); + ControlTypeRegistry.registerComponentTypes(entityRegistry); entityRegistry.registerSystem(new PhysicsControlRuntimeHolderSystem()); entityRegistry.registerSystem(new PhysicsControllableLifecycleSystem()); entityRegistry.registerSystem(new PhysicsControlSessionCleanupSystem()); @@ -40,7 +33,6 @@ protected void setup() { @Override protected void shutdown() { ControlLifecycle.disable(); - ImpulseControllableComponent.clearComponentType(); - PhysicsControlSessionComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlTypeRegistry.java new file mode 100644 index 00000000..3e7b6345 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlTypeRegistry.java @@ -0,0 +1,62 @@ +package dev.hytalemodding.impulse.core.internal.modules.control; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.IComponentRegistry; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Internal registration owner for the ImpulseControl subplugin component types. + */ +public final class ControlTypeRegistry { + + @Nullable + private static ComponentType controllableComponentType; + @Nullable + private static ComponentType sessionComponentType; + + private ControlTypeRegistry() { + } + + public static void registerComponentTypes(@Nonnull IComponentRegistry registry) { + controllableComponentType = registry.registerComponent( + ImpulseControllableComponent.class, + "ImpulseControllable", + ImpulseControllableComponent.CODEC); + sessionComponentType = registry.registerComponent( + PhysicsControlSessionComponent.class, + PhysicsControlSessionComponent::new); + } + + public static void clearComponentTypes() { + controllableComponentType = null; + sessionComponentType = null; + } + + public static boolean isControllableComponentTypeRegistered() { + return controllableComponentType != null; + } + + public static boolean isSessionComponentTypeRegistered() { + return sessionComponentType != null; + } + + @Nonnull + public static ComponentType controllableComponentType() { + if (controllableComponentType == null) { + throw new IllegalStateException("Impulse controllable component is not registered"); + } + return controllableComponentType; + } + + @Nonnull + public static ComponentType sessionComponentType() { + if (sessionComponentType == null) { + throw new IllegalStateException("Physics control session component is not registered"); + } + return sessionComponentType; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java index d0a86a73..a4912639 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -15,9 +16,6 @@ @Getter public class PhysicsControlSessionComponent implements Component { - @Nullable - private static ComponentType componentType; - @Nullable private Ref bodyRef; @Nullable @@ -61,25 +59,13 @@ public PhysicsControlSessionComponent(@Nonnull Ref bodyRef, this.active = true; } - public static void setComponentType( - @Nonnull ComponentType type) { - componentType = Objects.requireNonNull(type, "type"); - } - - public static void clearComponentType() { - componentType = null; - } - public static boolean isComponentTypeRegistered() { - return componentType != null; + return ControlTypeRegistry.isSessionComponentTypeRegistered(); } @Nonnull public static ComponentType getComponentType() { - if (componentType == null) { - throw new IllegalStateException("Physics control session component is not registered"); - } - return componentType; + return ControlTypeRegistry.sessionComponentType(); } public void deactivate() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponent.java index 3ef79e4a..aa19fcfb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponent.java @@ -4,9 +4,8 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import java.util.Objects; +import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; import javax.annotation.Nonnull; -import javax.annotation.Nullable; public class ImpulseControllableComponent implements Component { @@ -16,28 +15,13 @@ public class ImpulseControllableComponent implements Component { ImpulseControllableComponent::new) .build(); - @Nullable - private static ComponentType componentType; - - public static void setComponentType( - @Nonnull ComponentType type) { - componentType = Objects.requireNonNull(type, "type"); - } - - public static void clearComponentType() { - componentType = null; - } - public static boolean isComponentTypeRegistered() { - return componentType != null; + return ControlTypeRegistry.isControllableComponentTypeRegistered(); } @Nonnull public static ComponentType getComponentType() { - if (componentType == null) { - throw new IllegalStateException("Impulse controllable component is not registered"); - } - return componentType; + return ControlTypeRegistry.controllableComponentType(); } @Nonnull From abf3a58e4216b2af338ceb715c408af369b2c582 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 21:41:47 +0200 Subject: [PATCH 366/534] refactor(core): name physics integrations as subplugins Signed-off-by: Blovien --- impulse-core/build.gradle.kts | 4 ++-- .../{PhysicsChunkModule.java => PhysicsChunkSubPlugin.java} | 6 +++--- .../internal/modules/physicschunk/PhysicsChunkTypes.java | 2 +- ...PhysicsEntityModule.java => PhysicsEntitySubPlugin.java} | 6 +++--- .../modules/physicsentity/PhysicsEntityTypeRegistry.java | 2 +- .../plugin/modules/physicsentity/PhysicsEntityTypes.java | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{PhysicsChunkModule.java => PhysicsChunkSubPlugin.java} (86%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/{PhysicsEntityModule.java => PhysicsEntitySubPlugin.java} (85%) diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index 33e685fe..c15aa59c 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -113,14 +113,14 @@ hytaleTools { subPlugin ( "ImpulsePhysicsEntity", - "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityModule", + "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntitySubPlugin", false, /* disabledByDefault */ false /* includeAssetPack */ ) subPlugin ( "ImpulsePhysicsChunk", - "dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkModule", + "dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkSubPlugin", false, /* disabledByDefault */ false /* includeAssetPack */ ) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java similarity index 86% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java index 94956898..bb1d3349 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java @@ -10,13 +10,13 @@ import javax.annotation.Nonnull; /** - * Plugin module that enables Impulse PhysicsChunk terrain integration. + * Bundled subplugin that enables Impulse PhysicsChunk terrain integration. */ -public final class PhysicsChunkModule extends JavaPlugin { +public final class PhysicsChunkSubPlugin extends JavaPlugin { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - public PhysicsChunkModule(@Nonnull JavaPluginInit init) { + public PhysicsChunkSubPlugin(@Nonnull JavaPluginInit init) { super(init); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java index c40b60b7..0935ed60 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java @@ -8,7 +8,7 @@ import javax.annotation.Nonnull; /** - * Registered EntityStore type handles owned by the PhysicsChunk integration module. + * Registered EntityStore type handles owned by the PhysicsChunk subplugin. */ final class PhysicsChunkTypes { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java index 26902045..b39dd2e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java @@ -8,11 +8,11 @@ import javax.annotation.Nonnull; /** - * Plugin module that integrates authoritative PhysicsStore bodies with EntityStore entities. + * Bundled subplugin that integrates authoritative PhysicsStore bodies with EntityStore entities. */ -public final class PhysicsEntityModule extends JavaPlugin { +public final class PhysicsEntitySubPlugin extends JavaPlugin { - public PhysicsEntityModule(@Nonnull JavaPluginInit init) { + public PhysicsEntitySubPlugin(@Nonnull JavaPluginInit init) { super(init); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java index 4da0d8af..5c65639d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java @@ -25,7 +25,7 @@ import javax.annotation.Nullable; /** - * Registered EntityStore type handles owned by the PhysicsEntity integration module. + * Registered EntityStore type handles owned by the PhysicsEntity subplugin. */ public final class PhysicsEntityTypeRegistry { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index c1bebca7..9e63ffff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -14,7 +14,7 @@ import javax.annotation.Nullable; /** - * Public EntityStore type handles for the PhysicsEntity integration module. + * Public EntityStore type handles for the PhysicsEntity subplugin. */ public final class PhysicsEntityTypes { From cc83a81c50745c1042475c8fc041e675a21e7b42 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 21:48:56 +0200 Subject: [PATCH 367/534] refactor(core): mark worldcollision adapters deprecated Signed-off-by: Blovien --- .../modules/physicschunk/PhysicsChunkTerrainBuildStats.java | 2 ++ .../plugin/modules/physicschunk/PhysicsChunkTerrainMode.java | 2 ++ .../modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java | 2 ++ .../plugin/modules/physicschunk/PhysicsChunkTerrainStats.java | 2 ++ 4 files changed, 8 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java index 0cc18932..e6f6c4cc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java @@ -17,6 +17,7 @@ public record PhysicsChunkTerrainBuildStats(int scannedBlocks, int voxelBodies) { @Nonnull + @Deprecated(forRemoval = false) public static PhysicsChunkTerrainBuildStats fromWorldCollisionStats( @Nonnull WorldCollisionBuildStats stats) { return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), @@ -32,6 +33,7 @@ public static PhysicsChunkTerrainBuildStats fromWorldCollisionStats( } @Nonnull + @Deprecated(forRemoval = false) public WorldCollisionBuildStats toWorldCollisionStats() { return new WorldCollisionBuildStats(scannedBlocks, solidBlocks, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java index 46c4f1a5..0647d705 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java @@ -22,11 +22,13 @@ public enum PhysicsChunkTerrainMode { STREAMING; @Nonnull + @Deprecated(forRemoval = false) public WorldCollisionMode toWorldCollisionMode() { return WorldCollisionMode.valueOf(name()); } @Nonnull + @Deprecated(forRemoval = false) public static PhysicsChunkTerrainMode fromWorldCollisionMode( @Nonnull WorldCollisionMode mode) { return valueOf(mode.name()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java index e6dee545..e878196d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java @@ -9,6 +9,7 @@ public record PhysicsChunkTerrainPrewarmStats(int sectionTargets, @Nonnull PhysicsChunkTerrainBuildStats buildStats) { @Nonnull + @Deprecated(forRemoval = false) public static PhysicsChunkTerrainPrewarmStats fromWorldCollisionStats( @Nonnull WorldCollisionPrewarmStats stats) { return new PhysicsChunkTerrainPrewarmStats(stats.sectionTargets(), @@ -16,6 +17,7 @@ public static PhysicsChunkTerrainPrewarmStats fromWorldCollisionStats( } @Nonnull + @Deprecated(forRemoval = false) public WorldCollisionPrewarmStats toWorldCollisionStats() { return new WorldCollisionPrewarmStats(sectionTargets, buildStats.toWorldCollisionStats()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java index f685d8a6..2017e4f0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java @@ -11,6 +11,7 @@ public record PhysicsChunkTerrainStats(int spaces, int shapeTemplates) { @Nonnull + @Deprecated(forRemoval = false) public static PhysicsChunkTerrainStats fromWorldCollisionStats( @Nonnull WorldCollisionStats stats) { return new PhysicsChunkTerrainStats(stats.spaces(), @@ -20,6 +21,7 @@ public static PhysicsChunkTerrainStats fromWorldCollisionStats( } @Nonnull + @Deprecated(forRemoval = false) public WorldCollisionStats toWorldCollisionStats() { return new WorldCollisionStats(spaces, sections, From 47df4073c2a2a205187bb415e2126b898882423d Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 21:53:04 +0200 Subject: [PATCH 368/534] refactor(examples): use physicschunk terrain wording Signed-off-by: Blovien --- .../examples/commands/PhysicsChunkExampleCommand.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index 1c02e561..f63e1aa7 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -26,7 +26,7 @@ import org.joml.Vector3d; /** - * Debug commands for manually building/clearing world voxel collision. + * Debug commands for manually building/clearing PhysicsChunk terrain collision. */ public class PhysicsChunkExampleCommand extends AbstractCommandCollection { @@ -82,7 +82,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, playerPos, radius); - ctx.sender().sendMessage(Message.raw("Built world voxel collision: scanned " + ctx.sender().sendMessage(Message.raw("Built PhysicsChunk terrain collision: scanned " + stats.scannedBlocks() + " blocks, solid " + stats.solidBlocks() + ", culled " + stats.culledInteriorBlocks() @@ -138,7 +138,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, radius, Math.max(0L, world.getTick())); - ctx.sender().sendMessage(Message.raw("Ensured world voxel collision: targets " + ctx.sender().sendMessage(Message.raw("Ensured PhysicsChunk terrain collision: targets " + stats.sectionTargets() + ", bodies " + stats.buildStats().colliderBodies() @@ -174,7 +174,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Store physicsStore = physicsStore(world); int removed = PhysicsChunkTerrain.clearSpace(world, physicsStore, spaceId); ctx.sender().sendMessage(Message.raw("Removed " + removed - + " world voxel collision bodies.")); + + " PhysicsChunk terrain bodies.")); return CompletableFuture.completedFuture(null); } } From 2821fe324dd198cd9042df0e1567c437674b31a5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:02:42 +0200 Subject: [PATCH 369/534] test(core): cover subplugin boundary refactors Signed-off-by: Blovien --- .../ImpulseSubPluginRegistrationTest.java | 20 +++- .../modules/control/ControlLifecycleTest.java | 21 +--- ...ontrollableComponentRegistrationTest.java} | 18 ++-- .../PhysicsControlSessionComponentTest.java | 11 +-- .../PhysicsControlSystemRegistrationTest.java | 55 +++++------ .../PhysicsChunkProfilingResourceTest.java | 2 +- .../PersistentSpaceDtoSettingsTest.java | 84 ++++++++++++++++ .../PhysicsTypeRegistrationApiTest.java | 7 ++ .../WorldCollisionComponentTest.java | 39 ++++++++ .../settings}/PhysicsSpaceSettingsTest.java | 98 +++++-------------- .../ExampleControlTestSupport.java | 23 +++++ .../utils/ExamplePhysicsUtilsTest.java | 16 +-- 12 files changed, 240 insertions(+), 154 deletions(-) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/{plugin/modules/control/ImpulseControllableComponentTest.java => internal/modules/control/ImpulseControllableComponentRegistrationTest.java} (68%) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/{internal/resources => plugin/settings}/PhysicsSpaceSettingsTest.java (80%) create mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java index bc1fda3c..649bc752 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java @@ -33,6 +33,12 @@ void generatedManifestSubPluginsSupportHytalePendingLoadInheritance() throws IOE } assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulseControl"); assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulsePhysicsChunk"); + assertSubPluginMain(parent, + "ImpulsePhysicsEntity", + "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntitySubPlugin"); + assertSubPluginMain(parent, + "ImpulsePhysicsChunk", + "dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkSubPlugin"); } @Test @@ -43,7 +49,7 @@ void preparesEverySubPluginManifestForDynamicLoad() { List.of( manifest(null, "ImpulseControl", - "dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControlPlugin", + "dev.hytalemodding.impulse.core.internal.modules.control.ControlModule", List.of(), false)), false); @@ -80,6 +86,18 @@ private static void assertSubPluginLoadsBefore(@Nonnull PluginManifest parent, throw new AssertionError("Missing subplugin " + subPluginName); } + private static void assertSubPluginMain(@Nonnull PluginManifest parent, + @Nonnull String subPluginName, + @Nonnull String expectedMain) { + for (PluginManifest subPlugin : parent.getSubPlugins()) { + if (subPluginName.equals(subPlugin.getName())) { + assertEquals(expectedMain, subPlugin.getMain()); + return; + } + } + throw new AssertionError("Missing subplugin " + subPluginName); + } + private static PluginManifest manifest(String group, String name, String main, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java index 708b7c95..652400df 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java @@ -25,8 +25,7 @@ class ControlLifecycleTest { @AfterEach void disableLifecycle() { ControlLifecycle.disable(); - ImpulseControllableComponent.clearComponentType(); - PhysicsControlSessionComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); } @Test @@ -46,7 +45,7 @@ void lifecycleGenerationChangesWhenLifecycleIsDisabled() { @Test void disablingLifecycleWithoutRegisteredSessionComponentDoesNotThrow() { - PhysicsControlSessionComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); ControlLifecycle.enable(); assertDoesNotThrow(ControlLifecycle::disable); @@ -76,13 +75,7 @@ void controlSessionsAreAvailableOnlyWhenLifecycleAndComponentTypesAreRegistered( assertFalse(PhysicsControlSessions.isAvailable()); ComponentRegistry registry = new ComponentRegistry<>(); - ImpulseControllableComponent.setComponentType(registry.registerComponent( - ImpulseControllableComponent.class, - "ImpulseControllable", - ImpulseControllableComponent.CODEC)); - PhysicsControlSessionComponent.setComponentType(registry.registerComponent( - PhysicsControlSessionComponent.class, - PhysicsControlSessionComponent::new)); + ControlTypeRegistry.registerComponentTypes(registry); assertTrue(PhysicsControlSessions.isAvailable()); @@ -95,13 +88,7 @@ void controlSessionsAreAvailableOnlyWhenLifecycleAndComponentTypesAreRegistered( void disablingLifecycleSkipsStoresWhoseWorldThreadHasStopped() { ControlLifecycle.enable(); ComponentRegistry registry = new ComponentRegistry<>(); - ImpulseControllableComponent.setComponentType(registry.registerComponent( - ImpulseControllableComponent.class, - "ImpulseControllable", - ImpulseControllableComponent.CODEC)); - PhysicsControlSessionComponent.setComponentType(registry.registerComponent( - PhysicsControlSessionComponent.class, - PhysicsControlSessionComponent::new)); + ControlTypeRegistry.registerComponentTypes(registry); Store store = registry.addStore( new EntityStore(TestInstanceFactory.world("stopped-control-world")), EmptyResourceStorage.get()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseControllableComponentRegistrationTest.java similarity index 68% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponentTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseControllableComponentRegistrationTest.java index 6c0a3623..64deed29 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseControllableComponentRegistrationTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.control; +package dev.hytalemodding.impulse.core.internal.modules.control; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; @@ -8,33 +8,31 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -class ImpulseControllableComponentTest { +class ImpulseControllableComponentRegistrationTest { @AfterEach void clearRegistration() { - ImpulseControllableComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); } @Test - void componentTypeIsOwnedByControlModuleRegistration() { + void componentTypeIsOwnedByControlSubPluginRegistration() { assertFalse(ImpulseControllableComponent.isComponentTypeRegistered()); assertThrows(IllegalStateException.class, ImpulseControllableComponent::getComponentType); ComponentRegistry registry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(registry); ComponentType type = - registry.registerComponent(ImpulseControllableComponent.class, - "ImpulseControllable", - ImpulseControllableComponent.CODEC); - - ImpulseControllableComponent.setComponentType(type); + ImpulseControllableComponent.getComponentType(); assertTrue(ImpulseControllableComponent.isComponentTypeRegistered()); assertSame(type, ImpulseControllableComponent.getComponentType()); - ImpulseControllableComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); assertFalse(ImpulseControllableComponent.isComponentTypeRegistered()); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponentTest.java index 2564d72a..10663e86 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponentTest.java @@ -8,6 +8,7 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -15,22 +16,20 @@ class PhysicsControlSessionComponentTest { @AfterEach void clearRegistration() { - PhysicsControlSessionComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); } @Test void componentTypeCanBeClearedWhenControlModuleUnloads() { ComponentRegistry registry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(registry); ComponentType type = - registry.registerComponent(PhysicsControlSessionComponent.class, - PhysicsControlSessionComponent::new); - - PhysicsControlSessionComponent.setComponentType(type); + PhysicsControlSessionComponent.getComponentType(); assertTrue(PhysicsControlSessionComponent.isComponentTypeRegistered()); assertSame(type, PhysicsControlSessionComponent.getComponentType()); - PhysicsControlSessionComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); assertFalse(PhysicsControlSessionComponent.isComponentTypeRegistered()); assertThrows(IllegalStateException.class, PhysicsControlSessionComponent::getComponentType); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSystemRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSystemRegistrationTest.java index ec360056..fdff2204 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSystemRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSystemRegistrationTest.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; @@ -24,18 +25,21 @@ class PhysicsControlSystemRegistrationTest { @AfterEach void clearRegistrations() { ControlLifecycle.disable(); - ImpulseControllableComponent.clearComponentType(); - PhysicsControlSessionComponent.clearComponentType(); + ControlTypeRegistry.clearComponentTypes(); } @Test void sessionCleanupSystemCapturesCurrentSessionComponentType() { - ComponentType first = registerSessionType(); - PhysicsControlSessionComponent.setComponentType(first); + ComponentRegistry firstRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(firstRegistry); + ComponentType first = + PhysicsControlSessionComponent.getComponentType(); PhysicsControlSessionCleanupSystem firstSystem = new PhysicsControlSessionCleanupSystem(); - ComponentType second = registerSessionType(); - PhysicsControlSessionComponent.setComponentType(second); + ComponentRegistry secondRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(secondRegistry); + ComponentType second = + PhysicsControlSessionComponent.getComponentType(); PhysicsControlSessionCleanupSystem secondSystem = new PhysicsControlSessionCleanupSystem(); assertSame(first, firstSystem.componentType()); @@ -44,14 +48,16 @@ void sessionCleanupSystemCapturesCurrentSessionComponentType() { @Test void controllableLifecycleSystemCapturesCurrentControllableComponentType() { + ComponentRegistry firstRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(firstRegistry); ComponentType first = - registerControllableType(); - ImpulseControllableComponent.setComponentType(first); + ImpulseControllableComponent.getComponentType(); PhysicsControllableLifecycleSystem firstSystem = new PhysicsControllableLifecycleSystem(); + ComponentRegistry secondRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(secondRegistry); ComponentType second = - registerControllableType(); - ImpulseControllableComponent.setComponentType(second); + ImpulseControllableComponent.getComponentType(); PhysicsControllableLifecycleSystem secondSystem = new PhysicsControllableLifecycleSystem(); assertSame(first, firstSystem.componentType()); @@ -60,20 +66,20 @@ void controllableLifecycleSystemCapturesCurrentControllableComponentType() { @Test void holderSystemCapturesCurrentControlComponentTypes() throws ReflectiveOperationException { + ComponentRegistry firstRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(firstRegistry); ComponentType firstControllable = - registerControllableType(); + ImpulseControllableComponent.getComponentType(); ComponentType firstSession = - registerSessionType(); - ImpulseControllableComponent.setComponentType(firstControllable); - PhysicsControlSessionComponent.setComponentType(firstSession); + PhysicsControlSessionComponent.getComponentType(); PhysicsControlRuntimeHolderSystem firstSystem = new PhysicsControlRuntimeHolderSystem(); + ComponentRegistry secondRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(secondRegistry); ComponentType secondControllable = - registerControllableType(); + ImpulseControllableComponent.getComponentType(); ComponentType secondSession = - registerSessionType(); - ImpulseControllableComponent.setComponentType(secondControllable); - PhysicsControlSessionComponent.setComponentType(secondSession); + PhysicsControlSessionComponent.getComponentType(); PhysicsControlRuntimeHolderSystem secondSystem = new PhysicsControlRuntimeHolderSystem(); assertSame(firstControllable, field(firstSystem, "controllableType")); @@ -106,19 +112,6 @@ void holderLoadKeepsControllableMarker() { registry.shutdown(); } - private static ComponentType registerControllableType() { - ComponentRegistry registry = new ComponentRegistry<>(); - return registry.registerComponent(ImpulseControllableComponent.class, - "ImpulseControllable", - ImpulseControllableComponent.CODEC); - } - - private static ComponentType registerSessionType() { - ComponentRegistry registry = new ComponentRegistry<>(); - return registry.registerComponent(PhysicsControlSessionComponent.class, - PhysicsControlSessionComponent::new); - } - private static Object field(Object target, String name) throws ReflectiveOperationException { Field field = target.getClass().getDeclaredField(name); field.setAccessible(true); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java index 8e0b0ebb..d36c6254 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java @@ -23,7 +23,7 @@ void clearRegistration() { } @Test - void resourceTypeIsOwnedByPhysicsChunkModuleRegistration() { + void resourceTypeIsOwnedByPhysicsChunkSubPluginRegistration() { assertFalse(PhysicsChunkProfilingResource.isResourceTypeRegistered()); assertThrows(IllegalStateException.class, PhysicsChunkProfilingResource::getResourceType); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java new file mode 100644 index 00000000..6716f5ab --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -0,0 +1,84 @@ +package dev.hytalemodding.impulse.core.internal.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.codec.ExtraInfo; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import java.util.Objects; +import java.util.UUID; +import org.bson.BsonDocument; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PersistentSpaceDtoSettingsTest { + + @Test + void roundTripPreservesDetachedVisualCadenceSettingsAndCompatibilityKeys() { + PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); + original.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(true); + original.getPhysicsChunkTerrainSettings().setTerrainMaterial(0.85f, 0.2f); + original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); + original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); + original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); + + PhysicsChunkTerrainSettings terrain = original.getPhysicsChunkTerrainSettings(); + PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), + "test:settings-persistence", + new Vector3f(0.0f, -9.81f, 0.0f), + terrain.getTerrainMode(), + terrain.getEntityChunkBoundaryMode(), + terrain.isNativeVoxelTerrainEnabled(), + terrain.getTerrainRadius(), + terrain.getBodyTerrainRadius(), + terrain.getTerrainTtlTicks(), + terrain.getTerrainFriction(), + terrain.getTerrainRestitution(), + new SolverSettingsComponent(original.getSolverSettings()), + new VisualSyncSettingsComponent(original.getVisualSyncSettings()), + new VisualMaterializationSettingsComponent(original.getVisualMaterializationSettings()), + new CollisionLodSettingsComponent(original.getCollisionLodSettings()), + new ExtensionSettingsComponent(original.getExtensionSettings())); + + BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); + + assertTrue(encoded.containsKey("WorldCollisionMode")); + assertTrue(encoded.containsKey("WorldCollisionRadius")); + assertTrue(encoded.containsKey("WorldCollisionBodyRadius")); + assertTrue(encoded.containsKey("WorldCollisionTtlTicks")); + assertTrue(encoded.containsKey("NativeVoxelTerrain")); + assertTrue(encoded.containsKey("TerrainFriction")); + assertTrue(encoded.containsKey("TerrainRestitution")); + assertTrue(encoded.containsKey("VisualMaterializationSettings")); + PhysicsSpaceSettings decoded = Objects.requireNonNull( + PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())).toSettings(); + assertTrue(decoded.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); + assertEquals(0.85f, decoded.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); + assertEquals(0.2f, decoded.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); + assertDetachedVisualCadence(decoded, 7, 9, 11); + + PhysicsSpaceSettings copied = state.copy().toSettings(); + assertTrue(copied.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); + assertEquals(0.85f, copied.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); + assertEquals(0.2f, copied.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); + assertDetachedVisualCadence(copied, 7, 9, 11); + } + + private static void assertDetachedVisualCadence(PhysicsSpaceSettings settings, + int interestInterval, + int candidateInterval, + int visibilityInterval) { + assertEquals(interestInterval, + settings.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); + assertEquals(candidateInterval, + settings.getVisualMaterializationSettings().getDetachedVisualCandidateRefreshIntervalTicks()); + assertEquals(visibilityInterval, + settings.getVisualMaterializationSettings().getDetachedVisualVisibilityCheckIntervalTicks()); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java index 7df0adb9..99e50f73 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java @@ -4,8 +4,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.IComponentRegistry; +import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import java.lang.reflect.Method; @@ -19,13 +22,17 @@ class PhysicsTypeRegistrationApiTest { void internalRegistriesOwnRegistrationWithoutPublicTypeSetters() throws NoSuchMethodException { assertNotNull(PhysicsComponentTypeRegistry.class.getDeclaredMethod("registerComponentTypes", ComponentRegistryProxy.class)); + assertNotNull(ControlTypeRegistry.class.getDeclaredMethod("registerComponentTypes", + IComponentRegistry.class)); assertNotNull(PhysicsResourceTypes.class.getDeclaredMethod("registerResourceTypes", ComponentRegistryProxy.class)); assertFalse(hasPublicSetter(PhysicsComponentTypes.class)); assertFalse(hasPublicSetter(PhysicsResourceTypes.class)); + assertFalse(hasPublicSetter(ImpulseControllableComponent.class)); assertFalse(hasPublicRegistrationMethod(PhysicsComponentTypes.class)); assertFalse(hasPublicRegistrationMethod(PhysicsEntityTypes.class)); + assertFalse(hasPublicRegistrationMethod(ImpulseControllableComponent.class)); assertFalse(hasPublicMethodNamed(PhysicsChunkTerrain.class, "enableModule")); assertFalse(hasPublicMethodNamed(PhysicsChunkTerrain.class, "disableModule")); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java new file mode 100644 index 00000000..0f3322c8 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java @@ -0,0 +1,39 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") +class WorldCollisionComponentTest { + + @Test + void deprecatedWorldCollisionComponentAccessorsMutateTerrainState() { + WorldCollisionComponent component = new WorldCollisionComponent(WorldCollisionMode.STREAMING, + true, + 8, + 4, + 160, + 0.7f, + 0.1f); + + assertEquals(PhysicsChunkTerrainMode.STREAMING, component.getTerrainMode()); + assertEquals(WorldCollisionMode.STREAMING, component.getMode()); + + component.setMode(WorldCollisionMode.NONE); + + assertEquals(PhysicsChunkTerrainMode.NONE, component.getTerrainMode()); + assertEquals(WorldCollisionMode.NONE, component.getMode()); + + WorldCollisionComponent clone = component.clone(); + + assertEquals(PhysicsChunkTerrainMode.NONE, clone.getTerrainMode()); + assertEquals(8, clone.getRadius()); + assertEquals(4, clone.getBodyRadius()); + assertEquals(160, clone.getTtlTicks()); + assertEquals(0.7f, clone.getTerrainFriction(), 0.0001f); + assertEquals(0.1f, clone.getTerrainRestitution(), 0.0001f); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java similarity index 80% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index c4c7fcb6..d0ed0380 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.plugin.settings; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -7,26 +7,11 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.hypixel.hytale.codec.ExtraInfo; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; -import java.util.Objects; -import java.util.UUID; -import org.bson.BsonDocument; -import org.joml.Vector3f; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import org.junit.jupiter.api.Test; +@SuppressWarnings("deprecation") class PhysicsSpaceSettingsTest { @Test @@ -265,6 +250,26 @@ void streamingPhysicsChunkFactoryEnablesStreamingMode() { settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); } + @Test + void deprecatedWorldCollisionAccessorsMutatePhysicsChunkTerrainSettings() { + PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingWorldCollision(); + + assertSame(settings.getPhysicsChunkTerrainSettings(), settings.getWorldCollisionSettings()); + assertEquals(PhysicsChunkTerrainMode.STREAMING, + settings.getPhysicsChunkTerrainSettings().getTerrainMode()); + + settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.NONE); + settings.getWorldCollisionSettings().setWorldCollisionRadius(9); + settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(4); + settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(120); + + assertEquals(PhysicsChunkTerrainMode.NONE, + settings.getPhysicsChunkTerrainSettings().getTerrainMode()); + assertEquals(9, settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); + assertEquals(4, settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); + assertEquals(120, settings.getPhysicsChunkTerrainSettings().getTerrainTtlTicks()); + } + @Test void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { PhysicsSpaceSettings original = new PhysicsSpaceSettings(); @@ -325,61 +330,4 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { assertFalse(copy.getCollisionLodSettings().isCollisionLodFarSleepEnabled()); } - @Test - void persistentSpaceDtoRoundTripPreservesDetachedVisualCadenceSettings() { - PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); - original.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(true); - original.getPhysicsChunkTerrainSettings().setTerrainMaterial(0.85f, 0.2f); - original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); - original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); - original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); - - PhysicsChunkTerrainSettings collision = original.getPhysicsChunkTerrainSettings(); - PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), - "test:settings-persistence", - new Vector3f(0.0f, -9.81f, 0.0f), - collision.getTerrainMode(), - collision.getEntityChunkBoundaryMode(), - collision.isNativeVoxelTerrainEnabled(), - collision.getTerrainRadius(), - collision.getBodyTerrainRadius(), - collision.getTerrainTtlTicks(), - collision.getTerrainFriction(), - collision.getTerrainRestitution(), - new SolverSettingsComponent(original.getSolverSettings()), - new VisualSyncSettingsComponent(original.getVisualSyncSettings()), - new VisualMaterializationSettingsComponent(original.getVisualMaterializationSettings()), - new CollisionLodSettingsComponent(original.getCollisionLodSettings()), - new ExtensionSettingsComponent(original.getExtensionSettings())); - - BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); - - assertTrue(encoded.containsKey("NativeVoxelTerrain")); - assertTrue(encoded.containsKey("TerrainFriction")); - assertTrue(encoded.containsKey("TerrainRestitution")); - assertTrue(encoded.containsKey("VisualMaterializationSettings")); - PhysicsSpaceSettings decoded = Objects.requireNonNull( - PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())).toSettings(); - assertTrue(decoded.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.85f, decoded.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.2f, decoded.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); - assertDetachedVisualCadence(decoded, - 7, - 9, - 11); - PhysicsSpaceSettings copied = state.copy().toSettings(); - assertTrue(copied.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.85f, copied.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.2f, copied.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); - assertDetachedVisualCadence(copied, 7, 9, 11); - } - - private static void assertDetachedVisualCadence(PhysicsSpaceSettings settings, - int interestInterval, - int candidateInterval, - int visibilityInterval) { - assertEquals(interestInterval, settings.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); - assertEquals(candidateInterval, settings.getVisualMaterializationSettings().getDetachedVisualCandidateRefreshIntervalTicks()); - assertEquals(visibilityInterval, settings.getVisualMaterializationSettings().getDetachedVisualVisibilityCheckIntervalTicks()); - } } diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java new file mode 100644 index 00000000..41b6455c --- /dev/null +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java @@ -0,0 +1,23 @@ +package dev.hytalemodding.impulse.examples.testsupport; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; +import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; +import javax.annotation.Nonnull; + +public final class ExampleControlTestSupport { + + private ExampleControlTestSupport() { + } + + public static void enableControl(@Nonnull ComponentRegistry registry) { + ControlLifecycle.enable(); + ControlTypeRegistry.registerComponentTypes(registry); + } + + public static void clearControl() { + ControlLifecycle.disable(); + ControlTypeRegistry.clearComponentTypes(); + } +} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java index b34cb150..2d53f038 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java @@ -12,12 +12,11 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.examples.testsupport.ExampleControlTestSupport; import java.lang.reflect.Field; import javax.annotation.Nonnull; @@ -45,21 +44,12 @@ void registerComponentTypes() throws Exception { "BodyAttachment", BodyAttachmentComponent.CODEC); bodyAttachmentTypeField.set(null, bodyAttachmentType); - ControlLifecycle.enable(); - ImpulseControllableComponent.setComponentType(registry.registerComponent( - ImpulseControllableComponent.class, - "ImpulseControllable", - ImpulseControllableComponent.CODEC)); - PhysicsControlSessionComponent.setComponentType(registry.registerComponent( - PhysicsControlSessionComponent.class, - PhysicsControlSessionComponent::new)); + ExampleControlTestSupport.enableControl(registry); } @AfterEach void clearComponentTypes() throws Exception { - ControlLifecycle.disable(); - ImpulseControllableComponent.clearComponentType(); - PhysicsControlSessionComponent.clearComponentType(); + ExampleControlTestSupport.clearControl(); staticField(EntityModule.class, "instance").set(null, previousEntityModule); staticField(PhysicsEntityTypes.class, "bodyAttachmentComponentType") .set(null, previousBodyAttachmentComponentType); From 3eba847bdbc4be6fd62ab4cdff06fc89a1ba0191 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:07:50 +0200 Subject: [PATCH 370/534] refactor(examples): resolve physics spaces once Signed-off-by: Blovien --- .../examples/commands/DropCommand.java | 19 +++---- .../examples/commands/MaterialsCommand.java | 25 +++++---- .../examples/commands/RaycastCommand.java | 17 ++---- .../examples/commands/ShapesCommand.java | 27 +++++----- .../examples/utils/ExamplePhysicsUtils.java | 52 +++++++++++++++++++ 5 files changed, 88 insertions(+), 52 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index 81f8f5f3..a7fddb93 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -11,8 +11,6 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.concurrent.CompletableFuture; @@ -51,23 +49,18 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, float spawnY = (float) playerPos.y() + 5f; float spawnZ = (float) playerPos.z(); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { - return CompletableFuture.completedFuture(null); - } - Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, - spaceId); - if (spaceRef == null) { - ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() - + " is not bound yet.")); + ExamplePhysicsUtils.SpaceSelection space = ExamplePhysicsUtils.spaceSelection(ctx, + world, + spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.spawnBlockBody(store, time, - spaceRef, - spaceId, + space.spaceRef(), + space.spaceId(), new Vector3d(spawnX, spawnY, spawnZ), blockType(ctx), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 3850681b..9f141c2a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -41,26 +41,25 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { - return CompletableFuture.completedFuture(null); - } - Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, - spaceId); - if (spaceRef == null) { - ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() - + " is not bound yet.")); + ExamplePhysicsUtils.SpaceSelection space = ExamplePhysicsUtils.spaceSelection(ctx, + world, + spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-3.0, 5.0, 4.0); - spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin), 0.05f, 0.9f, 3.0f); - spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin).add(2.0, 0.0, 0.0), + spawnSphere(store, time, space.spaceRef(), space.spaceId(), new Vector3d(origin), + 0.05f, 0.9f, 3.0f); + spawnSphere(store, time, space.spaceRef(), space.spaceId(), + new Vector3d(origin).add(2.0, 0.0, 0.0), 0.95f, 0.9f, 3.0f); - spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin).add(4.0, 0.0, 0.0), + spawnSphere(store, time, space.spaceRef(), space.spaceId(), + new Vector3d(origin).add(4.0, 0.0, 0.0), 0.5f, 0.0f, 2.0f); - spawnSphere(store, time, spaceRef, spaceId, new Vector3d(origin).add(6.0, 0.0, 0.0), + spawnSphere(store, time, space.spaceRef(), space.spaceId(), + new Vector3d(origin).add(6.0, 0.0, 0.0), 0.5f, 0.95f, 2.0f); ctx.sender().sendMessage(Message.raw( diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index 7eb44937..c991b0a7 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -12,9 +12,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.TargetUtil; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; @@ -42,15 +40,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { - return CompletableFuture.completedFuture(null); - } - Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, - spaceId); - if (spaceRef == null) { - ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() - + " is not bound yet.")); + ExamplePhysicsUtils.SpaceSelection space = ExamplePhysicsUtils.spaceSelection(ctx, + world, + spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } @@ -63,7 +56,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, DebugUtils.FLAG_FADE); return PhysicsAsync.acceptOnWorldThread(world, PhysicsRaycasts.closestAsync(world, - spaceRef, + space.spaceRef(), ExamplePhysicsUtils.toVector3f(start), ExamplePhysicsUtils.toVector3f(end)), hit -> handleHit(ctx, world, hit.map(RaycastCommand::toResult).orElse(null))); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index 3079ea1e..89af288f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -41,26 +41,25 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { - return CompletableFuture.completedFuture(null); - } - Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, - spaceId); - if (spaceRef == null) { - ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() - + " is not bound yet.")); + ExamplePhysicsUtils.SpaceSelection space = ExamplePhysicsUtils.spaceSelection(ctx, + world, + spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); Vector3d origin = new Vector3d(playerPos).add(-4.0, 3.0, 3.0); - spawn(store, time, spaceRef, spaceId, ShapeType.BOX, PhysicsAxis.Y, + spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.BOX, PhysicsAxis.Y, origin, 0); - spawn(store, time, spaceRef, spaceId, ShapeType.SPHERE, PhysicsAxis.Y, origin, 2); - spawn(store, time, spaceRef, spaceId, ShapeType.CAPSULE, PhysicsAxis.Y, origin, 4); - spawn(store, time, spaceRef, spaceId, ShapeType.CYLINDER, PhysicsAxis.Y, origin, 6); - spawn(store, time, spaceRef, spaceId, ShapeType.CONE, PhysicsAxis.Y, origin, 8); + spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.SPHERE, PhysicsAxis.Y, + origin, 2); + spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.CAPSULE, PhysicsAxis.Y, + origin, 4); + spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.CYLINDER, PhysicsAxis.Y, + origin, 6); + spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.CONE, PhysicsAxis.Y, + origin, 8); ctx.sender().sendMessage(Message.raw("Spawned shape demo.")); return CompletableFuture.completedFuture(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 76ea01d1..ad71d7a0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -58,6 +58,49 @@ public static Ref resolveSpaceRef(@Nonnull World world, return PhysicsSpaces.resolveRef(store, spaceId); } + @Nullable + public static SpaceSelection spaceSelection(@Nonnull CommandContext ctx, + @Nonnull World world, + @Nonnull OptionalArg spaceArg) { + Store store = PhysicsThreading.store(world); + if (spaceArg.provided(ctx)) { + int rawSpaceId = spaceArg.get(ctx); + if (rawSpaceId <= 0) { + ctx.sender().sendMessage(Message.raw("Space id must be a positive integer.")); + return null; + } + SpaceId spaceId = new SpaceId(rawSpaceId); + Ref spaceRef = PhysicsSpaces.resolveRef(store, spaceId); + if (spaceRef != null) { + return new SpaceSelection(spaceId, spaceRef); + } + if (PhysicsSpaces.hasSpace(store, spaceId)) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + rawSpaceId + + " is not bound yet.")); + } else { + ctx.sender().sendMessage(Message.raw("No physics space id=" + rawSpaceId + " exists.")); + } + return null; + } + + SpaceId firstSpaceId = PhysicsSpaces.spaceIds(store) + .stream() + .min(Comparator.comparingInt(SpaceId::value)) + .orElse(null); + if (firstSpaceId == null) { + ctx.sender().sendMessage(Message.raw("No physics space exists. Run " + + "`/impulse space create --backend=` before running Impulse example commands.")); + return null; + } + Ref spaceRef = PhysicsSpaces.resolveRef(store, firstSpaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + firstSpaceId.value() + + " is not bound yet.")); + return null; + } + return new SpaceSelection(firstSpaceId, spaceRef); + } + @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyEntityDescriptor descriptor) { @@ -901,6 +944,15 @@ public record SpawnedBlockBody(@Nonnull UUID bodyUuid, @Nonnull Ref entity) { } + public record SpaceSelection(@Nonnull SpaceId spaceId, + @Nonnull Ref spaceRef) { + + public SpaceSelection { + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(spaceRef, "spaceRef"); + } + } + public record BlockBodyBatchTiming(int count, long entityApplyNanos, long entityAttachNanos) { From 7086e9c3dc86cfe4319f8072993773e11c7b76a9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:09:41 +0200 Subject: [PATCH 371/534] fix(core): preserve generated proxy attachments Signed-off-by: Blovien --- .../PhysicsGeneratedProxyCleanupSystem.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java index 31faf888..ddf7880d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java @@ -10,7 +10,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import java.util.Collections; import java.util.Map; @@ -19,7 +18,7 @@ import javax.annotation.Nonnull; /** - * Removes serialized generated visual proxies left by the pre-PhysicsStore runtime model. + * Removes incomplete generated visual proxy markers left by transition-era saves. */ public class PhysicsGeneratedProxyCleanupSystem extends TickingSystem { @@ -37,7 +36,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { if (shouldSkipCleanup(store)) { return; } - removeLegacyGeneratedVisualProxies(store); + removeOrphanGeneratedVisualProxyMarkers(store); } private boolean shouldSkipCleanup(@Nonnull Store store) { @@ -52,20 +51,9 @@ private boolean shouldSkipCleanup(@Nonnull Store store) { } } - private static void removeLegacyGeneratedVisualProxies(@Nonnull Store store) { + private static void removeOrphanGeneratedVisualProxyMarkers(@Nonnull Store store) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, commandBuffer) -> { - BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, - attachmentType); - if (attachment == null - || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { - return; - } - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); - }); - ComponentType generatedProxyType = GeneratedVisualProxyComponent.getComponentType(); store.forEachEntityParallel(generatedProxyType, From 7b693e2adec6498213a9812500741ce33974c1cc Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:09:58 +0200 Subject: [PATCH 372/534] test(core): guard generated proxy cleanup Signed-off-by: Blovien --- ...hysicsGeneratedProxyCleanupSystemTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java new file mode 100644 index 00000000..1735cf88 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java @@ -0,0 +1,22 @@ +package dev.hytalemodding.impulse.core.internal.systems.visual; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class PhysicsGeneratedProxyCleanupSystemTest { + + @Test + void cleanupDoesNotRemoveDurableGeneratedProxyAttachments() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/" + + "PhysicsGeneratedProxyCleanupSystem.java")); + + assertTrue(source.contains("removeOrphanGeneratedVisualProxyMarkers")); + assertFalse(source.contains("AttachmentLifecycle.GENERATED_PROXY")); + } +} From e5f0a0016727303c741366215e345de4e4a5cc2b Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:18:16 +0200 Subject: [PATCH 373/534] refactor(core): clarify physics subplugin naming Signed-off-by: Blovien --- README.md | 2 +- .../commands/PhysicsChunkCommand.java | 2 +- .../physicschunk/PhysicsChunkTerrain.java | 16 ++++++++++++---- .../physicschunk/PhysicsWorldCollision.java | 2 +- .../modules/physicschunk/package-info.java | 2 +- .../modules/physicsentity/package-info.java | 2 +- .../commands/PhysicsChunkExampleCommand.java | 12 ++++++------ 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9db690ae..2db2cc22 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ flowchart TB Plugin["Plugin API package"] - Modules["Internal modules\n- Hytale modules substitution (WIP)\n- PhysicsChunk terrain module\n- Control session module"] + Modules["Builtin subplugins\n- PhysicsEntity integration\n- PhysicsChunk terrain\n- Control sessions"] StoreSystems["PhysicsStore systems + resources"] Ordering["row mutation + backend step ordering"] diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java index ffdd472f..ab167c61 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java @@ -5,7 +5,7 @@ public final class PhysicsChunkCommand extends AbstractCommandCollection { public PhysicsChunkCommand() { - super("physicschunk", "Impulse PhysicsChunk module commands"); + super("physicschunk", "Impulse PhysicsChunk terrain commands"); addSubCommand(new PhysicsChunkSettingsCommand()); addSubCommand(new PhysicsChunkPerfCommand()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index dc005c03..431c298b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -30,10 +30,18 @@ public final class PhysicsChunkTerrain { private PhysicsChunkTerrain() { } - public static boolean isModuleEnabled() { + public static boolean isSubPluginEnabled() { return PhysicsChunkLifecycle.isEnabled(); } + /** + * @deprecated Use {@link #isSubPluginEnabled()}. + */ + @Deprecated(forRemoval = false) + public static boolean isModuleEnabled() { + return isSubPluginEnabled(); + } + @Nonnull public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, @Nonnull Store store, @@ -121,13 +129,13 @@ public static PhysicsChunkTerrainStats stats(@Nonnull World world) { throw new IllegalStateException("Cannot read PhysicsChunk terrain stats " + "outside the owning world thread"); } - return isModuleEnabled() + return isSubPluginEnabled() ? streaming(world).stats() : new PhysicsChunkTerrainStats(0, 0, 0, 0); } private static void requireEnabled() { - if (!isModuleEnabled()) { + if (!isSubPluginEnabled()) { throw new IllegalStateException("Impulse physics chunk subplugin is disabled"); } } @@ -188,7 +196,7 @@ private static int clearSpaceRows(@Nonnull World world, @Nonnull Store store, @Nonnull UUID spaceUuid) { int removed = 0; - if (isModuleEnabled()) { + if (isSubPluginEnabled()) { removed = streaming(world).clearSpace(spaceUuid, store.getResource(PhysicsTerrainMutationQueueResource.getResourceType())); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java index c6f1c902..6b422c79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java @@ -17,7 +17,7 @@ private PhysicsWorldCollision() { } public static boolean isModuleEnabled() { - return PhysicsChunkTerrain.isModuleEnabled(); + return PhysicsChunkTerrain.isSubPluginEnabled(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java index d4ce310b..2f851671 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java @@ -1,4 +1,4 @@ /** - * Optional ChunkStore PhysicsChunk terrain integration for authoritative PhysicsStore terrain. + * Public API for the bundled PhysicsChunk terrain subplugin. */ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java index 99a0a407..3b71da99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java @@ -1,4 +1,4 @@ /** - * Optional EntityStore projection, sync, debug, and profiling integration for PhysicsStore bodies. + * Public API for the bundled PhysicsEntity subplugin. */ package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index f63e1aa7..d6d1a474 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -31,7 +31,7 @@ public class PhysicsChunkExampleCommand extends AbstractCommandCollection { public PhysicsChunkExampleCommand() { - super("physicschunk", "Build static Impulse chunk collision from nearby world blocks"); + super("physicschunk", "Build PhysicsChunk terrain collision from nearby world blocks"); addSubCommand(new BuildCommand()); addSubCommand(new EnsureCommand()); addSubCommand(new ClearCommand()); @@ -58,7 +58,7 @@ private static final class BuildCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private BuildCommand() { - super("build", "Rebuild nearby static voxel collision"); + super("build", "Rebuild nearby PhysicsChunk terrain collision"); } @Nonnull @@ -113,7 +113,7 @@ private static final class EnsureCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private EnsureCommand() { - super("ensure", "Ensure nearby static voxel collision is available"); + super("ensure", "Ensure nearby PhysicsChunk terrain collision is available"); } @Nonnull @@ -157,7 +157,7 @@ private static final class ClearCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private ClearCommand() { - super("clear", "Remove generated static voxel collision"); + super("clear", "Remove generated PhysicsChunk terrain collision"); } @Nonnull @@ -182,7 +182,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static final class StatsCommand extends AbstractAsyncPlayerCommand { private StatsCommand() { - super("stats", "Show generated static voxel collision stats"); + super("stats", "Show generated PhysicsChunk terrain collision stats"); } @Nonnull @@ -193,7 +193,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsChunkTerrainStats stats = PhysicsChunkTerrain.stats(world); - ctx.sender().sendMessage(Message.raw("World voxel collision: " + ctx.sender().sendMessage(Message.raw("PhysicsChunk terrain collision: " + stats.spaces() + " spaces, " + stats.sections() + " sections, " + stats.bodies() + " bodies, " From d32e2f8622190d3b29c71f78dca5c84609906b8a Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:21:00 +0200 Subject: [PATCH 374/534] test(core): clarify subplugin lifecycle guards Signed-off-by: Blovien --- .../modules/SubPluginLifecycleGateTest.java | 8 ++++---- .../PhysicsTypeRegistrationApiTest.java | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/SubPluginLifecycleGateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/SubPluginLifecycleGateTest.java index e60dc011..92ba1bea 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/SubPluginLifecycleGateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/SubPluginLifecycleGateTest.java @@ -12,18 +12,18 @@ class SubPluginLifecycleGateTest { @Test void gateStartsDisabledAndFailsFastWithConfiguredMessage() { - SubPluginLifecycleGate gate = new SubPluginLifecycleGate("test module disabled"); + SubPluginLifecycleGate gate = new SubPluginLifecycleGate("test subplugin disabled"); assertFalse(gate.isEnabled()); IllegalStateException exception = assertThrows(IllegalStateException.class, gate::requireEnabled); - assertEquals("test module disabled", exception.getMessage()); + assertEquals("test subplugin disabled", exception.getMessage()); } @Test void generationChangesOnEnableAndDisableTransitions() { - SubPluginLifecycleGate gate = new SubPluginLifecycleGate("test module disabled"); + SubPluginLifecycleGate gate = new SubPluginLifecycleGate("test subplugin disabled"); long initialGeneration = gate.generation(); gate.enable(); @@ -43,7 +43,7 @@ void generationChangesOnEnableAndDisableTransitions() { @Test void disableRunsCleanupCallbacksOnlyWhenTransitioningFromEnabled() { - SubPluginLifecycleGate gate = new SubPluginLifecycleGate("test module disabled"); + SubPluginLifecycleGate gate = new SubPluginLifecycleGate("test subplugin disabled"); AtomicInteger cleanupCount = new AtomicInteger(); gate.onDisable(cleanupCount::incrementAndGet); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java index 99e50f73..9fe70fd4 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java @@ -19,7 +19,8 @@ class PhysicsTypeRegistrationApiTest { @Test - void internalRegistriesOwnRegistrationWithoutPublicTypeSetters() throws NoSuchMethodException { + void internalRegistriesOwnRegistrationAndLifecycleWithoutPublicMutators() + throws NoSuchMethodException { assertNotNull(PhysicsComponentTypeRegistry.class.getDeclaredMethod("registerComponentTypes", ComponentRegistryProxy.class)); assertNotNull(ControlTypeRegistry.class.getDeclaredMethod("registerComponentTypes", @@ -33,8 +34,7 @@ void internalRegistriesOwnRegistrationWithoutPublicTypeSetters() throws NoSuchMe assertFalse(hasPublicRegistrationMethod(PhysicsComponentTypes.class)); assertFalse(hasPublicRegistrationMethod(PhysicsEntityTypes.class)); assertFalse(hasPublicRegistrationMethod(ImpulseControllableComponent.class)); - assertFalse(hasPublicMethodNamed(PhysicsChunkTerrain.class, "enableModule")); - assertFalse(hasPublicMethodNamed(PhysicsChunkTerrain.class, "disableModule")); + assertFalse(hasPublicLifecycleMutator(PhysicsChunkTerrain.class)); } private static boolean hasPublicSetter(Class type) { @@ -59,4 +59,11 @@ private static boolean hasPublicMethodNamed(Class type, String name) { .map(Method::getName) .anyMatch(name::equals); } + + private static boolean hasPublicLifecycleMutator(Class type) { + return hasPublicMethodNamed(type, "enableModule") + || hasPublicMethodNamed(type, "disableModule") + || hasPublicMethodNamed(type, "enableSubPlugin") + || hasPublicMethodNamed(type, "disableSubPlugin"); + } } From 80f21c3174dce56951366af624dd36a8963d7084 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:25:08 +0200 Subject: [PATCH 375/534] refactor(core): reduce physics component accessor boilerplate Signed-off-by: Blovien --- .../components/BodyCommandComponent.java | 61 +++--------- .../plugin/components/JointComponent.java | 92 ++++--------------- .../plugin/components/MaterialComponent.java | 20 +--- ...isualMaterializationSettingsComponent.java | 41 ++------- .../VisualSyncSettingsComponent.java | 66 +++---------- 5 files changed, 60 insertions(+), 220 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java index 04970be0..ecdd96ef 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyCommandComponent.java @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.Objects; import javax.annotation.Nonnull; +import lombok.Getter; import org.joml.Vector3f; /** @@ -212,18 +213,30 @@ public static final class Entry { private Kind kind = Kind.WAKE; @Nonnull private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; + @Getter private boolean activate; + @Getter private float x; + @Getter private float y; + @Getter private float z; private boolean hasOffset; + @Getter private float offsetX; + @Getter private float offsetY; + @Getter private float offsetZ; + @Getter private float angularX; + @Getter private float angularY; + @Getter private float angularZ; + @Getter private int collisionGroup = PhysicsCollisionFilters.DYNAMIC_BODY; + @Getter private int collisionMask = PhysicsCollisionFilters.ALL; public Entry() { @@ -402,58 +415,10 @@ public PhysicsBodyType getBodyType() { return bodyType; } - public boolean isActivate() { - return activate; - } - - public float getX() { - return x; - } - - public float getY() { - return y; - } - - public float getZ() { - return z; - } - public boolean hasOffset() { return hasOffset; } - public float getOffsetX() { - return offsetX; - } - - public float getOffsetY() { - return offsetY; - } - - public float getOffsetZ() { - return offsetZ; - } - - public float getAngularX() { - return angularX; - } - - public float getAngularY() { - return angularY; - } - - public float getAngularZ() { - return angularZ; - } - - public int getCollisionGroup() { - return collisionGroup; - } - - public int getCollisionMask() { - return collisionMask; - } - @Nonnull @Override public Entry clone() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java index c35c16a8..c0e4ef90 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java @@ -14,6 +14,8 @@ import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import lombok.Getter; +import lombok.Setter; import org.joml.Vector3f; /** @@ -113,14 +115,32 @@ public final class JointComponent implements Component { private final Vector3f anchorB = new Vector3f(); @Nonnull private final Vector3f axis = new Vector3f(); + @Setter + @Getter private float lowerLimit; + @Setter + @Getter private float upperLimit; + @Setter + @Getter private boolean enabled = true; + @Setter + @Getter private boolean motorEnabled; + @Setter + @Getter private float motorTargetVelocity; + @Setter + @Getter private float motorMaxForce; + @Setter + @Getter private float springRestLength; + @Setter + @Getter private float springStiffness; + @Setter + @Getter private float springDamping; public JointComponent() { @@ -219,78 +239,6 @@ public void setAxis(@Nonnull Vector3f axis) { this.axis.set(Objects.requireNonNull(axis, "axis")); } - public float getLowerLimit() { - return lowerLimit; - } - - public void setLowerLimit(float lowerLimit) { - this.lowerLimit = lowerLimit; - } - - public float getUpperLimit() { - return upperLimit; - } - - public void setUpperLimit(float upperLimit) { - this.upperLimit = upperLimit; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean isMotorEnabled() { - return motorEnabled; - } - - public void setMotorEnabled(boolean motorEnabled) { - this.motorEnabled = motorEnabled; - } - - public float getMotorTargetVelocity() { - return motorTargetVelocity; - } - - public void setMotorTargetVelocity(float motorTargetVelocity) { - this.motorTargetVelocity = motorTargetVelocity; - } - - public float getMotorMaxForce() { - return motorMaxForce; - } - - public void setMotorMaxForce(float motorMaxForce) { - this.motorMaxForce = motorMaxForce; - } - - public float getSpringRestLength() { - return springRestLength; - } - - public void setSpringRestLength(float springRestLength) { - this.springRestLength = springRestLength; - } - - public float getSpringStiffness() { - return springStiffness; - } - - public void setSpringStiffness(float springStiffness) { - this.springStiffness = springStiffness; - } - - public float getSpringDamping() { - return springDamping; - } - - public void setSpringDamping(float springDamping) { - this.springDamping = springDamping; - } - @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.jointComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java index 3d969ecc..2b9ca9d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java @@ -7,10 +7,14 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import javax.annotation.Nonnull; +import lombok.Getter; +import lombok.Setter; /** * Physical material settings for one collider entity. */ +@Setter +@Getter public final class MaterialComponent implements Component { @Nonnull @@ -38,22 +42,6 @@ public MaterialComponent(float friction, float restitution) { this.restitution = restitution; } - public float getFriction() { - return friction; - } - - public void setFriction(float friction) { - this.friction = friction; - } - - public float getRestitution() { - return restitution; - } - - public void setRestitution(float restitution) { - this.restitution = restitution; - } - @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.materialComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java index 85c543db..264f3f87 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java @@ -10,6 +10,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; import java.util.Objects; import javax.annotation.Nonnull; +import lombok.Getter; /** * Authored detached visual materialization policy for one PhysicsStore space entity. @@ -88,20 +89,28 @@ public final class VisualMaterializationSettingsComponent implements Component

        Date: Wed, 17 Jun 2026 22:31:37 +0200 Subject: [PATCH 376/534] refactor(core): move visual settings under physics entity Signed-off-by: Blovien --- .../VisualMaterializationSettingsCommand.java | 2 +- .../commands/VisualSyncSettingsCommand.java | 2 +- .../systems/sync/PhysicsSyncPolicy.java | 2 +- ...isualMaterializationSettingsComponent.java | 2 +- .../VisualSyncSettingsComponent.java | 2 +- .../modules/physicsentity/package-info.java | 3 + .../PhysicsEntitySettingsValidation.java | 16 + .../PhysicsVisualMaterializationSettings.java | 273 ++++++++++++++ .../settings/PhysicsVisualSyncSettings.java | 345 ++++++++++++++++++ .../physicsentity/settings/package-info.java | 4 + .../PhysicsVisualMaterializationSettings.java | 270 +------------- .../settings/PhysicsVisualSyncSettings.java | 342 +---------------- impulse-core/src/module-info/module-info.java | 1 + .../commands/stress/StressBodiesCommand.java | 4 +- .../utils/ExampleBlockEntityVisuals.java | 2 +- .../examples/utils/ExamplePhysicsUtils.java | 2 +- 16 files changed, 675 insertions(+), 597 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsEntitySettingsValidation.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualMaterializationSettings.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java index cb41a7af..d378b9ea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java index 7ffb45ad..48624eca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java index 32ffbb62..43dcd195 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java @@ -3,7 +3,7 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import java.util.List; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java index 264f3f87..a4f2ed1a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import java.util.Objects; import javax.annotation.Nonnull; import lombok.Getter; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java index d635c25e..18206987 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import java.util.Objects; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java index 3b71da99..12c7a60f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java @@ -1,4 +1,7 @@ /** * Public API for the bundled PhysicsEntity subplugin. + * + *

        Visual sync and generated-proxy settings live under + * {@code dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings}.

        */ package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsEntitySettingsValidation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsEntitySettingsValidation.java new file mode 100644 index 00000000..f714c495 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsEntitySettingsValidation.java @@ -0,0 +1,16 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; + +import javax.annotation.Nonnull; + +final class PhysicsEntitySettingsValidation { + + private PhysicsEntitySettingsValidation() { + } + + static int requirePositiveAtMost(@Nonnull String label, int value, int maxValue) { + if (value < 1 || value > maxValue) { + throw new IllegalArgumentException(label + " must be between 1 and " + maxValue); + } + return value; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualMaterializationSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualMaterializationSettings.java new file mode 100644 index 00000000..66269cb1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualMaterializationSettings.java @@ -0,0 +1,273 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; + +import javax.annotation.Nonnull; +import lombok.Getter; +import lombok.Setter; + +/** + * Generated visual proxy settings for detached physics bodies. + */ +public class PhysicsVisualMaterializationSettings { + + /** + * Whether detached bodies should automatically create disposable Hytale visual followers near players. + * Disabled by default because integrators may provide their own render/materialization layer. + */ + public static final boolean DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED = false; + + /** + * Block radius around players where detached bodies materialize visual followers. + */ + public static final int DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS = 64; + + /** + * Hard block-radius cap for detached visual proxy materialization. + */ + public static final int MAX_DETACHED_VISUAL_MATERIALIZATION_RADIUS = 512; + + /** + * Larger radius used to avoid rapid visual proxy despawn/respawn at the edge. + */ + public static final int DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS = 80; + + /** + * Hard block-radius cap for detached visual proxy dematerialization. + */ + public static final int MAX_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS = 1_024; + + /** + * Maximum detached visual proxies spawned per world tick. + */ + public static final int DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK = 128; + + /** + * Hard cap on detached visual proxy spawns per tick. + */ + public static final int MAX_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK = 512; + + /** + * Maximum detached visual proxies allowed in one physics space at once. + */ + public static final int DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED = 1024; + + /** + * Hard cap on detached visual proxies materialized in one physics space. + */ + public static final int MAX_DETACHED_VISUAL_MAX_MATERIALIZED = 16_384; + + /** + * Default visual proxy block type. Integrators should override this for their own content. + */ + @Nonnull + public static final String DEFAULT_DETACHED_VISUAL_BLOCK_TYPE = "Rock_Stone"; + + /** + * Ticks between refreshing player/synthetic visual interests for detached materialization. + */ + public static final int DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS = 4; + + /** + * Ticks between refreshing detached materialization near-query/raycast spawn candidates. + */ + public static final int DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS = 4; + + /** + * Ticks between checking existing generated proxies for dematerialization. + */ + public static final int DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS = 10; + + /** + * Hard tick cap for detached visual materialization cache intervals. + */ + public static final int MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS = 1_200; + + /** + * If enabled, detached physics bodies create disposable visual followers near players. + */ + @Setter + @Getter + private boolean detachedVisualMaterializationEnabled = + DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED; + + /** + * Radius where detached bodies become visual followers. + */ + @Getter + private int detachedVisualMaterializationRadius = + DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS; + + /** + * Radius where detached visual followers are removed again. + */ + @Getter + private int detachedVisualDematerializationRadius = + DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS; + + /** + * Per-tick cap for spawning detached visual followers. + */ + @Getter + private int detachedVisualMaxSpawnsPerTick = + DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK; + + /** + * Total cap for detached visual followers in this space. + */ + @Getter + private int detachedVisualMaxMaterialized = + DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED; + + /** + * Refresh cadence for player/synthetic interests used by detached visual materialization. + */ + @Getter + private int detachedVisualInterestRefreshIntervalTicks = + DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS; + + /** + * Refresh cadence for detached visual materialization near-query/raycast candidates. + */ + @Getter + private int detachedVisualCandidateRefreshIntervalTicks = + DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS; + + /** + * Refresh cadence for existing generated-proxy visibility/dematerialization checks. + */ + @Getter + private int detachedVisualVisibilityCheckIntervalTicks = + DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS; + + /** + * Fallback Hytale block type for generated detached visual proxies. + * + *

        This is only a default materialization hint for simple demos and stress tests. Integrators + * that need body-specific visuals should store their own visual description on the physics body + * or disable generated visual materialization.

        + */ + @Nonnull + private String detachedVisualBlockType = DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; + + public PhysicsVisualMaterializationSettings() { + } + + public PhysicsVisualMaterializationSettings( + @Nonnull PhysicsVisualMaterializationSettings settings) { + detachedVisualMaterializationEnabled = settings.detachedVisualMaterializationEnabled; + detachedVisualMaterializationRadius = settings.detachedVisualMaterializationRadius; + detachedVisualDematerializationRadius = settings.detachedVisualDematerializationRadius; + detachedVisualMaxSpawnsPerTick = settings.detachedVisualMaxSpawnsPerTick; + detachedVisualMaxMaterialized = settings.detachedVisualMaxMaterialized; + detachedVisualInterestRefreshIntervalTicks = + settings.detachedVisualInterestRefreshIntervalTicks; + detachedVisualCandidateRefreshIntervalTicks = + settings.detachedVisualCandidateRefreshIntervalTicks; + detachedVisualVisibilityCheckIntervalTicks = + settings.detachedVisualVisibilityCheckIntervalTicks; + detachedVisualBlockType = settings.detachedVisualBlockType; + } + + public void setDetachedVisualMaterializationRadius( + int detachedVisualMaterializationRadius) { + int boundedDetachedVisualMaterializationRadius = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual materialization radius", + detachedVisualMaterializationRadius, + MAX_DETACHED_VISUAL_MATERIALIZATION_RADIUS); + if (boundedDetachedVisualMaterializationRadius > detachedVisualDematerializationRadius) { + throw new IllegalArgumentException( + "Detached visual materialization radius cannot exceed dematerialization radius"); + } + this.detachedVisualMaterializationRadius = boundedDetachedVisualMaterializationRadius; + } + + public void setDetachedVisualDematerializationRadius( + int detachedVisualDematerializationRadius) { + int boundedDetachedVisualDematerializationRadius = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual dematerialization radius", + detachedVisualDematerializationRadius, + MAX_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS); + if (boundedDetachedVisualDematerializationRadius < detachedVisualMaterializationRadius) { + throw new IllegalArgumentException( + "Detached visual dematerialization radius cannot be lower than materialization radius"); + } + this.detachedVisualDematerializationRadius = boundedDetachedVisualDematerializationRadius; + } + + public void setDetachedVisualRadii(int detachedVisualMaterializationRadius, + int detachedVisualDematerializationRadius) { + int boundedDetachedVisualMaterializationRadius = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual materialization radius", + detachedVisualMaterializationRadius, + MAX_DETACHED_VISUAL_MATERIALIZATION_RADIUS); + int boundedDetachedVisualDematerializationRadius = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual dematerialization radius", + detachedVisualDematerializationRadius, + MAX_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS); + if (boundedDetachedVisualMaterializationRadius + > boundedDetachedVisualDematerializationRadius) { + throw new IllegalArgumentException( + "Detached visual materialization radius cannot exceed dematerialization radius"); + } + this.detachedVisualMaterializationRadius = boundedDetachedVisualMaterializationRadius; + this.detachedVisualDematerializationRadius = boundedDetachedVisualDematerializationRadius; + } + + public void setDetachedVisualMaxSpawnsPerTick(int detachedVisualMaxSpawnsPerTick) { + this.detachedVisualMaxSpawnsPerTick = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual max spawns per tick", + detachedVisualMaxSpawnsPerTick, + MAX_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK); + } + + public void setDetachedVisualMaxMaterialized(int detachedVisualMaxMaterialized) { + this.detachedVisualMaxMaterialized = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual max materialized", + detachedVisualMaxMaterialized, + MAX_DETACHED_VISUAL_MAX_MATERIALIZED); + } + + public void setDetachedVisualInterestRefreshIntervalTicks( + int detachedVisualInterestRefreshIntervalTicks) { + this.detachedVisualInterestRefreshIntervalTicks = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual interest refresh interval", + detachedVisualInterestRefreshIntervalTicks, + MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS); + } + + public void setDetachedVisualCandidateRefreshIntervalTicks( + int detachedVisualCandidateRefreshIntervalTicks) { + this.detachedVisualCandidateRefreshIntervalTicks = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual candidate refresh interval", + detachedVisualCandidateRefreshIntervalTicks, + MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS); + } + + public void setDetachedVisualVisibilityCheckIntervalTicks( + int detachedVisualVisibilityCheckIntervalTicks) { + this.detachedVisualVisibilityCheckIntervalTicks = + PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Detached visual visibility check interval", + detachedVisualVisibilityCheckIntervalTicks, + MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS); + } + + @Nonnull + public String getDetachedVisualBlockType() { + return detachedVisualBlockType; + } + + public void setDetachedVisualBlockType(@Nonnull String detachedVisualBlockType) { + if (detachedVisualBlockType.isBlank()) { + throw new IllegalArgumentException("Detached visual block type cannot be blank"); + } + this.detachedVisualBlockType = detachedVisualBlockType; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java new file mode 100644 index 00000000..7f281d21 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java @@ -0,0 +1,345 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; + +import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import javax.annotation.Nonnull; +import lombok.Getter; +import lombok.Setter; + +/** + * Entity and follower transform sync sampling settings for a physics space. + */ +public class PhysicsVisualSyncSettings { + + /** + * Block radius around players where visual followers receive the full sync policy. + */ + public static final int DEFAULT_VISUAL_FULL_SYNC_RADIUS = 64; + + /** + * Hard block-radius cap for full-rate visual sync. + */ + public static final int MAX_VISUAL_FULL_SYNC_RADIUS = 512; + + /** + * Maximum block radius around players where visual followers receive any sync at all. + * Followers beyond this range stop writing Hytale transforms until they come back into + * interest. + */ + public static final int DEFAULT_VISUAL_MAX_SYNC_RADIUS = 128; + + /** + * Hard block-radius cap for any visual sync. + */ + public static final int MAX_VISUAL_MAX_SYNC_RADIUS = 1_024; + + /** + * Whether visuals beyond {@link #DEFAULT_VISUAL_MAX_SYNC_RADIUS} stop receiving transform sync. + */ + public static final boolean DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED = true; + + /** + * Minimum ticks between mid-range visual sync writes. A value of 1 means every tick is allowed. + */ + public static final int DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS = 1; + + /** + * Hard tick cap for mid-range visual sync intervals. + */ + public static final int MAX_VISUAL_MID_SYNC_INTERVAL_TICKS = 1_200; + + /** + * Minimum ticks between far-range visual sync writes when far cutoff is disabled. + */ + public static final int DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS = 40; + + /** + * Hard tick cap for far-range visual sync intervals. + */ + public static final int MAX_VISUAL_FAR_SYNC_INTERVAL_TICKS = 1_200; + + /** + * Whether visual prioritization should use backend raycasts for occlusion. + */ + @Nonnull + public static final VisualOcclusionMode DEFAULT_VISUAL_OCCLUSION_MODE = + VisualOcclusionMode.OFF; + + /** + * Maximum backend raycasts spent on visual occlusion per world tick. + */ + public static final int DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK = 256; + + /** + * Hard cap on visual occlusion backend raycasts spent per tick. + */ + public static final int MAX_VISUAL_OCCLUSION_RAYCASTS_PER_TICK = 4_096; + + /** + * Ticks a visual occlusion raycast result can be reused. + */ + public static final int DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS = 10; + + /** + * Hard tick cap for reusing visual occlusion raycast results. + */ + public static final int MAX_VISUAL_OCCLUSION_CACHE_TICKS = 1_200; + + /** + * Whether visual sync may predict near dynamic poses between published physics snapshots. + */ + public static final boolean DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED = false; + + /** + * Maximum seconds of visual pose prediction from the last published snapshot. + */ + public static final float DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS = 0.10f; + + /** + * Hard cap for visual snapshot prediction. + */ + public static final float MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS = 0.25f; + + /** + * Whether near dynamic visuals should ease toward published snapshots instead of snapping. + */ + public static final boolean DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED = false; + + /** + * Per-second rate used by visual snapshot smoothing. + */ + public static final float DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE = 14.0f; + + /** + * Hard cap for visual snapshot smoothing rate. + */ + public static final float MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE = 120.0f; + + /** + * Whether body-attached entity transforms should use player-interest culling. + * Disabled by default because gameplay code may rely on server-side transforms even + * when no player is currently near the body. + */ + public static final boolean DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED = false; + + /** + * Whether visual sync should require the body to be inside a player's approximate view cone. + * Disabled by default because custom/third-person cameras can diverge from head rotation. + */ + public static final boolean DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED = false; + + /** + * Full-rate visual sync radius for follower entities. + */ + @Getter + private int visualFullSyncRadius = DEFAULT_VISUAL_FULL_SYNC_RADIUS; + + /** + * Maximum visual sync radius for follower entities. + */ + @Getter + private int visualMaxSyncRadius = DEFAULT_VISUAL_MAX_SYNC_RADIUS; + + /** + * If enabled, visuals outside {@link #visualMaxSyncRadius} do not receive transform sync. + * If disabled, far visuals stay alive but sync at {@link #visualFarSyncIntervalTicks}. + */ + @Setter + @Getter + private boolean visualFarSyncCutoffEnabled = DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED; + + /** + * Minimum ticks between mid-range visual transform writes. + */ + @Getter + private int visualMidSyncIntervalTicks = DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS; + + /** + * Minimum ticks between far-range visual transform writes when hard cutoff is disabled. + */ + @Getter + private int visualFarSyncIntervalTicks = DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS; + + /** + * Optional raycast-backed visual occlusion behavior. + */ + @Nonnull + private VisualOcclusionMode visualOcclusionMode = DEFAULT_VISUAL_OCCLUSION_MODE; + + /** + * Backend raycast budget for visual occlusion checks. + */ + @Getter + private int visualOcclusionRaycastsPerTick = + DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK; + + /** + * Reuse window for visual occlusion raycast results. + */ + @Getter + private int visualOcclusionCacheTicks = DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS; + + /** + * If enabled, near dynamic visuals can dead-reckon briefly between snapshots. + */ + @Setter + @Getter + private boolean visualSnapshotPredictionEnabled = + DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED; + + /** + * Maximum dead-reckoning window for visual snapshot prediction. + */ + @Getter + private float visualSnapshotPredictionMaxSeconds = + DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS; + + /** + * If enabled, near dynamic visuals ease toward the latest snapshot target. + */ + @Getter + @Setter + private boolean visualSnapshotSmoothingEnabled = + DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED; + + /** + * Per-second convergence rate for visual snapshot smoothing. + */ + @Getter + private float visualSnapshotSmoothingRate = + DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE; + + /** + * If enabled, entity-backed physics body transforms use the same player-interest culling + * as follower visuals. Controlled bodies are always synced. + */ + @Setter + @Getter + private boolean entityVisualSyncCullingEnabled = DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED; + + /** + * If enabled, near-range visual sync also requires an approximate player view-cone hit. + * Keep disabled for custom cameras unless the server view direction matches the camera. + */ + @Setter + @Getter + private boolean visualVisibilityCullingEnabled = DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED; + + public PhysicsVisualSyncSettings() { + } + + public PhysicsVisualSyncSettings(@Nonnull PhysicsVisualSyncSettings settings) { + visualFullSyncRadius = settings.visualFullSyncRadius; + visualMaxSyncRadius = settings.visualMaxSyncRadius; + visualFarSyncCutoffEnabled = settings.visualFarSyncCutoffEnabled; + visualMidSyncIntervalTicks = settings.visualMidSyncIntervalTicks; + visualFarSyncIntervalTicks = settings.visualFarSyncIntervalTicks; + visualOcclusionMode = settings.visualOcclusionMode; + visualOcclusionRaycastsPerTick = settings.visualOcclusionRaycastsPerTick; + visualOcclusionCacheTicks = settings.visualOcclusionCacheTicks; + visualSnapshotPredictionEnabled = settings.visualSnapshotPredictionEnabled; + visualSnapshotPredictionMaxSeconds = settings.visualSnapshotPredictionMaxSeconds; + visualSnapshotSmoothingEnabled = settings.visualSnapshotSmoothingEnabled; + visualSnapshotSmoothingRate = settings.visualSnapshotSmoothingRate; + entityVisualSyncCullingEnabled = settings.entityVisualSyncCullingEnabled; + visualVisibilityCullingEnabled = settings.visualVisibilityCullingEnabled; + } + + public void setVisualFullSyncRadius(int visualFullSyncRadius) { + int boundedVisualFullSyncRadius = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual full sync radius", + visualFullSyncRadius, + MAX_VISUAL_FULL_SYNC_RADIUS); + if (boundedVisualFullSyncRadius > visualMaxSyncRadius) { + throw new IllegalArgumentException( + "Visual full sync radius cannot exceed visual max sync radius"); + } + this.visualFullSyncRadius = boundedVisualFullSyncRadius; + } + + public void setVisualMaxSyncRadius(int visualMaxSyncRadius) { + int boundedVisualMaxSyncRadius = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual max sync radius", + visualMaxSyncRadius, + MAX_VISUAL_MAX_SYNC_RADIUS); + if (boundedVisualMaxSyncRadius < visualFullSyncRadius) { + throw new IllegalArgumentException( + "Visual max sync radius cannot be lower than visual full sync radius"); + } + this.visualMaxSyncRadius = boundedVisualMaxSyncRadius; + } + + public void setVisualSyncRadii(int visualFullSyncRadius, int visualMaxSyncRadius) { + int boundedVisualFullSyncRadius = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual full sync radius", + visualFullSyncRadius, + MAX_VISUAL_FULL_SYNC_RADIUS); + int boundedVisualMaxSyncRadius = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual max sync radius", + visualMaxSyncRadius, + MAX_VISUAL_MAX_SYNC_RADIUS); + if (boundedVisualFullSyncRadius > boundedVisualMaxSyncRadius) { + throw new IllegalArgumentException( + "Visual full sync radius cannot exceed visual max sync radius"); + } + this.visualFullSyncRadius = boundedVisualFullSyncRadius; + this.visualMaxSyncRadius = boundedVisualMaxSyncRadius; + } + + public void setVisualMidSyncIntervalTicks(int visualMidSyncIntervalTicks) { + this.visualMidSyncIntervalTicks = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual mid sync interval", + visualMidSyncIntervalTicks, + MAX_VISUAL_MID_SYNC_INTERVAL_TICKS); + } + + public void setVisualFarSyncIntervalTicks(int visualFarSyncIntervalTicks) { + this.visualFarSyncIntervalTicks = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual far sync interval", + visualFarSyncIntervalTicks, + MAX_VISUAL_FAR_SYNC_INTERVAL_TICKS); + } + + @Nonnull + public VisualOcclusionMode getVisualOcclusionMode() { + return visualOcclusionMode; + } + + public void setVisualOcclusionMode(@Nonnull VisualOcclusionMode visualOcclusionMode) { + this.visualOcclusionMode = visualOcclusionMode; + } + + public void setVisualOcclusionRaycastsPerTick(int visualOcclusionRaycastsPerTick) { + this.visualOcclusionRaycastsPerTick = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual occlusion raycasts per tick", + visualOcclusionRaycastsPerTick, + MAX_VISUAL_OCCLUSION_RAYCASTS_PER_TICK); + } + + public void setVisualOcclusionCacheTicks(int visualOcclusionCacheTicks) { + this.visualOcclusionCacheTicks = PhysicsEntitySettingsValidation.requirePositiveAtMost( + "Visual occlusion cache ticks", + visualOcclusionCacheTicks, + MAX_VISUAL_OCCLUSION_CACHE_TICKS); + } + + public void setVisualSnapshotPredictionMaxSeconds(float visualSnapshotPredictionMaxSeconds) { + if (!Float.isFinite(visualSnapshotPredictionMaxSeconds) + || visualSnapshotPredictionMaxSeconds < 0.0f + || visualSnapshotPredictionMaxSeconds > MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS) { + throw new IllegalArgumentException("Visual snapshot prediction max seconds must be between 0 and " + + MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS); + } + this.visualSnapshotPredictionMaxSeconds = visualSnapshotPredictionMaxSeconds; + } + + public void setVisualSnapshotSmoothingRate(float visualSnapshotSmoothingRate) { + if (!Float.isFinite(visualSnapshotSmoothingRate) + || visualSnapshotSmoothingRate <= 0.0f + || visualSnapshotSmoothingRate > MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE) { + throw new IllegalArgumentException("Visual snapshot smoothing rate must be > 0 and <= " + + MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE); + } + this.visualSnapshotSmoothingRate = visualSnapshotSmoothingRate; + } + +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java new file mode 100644 index 00000000..7f3e3225 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java @@ -0,0 +1,4 @@ +/** + * Public settings owned by the bundled PhysicsEntity subplugin. + */ +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java index 442f5141..1b4d7c99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java @@ -1,273 +1,27 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import lombok.Getter; -import lombok.Setter; import javax.annotation.Nonnull; /** - * Generated visual proxy settings for detached physics bodies. + * @deprecated Use + * {@link dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings}. */ -public class PhysicsVisualMaterializationSettings { - - /** - * Whether detached bodies should automatically create disposable Hytale visual followers near players. - * Disabled by default because integrators may provide their own render/materialization layer. - */ - public static final boolean DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED = false; - - /** - * Block radius around players where detached bodies materialize visual followers. - */ - public static final int DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS = 64; - - /** - * Hard block-radius cap for detached visual proxy materialization. - */ - public static final int MAX_DETACHED_VISUAL_MATERIALIZATION_RADIUS = 512; - - /** - * Larger radius used to avoid rapid visual proxy despawn/respawn at the edge. - */ - public static final int DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS = 80; - - /** - * Hard block-radius cap for detached visual proxy dematerialization. - */ - public static final int MAX_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS = 1_024; - - /** - * Maximum detached visual proxies spawned per world tick. - */ - public static final int DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK = 128; - - /** - * Hard cap on detached visual proxy spawns per tick. - */ - public static final int MAX_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK = 512; - - /** - * Maximum detached visual proxies allowed in one physics space at once. - */ - public static final int DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED = 1024; - - /** - * Hard cap on detached visual proxies materialized in one physics space. - */ - public static final int MAX_DETACHED_VISUAL_MAX_MATERIALIZED = 16_384; - - /** - * Default visual proxy block type. Integrators should override this for their own content. - */ - @Nonnull - public static final String DEFAULT_DETACHED_VISUAL_BLOCK_TYPE = "Rock_Stone"; - - /** - * Ticks between refreshing player/synthetic visual interests for detached materialization. - */ - public static final int DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS = 4; - - /** - * Ticks between refreshing detached materialization near-query/raycast spawn candidates. - */ - public static final int DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS = 4; - - /** - * Ticks between checking existing generated proxies for dematerialization. - */ - public static final int DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS = 10; - - /** - * Hard tick cap for detached visual materialization cache intervals. - */ - public static final int MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS = 1_200; - - /** - * If enabled, detached physics bodies create disposable visual followers near players. - */ - @Setter - @Getter - private boolean detachedVisualMaterializationEnabled = - DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED; - - /** - * Radius where detached bodies become visual followers. - */ - @Getter - private int detachedVisualMaterializationRadius = - DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS; - - /** - * Radius where detached visual followers are removed again. - */ - @Getter - private int detachedVisualDematerializationRadius = - DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS; - - /** - * Per-tick cap for spawning detached visual followers. - */ - @Getter - private int detachedVisualMaxSpawnsPerTick = - DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK; - - /** - * Total cap for detached visual followers in this space. - */ - @Getter - private int detachedVisualMaxMaterialized = - DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED; - - /** - * Refresh cadence for player/synthetic interests used by detached visual materialization. - */ - @Getter - private int detachedVisualInterestRefreshIntervalTicks = - DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS; - - /** - * Refresh cadence for detached visual materialization near-query/raycast candidates. - */ - @Getter - private int detachedVisualCandidateRefreshIntervalTicks = - DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS; - - /** - * Refresh cadence for existing generated-proxy visibility/dematerialization checks. - */ - @Getter - private int detachedVisualVisibilityCheckIntervalTicks = - DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS; - - /** - * Fallback Hytale block type for generated detached visual proxies. - * - *

        This is only a default materialization hint for simple demos and stress tests. Integrators - * that need body-specific visuals should store their own visual description on the physics body - * or disable generated visual materialization.

        - */ - @Nonnull - private String detachedVisualBlockType = DEFAULT_DETACHED_VISUAL_BLOCK_TYPE; +@Deprecated(forRemoval = false) +public class PhysicsVisualMaterializationSettings + extends dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings + .PhysicsVisualMaterializationSettings { public PhysicsVisualMaterializationSettings() { } public PhysicsVisualMaterializationSettings( - @Nonnull PhysicsVisualMaterializationSettings settings) { - detachedVisualMaterializationEnabled = settings.detachedVisualMaterializationEnabled; - detachedVisualMaterializationRadius = settings.detachedVisualMaterializationRadius; - detachedVisualDematerializationRadius = settings.detachedVisualDematerializationRadius; - detachedVisualMaxSpawnsPerTick = settings.detachedVisualMaxSpawnsPerTick; - detachedVisualMaxMaterialized = settings.detachedVisualMaxMaterialized; - detachedVisualInterestRefreshIntervalTicks = - settings.detachedVisualInterestRefreshIntervalTicks; - detachedVisualCandidateRefreshIntervalTicks = - settings.detachedVisualCandidateRefreshIntervalTicks; - detachedVisualVisibilityCheckIntervalTicks = - settings.detachedVisualVisibilityCheckIntervalTicks; - detachedVisualBlockType = settings.detachedVisualBlockType; - } - - public void setDetachedVisualMaterializationRadius( - int detachedVisualMaterializationRadius) { - int boundedDetachedVisualMaterializationRadius = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual materialization radius", - detachedVisualMaterializationRadius, - MAX_DETACHED_VISUAL_MATERIALIZATION_RADIUS); - if (boundedDetachedVisualMaterializationRadius > detachedVisualDematerializationRadius) { - throw new IllegalArgumentException( - "Detached visual materialization radius cannot exceed dematerialization radius"); - } - this.detachedVisualMaterializationRadius = boundedDetachedVisualMaterializationRadius; - } - - public void setDetachedVisualDematerializationRadius( - int detachedVisualDematerializationRadius) { - int boundedDetachedVisualDematerializationRadius = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual dematerialization radius", - detachedVisualDematerializationRadius, - MAX_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS); - if (boundedDetachedVisualDematerializationRadius < detachedVisualMaterializationRadius) { - throw new IllegalArgumentException( - "Detached visual dematerialization radius cannot be lower than materialization radius"); - } - this.detachedVisualDematerializationRadius = boundedDetachedVisualDematerializationRadius; - } - - public void setDetachedVisualRadii(int detachedVisualMaterializationRadius, - int detachedVisualDematerializationRadius) { - int boundedDetachedVisualMaterializationRadius = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual materialization radius", - detachedVisualMaterializationRadius, - MAX_DETACHED_VISUAL_MATERIALIZATION_RADIUS); - int boundedDetachedVisualDematerializationRadius = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual dematerialization radius", - detachedVisualDematerializationRadius, - MAX_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS); - if (boundedDetachedVisualMaterializationRadius - > boundedDetachedVisualDematerializationRadius) { - throw new IllegalArgumentException( - "Detached visual materialization radius cannot exceed dematerialization radius"); - } - this.detachedVisualMaterializationRadius = boundedDetachedVisualMaterializationRadius; - this.detachedVisualDematerializationRadius = boundedDetachedVisualDematerializationRadius; - } - - public void setDetachedVisualMaxSpawnsPerTick(int detachedVisualMaxSpawnsPerTick) { - this.detachedVisualMaxSpawnsPerTick = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual max spawns per tick", - detachedVisualMaxSpawnsPerTick, - MAX_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK); - } - - public void setDetachedVisualMaxMaterialized(int detachedVisualMaxMaterialized) { - this.detachedVisualMaxMaterialized = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual max materialized", - detachedVisualMaxMaterialized, - MAX_DETACHED_VISUAL_MAX_MATERIALIZED); - } - - public void setDetachedVisualInterestRefreshIntervalTicks( - int detachedVisualInterestRefreshIntervalTicks) { - this.detachedVisualInterestRefreshIntervalTicks = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual interest refresh interval", - detachedVisualInterestRefreshIntervalTicks, - MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS); + @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings + .PhysicsVisualMaterializationSettings settings) { + super(settings); } - public void setDetachedVisualCandidateRefreshIntervalTicks( - int detachedVisualCandidateRefreshIntervalTicks) { - this.detachedVisualCandidateRefreshIntervalTicks = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual candidate refresh interval", - detachedVisualCandidateRefreshIntervalTicks, - MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS); - } - - public void setDetachedVisualVisibilityCheckIntervalTicks( - int detachedVisualVisibilityCheckIntervalTicks) { - this.detachedVisualVisibilityCheckIntervalTicks = - PhysicsSettingsValidation.requirePositiveAtMost( - "Detached visual visibility check interval", - detachedVisualVisibilityCheckIntervalTicks, - MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS); - } - - @Nonnull - public String getDetachedVisualBlockType() { - return detachedVisualBlockType; - } - - public void setDetachedVisualBlockType(@Nonnull String detachedVisualBlockType) { - if (detachedVisualBlockType.isBlank()) { - throw new IllegalArgumentException("Detached visual block type cannot be blank"); - } - this.detachedVisualBlockType = detachedVisualBlockType; + public PhysicsVisualMaterializationSettings( + @Nonnull PhysicsVisualMaterializationSettings settings) { + super(settings); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java index 055eb393..355d02bc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java @@ -1,344 +1,26 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import lombok.Getter; -import lombok.Setter; import javax.annotation.Nonnull; /** - * Entity and follower transform sync sampling settings for a physics space. + * @deprecated Use + * {@link dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings}. */ -public class PhysicsVisualSyncSettings { - - /** - * Block radius around players where visual followers receive the full sync policy. - */ - public static final int DEFAULT_VISUAL_FULL_SYNC_RADIUS = 64; - - /** - * Hard block-radius cap for full-rate visual sync. - */ - public static final int MAX_VISUAL_FULL_SYNC_RADIUS = 512; - - /** - * Maximum block radius around players where visual followers receive any sync at all. - * Followers beyond this range stop writing Hytale transforms until they come back into - * interest. - */ - public static final int DEFAULT_VISUAL_MAX_SYNC_RADIUS = 128; - - /** - * Hard block-radius cap for any visual sync. - */ - public static final int MAX_VISUAL_MAX_SYNC_RADIUS = 1_024; - - /** - * Whether visuals beyond {@link #DEFAULT_VISUAL_MAX_SYNC_RADIUS} stop receiving transform sync. - */ - public static final boolean DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED = true; - - /** - * Minimum ticks between mid-range visual sync writes. A value of 1 means every tick is allowed. - */ - public static final int DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS = 1; - - /** - * Hard tick cap for mid-range visual sync intervals. - */ - public static final int MAX_VISUAL_MID_SYNC_INTERVAL_TICKS = 1_200; - - /** - * Minimum ticks between far-range visual sync writes when far cutoff is disabled. - */ - public static final int DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS = 40; - - /** - * Hard tick cap for far-range visual sync intervals. - */ - public static final int MAX_VISUAL_FAR_SYNC_INTERVAL_TICKS = 1_200; - - /** - * Whether visual prioritization should use backend raycasts for occlusion. - */ - @Nonnull - public static final VisualOcclusionMode DEFAULT_VISUAL_OCCLUSION_MODE = - VisualOcclusionMode.OFF; - - /** - * Maximum backend raycasts spent on visual occlusion per world tick. - */ - public static final int DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK = 256; - - /** - * Hard cap on visual occlusion backend raycasts spent per tick. - */ - public static final int MAX_VISUAL_OCCLUSION_RAYCASTS_PER_TICK = 4_096; - - /** - * Ticks a visual occlusion raycast result can be reused. - */ - public static final int DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS = 10; - - /** - * Hard tick cap for reusing visual occlusion raycast results. - */ - public static final int MAX_VISUAL_OCCLUSION_CACHE_TICKS = 1_200; - - /** - * Whether visual sync may predict near dynamic poses between published physics snapshots. - */ - public static final boolean DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED = false; - - /** - * Maximum seconds of visual pose prediction from the last published snapshot. - */ - public static final float DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS = 0.10f; - - /** - * Hard cap for visual snapshot prediction. - */ - public static final float MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS = 0.25f; - - /** - * Whether near dynamic visuals should ease toward published snapshots instead of snapping. - */ - public static final boolean DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED = false; - - /** - * Per-second rate used by visual snapshot smoothing. - */ - public static final float DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE = 14.0f; - - /** - * Hard cap for visual snapshot smoothing rate. - */ - public static final float MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE = 120.0f; - - /** - * Whether body-attached entity transforms should use player-interest culling. - * Disabled by default because gameplay code may rely on server-side transforms even - * when no player is currently near the body. - */ - public static final boolean DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED = false; - - /** - * Whether visual sync should require the body to be inside a player's approximate view cone. - * Disabled by default because custom/third-person cameras can diverge from head rotation. - */ - public static final boolean DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED = false; - - /** - * Full-rate visual sync radius for follower entities. - */ - @Getter - private int visualFullSyncRadius = DEFAULT_VISUAL_FULL_SYNC_RADIUS; - - /** - * Maximum visual sync radius for follower entities. - */ - @Getter - private int visualMaxSyncRadius = DEFAULT_VISUAL_MAX_SYNC_RADIUS; - - /** - * If enabled, visuals outside {@link #visualMaxSyncRadius} do not receive transform sync. - * If disabled, far visuals stay alive but sync at {@link #visualFarSyncIntervalTicks}. - */ - @Setter - @Getter - private boolean visualFarSyncCutoffEnabled = DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED; - - /** - * Minimum ticks between mid-range visual transform writes. - */ - @Getter - private int visualMidSyncIntervalTicks = DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS; - - /** - * Minimum ticks between far-range visual transform writes when hard cutoff is disabled. - */ - @Getter - private int visualFarSyncIntervalTicks = DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS; - - /** - * Optional raycast-backed visual occlusion behavior. - */ - @Nonnull - private VisualOcclusionMode visualOcclusionMode = DEFAULT_VISUAL_OCCLUSION_MODE; - - /** - * Backend raycast budget for visual occlusion checks. - */ - @Getter - private int visualOcclusionRaycastsPerTick = - DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK; - - /** - * Reuse window for visual occlusion raycast results. - */ - @Getter - private int visualOcclusionCacheTicks = DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS; - - /** - * If enabled, near dynamic visuals can dead-reckon briefly between snapshots. - */ - @Setter - @Getter - private boolean visualSnapshotPredictionEnabled = - DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED; - - /** - * Maximum dead-reckoning window for visual snapshot prediction. - */ - @Getter - private float visualSnapshotPredictionMaxSeconds = - DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS; - - /** - * If enabled, near dynamic visuals ease toward the latest snapshot target. - */ - @Getter - @Setter - private boolean visualSnapshotSmoothingEnabled = - DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED; - - /** - * Per-second convergence rate for visual snapshot smoothing. - */ - @Getter - private float visualSnapshotSmoothingRate = - DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE; - - /** - * If enabled, entity-backed physics body transforms use the same player-interest culling - * as follower visuals. Controlled bodies are always synced. - */ - @Setter - @Getter - private boolean entityVisualSyncCullingEnabled = DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED; - - /** - * If enabled, near-range visual sync also requires an approximate player view-cone hit. - * Keep disabled for custom cameras unless the server view direction matches the camera. - */ - @Setter - @Getter - private boolean visualVisibilityCullingEnabled = DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED; +@Deprecated(forRemoval = false) +public class PhysicsVisualSyncSettings + extends dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings + .PhysicsVisualSyncSettings { public PhysicsVisualSyncSettings() { } - public PhysicsVisualSyncSettings(@Nonnull PhysicsVisualSyncSettings settings) { - visualFullSyncRadius = settings.visualFullSyncRadius; - visualMaxSyncRadius = settings.visualMaxSyncRadius; - visualFarSyncCutoffEnabled = settings.visualFarSyncCutoffEnabled; - visualMidSyncIntervalTicks = settings.visualMidSyncIntervalTicks; - visualFarSyncIntervalTicks = settings.visualFarSyncIntervalTicks; - visualOcclusionMode = settings.visualOcclusionMode; - visualOcclusionRaycastsPerTick = settings.visualOcclusionRaycastsPerTick; - visualOcclusionCacheTicks = settings.visualOcclusionCacheTicks; - visualSnapshotPredictionEnabled = settings.visualSnapshotPredictionEnabled; - visualSnapshotPredictionMaxSeconds = settings.visualSnapshotPredictionMaxSeconds; - visualSnapshotSmoothingEnabled = settings.visualSnapshotSmoothingEnabled; - visualSnapshotSmoothingRate = settings.visualSnapshotSmoothingRate; - entityVisualSyncCullingEnabled = settings.entityVisualSyncCullingEnabled; - visualVisibilityCullingEnabled = settings.visualVisibilityCullingEnabled; - } - - public void setVisualFullSyncRadius(int visualFullSyncRadius) { - int boundedVisualFullSyncRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual full sync radius", - visualFullSyncRadius, - MAX_VISUAL_FULL_SYNC_RADIUS); - if (boundedVisualFullSyncRadius > visualMaxSyncRadius) { - throw new IllegalArgumentException( - "Visual full sync radius cannot exceed visual max sync radius"); - } - this.visualFullSyncRadius = boundedVisualFullSyncRadius; - } - - public void setVisualMaxSyncRadius(int visualMaxSyncRadius) { - int boundedVisualMaxSyncRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual max sync radius", - visualMaxSyncRadius, - MAX_VISUAL_MAX_SYNC_RADIUS); - if (boundedVisualMaxSyncRadius < visualFullSyncRadius) { - throw new IllegalArgumentException( - "Visual max sync radius cannot be lower than visual full sync radius"); - } - this.visualMaxSyncRadius = boundedVisualMaxSyncRadius; - } - - public void setVisualSyncRadii(int visualFullSyncRadius, int visualMaxSyncRadius) { - int boundedVisualFullSyncRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual full sync radius", - visualFullSyncRadius, - MAX_VISUAL_FULL_SYNC_RADIUS); - int boundedVisualMaxSyncRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual max sync radius", - visualMaxSyncRadius, - MAX_VISUAL_MAX_SYNC_RADIUS); - if (boundedVisualFullSyncRadius > boundedVisualMaxSyncRadius) { - throw new IllegalArgumentException( - "Visual full sync radius cannot exceed visual max sync radius"); - } - this.visualFullSyncRadius = boundedVisualFullSyncRadius; - this.visualMaxSyncRadius = boundedVisualMaxSyncRadius; - } - - public void setVisualMidSyncIntervalTicks(int visualMidSyncIntervalTicks) { - this.visualMidSyncIntervalTicks = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual mid sync interval", - visualMidSyncIntervalTicks, - MAX_VISUAL_MID_SYNC_INTERVAL_TICKS); + public PhysicsVisualSyncSettings( + @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings + .PhysicsVisualSyncSettings settings) { + super(settings); } - public void setVisualFarSyncIntervalTicks(int visualFarSyncIntervalTicks) { - this.visualFarSyncIntervalTicks = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual far sync interval", - visualFarSyncIntervalTicks, - MAX_VISUAL_FAR_SYNC_INTERVAL_TICKS); - } - - @Nonnull - public VisualOcclusionMode getVisualOcclusionMode() { - return visualOcclusionMode; - } - - public void setVisualOcclusionMode(@Nonnull VisualOcclusionMode visualOcclusionMode) { - this.visualOcclusionMode = visualOcclusionMode; - } - - public void setVisualOcclusionRaycastsPerTick(int visualOcclusionRaycastsPerTick) { - this.visualOcclusionRaycastsPerTick = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual occlusion raycasts per tick", - visualOcclusionRaycastsPerTick, - MAX_VISUAL_OCCLUSION_RAYCASTS_PER_TICK); - } - - public void setVisualOcclusionCacheTicks(int visualOcclusionCacheTicks) { - this.visualOcclusionCacheTicks = PhysicsSettingsValidation.requirePositiveAtMost( - "Visual occlusion cache ticks", - visualOcclusionCacheTicks, - MAX_VISUAL_OCCLUSION_CACHE_TICKS); - } - - public void setVisualSnapshotPredictionMaxSeconds(float visualSnapshotPredictionMaxSeconds) { - if (!Float.isFinite(visualSnapshotPredictionMaxSeconds) - || visualSnapshotPredictionMaxSeconds < 0.0f - || visualSnapshotPredictionMaxSeconds > MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS) { - throw new IllegalArgumentException("Visual snapshot prediction max seconds must be between 0 and " - + MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS); - } - this.visualSnapshotPredictionMaxSeconds = visualSnapshotPredictionMaxSeconds; - } - - public void setVisualSnapshotSmoothingRate(float visualSnapshotSmoothingRate) { - if (!Float.isFinite(visualSnapshotSmoothingRate) - || visualSnapshotSmoothingRate <= 0.0f - || visualSnapshotSmoothingRate > MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE) { - throw new IllegalArgumentException("Visual snapshot smoothing rate must be > 0 and <= " - + MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE); - } - this.visualSnapshotSmoothingRate = visualSnapshotSmoothingRate; + public PhysicsVisualSyncSettings(@Nonnull PhysicsVisualSyncSettings settings) { + super(settings); } - } diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index a8262ade..781f6cca 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -11,6 +11,7 @@ exports dev.hytalemodding.impulse.core.plugin.modules.control; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; + exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk; exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; exports dev.hytalemodding.impulse.core.plugin.persistence; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index ac106792..c4a30760 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -25,8 +25,8 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisuals.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisuals.java index 0af9b398..225291cf 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisuals.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisuals.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.server.core.modules.physics.component.Velocity; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3d; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index ad71d7a0..569a2a69 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -28,7 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.ArrayList; From 304c7be5f7a40fd7f4b370b29751b4e54616070c Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:32:39 +0200 Subject: [PATCH 377/534] chore(core): tidy debug profiling lookups Signed-off-by: Blovien --- .../core/internal/systems/debug/PhysicsDebugSystem.java | 3 ++- .../modules/physicschunk/PhysicsChunkTerrainProfiling.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index fbdabe3d..f5dc7021 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -87,6 +87,7 @@ public Set> getDependencies() { public void tick(float dt, int index, @Nonnull Store store) { World world = store.getExternalData().getWorld(); PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); + assert PhysicsDebugResource.getResourceType() != null; PhysicsDebugResource debug = store.getResource(PhysicsDebugResource.getResourceType()); if (!debug.hasSubscribers()) { @@ -292,7 +293,7 @@ private static int renderDetachedBodies(@Nonnull Collection viewers, RenderedBodyCount rendered = new RenderedBodyCount(); double maxDistanceSquared = viewRadius * viewRadius; for (SpaceId spaceId : resource.getSpaceIds()) { - resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, snapshotSpaceId, kind, persistenceMode) -> { + resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, _, kind, _) -> { if (rendered.hasReached(maxBodies)) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java index 9e6cdf25..676e60c6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java @@ -86,6 +86,7 @@ private static MissingSectionSampleView view( @Nonnull private static PhysicsRuntimeProfilingResource runtimeProfiling( @Nonnull Store store) { + assert PhysicsRuntimeProfilingResource.getResourceType() != null; return store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); } From fc153fd0556d284a8e1c5a425604bcc295124612 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:37:14 +0200 Subject: [PATCH 378/534] refactor(core): move collision lod settings under physicschunk Signed-off-by: Blovien --- .../commands/CollisionLodSettingsCommand.java | 2 +- .../CollisionLodSettingsComponent.java | 2 +- .../modules/physicschunk/package-info.java | 3 + .../PhysicsChunkSettingsValidation.java | 16 ++ .../settings/PhysicsCollisionLodSettings.java | 162 ++++++++++++++++++ .../physicschunk/settings/package-info.java | 4 + .../settings/PhysicsCollisionLodSettings.java | 160 ++--------------- impulse-core/src/module-info/module-info.java | 1 + .../commands/stress/StressBodiesCommand.java | 2 +- 9 files changed, 201 insertions(+), 151 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsCollisionLodSettings.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java index 02df11b9..6329f09c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java index cf58daa1..0bb39adf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java index 2f851671..911c25f3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java @@ -1,4 +1,7 @@ /** * Public API for the bundled PhysicsChunk terrain subplugin. + * + *

        Collision LOD settings live under + * {@code dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings}.

        */ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java new file mode 100644 index 00000000..198dcf0d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java @@ -0,0 +1,16 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; + +import javax.annotation.Nonnull; + +final class PhysicsChunkSettingsValidation { + + private PhysicsChunkSettingsValidation() { + } + + static int requirePositiveAtMost(@Nonnull String label, int value, int maxValue) { + if (value < 1 || value > maxValue) { + throw new IllegalArgumentException(label + " must be between 1 and " + maxValue); + } + return value; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsCollisionLodSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsCollisionLodSettings.java new file mode 100644 index 00000000..bd9533e1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsCollisionLodSettings.java @@ -0,0 +1,162 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; + +import javax.annotation.Nonnull; +import lombok.Getter; +import lombok.Setter; + +/** + * Distance-based dynamic-body collision LOD settings for a physics space. + */ +@Getter +public class PhysicsCollisionLodSettings { + + /** + * Whether distance-based dynamic-body collision LOD is active for this space. + */ + public static final boolean DEFAULT_COLLISION_LOD_ENABLED = false; + + /** + * Radius where managed dynamic bodies keep full terrain plus dynamic-body collision. + */ + public static final int DEFAULT_COLLISION_LOD_NEAR_RADIUS = 64; + + /** + * Radius where managed dynamic bodies keep terrain collision but drop dynamic-body collision. + */ + public static final int DEFAULT_COLLISION_LOD_MID_RADIUS = 128; + + /** + * Hard block-radius cap for collision LOD tiers. + */ + public static final int MAX_COLLISION_LOD_RADIUS = 1_024; + + /** + * Extra radius used before downgrading an already higher-priority collision tier. + */ + public static final int DEFAULT_COLLISION_LOD_HYSTERESIS = 16; + + /** + * Hard block-radius cap for collision LOD hysteresis. + */ + public static final int MAX_COLLISION_LOD_HYSTERESIS = 256; + + /** + * Ticks between refreshing distance-based collision LOD decisions. + */ + public static final int DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS = 10; + + /** + * Hard tick cap for collision LOD refreshes. + */ + public static final int MAX_COLLISION_LOD_REFRESH_INTERVAL_TICKS = 1_200; + + /** + * Whether far managed dynamic bodies should be put to sleep after collision is reduced. + */ + public static final boolean DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED = true; + + /** + * If enabled, default dynamic bodies can reduce dynamic-body collision away from players. + */ + @Setter + private boolean collisionLodEnabled = DEFAULT_COLLISION_LOD_ENABLED; + + /** + * Full-collision radius for collision LOD. + */ + private int collisionLodNearRadius = DEFAULT_COLLISION_LOD_NEAR_RADIUS; + + /** + * Terrain-only radius for collision LOD. + */ + private int collisionLodMidRadius = DEFAULT_COLLISION_LOD_MID_RADIUS; + + /** + * Downgrade hysteresis for collision LOD. + */ + private int collisionLodHysteresis = DEFAULT_COLLISION_LOD_HYSTERESIS; + + /** + * Refresh cadence for collision LOD scans. + */ + private int collisionLodRefreshIntervalTicks = + DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS; + + /** + * If enabled, far collision LOD bodies are put to sleep after collision is reduced. + */ + @Setter + private boolean collisionLodFarSleepEnabled = DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED; + + public PhysicsCollisionLodSettings() { + } + + public PhysicsCollisionLodSettings(@Nonnull PhysicsCollisionLodSettings settings) { + collisionLodEnabled = settings.collisionLodEnabled; + collisionLodNearRadius = settings.collisionLodNearRadius; + collisionLodMidRadius = settings.collisionLodMidRadius; + collisionLodHysteresis = settings.collisionLodHysteresis; + collisionLodRefreshIntervalTicks = settings.collisionLodRefreshIntervalTicks; + collisionLodFarSleepEnabled = settings.collisionLodFarSleepEnabled; + } + + public void setCollisionLodNearRadius(int collisionLodNearRadius) { + int boundedNearRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "Collision LOD near radius", + collisionLodNearRadius, + MAX_COLLISION_LOD_RADIUS); + if (boundedNearRadius > collisionLodMidRadius) { + throw new IllegalArgumentException( + "Collision LOD near radius cannot exceed mid radius"); + } + this.collisionLodNearRadius = boundedNearRadius; + } + + public void setCollisionLodMidRadius(int collisionLodMidRadius) { + int boundedMidRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "Collision LOD mid radius", + collisionLodMidRadius, + MAX_COLLISION_LOD_RADIUS); + if (boundedMidRadius < collisionLodNearRadius) { + throw new IllegalArgumentException( + "Collision LOD mid radius cannot be lower than near radius"); + } + this.collisionLodMidRadius = boundedMidRadius; + } + + public void setCollisionLodRadii(int collisionLodNearRadius, + int collisionLodMidRadius) { + int boundedNearRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "Collision LOD near radius", + collisionLodNearRadius, + MAX_COLLISION_LOD_RADIUS); + int boundedMidRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "Collision LOD mid radius", + collisionLodMidRadius, + MAX_COLLISION_LOD_RADIUS); + if (boundedNearRadius > boundedMidRadius) { + throw new IllegalArgumentException( + "Collision LOD near radius cannot exceed mid radius"); + } + this.collisionLodNearRadius = boundedNearRadius; + this.collisionLodMidRadius = boundedMidRadius; + } + + public void setCollisionLodHysteresis(int collisionLodHysteresis) { + if (collisionLodHysteresis < 0 + || collisionLodHysteresis > MAX_COLLISION_LOD_HYSTERESIS) { + throw new IllegalArgumentException("Collision LOD hysteresis must be between 0 and " + + MAX_COLLISION_LOD_HYSTERESIS); + } + this.collisionLodHysteresis = collisionLodHysteresis; + } + + public void setCollisionLodRefreshIntervalTicks(int collisionLodRefreshIntervalTicks) { + this.collisionLodRefreshIntervalTicks = + PhysicsChunkSettingsValidation.requirePositiveAtMost( + "Collision LOD refresh interval", + collisionLodRefreshIntervalTicks, + MAX_COLLISION_LOD_REFRESH_INTERVAL_TICKS); + } + +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java new file mode 100644 index 00000000..b9173e12 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java @@ -0,0 +1,4 @@ +/** + * Public settings owned by the bundled PhysicsChunk subplugin. + */ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java index 0ec72e42..ddb36227 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java @@ -1,162 +1,26 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import lombok.Getter; -import lombok.Setter; import javax.annotation.Nonnull; /** - * Distance-based dynamic-body collision LOD settings for a physics space. + * @deprecated Use + * {@link dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings}. */ -@Getter -public class PhysicsCollisionLodSettings { - - /** - * Whether distance-based dynamic-body collision LOD is active for this space. - */ - public static final boolean DEFAULT_COLLISION_LOD_ENABLED = false; - - /** - * Radius where managed dynamic bodies keep full terrain plus dynamic-body collision. - */ - public static final int DEFAULT_COLLISION_LOD_NEAR_RADIUS = 64; - - /** - * Radius where managed dynamic bodies keep terrain collision but drop dynamic-body collision. - */ - public static final int DEFAULT_COLLISION_LOD_MID_RADIUS = 128; - - /** - * Hard block-radius cap for collision LOD tiers. - */ - public static final int MAX_COLLISION_LOD_RADIUS = 1_024; - - /** - * Extra radius used before downgrading an already higher-priority collision tier. - */ - public static final int DEFAULT_COLLISION_LOD_HYSTERESIS = 16; - - /** - * Hard block-radius cap for collision LOD hysteresis. - */ - public static final int MAX_COLLISION_LOD_HYSTERESIS = 256; - - /** - * Ticks between refreshing distance-based collision LOD decisions. - */ - public static final int DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS = 10; - - /** - * Hard tick cap for collision LOD refreshes. - */ - public static final int MAX_COLLISION_LOD_REFRESH_INTERVAL_TICKS = 1_200; - - /** - * Whether far managed dynamic bodies should be put to sleep after collision is reduced. - */ - public static final boolean DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED = true; - - /** - * If enabled, default dynamic bodies can reduce dynamic-body collision away from players. - */ - @Setter - private boolean collisionLodEnabled = DEFAULT_COLLISION_LOD_ENABLED; - - /** - * Full-collision radius for collision LOD. - */ - private int collisionLodNearRadius = DEFAULT_COLLISION_LOD_NEAR_RADIUS; - - /** - * Terrain-only radius for collision LOD. - */ - private int collisionLodMidRadius = DEFAULT_COLLISION_LOD_MID_RADIUS; - - /** - * Downgrade hysteresis for collision LOD. - */ - private int collisionLodHysteresis = DEFAULT_COLLISION_LOD_HYSTERESIS; - - /** - * Refresh cadence for collision LOD scans. - */ - private int collisionLodRefreshIntervalTicks = - DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS; - - /** - * If enabled, far collision LOD bodies are put to sleep after collision is reduced. - */ - @Setter - private boolean collisionLodFarSleepEnabled = DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED; +@Deprecated(forRemoval = false) +public class PhysicsCollisionLodSettings + extends dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsCollisionLodSettings { public PhysicsCollisionLodSettings() { } - public PhysicsCollisionLodSettings(@Nonnull PhysicsCollisionLodSettings settings) { - collisionLodEnabled = settings.collisionLodEnabled; - collisionLodNearRadius = settings.collisionLodNearRadius; - collisionLodMidRadius = settings.collisionLodMidRadius; - collisionLodHysteresis = settings.collisionLodHysteresis; - collisionLodRefreshIntervalTicks = settings.collisionLodRefreshIntervalTicks; - collisionLodFarSleepEnabled = settings.collisionLodFarSleepEnabled; - } - - public void setCollisionLodNearRadius(int collisionLodNearRadius) { - int boundedNearRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Collision LOD near radius", - collisionLodNearRadius, - MAX_COLLISION_LOD_RADIUS); - if (boundedNearRadius > collisionLodMidRadius) { - throw new IllegalArgumentException( - "Collision LOD near radius cannot exceed mid radius"); - } - this.collisionLodNearRadius = boundedNearRadius; - } - - public void setCollisionLodMidRadius(int collisionLodMidRadius) { - int boundedMidRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Collision LOD mid radius", - collisionLodMidRadius, - MAX_COLLISION_LOD_RADIUS); - if (boundedMidRadius < collisionLodNearRadius) { - throw new IllegalArgumentException( - "Collision LOD mid radius cannot be lower than near radius"); - } - this.collisionLodMidRadius = boundedMidRadius; + public PhysicsCollisionLodSettings( + @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsCollisionLodSettings settings) { + super(settings); } - public void setCollisionLodRadii(int collisionLodNearRadius, - int collisionLodMidRadius) { - int boundedNearRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Collision LOD near radius", - collisionLodNearRadius, - MAX_COLLISION_LOD_RADIUS); - int boundedMidRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "Collision LOD mid radius", - collisionLodMidRadius, - MAX_COLLISION_LOD_RADIUS); - if (boundedNearRadius > boundedMidRadius) { - throw new IllegalArgumentException( - "Collision LOD near radius cannot exceed mid radius"); - } - this.collisionLodNearRadius = boundedNearRadius; - this.collisionLodMidRadius = boundedMidRadius; - } - - public void setCollisionLodHysteresis(int collisionLodHysteresis) { - if (collisionLodHysteresis < 0 - || collisionLodHysteresis > MAX_COLLISION_LOD_HYSTERESIS) { - throw new IllegalArgumentException("Collision LOD hysteresis must be between 0 and " - + MAX_COLLISION_LOD_HYSTERESIS); - } - this.collisionLodHysteresis = collisionLodHysteresis; - } - - public void setCollisionLodRefreshIntervalTicks(int collisionLodRefreshIntervalTicks) { - this.collisionLodRefreshIntervalTicks = - PhysicsSettingsValidation.requirePositiveAtMost( - "Collision LOD refresh interval", - collisionLodRefreshIntervalTicks, - MAX_COLLISION_LOD_REFRESH_INTERVAL_TICKS); + public PhysicsCollisionLodSettings(@Nonnull PhysicsCollisionLodSettings settings) { + super(settings); } - } diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 781f6cca..7909c100 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -14,6 +14,7 @@ exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk; exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; + exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physicsstore; exports dev.hytalemodding.impulse.core.plugin.resources; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index c4a30760..94d75e81 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -22,7 +22,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; From 80b71ae08a302405779a292496a71abea3292196 Mon Sep 17 00:00:00 2001 From: Blovien Date: Wed, 17 Jun 2026 22:39:47 +0200 Subject: [PATCH 379/534] test(core): guard physicschunk collision lod compatibility Signed-off-by: Blovien --- .../PhysicsChunkNamingSourceGuardTest.java | 2 ++ .../settings/PhysicsSpaceSettingsTest.java | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java index 590df597..d4b6d047 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java @@ -49,6 +49,8 @@ void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { file + " should use PhysicsChunkTerrain names"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings"), file + " should use PhysicsChunkTerrainSettings"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings"), + file + " should use PhysicsChunk settings"); } } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index d0ed0380..26930a54 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -212,6 +212,40 @@ void groupedAccessorsExposeIndependentDomainState() { assertEquals(4, settings.getCollisionLodSettings().getCollisionLodHysteresis()); } + @Test + void deprecatedCollisionLodSettingsAliasCopiesCanonicalAndAliasValues() { + dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsCollisionLodSettings canonical = + new dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsCollisionLodSettings(); + + canonical.setCollisionLodEnabled(true); + canonical.setCollisionLodRadii(24, 96); + canonical.setCollisionLodHysteresis(6); + canonical.setCollisionLodRefreshIntervalTicks(8); + canonical.setCollisionLodFarSleepEnabled(false); + + PhysicsCollisionLodSettings canonicalCopy = + new PhysicsCollisionLodSettings(canonical); + PhysicsCollisionLodSettings aliasCopy = + new PhysicsCollisionLodSettings(canonicalCopy); + canonical.setCollisionLodRadii(32, 128); + canonicalCopy.setCollisionLodRadii(40, 160); + + assertTrue(canonicalCopy.isCollisionLodEnabled()); + assertEquals(40, canonicalCopy.getCollisionLodNearRadius()); + assertEquals(160, canonicalCopy.getCollisionLodMidRadius()); + assertEquals(6, canonicalCopy.getCollisionLodHysteresis()); + assertEquals(8, canonicalCopy.getCollisionLodRefreshIntervalTicks()); + assertFalse(canonicalCopy.isCollisionLodFarSleepEnabled()); + assertTrue(aliasCopy.isCollisionLodEnabled()); + assertEquals(24, aliasCopy.getCollisionLodNearRadius()); + assertEquals(96, aliasCopy.getCollisionLodMidRadius()); + assertEquals(6, aliasCopy.getCollisionLodHysteresis()); + assertEquals(8, aliasCopy.getCollisionLodRefreshIntervalTicks()); + assertFalse(aliasCopy.isCollisionLodFarSleepEnabled()); + } + @Test void extensionSettingsAreTypedAndCopyIsolated() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); From 8db48e40915cb9f726a92eb55df2f38402752339 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:11:43 +0200 Subject: [PATCH 380/534] refactor(core): move terrain settings under physicschunk Signed-off-by: Blovien --- .../PhysicsChunkBuildOptions.java | 2 +- .../PhysicsChunkTerrainRuntime.java | 2 +- .../commands/PhysicsChunkSettingsCommand.java | 2 +- .../PersistentPhysicsStorePreflight.java | 2 +- .../persistence/PersistentSpaceDto.java | 2 +- .../PhysicsWorldRuntimeResource.java | 2 +- .../PhysicsChunkTerrainComponent.java | 2 +- .../components/WorldCollisionComponent.java | 2 +- .../PhysicsChunkSettingsValidation.java | 7 + .../settings/PhysicsChunkTerrainSettings.java | 287 ++++++++++++++++++ .../settings/PhysicsChunkTerrainSettings.java | 284 +---------------- .../PhysicsWorldCollisionSettings.java | 9 +- .../commands/stress/StressBodiesCommand.java | 2 +- 13 files changed, 323 insertions(+), 282 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index 308bb1a4..c8c19233 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import java.util.Objects; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java index 058286c1..c401b8d3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java @@ -6,7 +6,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import it.unimi.dsi.fastutil.ints.Int2LongMap; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index e4dc71ba..c17fc821 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index 42776efb..4ca4f804 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -2,7 +2,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import java.util.ArrayList; import java.util.HashSet; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index 80c869d1..706e5558 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 4d7f3f8a..a3dbfbb4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -58,7 +58,7 @@ import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java index 42f4a5c8..ae0426f1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java index 422e8ef4..0666730f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java @@ -3,7 +3,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java index 198dcf0d..a9ba9625 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkSettingsValidation.java @@ -13,4 +13,11 @@ static int requirePositiveAtMost(@Nonnull String label, int value, int maxValue) } return value; } + + static float requireFiniteAtLeast(@Nonnull String label, float value, float minValue) { + if (!Float.isFinite(value) || value < minValue) { + throw new IllegalArgumentException(label + " must be finite and >= " + minValue); + } + return value; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java new file mode 100644 index 00000000..d0cda729 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java @@ -0,0 +1,287 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; + +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Terrain collider streaming settings for a PhysicsStore space. + */ +public class PhysicsChunkTerrainSettings { + + /** + * Block radius around each tracked player for streaming terrain colliders. + */ + public static final int DEFAULT_TERRAIN_RADIUS = 8; + + /** + * Hard block-radius cap for player-centered PhysicsChunk terrain streaming. + */ + public static final int MAX_TERRAIN_RADIUS = 128; + + /** + * Block radius around each active dynamic physics body for streaming terrain colliders. + */ + public static final int DEFAULT_BODY_TERRAIN_RADIUS = 4; + + /** + * Hard block-radius cap for dynamic-body PhysicsChunk terrain streaming. + */ + public static final int MAX_BODY_TERRAIN_RADIUS = 64; + + /** + * Ticks before an unused section's terrain colliders are pruned. + */ + public static final int DEFAULT_TERRAIN_TTL_TICKS = 100; + + /** + * Hard tick cap for retaining unused streamed terrain sections. + */ + public static final int MAX_TERRAIN_TTL_TICKS = 12_000; + + /** + * Default behavior when an entity-backed body reaches an unloaded chunk border. + */ + @Nonnull + public static final EntityChunkBoundaryMode DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE = + EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED; + + /** + * Whether full-cube world sections should use native backend voxel terrain when available. + */ + public static final boolean DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED = false; + + /** + * Default friction applied to generated terrain collider bodies. + */ + public static final float DEFAULT_TERRAIN_FRICTION = 0.75f; + + /** + * Default restitution applied to generated terrain collider bodies. + */ + public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; + + /** + * @deprecated Use {@link #DEFAULT_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int DEFAULT_WORLD_COLLISION_RADIUS = DEFAULT_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #MAX_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int MAX_WORLD_COLLISION_RADIUS = MAX_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #DEFAULT_BODY_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int DEFAULT_WORLD_COLLISION_BODY_RADIUS = DEFAULT_BODY_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #MAX_BODY_TERRAIN_RADIUS}. + */ + @Deprecated(forRemoval = false) + public static final int MAX_WORLD_COLLISION_BODY_RADIUS = MAX_BODY_TERRAIN_RADIUS; + + /** + * @deprecated Use {@link #DEFAULT_TERRAIN_TTL_TICKS}. + */ + @Deprecated(forRemoval = false) + public static final int DEFAULT_WORLD_COLLISION_TTL_TICKS = DEFAULT_TERRAIN_TTL_TICKS; + + /** + * @deprecated Use {@link #MAX_TERRAIN_TTL_TICKS}. + */ + @Deprecated(forRemoval = false) + public static final int MAX_WORLD_COLLISION_TTL_TICKS = MAX_TERRAIN_TTL_TICKS; + + @Nonnull + private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; + @Nonnull + private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + private boolean nativeVoxelTerrainEnabled = DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private int terrainRadius = DEFAULT_TERRAIN_RADIUS; + private int bodyTerrainRadius = DEFAULT_BODY_TERRAIN_RADIUS; + private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; + private float terrainFriction = DEFAULT_TERRAIN_FRICTION; + private float terrainRestitution = DEFAULT_TERRAIN_RESTITUTION; + + public PhysicsChunkTerrainSettings() { + } + + public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings) { + terrainMode = settings.terrainMode; + entityChunkBoundaryMode = settings.entityChunkBoundaryMode; + nativeVoxelTerrainEnabled = settings.nativeVoxelTerrainEnabled; + terrainRadius = settings.terrainRadius; + bodyTerrainRadius = settings.bodyTerrainRadius; + terrainTtlTicks = settings.terrainTtlTicks; + terrainFriction = settings.terrainFriction; + terrainRestitution = settings.terrainRestitution; + } + + @Nonnull + public PhysicsChunkTerrainMode getTerrainMode() { + return terrainMode; + } + + public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { + this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + } + + @Nonnull + public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { + return entityChunkBoundaryMode; + } + + public void setEntityChunkBoundaryMode( + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); + } + + public boolean isNativeVoxelTerrainEnabled() { + return nativeVoxelTerrainEnabled; + } + + public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { + this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + } + + public int getTerrainRadius() { + return terrainRadius; + } + + public void setTerrainRadius(int terrainRadius) { + this.terrainRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "PhysicsChunk terrain radius", + terrainRadius, + MAX_TERRAIN_RADIUS); + } + + public int getBodyTerrainRadius() { + return bodyTerrainRadius; + } + + public void setBodyTerrainRadius(int bodyTerrainRadius) { + this.bodyTerrainRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "PhysicsChunk terrain body radius", + bodyTerrainRadius, + MAX_BODY_TERRAIN_RADIUS); + } + + public int getTerrainTtlTicks() { + return terrainTtlTicks; + } + + public void setTerrainTtlTicks(int terrainTtlTicks) { + this.terrainTtlTicks = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "PhysicsChunk terrain TTL", + terrainTtlTicks, + MAX_TERRAIN_TTL_TICKS); + } + + public float getTerrainFriction() { + return terrainFriction; + } + + public void setTerrainFriction(float terrainFriction) { + this.terrainFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( + "Terrain friction", + terrainFriction, + 0.0f); + } + + public float getTerrainRestitution() { + return terrainRestitution; + } + + public void setTerrainRestitution(float terrainRestitution) { + this.terrainRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( + "Terrain restitution", + terrainRestitution, + 0.0f); + } + + public void setTerrainMaterial(float terrainFriction, float terrainRestitution) { + float validatedFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( + "Terrain friction", + terrainFriction, + 0.0f); + float validatedRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( + "Terrain restitution", + terrainRestitution, + 0.0f); + this.terrainFriction = validatedFriction; + this.terrainRestitution = validatedRestitution; + } + + /** + * @deprecated Use {@link #getTerrainMode()}. + */ + @Deprecated(forRemoval = false) + @Nonnull + public WorldCollisionMode getWorldCollisionMode() { + return terrainMode.toWorldCollisionMode(); + } + + /** + * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionMode(@Nonnull WorldCollisionMode worldCollisionMode) { + setTerrainMode(worldCollisionMode.toPhysicsChunkTerrainMode()); + } + + /** + * @deprecated Use {@link #getTerrainRadius()}. + */ + @Deprecated(forRemoval = false) + public int getWorldCollisionRadius() { + return getTerrainRadius(); + } + + /** + * @deprecated Use {@link #setTerrainRadius(int)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionRadius(int worldCollisionRadius) { + setTerrainRadius(worldCollisionRadius); + } + + /** + * @deprecated Use {@link #getBodyTerrainRadius()}. + */ + @Deprecated(forRemoval = false) + public int getWorldCollisionBodyRadius() { + return getBodyTerrainRadius(); + } + + /** + * @deprecated Use {@link #setBodyTerrainRadius(int)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionBodyRadius(int worldCollisionBodyRadius) { + setBodyTerrainRadius(worldCollisionBodyRadius); + } + + /** + * @deprecated Use {@link #getTerrainTtlTicks()}. + */ + @Deprecated(forRemoval = false) + public int getWorldCollisionTtlTicks() { + return getTerrainTtlTicks(); + } + + /** + * @deprecated Use {@link #setTerrainTtlTicks(int)}. + */ + @Deprecated(forRemoval = false) + public void setWorldCollisionTtlTicks(int worldCollisionTtlTicks) { + setTerrainTtlTicks(worldCollisionTtlTicks); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java index cab29576..a86b09ae 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java @@ -1,286 +1,26 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import java.util.Objects; import javax.annotation.Nonnull; /** - * Terrain collider streaming settings for a PhysicsStore space. + * @deprecated Use + * {@link dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings}. */ -public class PhysicsChunkTerrainSettings { - - /** - * Block radius around each tracked player for streaming terrain colliders. - */ - public static final int DEFAULT_TERRAIN_RADIUS = 8; - - /** - * Hard block-radius cap for player-centered PhysicsChunk terrain streaming. - */ - public static final int MAX_TERRAIN_RADIUS = 128; - - /** - * Block radius around each active dynamic physics body for streaming terrain colliders. - */ - public static final int DEFAULT_BODY_TERRAIN_RADIUS = 4; - - /** - * Hard block-radius cap for dynamic-body PhysicsChunk terrain streaming. - */ - public static final int MAX_BODY_TERRAIN_RADIUS = 64; - - /** - * Ticks before an unused section's terrain colliders are pruned. - */ - public static final int DEFAULT_TERRAIN_TTL_TICKS = 100; - - /** - * Hard tick cap for retaining unused streamed terrain sections. - */ - public static final int MAX_TERRAIN_TTL_TICKS = 12_000; - - /** - * Default behavior when an entity-backed body reaches an unloaded chunk border. - */ - @Nonnull - public static final EntityChunkBoundaryMode DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE = - EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED; - - /** - * Whether full-cube world sections should use native backend voxel terrain when available. - */ - public static final boolean DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED = false; - - /** - * Default friction applied to generated terrain collider bodies. - */ - public static final float DEFAULT_TERRAIN_FRICTION = 0.75f; - - /** - * Default restitution applied to generated terrain collider bodies. - */ - public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; - - /** - * @deprecated Use {@link #DEFAULT_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int DEFAULT_WORLD_COLLISION_RADIUS = DEFAULT_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #MAX_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int MAX_WORLD_COLLISION_RADIUS = MAX_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #DEFAULT_BODY_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int DEFAULT_WORLD_COLLISION_BODY_RADIUS = DEFAULT_BODY_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #MAX_BODY_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int MAX_WORLD_COLLISION_BODY_RADIUS = MAX_BODY_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #DEFAULT_TERRAIN_TTL_TICKS}. - */ - @Deprecated(forRemoval = false) - public static final int DEFAULT_WORLD_COLLISION_TTL_TICKS = DEFAULT_TERRAIN_TTL_TICKS; - - /** - * @deprecated Use {@link #MAX_TERRAIN_TTL_TICKS}. - */ - @Deprecated(forRemoval = false) - public static final int MAX_WORLD_COLLISION_TTL_TICKS = MAX_TERRAIN_TTL_TICKS; - - @Nonnull - private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; - @Nonnull - private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - private boolean nativeVoxelTerrainEnabled = DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; - private int terrainRadius = DEFAULT_TERRAIN_RADIUS; - private int bodyTerrainRadius = DEFAULT_BODY_TERRAIN_RADIUS; - private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; - private float terrainFriction = DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = DEFAULT_TERRAIN_RESTITUTION; +@Deprecated(forRemoval = false) +public class PhysicsChunkTerrainSettings + extends dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsChunkTerrainSettings { public PhysicsChunkTerrainSettings() { } - public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings) { - terrainMode = settings.terrainMode; - entityChunkBoundaryMode = settings.entityChunkBoundaryMode; - nativeVoxelTerrainEnabled = settings.nativeVoxelTerrainEnabled; - terrainRadius = settings.terrainRadius; - bodyTerrainRadius = settings.bodyTerrainRadius; - terrainTtlTicks = settings.terrainTtlTicks; - terrainFriction = settings.terrainFriction; - terrainRestitution = settings.terrainRestitution; - } - - @Nonnull - public PhysicsChunkTerrainMode getTerrainMode() { - return terrainMode; - } - - public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { - this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); - } - - @Nonnull - public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { - return entityChunkBoundaryMode; - } - - public void setEntityChunkBoundaryMode( - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { - this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, - "entityChunkBoundaryMode"); - } - - public boolean isNativeVoxelTerrainEnabled() { - return nativeVoxelTerrainEnabled; - } - - public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { - this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; - } - - public int getTerrainRadius() { - return terrainRadius; - } - - public void setTerrainRadius(int terrainRadius) { - this.terrainRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain radius", - terrainRadius, - MAX_TERRAIN_RADIUS); + public PhysicsChunkTerrainSettings( + @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsChunkTerrainSettings settings) { + super(settings); } - public int getBodyTerrainRadius() { - return bodyTerrainRadius; - } - - public void setBodyTerrainRadius(int bodyTerrainRadius) { - this.bodyTerrainRadius = PhysicsSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain body radius", - bodyTerrainRadius, - MAX_BODY_TERRAIN_RADIUS); - } - - public int getTerrainTtlTicks() { - return terrainTtlTicks; - } - - public void setTerrainTtlTicks(int terrainTtlTicks) { - this.terrainTtlTicks = PhysicsSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain TTL", - terrainTtlTicks, - MAX_TERRAIN_TTL_TICKS); - } - - public float getTerrainFriction() { - return terrainFriction; - } - - public void setTerrainFriction(float terrainFriction) { - this.terrainFriction = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain friction", - terrainFriction, - 0.0f); - } - - public float getTerrainRestitution() { - return terrainRestitution; - } - - public void setTerrainRestitution(float terrainRestitution) { - this.terrainRestitution = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain restitution", - terrainRestitution, - 0.0f); - } - - public void setTerrainMaterial(float terrainFriction, float terrainRestitution) { - float validatedFriction = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain friction", - terrainFriction, - 0.0f); - float validatedRestitution = PhysicsSettingsValidation.requireFiniteAtLeast( - "Terrain restitution", - terrainRestitution, - 0.0f); - this.terrainFriction = validatedFriction; - this.terrainRestitution = validatedRestitution; - } - - /** - * @deprecated Use {@link #getTerrainMode()}. - */ - @Deprecated(forRemoval = false) - @Nonnull - public WorldCollisionMode getWorldCollisionMode() { - return terrainMode.toWorldCollisionMode(); - } - - /** - * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionMode(@Nonnull WorldCollisionMode worldCollisionMode) { - setTerrainMode(worldCollisionMode.toPhysicsChunkTerrainMode()); - } - - /** - * @deprecated Use {@link #getTerrainRadius()}. - */ - @Deprecated(forRemoval = false) - public int getWorldCollisionRadius() { - return getTerrainRadius(); - } - - /** - * @deprecated Use {@link #setTerrainRadius(int)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionRadius(int worldCollisionRadius) { - setTerrainRadius(worldCollisionRadius); - } - - /** - * @deprecated Use {@link #getBodyTerrainRadius()}. - */ - @Deprecated(forRemoval = false) - public int getWorldCollisionBodyRadius() { - return getBodyTerrainRadius(); - } - - /** - * @deprecated Use {@link #setBodyTerrainRadius(int)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionBodyRadius(int worldCollisionBodyRadius) { - setBodyTerrainRadius(worldCollisionBodyRadius); - } - - /** - * @deprecated Use {@link #getTerrainTtlTicks()}. - */ - @Deprecated(forRemoval = false) - public int getWorldCollisionTtlTicks() { - return getTerrainTtlTicks(); - } - - /** - * @deprecated Use {@link #setTerrainTtlTicks(int)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionTtlTicks(int worldCollisionTtlTicks) { - setTerrainTtlTicks(worldCollisionTtlTicks); + public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings) { + super(settings); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java index 77af5db6..5ae8a84a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java @@ -3,7 +3,8 @@ import javax.annotation.Nonnull; /** - * @deprecated Use {@link PhysicsChunkTerrainSettings}. + * @deprecated Use + * {@link dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings}. */ @Deprecated(forRemoval = false) public class PhysicsWorldCollisionSettings extends PhysicsChunkTerrainSettings { @@ -11,6 +12,12 @@ public class PhysicsWorldCollisionSettings extends PhysicsChunkTerrainSettings { public PhysicsWorldCollisionSettings() { } + public PhysicsWorldCollisionSettings( + @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsChunkTerrainSettings settings) { + super(settings); + } + public PhysicsWorldCollisionSettings(@Nonnull PhysicsChunkTerrainSettings settings) { super(settings); } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 94d75e81..187b29f2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -27,7 +27,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; From 1f7e2a86df44456f36bd8f0d42a860e426eb1fcb Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:13:16 +0200 Subject: [PATCH 381/534] test(core): guard terrain settings compatibility Signed-off-by: Blovien --- .../PhysicsChunkNamingSourceGuardTest.java | 2 + .../VoxelTerrainCollisionCacheTest.java | 2 +- .../PersistentSpaceDtoSettingsTest.java | 2 +- .../settings/PhysicsSpaceSettingsTest.java | 38 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java index d4b6d047..45de1a3f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java @@ -49,6 +49,8 @@ void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { file + " should use PhysicsChunkTerrain names"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings"), file + " should use PhysicsChunkTerrainSettings"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings"), + file + " should use PhysicsChunk settings"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings"), file + " should use PhysicsChunk settings"); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java index f4eec231..784a2a04 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index 6716f5ab..e53a0acc 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index 26930a54..34c4f51f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -246,6 +246,44 @@ void deprecatedCollisionLodSettingsAliasCopiesCanonicalAndAliasValues() { assertFalse(aliasCopy.isCollisionLodFarSleepEnabled()); } + @Test + void deprecatedTerrainSettingsAliasCopiesCanonicalAndAliasValues() { + dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsChunkTerrainSettings canonical = + new dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings + .PhysicsChunkTerrainSettings(); + + canonical.setTerrainMode(PhysicsChunkTerrainMode.STREAMING); + canonical.setEntityChunkBoundaryMode(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK); + canonical.setNativeVoxelTerrainEnabled(true); + canonical.setTerrainRadius(18); + canonical.setBodyTerrainRadius(7); + canonical.setTerrainTtlTicks(240); + canonical.setTerrainMaterial(0.85f, 0.2f); + + PhysicsChunkTerrainSettings canonicalCopy = + new PhysicsChunkTerrainSettings(canonical); + PhysicsChunkTerrainSettings aliasCopy = + new PhysicsChunkTerrainSettings(canonicalCopy); + PhysicsWorldCollisionSettings worldCollisionCopy = + new PhysicsWorldCollisionSettings(canonical); + canonical.setTerrainRadius(24); + canonicalCopy.setTerrainRadius(30); + + assertEquals(PhysicsChunkTerrainMode.STREAMING, canonicalCopy.getTerrainMode()); + assertEquals(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK, + canonicalCopy.getEntityChunkBoundaryMode()); + assertTrue(canonicalCopy.isNativeVoxelTerrainEnabled()); + assertEquals(30, canonicalCopy.getTerrainRadius()); + assertEquals(7, canonicalCopy.getBodyTerrainRadius()); + assertEquals(240, canonicalCopy.getTerrainTtlTicks()); + assertEquals(0.85f, canonicalCopy.getTerrainFriction(), 0.0001f); + assertEquals(0.2f, canonicalCopy.getTerrainRestitution(), 0.0001f); + assertEquals(18, aliasCopy.getTerrainRadius()); + assertEquals(18, worldCollisionCopy.getTerrainRadius()); + assertEquals(18, worldCollisionCopy.getWorldCollisionRadius()); + } + @Test void extensionSettingsAreTypedAndCopyIsolated() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); From ca6703fc4484700d78cba697161ce028cd26c391 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:18:52 +0200 Subject: [PATCH 382/534] test(core): guard moved settings imports in examples Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkNamingSourceGuardTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java index 45de1a3f..4472b35c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java @@ -53,6 +53,10 @@ void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { file + " should use PhysicsChunk settings"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings"), file + " should use PhysicsChunk settings"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings"), + file + " should use PhysicsEntity settings"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings"), + file + " should use PhysicsEntity settings"); } } } From 3e24da76604545430b7eaa5ecf1dd3778efce3d1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:29:37 +0200 Subject: [PATCH 383/534] fix(core): wait for physics store owner lane before async mutations Signed-off-by: Blovien --- .../internal/resources/PhysicsWorldRuntimeResource.java | 8 +++++++- .../impulse/core/plugin/physicsstore/PhysicsWorlds.java | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index a3dbfbb4..3419aa80 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -322,7 +322,12 @@ private PhysicsMutationHandle enqueueAuthoritativePhysicsStoreMutation( World world = requireAuthoritativeWorld(operation); return PhysicsMutationHandle.fromCompletion(operation, value, - PhysicsThreading.executeOnWorldThread(world, operation, mutation)); + PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + operation, + store -> { + mutation.accept(store); + return null; + })); } private void runDirectRuntimeMutation(@Nonnull String operation, @@ -431,6 +436,7 @@ private void setWorldSettingsDirect(@Nonnull PhysicsWorldSettings settings) { private void setAuthoritativeWorldSettings(@Nonnull Store store, @Nonnull PhysicsWorldSettings settings) { + PhysicsThreading.requireBackendIdle(store, "set physics world settings"); validateAuthoritativeStepModeSupported(store, settings.getStepMode()); store.getResource(PhysicsWorldSettingsResource.getResourceType()).setSettings(settings); simulationRuntime.setWorldSettings(settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java index 495d1cab..e8043d98 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java @@ -58,6 +58,7 @@ public static void putSettings(@Nonnull Store store, @Nonnull PhysicsWorldSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore world settings"); + PhysicsThreading.requireBackendIdle(checkedStore, "update PhysicsStore world settings"); PhysicsWorldSettings requested = new PhysicsWorldSettings( Objects.requireNonNull(settings, "settings")); validateStepModeSupported(checkedStore, requested.getStepMode()); @@ -70,9 +71,12 @@ public static CompletionStage putSettingsAsync(@Nonnull World world, @Nonnull PhysicsWorldSettings settings) { PhysicsWorldSettings requested = new PhysicsWorldSettings( Objects.requireNonNull(settings, "settings")); - return PhysicsThreading.executeOnWorldThread(world, + return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, "queue PhysicsStore world settings update", - store -> putSettings(store, requested)); + store -> { + putSettings(store, requested); + return null; + }); } private static void validateStepModeSupported(@Nonnull Store store, From 57c90b1f3987ede7096c0f3fb28c75f4497c893c Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:30:47 +0200 Subject: [PATCH 384/534] test(core): guard physics store owner lane scheduling Signed-off-by: Blovien --- .../PhysicsChunkNamingSourceGuardTest.java | 16 ++++++++++++++ ...csStoreRuntimeBoundarySourceGuardTest.java | 22 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java index 4472b35c..9b160e4a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java @@ -43,6 +43,8 @@ void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { String source = Files.readString(file); assertFalse(source.contains("import dev.hytalemodding.impulse.core.internal."), file + " should use exported plugin APIs"); + assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.*;"), + file + " should not wildcard-import deprecated flat settings"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision"), file + " should use PhysicsChunkTerrain"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollision"), @@ -57,6 +59,20 @@ void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { file + " should use PhysicsEntity settings"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings"), file + " should use PhysicsEntity settings"); + assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision"), + file + " should use PhysicsChunkTerrain"); + assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollision"), + file + " should use PhysicsChunkTerrain names"); + assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings"), + file + " should use PhysicsChunkTerrainSettings"); + assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings"), + file + " should use PhysicsChunk settings"); + assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings"), + file + " should use PhysicsChunk settings"); + assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings"), + file + " should use PhysicsEntity settings"); + assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings"), + file + " should use PhysicsEntity settings"); } } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java index e33fe67d..105e7a24 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.internal.physicsstore; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.file.Files; @@ -76,6 +77,27 @@ void legacyWorldResourceFacadeDoesNotIterateRuntimeSpacesByUuid() throws IOExcep assertFalse(source.contains("forEachSpaceBinding")); } + @Test + void legacyAuthoritativeAsyncMutationsWaitForBackendIdle() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java")); + + assertTrue(source.contains("PhysicsThreading.callWhenBackendIdleOnWorldThread(world," + + System.lineSeparator() + " operation,")); + assertFalse(source.contains("PhysicsThreading.executeOnWorldThread(world, operation, mutation)")); + } + + @Test + void publicWorldSettingsAsyncWaitsForBackendIdle() throws IOException { + String source = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java")); + + assertTrue(source.contains("PhysicsThreading.callWhenBackendIdleOnWorldThread(world," + + System.lineSeparator() + " \"queue PhysicsStore world settings update\"")); + assertFalse(source.contains("PhysicsThreading.executeOnWorldThread(world," + + System.lineSeparator() + " \"queue PhysicsStore world settings update\"")); + } + @Test void runtimeResourceDoesNotExposeUuidRuntimeReadApis() throws IOException { String source = Files.readString(Path.of( From 0dd88e859dd0210621ce7df6bc594d848702567b Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:40:08 +0200 Subject: [PATCH 385/534] refactor(core): remove physicschunk compatibility aliases Signed-off-by: Blovien --- .../persistence/PersistentSpaceDto.java | 21 ++-- .../PhysicsComponentTypeRegistry.java | 2 +- .../core/plugin/body/PhysicsBodyKind.java | 8 +- .../physicschunk/PhysicsChunkTerrain.java | 8 -- .../PhysicsChunkTerrainBuildStats.java | 33 ------ .../physicschunk/PhysicsChunkTerrainMode.java | 17 +-- .../PhysicsChunkTerrainPrewarmStats.java | 15 --- .../PhysicsChunkTerrainStats.java | 21 ---- .../physicschunk/PhysicsWorldCollision.java | 77 ------------- .../PhysicsWorldCollisionProfiling.java | 92 --------------- .../WorldCollisionBuildStats.java | 17 --- .../physicschunk/WorldCollisionMode.java | 24 ---- .../WorldCollisionPrewarmStats.java | 9 -- .../physicschunk/WorldCollisionStats.java | 11 -- .../PhysicsChunkTerrainComponent.java | 77 ------------- .../components/WorldCollisionComponent.java | 108 ------------------ .../settings/PhysicsChunkTerrainSettings.java | 101 ---------------- .../settings/PhysicsChunkTerrainSettings.java | 26 ----- .../settings/PhysicsCollisionLodSettings.java | 26 ----- .../plugin/settings/PhysicsSpaceSettings.java | 27 +---- .../PhysicsVisualMaterializationSettings.java | 27 ----- .../settings/PhysicsVisualSyncSettings.java | 26 ----- .../PhysicsWorldCollisionSettings.java | 28 ----- 23 files changed, 16 insertions(+), 785 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index 706e5558..f47044b4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; @@ -22,7 +21,6 @@ import javax.annotation.Nonnull; import org.joml.Vector3f; -@SuppressWarnings("deprecation") public final class PersistentSpaceDto { @Nonnull @@ -45,13 +43,13 @@ public final class PersistentSpaceDto { .addValidator(PhysicsStorePersistenceValidation.finiteVector( "Persisted PhysicsStore space gravity must be finite")) .add() - .append(new KeyedCodec<>("WorldCollisionMode", - new EnumCodec<>(WorldCollisionMode.class), + .append(new KeyedCodec<>("PhysicsChunkTerrainMode", + new EnumCodec<>(PhysicsChunkTerrainMode.class), false), (dto, value) -> dto.terrainMode = value != null - ? value.toPhysicsChunkTerrainMode() + ? value : PhysicsChunkTerrainMode.NONE, - PersistentSpaceDto::getPersistedWorldCollisionMode) + PersistentSpaceDto::getTerrainMode) .add() .append(new KeyedCodec<>("EntityChunkBoundaryMode", new EnumCodec<>(EntityChunkBoundaryMode.class), @@ -65,19 +63,19 @@ public final class PersistentSpaceDto { (dto, value) -> dto.nativeVoxelTerrainEnabled = value != null && value, PersistentSpaceDto::isNativeVoxelTerrainEnabled) .add() - .append(new KeyedCodec<>("WorldCollisionRadius", Codec.INTEGER, false), + .append(new KeyedCodec<>("TerrainRadius", Codec.INTEGER, false), (dto, value) -> dto.terrainRadius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, PersistentSpaceDto::getTerrainRadius) .add() - .append(new KeyedCodec<>("WorldCollisionBodyRadius", Codec.INTEGER, false), + .append(new KeyedCodec<>("BodyTerrainRadius", Codec.INTEGER, false), (dto, value) -> dto.bodyTerrainRadius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, PersistentSpaceDto::getBodyTerrainRadius) .add() - .append(new KeyedCodec<>("WorldCollisionTtlTicks", Codec.INTEGER, false), + .append(new KeyedCodec<>("TerrainTtlTicks", Codec.INTEGER, false), (dto, value) -> dto.terrainTtlTicks = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, @@ -375,9 +373,4 @@ public PersistentSpaceDto copy() { collisionLodSettings, extensionSettings); } - - @Nonnull - private WorldCollisionMode getPersistedWorldCollisionMode() { - return terrainMode.toWorldCollisionMode(); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java index 0184d337..37a264b5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -94,7 +94,7 @@ public static void registerComponentTypes( TerrainColliderComponent.CODEC); physicsChunkTerrainComponentType = registry.registerComponent( PhysicsChunkTerrainComponent.class, - "WorldCollision", + "PhysicsChunkTerrain", PhysicsChunkTerrainComponent.CODEC); dynamicsComponentType = registry.registerComponent( DynamicsComponent.class, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java index eabae795..5c739fff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java @@ -6,16 +6,10 @@ */ public enum PhysicsBodyKind { BODY, - - /** - * @deprecated Use {@link #TERRAIN}. - */ - @Deprecated(forRemoval = false) - WORLD_COLLISION, TEMPORARY, TERRAIN; public boolean isTerrainCollider() { - return this == TERRAIN || this == WORLD_COLLISION; + return this == TERRAIN; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 431c298b..760b840d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -34,14 +34,6 @@ public static boolean isSubPluginEnabled() { return PhysicsChunkLifecycle.isEnabled(); } - /** - * @deprecated Use {@link #isSubPluginEnabled()}. - */ - @Deprecated(forRemoval = false) - public static boolean isModuleEnabled() { - return isSubPluginEnabled(); - } - @Nonnull public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, @Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java index e6f6c4cc..f98d2e4e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java @@ -1,7 +1,5 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; -import javax.annotation.Nonnull; - /** * Aggregate statistics from building or rebuilding streamed PhysicsChunk terrain geometry. */ @@ -15,35 +13,4 @@ public record PhysicsChunkTerrainBuildStats(int scannedBlocks, int sectionsBuilt, int sectionsRebuilt, int voxelBodies) { - - @Nonnull - @Deprecated(forRemoval = false) - public static PhysicsChunkTerrainBuildStats fromWorldCollisionStats( - @Nonnull WorldCollisionBuildStats stats) { - return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), - stats.solidBlocks(), - stats.culledInteriorBlocks(), - stats.fullCubeRuns(), - stats.detailBoxes(), - stats.colliderBodies(), - stats.removedBodies(), - stats.sectionsBuilt(), - stats.sectionsRebuilt(), - stats.voxelBodies()); - } - - @Nonnull - @Deprecated(forRemoval = false) - public WorldCollisionBuildStats toWorldCollisionStats() { - return new WorldCollisionBuildStats(scannedBlocks, - solidBlocks, - culledInteriorBlocks, - fullCubeRuns, - detailBoxes, - colliderBodies, - removedBodies, - sectionsBuilt, - sectionsRebuilt, - voxelBodies); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java index 0647d705..652a924e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java @@ -1,7 +1,5 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; -import javax.annotation.Nonnull; - /** * Controls PhysicsChunk terrain collider generation for a PhysicsStore space. */ @@ -19,18 +17,5 @@ public enum PhysicsChunkTerrainMode { /** * Terrain colliders stream around players and configured physics bodies. */ - STREAMING; - - @Nonnull - @Deprecated(forRemoval = false) - public WorldCollisionMode toWorldCollisionMode() { - return WorldCollisionMode.valueOf(name()); - } - - @Nonnull - @Deprecated(forRemoval = false) - public static PhysicsChunkTerrainMode fromWorldCollisionMode( - @Nonnull WorldCollisionMode mode) { - return valueOf(mode.name()); - } + STREAMING } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java index e878196d..63791fb2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java @@ -7,19 +7,4 @@ */ public record PhysicsChunkTerrainPrewarmStats(int sectionTargets, @Nonnull PhysicsChunkTerrainBuildStats buildStats) { - - @Nonnull - @Deprecated(forRemoval = false) - public static PhysicsChunkTerrainPrewarmStats fromWorldCollisionStats( - @Nonnull WorldCollisionPrewarmStats stats) { - return new PhysicsChunkTerrainPrewarmStats(stats.sectionTargets(), - PhysicsChunkTerrainBuildStats.fromWorldCollisionStats(stats.buildStats())); - } - - @Nonnull - @Deprecated(forRemoval = false) - public WorldCollisionPrewarmStats toWorldCollisionStats() { - return new WorldCollisionPrewarmStats(sectionTargets, - buildStats.toWorldCollisionStats()); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java index 2017e4f0..89104f70 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java @@ -1,7 +1,5 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; -import javax.annotation.Nonnull; - /** * Current size of the generated PhysicsChunk terrain cache. */ @@ -9,23 +7,4 @@ public record PhysicsChunkTerrainStats(int spaces, int sections, int bodies, int shapeTemplates) { - - @Nonnull - @Deprecated(forRemoval = false) - public static PhysicsChunkTerrainStats fromWorldCollisionStats( - @Nonnull WorldCollisionStats stats) { - return new PhysicsChunkTerrainStats(stats.spaces(), - stats.sections(), - stats.bodies(), - stats.shapeTemplates()); - } - - @Nonnull - @Deprecated(forRemoval = false) - public WorldCollisionStats toWorldCollisionStats() { - return new WorldCollisionStats(spaces, - sections, - bodies, - shapeTemplates); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java deleted file mode 100644 index 6b422c79..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollision.java +++ /dev/null @@ -1,77 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; -import javax.annotation.Nonnull; -import org.joml.Vector3d; - -/** - * @deprecated Use {@link PhysicsChunkTerrain}. - */ -@Deprecated(forRemoval = false) -public final class PhysicsWorldCollision { - - private PhysicsWorldCollision() { - } - - public static boolean isModuleEnabled() { - return PhysicsChunkTerrain.isSubPluginEnabled(); - } - - @Nonnull - public static WorldCollisionBuildStats rebuildAround(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - return PhysicsChunkTerrain.rebuildAround(world, - store, - spaceId, - center, - radius) - .toWorldCollisionStats(); - } - - @Nonnull - public static WorldCollisionBuildStats refreshAround(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - return PhysicsChunkTerrain.refreshAround(world, - store, - spaceId, - center, - radius) - .toWorldCollisionStats(); - } - - @Nonnull - public static WorldCollisionPrewarmStats ensureAround(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Iterable centers, - int radius, - long tick) { - return PhysicsChunkTerrain.ensureAround(world, - store, - spaceId, - centers, - radius, - tick) - .toWorldCollisionStats(); - } - - public static int clearSpace(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - return PhysicsChunkTerrain.clearSpace(world, store, spaceId); - } - - @Nonnull - public static WorldCollisionStats stats(@Nonnull World world) { - return PhysicsChunkTerrain.stats(world).toWorldCollisionStats(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java deleted file mode 100644 index fcb091e8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsWorldCollisionProfiling.java +++ /dev/null @@ -1,92 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import java.util.List; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * @deprecated Use {@link PhysicsChunkTerrainProfiling}. - */ -@Deprecated(forRemoval = false) -public final class PhysicsWorldCollisionProfiling { - - private PhysicsWorldCollisionProfiling() { - } - - public static boolean isRuntimeProfilingEnabled(@Nonnull Store store) { - return PhysicsChunkTerrainProfiling.isRuntimeProfilingEnabled(store); - } - - public static void setRuntimeProfilingEnabled(@Nonnull World world, - @Nonnull Store store, - boolean enabled) { - PhysicsChunkTerrainProfiling.setRuntimeProfilingEnabled(world, store, enabled); - } - - public static void resetRuntimeProfiling(@Nonnull World world, - @Nonnull Store store) { - PhysicsChunkTerrainProfiling.resetRuntimeProfiling(world, store); - } - - @Nonnull - public static Snapshots snapshots(@Nonnull Store store) { - return new Snapshots(PhysicsChunkTerrainProfiling.snapshots(store)); - } - - @Nonnull - public static List missingSectionSamples( - @Nonnull SnapshotView snapshot) { - return PhysicsChunkTerrainProfiling.missingSectionSamples(snapshot) - .stream() - .map(MissingSectionSampleView::new) - .toList(); - } - - public record Snapshots(@Nonnull SnapshotView cumulative, - @Nonnull SnapshotView latest, - @Nonnull SnapshotView worst, - boolean enabled) { - - private Snapshots(@Nonnull PhysicsChunkTerrainProfiling.Snapshots snapshots) { - this(new SnapshotView(snapshots.cumulative()), - new SnapshotView(snapshots.latest()), - new SnapshotView(snapshots.worst()), - snapshots.enabled()); - } - } - - public static final class SnapshotView extends PhysicsChunkTerrainProfiling.SnapshotView { - - private SnapshotView(@Nonnull PhysicsChunkTerrainProfiling.SnapshotView view) { - super(view.rawSnapshot()); - } - } - - public record MissingSectionSampleView(int chunkX, - int sectionY, - int chunkZ, - @Nonnull String reason, - @Nonnull String retainedEnvelopeStatus, - @Nonnull String targetType, - @Nullable UUID bodyUuid, - @Nullable String snapshotPosition, - @Nullable String livePosition) { - - private MissingSectionSampleView( - @Nonnull PhysicsChunkTerrainProfiling.MissingSectionSampleView sample) { - this(sample.chunkX(), - sample.sectionY(), - sample.chunkZ(), - sample.reason(), - sample.retainedEnvelopeStatus(), - sample.targetType(), - sample.bodyUuid(), - sample.snapshotPosition(), - sample.livePosition()); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java deleted file mode 100644 index dd893e22..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionBuildStats.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -/** - * @deprecated Use {@link PhysicsChunkTerrainBuildStats}. - */ -@Deprecated(forRemoval = false) -public record WorldCollisionBuildStats(int scannedBlocks, - int solidBlocks, - int culledInteriorBlocks, - int fullCubeRuns, - int detailBoxes, - int colliderBodies, - int removedBodies, - int sectionsBuilt, - int sectionsRebuilt, - int voxelBodies) { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java deleted file mode 100644 index 04caf731..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionMode.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -import javax.annotation.Nonnull; - -/** - * @deprecated Use {@link PhysicsChunkTerrainMode}. - */ -@Deprecated(forRemoval = false) -public enum WorldCollisionMode { - NONE, - MANUAL, - STREAMING; - - @Nonnull - public PhysicsChunkTerrainMode toPhysicsChunkTerrainMode() { - return PhysicsChunkTerrainMode.valueOf(name()); - } - - @Nonnull - public static WorldCollisionMode fromPhysicsChunkTerrainMode( - @Nonnull PhysicsChunkTerrainMode mode) { - return valueOf(mode.name()); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java deleted file mode 100644 index a0fa180c..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionPrewarmStats.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -/** - * @deprecated Use {@link PhysicsChunkTerrainPrewarmStats}. - */ -@Deprecated(forRemoval = false) -public record WorldCollisionPrewarmStats(int sectionTargets, - WorldCollisionBuildStats buildStats) { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java deleted file mode 100644 index 2c273b3b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/WorldCollisionStats.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -/** - * @deprecated Use {@link PhysicsChunkTerrainStats}. - */ -@Deprecated(forRemoval = false) -public record WorldCollisionStats(int spaces, - int sections, - int bodies, - int shapeTemplates) { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java index ae0426f1..2253862b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java @@ -9,11 +9,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -103,14 +101,6 @@ public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainSettings setting settings.getTerrainRestitution()); } - /** - * @deprecated Use {@link #PhysicsChunkTerrainComponent(PhysicsChunkTerrainSettings)}. - */ - @Deprecated(forRemoval = false) - public PhysicsChunkTerrainComponent(@Nonnull PhysicsWorldCollisionSettings settings) { - this((PhysicsChunkTerrainSettings) settings); - } - public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, boolean nativeVoxelTerrainEnabled, int radius, @@ -147,48 +137,6 @@ public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainMode terrainMode this.terrainRestitution = terrainRestitution; } - /** - * @deprecated Use {@link #PhysicsChunkTerrainComponent(PhysicsChunkTerrainMode, boolean, int, int, int, float, float)}. - */ - @Deprecated(forRemoval = false) - public PhysicsChunkTerrainComponent(@Nonnull WorldCollisionMode mode, - boolean nativeVoxelTerrainEnabled, - int radius, - int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { - this(mode.toPhysicsChunkTerrainMode(), - nativeVoxelTerrainEnabled, - radius, - bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); - } - - /** - * @deprecated Use {@link #PhysicsChunkTerrainComponent(PhysicsChunkTerrainMode, EntityChunkBoundaryMode, boolean, int, int, int, float, float)}. - */ - @Deprecated(forRemoval = false) - public PhysicsChunkTerrainComponent(@Nonnull WorldCollisionMode mode, - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, - int radius, - int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { - this(mode.toPhysicsChunkTerrainMode(), - entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, - radius, - bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); - } - @Nonnull public PhysicsChunkTerrainMode getTerrainMode() { return terrainMode; @@ -198,23 +146,6 @@ public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); } - /** - * @deprecated Use {@link #getTerrainMode()}. - */ - @Deprecated(forRemoval = false) - @Nonnull - public WorldCollisionMode getMode() { - return terrainMode.toWorldCollisionMode(); - } - - /** - * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. - */ - @Deprecated(forRemoval = false) - public void setMode(@Nonnull WorldCollisionMode mode) { - setTerrainMode(mode.toPhysicsChunkTerrainMode()); - } - @Nonnull public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { return entityChunkBoundaryMode; @@ -288,14 +219,6 @@ public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { settings.setTerrainMaterial(terrainFriction, terrainRestitution); } - /** - * @deprecated Use {@link #copyTo(PhysicsChunkTerrainSettings)}. - */ - @Deprecated(forRemoval = false) - public void copyTo(@Nonnull PhysicsWorldCollisionSettings settings) { - copyTo((PhysicsChunkTerrainSettings) settings); - } - @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.physicsChunkTerrainComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java deleted file mode 100644 index 0666730f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponent.java +++ /dev/null @@ -1,108 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; - -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings; -import javax.annotation.Nonnull; - -/** - * @deprecated Use {@link PhysicsChunkTerrainComponent}. The serialized component name remains - * {@code WorldCollision} for saved-world compatibility. - */ -@Deprecated(forRemoval = false) -public class WorldCollisionComponent extends PhysicsChunkTerrainComponent { - - public WorldCollisionComponent() { - } - - public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainSettings settings) { - super(settings); - } - - public WorldCollisionComponent(@Nonnull PhysicsWorldCollisionSettings settings) { - super(settings); - } - - public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, - boolean nativeVoxelTerrainEnabled, - int radius, - int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { - super(terrainMode, - nativeVoxelTerrainEnabled, - radius, - bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); - } - - public WorldCollisionComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, - int radius, - int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { - super(terrainMode, - entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, - radius, - bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); - } - - public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, - boolean nativeVoxelTerrainEnabled, - int radius, - int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { - super(mode, - nativeVoxelTerrainEnabled, - radius, - bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); - } - - public WorldCollisionComponent(@Nonnull WorldCollisionMode mode, - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, - int radius, - int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { - super(mode, - entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, - radius, - bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); - } - - @Nonnull - @Override - public WorldCollisionComponent clone() { - return new WorldCollisionComponent(getTerrainMode(), - getEntityChunkBoundaryMode(), - isNativeVoxelTerrainEnabled(), - getRadius(), - getBodyRadius(), - getTtlTicks(), - getTerrainFriction(), - getTerrainRestitution()); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java index d0cda729..8b3e3f43 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import java.util.Objects; import javax.annotation.Nonnull; @@ -63,42 +62,6 @@ public class PhysicsChunkTerrainSettings { */ public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; - /** - * @deprecated Use {@link #DEFAULT_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int DEFAULT_WORLD_COLLISION_RADIUS = DEFAULT_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #MAX_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int MAX_WORLD_COLLISION_RADIUS = MAX_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #DEFAULT_BODY_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int DEFAULT_WORLD_COLLISION_BODY_RADIUS = DEFAULT_BODY_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #MAX_BODY_TERRAIN_RADIUS}. - */ - @Deprecated(forRemoval = false) - public static final int MAX_WORLD_COLLISION_BODY_RADIUS = MAX_BODY_TERRAIN_RADIUS; - - /** - * @deprecated Use {@link #DEFAULT_TERRAIN_TTL_TICKS}. - */ - @Deprecated(forRemoval = false) - public static final int DEFAULT_WORLD_COLLISION_TTL_TICKS = DEFAULT_TERRAIN_TTL_TICKS; - - /** - * @deprecated Use {@link #MAX_TERRAIN_TTL_TICKS}. - */ - @Deprecated(forRemoval = false) - public static final int MAX_WORLD_COLLISION_TTL_TICKS = MAX_TERRAIN_TTL_TICKS; - @Nonnull private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull @@ -220,68 +183,4 @@ public void setTerrainMaterial(float terrainFriction, float terrainRestitution) this.terrainRestitution = validatedRestitution; } - /** - * @deprecated Use {@link #getTerrainMode()}. - */ - @Deprecated(forRemoval = false) - @Nonnull - public WorldCollisionMode getWorldCollisionMode() { - return terrainMode.toWorldCollisionMode(); - } - - /** - * @deprecated Use {@link #setTerrainMode(PhysicsChunkTerrainMode)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionMode(@Nonnull WorldCollisionMode worldCollisionMode) { - setTerrainMode(worldCollisionMode.toPhysicsChunkTerrainMode()); - } - - /** - * @deprecated Use {@link #getTerrainRadius()}. - */ - @Deprecated(forRemoval = false) - public int getWorldCollisionRadius() { - return getTerrainRadius(); - } - - /** - * @deprecated Use {@link #setTerrainRadius(int)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionRadius(int worldCollisionRadius) { - setTerrainRadius(worldCollisionRadius); - } - - /** - * @deprecated Use {@link #getBodyTerrainRadius()}. - */ - @Deprecated(forRemoval = false) - public int getWorldCollisionBodyRadius() { - return getBodyTerrainRadius(); - } - - /** - * @deprecated Use {@link #setBodyTerrainRadius(int)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionBodyRadius(int worldCollisionBodyRadius) { - setBodyTerrainRadius(worldCollisionBodyRadius); - } - - /** - * @deprecated Use {@link #getTerrainTtlTicks()}. - */ - @Deprecated(forRemoval = false) - public int getWorldCollisionTtlTicks() { - return getTerrainTtlTicks(); - } - - /** - * @deprecated Use {@link #setTerrainTtlTicks(int)}. - */ - @Deprecated(forRemoval = false) - public void setWorldCollisionTtlTicks(int worldCollisionTtlTicks) { - setTerrainTtlTicks(worldCollisionTtlTicks); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java deleted file mode 100644 index a86b09ae..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsChunkTerrainSettings.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.settings; - -import javax.annotation.Nonnull; - -/** - * @deprecated Use - * {@link dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings}. - */ -@Deprecated(forRemoval = false) -public class PhysicsChunkTerrainSettings - extends dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsChunkTerrainSettings { - - public PhysicsChunkTerrainSettings() { - } - - public PhysicsChunkTerrainSettings( - @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsChunkTerrainSettings settings) { - super(settings); - } - - public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings) { - super(settings); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java deleted file mode 100644 index ddb36227..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsCollisionLodSettings.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.settings; - -import javax.annotation.Nonnull; - -/** - * @deprecated Use - * {@link dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings}. - */ -@Deprecated(forRemoval = false) -public class PhysicsCollisionLodSettings - extends dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsCollisionLodSettings { - - public PhysicsCollisionLodSettings() { - } - - public PhysicsCollisionLodSettings( - @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsCollisionLodSettings settings) { - super(settings); - } - - public PhysicsCollisionLodSettings(@Nonnull PhysicsCollisionLodSettings settings) { - super(settings); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index bfd3517e..bc33007b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -1,6 +1,10 @@ package dev.hytalemodding.impulse.core.plugin.settings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import javax.annotation.Nonnull; @@ -20,7 +24,6 @@ * which keeps Impulse fully opt-in: no terrain bodies are created unless the integrator * explicitly opts in.

        */ -@SuppressWarnings("deprecation") public class PhysicsSpaceSettings { @Nonnull @@ -37,7 +40,7 @@ public class PhysicsSpaceSettings { private final PhysicsExtensionSettings extensionSettings; public PhysicsSpaceSettings() { - physicsChunkTerrainSettings = new PhysicsWorldCollisionSettings(); + physicsChunkTerrainSettings = new PhysicsChunkTerrainSettings(); visualSyncSettings = new PhysicsVisualSyncSettings(); solverSettings = new PhysicsSolverSettings(); visualMaterializationSettings = new PhysicsVisualMaterializationSettings(); @@ -47,7 +50,7 @@ public PhysicsSpaceSettings() { public PhysicsSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { physicsChunkTerrainSettings = - new PhysicsWorldCollisionSettings(settings.physicsChunkTerrainSettings); + new PhysicsChunkTerrainSettings(settings.physicsChunkTerrainSettings); visualSyncSettings = new PhysicsVisualSyncSettings(settings.visualSyncSettings); solverSettings = @@ -67,15 +70,6 @@ public PhysicsChunkTerrainSettings getPhysicsChunkTerrainSettings() { return physicsChunkTerrainSettings; } - /** - * @deprecated Use {@link #getPhysicsChunkTerrainSettings()}. - */ - @Deprecated(forRemoval = false) - @Nonnull - public PhysicsWorldCollisionSettings getWorldCollisionSettings() { - return (PhysicsWorldCollisionSettings) physicsChunkTerrainSettings; - } - /** * Server-to-Hytale transform sync sampling for entity-backed and follower visuals. */ @@ -131,13 +125,4 @@ public static PhysicsSpaceSettings streamingPhysicsChunk() { .setTerrainMode(PhysicsChunkTerrainMode.STREAMING); return settings; } - - /** - * @deprecated Use {@link #streamingPhysicsChunk()}. - */ - @Deprecated - @Nonnull - public static PhysicsSpaceSettings streamingWorldCollision() { - return streamingPhysicsChunk(); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java deleted file mode 100644 index 1b4d7c99..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualMaterializationSettings.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.settings; - -import javax.annotation.Nonnull; - -/** - * @deprecated Use - * {@link dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings}. - */ -@Deprecated(forRemoval = false) -public class PhysicsVisualMaterializationSettings - extends dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings - .PhysicsVisualMaterializationSettings { - - public PhysicsVisualMaterializationSettings() { - } - - public PhysicsVisualMaterializationSettings( - @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings - .PhysicsVisualMaterializationSettings settings) { - super(settings); - } - - public PhysicsVisualMaterializationSettings( - @Nonnull PhysicsVisualMaterializationSettings settings) { - super(settings); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java deleted file mode 100644 index 355d02bc..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsVisualSyncSettings.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.settings; - -import javax.annotation.Nonnull; - -/** - * @deprecated Use - * {@link dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings}. - */ -@Deprecated(forRemoval = false) -public class PhysicsVisualSyncSettings - extends dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings - .PhysicsVisualSyncSettings { - - public PhysicsVisualSyncSettings() { - } - - public PhysicsVisualSyncSettings( - @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings - .PhysicsVisualSyncSettings settings) { - super(settings); - } - - public PhysicsVisualSyncSettings(@Nonnull PhysicsVisualSyncSettings settings) { - super(settings); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java deleted file mode 100644 index 5ae8a84a..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldCollisionSettings.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.settings; - -import javax.annotation.Nonnull; - -/** - * @deprecated Use - * {@link dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings}. - */ -@Deprecated(forRemoval = false) -public class PhysicsWorldCollisionSettings extends PhysicsChunkTerrainSettings { - - public PhysicsWorldCollisionSettings() { - } - - public PhysicsWorldCollisionSettings( - @Nonnull dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsChunkTerrainSettings settings) { - super(settings); - } - - public PhysicsWorldCollisionSettings(@Nonnull PhysicsChunkTerrainSettings settings) { - super(settings); - } - - public PhysicsWorldCollisionSettings(@Nonnull PhysicsWorldCollisionSettings settings) { - super(settings); - } -} From e2d4b50af71c6c8ea92bd790f1e3a75c4616f02b Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:40:30 +0200 Subject: [PATCH 386/534] test(core): guard canonical physicschunk naming Signed-off-by: Blovien --- .../PhysicsChunkNamingSourceGuardTest.java | 84 +++++++++++-------- .../PersistentSpaceDtoSettingsTest.java | 10 +-- .../WorldCollisionComponentTest.java | 39 --------- .../settings/PhysicsSpaceSettingsTest.java | 64 +++++--------- 4 files changed, 75 insertions(+), 122 deletions(-) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java index 9b160e4a..c7832170 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java @@ -11,13 +11,13 @@ class PhysicsChunkNamingSourceGuardTest { @Test - void physicsChunkTerrainFacadeDoesNotDelegateThroughDeprecatedWorldCollision() + void physicsChunkTerrainFacadeDoesNotDelegateThroughRemovedCompatibilityFacade() throws IOException { String source = Files.readString(Path.of( "src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java")); - assertFalse(source.contains("PhysicsWorldCollision."), - "new PhysicsChunk terrain facade must own the implementation path"); + assertFalse(source.contains(removedPhysicsTerrainFacade() + "."), + "PhysicsChunk terrain facade must own the implementation path"); } @Test @@ -26,7 +26,7 @@ void physicsChunkCommandsUseTerrainNamedProfilingApi() throws IOException { "src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands"))) { for (Path file : files.filter(path -> path.toString().endsWith(".java")).toList()) { String source = Files.readString(file); - assertFalse(source.contains("PhysicsWorldCollisionProfiling"), + assertFalse(source.contains(removedPhysicsTerrainFacade() + "Profiling"), file + " should use PhysicsChunkTerrainProfiling"); } } @@ -44,36 +44,54 @@ void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { assertFalse(source.contains("import dev.hytalemodding.impulse.core.internal."), file + " should use exported plugin APIs"); assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.*;"), - file + " should not wildcard-import deprecated flat settings"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision"), - file + " should use PhysicsChunkTerrain"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollision"), - file + " should use PhysicsChunkTerrain names"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings"), - file + " should use PhysicsChunkTerrainSettings"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings"), - file + " should use PhysicsChunk settings"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings"), - file + " should use PhysicsChunk settings"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings"), - file + " should use PhysicsEntity settings"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings"), - file + " should use PhysicsEntity settings"); - assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsWorldCollision"), - file + " should use PhysicsChunkTerrain"); - assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollision"), - file + " should use PhysicsChunkTerrain names"); - assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldCollisionSettings"), - file + " should use PhysicsChunkTerrainSettings"); - assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsChunkTerrainSettings"), - file + " should use PhysicsChunk settings"); - assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsCollisionLodSettings"), - file + " should use PhysicsChunk settings"); - assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualSyncSettings"), - file + " should use PhysicsEntity settings"); - assertFalse(source.contains("dev.hytalemodding.impulse.core.plugin.settings.PhysicsVisualMaterializationSettings"), - file + " should use PhysicsEntity settings"); + file + " should not wildcard-import flat settings"); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.modules.physicschunk.", + removedPhysicsTerrainFacade()); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.modules.physicschunk.", + removedTerrainPrefix() + "Mode"); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.settings.", + removedPhysicsTerrainFacade() + "Settings"); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.settings.", + "PhysicsChunkTerrainSettings"); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.settings.", + "PhysicsCollisionLodSettings"); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.settings.", + "PhysicsVisualSyncSettings"); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.settings.", + "PhysicsVisualMaterializationSettings"); } } } + + private static void assertRemovedApiAbsent(String source, + Path file, + String packageName, + String typeName) { + assertFalse(source.contains("import " + packageName + typeName), + file + " should use canonical module APIs"); + assertFalse(source.contains(packageName + typeName), + file + " should use canonical module APIs"); + } + + private static String removedPhysicsTerrainFacade() { + return "Physics" + removedTerrainPrefix(); + } + + private static String removedTerrainPrefix() { + return "World" + "Collision"; + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index e53a0acc..956f127d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -20,7 +20,7 @@ class PersistentSpaceDtoSettingsTest { @Test - void roundTripPreservesDetachedVisualCadenceSettingsAndCompatibilityKeys() { + void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); original.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(true); original.getPhysicsChunkTerrainSettings().setTerrainMaterial(0.85f, 0.2f); @@ -48,10 +48,10 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndCompatibilityKeys() { BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); - assertTrue(encoded.containsKey("WorldCollisionMode")); - assertTrue(encoded.containsKey("WorldCollisionRadius")); - assertTrue(encoded.containsKey("WorldCollisionBodyRadius")); - assertTrue(encoded.containsKey("WorldCollisionTtlTicks")); + assertTrue(encoded.containsKey("PhysicsChunkTerrainMode")); + assertTrue(encoded.containsKey("TerrainRadius")); + assertTrue(encoded.containsKey("BodyTerrainRadius")); + assertTrue(encoded.containsKey("TerrainTtlTicks")); assertTrue(encoded.containsKey("NativeVoxelTerrain")); assertTrue(encoded.containsKey("TerrainFriction")); assertTrue(encoded.containsKey("TerrainRestitution")); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java deleted file mode 100644 index 0f3322c8..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/WorldCollisionComponentTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("deprecation") -class WorldCollisionComponentTest { - - @Test - void deprecatedWorldCollisionComponentAccessorsMutateTerrainState() { - WorldCollisionComponent component = new WorldCollisionComponent(WorldCollisionMode.STREAMING, - true, - 8, - 4, - 160, - 0.7f, - 0.1f); - - assertEquals(PhysicsChunkTerrainMode.STREAMING, component.getTerrainMode()); - assertEquals(WorldCollisionMode.STREAMING, component.getMode()); - - component.setMode(WorldCollisionMode.NONE); - - assertEquals(PhysicsChunkTerrainMode.NONE, component.getTerrainMode()); - assertEquals(WorldCollisionMode.NONE, component.getMode()); - - WorldCollisionComponent clone = component.clone(); - - assertEquals(PhysicsChunkTerrainMode.NONE, clone.getTerrainMode()); - assertEquals(8, clone.getRadius()); - assertEquals(4, clone.getBodyRadius()); - assertEquals(160, clone.getTtlTicks()); - assertEquals(0.7f, clone.getTerrainFriction(), 0.0001f); - assertEquals(0.1f, clone.getTerrainRestitution(), 0.0001f); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index 34c4f51f..30ce0723 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -8,10 +8,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.WorldCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import org.junit.jupiter.api.Test; -@SuppressWarnings("deprecation") class PhysicsSpaceSettingsTest { @Test @@ -213,11 +215,8 @@ void groupedAccessorsExposeIndependentDomainState() { } @Test - void deprecatedCollisionLodSettingsAliasCopiesCanonicalAndAliasValues() { - dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsCollisionLodSettings canonical = - new dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsCollisionLodSettings(); + void collisionLodSettingsCopyConstructorCopiesValues() { + PhysicsCollisionLodSettings canonical = new PhysicsCollisionLodSettings(); canonical.setCollisionLodEnabled(true); canonical.setCollisionLodRadii(24, 96); @@ -227,7 +226,7 @@ void deprecatedCollisionLodSettingsAliasCopiesCanonicalAndAliasValues() { PhysicsCollisionLodSettings canonicalCopy = new PhysicsCollisionLodSettings(canonical); - PhysicsCollisionLodSettings aliasCopy = + PhysicsCollisionLodSettings secondCopy = new PhysicsCollisionLodSettings(canonicalCopy); canonical.setCollisionLodRadii(32, 128); canonicalCopy.setCollisionLodRadii(40, 160); @@ -238,20 +237,17 @@ void deprecatedCollisionLodSettingsAliasCopiesCanonicalAndAliasValues() { assertEquals(6, canonicalCopy.getCollisionLodHysteresis()); assertEquals(8, canonicalCopy.getCollisionLodRefreshIntervalTicks()); assertFalse(canonicalCopy.isCollisionLodFarSleepEnabled()); - assertTrue(aliasCopy.isCollisionLodEnabled()); - assertEquals(24, aliasCopy.getCollisionLodNearRadius()); - assertEquals(96, aliasCopy.getCollisionLodMidRadius()); - assertEquals(6, aliasCopy.getCollisionLodHysteresis()); - assertEquals(8, aliasCopy.getCollisionLodRefreshIntervalTicks()); - assertFalse(aliasCopy.isCollisionLodFarSleepEnabled()); + assertTrue(secondCopy.isCollisionLodEnabled()); + assertEquals(24, secondCopy.getCollisionLodNearRadius()); + assertEquals(96, secondCopy.getCollisionLodMidRadius()); + assertEquals(6, secondCopy.getCollisionLodHysteresis()); + assertEquals(8, secondCopy.getCollisionLodRefreshIntervalTicks()); + assertFalse(secondCopy.isCollisionLodFarSleepEnabled()); } @Test - void deprecatedTerrainSettingsAliasCopiesCanonicalAndAliasValues() { - dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsChunkTerrainSettings canonical = - new dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings - .PhysicsChunkTerrainSettings(); + void terrainSettingsCopyConstructorCopiesValues() { + PhysicsChunkTerrainSettings canonical = new PhysicsChunkTerrainSettings(); canonical.setTerrainMode(PhysicsChunkTerrainMode.STREAMING); canonical.setEntityChunkBoundaryMode(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK); @@ -263,10 +259,8 @@ void deprecatedTerrainSettingsAliasCopiesCanonicalAndAliasValues() { PhysicsChunkTerrainSettings canonicalCopy = new PhysicsChunkTerrainSettings(canonical); - PhysicsChunkTerrainSettings aliasCopy = + PhysicsChunkTerrainSettings secondCopy = new PhysicsChunkTerrainSettings(canonicalCopy); - PhysicsWorldCollisionSettings worldCollisionCopy = - new PhysicsWorldCollisionSettings(canonical); canonical.setTerrainRadius(24); canonicalCopy.setTerrainRadius(30); @@ -279,9 +273,9 @@ void deprecatedTerrainSettingsAliasCopiesCanonicalAndAliasValues() { assertEquals(240, canonicalCopy.getTerrainTtlTicks()); assertEquals(0.85f, canonicalCopy.getTerrainFriction(), 0.0001f); assertEquals(0.2f, canonicalCopy.getTerrainRestitution(), 0.0001f); - assertEquals(18, aliasCopy.getTerrainRadius()); - assertEquals(18, worldCollisionCopy.getTerrainRadius()); - assertEquals(18, worldCollisionCopy.getWorldCollisionRadius()); + assertEquals(18, secondCopy.getTerrainRadius()); + assertEquals(7, secondCopy.getBodyTerrainRadius()); + assertEquals(240, secondCopy.getTerrainTtlTicks()); } @Test @@ -322,26 +316,6 @@ void streamingPhysicsChunkFactoryEnablesStreamingMode() { settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); } - @Test - void deprecatedWorldCollisionAccessorsMutatePhysicsChunkTerrainSettings() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingWorldCollision(); - - assertSame(settings.getPhysicsChunkTerrainSettings(), settings.getWorldCollisionSettings()); - assertEquals(PhysicsChunkTerrainMode.STREAMING, - settings.getPhysicsChunkTerrainSettings().getTerrainMode()); - - settings.getWorldCollisionSettings().setWorldCollisionMode(WorldCollisionMode.NONE); - settings.getWorldCollisionSettings().setWorldCollisionRadius(9); - settings.getWorldCollisionSettings().setWorldCollisionBodyRadius(4); - settings.getWorldCollisionSettings().setWorldCollisionTtlTicks(120); - - assertEquals(PhysicsChunkTerrainMode.NONE, - settings.getPhysicsChunkTerrainSettings().getTerrainMode()); - assertEquals(9, settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); - assertEquals(4, settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); - assertEquals(120, settings.getPhysicsChunkTerrainSettings().getTerrainTtlTicks()); - } - @Test void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { PhysicsSpaceSettings original = new PhysicsSpaceSettings(); From bd28675d0122b322112870e5d9512eff559cb17c Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 06:51:58 +0200 Subject: [PATCH 387/534] test(core): benchmark physics component granularity Signed-off-by: Blovien --- .../ComponentGranularityStoreProbeTest.java | 635 ++++++++++++++++++ 1 file changed, 635 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java new file mode 100644 index 00000000..253525f4 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java @@ -0,0 +1,635 @@ +package dev.hytalemodding.impulse.core.internal.benchmark; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.IntFunction; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class ComponentGranularityStoreProbeTest { + + private static final int DEFAULT_SPACE_COUNT = 1024; + private static final int DEFAULT_BODY_COUNT = 10_000; + private static final int OPTIONAL_STRIDE = 10; + + @Test + void reportsGroupedDomainAndTinyComponentCosts() throws IOException { + int spaceCount = intProperty("impulse.componentProbe.spaceCount", DEFAULT_SPACE_COUNT); + int bodyCount = intProperty("impulse.componentProbe.bodyCount", DEFAULT_BODY_COUNT); + List results = new ArrayList<>(); + + results.add(run("space-grouped-settings", spaceCount, ComponentLayout.GROUPED, false)); + results.add(run("space-split-domain-settings", spaceCount, ComponentLayout.DOMAIN_SPLIT, false)); + results.add(run("space-tiny-settings", spaceCount, ComponentLayout.TINY, false)); + results.add(run("space-split-domain-settings-10pct-optional", + spaceCount, + ComponentLayout.DOMAIN_SPLIT, + true)); + results.add(run("body-grouped-authoring", bodyCount, ComponentLayout.GROUPED, false)); + results.add(run("body-current-domain-authoring", bodyCount, ComponentLayout.DOMAIN_SPLIT, false)); + results.add(run("body-tiny-authoring", bodyCount, ComponentLayout.TINY, false)); + results.add(run("body-current-domain-authoring-10pct-optional", + bodyCount, + ComponentLayout.DOMAIN_SPLIT, + true)); + + Path report = Path.of("build", + "reports", + "impulse", + "component-granularity-probe.tsv"); + Files.createDirectories(report.getParent()); + Files.writeString(report, ScenarioResult.tsv(results)); + + assertEquals(8, results.size()); + for (ScenarioResult result : results) { + assertEquals(result.entities(), result.storeEntityCount(), result.name()); + assertTrue(result.archetypeChunkCount() > 0, result.name()); + assertNotEquals(0L, result.iterationChecksum(), result.name()); + } + } + + @Nonnull + private static ScenarioResult run(@Nonnull String name, + int entities, + @Nonnull ComponentLayout layout, + boolean optionalFragmentation) { + ComponentRegistry registry = new ComponentRegistry<>(); + List> required = layout.registerRequired(registry); + ComponentSpec optional = + optionalFragmentation ? optional(registry) : null; + Store store = registry.addStore(new BenchmarkWorld(name), + EmptyResourceStorage.get()); + try { + forceGc(); + long heapBefore = usedHeap(); + long addStart = System.nanoTime(); + Ref[] refs = addEntities(store, + registry, + required, + optional, + entities); + long addNanos = System.nanoTime() - addStart; + forceGc(); + long heapAfterAdd = usedHeap(); + + long iterateStart = System.nanoTime(); + long checksum = iterate(store, required); + long iterateNanos = System.nanoTime() - iterateStart; + + long mutateStart = System.nanoTime(); + replaceComponent(store, required.getFirst(), refs); + long mutateNanos = System.nanoTime() - mutateStart; + + int entityCount = store.getEntityCount(); + int archetypeChunkCount = store.getArchetypeChunkCount(); + int archetypeDataCount = store.collectArchetypeChunkData().length; + + return new ScenarioResult(name, + entities, + entityCount, + required.size(), + optionalFragmentation ? 1 : 0, + Math.max(0L, heapAfterAdd - heapBefore), + addNanos, + iterateNanos, + mutateNanos, + archetypeChunkCount, + archetypeDataCount, + checksum); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static Ref[] addEntities(@Nonnull Store store, + @Nonnull ComponentRegistry registry, + @Nonnull List> required, + ComponentSpec optional, + int entities) { + @SuppressWarnings("unchecked") + Holder[] holders = new Holder[entities]; + for (int entity = 0; entity < entities; entity++) { + Holder holder = registry.newHolder(); + for (ComponentSpec spec : required) { + addComponent(holder, spec, entity); + } + if (optional != null && entity % OPTIONAL_STRIDE == 0) { + addComponent(holder, optional, entity); + } + holders[entity] = holder; + } + return store.addEntities(holders, AddReason.SPAWN); + } + + private static void addComponent( + @Nonnull Holder holder, + @Nonnull ComponentSpec spec, + int seed) { + holder.addComponent(spec.type(), spec.factory().apply(seed)); + } + + private static long iterate(@Nonnull Store store, + @Nonnull List> required) { + Query query = query(required); + long[] checksum = {0L}; + BiConsumer, CommandBuffer> consumer = + (chunk, _) -> checksum[0] += checksum(required, chunk); + store.forEachChunk(query, consumer); + return checksum[0]; + } + + private static long checksum(@Nonnull List> specs, + @Nonnull ArchetypeChunk chunk) { + long checksum = 0L; + for (int index = 0; index < chunk.size(); index++) { + for (ComponentSpec spec : specs) { + checksum += component(chunk, index, spec).checksum(); + } + } + return checksum; + } + + private static T component( + @Nonnull ArchetypeChunk chunk, + int index, + @Nonnull ComponentSpec spec) { + return chunk.getComponent(index, spec.type()); + } + + private static void replaceComponent( + @Nonnull Store store, + @Nonnull ComponentSpec spec, + @Nonnull Ref[] refs) { + for (int index = 0; index < refs.length; index++) { + store.putComponent(refs[index], spec.type(), spec.factory().apply(index + 1)); + } + } + + @Nonnull + @SuppressWarnings("unchecked") + private static Query query( + @Nonnull List> specs) { + Query[] queries = specs.stream() + .map(ComponentSpec::type) + .toArray(Query[]::new); + return Query.and(queries); + } + + @Nonnull + private static ComponentSpec optional( + @Nonnull ComponentRegistry registry) { + return spec(registry, OptionalComponent.class, OptionalComponent::new); + } + + @Nonnull + private static ComponentSpec spec( + @Nonnull ComponentRegistry registry, + @Nonnull Class typeClass, + @Nonnull IntFunction factory) { + return new ComponentSpec<>(registry.registerComponent(typeClass, + () -> factory.apply(0)), factory); + } + + private static int intProperty(@Nonnull String name, int defaultValue) { + Integer propertyValue = Integer.getInteger(name); + if (propertyValue != null) { + return propertyValue; + } + String environmentName = name.replaceAll("([a-z0-9])([A-Z])", "$1_$2") + .toUpperCase() + .replace('.', '_') + .replace('-', '_'); + String environmentValue = System.getenv(environmentName); + if (environmentValue == null || environmentValue.isBlank()) { + return defaultValue; + } + return Integer.parseInt(environmentValue); + } + + private static long usedHeap() { + Runtime runtime = Runtime.getRuntime(); + return runtime.totalMemory() - runtime.freeMemory(); + } + + private static void forceGc() { + for (int i = 0; i < 3; i++) { + System.gc(); + try { + Thread.sleep(10L); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + return; + } + } + } + + private enum ComponentLayout { + GROUPED { + @Nonnull + @Override + List> registerRequired( + @Nonnull ComponentRegistry registry) { + return List.of(spec(registry, GroupedComponent.class, GroupedComponent::new)); + } + }, + DOMAIN_SPLIT { + @Nonnull + @Override + List> registerRequired( + @Nonnull ComponentRegistry registry) { + return List.of( + spec(registry, DomainComponentA.class, DomainComponentA::new), + spec(registry, DomainComponentB.class, DomainComponentB::new), + spec(registry, DomainComponentC.class, DomainComponentC::new), + spec(registry, DomainComponentD.class, DomainComponentD::new), + spec(registry, DomainComponentE.class, DomainComponentE::new), + spec(registry, DomainComponentF.class, DomainComponentF::new), + spec(registry, DomainComponentG.class, DomainComponentG::new)); + } + }, + TINY { + @Nonnull + @Override + List> registerRequired( + @Nonnull ComponentRegistry registry) { + return List.of( + spec(registry, TinyComponent01.class, TinyComponent01::new), + spec(registry, TinyComponent02.class, TinyComponent02::new), + spec(registry, TinyComponent03.class, TinyComponent03::new), + spec(registry, TinyComponent04.class, TinyComponent04::new), + spec(registry, TinyComponent05.class, TinyComponent05::new), + spec(registry, TinyComponent06.class, TinyComponent06::new), + spec(registry, TinyComponent07.class, TinyComponent07::new), + spec(registry, TinyComponent08.class, TinyComponent08::new), + spec(registry, TinyComponent09.class, TinyComponent09::new), + spec(registry, TinyComponent10.class, TinyComponent10::new), + spec(registry, TinyComponent11.class, TinyComponent11::new), + spec(registry, TinyComponent12.class, TinyComponent12::new), + spec(registry, TinyComponent13.class, TinyComponent13::new), + spec(registry, TinyComponent14.class, TinyComponent14::new), + spec(registry, TinyComponent15.class, TinyComponent15::new), + spec(registry, TinyComponent16.class, TinyComponent16::new), + spec(registry, TinyComponent17.class, TinyComponent17::new), + spec(registry, TinyComponent18.class, TinyComponent18::new), + spec(registry, TinyComponent19.class, TinyComponent19::new), + spec(registry, TinyComponent20.class, TinyComponent20::new), + spec(registry, TinyComponent21.class, TinyComponent21::new), + spec(registry, TinyComponent22.class, TinyComponent22::new), + spec(registry, TinyComponent23.class, TinyComponent23::new), + spec(registry, TinyComponent24.class, TinyComponent24::new), + spec(registry, TinyComponent25.class, TinyComponent25::new), + spec(registry, TinyComponent26.class, TinyComponent26::new), + spec(registry, TinyComponent27.class, TinyComponent27::new), + spec(registry, TinyComponent28.class, TinyComponent28::new), + spec(registry, TinyComponent29.class, TinyComponent29::new), + spec(registry, TinyComponent30.class, TinyComponent30::new)); + } + }; + + @Nonnull + abstract List> registerRequired( + @Nonnull ComponentRegistry registry); + } + + private record ComponentSpec( + @Nonnull ComponentType type, + @Nonnull IntFunction factory) { + } + + private record ScenarioResult(@Nonnull String name, + int entities, + int storeEntityCount, + int requiredComponents, + int optionalComponents, + long heapBytes, + long addNanos, + long iterateNanos, + long mutateNanos, + int archetypeChunkCount, + int archetypeDataCount, + long iterationChecksum) { + + @Nonnull + static String tsv(@Nonnull List results) { + String header = String.join("\t", + "scenario", + "entities", + "requiredComponents", + "optionalComponents", + "heapBytes", + "bytesPerEntity", + "addNsPerEntity", + "iterateNsPerEntity", + "mutateNsPerEntity", + "archetypeChunkCount", + "archetypeDataCount", + "iterationChecksum"); + String rows = results.stream() + .map(ScenarioResult::tsvRow) + .collect(Collectors.joining(System.lineSeparator())); + return header + System.lineSeparator() + rows + System.lineSeparator(); + } + + @Nonnull + private String tsvRow() { + return String.join("\t", + name, + Integer.toString(entities), + Integer.toString(requiredComponents), + Integer.toString(optionalComponents), + Long.toString(heapBytes), + Long.toString(heapBytes / Math.max(1, entities)), + Long.toString(addNanos / Math.max(1, entities)), + Long.toString(iterateNanos / Math.max(1, entities)), + Long.toString(mutateNanos / Math.max(1, entities)), + Integer.toString(archetypeChunkCount), + Integer.toString(archetypeDataCount), + Long.toString(iterationChecksum)); + } + } + + private record BenchmarkWorld(@Nonnull String name) { + } + + private interface ProbeComponent extends Component { + + long checksum(); + } + + private abstract static class BaseComponent implements ProbeComponent { + + private final int seed; + + private BaseComponent(int seed) { + this.seed = seed; + } + + protected final int seed() { + return seed; + } + + @Override + public abstract BaseComponent clone(); + } + + private abstract static class GroupedBase extends BaseComponent { + + private GroupedBase(int seed) { + super(seed); + } + + @Override + public long checksum() { + long sum = 0L; + for (int i = 0; i < 30; i++) { + sum += seed() + i; + } + return sum; + } + } + + private abstract static class DomainBase extends BaseComponent { + + private DomainBase(int seed) { + super(seed); + } + + @Override + public long checksum() { + long sum = 0L; + for (int i = 0; i < 5; i++) { + sum += seed() + i; + } + return sum; + } + } + + private abstract static class TinyBase extends BaseComponent { + + private TinyBase(int seed) { + super(seed); + } + + @Override + public long checksum() { + return seed(); + } + } + + private static final class GroupedComponent extends GroupedBase { + private GroupedComponent(int seed) { super(seed); } + @Override public GroupedComponent clone() { return new GroupedComponent(seed()); } + } + + private static final class DomainComponentA extends DomainBase { + private DomainComponentA(int seed) { super(seed); } + @Override public DomainComponentA clone() { return new DomainComponentA(seed()); } + } + + private static final class DomainComponentB extends DomainBase { + private DomainComponentB(int seed) { super(seed); } + @Override public DomainComponentB clone() { return new DomainComponentB(seed()); } + } + + private static final class DomainComponentC extends DomainBase { + private DomainComponentC(int seed) { super(seed); } + @Override public DomainComponentC clone() { return new DomainComponentC(seed()); } + } + + private static final class DomainComponentD extends DomainBase { + private DomainComponentD(int seed) { super(seed); } + @Override public DomainComponentD clone() { return new DomainComponentD(seed()); } + } + + private static final class DomainComponentE extends DomainBase { + private DomainComponentE(int seed) { super(seed); } + @Override public DomainComponentE clone() { return new DomainComponentE(seed()); } + } + + private static final class DomainComponentF extends DomainBase { + private DomainComponentF(int seed) { super(seed); } + @Override public DomainComponentF clone() { return new DomainComponentF(seed()); } + } + + private static final class DomainComponentG extends DomainBase { + private DomainComponentG(int seed) { super(seed); } + @Override public DomainComponentG clone() { return new DomainComponentG(seed()); } + } + + private static final class OptionalComponent extends TinyBase { + private OptionalComponent(int seed) { super(seed); } + @Override public OptionalComponent clone() { return new OptionalComponent(seed()); } + } + + private static final class TinyComponent01 extends TinyBase { + private TinyComponent01(int seed) { super(seed); } + @Override public TinyComponent01 clone() { return new TinyComponent01(seed()); } + } + + private static final class TinyComponent02 extends TinyBase { + private TinyComponent02(int seed) { super(seed); } + @Override public TinyComponent02 clone() { return new TinyComponent02(seed()); } + } + + private static final class TinyComponent03 extends TinyBase { + private TinyComponent03(int seed) { super(seed); } + @Override public TinyComponent03 clone() { return new TinyComponent03(seed()); } + } + + private static final class TinyComponent04 extends TinyBase { + private TinyComponent04(int seed) { super(seed); } + @Override public TinyComponent04 clone() { return new TinyComponent04(seed()); } + } + + private static final class TinyComponent05 extends TinyBase { + private TinyComponent05(int seed) { super(seed); } + @Override public TinyComponent05 clone() { return new TinyComponent05(seed()); } + } + + private static final class TinyComponent06 extends TinyBase { + private TinyComponent06(int seed) { super(seed); } + @Override public TinyComponent06 clone() { return new TinyComponent06(seed()); } + } + + private static final class TinyComponent07 extends TinyBase { + private TinyComponent07(int seed) { super(seed); } + @Override public TinyComponent07 clone() { return new TinyComponent07(seed()); } + } + + private static final class TinyComponent08 extends TinyBase { + private TinyComponent08(int seed) { super(seed); } + @Override public TinyComponent08 clone() { return new TinyComponent08(seed()); } + } + + private static final class TinyComponent09 extends TinyBase { + private TinyComponent09(int seed) { super(seed); } + @Override public TinyComponent09 clone() { return new TinyComponent09(seed()); } + } + + private static final class TinyComponent10 extends TinyBase { + private TinyComponent10(int seed) { super(seed); } + @Override public TinyComponent10 clone() { return new TinyComponent10(seed()); } + } + + private static final class TinyComponent11 extends TinyBase { + private TinyComponent11(int seed) { super(seed); } + @Override public TinyComponent11 clone() { return new TinyComponent11(seed()); } + } + + private static final class TinyComponent12 extends TinyBase { + private TinyComponent12(int seed) { super(seed); } + @Override public TinyComponent12 clone() { return new TinyComponent12(seed()); } + } + + private static final class TinyComponent13 extends TinyBase { + private TinyComponent13(int seed) { super(seed); } + @Override public TinyComponent13 clone() { return new TinyComponent13(seed()); } + } + + private static final class TinyComponent14 extends TinyBase { + private TinyComponent14(int seed) { super(seed); } + @Override public TinyComponent14 clone() { return new TinyComponent14(seed()); } + } + + private static final class TinyComponent15 extends TinyBase { + private TinyComponent15(int seed) { super(seed); } + @Override public TinyComponent15 clone() { return new TinyComponent15(seed()); } + } + + private static final class TinyComponent16 extends TinyBase { + private TinyComponent16(int seed) { super(seed); } + @Override public TinyComponent16 clone() { return new TinyComponent16(seed()); } + } + + private static final class TinyComponent17 extends TinyBase { + private TinyComponent17(int seed) { super(seed); } + @Override public TinyComponent17 clone() { return new TinyComponent17(seed()); } + } + + private static final class TinyComponent18 extends TinyBase { + private TinyComponent18(int seed) { super(seed); } + @Override public TinyComponent18 clone() { return new TinyComponent18(seed()); } + } + + private static final class TinyComponent19 extends TinyBase { + private TinyComponent19(int seed) { super(seed); } + @Override public TinyComponent19 clone() { return new TinyComponent19(seed()); } + } + + private static final class TinyComponent20 extends TinyBase { + private TinyComponent20(int seed) { super(seed); } + @Override public TinyComponent20 clone() { return new TinyComponent20(seed()); } + } + + private static final class TinyComponent21 extends TinyBase { + private TinyComponent21(int seed) { super(seed); } + @Override public TinyComponent21 clone() { return new TinyComponent21(seed()); } + } + + private static final class TinyComponent22 extends TinyBase { + private TinyComponent22(int seed) { super(seed); } + @Override public TinyComponent22 clone() { return new TinyComponent22(seed()); } + } + + private static final class TinyComponent23 extends TinyBase { + private TinyComponent23(int seed) { super(seed); } + @Override public TinyComponent23 clone() { return new TinyComponent23(seed()); } + } + + private static final class TinyComponent24 extends TinyBase { + private TinyComponent24(int seed) { super(seed); } + @Override public TinyComponent24 clone() { return new TinyComponent24(seed()); } + } + + private static final class TinyComponent25 extends TinyBase { + private TinyComponent25(int seed) { super(seed); } + @Override public TinyComponent25 clone() { return new TinyComponent25(seed()); } + } + + private static final class TinyComponent26 extends TinyBase { + private TinyComponent26(int seed) { super(seed); } + @Override public TinyComponent26 clone() { return new TinyComponent26(seed()); } + } + + private static final class TinyComponent27 extends TinyBase { + private TinyComponent27(int seed) { super(seed); } + @Override public TinyComponent27 clone() { return new TinyComponent27(seed()); } + } + + private static final class TinyComponent28 extends TinyBase { + private TinyComponent28(int seed) { super(seed); } + @Override public TinyComponent28 clone() { return new TinyComponent28(seed()); } + } + + private static final class TinyComponent29 extends TinyBase { + private TinyComponent29(int seed) { super(seed); } + @Override public TinyComponent29 clone() { return new TinyComponent29(seed()); } + } + + private static final class TinyComponent30 extends TinyBase { + private TinyComponent30(int seed) { super(seed); } + @Override public TinyComponent30 clone() { return new TinyComponent30(seed()); } + } +} From 087edc9fd092b40e869f3b1af19ad281fe0a9728 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:13:45 +0200 Subject: [PATCH 388/534] refactor(core): store sparse physics space settings Signed-off-by: Blovien --- .../PhysicsStoreSpaceMutations.java | 160 ++++++++++++++++-- .../systems/PersistenceHydrationSystem.java | 48 ++++-- .../ExtensionSettingsComponent.java | 4 + .../components/SolverSettingsComponent.java | 12 ++ ...isualMaterializationSettingsComponent.java | 27 +++ .../VisualSyncSettingsComponent.java | 28 +++ .../CollisionLodSettingsComponent.java | 14 ++ .../PhysicsChunkTerrainComponent.java | 15 ++ .../plugin/physicsstore/PhysicsEntities.java | 20 ++- .../plugin/physicsstore/PhysicsSpaces.java | 132 ++++++++++++++- 10 files changed, 426 insertions(+), 34 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index ee69c9f4..1d3828d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -1,6 +1,9 @@ package dev.hytalemodding.impulse.core.internal.physicsstore; import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; @@ -26,6 +29,7 @@ import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3f; /** @@ -36,12 +40,33 @@ public final class PhysicsStoreSpaceMutations { private PhysicsStoreSpaceMutations() { } + @Nonnull + public static Ref addSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId compatibilitySpaceId, + @Nonnull BackendId backendId) { + return addSpace0(store, spaceUuid, compatibilitySpaceId, backendId, null); + } + @Nonnull public static Ref addSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull SpaceId compatibilitySpaceId, @Nonnull BackendId backendId, @Nonnull PhysicsSpaceSettings settings) { + return addSpace0(store, + spaceUuid, + compatibilitySpaceId, + backendId, + Objects.requireNonNull(settings, "settings")); + } + + @Nonnull + private static Ref addSpace0(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId compatibilitySpaceId, + @Nonnull BackendId backendId, + @Nullable PhysicsSpaceSettings settings) { PhysicsThreading.requireWorldThread(store, "add a PhysicsStore space entity"); if (backendId.value().isBlank()) { @@ -61,15 +86,13 @@ public static Ref addSpace(@Nonnull Store store, throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid + " is already registered"); } - Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, + Holder holder = PhysicsEntities.spaceHolder(store, spaceUuid, - new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f)), - new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings()), - new SolverSettingsComponent(settings.getSolverSettings()), - new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), - new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), - new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), - new ExtensionSettingsComponent(settings.getExtensionSettings())), AddReason.SPAWN); + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))); + if (settings != null) { + addSpaceSettingsComponents(holder, settings); + } + Ref ref = store.addEntity(holder, AddReason.SPAWN); assert ref != null; identity.putUuid(spaceUuid, ref); compatibility.putSpace(compatibilitySpaceId, spaceUuid); @@ -117,20 +140,121 @@ public static void putSpaceSettings(@Nonnull Store store, @Nonnull PhysicsSpaceSettings settings) { requireSpaceUuid(store, ref); PhysicsThreading.requireWorldThread(store, "update a PhysicsStore space entity"); - store.putComponent(ref, - PhysicsChunkTerrainComponent.getComponentType(), - new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings())); - PhysicsEntities.putSpaceSettingsComponents(store, - ref, - new SolverSettingsComponent(settings.getSolverSettings()), - new VisualSyncSettingsComponent(settings.getVisualSyncSettings()), - new VisualMaterializationSettingsComponent(settings.getVisualMaterializationSettings()), - new CollisionLodSettingsComponent(settings.getCollisionLodSettings()), - new ExtensionSettingsComponent(settings.getExtensionSettings())); + putSpaceSettingsComponents(store, ref, Objects.requireNonNull(settings, "settings")); store.getResource(PhysicsRuntimeResource.getResourceType()) .markSpaceSettingsPending(ref); } + private static void addSpaceSettingsComponents(@Nonnull Holder holder, + @Nonnull PhysicsSpaceSettings settings) { + PhysicsChunkTerrainComponent terrain = + new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings()); + addIfNonDefault(holder, + PhysicsChunkTerrainComponent.getComponentType(), + terrain, + terrain.isDefault()); + SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); + addIfNonDefault(holder, + SolverSettingsComponent.getComponentType(), + solver, + solver.isDefault()); + VisualSyncSettingsComponent visualSync = + new VisualSyncSettingsComponent(settings.getVisualSyncSettings()); + addIfNonDefault(holder, + VisualSyncSettingsComponent.getComponentType(), + visualSync, + visualSync.isDefault()); + VisualMaterializationSettingsComponent visualMaterialization = + new VisualMaterializationSettingsComponent( + settings.getVisualMaterializationSettings()); + addIfNonDefault(holder, + VisualMaterializationSettingsComponent.getComponentType(), + visualMaterialization, + visualMaterialization.isDefault()); + CollisionLodSettingsComponent collisionLod = + new CollisionLodSettingsComponent(settings.getCollisionLodSettings()); + addIfNonDefault(holder, + CollisionLodSettingsComponent.getComponentType(), + collisionLod, + collisionLod.isDefault()); + ExtensionSettingsComponent extension = + new ExtensionSettingsComponent(settings.getExtensionSettings()); + addIfNonDefault(holder, + ExtensionSettingsComponent.getComponentType(), + extension, + extension.isDefault()); + } + + private static > void addIfNonDefault( + @Nonnull Holder holder, + @Nonnull ComponentType componentType, + @Nonnull T component, + boolean defaultValue) { + if (!defaultValue) { + holder.addComponent(componentType, component); + } + } + + private static void putSpaceSettingsComponents(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsSpaceSettings settings) { + PhysicsChunkTerrainComponent terrain = + new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings()); + putOrRemoveDefault(store, + ref, + PhysicsChunkTerrainComponent.getComponentType(), + terrain, + terrain.isDefault()); + SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); + putOrRemoveDefault(store, + ref, + SolverSettingsComponent.getComponentType(), + solver, + solver.isDefault()); + VisualSyncSettingsComponent visualSync = + new VisualSyncSettingsComponent(settings.getVisualSyncSettings()); + putOrRemoveDefault(store, + ref, + VisualSyncSettingsComponent.getComponentType(), + visualSync, + visualSync.isDefault()); + VisualMaterializationSettingsComponent visualMaterialization = + new VisualMaterializationSettingsComponent( + settings.getVisualMaterializationSettings()); + putOrRemoveDefault(store, + ref, + VisualMaterializationSettingsComponent.getComponentType(), + visualMaterialization, + visualMaterialization.isDefault()); + CollisionLodSettingsComponent collisionLod = + new CollisionLodSettingsComponent(settings.getCollisionLodSettings()); + putOrRemoveDefault(store, + ref, + CollisionLodSettingsComponent.getComponentType(), + collisionLod, + collisionLod.isDefault()); + ExtensionSettingsComponent extension = + new ExtensionSettingsComponent(settings.getExtensionSettings()); + putOrRemoveDefault(store, + ref, + ExtensionSettingsComponent.getComponentType(), + extension, + extension.isDefault()); + } + + private static > void putOrRemoveDefault( + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull ComponentType componentType, + @Nonnull T component, + boolean defaultValue) { + if (defaultValue) { + store.removeComponentIfExists(ref, componentType); + } else { + store.putComponent(ref, componentType, component); + } + } + public static void removeEmptySpace(@Nonnull Store store, @Nonnull SpaceId spaceId) { UUID spaceUuid = requireSpaceUuid(store, spaceId); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 3febe707..21be2835 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -1,6 +1,8 @@ package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; @@ -87,21 +89,43 @@ private static void addSpace(@Nonnull Store store, Holder holder = row(store, dto.getSpaceUuid()); holder.addComponent(SpaceComponent.getComponentType(), new SpaceComponent(new BackendId(dto.getBackendId()), dto.getGravity())); - holder.addComponent(PhysicsChunkTerrainComponent.getComponentType(), - dto.getPhysicsChunkTerrain()); - holder.addComponent(SolverSettingsComponent.getComponentType(), - dto.getSolverSettings()); - holder.addComponent(VisualSyncSettingsComponent.getComponentType(), - dto.getVisualSyncSettings()); - holder.addComponent(VisualMaterializationSettingsComponent.getComponentType(), - dto.getVisualMaterializationSettings()); - holder.addComponent(CollisionLodSettingsComponent.getComponentType(), - dto.getCollisionLodSettings()); - holder.addComponent(ExtensionSettingsComponent.getComponentType(), - dto.getExtensionSettings()); + addIfNonDefault(holder, + PhysicsChunkTerrainComponent.getComponentType(), + dto.getPhysicsChunkTerrain(), + dto.getPhysicsChunkTerrain().isDefault()); + addIfNonDefault(holder, + SolverSettingsComponent.getComponentType(), + dto.getSolverSettings(), + dto.getSolverSettings().isDefault()); + addIfNonDefault(holder, + VisualSyncSettingsComponent.getComponentType(), + dto.getVisualSyncSettings(), + dto.getVisualSyncSettings().isDefault()); + addIfNonDefault(holder, + VisualMaterializationSettingsComponent.getComponentType(), + dto.getVisualMaterializationSettings(), + dto.getVisualMaterializationSettings().isDefault()); + addIfNonDefault(holder, + CollisionLodSettingsComponent.getComponentType(), + dto.getCollisionLodSettings(), + dto.getCollisionLodSettings().isDefault()); + addIfNonDefault(holder, + ExtensionSettingsComponent.getComponentType(), + dto.getExtensionSettings(), + dto.getExtensionSettings().isDefault()); add(store, holder); } + private static > void addIfNonDefault( + @Nonnull Holder holder, + @Nonnull ComponentType componentType, + @Nonnull T component, + boolean defaultValue) { + if (!defaultValue) { + holder.addComponent(componentType, component); + } + } + private static void addBodies(@Nonnull Store store, @Nonnull PersistentPhysicsStoreResource persistent) { Map collidersByUuid = new Object2ObjectOpenHashMap<>(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java index 0173c94e..331cf20f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java @@ -65,6 +65,10 @@ public void copyTo(@Nonnull PhysicsExtensionSettings settings) { } } + public boolean isDefault() { + return entries.length == 0; + } + @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.extensionSettingsComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java index 7758a141..8e2bae36 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java @@ -135,6 +135,18 @@ public void copyTo(@Nonnull PhysicsSolverSettings settings) { dynamicSleepTimeUntilSleep); } + public boolean isDefault() { + return solverIterations == PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS + && stabilizationIterations + == PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS + && Float.compare(dynamicSleepLinearThreshold, + PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_LINEAR_THRESHOLD) == 0 + && Float.compare(dynamicSleepAngularThreshold, + PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_ANGULAR_THRESHOLD) == 0 + && Float.compare(dynamicSleepTimeUntilSleep, + PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_TIME_UNTIL_SLEEP) == 0; + } + @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.solverSettingsComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java index a4f2ed1a..0e062676 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java @@ -184,6 +184,33 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { target.setDetachedVisualBlockType(detachedVisualBlockType); } + public boolean isDefault() { + return detachedVisualMaterializationEnabled + == PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED + && detachedVisualMaterializationRadius + == PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_MATERIALIZATION_RADIUS + && detachedVisualDematerializationRadius + == PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_DEMATERIALIZATION_RADIUS + && detachedVisualMaxSpawnsPerTick + == PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_SPAWNS_PER_TICK + && detachedVisualMaxMaterialized + == PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_MAX_MATERIALIZED + && detachedVisualInterestRefreshIntervalTicks + == PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS + && detachedVisualCandidateRefreshIntervalTicks + == PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS + && detachedVisualVisibilityCheckIntervalTicks + == PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS + && detachedVisualBlockType.equals( + PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_BLOCK_TYPE); + } + @Nonnull public static ComponentType getComponentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java index 18206987..e4344896 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java @@ -227,6 +227,34 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { target.setVisualVisibilityCullingEnabled(visualVisibilityCullingEnabled); } + public boolean isDefault() { + return visualFullSyncRadius == PhysicsVisualSyncSettings.DEFAULT_VISUAL_FULL_SYNC_RADIUS + && visualMaxSyncRadius == PhysicsVisualSyncSettings.DEFAULT_VISUAL_MAX_SYNC_RADIUS + && visualFarSyncCutoffEnabled + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_CUTOFF_ENABLED + && visualMidSyncIntervalTicks + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS + && visualFarSyncIntervalTicks + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_FAR_SYNC_INTERVAL_TICKS + && visualOcclusionMode == PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_MODE + && visualOcclusionRaycastsPerTick + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_RAYCASTS_PER_TICK + && visualOcclusionCacheTicks + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_OCCLUSION_CACHE_TICKS + && visualSnapshotPredictionEnabled + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED + && Float.compare(visualSnapshotPredictionMaxSeconds, + PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS) == 0 + && visualSnapshotSmoothingEnabled + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED + && Float.compare(visualSnapshotSmoothingRate, + PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE) == 0 + && entityVisualSyncCullingEnabled + == PhysicsVisualSyncSettings.DEFAULT_ENTITY_VISUAL_SYNC_CULLING_ENABLED + && visualVisibilityCullingEnabled + == PhysicsVisualSyncSettings.DEFAULT_VISUAL_VISIBILITY_CULLING_ENABLED; + } + @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.visualSyncSettingsComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java index 0bb39adf..f215e2d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java @@ -130,6 +130,20 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { target.setCollisionLodFarSleepEnabled(collisionLodFarSleepEnabled); } + public boolean isDefault() { + return collisionLodEnabled == PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_ENABLED + && collisionLodNearRadius + == PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_NEAR_RADIUS + && collisionLodMidRadius + == PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_MID_RADIUS + && collisionLodHysteresis + == PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_HYSTERESIS + && collisionLodRefreshIntervalTicks + == PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS + && collisionLodFarSleepEnabled + == PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_FAR_SLEEP_ENABLED; + } + @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.collisionLodSettingsComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java index 2253862b..697c5456 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java @@ -219,6 +219,21 @@ public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { settings.setTerrainMaterial(terrainFriction, terrainRestitution); } + public boolean isDefault() { + return terrainMode == PhysicsChunkTerrainMode.NONE + && entityChunkBoundaryMode + == PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE + && nativeVoxelTerrainEnabled + == PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED + && radius == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS + && bodyRadius == PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS + && ttlTicks == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS + && Float.compare(terrainFriction, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION) == 0 + && Float.compare(terrainRestitution, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; + } + @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.physicsChunkTerrainComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index 2eaf1a17..41cf8ce4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -61,6 +61,15 @@ public static Ref resolveRef(@Nonnull Store store, return ref != null && ref.getStore() == checkedStore && ref.isValid() ? ref : null; } + @Nonnull + public static Holder spaceHolder(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull SpaceComponent space) { + Holder holder = entityHolder(store, spaceUuid); + addSpaceComponent(holder, space); + return holder; + } + @Nonnull public static Holder spaceHolder(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -83,6 +92,13 @@ public static Holder spaceHolder(@Nonnull Store stor return holder; } + public static void addSpaceComponent(@Nonnull Holder holder, + @Nonnull SpaceComponent space) { + Objects.requireNonNull(holder, "holder") + .addComponent(SpaceComponent.getComponentType(), + Objects.requireNonNull(space, "space").clone()); + } + @Nonnull public static Holder bodyHolder(@Nonnull Store store, @Nonnull UUID bodyUuid, @@ -126,9 +142,7 @@ public static void addSpaceComponents(@Nonnull Holder holder, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { - Objects.requireNonNull(holder, "holder") - .addComponent(SpaceComponent.getComponentType(), - Objects.requireNonNull(space, "space").clone()); + addSpaceComponent(holder, space); holder.addComponent(PhysicsChunkTerrainComponent.getComponentType(), Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); addSpaceSettingsComponents(holder, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index a08ae1e5..b5b4ddb0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -1,5 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.physicsstore; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -8,6 +10,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; @@ -58,7 +61,9 @@ public static boolean hasSpace(@Nonnull Store store, @Nonnull public static SpaceId create(@Nonnull Store store, @Nonnull BackendId backendId) { - return create(store, backendId, PhysicsSpaceSettings.defaults()); + SpaceId spaceId = SpaceId.next(); + create(store, UUID.randomUUID(), spaceId, backendId); + return spaceId; } @Nonnull @@ -70,6 +75,19 @@ public static SpaceId create(@Nonnull Store store, return spaceId; } + @Nonnull + public static Ref create(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull SpaceId spaceId, + @Nonnull BackendId backendId) { + Store checkedStore = requireWorldThread(store, + "create a PhysicsStore space"); + return PhysicsStoreSpaceMutations.addSpace(checkedStore, + Objects.requireNonNull(spaceUuid, "spaceUuid"), + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(backendId, "backendId")); + } + @Nonnull public static Ref create(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -173,6 +191,89 @@ public static void putSettings(@Nonnull Store store, Objects.requireNonNull(settings, "settings")); } + @Nullable + public static > T getSpaceComponent( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull ComponentType componentType) { + Store checkedStore = requireWorldThread(store, + "read a PhysicsStore space component"); + Ref ref = requireSpaceRef(checkedStore, + Objects.requireNonNull(spaceId, "spaceId")); + return checkedStore.getComponent(ref, Objects.requireNonNull(componentType, + "componentType")); + } + + @Nullable + public static > T getSpaceComponent( + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull ComponentType componentType) { + Store checkedStore = requireWorldThread(store, + "read a PhysicsStore space component"); + Ref ref = requireSpaceRef(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef")); + return checkedStore.getComponent(ref, Objects.requireNonNull(componentType, + "componentType")); + } + + public static > void putSpaceComponent( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull ComponentType componentType, + @Nonnull T component) { + Store checkedStore = requireWorldThread(store, + "put a PhysicsStore space component"); + putSpaceComponent(checkedStore, + requireSpaceRef(checkedStore, Objects.requireNonNull(spaceId, "spaceId")), + componentType, + component); + } + + public static > void putSpaceComponent( + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull ComponentType componentType, + @Nonnull T component) { + Store checkedStore = requireWorldThread(store, + "put a PhysicsStore space component"); + Ref ref = requireSpaceRef(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef")); + checkedStore.putComponent(ref, + Objects.requireNonNull(componentType, "componentType"), + copy(Objects.requireNonNull(component, "component"))); + checkedStore.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + + public static > boolean removeSpaceComponent( + @Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull ComponentType componentType) { + Store checkedStore = requireWorldThread(store, + "remove a PhysicsStore space component"); + return removeSpaceComponent(checkedStore, + requireSpaceRef(checkedStore, Objects.requireNonNull(spaceId, "spaceId")), + componentType); + } + + public static > boolean removeSpaceComponent( + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull ComponentType componentType) { + Store checkedStore = requireWorldThread(store, + "remove a PhysicsStore space component"); + Ref ref = requireSpaceRef(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef")); + boolean removed = checkedStore.removeComponentIfExists(ref, + Objects.requireNonNull(componentType, "componentType")); + if (removed) { + checkedStore.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + return removed; + } + public static void removeEmpty(@Nonnull Store store, @Nonnull SpaceId spaceId) { Store checkedStore = requireWorldThread(store, @@ -197,4 +298,33 @@ private static Store requireWorldThread(@Nonnull Store requireSpaceRef(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + if (ref == null) { + throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() + + " is not registered"); + } + return ref; + } + + @Nonnull + private static Ref requireSpaceRef(@Nonnull Store store, + @Nonnull Ref ref) { + if (ref.getStore() != store || !ref.isValid()) { + throw new IllegalArgumentException("PhysicsStore space entity is not valid: " + ref); + } + if (store.getComponent(ref, SpaceComponent.getComponentType()) == null) { + throw new IllegalArgumentException("PhysicsStore entity is not a space entity: " + ref); + } + return ref; + } + + @Nonnull + @SuppressWarnings("unchecked") + private static > T copy(@Nonnull T component) { + return (T) component.clone(); + } } From c5a5a34b081060b0c047f214494a37c65cbc4b11 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:20:26 +0200 Subject: [PATCH 389/534] refactor(core): bind voxel shapes through body rows Signed-off-by: Blovien --- .../internal/systems/BodyBindingSystem.java | 122 +++++++++++++----- 1 file changed, 92 insertions(+), 30 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java index 08a944a3..a6e9f010 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java @@ -12,11 +12,14 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -53,14 +56,17 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsTerrainPayloadResource terrainPayloads = store.getResource( + PhysicsTerrainPayloadResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindBodies(runtime, identity, restore, chunk); + (chunk, _) -> bindBodies(runtime, terrainPayloads, identity, restore, chunk); store.forEachChunk(systemIndex, collector); } private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ArchetypeChunk chunk) { @@ -78,6 +84,7 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, continue; } bindBody(runtime, + terrainPayloads, identity, restore, bodyRef, @@ -93,6 +100,7 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, } private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Ref bodyRef, @@ -127,36 +135,57 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, float mass = bodyType == PhysicsBodyType.DYNAMIC ? bodyDynamics.getMass() : 0.0f; long bodyId = Long.MIN_VALUE; try { - bodyId = backendRuntime.createBody(spaceHandle.value(), - BackendRuntimeCodes.shapeTypeCode(shape.getShapeType()), - shape.getHalfExtentX(), - shape.getHalfExtentY(), - shape.getHalfExtentZ(), - shape.getRadius(), - shape.getHalfHeight(), - BackendRuntimeCodes.axisCode(shape.getAxis()), - shape.getGroundY(), - mass, - BackendRuntimeCodes.bodyTypeCode(bodyType), - position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w); + if (shape.getShapeType() == ShapeType.VOXELS) { + if (bodyType != PhysicsBodyType.STATIC) { + restore.recordSoftSkip("Voxel body must be static: " + bodyUuid); + return; + } + bodyId = createVoxelBody(terrainPayloads, + backendRuntime, + spaceHandle, + shape, + material, + filter, + position); + if (bodyId == Long.MIN_VALUE) { + restore.recordSoftSkip("Voxel body payload is missing or unsupported: " + + bodyUuid); + return; + } + } else { + bodyId = backendRuntime.createBody(spaceHandle.value(), + BackendRuntimeCodes.shapeTypeCode(shape.getShapeType()), + shape.getHalfExtentX(), + shape.getHalfExtentY(), + shape.getHalfExtentZ(), + shape.getRadius(), + shape.getHalfHeight(), + BackendRuntimeCodes.axisCode(shape.getAxis()), + shape.getGroundY(), + mass, + BackendRuntimeCodes.bodyTypeCode(bodyType), + position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w); + backendRuntime.setBodyDamping(spaceHandle.value(), + bodyId, + bodyDynamics.getLinearDamping(), + bodyDynamics.getAngularDamping()); + backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, material.getFriction()); + backendRuntime.setBodyRestitution(spaceHandle.value(), + bodyId, + material.getRestitution()); + backendRuntime.setBodyCollisionFilter(spaceHandle.value(), + bodyId, + filter.getCollisionGroup(), + filter.getCollisionMask()); + backendRuntime.setBodySensor(spaceHandle.value(), bodyId, collider.isSensor()); + } BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); - backendRuntime.setBodyDamping(spaceHandle.value(), - bodyId, - bodyDynamics.getLinearDamping(), - bodyDynamics.getAngularDamping()); - backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, material.getFriction()); - backendRuntime.setBodyRestitution(spaceHandle.value(), bodyId, material.getRestitution()); - backendRuntime.setBodyCollisionFilter(spaceHandle.value(), - bodyId, - filter.getCollisionGroup(), - filter.getCollisionMask()); - backendRuntime.setBodySensor(spaceHandle.value(), bodyId, collider.isSensor()); if (bodyDynamics.isContinuousCollisionEnabled() && backendRuntime.supportsContinuousCollision(spaceHandle.value())) { backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); @@ -182,6 +211,39 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, } } + private static long createVoxelBody(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull ShapeComponent shape, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter, + @Nonnull Vector3f position) { + String payloadKey = shape.getResourceKey(); + if (payloadKey.isBlank() || !backendRuntime.supportsVoxelTerrain(spaceHandle.value())) { + return Long.MIN_VALUE; + } + TerrainColliderPayload payload = terrainPayloads.get(payloadKey); + if (payload == null || !payload.hasFullCubeVoxels()) { + return Long.MIN_VALUE; + } + try { + return backendRuntime.createVoxelTerrain(spaceHandle.value(), + payload.voxelSizeX(), + payload.voxelSizeY(), + payload.voxelSizeZ(), + payload.voxelCoordinates(), + position.x, + position.y, + position.z, + material.getFriction(), + material.getRestitution(), + filter.getCollisionGroup(), + filter.getCollisionMask()); + } catch (UnsupportedOperationException exception) { + return Long.MIN_VALUE; + } + } + private static void applyInitialTargetState(@Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle bodyHandle, From eb62aa8dfac18e17d9b15bb62997dc6e20ace4b3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:22:30 +0200 Subject: [PATCH 390/534] feat(core): add chunk collision source component Signed-off-by: Blovien --- .../PhysicsComponentTypeRegistry.java | 13 ++ .../components/PhysicsComponentTypes.java | 7 + .../ChunkCollisionSourceComponent.java | 139 ++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java index 37a264b5..8985740e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import javax.annotation.Nonnull; @@ -40,6 +41,8 @@ public final class PhysicsComponentTypeRegistry { @Nullable private static ComponentType terrainColliderComponentType; @Nullable + private static ComponentType chunkCollisionSourceComponentType; + @Nullable private static ComponentType physicsChunkTerrainComponentType; @Nullable private static ComponentType dynamicsComponentType; @@ -92,6 +95,10 @@ public static void registerComponentTypes( TerrainColliderComponent.class, "TerrainCollider", TerrainColliderComponent.CODEC); + chunkCollisionSourceComponentType = registry.registerComponent( + ChunkCollisionSourceComponent.class, + "ChunkCollisionSource", + ChunkCollisionSourceComponent.CODEC); physicsChunkTerrainComponentType = registry.registerComponent( PhysicsChunkTerrainComponent.class, "PhysicsChunkTerrain", @@ -172,6 +179,12 @@ public static ComponentType bodyCommandCompo return terrainColliderComponentType; } + @Nonnull + public static ComponentType + chunkCollisionSourceComponentType() { + return chunkCollisionSourceComponentType; + } + @Nonnull public static ComponentType physicsChunkTerrainComponentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 346b3452..8bf9ac86 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -3,6 +3,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import javax.annotation.Nonnull; @@ -41,6 +42,12 @@ public static ComponentType bodyCommandCompo return PhysicsComponentTypeRegistry.terrainColliderComponentType(); } + @Nonnull + public static ComponentType + chunkCollisionSourceComponentType() { + return PhysicsComponentTypeRegistry.chunkCollisionSourceComponentType(); + } + @Nonnull public static ComponentType physicsChunkTerrainComponentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java new file mode 100644 index 00000000..35a9c413 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java @@ -0,0 +1,139 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * PhysicsChunk source metadata for generated runtime-only terrain body rows. + */ +public final class ChunkCollisionSourceComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChunkCollisionSourceComponent.class, + ChunkCollisionSourceComponent::new) + .append(new KeyedCodec<>("SourceKey", Codec.STRING, false), + (component, value) -> component.sourceKey = value != null ? value : "", + ChunkCollisionSourceComponent::getSourceKey) + .add() + .append(new KeyedCodec<>("ChunkX", Codec.INTEGER, false), + (component, value) -> component.chunkX = value != null ? value : 0, + ChunkCollisionSourceComponent::getChunkX) + .add() + .append(new KeyedCodec<>("SectionY", Codec.INTEGER, false), + (component, value) -> component.sectionY = value != null ? value : 0, + ChunkCollisionSourceComponent::getSectionY) + .add() + .append(new KeyedCodec<>("ChunkZ", Codec.INTEGER, false), + (component, value) -> component.chunkZ = value != null ? value : 0, + ChunkCollisionSourceComponent::getChunkZ) + .add() + .append(new KeyedCodec<>("PayloadResourceKey", Codec.STRING, false), + (component, value) -> component.payloadResourceKey = value != null ? value : "", + ChunkCollisionSourceComponent::getPayloadResourceKey) + .add() + .append(new KeyedCodec<>("PartKind", new EnumCodec<>(PartKind.class), false), + (component, value) -> component.partKind = value != null + ? value + : PartKind.BOX, + ChunkCollisionSourceComponent::getPartKind) + .add() + .append(new KeyedCodec<>("PartIndex", Codec.INTEGER, false), + (component, value) -> component.partIndex = value != null ? value : 0, + ChunkCollisionSourceComponent::getPartIndex) + .add() + .build(); + + @Nonnull + private String sourceKey = ""; + private int chunkX; + private int sectionY; + private int chunkZ; + @Nonnull + private String payloadResourceKey = ""; + @Nonnull + private PartKind partKind = PartKind.BOX; + private int partIndex; + + public ChunkCollisionSourceComponent() { + } + + public ChunkCollisionSourceComponent(@Nonnull String sourceKey, + int chunkX, + int sectionY, + int chunkZ, + @Nonnull String payloadResourceKey, + @Nonnull PartKind partKind, + int partIndex) { + this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); + this.chunkX = chunkX; + this.sectionY = sectionY; + this.chunkZ = chunkZ; + this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, + "payloadResourceKey"); + this.partKind = Objects.requireNonNull(partKind, "partKind"); + this.partIndex = partIndex; + } + + @Nonnull + public String getSourceKey() { + return sourceKey; + } + + public int getChunkX() { + return chunkX; + } + + public int getSectionY() { + return sectionY; + } + + public int getChunkZ() { + return chunkZ; + } + + @Nonnull + public String getPayloadResourceKey() { + return payloadResourceKey; + } + + @Nonnull + public PartKind getPartKind() { + return partKind; + } + + public int getPartIndex() { + return partIndex; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsComponentTypes.chunkCollisionSourceComponentType(); + } + + @Nonnull + @Override + public ChunkCollisionSourceComponent clone() { + return new ChunkCollisionSourceComponent(sourceKey, + chunkX, + sectionY, + chunkZ, + payloadResourceKey, + partKind, + partIndex); + } + + public enum PartKind { + BOX, + DETAIL_BOX, + VOXEL_TERRAIN + } +} From 17ac74e500f55cfc66525f5c237f36063e051044 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:31:24 +0200 Subject: [PATCH 391/534] refactor(core): fan out terrain into body rows Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 51 ++- .../PhysicsStoreRegistration.java | 2 +- .../internal/systems/BodyBindingSystem.java | 3 +- .../internal/systems/IdentityIndexSystem.java | 2 +- .../systems/TerrainMutationDrainSystem.java | 417 ++++++++++++++---- 5 files changed, 360 insertions(+), 115 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index faf153e8..9804dc55 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import it.unimi.dsi.fastutil.longs.LongArrayList; import java.util.ArrayList; import java.util.List; @@ -96,12 +97,18 @@ public static int clearTerrainForSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore terrain rows"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); int removedBodies = 0; - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); + Ref spaceRef = identity.getByUuid(spaceUuid); List removals = collectTerrainRows(store, spaceUuid, spaceRef); for (RowRemoval removal : removals) { - removedBodies += removeRuntimeTerrain(runtime, removal); + if (removal.kind() == RowKind.TERRAIN) { + removedBodies += removeRuntimeTerrain(runtime, removal); + } else if (removal.kind() == RowKind.BODY + && removeRuntimeBody(runtime, identity, removal)) { + removedBodies++; + } } store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); @@ -222,8 +229,13 @@ private static List collectRows(@Nonnull Store store, return; } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + ChunkCollisionSourceComponent source = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()); if (matchesBody(body, rowUuid, ref, spaceUuid, spaceRef, bodyUuid, bodyRef)) { - removals.add(new RowRemoval(ref, rowUuid, RowKind.BODY, null)); + removals.add(new RowRemoval(ref, + rowUuid, + RowKind.BODY, + source != null ? source.getPayloadResourceKey() : null)); } }); return new ArrayList<>(removals); @@ -238,20 +250,32 @@ private static List collectTerrainRows(@Nonnull Store store.forEachEntityParallel(uuidType, (index, chunk, _) -> { TerrainColliderComponent terrain = chunk.getComponent(index, TerrainColliderComponent.getComponentType()); - if (terrain == null || !matchesSpace(terrain.getSpaceRef(), + UuidComponent uuid = chunk.getComponent(index, uuidType); + if (uuid == null) { + return; + } + if (terrain != null && matchesSpace(terrain.getSpaceRef(), terrain.getSpaceUuid(), spaceRef, spaceUuid)) { + removals.add(new RowRemoval(chunk.getReferenceTo(index), + uuid.getUuid(), + RowKind.TERRAIN, + terrain.getPayloadResourceKey())); return; } - UuidComponent uuid = chunk.getComponent(index, uuidType); - if (uuid == null) { - return; + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + ChunkCollisionSourceComponent source = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()); + if (source != null + && body != null + && body.getKind().isTerrainCollider() + && matchesSpace(body.getSpaceRef(), body.getSpaceUuid(), spaceRef, spaceUuid)) { + removals.add(new RowRemoval(chunk.getReferenceTo(index), + uuid.getUuid(), + RowKind.BODY, + source.getPayloadResourceKey())); } - removals.add(new RowRemoval(chunk.getReferenceTo(index), - uuid.getUuid(), - RowKind.TERRAIN, - terrain.getPayloadResourceKey())); }); return new ArrayList<>(removals); } @@ -340,8 +364,7 @@ private static void removeRows(@Nonnull Store store, continue; } identity.removeUuid(removal.rowUuid(), removal.ref()); - if (removal.kind() == RowKind.TERRAIN - && removal.payloadResourceKey() != null + if (removal.payloadResourceKey() != null && !removal.payloadResourceKey().isBlank()) { terrainPayloads.remove(removal.payloadResourceKey()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 2c6a5044..5295deff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -72,11 +72,11 @@ public static void register(@Nonnull ComponentRegistryProxy regist PhysicsResourceTypes.registerResourceTypes(registry); registry.registerSystem(new PersistenceHydrationSystem()); - registry.registerSystem(new TerrainMutationDrainSystem()); registry.registerSystem(new IdentityIndexSystem()); registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); + registry.registerSystem(new TerrainMutationDrainSystem()); registry.registerSystem(new BodyBindingSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java index a6e9f010..f6f4dadd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java @@ -45,7 +45,8 @@ public final class BodyBindingSystem extends TickingSystem private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), - new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class) + new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class), + new SystemDependency<>(Order.AFTER, TerrainMutationDrainSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java index 49e180bd..94f86d53 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java @@ -24,7 +24,7 @@ public final class IdentityIndexSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TerrainMutationDrainSystem.class) + new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java index 2d4dcae8..d89e2419 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java @@ -2,190 +2,386 @@ import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; /** - * Applies copied terrain mutations before backend reconciliation. + * Applies copied PhysicsChunk terrain mutations as runtime-only terrain body rows. */ public final class TerrainMutationDrainSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistenceHydrationSystem.class) + new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), + new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class) ); @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } PhysicsTerrainMutationQueueResource queue = store.getResource( PhysicsTerrainMutationQueueResource.getResourceType()); List mutations = queue.drain(); if (mutations.isEmpty()) { return; } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource( PhysicsIdentityIndexResource.getResourceType()); - PhysicsRestoreStatusResource restore = store.getResource( - PhysicsRestoreStatusResource.getResourceType()); PhysicsTerrainPayloadResource terrainPayloads = store.getResource( PhysicsTerrainPayloadResource.getResourceType()); - Map> refsThisDrain = new Object2ObjectOpenHashMap<>(); - applyRemovals(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - mutations); - applyUpserts(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - mutations); + applyRemovals(store, runtime, identity, terrainPayloads, mutations); + applyUpserts(store, runtime, identity, terrainPayloads, restore, mutations); } private static void applyRemovals(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull Map> refsThisDrain, - @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List mutations) { for (TerrainColliderMutation mutation : mutations) { if (mutation.remove()) { - applyTerrainMutation(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - mutation); + removeGeneratedRows(store, runtime, identity, terrainPayloads, mutation); + removePayload(terrainPayloads, mutation.payloadResourceKey()); } } } private static void applyUpserts(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List mutations) { for (TerrainColliderMutation mutation : mutations) { if (!mutation.remove()) { - applyTerrainMutation(store, - identity, - terrainPayloads, - refsThisDrain, - restore, - mutation); + applyUpsert(store, runtime, identity, terrainPayloads, restore, mutation); } } } - private static void applyTerrainMutation(@Nonnull Store store, + private static void applyUpsert(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull Map> refsThisDrain, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull TerrainColliderMutation mutation) { - UUID terrainUuid = mutation.terrainColliderUuid(); - Ref ref = refForUuid(identity, refsThisDrain, terrainUuid); - if (mutation.remove()) { - if (ref != null) { - TerrainColliderComponent existing = store.getComponent(ref, - TerrainColliderComponent.getComponentType()); - if (existing != null) { - removePayload(terrainPayloads, existing.getPayloadResourceKey()); - } - PhysicsEntities.putTerrainColliderComponent(store, - ref, - removedTerrainComponent(identity, mutation)); - } - removePayload(terrainPayloads, mutation.payloadResourceKey()); - return; - } TerrainColliderPayload payload = mutation.payload(); if (payload == null || payload.isEmpty()) { restore.recordSoftSkip("Terrain upsert payload is missing: " + mutation.sourceKey()); return; } - terrainPayloads.put(mutation.payloadResourceKey(), payload); - TerrainColliderComponent component = activeTerrainComponent(identity, mutation); - if (ref != null) { - TerrainColliderComponent existing = store.getComponent(ref, - TerrainColliderComponent.getComponentType()); - if (existing != null - && !existing.getPayloadResourceKey().equals(component.getPayloadResourceKey())) { - removePayload(terrainPayloads, existing.getPayloadResourceKey()); - } - PhysicsEntities.putTerrainColliderComponent(store, ref, component); - refsThisDrain.put(terrainUuid, ref); + Ref spaceRef = PhysicsStoreSystemSupport.refForUuid(identity, + mutation.spaceUuid()); + BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; + PhysicsBackendRuntime backendRuntime = spaceHandle != null + ? runtime.runtimeForSpaceHandle(spaceHandle) + : null; + if (spaceRef == null || spaceHandle == null || backendRuntime == null) { + restore.recordSoftSkip("Terrain references unbound space: " + mutation.sourceKey()); return; } - refsThisDrain.put(terrainUuid, - store.addEntity(PhysicsEntities.terrainColliderHolder(store, - terrainUuid, - component), - AddReason.SPAWN)); - } - - @Nullable - private static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Map> refsThisDrain, - @Nonnull UUID uuid) { - Ref ref = refsThisDrain.get(uuid); - if (ref != null && ref.isValid()) { - return ref; + removeGeneratedRows(store, runtime, identity, terrainPayloads, mutation); + terrainPayloads.put(mutation.payloadResourceKey(), payload); + + boolean nativeVoxel = payload.nativeVoxelTerrainEnabled() + && payload.hasFullCubeVoxels() + && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); + if (nativeVoxel) { + addVoxelBody(store, identity, spaceRef, mutation, payload); + } else { + addBoxBodies(store, + identity, + spaceRef, + mutation, + payload, + payload.mergedFullCubeBoxes(), + PartKind.BOX); } - return PhysicsStoreSystemSupport.refForUuid(identity, uuid); + addBoxBodies(store, + identity, + spaceRef, + mutation, + payload, + payload.detailBoxes(), + PartKind.DETAIL_BOX); } - @Nonnull - private static TerrainColliderComponent activeTerrainComponent( + private static void addVoxelBody(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull TerrainColliderMutation mutation) { - return terrainComponent(identity, mutation, mutation.payloadResourceKey(), true); + @Nonnull Ref spaceRef, + @Nonnull TerrainColliderMutation mutation, + @Nonnull TerrainColliderPayload payload) { + TargetComponent target = new TargetComponent(); + target.setPosition(new Vector3f(mutation.chunkX() << ChunkUtil.BITS, + mutation.sectionY() << ChunkUtil.BITS, + mutation.chunkZ() << ChunkUtil.BITS)); + addTerrainBody(store, + identity, + spaceRef, + mutation, + target, + new ShapeComponent(ShapeType.VOXELS, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + mutation.payloadResourceKey()), + material(payload), + filter(payload), + PartKind.VOXEL_TERRAIN, + 0); + } + + private static void addBoxBodies(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Ref spaceRef, + @Nonnull TerrainColliderMutation mutation, + @Nonnull TerrainColliderPayload payload, + @Nonnull List boxes, + @Nonnull PartKind partKind) { + for (int index = 0; index < boxes.size(); index++) { + BoxPayload box = boxes.get(index); + if (box.halfX() <= 0.0 || box.halfY() <= 0.0 || box.halfZ() <= 0.0) { + continue; + } + TargetComponent target = new TargetComponent(); + target.setPosition(new Vector3f((float) box.centerX(), + (float) box.centerY(), + (float) box.centerZ())); + addTerrainBody(store, + identity, + spaceRef, + mutation, + target, + new ShapeComponent(ShapeType.BOX, + (float) box.halfX(), + (float) box.halfY(), + (float) box.halfZ(), + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + material(payload), + filter(payload), + partKind, + index); + } + } + + private static void addTerrainBody(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Ref spaceRef, + @Nonnull TerrainColliderMutation mutation, + @Nonnull TargetComponent target, + @Nonnull ShapeComponent shape, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter, + @Nonnull PartKind partKind, + int partIndex) { + UUID bodyUuid = terrainBodyUuid(mutation.spaceUuid(), + mutation.sourceKey(), + partKind, + partIndex); + BodyComponent body = new BodyComponent(mutation.spaceUuid(), + PhysicsBodyKind.TERRAIN, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + body.setSpaceRef(spaceRef); + var holder = PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.STATIC, 0.0f, 0.0f, 0.0f, false), + target, + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + shape, + material, + filter); + holder.addComponent(ChunkCollisionSourceComponent.getComponentType(), + new ChunkCollisionSourceComponent(mutation.sourceKey(), + mutation.chunkX(), + mutation.sectionY(), + mutation.chunkZ(), + mutation.payloadResourceKey(), + partKind, + partIndex)); + Ref ref = store.addEntity(holder, AddReason.SPAWN); + assert ref != null; + identity.putUuid(bodyUuid, ref); + store.getExternalData().putRefForUUID(bodyUuid, ref); } @Nonnull - private static TerrainColliderComponent removedTerrainComponent( + private static MaterialComponent material(@Nonnull TerrainColliderPayload payload) { + return new MaterialComponent(payload.friction(), payload.restitution()); + } + + @Nonnull + private static CollisionFilterComponent filter(@Nonnull TerrainColliderPayload payload) { + return new CollisionFilterComponent(payload.collisionGroup(), payload.collisionMask()); + } + + private static void removeGeneratedRows(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nonnull TerrainColliderMutation mutation) { - return terrainComponent(identity, mutation, mutation.payloadResourceKey(), false); + List rows = collectGeneratedRows(store, mutation); + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = store.getResource( + PhysicsBodyRegistrationResource.getResourceType()); + for (GeneratedRow row : rows) { + if (row.kind() == GeneratedRowKind.BODY) { + removeRuntimeBody(runtime, identity, row); + PhysicsControlRuntimeStates.clearControlled(row.ref()); + snapshots.removeBody(row.uuid()); + registrations.removeBody(row.uuid()); + } else { + removeRuntimeTerrain(runtime, row); + } + removePayload(terrainPayloads, row.payloadResourceKey()); + identity.removeUuid(row.uuid(), row.ref()); + if (row.ref().isValid()) { + store.removeEntity(row.ref(), + store.getRegistry().newHolder(), + RemoveReason.REMOVE); + } + } } @Nonnull - private static TerrainColliderComponent terrainComponent( + private static List collectGeneratedRows(@Nonnull Store store, + @Nonnull TerrainColliderMutation mutation) { + ConcurrentLinkedQueue rows = new ConcurrentLinkedQueue<>(); + store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { + UUID rowUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(rowUuid)) { + return; + } + ChunkCollisionSourceComponent source = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()); + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (source != null + && body != null + && matchesSource(mutation, body, source)) { + rows.add(new GeneratedRow(chunk.getReferenceTo(index), + rowUuid, + GeneratedRowKind.BODY, + source.getPayloadResourceKey())); + return; + } + TerrainColliderComponent terrain = chunk.getComponent(index, + TerrainColliderComponent.getComponentType()); + if (terrain != null && matchesLegacyTerrain(mutation, rowUuid, terrain)) { + rows.add(new GeneratedRow(chunk.getReferenceTo(index), + rowUuid, + GeneratedRowKind.TERRAIN, + terrain.getPayloadResourceKey())); + } + }); + return new ArrayList<>(rows); + } + + private static boolean matchesSource(@Nonnull TerrainColliderMutation mutation, + @Nonnull BodyComponent body, + @Nonnull ChunkCollisionSourceComponent source) { + return mutation.spaceUuid().equals(body.getSpaceUuid()) + && mutation.sourceKey().equals(source.getSourceKey()); + } + + private static boolean matchesLegacyTerrain(@Nonnull TerrainColliderMutation mutation, + @Nonnull UUID rowUuid, + @Nonnull TerrainColliderComponent terrain) { + return mutation.terrainColliderUuid().equals(rowUuid) + || (mutation.spaceUuid().equals(terrain.getSpaceUuid()) + && mutation.sourceKey().equals(terrain.getSourceKey())); + } + + private static void removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull TerrainColliderMutation mutation, - @Nullable String payloadResourceKey, - boolean retained) { - TerrainColliderComponent component = new TerrainColliderComponent(mutation.spaceUuid(), - mutation.sourceKey(), - mutation.chunkX(), - mutation.sectionY(), - mutation.chunkZ(), - payloadResourceKey != null ? payloadResourceKey : "", - retained); - component.setSpaceRef(PhysicsStoreSystemSupport.refForUuid(identity, mutation.spaceUuid())); - return component; + @Nonnull GeneratedRow row) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(row.ref()); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(row.ref()); + if (bodyHandle != null && spaceHandle != null) { + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (backendRuntime != null) { + backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); + } + identity.removeBodyHandle(bodyHandle); + } + runtime.removeBodyHandle(row.uuid(), row.ref()); + } + + private static void removeRuntimeTerrain(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull GeneratedRow row) { + BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(row.ref()); + LongArrayList bodyHandles = new LongArrayList(); + runtime.forEachTerrainBodyHandle(row.ref(), bodyHandles::add); + if (spaceHandle != null) { + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (backendRuntime != null) { + for (int index = 0; index < bodyHandles.size(); index++) { + backendRuntime.removeBody(spaceHandle.value(), bodyHandles.getLong(index)); + } + } + } + runtime.removeTerrainHandles(row.ref(), row.uuid()); } private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, @@ -195,9 +391,34 @@ private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrain } } + @Nonnull + private static UUID terrainBodyUuid(@Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + @Nonnull PartKind partKind, + int partIndex) { + String key = spaceUuid + "|" + sourceKey + "|" + partKind.name() + "|" + partIndex; + return UUID.nameUUIDFromBytes(key.getBytes(StandardCharsets.UTF_8)); + } + @Nonnull @Override public Set> getDependencies() { return DEPENDENCIES; } + + private enum GeneratedRowKind { + BODY, + TERRAIN + } + + private record GeneratedRow(@Nonnull Ref ref, + @Nonnull UUID uuid, + @Nonnull GeneratedRowKind kind, + @Nullable String payloadResourceKey) { + private GeneratedRow { + Objects.requireNonNull(ref, "ref"); + Objects.requireNonNull(uuid, "uuid"); + Objects.requireNonNull(kind, "kind"); + } + } } From 86c1492b22517a8d9b10e8c0d78841cedda9c2cd Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:34:50 +0200 Subject: [PATCH 392/534] feat(core): stitch voxel terrain body rows Signed-off-by: Blovien --- .../PhysicsStoreRegistration.java | 2 + .../resources/PhysicsRuntimeResource.java | 21 ++ .../internal/systems/TargetBindingSystem.java | 1 + .../systems/TerrainMutationDrainSystem.java | 2 +- .../systems/TerrainVoxelStitchingSystem.java | 188 ++++++++++++++++++ 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainVoxelStitchingSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 5295deff..4842ef2b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -40,6 +40,7 @@ import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; +import dev.hytalemodding.impulse.core.internal.systems.TerrainVoxelStitchingSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; @@ -82,6 +83,7 @@ public static void register(@Nonnull ComponentRegistryProxy regist registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); registry.registerSystem(new TerrainColliderBindingSystem()); + registry.registerSystem(new TerrainVoxelStitchingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index 0647d7fc..bc712ae1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -110,6 +110,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap terrainPayloadKeysByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull + private final Int2ObjectOpenHashMap chunkCollisionPayloadKeysByRowIndex = + new Int2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap bodyHandlesBySpaceHandle = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -197,6 +200,7 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { bodyRefsByRowIndex.remove(rowIndex); bodyHandlesByRowIndex.remove(rowIndex); bodySpaceHandlesByRowIndex.remove(rowIndex); + chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); } }); } @@ -240,6 +244,7 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRefsByRowIndex.remove(rowIndex); BackendBodyHandle removedByRef = bodyHandlesByRowIndex.remove(rowIndex); BackendSpaceHandle spaceHandleByRef = bodySpaceHandlesByRowIndex.remove(rowIndex); + chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); removeBodyHandleIndexes(removed != null ? removed : removedByRef, spaceHandle != null ? spaceHandle : spaceHandleByRef); markRegistrationTopologyChanged(); @@ -467,6 +472,20 @@ public boolean isTerrainPayloadBound(@Nonnull Ref terrainRef, return payloadKey.equals(terrainPayloadKeysByRowIndex.get(terrainRef.getIndex())); } + public void markChunkCollisionPayloadBound(@Nonnull Ref bodyRef, + @Nonnull String payloadKey) { + chunkCollisionPayloadKeysByRowIndex.put(bodyRef.getIndex(), payloadKey); + } + + public boolean isChunkCollisionPayloadBound(@Nonnull Ref bodyRef, + @Nonnull String payloadKey) { + return payloadKey.equals(chunkCollisionPayloadKeysByRowIndex.get(bodyRef.getIndex())); + } + + public void clearChunkCollisionPayloadBound(@Nonnull Ref bodyRef) { + chunkCollisionPayloadKeysByRowIndex.remove(bodyRef.getIndex()); + } + public boolean hasTerrainBodyHandles(@Nonnull Ref terrainRef) { LongList bodyHandles = terrainBodyHandlesByRowIndex.get(terrainRef.getIndex()); return bodyHandles != null && !bodyHandles.isEmpty(); @@ -592,6 +611,7 @@ public void clear() { terrainVoxelBodyHandlesByRowIndex.clear(); terrainSpaceHandlesByRowIndex.clear(); terrainPayloadKeysByRowIndex.clear(); + chunkCollisionPayloadKeysByRowIndex.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); bodySnapshotMetadataByHandle.clear(); @@ -748,6 +768,7 @@ public PhysicsRuntimeResource clone() { copy.terrainVoxelBodyHandlesByRowIndex.putAll(terrainVoxelBodyHandlesByRowIndex); copy.terrainSpaceHandlesByRowIndex.putAll(terrainSpaceHandlesByRowIndex); copy.terrainPayloadKeysByRowIndex.putAll(terrainPayloadKeysByRowIndex); + copy.chunkCollisionPayloadKeysByRowIndex.putAll(chunkCollisionPayloadKeysByRowIndex); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java index 5242df6f..987f8dd2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java @@ -32,6 +32,7 @@ public final class TargetBindingSystem extends TickingSystem private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class), + new SystemDependency<>(Order.AFTER, TerrainVoxelStitchingSystem.class), new SystemDependency<>(Order.AFTER, BodyCommandApplicationSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java index d89e2419..44b5d7ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java @@ -392,7 +392,7 @@ private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrain } @Nonnull - private static UUID terrainBodyUuid(@Nonnull UUID spaceUuid, + static UUID terrainBodyUuid(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, @Nonnull PartKind partKind, int partIndex) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainVoxelStitchingSystem.java new file mode 100644 index 00000000..9a43e040 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainVoxelStitchingSystem.java @@ -0,0 +1,188 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Applies native voxel terrain adjacency hints for generated PhysicsChunk body rows. + */ +public final class TerrainVoxelStitchingSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, BodyBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + if (restore.isFailed()) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + PhysicsTerrainPayloadResource payloads = store.getResource( + PhysicsTerrainPayloadResource.getResourceType()); + Set stitchedPairs = new ObjectOpenHashSet<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> stitchChunk(runtime, identity, payloads, restore, stitchedPairs, chunk); + store.forEachChunk(systemIndex, collector); + } + + private static void stitchChunk(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set stitchedPairs, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + ChunkCollisionSourceComponent source = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()); + if (source == null || source.getPartKind() != PartKind.VOXEL_TERRAIN) { + continue; + } + Ref bodyRef = chunk.getReferenceTo(index); + if (runtime.isChunkCollisionPayloadBound(bodyRef, source.getPayloadResourceKey())) { + continue; + } + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body == null) { + continue; + } + stitchBody(runtime, + identity, + payloads, + restore, + stitchedPairs, + bodyRef, + body, + source); + } + } + + private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Set stitchedPairs, + @Nonnull Ref bodyRef, + @Nonnull BodyComponent body, + @Nonnull ChunkCollisionSourceComponent source) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyRef); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyRef); + if (bodyHandle == null || spaceHandle == null) { + return; + } + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (backendRuntime == null) { + restore.recordSoftSkip("Voxel terrain backend runtime is missing: " + + source.getSourceKey()); + return; + } + TerrainColliderPayload payload = payloads.get(source.getPayloadResourceKey()); + if (payload == null) { + restore.recordSoftSkip("Voxel terrain payload is missing: " + source.getSourceKey()); + return; + } + for (TerrainColliderPayload.TerrainNeighbor neighbor : payload.neighbors()) { + stitchNeighbor(runtime, + identity, + backendRuntime, + spaceHandle, + body.getSpaceUuid(), + bodyHandle, + neighbor, + stitchedPairs); + } + runtime.markChunkCollisionPayloadBound(bodyRef, source.getPayloadResourceKey()); + } + + private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull UUID spaceUuid, + @Nonnull BackendBodyHandle bodyHandle, + @Nonnull TerrainColliderPayload.TerrainNeighbor neighbor, + @Nonnull Set stitchedPairs) { + Ref neighborRef = neighborRef(identity, spaceUuid, neighbor.sourceKey()); + if (neighborRef == null) { + return; + } + BackendBodyHandle neighborBody = runtime.getBodyHandle(neighborRef); + BackendSpaceHandle neighborSpace = runtime.getBodySpaceHandle(neighborRef); + if (neighborBody == null + || neighborSpace == null + || neighborSpace.value() != spaceHandle.value()) { + return; + } + BodyPair pair = BodyPair.of(bodyHandle.value(), neighborBody.value()); + if (!stitchedPairs.add(pair)) { + return; + } + backendRuntime.combineVoxelTerrains(spaceHandle.value(), + bodyHandle.value(), + neighborBody.value(), + neighbor.shiftX(), + neighbor.shiftY(), + neighbor.shiftZ()); + } + + @Nullable + private static Ref neighborRef(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID spaceUuid, + @Nonnull String sourceKey) { + UUID neighborUuid = TerrainMutationDrainSystem.terrainBodyUuid(spaceUuid, + sourceKey, + PartKind.VOXEL_TERRAIN, + 0); + return PhysicsStoreSystemSupport.refForUuid(identity, neighborUuid); + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.uuidQuery(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + private record BodyPair(long first, long second) { + + private static BodyPair of(long first, long second) { + return first <= second ? new BodyPair(first, second) : new BodyPair(second, first); + } + } +} From 69e931966c98f9256f0b6ebc8552968bd72c7285 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:36:23 +0200 Subject: [PATCH 393/534] refactor(core): stop persisting terrain collider rows Signed-off-by: Blovien --- .../systems/PersistenceCaptureSystem.java | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index 980e11f1..fb668fe3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -34,7 +34,6 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; @@ -110,8 +109,6 @@ private static final class Capture { private final List bodyRows = new ArrayList<>(); @Nonnull private final List jointRows = new ArrayList<>(); - @Nonnull - private final List terrainRows = new ArrayList<>(); private Capture(@Nonnull Map snapshotsByBodyUuid) { this.snapshotsByBodyUuid = snapshotsByBodyUuid; @@ -157,11 +154,6 @@ private void collectRow(@Nonnull UUID uuid, if (joint != null) { jointRows.add(new JointRow(uuid, joint)); } - TerrainColliderComponent terrain = chunk.getComponent(index, - TerrainColliderComponent.getComponentType()); - if (terrain != null) { - terrainRows.add(new TerrainColliderRow(uuid, terrain)); - } } private void writeTo(@Nonnull PersistentPhysicsStoreResource persistent) { @@ -173,7 +165,7 @@ private void writeTo(@Nonnull PersistentPhysicsStoreResource persistent) { persistent.setShapes(shapeDtos(bodyUuids)); persistent.setMaterials(materialDtos(bodyUuids)); persistent.setJoints(jointDtos(bodyUuids)); - persistent.setTerrainColliders(terrainDtos()); + persistent.setTerrainColliders(new PersistentTerrainColliderDto[0]); } @Nonnull @@ -365,21 +357,6 @@ private PersistentJointDto[] jointDtos(@Nonnull Set bodyUuids) { .toArray(PersistentJointDto[]::new); } - @Nonnull - private PersistentTerrainColliderDto[] terrainDtos() { - return terrainRows.stream() - .filter(row -> row.terrain().isRetained()) - .map(row -> new PersistentTerrainColliderDto(row.uuid(), - row.terrain().getSpaceUuid(), - row.terrain().getSourceKey(), - row.terrain().getChunkX(), - row.terrain().getSectionY(), - row.terrain().getChunkZ(), - row.terrain().getPayloadResourceKey(), - row.terrain().isRetained())) - .sorted(Comparator.comparing(PersistentTerrainColliderDto::getTerrainColliderUuid)) - .toArray(PersistentTerrainColliderDto[]::new); - } } private record SpaceRow(@Nonnull UUID uuid, @@ -408,8 +385,4 @@ private boolean hasAggregateCollider() { private record JointRow(@Nonnull UUID uuid, @Nonnull JointComponent joint) { } - - private record TerrainColliderRow(@Nonnull UUID uuid, - @Nonnull TerrainColliderComponent terrain) { - } } From 1b58020a45a75f6fe7ce73cd50805b5b0dd05f87 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:39:34 +0200 Subject: [PATCH 394/534] refactor(core): include terrain body rows in debug queries Signed-off-by: Blovien --- .../debug/PhysicsStoreDebugQueries.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 49055602..3c6844db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -18,11 +18,15 @@ import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.BiConsumer; @@ -223,6 +227,7 @@ private static List physicsChunkSections( PhysicsTerrainPayloadResource.getResourceType()); double maxDistanceSquared = viewRadius * viewRadius; List visible = new ArrayList<>(); + Set seenSources = new ObjectOpenHashSet<>(); BiConsumer, CommandBuffer> collector = (chunk, _) -> collectPhysicsChunkTerrainChunk(chunk, payloads, @@ -233,8 +238,22 @@ private static List physicsChunkSections( viewerY, viewerZ, maxDistanceSquared, + seenSources, visible); store.forEachChunk(TerrainColliderComponent.getComponentType(), collector); + BiConsumer, CommandBuffer> sourceCollector = + (chunk, _) -> collectPhysicsChunkSourceChunk(chunk, + payloads, + spaceContext, + spaceRef, + spaceUuid, + viewerX, + viewerY, + viewerZ, + maxDistanceSquared, + seenSources, + visible); + store.forEachChunk(ChunkCollisionSourceComponent.getComponentType(), sourceCollector); return List.copyOf(visible); } @@ -247,6 +266,7 @@ private static void collectPhysicsChunkTerrainChunk(@Nonnull ArchetypeChunk seenSources, @Nonnull List visible) { for (int index = 0; index < chunk.size(); index++) { TerrainColliderComponent terrain = chunk.getComponent(index, @@ -263,10 +283,46 @@ private static void collectPhysicsChunkTerrainChunk(@Nonnull ArchetypeChunk chunk, + @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull SpaceContext spaceContext, + @Nullable Ref spaceRef, + @Nonnull UUID spaceUuid, + double viewerX, + double viewerY, + double viewerZ, + double maxDistanceSquared, + @Nonnull Set seenSources, + @Nonnull List visible) { + for (int index = 0; index < chunk.size(); index++) { + ChunkCollisionSourceComponent source = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()); + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (source == null || body == null || !matchesSpace(body, spaceRef, spaceUuid)) { + continue; + } + if (distanceSquaredToSection(source, viewerX, viewerY, viewerZ) + > maxDistanceSquared) { + continue; + } + TerrainColliderPayload payload = payloads.get(source.getPayloadResourceKey()); + if (payload == null || payload.isEmpty()) { + continue; + } + if (!seenSources.add(sourceKey(body.getSpaceUuid(), source.getSourceKey()))) { + continue; + } + visible.add(toPhysicsChunkSectionView(source, payload, spaceContext)); + } + } + @Nonnull private static PhysicsChunkDebugSectionView toPhysicsChunkSectionView( @Nonnull TerrainColliderComponent terrain, @@ -284,6 +340,23 @@ private static PhysicsChunkDebugSectionView toPhysicsChunkSectionView( boxes(payload.detailBoxes())); } + @Nonnull + private static PhysicsChunkDebugSectionView toPhysicsChunkSectionView( + @Nonnull ChunkCollisionSourceComponent source, + @Nonnull TerrainColliderPayload payload, + @Nonnull SpaceContext spaceContext) { + boolean voxelTerrain = payload.nativeVoxelTerrainEnabled() + && payload.hasFullCubeVoxels() + && spaceContext.backendRuntime() + .supportsVoxelTerrain(spaceContext.spaceHandle().value()); + return new PhysicsChunkDebugSectionView(source.getChunkX(), + source.getSectionY(), + source.getChunkZ(), + voxelTerrain, + boxes(payload.mergedFullCubeBoxes()), + boxes(payload.detailBoxes())); + } + @Nonnull private static List boxes( @Nonnull List payloadBoxes) { @@ -396,6 +469,16 @@ private static boolean matchesSpace(@Nonnull TerrainColliderComponent terrain, return spaceUuid.equals(terrain.getSpaceUuid()); } + private static boolean matchesSpace(@Nonnull BodyComponent body, + @Nullable Ref spaceRef, + @Nonnull UUID spaceUuid) { + Ref bodySpaceRef = body.getSpaceRef(); + if (bodySpaceRef != null && spaceRef != null) { + return sameRef(bodySpaceRef, spaceRef); + } + return spaceUuid.equals(body.getSpaceUuid()); + } + @Nullable private static PhysicsBodySnapshot bodySnapshot( @Nonnull PhysicsSnapshotResource snapshots, @@ -504,6 +587,29 @@ private static double distanceSquaredToSection(@Nonnull TerrainColliderComponent minZ + ChunkUtil.SIZE); } + private static double distanceSquaredToSection(@Nonnull ChunkCollisionSourceComponent source, + double viewerX, + double viewerY, + double viewerZ) { + double minX = source.getChunkX() << ChunkUtil.BITS; + double minY = source.getSectionY() << ChunkUtil.BITS; + double minZ = source.getChunkZ() << ChunkUtil.BITS; + return distanceSquaredToBounds(viewerX, + viewerY, + viewerZ, + minX, + minY, + minZ, + minX + ChunkUtil.SIZE, + minY + ChunkUtil.SIZE, + minZ + ChunkUtil.SIZE); + } + + @Nonnull + private static String sourceKey(@Nonnull UUID spaceUuid, @Nonnull String sourceKey) { + return spaceUuid + "|" + sourceKey; + } + private static double distanceSquaredToBounds(double viewerX, double viewerY, double viewerZ, From d1a5da282fc6b0b1b14db1a66462efb2316df019 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:47:58 +0200 Subject: [PATCH 395/534] refactor(core): remove terrain collider rows Signed-off-by: Blovien --- .../PersistentPhysicsStorePreflight.java | 21 -- .../PersistentPhysicsStoreResource.java | 32 -- .../PersistentTerrainColliderDto.java | 136 ------- .../PhysicsStoreTopologyMutations.java | 62 +--- .../PhysicsComponentTypeRegistry.java | 13 - .../PhysicsStoreRegistration.java | 2 - .../resources/PhysicsRuntimeResource.java | 259 -------------- .../PhysicsTerrainPayloadResource.java | 2 +- .../systems/BodyCommandApplicationSystem.java | 3 +- .../CompletedStepPublicationSystem.java | 18 +- .../systems/PersistenceCaptureSystem.java | 2 - .../systems/PersistenceHydrationSystem.java | 19 - .../internal/systems/TargetBindingSystem.java | 1 - .../systems/TerrainColliderBindingSystem.java | 337 ------------------ .../systems/TerrainMutationDrainSystem.java | 55 +-- .../debug/PhysicsStoreDebugQueries.java | 107 +----- .../components/PhysicsComponentTypes.java | 6 - .../components/TerrainColliderComponent.java | 151 -------- .../plugin/physicsstore/PhysicsEntities.java | 22 -- 19 files changed, 10 insertions(+), 1238 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentTerrainColliderDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index 4ca4f804..8808d67c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -38,7 +38,6 @@ public static Result validate(@Nonnull PersistentPhysicsStoreResource resource) errors); validateBodyColliderRefs(resource.getBodies(), colliders, errors); validateJoints(resource.getJoints(), spaces, bodies, errors); - validateTerrain(resource.getTerrainColliders(), spaces, errors); return new Result(errors.isEmpty(), errors); } @@ -234,26 +233,6 @@ private static void validateJoints(@Nonnull PersistentJointDto[] joints, } } - private static void validateTerrain(@Nonnull PersistentTerrainColliderDto[] terrainColliders, - @Nonnull Set spaces, - @Nonnull List errors) { - Set seen = new HashSet<>(); - for (PersistentTerrainColliderDto terrain : terrainColliders) { - UUID uuid = terrain.getTerrainColliderUuid(); - requireUuid("terrain collider", uuid, errors); - if (!seen.add(uuid)) { - errors.add("Duplicate PhysicsStore terrain collider UUID " + uuid); - } - if (!spaces.contains(terrain.getSpaceUuid())) { - errors.add("Terrain collider " + uuid + " references missing space " - + terrain.getSpaceUuid()); - } - if (terrain.getSourceKey().isBlank()) { - errors.add("Terrain collider " + uuid + " has blank source key"); - } - } - } - private static void requireUuid(@Nonnull String kind, @Nonnull UUID uuid, @Nonnull List errors) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java index ecdea439..3ab9f9b0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java @@ -24,8 +24,6 @@ public final class PersistentPhysicsStoreResource implements Resource CODEC = @@ -85,15 +83,6 @@ public final class PersistentPhysicsStoreResource implements Resource("TerrainColliders", - new ArrayCodec<>(PersistentTerrainColliderDto.CODEC, - PersistentTerrainColliderDto[]::new), - false), - (resource, value) -> resource.terrainColliders = copyTerrainColliders(value), - PersistentPhysicsStoreResource::getTerrainColliders) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() .build(); private int schemaVersion = CURRENT_SCHEMA_VERSION; @@ -109,8 +98,6 @@ public final class PersistentPhysicsStoreResource implements Resource CODEC = - BuilderCodec.builder(PersistentTerrainColliderDto.class, PersistentTerrainColliderDto::new) - .append(new KeyedCodec<>("TerrainColliderUuid", Codec.UUID_BINARY), - (dto, value) -> dto.terrainColliderUuid = value, - PersistentTerrainColliderDto::getTerrainColliderUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), - (dto, value) -> dto.spaceUuid = value, - PersistentTerrainColliderDto::getSpaceUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("SourceKey", Codec.STRING, false), - (dto, value) -> dto.sourceKey = value != null ? value : "", - PersistentTerrainColliderDto::getSourceKey) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("ChunkX", Codec.INTEGER, false), - (dto, value) -> dto.chunkX = value != null ? value : 0, - PersistentTerrainColliderDto::getChunkX) - .add() - .append(new KeyedCodec<>("SectionY", Codec.INTEGER, false), - (dto, value) -> dto.sectionY = value != null ? value : 0, - PersistentTerrainColliderDto::getSectionY) - .add() - .append(new KeyedCodec<>("ChunkZ", Codec.INTEGER, false), - (dto, value) -> dto.chunkZ = value != null ? value : 0, - PersistentTerrainColliderDto::getChunkZ) - .add() - .append(new KeyedCodec<>("PayloadResourceKey", Codec.STRING, false), - (dto, value) -> dto.payloadResourceKey = value != null ? value : "", - PersistentTerrainColliderDto::getPayloadResourceKey) - .add() - .append(new KeyedCodec<>("Retained", Codec.BOOLEAN, false), - (dto, value) -> dto.retained = value == null || value, - PersistentTerrainColliderDto::isRetained) - .add() - .build(); - - @Nonnull - private UUID terrainColliderUuid = new UUID(0L, 0L); - @Nonnull - private UUID spaceUuid = new UUID(0L, 0L); - @Nonnull - private String sourceKey = ""; - private int chunkX; - private int sectionY; - private int chunkZ; - @Nonnull - private String payloadResourceKey = ""; - private boolean retained = true; - - public PersistentTerrainColliderDto() { - } - - public PersistentTerrainColliderDto(@Nonnull UUID terrainColliderUuid, - @Nonnull UUID spaceUuid, - @Nonnull String sourceKey, - int chunkX, - int sectionY, - int chunkZ, - @Nonnull String payloadResourceKey, - boolean retained) { - this.terrainColliderUuid = Objects.requireNonNull(terrainColliderUuid, - "terrainColliderUuid"); - this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); - this.chunkX = chunkX; - this.sectionY = sectionY; - this.chunkZ = chunkZ; - this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, - "payloadResourceKey"); - this.retained = retained; - } - - @Nonnull - public UUID getTerrainColliderUuid() { - return terrainColliderUuid; - } - - @Nonnull - public UUID getSpaceUuid() { - return spaceUuid; - } - - @Nonnull - public String getSourceKey() { - return sourceKey; - } - - public int getChunkX() { - return chunkX; - } - - public int getSectionY() { - return sectionY; - } - - public int getChunkZ() { - return chunkZ; - } - - @Nonnull - public String getPayloadResourceKey() { - return payloadResourceKey; - } - - public boolean isRetained() { - return retained; - } - - @Nonnull - public PersistentTerrainColliderDto copy() { - return new PersistentTerrainColliderDto(terrainColliderUuid, - spaceUuid, - sourceKey, - chunkX, - sectionY, - chunkZ, - payloadResourceKey, - retained); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 9804dc55..17951567 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -22,10 +22,8 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; -import it.unimi.dsi.fastutil.longs.LongArrayList; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -103,10 +101,7 @@ public static int clearTerrainForSpace(@Nonnull Store store, Ref spaceRef = identity.getByUuid(spaceUuid); List removals = collectTerrainRows(store, spaceUuid, spaceRef); for (RowRemoval removal : removals) { - if (removal.kind() == RowKind.TERRAIN) { - removedBodies += removeRuntimeTerrain(runtime, removal); - } else if (removal.kind() == RowKind.BODY - && removeRuntimeBody(runtime, identity, removal)) { + if (removeRuntimeBody(runtime, identity, removal)) { removedBodies++; } } @@ -124,11 +119,6 @@ private static void removeRuntimeRows(@Nonnull PhysicsRuntimeResource runtime, removeRuntimeJoint(runtime, identity, removal); } } - for (RowRemoval removal : removals) { - if (removal.kind() == RowKind.TERRAIN) { - removeRuntimeTerrain(runtime, removal); - } - } for (RowRemoval removal : removals) { if (removal.kind() == RowKind.BODY) { removeRuntimeBody(runtime, identity, removal); @@ -154,23 +144,6 @@ private static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtim return true; } - private static int removeRuntimeTerrain(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull RowRemoval removal) { - BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(removal.ref()); - LongArrayList bodyHandles = new LongArrayList(); - runtime.forEachTerrainBodyHandle(removal.ref(), bodyId -> bodyHandles.add(bodyId)); - if (spaceHandle != null) { - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); - if (backendRuntime != null) { - for (int index = 0; index < bodyHandles.size(); index++) { - backendRuntime.removeBody(spaceHandle.value(), bodyHandles.getLong(index)); - } - } - } - runtime.removeTerrainHandles(removal.ref(), removal.rowUuid()); - return bodyHandles.size(); - } - private static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull RowRemoval removal) { @@ -219,15 +192,6 @@ private static List collectRows(@Nonnull Store store, removals.add(new RowRemoval(ref, rowUuid, RowKind.JOINT, null)); return; } - TerrainColliderComponent terrain = chunk.getComponent(index, - TerrainColliderComponent.getComponentType()); - if (matchesTerrain(terrain, spaceUuid, spaceRef, bodyUuid)) { - removals.add(new RowRemoval(ref, - rowUuid, - RowKind.TERRAIN, - terrain.getPayloadResourceKey())); - return; - } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); ChunkCollisionSourceComponent source = chunk.getComponent(index, ChunkCollisionSourceComponent.getComponentType()); @@ -248,22 +212,10 @@ private static List collectTerrainRows(@Nonnull Store ComponentType uuidType = UuidComponent.getComponentType(); ConcurrentLinkedQueue removals = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(uuidType, (index, chunk, _) -> { - TerrainColliderComponent terrain = chunk.getComponent(index, - TerrainColliderComponent.getComponentType()); UuidComponent uuid = chunk.getComponent(index, uuidType); if (uuid == null) { return; } - if (terrain != null && matchesSpace(terrain.getSpaceRef(), - terrain.getSpaceUuid(), - spaceRef, - spaceUuid)) { - removals.add(new RowRemoval(chunk.getReferenceTo(index), - uuid.getUuid(), - RowKind.TERRAIN, - terrain.getPayloadResourceKey())); - return; - } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); ChunkCollisionSourceComponent source = chunk.getComponent(index, ChunkCollisionSourceComponent.getComponentType()); @@ -296,15 +248,6 @@ private static boolean matchesJoint(@Nullable JointComponent joint, || matchesEndpoint(joint.getBodyBRef(), joint.getBodyBUuid(), bodyRef, bodyUuid); } - private static boolean matchesTerrain(@Nullable TerrainColliderComponent terrain, - @Nullable UUID spaceUuid, - @Nullable Ref spaceRef, - @Nullable UUID bodyUuid) { - return bodyUuid == null - && terrain != null - && matchesSpace(terrain.getSpaceRef(), terrain.getSpaceUuid(), spaceRef, spaceUuid); - } - private static boolean matchesBody(@Nullable BodyComponent body, @Nonnull UUID rowUuid, @Nonnull Ref rowRef, @@ -387,8 +330,7 @@ private static void clearCopiedBodyState(@Nonnull Store store) { private enum RowKind { BODY, - JOINT, - TERRAIN + JOINT } private record RowRemoval(@Nonnull Ref ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java index 8985740e..7b103eff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; @@ -39,8 +38,6 @@ public final class PhysicsComponentTypeRegistry { @Nullable private static ComponentType bodyCommandComponentType; @Nullable - private static ComponentType terrainColliderComponentType; - @Nullable private static ComponentType chunkCollisionSourceComponentType; @Nullable private static ComponentType physicsChunkTerrainComponentType; @@ -91,10 +88,6 @@ public static void registerComponentTypes( BodyCommandComponent.class, "BodyCommand", BodyCommandComponent.CODEC); - terrainColliderComponentType = registry.registerComponent( - TerrainColliderComponent.class, - "TerrainCollider", - TerrainColliderComponent.CODEC); chunkCollisionSourceComponentType = registry.registerComponent( ChunkCollisionSourceComponent.class, "ChunkCollisionSource", @@ -173,12 +166,6 @@ public static ComponentType bodyCommandCompo return bodyCommandComponentType; } - @Nonnull - public static ComponentType - terrainColliderComponentType() { - return terrainColliderComponentType; - } - @Nonnull public static ComponentType chunkCollisionSourceComponentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 4842ef2b..bf191a26 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -38,7 +38,6 @@ import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.TerrainColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.systems.TerrainVoxelStitchingSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; @@ -82,7 +81,6 @@ public static void register(@Nonnull ComponentRegistryProxy regist registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); - registry.registerSystem(new TerrainColliderBindingSystem()); registry.registerSystem(new TerrainVoxelStitchingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index bc712ae1..18cfd3d5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -83,33 +83,6 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap jointSpaceHandlesByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map terrainBodyHandlesByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map terrainVoxelBodyHandlesByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map terrainSpaceHandlesByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map terrainPayloadKeysByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map> terrainRefsByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectOpenHashMap terrainBodyHandlesByRowIndex = - new Int2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectOpenHashMap terrainVoxelBodyHandlesByRowIndex = - new Int2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectOpenHashMap terrainSpaceHandlesByRowIndex = - new Int2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectOpenHashMap terrainPayloadKeysByRowIndex = - new Int2ObjectOpenHashMap<>(); - @Nonnull private final Int2ObjectOpenHashMap chunkCollisionPayloadKeysByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -204,7 +177,6 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { } }); } - removeTerrainHandlesForSpace(removed); markRegistrationTopologyChanged(); } } @@ -412,66 +384,6 @@ public List> jointRefsForSpaceHandle( return jointRefs; } - public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle handle, - boolean voxelTerrainBody) { - putTerrainBodyHandle(terrainUuid, null, spaceHandle, handle, voxelTerrainBody); - } - - public void putTerrainBodyHandle(@Nonnull UUID terrainUuid, - @Nullable Ref terrainRef, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle handle, - boolean voxelTerrainBody) { - bindTerrainRef(terrainUuid, terrainRef); - terrainSpaceHandlesByUuid.put(terrainUuid, spaceHandle); - terrainBodyHandlesByUuid.computeIfAbsent(terrainUuid, _ -> new LongArrayList()) - .add(handle.value()); - if (voxelTerrainBody) { - terrainVoxelBodyHandlesByUuid.put(terrainUuid, handle); - } - if (terrainRef != null) { - int rowIndex = terrainRef.getIndex(); - terrainSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); - terrainBodyHandlesByRowIndex.computeIfAbsent(rowIndex, _ -> new LongArrayList()) - .add(handle.value()); - if (voxelTerrainBody) { - terrainVoxelBodyHandlesByRowIndex.put(rowIndex, handle); - } - } - markRegistrationTopologyChanged(); - } - - public void putTerrainBodyHandle(@Nonnull Ref terrainRef, - @Nonnull UUID terrainUuid, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle handle, - boolean voxelTerrainBody) { - putTerrainBodyHandle(terrainUuid, terrainRef, spaceHandle, handle, voxelTerrainBody); - } - - public void markTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String payloadKey) { - terrainPayloadKeysByUuid.put(terrainUuid, payloadKey); - } - - public void markTerrainPayloadBound(@Nonnull Ref terrainRef, - @Nonnull UUID terrainUuid, - @Nonnull String payloadKey) { - bindTerrainRef(terrainUuid, terrainRef); - terrainPayloadKeysByUuid.put(terrainUuid, payloadKey); - terrainPayloadKeysByRowIndex.put(terrainRef.getIndex(), payloadKey); - } - - public boolean isTerrainPayloadBound(@Nonnull UUID terrainUuid, @Nonnull String payloadKey) { - return payloadKey.equals(terrainPayloadKeysByUuid.get(terrainUuid)); - } - - public boolean isTerrainPayloadBound(@Nonnull Ref terrainRef, - @Nonnull String payloadKey) { - return payloadKey.equals(terrainPayloadKeysByRowIndex.get(terrainRef.getIndex())); - } - public void markChunkCollisionPayloadBound(@Nonnull Ref bodyRef, @Nonnull String payloadKey) { chunkCollisionPayloadKeysByRowIndex.put(bodyRef.getIndex(), payloadKey); @@ -486,78 +398,6 @@ public void clearChunkCollisionPayloadBound(@Nonnull Ref bodyRef) chunkCollisionPayloadKeysByRowIndex.remove(bodyRef.getIndex()); } - public boolean hasTerrainBodyHandles(@Nonnull Ref terrainRef) { - LongList bodyHandles = terrainBodyHandlesByRowIndex.get(terrainRef.getIndex()); - return bodyHandles != null && !bodyHandles.isEmpty(); - } - - @Nullable - public BackendSpaceHandle getTerrainSpaceHandle(@Nonnull Ref terrainRef) { - return terrainSpaceHandlesByRowIndex.get(terrainRef.getIndex()); - } - - @Nullable - public BackendBodyHandle getTerrainVoxelBodyHandle(@Nonnull Ref terrainRef) { - return terrainVoxelBodyHandlesByRowIndex.get(terrainRef.getIndex()); - } - - public void forEachTerrainBodyHandle(@Nonnull Ref terrainRef, - @Nonnull LongConsumer consumer) { - LongList bodyHandles = terrainBodyHandlesByRowIndex.get(terrainRef.getIndex()); - if (bodyHandles == null) { - return; - } - bodyHandles.forEach(consumer); - } - - public void removeTerrainHandles(@Nonnull UUID terrainUuid) { - boolean changed = false; - LongList bodyHandles = terrainBodyHandlesByUuid.remove(terrainUuid); - if (bodyHandles != null) { - bodyHandles.forEach(bodyHitMetadataByHandle::remove); - changed = true; - } - changed |= terrainVoxelBodyHandlesByUuid.remove(terrainUuid) != null; - changed |= terrainSpaceHandlesByUuid.remove(terrainUuid) != null; - changed |= terrainPayloadKeysByUuid.remove(terrainUuid) != null; - Ref terrainRef = terrainRefsByUuid.remove(terrainUuid); - if (terrainRef != null) { - changed |= removeTerrainRefMaps(terrainRef); - } - if (changed) { - markRegistrationTopologyChanged(); - } - } - - public void removeTerrainHandles(@Nonnull UUID terrainUuid, - @Nonnull Ref terrainRef) { - removeTerrainHandles(terrainUuid); - if (removeTerrainRefMaps(terrainRef)) { - markRegistrationTopologyChanged(); - } - } - - public void removeTerrainHandles(@Nonnull Ref terrainRef, - @Nonnull UUID terrainUuid) { - removeTerrainHandles(terrainUuid, terrainRef); - } - - @Nonnull - public List> terrainRefsForSpaceHandle( - @Nonnull BackendSpaceHandle spaceHandle) { - List> terrainRefs = new ArrayList<>(); - int targetSpaceHandle = spaceHandle.value(); - terrainSpaceHandlesByRowIndex.forEach((rowIndex, handle) -> { - if (handle.value() == targetSpaceHandle) { - Ref terrainRef = terrainRefForRowIndex((int) rowIndex); - if (terrainRef != null) { - terrainRefs.add(terrainRef); - } - } - }); - return terrainRefs; - } - public void forEachRuntimeSpaceBinding(@Nonnull RuntimeSpaceBindingConsumer consumer) { spaceRefsByUuid.values().forEach(spaceRef -> { int rowIndex = spaceRef.getIndex(); @@ -602,15 +442,6 @@ public void clear() { jointRefsByUuid.clear(); jointHandlesByRowIndex.clear(); jointSpaceHandlesByRowIndex.clear(); - terrainBodyHandlesByUuid.clear(); - terrainVoxelBodyHandlesByUuid.clear(); - terrainSpaceHandlesByUuid.clear(); - terrainPayloadKeysByUuid.clear(); - terrainRefsByUuid.clear(); - terrainBodyHandlesByRowIndex.clear(); - terrainVoxelBodyHandlesByRowIndex.clear(); - terrainSpaceHandlesByRowIndex.clear(); - terrainPayloadKeysByRowIndex.clear(); chunkCollisionPayloadKeysByRowIndex.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); @@ -662,22 +493,6 @@ public void destroyBackendBindings() { failure = appendShutdownFailure(failure, exception); } } - for (Map.Entry entry - : new ArrayList<>(terrainBodyHandlesByUuid.entrySet())) { - BackendSpaceHandle spaceHandle = terrainSpaceHandlesByUuid.get(entry.getKey()); - PhysicsBackendRuntime runtime = runtimeForSpaceHandle(spaceHandle); - if (spaceHandle == null || runtime == null) { - continue; - } - LongList bodyHandles = new LongArrayList(entry.getValue()); - for (int index = 0; index < bodyHandles.size(); index++) { - try { - runtime.removeBody(spaceHandle.value(), bodyHandles.getLong(index)); - } catch (RuntimeException exception) { - failure = appendShutdownFailure(failure, exception); - } - } - } for (Map.Entry entry : new ArrayList<>(bodyHandlesByUuid.entrySet())) { BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.get(entry.getKey()); @@ -756,18 +571,6 @@ public PhysicsRuntimeResource clone() { copy.jointRefsByUuid.putAll(jointRefsByUuid); copy.jointHandlesByRowIndex.putAll(jointHandlesByRowIndex); copy.jointSpaceHandlesByRowIndex.putAll(jointSpaceHandlesByRowIndex); - terrainBodyHandlesByUuid.forEach((terrainUuid, bodyHandles) -> - copy.terrainBodyHandlesByUuid.put(terrainUuid, new LongArrayList(bodyHandles))); - copy.terrainVoxelBodyHandlesByUuid.putAll(terrainVoxelBodyHandlesByUuid); - copy.terrainSpaceHandlesByUuid.putAll(terrainSpaceHandlesByUuid); - copy.terrainPayloadKeysByUuid.putAll(terrainPayloadKeysByUuid); - copy.terrainRefsByUuid.putAll(terrainRefsByUuid); - terrainBodyHandlesByRowIndex.forEach((rowIndex, bodyHandles) -> - copy.terrainBodyHandlesByRowIndex.put((int) rowIndex, - new LongArrayList(bodyHandles))); - copy.terrainVoxelBodyHandlesByRowIndex.putAll(terrainVoxelBodyHandlesByRowIndex); - copy.terrainSpaceHandlesByRowIndex.putAll(terrainSpaceHandlesByRowIndex); - copy.terrainPayloadKeysByRowIndex.putAll(terrainPayloadKeysByRowIndex); copy.chunkCollisionPayloadKeysByRowIndex.putAll(chunkCollisionPayloadKeysByRowIndex); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); @@ -917,52 +720,6 @@ private void markRegistrationTopologyChanged() { registrationTopologyGeneration++; } - private void removeTerrainHandlesForSpace(@Nonnull BackendSpaceHandle spaceHandle) { - boolean removedAny = terrainSpaceHandlesByUuid.entrySet().removeIf(entry -> { - if (entry.getValue().value() != spaceHandle.value()) { - return false; - } - UUID terrainUuid = entry.getKey(); - LongList bodyHandles = terrainBodyHandlesByUuid.get(terrainUuid); - if (bodyHandles != null) { - bodyHandles.forEach(bodyHitMetadataByHandle::remove); - } - terrainBodyHandlesByUuid.remove(terrainUuid); - terrainVoxelBodyHandlesByUuid.remove(terrainUuid); - terrainPayloadKeysByUuid.remove(terrainUuid); - Ref terrainRef = terrainRefsByUuid.remove(terrainUuid); - if (terrainRef != null) { - removeTerrainRefMaps(terrainRef); - } - return true; - }); - if (removedAny) { - markRegistrationTopologyChanged(); - } - } - - private void bindTerrainRef(@Nonnull UUID terrainUuid, - @Nullable Ref terrainRef) { - if (terrainRef == null) { - return; - } - Ref previousRef = terrainRefsByUuid.put(terrainUuid, terrainRef); - if (previousRef != null && !sameRef(previousRef, terrainRef)) { - if (removeTerrainRefMaps(previousRef)) { - markRegistrationTopologyChanged(); - } - } - } - - private boolean removeTerrainRefMaps(@Nonnull Ref terrainRef) { - int rowIndex = terrainRef.getIndex(); - boolean changed = terrainBodyHandlesByRowIndex.remove(rowIndex) != null; - changed |= terrainVoxelBodyHandlesByRowIndex.remove(rowIndex) != null; - changed |= terrainSpaceHandlesByRowIndex.remove(rowIndex) != null; - changed |= terrainPayloadKeysByRowIndex.remove(rowIndex) != null; - return changed; - } - @Nullable private Ref jointRefForRowIndex(int rowIndex) { for (Ref jointRef : jointRefsByUuid.values()) { @@ -973,20 +730,4 @@ private Ref jointRefForRowIndex(int rowIndex) { return null; } - @Nullable - private Ref terrainRefForRowIndex(int rowIndex) { - for (Ref terrainRef : terrainRefsByUuid.values()) { - if (terrainRef.getIndex() == rowIndex) { - return terrainRef; - } - } - return null; - } - - private static boolean sameRef(@Nonnull Ref first, - @Nonnull Ref second) { - return first == second - || (first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java index dc936c91..258843ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java @@ -10,7 +10,7 @@ import javax.annotation.Nullable; /** - * Runtime-only copied terrain payloads keyed by TerrainColliderComponent payload keys. + * Runtime-only copied PhysicsChunk collision payloads keyed by ChunkCollisionSourceComponent payload keys. */ public final class PhysicsTerrainPayloadResource implements Resource { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index 7cbc263c..5dee0fd6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -35,8 +35,7 @@ public final class BodyCommandApplicationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, BodyBindingSystem.class), - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) + new SystemDependency<>(Order.AFTER, BodyBindingSystem.class) ); private static final Query QUERY = BodyCommandComponent.getComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index b3e2be83..9c516ebc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -22,11 +22,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.util.ArrayList; @@ -43,8 +40,7 @@ public final class CompletedStepPublicationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TargetBindingSystem.class), - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class) + new SystemDependency<>(Order.AFTER, TargetBindingSystem.class) ); @Override @@ -171,18 +167,6 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run body.getPersistenceMode()))); } } - TerrainColliderComponent terrain = - chunk.getComponent(index, TerrainColliderComponent.getComponentType()); - if (terrain != null && runtime.hasTerrainBodyHandles(rowRef)) { - SpaceId spaceId = compatibility.getSpaceId(terrain.getSpaceUuid()); - if (spaceId != null) { - registrations.add(new BodyRegistrationPublication(rowRef, - new PhysicsBodyRegistrationView(rowUuid, - spaceId, - PhysicsBodyKind.TERRAIN, - PhysicsBodyPersistenceMode.RUNTIME_ONLY))); - } - } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index fb668fe3..144796a6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -18,7 +18,6 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentTerrainColliderDto; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; @@ -165,7 +164,6 @@ private void writeTo(@Nonnull PersistentPhysicsStoreResource persistent) { persistent.setShapes(shapeDtos(bodyUuids)); persistent.setMaterials(materialDtos(bodyUuids)); persistent.setJoints(jointDtos(bodyUuids)); - persistent.setTerrainColliders(new PersistentTerrainColliderDto[0]); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 21be2835..9248d8e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -18,7 +18,6 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentTerrainColliderDto; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -33,7 +32,6 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; @@ -79,9 +77,6 @@ private static void hydrateRows(@Nonnull Store store, for (PersistentJointDto dto : persistent.getJoints()) { addJoint(store, dto); } - for (PersistentTerrainColliderDto dto : persistent.getTerrainColliders()) { - addTerrainCollider(store, dto); - } } private static void addSpace(@Nonnull Store store, @@ -238,20 +233,6 @@ private static void addJoint(@Nonnull Store store, add(store, holder); } - private static void addTerrainCollider(@Nonnull Store store, - @Nonnull PersistentTerrainColliderDto dto) { - Holder holder = row(store, dto.getTerrainColliderUuid()); - holder.addComponent(TerrainColliderComponent.getComponentType(), - new TerrainColliderComponent(dto.getSpaceUuid(), - dto.getSourceKey(), - dto.getChunkX(), - dto.getSectionY(), - dto.getChunkZ(), - dto.getPayloadResourceKey(), - dto.isRetained())); - add(store, holder); - } - @Nonnull private static TargetComponent inactiveTarget(@Nonnull PersistentBodyRuntimeStateDto dto) { TargetComponent target = new TargetComponent(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java index 987f8dd2..f5fae8cd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java @@ -31,7 +31,6 @@ public final class TargetBindingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TerrainColliderBindingSystem.class), new SystemDependency<>(Order.AFTER, TerrainVoxelStitchingSystem.class), new SystemDependency<>(Order.AFTER, BodyCommandApplicationSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java deleted file mode 100644 index 5ca7cf04..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java +++ /dev/null @@ -1,337 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems; - -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.QuerySystem; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.BoxPayload; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.TerrainNeighbor; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; -import java.util.Set; -import java.util.UUID; -import java.util.function.BiConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Binds retained terrain collider rows to backend terrain bodies. - */ -public final class TerrainColliderBindingSystem extends TickingSystem - implements QuerySystem { - - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, JointBindingSystem.class) - ); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsTerrainPayloadResource payloads = store.getResource( - PhysicsTerrainPayloadResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - PhysicsRestoreStatusResource restore = store.getResource( - PhysicsRestoreStatusResource.getResourceType()); - if (restore.isFailed()) { - return; - } - BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindChunk(runtime, payloads, identity, restore, chunk); - store.forEachChunk(systemIndex, collector); - } - - private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsTerrainPayloadResource payloads, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull ArchetypeChunk chunk) { - for (int index = 0; index < chunk.size(); index++) { - TerrainColliderComponent terrain = chunk.getComponent(index, - TerrainColliderComponent.getComponentType()); - if (terrain == null) { - continue; - } - UUID terrainUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (PhysicsStoreSystemSupport.isNil(terrainUuid)) { - continue; - } - Ref terrainRef = chunk.getReferenceTo(index); - if (!terrain.isRetained()) { - removeTerrain(runtime, terrainUuid, terrainRef); - continue; - } - if (runtime.isTerrainPayloadBound(terrainRef, terrain.getPayloadResourceKey())) { - continue; - } - TerrainColliderPayload payload = payloads.get(terrain.getPayloadResourceKey()); - if (payload == null || payload.isEmpty()) { - restore.recordSoftSkip("Terrain payload is missing: " + terrain.getSourceKey()); - continue; - } - bindTerrain(runtime, - identity, - restore, - terrainUuid, - terrainRef, - terrain, - payload); - } - } - - private static void bindTerrain(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull UUID terrainUuid, - @Nonnull Ref terrainRef, - @Nonnull TerrainColliderComponent terrain, - @Nonnull TerrainColliderPayload payload) { - Ref spaceRef = resolveSpaceRef(identity, terrain); - BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; - if (spaceHandle == null) { - restore.recordSoftSkip("Terrain references unbound space: " + terrain.getSourceKey()); - return; - } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceRef); - if (backendRuntime == null) { - restore.recordSoftSkip("Terrain references missing backend runtime: " - + terrain.getSourceKey()); - return; - } - if (runtime.hasTerrainBodyHandles(terrainRef)) { - removeTerrain(runtime, terrainUuid, terrainRef); - } - try { - boolean nativeVoxel = payload.nativeVoxelTerrainEnabled() - && payload.hasFullCubeVoxels() - && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); - if (nativeVoxel) { - addVoxelTerrain(runtime, - backendRuntime, - spaceHandle, - terrainUuid, - terrainRef, - terrain, - payload); - } else { - for (BoxPayload box : payload.mergedFullCubeBoxes()) { - addStaticBox(runtime, - backendRuntime, - spaceHandle, - terrainUuid, - terrainRef, - box, - payload); - } - } - for (BoxPayload box : payload.detailBoxes()) { - addStaticBox(runtime, - backendRuntime, - spaceHandle, - terrainUuid, - terrainRef, - box, - payload); - } - if (!runtime.hasTerrainBodyHandles(terrainRef)) { - restore.recordSoftSkip("Terrain payload produced no backend bodies: " - + terrain.getSourceKey()); - return; - } - runtime.markTerrainPayloadBound(terrainRef, - terrainUuid, - terrain.getPayloadResourceKey()); - stitchNeighbors(runtime, - identity, - backendRuntime, - spaceHandle, - terrainRef, - terrain, - payload); - } catch (RuntimeException exception) { - removeTerrain(runtime, terrainUuid, terrainRef); - restore.markFailed("PhysicsStore terrain " + terrain.getSourceKey() - + " failed backend binding: " + exception.getMessage()); - } - } - - private static void addVoxelTerrain(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsBackendRuntime backendRuntime, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull UUID terrainUuid, - @Nonnull Ref terrainRef, - @Nonnull TerrainColliderComponent terrain, - @Nonnull TerrainColliderPayload payload) { - long bodyId = backendRuntime.createVoxelTerrain(spaceHandle.value(), - payload.voxelSizeX(), - payload.voxelSizeY(), - payload.voxelSizeZ(), - payload.voxelCoordinates(), - terrain.getChunkX() << ChunkUtil.BITS, - terrain.getSectionY() << ChunkUtil.BITS, - terrain.getChunkZ() << ChunkUtil.BITS, - payload.friction(), - payload.restitution(), - payload.collisionGroup(), - payload.collisionMask()); - BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); - runtime.putTerrainBodyHandle(terrainRef, terrainUuid, spaceHandle, bodyHandle, true); - runtime.putBodyHitMetadata(bodyHandle, - terrainUuid, - terrainRef, - PhysicsBodyType.STATIC, - ShapeType.VOXELS); - } - - private static void addStaticBox(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsBackendRuntime backendRuntime, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull UUID terrainUuid, - @Nonnull Ref terrainRef, - @Nonnull BoxPayload box, - @Nonnull TerrainColliderPayload payload) { - if (box.halfX() <= 0.0 || box.halfY() <= 0.0 || box.halfZ() <= 0.0) { - return; - } - long bodyId = backendRuntime.createBody(spaceHandle.value(), - BackendRuntimeCodes.SHAPE_BOX, - (float) box.halfX(), - (float) box.halfY(), - (float) box.halfZ(), - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 0.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC), - (float) box.centerX(), - (float) box.centerY(), - (float) box.centerZ(), - 0.0f, - 0.0f, - 0.0f, - 1.0f); - backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, payload.friction()); - backendRuntime.setBodyRestitution(spaceHandle.value(), bodyId, payload.restitution()); - backendRuntime.setBodyCollisionFilter(spaceHandle.value(), - bodyId, - payload.collisionGroup(), - payload.collisionMask()); - BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); - runtime.putTerrainBodyHandle(terrainRef, - terrainUuid, - spaceHandle, - bodyHandle, - false); - runtime.putBodyHitMetadata(bodyHandle, - terrainUuid, - terrainRef, - PhysicsBodyType.STATIC, - ShapeType.BOX); - } - - private static void stitchNeighbors(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsBackendRuntime backendRuntime, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull Ref terrainRef, - @Nonnull TerrainColliderComponent terrain, - @Nonnull TerrainColliderPayload payload) { - BackendBodyHandle voxelBody = runtime.getTerrainVoxelBodyHandle(terrainRef); - if (voxelBody == null) { - return; - } - for (TerrainNeighbor neighbor : payload.neighbors()) { - UUID neighborUuid = TerrainColliderMutation.terrainColliderUuid(terrain.getSpaceUuid(), - neighbor.sourceKey()); - Ref neighborRef = PhysicsStoreSystemSupport.refForUuid(identity, - neighborUuid); - if (neighborRef == null) { - continue; - } - BackendBodyHandle neighborBody = runtime.getTerrainVoxelBodyHandle(neighborRef); - BackendSpaceHandle neighborSpace = runtime.getTerrainSpaceHandle(neighborRef); - if (neighborBody == null - || neighborSpace == null - || neighborSpace.value() != spaceHandle.value()) { - continue; - } - backendRuntime.combineVoxelTerrains(spaceHandle.value(), - voxelBody.value(), - neighborBody.value(), - neighbor.shiftX(), - neighbor.shiftY(), - neighbor.shiftZ()); - } - } - - private static void removeTerrain(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull UUID terrainUuid, - @Nonnull Ref terrainRef) { - BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(terrainRef); - if (spaceHandle == null) { - runtime.removeTerrainHandles(terrainRef, terrainUuid); - return; - } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); - if (backendRuntime != null) { - runtime.forEachTerrainBodyHandle(terrainRef, - bodyId -> backendRuntime.removeBody(spaceHandle.value(), bodyId)); - } - runtime.removeTerrainHandles(terrainRef, terrainUuid); - } - - @Nullable - private static Ref resolveSpaceRef(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull TerrainColliderComponent terrain) { - Ref spaceRef = PhysicsStoreSystemSupport.resolvedRef(identity, - terrain.getSpaceUuid(), - terrain.getSpaceRef()); - terrain.setSpaceRef(spaceRef); - return spaceRef; - } - - @Nullable - private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull Ref spaceRef) { - var backendId = runtime.getSpaceBackendId(spaceRef); - return backendId != null ? runtime.getRuntime(backendId) : null; - } - - @Nullable - private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull BackendSpaceHandle spaceHandle) { - return runtime.runtimeForSpaceHandle(spaceHandle); - } - - @Nonnull - @Override - public Query getQuery() { - return PhysicsStoreSystemSupport.uuidQuery(); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java index 44b5d7ff..3b4e2b31 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java @@ -36,12 +36,10 @@ import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import it.unimi.dsi.fastutil.longs.LongArrayList; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -287,14 +285,10 @@ private static void removeGeneratedRows(@Nonnull Store store, PhysicsBodyRegistrationResource registrations = store.getResource( PhysicsBodyRegistrationResource.getResourceType()); for (GeneratedRow row : rows) { - if (row.kind() == GeneratedRowKind.BODY) { - removeRuntimeBody(runtime, identity, row); - PhysicsControlRuntimeStates.clearControlled(row.ref()); - snapshots.removeBody(row.uuid()); - registrations.removeBody(row.uuid()); - } else { - removeRuntimeTerrain(runtime, row); - } + removeRuntimeBody(runtime, identity, row); + PhysicsControlRuntimeStates.clearControlled(row.ref()); + snapshots.removeBody(row.uuid()); + registrations.removeBody(row.uuid()); removePayload(terrainPayloads, row.payloadResourceKey()); identity.removeUuid(row.uuid(), row.ref()); if (row.ref().isValid()) { @@ -322,17 +316,7 @@ private static List collectGeneratedRows(@Nonnull Store(rows); @@ -345,14 +329,6 @@ private static boolean matchesSource(@Nonnull TerrainColliderMutation mutation, && mutation.sourceKey().equals(source.getSourceKey()); } - private static boolean matchesLegacyTerrain(@Nonnull TerrainColliderMutation mutation, - @Nonnull UUID rowUuid, - @Nonnull TerrainColliderComponent terrain) { - return mutation.terrainColliderUuid().equals(rowUuid) - || (mutation.spaceUuid().equals(terrain.getSpaceUuid()) - && mutation.sourceKey().equals(terrain.getSourceKey())); - } - private static void removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull GeneratedRow row) { @@ -368,22 +344,6 @@ private static void removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, runtime.removeBodyHandle(row.uuid(), row.ref()); } - private static void removeRuntimeTerrain(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull GeneratedRow row) { - BackendSpaceHandle spaceHandle = runtime.getTerrainSpaceHandle(row.ref()); - LongArrayList bodyHandles = new LongArrayList(); - runtime.forEachTerrainBodyHandle(row.ref(), bodyHandles::add); - if (spaceHandle != null) { - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); - if (backendRuntime != null) { - for (int index = 0; index < bodyHandles.size(); index++) { - backendRuntime.removeBody(spaceHandle.value(), bodyHandles.getLong(index)); - } - } - } - runtime.removeTerrainHandles(row.ref(), row.uuid()); - } - private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, @Nullable String payloadResourceKey) { if (payloadResourceKey != null && !payloadResourceKey.isBlank()) { @@ -406,19 +366,12 @@ public Set> getDependencies() { return DEPENDENCIES; } - private enum GeneratedRowKind { - BODY, - TERRAIN - } - private record GeneratedRow(@Nonnull Ref ref, @Nonnull UUID uuid, - @Nonnull GeneratedRowKind kind, @Nullable String payloadResourceKey) { private GeneratedRow { Objects.requireNonNull(ref, "ref"); Objects.requireNonNull(uuid, "uuid"); - Objects.requireNonNull(kind, "kind"); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 3c6844db..7494d47a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -20,13 +20,10 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.List; -import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.BiConsumer; @@ -227,21 +224,7 @@ private static List physicsChunkSections( PhysicsTerrainPayloadResource.getResourceType()); double maxDistanceSquared = viewRadius * viewRadius; List visible = new ArrayList<>(); - Set seenSources = new ObjectOpenHashSet<>(); BiConsumer, CommandBuffer> collector = - (chunk, _) -> collectPhysicsChunkTerrainChunk(chunk, - payloads, - spaceContext, - spaceRef, - spaceUuid, - viewerX, - viewerY, - viewerZ, - maxDistanceSquared, - seenSources, - visible); - store.forEachChunk(TerrainColliderComponent.getComponentType(), collector); - BiConsumer, CommandBuffer> sourceCollector = (chunk, _) -> collectPhysicsChunkSourceChunk(chunk, payloads, spaceContext, @@ -251,45 +234,11 @@ private static List physicsChunkSections( viewerY, viewerZ, maxDistanceSquared, - seenSources, visible); - store.forEachChunk(ChunkCollisionSourceComponent.getComponentType(), sourceCollector); + store.forEachChunk(ChunkCollisionSourceComponent.getComponentType(), collector); return List.copyOf(visible); } - private static void collectPhysicsChunkTerrainChunk(@Nonnull ArchetypeChunk chunk, - @Nonnull PhysicsTerrainPayloadResource payloads, - @Nonnull SpaceContext spaceContext, - @Nullable Ref spaceRef, - @Nonnull UUID spaceUuid, - double viewerX, - double viewerY, - double viewerZ, - double maxDistanceSquared, - @Nonnull Set seenSources, - @Nonnull List visible) { - for (int index = 0; index < chunk.size(); index++) { - TerrainColliderComponent terrain = chunk.getComponent(index, - TerrainColliderComponent.getComponentType()); - if (terrain == null || !terrain.isRetained() - || !matchesSpace(terrain, spaceRef, spaceUuid)) { - continue; - } - if (distanceSquaredToSection(terrain, viewerX, viewerY, viewerZ) - > maxDistanceSquared) { - continue; - } - TerrainColliderPayload payload = payloads.get(terrain.getPayloadResourceKey()); - if (payload == null || payload.isEmpty()) { - continue; - } - if (!seenSources.add(sourceKey(terrain.getSpaceUuid(), terrain.getSourceKey()))) { - continue; - } - visible.add(toPhysicsChunkSectionView(terrain, payload, spaceContext)); - } - } - private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk chunk, @Nonnull PhysicsTerrainPayloadResource payloads, @Nonnull SpaceContext spaceContext, @@ -299,7 +248,6 @@ private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk seenSources, @Nonnull List visible) { for (int index = 0; index < chunk.size(); index++) { ChunkCollisionSourceComponent source = chunk.getComponent(index, @@ -316,30 +264,10 @@ private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk spaceRef, - @Nonnull UUID spaceUuid) { - Ref terrainSpaceRef = terrain.getSpaceRef(); - if (terrainSpaceRef != null && spaceRef != null) { - return sameRef(terrainSpaceRef, spaceRef); - } - return spaceUuid.equals(terrain.getSpaceUuid()); - } - private static boolean matchesSpace(@Nonnull BodyComponent body, @Nullable Ref spaceRef, @Nonnull UUID spaceUuid) { @@ -569,24 +487,6 @@ private static double distanceSquared(double x, return dx * dx + dy * dy + dz * dz; } - private static double distanceSquaredToSection(@Nonnull TerrainColliderComponent terrain, - double viewerX, - double viewerY, - double viewerZ) { - double minX = terrain.getChunkX() << ChunkUtil.BITS; - double minY = terrain.getSectionY() << ChunkUtil.BITS; - double minZ = terrain.getChunkZ() << ChunkUtil.BITS; - return distanceSquaredToBounds(viewerX, - viewerY, - viewerZ, - minX, - minY, - minZ, - minX + ChunkUtil.SIZE, - minY + ChunkUtil.SIZE, - minZ + ChunkUtil.SIZE); - } - private static double distanceSquaredToSection(@Nonnull ChunkCollisionSourceComponent source, double viewerX, double viewerY, @@ -605,11 +505,6 @@ private static double distanceSquaredToSection(@Nonnull ChunkCollisionSourceComp minZ + ChunkUtil.SIZE); } - @Nonnull - private static String sourceKey(@Nonnull UUID spaceUuid, @Nonnull String sourceKey) { - return spaceUuid + "|" + sourceKey; - } - private static double distanceSquaredToBounds(double viewerX, double viewerY, double viewerZ, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 8bf9ac86..7b067aad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -36,12 +36,6 @@ public static ComponentType bodyCommandCompo return PhysicsComponentTypeRegistry.bodyCommandComponentType(); } - @Nonnull - public static ComponentType - terrainColliderComponentType() { - return PhysicsComponentTypeRegistry.terrainColliderComponentType(); - } - @Nonnull public static ComponentType chunkCollisionSourceComponentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java deleted file mode 100644 index 3c3fec72..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/TerrainColliderComponent.java +++ /dev/null @@ -1,151 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.components; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import lombok.Getter; -import lombok.Setter; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Terrain collider entity mirrored from ChunkStore terrain source data. - */ -public final class TerrainColliderComponent implements Component { - - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - TerrainColliderComponent.class, - TerrainColliderComponent::new) - .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY, false), - (component, value) -> component.spaceUuid = value, - TerrainColliderComponent::getSpaceUuid) - .add() - .append(new KeyedCodec<>("SourceKey", Codec.STRING, false), - (component, value) -> component.sourceKey = value != null ? value : "", - TerrainColliderComponent::getSourceKey) - .add() - .append(new KeyedCodec<>("ChunkX", Codec.INTEGER, false), - (component, value) -> component.chunkX = value != null ? value : 0, - TerrainColliderComponent::getChunkX) - .add() - .append(new KeyedCodec<>("SectionY", Codec.INTEGER, false), - (component, value) -> component.sectionY = value != null ? value : 0, - TerrainColliderComponent::getSectionY) - .add() - .append(new KeyedCodec<>("ChunkZ", Codec.INTEGER, false), - (component, value) -> component.chunkZ = value != null ? value : 0, - TerrainColliderComponent::getChunkZ) - .add() - .append(new KeyedCodec<>("PayloadResourceKey", Codec.STRING, false), - (component, value) -> component.payloadResourceKey = value != null ? value : "", - TerrainColliderComponent::getPayloadResourceKey) - .add() - .append(new KeyedCodec<>("Retained", Codec.BOOLEAN, false), - (component, value) -> component.retained = value == null || value, - TerrainColliderComponent::isRetained) - .add() - .build(); - - @Nonnull - private UUID spaceUuid = new UUID(0L, 0L); - @Nullable - private transient Ref spaceRef; - @Nonnull - private String sourceKey = ""; - @Setter - @Getter - private int chunkX; - @Setter - @Getter - private int sectionY; - @Setter - @Getter - private int chunkZ; - @Nonnull - private String payloadResourceKey = ""; - @Setter - @Getter - private boolean retained = true; - - public TerrainColliderComponent() { - } - - public TerrainColliderComponent(@Nonnull UUID spaceUuid, - @Nonnull String sourceKey, - int chunkX, - int sectionY, - int chunkZ, - @Nonnull String payloadResourceKey, - boolean retained) { - this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); - this.chunkX = chunkX; - this.sectionY = sectionY; - this.chunkZ = chunkZ; - this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); - this.retained = retained; - } - - @Nonnull - public UUID getSpaceUuid() { - return spaceUuid; - } - - public void setSpaceUuid(@Nonnull UUID spaceUuid) { - this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.spaceRef = null; - } - - @Nullable - public Ref getSpaceRef() { - return spaceRef; - } - - public void setSpaceRef(@Nullable Ref spaceRef) { - this.spaceRef = spaceRef; - } - - @Nonnull - public String getSourceKey() { - return sourceKey; - } - - public void setSourceKey(@Nonnull String sourceKey) { - this.sourceKey = Objects.requireNonNull(sourceKey, "sourceKey"); - } - - @Nonnull - public String getPayloadResourceKey() { - return payloadResourceKey; - } - - public void setPayloadResourceKey(@Nonnull String payloadResourceKey) { - this.payloadResourceKey = Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); - } - - @Nonnull - public static ComponentType getComponentType() { - return PhysicsComponentTypes.terrainColliderComponentType(); - } - - @Nonnull - @Override - public TerrainColliderComponent clone() { - TerrainColliderComponent copy = new TerrainColliderComponent(spaceUuid, - sourceKey, - chunkX, - sectionY, - chunkZ, - payloadResourceKey, - retained); - copy.spaceRef = spaceRef; - return copy; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index 41cf8ce4..fc5de083 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -17,7 +17,6 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.TerrainColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; @@ -124,16 +123,6 @@ public static Holder jointHolder(@Nonnull Store stor return holder; } - @Nonnull - public static Holder terrainColliderHolder(@Nonnull Store store, - @Nonnull UUID terrainColliderUuid, - @Nonnull TerrainColliderComponent terrainCollider) { - Holder holder = entityHolder(store, terrainColliderUuid); - holder.addComponent(TerrainColliderComponent.getComponentType(), - Objects.requireNonNull(terrainCollider, "terrainCollider").clone()); - return holder; - } - public static void addSpaceComponents(@Nonnull Holder holder, @Nonnull SpaceComponent space, @Nonnull PhysicsChunkTerrainComponent terrainSettings, @@ -303,15 +292,4 @@ public static void putJointComponent(@Nonnull Store store, Objects.requireNonNull(joint, "joint").clone()); } - public static void putTerrainColliderComponent(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull TerrainColliderComponent terrainCollider) { - Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsThreading.requireWorldThread(checkedStore, - "put PhysicsStore terrain collider component"); - checkedStore - .putComponent(Objects.requireNonNull(ref, "ref"), - TerrainColliderComponent.getComponentType(), - Objects.requireNonNull(terrainCollider, "terrainCollider").clone()); - } } From f7396d60a11c20f3910e22243c0c86416bc692bc Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:52:49 +0200 Subject: [PATCH 396/534] refactor(core): rename chunk collision pipeline Signed-off-by: Blovien --- ...tachedStreamingBenchmarkCrucibleTests.java | 6 +- .../PhysicsStoreBenchmarkQueries.java | 2 +- ...liderMode.java => ChunkCollisionMode.java} | 4 +- .../physicschunk/ChunkCollisionMutation.java} | 26 +++--- .../physicschunk/ChunkCollisionPayload.java} | 14 +-- .../PhysicsChunkBuildOptions.java | 10 +-- .../PhysicsChunkMutationCache.java | 24 ++--- .../PhysicsChunkTerrainStreamingResource.java | 18 ++-- ... PhysicsStoreChunkCollisionMutations.java} | 42 ++++----- .../commands/PhysicsChunkSettingsCommand.java | 2 +- .../PhysicsChunkTerrainProducerSystem.java | 12 +-- .../PhysicsStoreRuntimeCleaner.java | 8 +- .../PhysicsStoreTopologyMutations.java | 20 ++--- .../PhysicsStoreRegistration.java | 20 ++--- ...sChunkCollisionMutationQueueResource.java} | 30 +++---- ...PhysicsChunkCollisionPayloadResource.java} | 22 ++--- .../PhysicsChunkSettingsIndexResource.java | 4 +- .../resources/PhysicsResourceTypes.java | 12 +-- .../PhysicsWorldRuntimeResource.java | 8 +- .../internal/systems/BodyBindingSystem.java | 24 ++--- ...=> ChunkCollisionMutationDrainSystem.java} | 88 +++++++++---------- ...> ChunkCollisionVoxelStitchingSystem.java} | 24 ++--- .../internal/systems/TargetBindingSystem.java | 2 +- .../debug/PhysicsStoreDebugQueries.java | 18 ++-- .../core/plugin/body/PhysicsBodyKind.java | 2 +- .../physicschunk/PhysicsChunkTerrain.java | 12 +-- .../physicschunk/PhysicsChunkTerrainMode.java | 2 +- .../ChunkCollisionSourceComponent.java | 2 +- .../settings/PhysicsChunkTerrainSettings.java | 10 +-- .../systems/ExplosiveFuseContactSystem.java | 6 +- 30 files changed, 237 insertions(+), 237 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{TerrainColliderMode.java => ChunkCollisionMode.java} (75%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{terrain/TerrainColliderMutation.java => modules/physicschunk/ChunkCollisionMutation.java} (65%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{terrain/TerrainColliderPayload.java => modules/physicschunk/ChunkCollisionPayload.java} (81%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{PhysicsStoreTerrainMutations.java => PhysicsStoreChunkCollisionMutations.java} (68%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/{PhysicsTerrainMutationQueueResource.java => PhysicsChunkCollisionMutationQueueResource.java} (50%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/{PhysicsTerrainPayloadResource.java => PhysicsChunkCollisionPayloadResource.java} (54%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{TerrainMutationDrainSystem.java => ChunkCollisionMutationDrainSystem.java} (81%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{TerrainVoxelStitchingSystem.java => ChunkCollisionVoxelStitchingSystem.java} (89%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 718d1deb..09877c19 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -370,8 +370,8 @@ private void restoreStepSettings() { private PrewarmStats prewarmPhysicsChunkTerrain(@Nonnull SpaceId spaceId, int count) { BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(physicsStore, spaceId); - PhysicsTerrainMutationQueueResource queue = physicsStore.getResource( - PhysicsTerrainMutationQueueResource.getResourceType()); + PhysicsChunkCollisionMutationQueueResource queue = physicsStore.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( physics.getSpaceSettings(spaceId).getPhysicsChunkTerrainSettings()); PhysicsChunkTerrainPrewarmStats stats = terrainStreaming.ensureAround(world, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 88807d64..5bc7beab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -107,7 +107,7 @@ private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, if (shape != null && shape.getShapeType() == ShapeType.PLANE) { return; } - if (body.getKind().isTerrainCollider()) { + if (body.getKind().isTerrain()) { stats.terrainBodies++; return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/TerrainColliderMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMode.java similarity index 75% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/TerrainColliderMode.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMode.java index 23ba3abb..86049b1d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/TerrainColliderMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMode.java @@ -3,11 +3,11 @@ /** * Runtime representation used for full-cube terrain collision. */ -public enum TerrainColliderMode { +public enum ChunkCollisionMode { MERGED_BOXES, NATIVE_VOXELS_WHEN_SUPPORTED; - public static TerrainColliderMode fromNativeVoxelTerrainEnabled(boolean enabled) { + public static ChunkCollisionMode fromNativeVoxelTerrainEnabled(boolean enabled) { return enabled ? NATIVE_VOXELS_WHEN_SUPPORTED : MERGED_BOXES; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderMutation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java similarity index 65% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderMutation.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java index 3c7beca9..3443b874 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderMutation.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.terrain; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -7,37 +7,37 @@ import javax.annotation.Nullable; /** - * Copied terrain collider mutation emitted from PhysicsChunk terrain code. + * Copied chunk collision mutation emitted from PhysicsChunk terrain code. */ -public record TerrainColliderMutation(@Nonnull UUID spaceUuid, +public record ChunkCollisionMutation(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, int chunkX, int sectionY, int chunkZ, @Nonnull String payloadResourceKey, - @Nullable TerrainColliderPayload payload, + @Nullable ChunkCollisionPayload payload, boolean remove) { - public TerrainColliderMutation { + public ChunkCollisionMutation { Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(sourceKey, "sourceKey"); Objects.requireNonNull(payloadResourceKey, "payloadResourceKey"); } @Nonnull - public UUID terrainColliderUuid() { - return terrainColliderUuid(spaceUuid, sourceKey); + public UUID chunkCollisionUuid() { + return chunkCollisionUuid(spaceUuid, sourceKey); } @Nonnull - public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, + public static ChunkCollisionMutation upsert(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, int chunkX, int sectionY, int chunkZ, @Nonnull String payloadResourceKey, - @Nonnull TerrainColliderPayload payload) { - return new TerrainColliderMutation(spaceUuid, + @Nonnull ChunkCollisionPayload payload) { + return new ChunkCollisionMutation(spaceUuid, sourceKey, chunkX, sectionY, @@ -48,12 +48,12 @@ public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, } @Nonnull - public static TerrainColliderMutation remove(@Nonnull UUID spaceUuid, + public static ChunkCollisionMutation remove(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, int chunkX, int sectionY, int chunkZ) { - return new TerrainColliderMutation(spaceUuid, + return new ChunkCollisionMutation(spaceUuid, sourceKey, chunkX, sectionY, @@ -64,7 +64,7 @@ public static TerrainColliderMutation remove(@Nonnull UUID spaceUuid, } @Nonnull - public static UUID terrainColliderUuid(@Nonnull UUID spaceUuid, + public static UUID chunkCollisionUuid(@Nonnull UUID spaceUuid, @Nonnull String sourceKey) { String key = spaceUuid + "|" + sourceKey; return UUID.nameUUIDFromBytes(key.getBytes(StandardCharsets.UTF_8)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderPayload.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java similarity index 81% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderPayload.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java index 1abb65d4..02477d20 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/terrain/TerrainColliderPayload.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.terrain; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import java.util.Arrays; import java.util.List; @@ -6,9 +6,9 @@ import javax.annotation.Nonnull; /** - * Copied terrain payload carried across the ChunkStore to PhysicsStore boundary. + * Copied chunk collision payload carried across the ChunkStore to PhysicsStore boundary. */ -public record TerrainColliderPayload(float voxelSizeX, +public record ChunkCollisionPayload(float voxelSizeX, float voxelSizeY, float voxelSizeZ, @Nonnull int[] voxelCoordinates, @@ -19,9 +19,9 @@ public record TerrainColliderPayload(float voxelSizeX, float restitution, int collisionGroup, int collisionMask, - @Nonnull List neighbors) { + @Nonnull List neighbors) { - public TerrainColliderPayload { + public ChunkCollisionPayload { voxelCoordinates = Arrays.copyOf(voxelCoordinates, voxelCoordinates.length); mergedFullCubeBoxes = List.copyOf(mergedFullCubeBoxes); detailBoxes = List.copyOf(detailBoxes); @@ -57,9 +57,9 @@ public record BoxPayload(double centerX, /** * Neighbor terrain source used for optional native-voxel stitching. */ - public record TerrainNeighbor(@Nonnull String sourceKey, int shiftX, int shiftY, int shiftZ) { + public record Neighbor(@Nonnull String sourceKey, int shiftX, int shiftY, int shiftZ) { - public TerrainNeighbor { + public Neighbor { Objects.requireNonNull(sourceKey, "sourceKey"); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index c8c19233..304bacf1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -7,7 +7,7 @@ /** * Options that control generated PhysicsChunk terrain backend geometry. */ -public record PhysicsChunkBuildOptions(@Nonnull TerrainColliderMode terrainColliderMode, +public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisionMode, float terrainFriction, float terrainRestitution) { @@ -15,7 +15,7 @@ public record PhysicsChunkBuildOptions(@Nonnull TerrainColliderMode terrainColli fromNativeVoxelTerrainEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); public PhysicsChunkBuildOptions { - Objects.requireNonNull(terrainColliderMode, "terrainColliderMode"); + Objects.requireNonNull(chunkCollisionMode, "chunkCollisionMode"); if (!Float.isFinite(terrainFriction) || terrainFriction < 0.0f) { throw new IllegalArgumentException("terrainFriction must be finite and >= 0"); } @@ -27,19 +27,19 @@ public record PhysicsChunkBuildOptions(@Nonnull TerrainColliderMode terrainColli @Nonnull public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrainSettings settings) { return new PhysicsChunkBuildOptions( - TerrainColliderMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), + ChunkCollisionMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), settings.getTerrainFriction(), settings.getTerrainRestitution()); } @Nonnull public static PhysicsChunkBuildOptions fromNativeVoxelTerrainEnabled(boolean enabled) { - return new PhysicsChunkBuildOptions(TerrainColliderMode.fromNativeVoxelTerrainEnabled(enabled), + return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelTerrainEnabled(enabled), PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); } public boolean nativeVoxelTerrainEnabled() { - return terrainColliderMode.nativeVoxelTerrainEnabled(); + return chunkCollisionMode.nativeVoxelTerrainEnabled(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java index 75a8db2e..d5c8a9df 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.MissingSectionReason; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2LongMap; @@ -51,7 +51,7 @@ public final class PhysicsChunkMutationCache { @Nonnull public synchronized VoxelTerrainCollisionCache.BuildStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Vector3d center, int radius, long tick, @@ -109,7 +109,7 @@ public synchronized VoxelTerrainCollisionCache.BuildStats ensureAround(@Nonnull } public synchronized int pruneUnused(@Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, long currentTick, int ttlTicks, @Nullable Snapshot profiling) { @@ -145,7 +145,7 @@ public synchronized int pruneUnused(@Nonnull UUID spaceUuid, public synchronized int pruneUnloaded(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nullable Snapshot profiling) { long start = profiling != null ? System.nanoTime() : 0L; SpaceCollisionCache cache = spaces.get(spaceUuid); @@ -177,7 +177,7 @@ public synchronized int pruneUnloaded(@Nonnull World world, } public synchronized void retainSpaces(@Nonnull Set retainedSpaces, - @Nonnull PhysicsTerrainMutationQueueResource queue) { + @Nonnull PhysicsChunkCollisionMutationQueueResource queue) { Iterator> iterator = spaces.object2ObjectEntrySet().iterator(); while (iterator.hasNext()) { @@ -191,7 +191,7 @@ public synchronized void retainSpaces(@Nonnull Set retainedSpaces, } public synchronized int clearSpace(@Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue) { + @Nonnull PhysicsChunkCollisionMutationQueueResource queue) { SpaceCollisionCache cache = spaces.remove(spaceUuid); if (cache == null) { return 0; @@ -200,7 +200,7 @@ public synchronized int clearSpace(@Nonnull UUID spaceUuid, } public synchronized int clearSectionsAround(@Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Vector3d center, int radius) { SpaceCollisionCache cache = spaces.get(spaceUuid); @@ -467,7 +467,7 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, @Nonnull private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, int chunkX, int sectionY, int chunkZ, @@ -563,7 +563,7 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world buildOptions.nativeVoxelTerrainEnabled() && geometry.hasFullCubeVoxels()); int removedBodies = cached != null ? removeSection(spaceUuid, queue, cached) : 0; if (built.bodyCount > 0) { - queue.enqueue(PhysicsStoreTerrainMutations.upsert(spaceUuid, + queue.enqueue(PhysicsStoreChunkCollisionMutations.upsert(spaceUuid, chunkX, sectionY, chunkZ, @@ -600,12 +600,12 @@ private static int bodyCount(@Nonnull SectionCollisionGeometry geometry, } private static int removeSection(@Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull CachedSection section) { if (section.bodyCount <= 0) { return 0; } - queue.enqueue(PhysicsStoreTerrainMutations.remove(spaceUuid, + queue.enqueue(PhysicsStoreChunkCollisionMutations.remove(spaceUuid, section.chunkX, section.sectionY, section.chunkZ)); @@ -613,7 +613,7 @@ private static int removeSection(@Nonnull UUID spaceUuid, } private static int removeAllSections(@Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull SpaceCollisionCache cache) { int removed = 0; for (CachedSection section : cache.sections.values()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java index d9ab8098..1a66db73 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelTerrainCollisionCache.BuildStats; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; @@ -24,7 +24,7 @@ import org.joml.Vector3d; /** - * Shared EntityStore-side producer state for copied PhysicsStore terrain mutations. + * Shared EntityStore-side producer state for copied PhysicsStore chunk collision mutations. */ public final class PhysicsChunkTerrainStreamingResource implements Resource { @@ -60,14 +60,14 @@ public synchronized long nextTick() { } public synchronized void retainSpaces(@Nonnull Set retainedSpaces, - @Nonnull PhysicsTerrainMutationQueueResource queue) { + @Nonnull PhysicsChunkCollisionMutationQueueResource queue) { cache.retainSpaces(retainedSpaces, queue); } @Nonnull public synchronized PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Iterable centers, int radius, long tick, @@ -93,7 +93,7 @@ public synchronized PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World @Nonnull public synchronized PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Vector3d center, int radius, long tick, @@ -116,7 +116,7 @@ public synchronized PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World w @Nonnull public synchronized BuildStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Vector3d center, int radius, long tick, @@ -195,13 +195,13 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, public synchronized int pruneUnloaded(@Nonnull World world, @Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nullable Snapshot profiling) { return cache.pruneUnloaded(world, spaceUuid, queue, profiling); } public synchronized int pruneUnused(@Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, long currentTick, int ttlTicks, @Nullable Snapshot profiling) { @@ -209,7 +209,7 @@ public synchronized int pruneUnused(@Nonnull UUID spaceUuid, } public synchronized int clearSpace(@Nonnull UUID spaceUuid, - @Nonnull PhysicsTerrainMutationQueueResource queue) { + @Nonnull PhysicsChunkCollisionMutationQueueResource queue) { return cache.clearSpace(spaceUuid, queue); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java similarity index 68% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java index d0daa6f7..d6b962db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreTerrainMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java @@ -2,26 +2,26 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.BoxPayload; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.TerrainNeighbor; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.Neighbor; import java.util.List; import java.util.UUID; import javax.annotation.Nonnull; /** - * Converts generated PhysicsChunk terrain sections into copied PhysicsStore terrain mutations. + * Converts generated PhysicsChunk terrain sections into copied PhysicsStore chunk collision mutations. */ -public final class PhysicsStoreTerrainMutations { +public final class PhysicsStoreChunkCollisionMutations { private static final int ADJACENT_SECTION_VOXEL_SHIFT = 16; - private PhysicsStoreTerrainMutations() { + private PhysicsStoreChunkCollisionMutations() { } @Nonnull - public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, + public static ChunkCollisionMutation upsert(@Nonnull UUID spaceUuid, int chunkX, int sectionY, int chunkZ, @@ -29,7 +29,7 @@ public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, @Nonnull SectionCollisionGeometry geometry, @Nonnull PhysicsChunkBuildOptions buildOptions) { String sourceKey = sourceKey(chunkX, sectionY, chunkZ); - return TerrainColliderMutation.upsert(spaceUuid, + return ChunkCollisionMutation.upsert(spaceUuid, sourceKey, chunkX, sectionY, @@ -39,11 +39,11 @@ public static TerrainColliderMutation upsert(@Nonnull UUID spaceUuid, } @Nonnull - public static TerrainColliderMutation remove(@Nonnull UUID spaceUuid, + public static ChunkCollisionMutation remove(@Nonnull UUID spaceUuid, int chunkX, int sectionY, int chunkZ) { - return TerrainColliderMutation.remove(spaceUuid, + return ChunkCollisionMutation.remove(spaceUuid, sourceKey(chunkX, sectionY, chunkZ), chunkX, sectionY, @@ -66,10 +66,10 @@ private static String payloadKey(@Nonnull String sourceKey, } @Nonnull - private static TerrainColliderPayload payload(@Nonnull SectionCollisionGeometry geometry, + private static ChunkCollisionPayload payload(@Nonnull SectionCollisionGeometry geometry, @Nonnull PhysicsChunkBuildOptions buildOptions, - @Nonnull List neighbors) { - return new TerrainColliderPayload(1.0f, + @Nonnull List neighbors) { + return new ChunkCollisionPayload(1.0f, 1.0f, 1.0f, geometry.fullCubeVoxels(), @@ -96,29 +96,29 @@ private static List boxes(@Nonnull List boxes) { } @Nonnull - private static List adjacentNeighbors(int chunkX, int sectionY, int chunkZ) { + private static List adjacentNeighbors(int chunkX, int sectionY, int chunkZ) { return List.of( - new TerrainNeighbor(sourceKey(chunkX - 1, sectionY, chunkZ), + new Neighbor(sourceKey(chunkX - 1, sectionY, chunkZ), -ADJACENT_SECTION_VOXEL_SHIFT, 0, 0), - new TerrainNeighbor(sourceKey(chunkX + 1, sectionY, chunkZ), + new Neighbor(sourceKey(chunkX + 1, sectionY, chunkZ), ADJACENT_SECTION_VOXEL_SHIFT, 0, 0), - new TerrainNeighbor(sourceKey(chunkX, sectionY - 1, chunkZ), + new Neighbor(sourceKey(chunkX, sectionY - 1, chunkZ), 0, -ADJACENT_SECTION_VOXEL_SHIFT, 0), - new TerrainNeighbor(sourceKey(chunkX, sectionY + 1, chunkZ), + new Neighbor(sourceKey(chunkX, sectionY + 1, chunkZ), 0, ADJACENT_SECTION_VOXEL_SHIFT, 0), - new TerrainNeighbor(sourceKey(chunkX, sectionY, chunkZ - 1), + new Neighbor(sourceKey(chunkX, sectionY, chunkZ - 1), 0, 0, -ADJACENT_SECTION_VOXEL_SHIFT), - new TerrainNeighbor(sourceKey(chunkX, sectionY, chunkZ + 1), + new Neighbor(sourceKey(chunkX, sectionY, chunkZ + 1), 0, 0, ADJACENT_SECTION_VOXEL_SHIFT)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index c17fc821..6a6093c4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -53,7 +53,7 @@ public class PhysicsChunkSettingsCommand extends AbstractAsyncPlayerCommand { ArgTypes.STRING); private final OptionalArg terrainArg = this.withOptionalArg( "terrain", - "PhysicsChunk terrain collider: boxes or native_voxels", + "PhysicsChunk collision bodies: boxes or native_voxels", ArgTypes.STRING); private final OptionalArg spaceArg = this.withOptionalArg( "space", diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java index 8df20074..e3dc6b2a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java @@ -24,7 +24,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; @@ -48,7 +48,7 @@ import org.joml.Vector3f; /** - * Produces copied PhysicsStore terrain mutations from EntityStore and ChunkStore state. + * Produces copied PhysicsStore chunk collision mutations from EntityStore and ChunkStore state. */ public final class PhysicsChunkTerrainProducerSystem extends TickingSystem implements QuerySystem { @@ -78,9 +78,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { World world = store.getExternalData().getWorld(); Store physics = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(physics, - "produce PhysicsStore PhysicsChunk terrain mutations"); - PhysicsTerrainMutationQueueResource queue = physics.getResource( - PhysicsTerrainMutationQueueResource.getResourceType()); + "produce PhysicsStore PhysicsChunk chunk collision mutations"); + PhysicsChunkCollisionMutationQueueResource queue = physics.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); PhysicsChunkSettingsIndexResource terrainSettingsIndex = physics.getResource( PhysicsChunkSettingsIndexResource.getResourceType()); PhysicsSnapshotResource snapshotResource = physics.getResource( @@ -129,7 +129,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { private static void processSpace(@Nonnull World world, @Nonnull PhysicsChunkTerrainStreamingResource streaming, - @Nonnull PhysicsTerrainMutationQueueResource queue, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull PhysicsChunkSpaceSettings settings, @Nonnull List playerPositions, @Nonnull PhysicsSnapshotFrame physicsFrame, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java index c5dcb091..6a6272ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java @@ -11,8 +11,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; @@ -32,7 +32,7 @@ public static void clearAll(@Nonnull Store store) { (index, chunk, commandBuffer) -> commandBuffer.removeEntity( chunk.getReferenceTo(index), RemoveReason.REMOVE)); - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear(); + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()).clear(); store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings(); store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); @@ -41,7 +41,7 @@ public static void clearAll(@Nonnull Store store) { store.getResource(PhysicsEventResource.getResourceType()).clear(); store.getResource(PhysicsProfilingResource.getResourceType()).reset(); store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); - store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()).clear(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 17951567..52581823 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -12,8 +12,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; @@ -66,8 +66,8 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( removeRuntimeRows(runtime, identity, removals); removeRows(store, removals); clearCopiedBodyState(store); - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()).clear(); - store.getResource(PhysicsTerrainPayloadResource.getResourceType()).clear(); + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()).clear(); + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); runtime.clearTransientBodyOperations(); int keptSpaces = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .size(); @@ -85,7 +85,7 @@ public static void removeSpaceWithContents(@Nonnull Store store, Ref spaceRef = identity.getByUuid(spaceUuid); List removals = collectRows(store, spaceUuid, spaceRef, null, null); removeRuntimeRows(runtime, identity, removals); - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()) .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); removeRows(store, removals); PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceUuid); @@ -105,7 +105,7 @@ public static int clearTerrainForSpace(@Nonnull Store store, removedBodies++; } } - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType()) + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()) .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); removeRows(store, removals); return removedBodies; @@ -221,7 +221,7 @@ private static List collectTerrainRows(@Nonnull Store ChunkCollisionSourceComponent.getComponentType()); if (source != null && body != null - && body.getKind().isTerrainCollider() + && body.getKind().isTerrain() && matchesSpace(body.getSpaceRef(), body.getSpaceUuid(), spaceRef, spaceUuid)) { removals.add(new RowRemoval(chunk.getReferenceTo(index), uuid.getUuid(), @@ -297,8 +297,8 @@ private static void removeRows(@Nonnull Store store, @Nonnull List removals) { PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); - PhysicsTerrainPayloadResource terrainPayloads = - store.getResource(PhysicsTerrainPayloadResource.getResourceType()); + PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); PhysicsBodyRegistrationResource registrations = store.getResource(PhysicsBodyRegistrationResource.getResourceType()); @@ -309,7 +309,7 @@ private static void removeRows(@Nonnull Store store, identity.removeUuid(removal.rowUuid(), removal.ref()); if (removal.payloadResourceKey() != null && !removal.payloadResourceKey().isBlank()) { - terrainPayloads.remove(removal.payloadResourceKey()); + chunkCollisionPayloads.remove(removal.payloadResourceKey()); } if (removal.kind() == RowKind.BODY) { PhysicsControlRuntimeStates.clearControlled(removal.ref()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index bf191a26..71a071ce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -20,8 +20,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.TickDecision; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; @@ -38,8 +38,8 @@ import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.TerrainMutationDrainSystem; -import dev.hytalemodding.impulse.core.internal.systems.TerrainVoxelStitchingSystem; +import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionMutationDrainSystem; +import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionVoxelStitchingSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; @@ -76,12 +76,12 @@ public static void register(@Nonnull ComponentRegistryProxy regist registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); - registry.registerSystem(new TerrainMutationDrainSystem()); + registry.registerSystem(new ChunkCollisionMutationDrainSystem()); registry.registerSystem(new BodyBindingSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); - registry.registerSystem(new TerrainVoxelStitchingSystem()); + registry.registerSystem(new ChunkCollisionVoxelStitchingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); @@ -108,12 +108,12 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic PhysicsRuntimeResource::destroyBackendBindings)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, - PhysicsTerrainMutationQueueResource.getResourceType(), - PhysicsTerrainMutationQueueResource::clear)); + PhysicsChunkCollisionMutationQueueResource.getResourceType(), + PhysicsChunkCollisionMutationQueueResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, - PhysicsTerrainPayloadResource.getResourceType(), - PhysicsTerrainPayloadResource::clear)); + PhysicsChunkCollisionPayloadResource.getResourceType(), + PhysicsChunkCollisionPayloadResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsChunkSettingsIndexResource.getResourceType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java similarity index 50% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java index f103b0e8..d4ecb4bf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -14,26 +14,26 @@ import javax.annotation.Nullable; /** - * Copied terrain mutation queue drained by PhysicsStore.tick(). + * Copied chunk collision mutation queue drained by PhysicsStore.tick(). */ -public final class PhysicsTerrainMutationQueueResource implements Resource { +public final class PhysicsChunkCollisionMutationQueueResource implements Resource { @Nullable - private static ResourceType resourceType; + private static ResourceType resourceType; @Nonnull - private final Queue mutations = new ArrayDeque<>(); + private final Queue mutations = new ArrayDeque<>(); - public PhysicsTerrainMutationQueueResource() { + public PhysicsChunkCollisionMutationQueueResource() { } - public synchronized void enqueue(@Nonnull TerrainColliderMutation mutation) { + public synchronized void enqueue(@Nonnull ChunkCollisionMutation mutation) { mutations.add(Objects.requireNonNull(mutation, "mutation")); } @Nonnull - public synchronized List drain() { - List drained = new ArrayList<>(mutations.size()); - TerrainColliderMutation mutation; + public synchronized List drain() { + List drained = new ArrayList<>(mutations.size()); + ChunkCollisionMutation mutation; while ((mutation = mutations.poll()) != null) { drained.add(mutation); } @@ -44,7 +44,7 @@ public synchronized int size() { return mutations.size(); } - public synchronized int removeIf(@Nonnull Predicate predicate) { + public synchronized int removeIf(@Nonnull Predicate predicate) { Objects.requireNonNull(predicate, "predicate"); int before = mutations.size(); mutations.removeIf(predicate); @@ -57,19 +57,19 @@ public synchronized void clear() { @Nonnull @Override - public synchronized PhysicsTerrainMutationQueueResource clone() { - PhysicsTerrainMutationQueueResource copy = new PhysicsTerrainMutationQueueResource(); + public synchronized PhysicsChunkCollisionMutationQueueResource clone() { + PhysicsChunkCollisionMutationQueueResource copy = new PhysicsChunkCollisionMutationQueueResource(); copy.mutations.addAll(mutations); return copy; } @Nonnull - public static ResourceType getResourceType() { + public static ResourceType getResourceType() { return resourceType; } public static void setResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { resourceType = type; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java similarity index 54% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java index 258843ff..2a890b3a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsTerrainPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import javax.annotation.Nonnull; @@ -12,23 +12,23 @@ /** * Runtime-only copied PhysicsChunk collision payloads keyed by ChunkCollisionSourceComponent payload keys. */ -public final class PhysicsTerrainPayloadResource implements Resource { +public final class PhysicsChunkCollisionPayloadResource implements Resource { @Nullable - private static ResourceType resourceType; + private static ResourceType resourceType; @Nonnull - private final Map payloadsByKey = + private final Map payloadsByKey = new Object2ObjectOpenHashMap<>(); - public PhysicsTerrainPayloadResource() { + public PhysicsChunkCollisionPayloadResource() { } - public void put(@Nonnull String key, @Nonnull TerrainColliderPayload payload) { + public void put(@Nonnull String key, @Nonnull ChunkCollisionPayload payload) { payloadsByKey.put(key, payload); } @Nullable - public TerrainColliderPayload get(@Nonnull String key) { + public ChunkCollisionPayload get(@Nonnull String key) { return payloadsByKey.get(key); } @@ -42,19 +42,19 @@ public void clear() { @Nonnull @Override - public PhysicsTerrainPayloadResource clone() { - PhysicsTerrainPayloadResource copy = new PhysicsTerrainPayloadResource(); + public PhysicsChunkCollisionPayloadResource clone() { + PhysicsChunkCollisionPayloadResource copy = new PhysicsChunkCollisionPayloadResource(); copy.payloadsByKey.putAll(payloadsByKey); return copy; } @Nonnull - public static ResourceType getResourceType() { + public static ResourceType getResourceType() { return resourceType; } public static void setResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { resourceType = type; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index 1533db05..df464844 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.TerrainColliderMode; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -80,7 +80,7 @@ public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, @Nonnull public PhysicsChunkBuildOptions buildOptions() { return new PhysicsChunkBuildOptions( - TerrainColliderMode.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled), + ChunkCollisionMode.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled), terrainFriction, terrainRestitution); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index ebf7cac8..4dc62c86 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -87,12 +87,12 @@ public static void registerResourceTypes( debugResourceType = registry.registerResource( dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource.class, dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource::new); - PhysicsTerrainMutationQueueResource.setResourceType(registry.registerResource( - PhysicsTerrainMutationQueueResource.class, - PhysicsTerrainMutationQueueResource::new)); - PhysicsTerrainPayloadResource.setResourceType(registry.registerResource( - PhysicsTerrainPayloadResource.class, - PhysicsTerrainPayloadResource::new)); + PhysicsChunkCollisionMutationQueueResource.setResourceType(registry.registerResource( + PhysicsChunkCollisionMutationQueueResource.class, + PhysicsChunkCollisionMutationQueueResource::new)); + PhysicsChunkCollisionPayloadResource.setResourceType(registry.registerResource( + PhysicsChunkCollisionPayloadResource.class, + PhysicsChunkCollisionPayloadResource::new)); PhysicsChunkSettingsIndexResource.setResourceType(registry.registerResource( PhysicsChunkSettingsIndexResource.class, PhysicsChunkSettingsIndexResource::new)); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 3419aa80..f9f12933 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1070,8 +1070,8 @@ private void clearAuthoritativePhysicsChunkTerrainStreaming(@Nonnull Store private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class), - new SystemDependency<>(Order.AFTER, TerrainMutationDrainSystem.class) + new SystemDependency<>(Order.AFTER, ChunkCollisionMutationDrainSystem.class) ); @Override @@ -57,17 +57,17 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsTerrainPayloadResource terrainPayloads = store.getResource( - PhysicsTerrainPayloadResource.getResourceType()); + PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = store.getResource( + PhysicsChunkCollisionPayloadResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindBodies(runtime, terrainPayloads, identity, restore, chunk); + (chunk, _) -> bindBodies(runtime, chunkCollisionPayloads, identity, restore, chunk); store.forEachChunk(systemIndex, collector); } private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ArchetypeChunk chunk) { @@ -85,7 +85,7 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, continue; } bindBody(runtime, - terrainPayloads, + chunkCollisionPayloads, identity, restore, bodyRef, @@ -101,7 +101,7 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, } private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Ref bodyRef, @@ -141,7 +141,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, restore.recordSoftSkip("Voxel body must be static: " + bodyUuid); return; } - bodyId = createVoxelBody(terrainPayloads, + bodyId = createVoxelBody(chunkCollisionPayloads, backendRuntime, spaceHandle, shape, @@ -212,7 +212,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, } } - private static long createVoxelBody(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, + private static long createVoxelBody(@Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull ShapeComponent shape, @@ -223,7 +223,7 @@ private static long createVoxelBody(@Nonnull PhysicsTerrainPayloadResource terra if (payloadKey.isBlank() || !backendRuntime.supportsVoxelTerrain(spaceHandle.value())) { return Long.MIN_VALUE; } - TerrainColliderPayload payload = terrainPayloads.get(payloadKey); + ChunkCollisionPayload payload = chunkCollisionPayloads.get(payloadKey); if (payload == null || !payload.hasFullCubeVoxels()) { return Long.MIN_VALUE; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java similarity index 81% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 3b4e2b31..1aa6df0b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -22,11 +22,11 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderMutation; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -53,9 +53,9 @@ import org.joml.Vector3f; /** - * Applies copied PhysicsChunk terrain mutations as runtime-only terrain body rows. + * Applies copied PhysicsChunk chunk collision mutations as runtime-only chunk collision body rows. */ -public final class TerrainMutationDrainSystem extends TickingSystem { +public final class ChunkCollisionMutationDrainSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), @@ -69,31 +69,31 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (restore.isFailed()) { return; } - PhysicsTerrainMutationQueueResource queue = store.getResource( - PhysicsTerrainMutationQueueResource.getResourceType()); - List mutations = queue.drain(); + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + List mutations = queue.drain(); if (mutations.isEmpty()) { return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource( PhysicsIdentityIndexResource.getResourceType()); - PhysicsTerrainPayloadResource terrainPayloads = store.getResource( - PhysicsTerrainPayloadResource.getResourceType()); + PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = store.getResource( + PhysicsChunkCollisionPayloadResource.getResourceType()); - applyRemovals(store, runtime, identity, terrainPayloads, mutations); - applyUpserts(store, runtime, identity, terrainPayloads, restore, mutations); + applyRemovals(store, runtime, identity, chunkCollisionPayloads, mutations); + applyUpserts(store, runtime, identity, chunkCollisionPayloads, restore, mutations); } private static void applyRemovals(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull List mutations) { - for (TerrainColliderMutation mutation : mutations) { + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + @Nonnull List mutations) { + for (ChunkCollisionMutation mutation : mutations) { if (mutation.remove()) { - removeGeneratedRows(store, runtime, identity, terrainPayloads, mutation); - removePayload(terrainPayloads, mutation.payloadResourceKey()); + removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); + removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); } } } @@ -101,12 +101,12 @@ private static void applyRemovals(@Nonnull Store store, private static void applyUpserts(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull List mutations) { - for (TerrainColliderMutation mutation : mutations) { + @Nonnull List mutations) { + for (ChunkCollisionMutation mutation : mutations) { if (!mutation.remove()) { - applyUpsert(store, runtime, identity, terrainPayloads, restore, mutation); + applyUpsert(store, runtime, identity, chunkCollisionPayloads, restore, mutation); } } } @@ -114,10 +114,10 @@ private static void applyUpserts(@Nonnull Store store, private static void applyUpsert(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull TerrainColliderMutation mutation) { - TerrainColliderPayload payload = mutation.payload(); + @Nonnull ChunkCollisionMutation mutation) { + ChunkCollisionPayload payload = mutation.payload(); if (payload == null || payload.isEmpty()) { restore.recordSoftSkip("Terrain upsert payload is missing: " + mutation.sourceKey()); return; @@ -132,8 +132,8 @@ private static void applyUpsert(@Nonnull Store store, restore.recordSoftSkip("Terrain references unbound space: " + mutation.sourceKey()); return; } - removeGeneratedRows(store, runtime, identity, terrainPayloads, mutation); - terrainPayloads.put(mutation.payloadResourceKey(), payload); + removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); + chunkCollisionPayloads.put(mutation.payloadResourceKey(), payload); boolean nativeVoxel = payload.nativeVoxelTerrainEnabled() && payload.hasFullCubeVoxels() @@ -161,8 +161,8 @@ private static void applyUpsert(@Nonnull Store store, private static void addVoxelBody(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, - @Nonnull TerrainColliderMutation mutation, - @Nonnull TerrainColliderPayload payload) { + @Nonnull ChunkCollisionMutation mutation, + @Nonnull ChunkCollisionPayload payload) { TargetComponent target = new TargetComponent(); target.setPosition(new Vector3f(mutation.chunkX() << ChunkUtil.BITS, mutation.sectionY() << ChunkUtil.BITS, @@ -190,8 +190,8 @@ private static void addVoxelBody(@Nonnull Store store, private static void addBoxBodies(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, - @Nonnull TerrainColliderMutation mutation, - @Nonnull TerrainColliderPayload payload, + @Nonnull ChunkCollisionMutation mutation, + @Nonnull ChunkCollisionPayload payload, @Nonnull List boxes, @Nonnull PartKind partKind) { for (int index = 0; index < boxes.size(); index++) { @@ -227,14 +227,14 @@ private static void addBoxBodies(@Nonnull Store store, private static void addTerrainBody(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, - @Nonnull TerrainColliderMutation mutation, + @Nonnull ChunkCollisionMutation mutation, @Nonnull TargetComponent target, @Nonnull ShapeComponent shape, @Nonnull MaterialComponent material, @Nonnull CollisionFilterComponent filter, @Nonnull PartKind partKind, int partIndex) { - UUID bodyUuid = terrainBodyUuid(mutation.spaceUuid(), + UUID bodyUuid = chunkCollisionBodyUuid(mutation.spaceUuid(), mutation.sourceKey(), partKind, partIndex); @@ -266,20 +266,20 @@ private static void addTerrainBody(@Nonnull Store store, } @Nonnull - private static MaterialComponent material(@Nonnull TerrainColliderPayload payload) { + private static MaterialComponent material(@Nonnull ChunkCollisionPayload payload) { return new MaterialComponent(payload.friction(), payload.restitution()); } @Nonnull - private static CollisionFilterComponent filter(@Nonnull TerrainColliderPayload payload) { + private static CollisionFilterComponent filter(@Nonnull ChunkCollisionPayload payload) { return new CollisionFilterComponent(payload.collisionGroup(), payload.collisionMask()); } private static void removeGeneratedRows(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource terrainPayloads, - @Nonnull TerrainColliderMutation mutation) { + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + @Nonnull ChunkCollisionMutation mutation) { List rows = collectGeneratedRows(store, mutation); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); PhysicsBodyRegistrationResource registrations = store.getResource( @@ -289,7 +289,7 @@ private static void removeGeneratedRows(@Nonnull Store store, PhysicsControlRuntimeStates.clearControlled(row.ref()); snapshots.removeBody(row.uuid()); registrations.removeBody(row.uuid()); - removePayload(terrainPayloads, row.payloadResourceKey()); + removePayload(chunkCollisionPayloads, row.payloadResourceKey()); identity.removeUuid(row.uuid(), row.ref()); if (row.ref().isValid()) { store.removeEntity(row.ref(), @@ -301,7 +301,7 @@ private static void removeGeneratedRows(@Nonnull Store store, @Nonnull private static List collectGeneratedRows(@Nonnull Store store, - @Nonnull TerrainColliderMutation mutation) { + @Nonnull ChunkCollisionMutation mutation) { ConcurrentLinkedQueue rows = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { UUID rowUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); @@ -322,7 +322,7 @@ && matchesSource(mutation, body, source)) { return new ArrayList<>(rows); } - private static boolean matchesSource(@Nonnull TerrainColliderMutation mutation, + private static boolean matchesSource(@Nonnull ChunkCollisionMutation mutation, @Nonnull BodyComponent body, @Nonnull ChunkCollisionSourceComponent source) { return mutation.spaceUuid().equals(body.getSpaceUuid()) @@ -344,15 +344,15 @@ private static void removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, runtime.removeBodyHandle(row.uuid(), row.ref()); } - private static void removePayload(@Nonnull PhysicsTerrainPayloadResource terrainPayloads, + private static void removePayload(@Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nullable String payloadResourceKey) { if (payloadResourceKey != null && !payloadResourceKey.isBlank()) { - terrainPayloads.remove(payloadResourceKey); + chunkCollisionPayloads.remove(payloadResourceKey); } } @Nonnull - static UUID terrainBodyUuid(@Nonnull UUID spaceUuid, + static UUID chunkCollisionBodyUuid(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, @Nonnull PartKind partKind, int partIndex) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java similarity index 89% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainVoxelStitchingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java index 9a43e040..4c179fe4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java @@ -17,8 +17,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; @@ -32,7 +32,7 @@ /** * Applies native voxel terrain adjacency hints for generated PhysicsChunk body rows. */ -public final class TerrainVoxelStitchingSystem extends TickingSystem +public final class ChunkCollisionVoxelStitchingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( @@ -49,8 +49,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); - PhysicsTerrainPayloadResource payloads = store.getResource( - PhysicsTerrainPayloadResource.getResourceType()); + PhysicsChunkCollisionPayloadResource payloads = store.getResource( + PhysicsChunkCollisionPayloadResource.getResourceType()); Set stitchedPairs = new ObjectOpenHashSet<>(); BiConsumer, CommandBuffer> collector = (chunk, _) -> stitchChunk(runtime, identity, payloads, restore, stitchedPairs, chunk); @@ -59,7 +59,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) private static void stitchChunk(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull PhysicsChunkCollisionPayloadResource payloads, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Set stitchedPairs, @Nonnull ArchetypeChunk chunk) { @@ -90,7 +90,7 @@ private static void stitchChunk(@Nonnull PhysicsRuntimeResource runtime, private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull PhysicsChunkCollisionPayloadResource payloads, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Set stitchedPairs, @Nonnull Ref bodyRef, @@ -107,12 +107,12 @@ private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, + source.getSourceKey()); return; } - TerrainColliderPayload payload = payloads.get(source.getPayloadResourceKey()); + ChunkCollisionPayload payload = payloads.get(source.getPayloadResourceKey()); if (payload == null) { - restore.recordSoftSkip("Voxel terrain payload is missing: " + source.getSourceKey()); + restore.recordSoftSkip("Voxel chunk collision payload is missing: " + source.getSourceKey()); return; } - for (TerrainColliderPayload.TerrainNeighbor neighbor : payload.neighbors()) { + for (ChunkCollisionPayload.Neighbor neighbor : payload.neighbors()) { stitchNeighbor(runtime, identity, backendRuntime, @@ -131,7 +131,7 @@ private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull UUID spaceUuid, @Nonnull BackendBodyHandle bodyHandle, - @Nonnull TerrainColliderPayload.TerrainNeighbor neighbor, + @Nonnull ChunkCollisionPayload.Neighbor neighbor, @Nonnull Set stitchedPairs) { Ref neighborRef = neighborRef(identity, spaceUuid, neighbor.sourceKey()); if (neighborRef == null) { @@ -160,7 +160,7 @@ private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, private static Ref neighborRef(@Nonnull PhysicsIdentityIndexResource identity, @Nonnull UUID spaceUuid, @Nonnull String sourceKey) { - UUID neighborUuid = TerrainMutationDrainSystem.terrainBodyUuid(spaceUuid, + UUID neighborUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, PartKind.VOXEL_TERRAIN, 0); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java index f5fae8cd..7131fbc8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java @@ -31,7 +31,7 @@ public final class TargetBindingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, TerrainVoxelStitchingSystem.class), + new SystemDependency<>(Order.AFTER, ChunkCollisionVoxelStitchingSystem.class), new SystemDependency<>(Order.AFTER, BodyCommandApplicationSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 7494d47a..3dee29d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -14,8 +14,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainPayloadResource; -import dev.hytalemodding.impulse.core.internal.terrain.TerrainColliderPayload; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -220,8 +220,8 @@ private static List physicsChunkSections( Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) .getByUuid(spaceUuid); - PhysicsTerrainPayloadResource payloads = store.getResource( - PhysicsTerrainPayloadResource.getResourceType()); + PhysicsChunkCollisionPayloadResource payloads = store.getResource( + PhysicsChunkCollisionPayloadResource.getResourceType()); double maxDistanceSquared = viewRadius * viewRadius; List visible = new ArrayList<>(); BiConsumer, CommandBuffer> collector = @@ -240,7 +240,7 @@ private static List physicsChunkSections( } private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk chunk, - @Nonnull PhysicsTerrainPayloadResource payloads, + @Nonnull PhysicsChunkCollisionPayloadResource payloads, @Nonnull SpaceContext spaceContext, @Nullable Ref spaceRef, @Nonnull UUID spaceUuid, @@ -260,7 +260,7 @@ private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk maxDistanceSquared) { continue; } - TerrainColliderPayload payload = payloads.get(source.getPayloadResourceKey()); + ChunkCollisionPayload payload = payloads.get(source.getPayloadResourceKey()); if (payload == null || payload.isEmpty()) { continue; } @@ -271,7 +271,7 @@ private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk boxes( - @Nonnull List payloadBoxes) { + @Nonnull List payloadBoxes) { if (payloadBoxes.isEmpty()) { return List.of(); } List boxes = new ArrayList<>(payloadBoxes.size()); - for (TerrainColliderPayload.BoxPayload box : payloadBoxes) { + for (ChunkCollisionPayload.BoxPayload box : payloadBoxes) { boxes.add(new BoxCollider(box.centerX(), box.centerY(), box.centerZ(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java index 5c739fff..85970249 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java @@ -9,7 +9,7 @@ public enum PhysicsBodyKind { TEMPORARY, TERRAIN; - public boolean isTerrainCollider() { + public boolean isTerrain() { return this == TERRAIN; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 760b840d..0327f0b9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -12,7 +12,7 @@ import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsTerrainMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; @@ -45,8 +45,8 @@ public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, store, "rebuild PhysicsChunk terrain"); PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); - PhysicsTerrainMutationQueueResource queue = checkedStore.getResource( - PhysicsTerrainMutationQueueResource.getResourceType()); + PhysicsChunkCollisionMutationQueueResource queue = checkedStore.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); PhysicsChunkTerrainPrewarmStats stats = streaming(world).ensureAround(world, settings.spaceUuid(), @@ -73,7 +73,7 @@ public static PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); return streaming(world).refreshAround(world, settings.spaceUuid(), - checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + checkedStore.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()), Objects.requireNonNull(center, "center"), radius, Math.max(0L, world.getTick()), @@ -95,7 +95,7 @@ public static PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); return streaming(world).ensureAround(world, settings.spaceUuid(), - checkedStore.getResource(PhysicsTerrainMutationQueueResource.getResourceType()), + checkedStore.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()), Objects.requireNonNull(centers, "centers"), radius, tick, @@ -190,7 +190,7 @@ private static int clearSpaceRows(@Nonnull World world, int removed = 0; if (isSubPluginEnabled()) { removed = streaming(world).clearSpace(spaceUuid, - store.getResource(PhysicsTerrainMutationQueueResource.getResourceType())); + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType())); } int directlyRemoved = PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java index 652a924e..220c7c15 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Controls PhysicsChunk terrain collider generation for a PhysicsStore space. + * Controls PhysicsChunk collision body generation for a PhysicsStore space. */ public enum PhysicsChunkTerrainMode { /** diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java index 35a9c413..2e191aae 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java @@ -12,7 +12,7 @@ import javax.annotation.Nonnull; /** - * PhysicsChunk source metadata for generated runtime-only terrain body rows. + * PhysicsChunk source metadata for generated runtime-only chunk collision body rows. */ public final class ChunkCollisionSourceComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java index 8b3e3f43..830e6fab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java @@ -11,7 +11,7 @@ public class PhysicsChunkTerrainSettings { /** - * Block radius around each tracked player for streaming terrain colliders. + * Block radius around each tracked player for streaming chunk collision bodies. */ public static final int DEFAULT_TERRAIN_RADIUS = 8; @@ -21,7 +21,7 @@ public class PhysicsChunkTerrainSettings { public static final int MAX_TERRAIN_RADIUS = 128; /** - * Block radius around each active dynamic physics body for streaming terrain colliders. + * Block radius around each active dynamic physics body for streaming chunk collision bodies. */ public static final int DEFAULT_BODY_TERRAIN_RADIUS = 4; @@ -31,7 +31,7 @@ public class PhysicsChunkTerrainSettings { public static final int MAX_BODY_TERRAIN_RADIUS = 64; /** - * Ticks before an unused section's terrain colliders are pruned. + * Ticks before an unused section's chunk collision bodies are pruned. */ public static final int DEFAULT_TERRAIN_TTL_TICKS = 100; @@ -53,12 +53,12 @@ public class PhysicsChunkTerrainSettings { public static final boolean DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED = false; /** - * Default friction applied to generated terrain collider bodies. + * Default friction applied to generated chunk collision bodies. */ public static final float DEFAULT_TERRAIN_FRICTION = 0.75f; /** - * Default restitution applied to generated terrain collider bodies. + * Default restitution applied to generated chunk collision bodies. */ public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index 75a491d1..bd746f66 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -77,7 +77,7 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer ref : PhysicsEntityAttachments.attachments(store, explosiveBodyUuid)) { @@ -97,11 +97,11 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer physicsStore, + private static boolean isTerrain(@Nonnull Store physicsStore, @Nonnull UUID bodyUuid) { PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView(physicsStore, bodyUuid); - return registration != null && registration.kind().isTerrainCollider(); + return registration != null && registration.kind().isTerrain(); } @Nonnull From 3321865a502e85cd06a57ac1aae04b44911026c7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 20:54:19 +0200 Subject: [PATCH 397/534] test(core): update chunk collision runtime guards Signed-off-by: Blovien --- ...PhysicsStoreRuntimeBoundarySourceGuardTest.java | 14 ++++---------- .../resources/PhysicsStoreResourceIndexTest.java | 7 ------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java index 105e7a24..719f4301 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java @@ -48,16 +48,15 @@ void completedStepPublicationIteratesRuntimeSpacesByRef() throws IOException { "src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java")); assertFalse(source.contains("runtime.forEachSpaceBinding")); - assertFalse(source.contains("runtime.hasTerrainBodyHandles(rowUuid)")); } @Test - void terrainNeighborStitchingDoesNotResolveRuntimeBindingsByUuid() throws IOException { + void chunkCollisionVoxelStitchingDoesNotResolveRuntimeBindingsByUuid() throws IOException { String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/systems/TerrainColliderBindingSystem.java")); + "src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java")); - assertFalse(source.contains("runtime.getTerrainVoxelBodyHandle(neighborUuid)")); - assertFalse(source.contains("runtime.getTerrainSpaceHandle(neighborUuid)")); + assertFalse(source.contains("runtime.getBodyHandle(neighborUuid)")); + assertFalse(source.contains("runtime.getBodySpaceHandle(neighborUuid)")); } @Test @@ -109,13 +108,8 @@ void runtimeResourceDoesNotExposeUuidRuntimeReadApis() throws IOException { assertFalse(source.contains("getBodySpaceHandle(@Nonnull UUID")); assertFalse(source.contains("getJointHandle(@Nonnull UUID")); assertFalse(source.contains("getJointSpaceHandle(@Nonnull UUID")); - assertFalse(source.contains("getTerrainSpaceHandle(@Nonnull UUID")); - assertFalse(source.contains("getTerrainVoxelBodyHandle(@Nonnull UUID")); - assertFalse(source.contains("hasTerrainBodyHandles(@Nonnull UUID")); - assertFalse(source.contains("forEachTerrainBodyHandle(@Nonnull UUID")); assertFalse(source.contains("bodyUuidsForSpaceHandle")); assertFalse(source.contains("jointUuidsForSpaceHandle")); - assertFalse(source.contains("terrainUuidsForSpaceHandle")); assertFalse(source.contains("forEachSpaceBinding")); assertFalse(source.contains("@Nullable Ref spaceRef")); assertFalse(source.contains("@Nullable Ref jointRef")); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index 99f6fe6c..b96175f2 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -92,33 +92,26 @@ void runtimeIndexesExposeRefsForTopologyCleanup() throws ReflectiveOperationExce UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000007"); UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000008"); UUID jointUuid = UUID.fromString("00000000-0000-0000-0000-000000000009"); - UUID terrainUuid = UUID.fromString("00000000-0000-0000-0000-00000000000a"); BackendId backendId = new BackendId("test:runtime-ref-index"); BackendSpaceHandle spaceHandle = new BackendSpaceHandle(43); BackendBodyHandle bodyHandle = new BackendBodyHandle(44L); BackendJointHandle jointHandle = new BackendJointHandle(45L); - BackendBodyHandle terrainHandle = new BackendBodyHandle(46L); Ref spaceRef = new TestRef(true); Ref bodyRef = new TestRef(true); Ref jointRef = new TestRef(true); - Ref terrainRef = new TestRef(true); runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); runtime.putJointHandle(jointRef, jointUuid, spaceHandle, jointHandle); - runtime.putTerrainBodyHandle(terrainRef, terrainUuid, spaceHandle, terrainHandle, true); assertEquals(List.of(bodyRef), refsFor(runtime, "bodyRefsForSpaceHandle", spaceHandle)); assertEquals(List.of(jointRef), refsFor(runtime, "jointRefsForSpaceHandle", spaceHandle)); - assertEquals(List.of(terrainRef), refsFor(runtime, "terrainRefsForSpaceHandle", spaceHandle)); runtime.removeBodyHandle(bodyUuid, bodyRef); runtime.removeJointHandle(jointUuid, jointRef); - runtime.removeTerrainHandles(terrainRef, terrainUuid); assertEquals(List.of(), refsFor(runtime, "bodyRefsForSpaceHandle", spaceHandle)); assertEquals(List.of(), refsFor(runtime, "jointRefsForSpaceHandle", spaceHandle)); - assertEquals(List.of(), refsFor(runtime, "terrainRefsForSpaceHandle", spaceHandle)); } @Test From 26c2c289f153803d9d8f864ebb734ecb46e73887 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 21:09:09 +0200 Subject: [PATCH 398/534] refactor(core): model chunk collision material as body material Signed-off-by: Blovien --- .../persistence/PersistentSpaceDto.java | 27 ++++-- .../PhysicsStoreSpaceMutations.java | 41 ++++++-- .../PhysicsComponentTypeRegistry.java | 18 ++-- .../PhysicsWorldRuntimeResource.java | 11 ++- .../systems/PersistenceCaptureSystem.java | 21 ++-- .../systems/PersistenceHydrationSystem.java | 12 ++- .../PhysicsChunkSettingsIndexSystem.java | 22 +++-- .../plugin/components/MaterialComponent.java | 2 +- .../components/PhysicsComponentTypes.java | 8 +- .../physicschunk/PhysicsChunkTerrain.java | 22 +++-- ...a => ChunkCollisionSettingsComponent.java} | 97 +++++-------------- .../plugin/physicsstore/PhysicsEntities.java | 12 +-- .../plugin/physicsstore/PhysicsSpaces.java | 13 ++- 13 files changed, 169 insertions(+), 137 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/{PhysicsChunkTerrainComponent.java => ChunkCollisionSettingsComponent.java} (66%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index f47044b4..a3ef464a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -9,10 +9,11 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -306,15 +307,25 @@ public float getTerrainRestitution() { } @Nonnull - public PhysicsChunkTerrainComponent getPhysicsChunkTerrain() { - return new PhysicsChunkTerrainComponent(getTerrainMode(), + public ChunkCollisionSettingsComponent getChunkCollisionSettings() { + return new ChunkCollisionSettingsComponent(getTerrainMode(), entityChunkBoundaryMode, nativeVoxelTerrainEnabled, terrainRadius, bodyTerrainRadius, - terrainTtlTicks, - terrainFriction, - terrainRestitution); + terrainTtlTicks); + } + + @Nonnull + public MaterialComponent getChunkCollisionMaterial() { + return new MaterialComponent(terrainFriction, terrainRestitution); + } + + public boolean isDefaultChunkCollisionMaterial() { + return Float.compare(terrainFriction, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION) == 0 + && Float.compare(terrainRestitution, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; } @Nonnull @@ -345,7 +356,9 @@ public ExtensionSettingsComponent getExtensionSettings() { @Nonnull public PhysicsSpaceSettings toSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - getPhysicsChunkTerrain().copyTo(settings); + getChunkCollisionSettings().copyTo(settings); + settings.getPhysicsChunkTerrainSettings() + .setTerrainMaterial(terrainFriction, terrainRestitution); solverSettings.copyTo(settings); visualSyncSettings.copyTo(settings); visualMaterializationSettings.copyTo(settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 1d3828d1..adebeb04 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -19,12 +19,14 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; @@ -147,12 +149,17 @@ public static void putSpaceSettings(@Nonnull Store store, private static void addSpaceSettingsComponents(@Nonnull Holder holder, @Nonnull PhysicsSpaceSettings settings) { - PhysicsChunkTerrainComponent terrain = - new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings()); + ChunkCollisionSettingsComponent terrain = + new ChunkCollisionSettingsComponent(settings.getPhysicsChunkTerrainSettings()); addIfNonDefault(holder, - PhysicsChunkTerrainComponent.getComponentType(), + ChunkCollisionSettingsComponent.getComponentType(), terrain, terrain.isDefault()); + MaterialComponent material = chunkMaterial(settings.getPhysicsChunkTerrainSettings()); + addIfNonDefault(holder, + MaterialComponent.getComponentType(), + material, + isDefaultChunkMaterial(material)); SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); addIfNonDefault(holder, SolverSettingsComponent.getComponentType(), @@ -198,13 +205,19 @@ private static > void addIfNonDefault( private static void putSpaceSettingsComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PhysicsSpaceSettings settings) { - PhysicsChunkTerrainComponent terrain = - new PhysicsChunkTerrainComponent(settings.getPhysicsChunkTerrainSettings()); + ChunkCollisionSettingsComponent terrain = + new ChunkCollisionSettingsComponent(settings.getPhysicsChunkTerrainSettings()); putOrRemoveDefault(store, ref, - PhysicsChunkTerrainComponent.getComponentType(), + ChunkCollisionSettingsComponent.getComponentType(), terrain, terrain.isDefault()); + MaterialComponent material = chunkMaterial(settings.getPhysicsChunkTerrainSettings()); + putOrRemoveDefault(store, + ref, + MaterialComponent.getComponentType(), + material, + isDefaultChunkMaterial(material)); SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); putOrRemoveDefault(store, ref, @@ -242,6 +255,20 @@ private static void putSpaceSettingsComponents(@Nonnull Store stor extension.isDefault()); } + @Nonnull + private static MaterialComponent chunkMaterial( + @Nonnull PhysicsChunkTerrainSettings settings) { + return new MaterialComponent(settings.getTerrainFriction(), + settings.getTerrainRestitution()); + } + + private static boolean isDefaultChunkMaterial(@Nonnull MaterialComponent material) { + return Float.compare(material.getFriction(), + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION) == 0 + && Float.compare(material.getRestitution(), + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; + } + private static > void putOrRemoveDefault( @Nonnull Store store, @Nonnull Ref ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java index 7b103eff..cc709335 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -40,7 +40,7 @@ public final class PhysicsComponentTypeRegistry { @Nullable private static ComponentType chunkCollisionSourceComponentType; @Nullable - private static ComponentType physicsChunkTerrainComponentType; + private static ComponentType chunkCollisionSettingsComponentType; @Nullable private static ComponentType dynamicsComponentType; @Nullable @@ -92,10 +92,10 @@ public static void registerComponentTypes( ChunkCollisionSourceComponent.class, "ChunkCollisionSource", ChunkCollisionSourceComponent.CODEC); - physicsChunkTerrainComponentType = registry.registerComponent( - PhysicsChunkTerrainComponent.class, - "PhysicsChunkTerrain", - PhysicsChunkTerrainComponent.CODEC); + chunkCollisionSettingsComponentType = registry.registerComponent( + ChunkCollisionSettingsComponent.class, + "ChunkCollisionSettings", + ChunkCollisionSettingsComponent.CODEC); dynamicsComponentType = registry.registerComponent( DynamicsComponent.class, "Dynamics", @@ -173,9 +173,9 @@ public static ComponentType bodyCommandCompo } @Nonnull - public static ComponentType - physicsChunkTerrainComponentType() { - return physicsChunkTerrainComponentType; + public static ComponentType + chunkCollisionSettingsComponentType() { + return chunkCollisionSettingsComponentType; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index f9f12933..ab55d2a2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -51,7 +51,7 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; @@ -259,8 +259,8 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( if (space == null) { return null; } - PhysicsChunkTerrainComponent terrainSettings = store.getComponent(ref, - PhysicsChunkTerrainComponent.getComponentType()); + ChunkCollisionSettingsComponent terrainSettings = store.getComponent(ref, + ChunkCollisionSettingsComponent.getComponentType()); SolverSettingsComponent solverSettings = store.getComponent(ref, SolverSettingsComponent.getComponentType()); VisualSyncSettingsComponent visualSyncSettings = store.getComponent(ref, @@ -275,6 +275,11 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( if (terrainSettings != null) { terrainSettings.copyTo(settings); } + MaterialComponent material = store.getComponent(ref, MaterialComponent.getComponentType()); + if (material != null) { + settings.getPhysicsChunkTerrainSettings() + .setTerrainMaterial(material.getFriction(), material.getRestitution()); + } if (solverSettings != null) { solverSettings.copyTo(settings); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index 144796a6..499ba091 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -35,7 +35,8 @@ import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -130,7 +131,8 @@ private void collectRow(@Nonnull UUID uuid, if (space != null) { spaceRows.add(new SpaceRow(uuid, space, - chunk.getComponent(index, PhysicsChunkTerrainComponent.getComponentType()), + chunk.getComponent(index, ChunkCollisionSettingsComponent.getComponentType()), + chunk.getComponent(index, MaterialComponent.getComponentType()), chunk.getComponent(index, SolverSettingsComponent.getComponentType()), chunk.getComponent(index, VisualSyncSettingsComponent.getComponentType()), chunk.getComponent(index, @@ -188,9 +190,13 @@ private PersistentSpaceDto[] spaceDtos() { @Nonnull private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { - PhysicsChunkTerrainComponent terrain = row.physicsChunkTerrain() != null + ChunkCollisionSettingsComponent terrain = row.physicsChunkTerrain() != null ? row.physicsChunkTerrain() - : new PhysicsChunkTerrainComponent(); + : new ChunkCollisionSettingsComponent(); + MaterialComponent material = row.material() != null + ? row.material() + : new MaterialComponent(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); return new PersistentSpaceDto(row.uuid(), row.space().getBackendIdValue(), row.space().getGravity(), @@ -200,8 +206,8 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { terrain.getRadius(), terrain.getBodyRadius(), terrain.getTtlTicks(), - terrain.getTerrainFriction(), - terrain.getTerrainRestitution(), + material.getFriction(), + material.getRestitution(), row.solverSettings() != null ? row.solverSettings() : new SolverSettingsComponent(), @@ -359,7 +365,8 @@ private PersistentJointDto[] jointDtos(@Nonnull Set bodyUuids) { private record SpaceRow(@Nonnull UUID uuid, @Nonnull SpaceComponent space, - @Nullable PhysicsChunkTerrainComponent physicsChunkTerrain, + @Nullable ChunkCollisionSettingsComponent physicsChunkTerrain, + @Nullable MaterialComponent material, @Nullable SolverSettingsComponent solverSettings, @Nullable VisualSyncSettingsComponent visualSyncSettings, @Nullable VisualMaterializationSettingsComponent visualMaterializationSettings, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 9248d8e8..5a9566a6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -35,7 +35,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; @@ -85,9 +85,13 @@ private static void addSpace(@Nonnull Store store, holder.addComponent(SpaceComponent.getComponentType(), new SpaceComponent(new BackendId(dto.getBackendId()), dto.getGravity())); addIfNonDefault(holder, - PhysicsChunkTerrainComponent.getComponentType(), - dto.getPhysicsChunkTerrain(), - dto.getPhysicsChunkTerrain().isDefault()); + ChunkCollisionSettingsComponent.getComponentType(), + dto.getChunkCollisionSettings(), + dto.getChunkCollisionSettings().isDefault()); + addIfNonDefault(holder, + MaterialComponent.getComponentType(), + dto.getChunkCollisionMaterial(), + dto.isDefaultChunkCollisionMaterial()); addIfNonDefault(holder, SolverSettingsComponent.getComponentType(), dto.getSolverSettings(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index d45321cc..abace79e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -12,8 +12,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; @@ -54,11 +56,13 @@ private static void collectChunk( if (PhysicsStoreSystemSupport.isNil(spaceUuid)) { continue; } - PhysicsChunkTerrainComponent terrain = chunk.getComponent(index, - PhysicsChunkTerrainComponent.getComponentType()); - PhysicsChunkTerrainComponent settings = terrain != null + ChunkCollisionSettingsComponent terrain = chunk.getComponent(index, + ChunkCollisionSettingsComponent.getComponentType()); + ChunkCollisionSettingsComponent settings = terrain != null ? terrain - : new PhysicsChunkTerrainComponent(); + : new ChunkCollisionSettingsComponent(); + MaterialComponent material = chunk.getComponent(index, + MaterialComponent.getComponentType()); settingsBySpaceUuid.put(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), @@ -66,8 +70,12 @@ private static void collectChunk( settings.getRadius(), settings.getBodyRadius(), settings.getTtlTicks(), - settings.getTerrainFriction(), - settings.getTerrainRestitution())); + material != null + ? material.getFriction() + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + material != null + ? material.getRestitution() + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION)); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java index 2b9ca9d1..65cf683f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/MaterialComponent.java @@ -11,7 +11,7 @@ import lombok.Setter; /** - * Physical material settings for one collider entity. + * Physical material settings for one physics row that authors collider material. */ @Setter @Getter diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 7b067aad..18bf2752 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -5,7 +5,7 @@ import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import javax.annotation.Nonnull; /** @@ -43,9 +43,9 @@ public static ComponentType bodyCommandCompo } @Nonnull - public static ComponentType - physicsChunkTerrainComponentType() { - return PhysicsComponentTypeRegistry.physicsChunkTerrainComponentType(); + public static ComponentType + chunkCollisionSettingsComponentType() { + return PhysicsComponentTypeRegistry.chunkCollisionSettingsComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 0327f0b9..e7d09bea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -13,7 +13,9 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Objects; @@ -157,14 +159,16 @@ private static PhysicsChunkSpaceSettings requireSettings( throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() + " is not bound yet"); } - PhysicsChunkTerrainComponent component = - store.getComponent(spaceRef, PhysicsChunkTerrainComponent.getComponentType()); - PhysicsChunkTerrainComponent settings = - component != null ? component : new PhysicsChunkTerrainComponent(); + ChunkCollisionSettingsComponent component = + store.getComponent(spaceRef, ChunkCollisionSettingsComponent.getComponentType()); + ChunkCollisionSettingsComponent settings = + component != null ? component : new ChunkCollisionSettingsComponent(); if (settings.getTerrainMode() == PhysicsChunkTerrainMode.NONE) { throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); } + MaterialComponent material = + store.getComponent(spaceRef, MaterialComponent.getComponentType()); return new PhysicsChunkSpaceSettings(spaceUuid, settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), @@ -172,8 +176,12 @@ private static PhysicsChunkSpaceSettings requireSettings( settings.getRadius(), settings.getBodyRadius(), settings.getTtlTicks(), - settings.getTerrainFriction(), - settings.getTerrainRestitution()); + material != null + ? material.getFriction() + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + material != null + ? material.getRestitution() + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java similarity index 66% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java index 697c5456..ded85a2c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/PhysicsChunkTerrainComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java @@ -16,23 +16,23 @@ import javax.annotation.Nonnull; /** - * Authored PhysicsChunk terrain streaming settings for one PhysicsStore space entity. + * Authored PhysicsChunk collision streaming settings for one PhysicsStore space entity. */ -public class PhysicsChunkTerrainComponent implements Component { +public class ChunkCollisionSettingsComponent implements Component { @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - PhysicsChunkTerrainComponent.class, - PhysicsChunkTerrainComponent::new) + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChunkCollisionSettingsComponent.class, + ChunkCollisionSettingsComponent::new) .append(new KeyedCodec<>("Mode", new EnumCodec<>(PhysicsChunkTerrainMode.class), false), (component, value) -> component.terrainMode = value != null ? value : PhysicsChunkTerrainMode.NONE, - PhysicsChunkTerrainComponent::getTerrainMode) + ChunkCollisionSettingsComponent::getTerrainMode) .add() .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), (component, value) -> component.nativeVoxelTerrainEnabled = value != null && value, - PhysicsChunkTerrainComponent::isNativeVoxelTerrainEnabled) + ChunkCollisionSettingsComponent::isNativeVoxelTerrainEnabled) .add() .append(new KeyedCodec<>("EntityChunkBoundaryMode", new EnumCodec<>(EntityChunkBoundaryMode.class), @@ -40,37 +40,25 @@ public class PhysicsChunkTerrainComponent implements Component { (component, value) -> component.entityChunkBoundaryMode = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - PhysicsChunkTerrainComponent::getEntityChunkBoundaryMode) + ChunkCollisionSettingsComponent::getEntityChunkBoundaryMode) .add() .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), (component, value) -> component.radius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, - PhysicsChunkTerrainComponent::getRadius) + ChunkCollisionSettingsComponent::getRadius) .add() .append(new KeyedCodec<>("BodyRadius", Codec.INTEGER, false), (component, value) -> component.bodyRadius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, - PhysicsChunkTerrainComponent::getBodyRadius) + ChunkCollisionSettingsComponent::getBodyRadius) .add() .append(new KeyedCodec<>("TtlTicks", Codec.INTEGER, false), (component, value) -> component.ttlTicks = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - PhysicsChunkTerrainComponent::getTtlTicks) - .add() - .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), - (component, value) -> component.terrainFriction = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsChunkTerrainComponent::getTerrainFriction) - .add() - .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), - (component, value) -> component.terrainRestitution = value != null - ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, - PhysicsChunkTerrainComponent::getTerrainRestitution) + ChunkCollisionSettingsComponent::getTtlTicks) .add() .build(); @@ -84,48 +72,38 @@ public class PhysicsChunkTerrainComponent implements Component { private int radius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; private int bodyRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; private int ttlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; - private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; - public PhysicsChunkTerrainComponent() { + public ChunkCollisionSettingsComponent() { } - public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainSettings settings) { + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainSettings settings) { this(settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelTerrainEnabled(), settings.getTerrainRadius(), settings.getBodyTerrainRadius(), - settings.getTerrainTtlTicks(), - settings.getTerrainFriction(), - settings.getTerrainRestitution()); + settings.getTerrainTtlTicks()); } - public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, boolean nativeVoxelTerrainEnabled, int radius, int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { + int ttlTicks) { this(terrainMode, PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, nativeVoxelTerrainEnabled, radius, bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); + ttlTicks); } - public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelTerrainEnabled, int radius, int bodyRadius, - int ttlTicks, - float terrainFriction, - float terrainRestitution) { + int ttlTicks) { this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, "entityChunkBoundaryMode"); @@ -133,8 +111,6 @@ public PhysicsChunkTerrainComponent(@Nonnull PhysicsChunkTerrainMode terrainMode this.radius = radius; this.bodyRadius = bodyRadius; this.ttlTicks = ttlTicks; - this.terrainFriction = terrainFriction; - this.terrainRestitution = terrainRestitution; } @Nonnull @@ -189,22 +165,6 @@ public void setTtlTicks(int ttlTicks) { this.ttlTicks = ttlTicks; } - public float getTerrainFriction() { - return terrainFriction; - } - - public void setTerrainFriction(float terrainFriction) { - this.terrainFriction = terrainFriction; - } - - public float getTerrainRestitution() { - return terrainRestitution; - } - - public void setTerrainRestitution(float terrainRestitution) { - this.terrainRestitution = terrainRestitution; - } - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { copyTo(settings.getPhysicsChunkTerrainSettings()); } @@ -216,7 +176,6 @@ public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { settings.setTerrainRadius(radius); settings.setBodyTerrainRadius(bodyRadius); settings.setTerrainTtlTicks(ttlTicks); - settings.setTerrainMaterial(terrainFriction, terrainRestitution); } public boolean isDefault() { @@ -227,28 +186,22 @@ public boolean isDefault() { == PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED && radius == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS && bodyRadius == PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS - && ttlTicks == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS - && Float.compare(terrainFriction, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION) == 0 - && Float.compare(terrainRestitution, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; + && ttlTicks == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; } @Nonnull - public static ComponentType getComponentType() { - return PhysicsComponentTypes.physicsChunkTerrainComponentType(); + public static ComponentType getComponentType() { + return PhysicsComponentTypes.chunkCollisionSettingsComponentType(); } @Nonnull @Override - public PhysicsChunkTerrainComponent clone() { - return new PhysicsChunkTerrainComponent(terrainMode, + public ChunkCollisionSettingsComponent clone() { + return new ChunkCollisionSettingsComponent(terrainMode, entityChunkBoundaryMode, nativeVoxelTerrainEnabled, radius, bodyRadius, - ttlTicks, - terrainFriction, - terrainRestitution); + ttlTicks); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index fc5de083..32b651a4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -73,7 +73,7 @@ public static Holder spaceHolder(@Nonnull Store stor public static Holder spaceHolder(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull SpaceComponent space, - @Nonnull PhysicsChunkTerrainComponent terrainSettings, + @Nonnull ChunkCollisionSettingsComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -125,14 +125,14 @@ public static Holder jointHolder(@Nonnull Store stor public static void addSpaceComponents(@Nonnull Holder holder, @Nonnull SpaceComponent space, - @Nonnull PhysicsChunkTerrainComponent terrainSettings, + @Nonnull ChunkCollisionSettingsComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { addSpaceComponent(holder, space); - holder.addComponent(PhysicsChunkTerrainComponent.getComponentType(), + holder.addComponent(ChunkCollisionSettingsComponent.getComponentType(), Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); addSpaceSettingsComponents(holder, solverSettings, @@ -191,7 +191,7 @@ public static void addBodyComponents(@Nonnull Holder holder, public static void putSpaceComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull SpaceComponent space, - @Nonnull PhysicsChunkTerrainComponent terrainSettings, + @Nonnull ChunkCollisionSettingsComponent terrainSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -204,7 +204,7 @@ public static void putSpaceComponents(@Nonnull Store store, SpaceComponent.getComponentType(), Objects.requireNonNull(space, "space").clone()); checkedStore.putComponent(ref, - PhysicsChunkTerrainComponent.getComponentType(), + ChunkCollisionSettingsComponent.getComponentType(), Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); putSpaceSettingsComponents(checkedStore, ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index b5b4ddb0..4e454826 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -14,11 +14,12 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.PhysicsChunkTerrainComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Collection; import java.util.List; @@ -137,11 +138,17 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, return null; } PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - PhysicsChunkTerrainComponent terrainSettings = checkedStore.getComponent(checkedRef, - PhysicsChunkTerrainComponent.getComponentType()); + ChunkCollisionSettingsComponent terrainSettings = checkedStore.getComponent(checkedRef, + ChunkCollisionSettingsComponent.getComponentType()); if (terrainSettings != null) { terrainSettings.copyTo(settings); } + MaterialComponent material = checkedStore.getComponent(checkedRef, + MaterialComponent.getComponentType()); + if (material != null) { + settings.getPhysicsChunkTerrainSettings() + .setTerrainMaterial(material.getFriction(), material.getRestitution()); + } SolverSettingsComponent solverSettings = checkedStore.getComponent(checkedRef, SolverSettingsComponent.getComponentType()); if (solverSettings != null) { From 289c666e8deff7996f5481485f637c8f76d9a52b Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 21:19:39 +0200 Subject: [PATCH 399/534] refactor(core): model chunk collision filter as body filter Signed-off-by: Blovien --- .../PhysicsChunkBuildOptions.java | 13 ++- .../PhysicsStoreChunkCollisionMutations.java | 5 +- .../VoxelTerrainCollisionCache.java | 9 +- .../persistence/PersistentSpaceDto.java | 82 +++++++++++++++++++ .../PhysicsChunkSettingsIndexResource.java | 8 +- .../systems/PersistenceCaptureSystem.java | 9 ++ .../systems/PersistenceHydrationSystem.java | 4 + .../PhysicsChunkSettingsIndexSystem.java | 12 ++- .../physicschunk/PhysicsChunkTerrain.java | 12 ++- 9 files changed, 139 insertions(+), 15 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index 304bacf1..e0ea12c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -9,7 +10,9 @@ */ public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisionMode, float terrainFriction, - float terrainRestitution) { + float terrainRestitution, + int collisionGroup, + int collisionMask) { public static final PhysicsChunkBuildOptions DEFAULT = fromNativeVoxelTerrainEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); @@ -29,14 +32,18 @@ public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrain return new PhysicsChunkBuildOptions( ChunkCollisionMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), settings.getTerrainFriction(), - settings.getTerrainRestitution()); + settings.getTerrainRestitution(), + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL); } @Nonnull public static PhysicsChunkBuildOptions fromNativeVoxelTerrainEnabled(boolean enabled) { return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelTerrainEnabled(enabled), PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL); } public boolean nativeVoxelTerrainEnabled() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java index d6b962db..adbe8217 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; @@ -78,8 +77,8 @@ private static ChunkCollisionPayload payload(@Nonnull SectionCollisionGeometry g buildOptions.nativeVoxelTerrainEnabled(), buildOptions.terrainFriction(), buildOptions.terrainRestitution(), - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL, + buildOptions.collisionGroup(), + buildOptions.collisionMask(), neighbors); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java index 2097b950..4799680b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; @@ -1008,8 +1007,8 @@ private static void addVoxelTerrain(@Nonnull PhysicsSpaceBinding space, chunkZ << ChunkUtil.BITS, buildOptions.terrainFriction(), buildOptions.terrainRestitution(), - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); + buildOptions.collisionGroup(), + buildOptions.collisionMask()); section.backendBodyIds.add(backendBodyId); section.voxelTerrainBodyId = backendBodyId; section.voxelTerrain = true; @@ -1046,8 +1045,8 @@ private static void addStaticBox(@Nonnull PhysicsSpaceBinding space, space.runtime() .setBodyCollisionFilter(space.backendSpaceHandle().value(), backendBodyId, - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); + buildOptions.collisionGroup(), + buildOptions.collisionMask()); } private static void applyTerrainMaterial(@Nonnull PhysicsSpaceBinding space, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index a3ef464a..cb6e4028 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -6,8 +6,10 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; @@ -94,6 +96,14 @@ public final class PersistentSpaceDto { : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, PersistentSpaceDto::getTerrainRestitution) .add() + .append(new KeyedCodec<>("ChunkCollisionFilter", + CollisionFilterComponent.CODEC, + false), + (dto, value) -> dto.chunkCollisionFilter = value != null + ? value.clone() + : defaultChunkCollisionFilter(), + PersistentSpaceDto::getChunkCollisionFilter) + .add() .append(new KeyedCodec<>("SolverSettings", SolverSettingsComponent.CODEC, false), (dto, value) -> dto.solverSettings = value != null ? value.clone() @@ -154,6 +164,8 @@ public final class PersistentSpaceDto { private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; @Nonnull + private CollisionFilterComponent chunkCollisionFilter = defaultChunkCollisionFilter(); + @Nonnull private SolverSettingsComponent solverSettings = new SolverSettingsComponent(); @Nonnull private VisualSyncSettingsComponent visualSyncSettings = new VisualSyncSettingsComponent(); @@ -183,6 +195,8 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL, new SolverSettingsComponent(), new VisualSyncSettingsComponent(), new VisualMaterializationSettingsComponent(), @@ -211,6 +225,8 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, terrainTtlTicks, terrainFriction, terrainRestitution, + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL, new SolverSettingsComponent(), new VisualSyncSettingsComponent(), new VisualMaterializationSettingsComponent(), @@ -234,6 +250,44 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { + this(spaceUuid, + backendId, + gravity, + terrainMode, + entityChunkBoundaryMode, + nativeVoxelTerrainEnabled, + terrainRadius, + bodyTerrainRadius, + terrainTtlTicks, + terrainFriction, + terrainRestitution, + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL, + solverSettings, + visualSyncSettings, + visualMaterializationSettings, + collisionLodSettings, + extensionSettings); + } + + public PersistentSpaceDto(@Nonnull UUID spaceUuid, + @Nonnull String backendId, + @Nonnull Vector3f gravity, + @Nonnull PhysicsChunkTerrainMode terrainMode, + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, + boolean nativeVoxelTerrainEnabled, + int terrainRadius, + int bodyTerrainRadius, + int terrainTtlTicks, + float terrainFriction, + float terrainRestitution, + int chunkCollisionGroup, + int chunkCollisionMask, + @Nonnull SolverSettingsComponent solverSettings, + @Nonnull VisualSyncSettingsComponent visualSyncSettings, + @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, + @Nonnull CollisionLodSettingsComponent collisionLodSettings, + @Nonnull ExtensionSettingsComponent extensionSettings) { this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); this.backendId = Objects.requireNonNull(backendId, "backendId"); this.gravity.set(Objects.requireNonNull(gravity, "gravity")); @@ -246,6 +300,8 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this.terrainTtlTicks = terrainTtlTicks; this.terrainFriction = terrainFriction; this.terrainRestitution = terrainRestitution; + this.chunkCollisionFilter = new CollisionFilterComponent(chunkCollisionGroup, + chunkCollisionMask); this.solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); this.visualSyncSettings = Objects.requireNonNull(visualSyncSettings, "visualSyncSettings").clone(); @@ -306,6 +362,14 @@ public float getTerrainRestitution() { return terrainRestitution; } + public int getChunkCollisionGroup() { + return chunkCollisionFilter.getCollisionGroup(); + } + + public int getChunkCollisionMask() { + return chunkCollisionFilter.getCollisionMask(); + } + @Nonnull public ChunkCollisionSettingsComponent getChunkCollisionSettings() { return new ChunkCollisionSettingsComponent(getTerrainMode(), @@ -328,6 +392,16 @@ public boolean isDefaultChunkCollisionMaterial() { PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; } + @Nonnull + public CollisionFilterComponent getChunkCollisionFilter() { + return chunkCollisionFilter.clone(); + } + + public boolean isDefaultChunkCollisionFilter() { + return getChunkCollisionGroup() == PhysicsCollisionFilters.TERRAIN + && getChunkCollisionMask() == PhysicsCollisionFilters.ALL; + } + @Nonnull public SolverSettingsComponent getSolverSettings() { return solverSettings.clone(); @@ -380,10 +454,18 @@ public PersistentSpaceDto copy() { terrainTtlTicks, terrainFriction, terrainRestitution, + getChunkCollisionGroup(), + getChunkCollisionMask(), solverSettings, visualSyncSettings, visualMaterializationSettings, collisionLodSettings, extensionSettings); } + + @Nonnull + private static CollisionFilterComponent defaultChunkCollisionFilter() { + return new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL); + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index df464844..bc5cab03 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -75,14 +75,18 @@ public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, int bodyRadius, int ttlTicks, float terrainFriction, - float terrainRestitution) { + float terrainRestitution, + int collisionGroup, + int collisionMask) { @Nonnull public PhysicsChunkBuildOptions buildOptions() { return new PhysicsChunkBuildOptions( ChunkCollisionMode.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled), terrainFriction, - terrainRestitution); + terrainRestitution, + collisionGroup, + collisionMask); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index 499ba091..fdb882dd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -10,6 +10,7 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyRuntimeStateDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; @@ -133,6 +134,7 @@ private void collectRow(@Nonnull UUID uuid, space, chunk.getComponent(index, ChunkCollisionSettingsComponent.getComponentType()), chunk.getComponent(index, MaterialComponent.getComponentType()), + chunk.getComponent(index, CollisionFilterComponent.getComponentType()), chunk.getComponent(index, SolverSettingsComponent.getComponentType()), chunk.getComponent(index, VisualSyncSettingsComponent.getComponentType()), chunk.getComponent(index, @@ -197,6 +199,10 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { ? row.material() : new MaterialComponent(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); + CollisionFilterComponent filter = row.filter() != null + ? row.filter() + : new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL); return new PersistentSpaceDto(row.uuid(), row.space().getBackendIdValue(), row.space().getGravity(), @@ -208,6 +214,8 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { terrain.getTtlTicks(), material.getFriction(), material.getRestitution(), + filter.getCollisionGroup(), + filter.getCollisionMask(), row.solverSettings() != null ? row.solverSettings() : new SolverSettingsComponent(), @@ -367,6 +375,7 @@ private record SpaceRow(@Nonnull UUID uuid, @Nonnull SpaceComponent space, @Nullable ChunkCollisionSettingsComponent physicsChunkTerrain, @Nullable MaterialComponent material, + @Nullable CollisionFilterComponent filter, @Nullable SolverSettingsComponent solverSettings, @Nullable VisualSyncSettingsComponent visualSyncSettings, @Nullable VisualMaterializationSettingsComponent visualMaterializationSettings, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 5a9566a6..73d0f360 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -92,6 +92,10 @@ private static void addSpace(@Nonnull Store store, MaterialComponent.getComponentType(), dto.getChunkCollisionMaterial(), dto.isDefaultChunkCollisionMaterial()); + addIfNonDefault(holder, + CollisionFilterComponent.getComponentType(), + dto.getChunkCollisionFilter(), + dto.isDefaultChunkCollisionFilter()); addIfNonDefault(holder, SolverSettingsComponent.getComponentType(), dto.getSolverSettings(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index abace79e..ade17465 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -12,6 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; @@ -63,6 +65,8 @@ private static void collectChunk( : new ChunkCollisionSettingsComponent(); MaterialComponent material = chunk.getComponent(index, MaterialComponent.getComponentType()); + CollisionFilterComponent filter = chunk.getComponent(index, + CollisionFilterComponent.getComponentType()); settingsBySpaceUuid.put(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), @@ -75,7 +79,13 @@ private static void collectChunk( : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, material != null ? material.getRestitution() - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION)); + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + filter != null + ? filter.getCollisionGroup() + : PhysicsCollisionFilters.TERRAIN, + filter != null + ? filter.getCollisionMask() + : PhysicsCollisionFilters.ALL)); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index e7d09bea..5fbfa4b6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -13,6 +13,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; @@ -169,6 +171,8 @@ private static PhysicsChunkSpaceSettings requireSettings( } MaterialComponent material = store.getComponent(spaceRef, MaterialComponent.getComponentType()); + CollisionFilterComponent filter = + store.getComponent(spaceRef, CollisionFilterComponent.getComponentType()); return new PhysicsChunkSpaceSettings(spaceUuid, settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), @@ -181,7 +185,13 @@ private static PhysicsChunkSpaceSettings requireSettings( : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, material != null ? material.getRestitution() - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); + : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + filter != null + ? filter.getCollisionGroup() + : PhysicsCollisionFilters.TERRAIN, + filter != null + ? filter.getCollisionMask() + : PhysicsCollisionFilters.ALL); } @Nonnull From 4f5907e5ee613d150917a8dad9cc168ca81f2670 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 21:22:17 +0200 Subject: [PATCH 400/534] test(core): cover chunk collision filter persistence Signed-off-by: Blovien --- .../VoxelTerrainCollisionCacheTest.java | 22 ++++++++++-- .../PersistentSpaceDtoSettingsTest.java | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java index 784a2a04..972a664f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java @@ -273,7 +273,7 @@ void supportedRuntimeCreatesVoxelTerrainAndKeepsDebugBoxes() throws Throwable { } @Test - void terrainMaterialSettingsApplyToNativeVoxelAndFallbackBoxes() throws Throwable { + void terrainMaterialAndCollisionFilterApplyToNativeVoxelAndFallbackBoxes() throws Throwable { PhysicsChunkTerrainSettings nativeSettings = new PhysicsChunkTerrainSettings(); nativeSettings.setNativeVoxelTerrainEnabled(true); nativeSettings.setTerrainMaterial(0.9f, 0.25f); @@ -293,13 +293,15 @@ void terrainMaterialSettingsApplyToNativeVoxelAndFallbackBoxes() throws Throwabl 0, 0, 0, - PhysicsChunkBuildOptions.fromSettings(nativeSettings)); + buildOptions(nativeSettings, 0x40, 0x07)); List calls = nativeFixture.runtime().voxelTerrainCalls(nativeFixture.backendSpaceId()); assertEquals(1, calls.size()); assertEquals(0.9f, calls.getFirst().friction(), 0.0001f); assertEquals(0.25f, calls.getFirst().restitution(), 0.0001f); + assertEquals(0x40, calls.getFirst().collisionGroup()); + assertEquals(0x07, calls.getFirst().collisionMask()); PhysicsChunkTerrainSettings fallbackSettings = new PhysicsChunkTerrainSettings(); fallbackSettings.setTerrainMaterial(0.8f, 0.1f); @@ -312,12 +314,14 @@ void terrainMaterialSettingsApplyToNativeVoxelAndFallbackBoxes() throws Throwabl 0, 0, 0, - PhysicsChunkBuildOptions.fromSettings(fallbackSettings)); + buildOptions(fallbackSettings, 0x20, 0x05)); long fallbackBodyId = firstBackendBodyId(fallbackSection); var snapshot = PhysicsBodySnapshots.read(fallbackFixture.binding(), fallbackBodyId); assertEquals(0.8f, snapshot.friction(), 0.0001f); assertEquals(0.1f, snapshot.restitution(), 0.0001f); + assertEquals(0x20, snapshot.collisionGroup()); + assertEquals(0x05, snapshot.collisionMask()); } @Test @@ -439,6 +443,18 @@ private static Object newCachedSection(int chunkX, int sectionY, int chunkZ) thr return constructor.newInstance(chunkX, sectionY, chunkZ, 0L, 1L); } + @Nonnull + private static PhysicsChunkBuildOptions buildOptions(@Nonnull PhysicsChunkTerrainSettings settings, + int collisionGroup, + int collisionMask) { + return new PhysicsChunkBuildOptions( + ChunkCollisionMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), + settings.getTerrainFriction(), + settings.getTerrainRestitution(), + collisionGroup, + collisionMask); + } + private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, @Nonnull Object target, @Nonnull SectionCollisionGeometry geometry, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index 956f127d..a27ab324 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -70,6 +70,40 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertDetachedVisualCadence(copied, 7, 9, 11); } + @Test + void roundTripPreservesChunkCollisionFilter() { + PhysicsChunkTerrainSettings terrain = + PhysicsSpaceSettings.defaults().getPhysicsChunkTerrainSettings(); + PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), + "test:chunk-filter-persistence", + new Vector3f(0.0f, -9.81f, 0.0f), + terrain.getTerrainMode(), + terrain.getEntityChunkBoundaryMode(), + false, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, + PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + 0x40, + 0x03, + new SolverSettingsComponent(), + new VisualSyncSettingsComponent(), + new VisualMaterializationSettingsComponent(), + new CollisionLodSettingsComponent(), + new ExtensionSettingsComponent()); + + BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); + + assertTrue(encoded.containsKey("ChunkCollisionFilter")); + PersistentSpaceDto decoded = Objects.requireNonNull( + PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())); + assertEquals(0x40, decoded.getChunkCollisionGroup()); + assertEquals(0x03, decoded.getChunkCollisionMask()); + assertEquals(0x40, state.copy().getChunkCollisionGroup()); + assertEquals(0x03, state.copy().getChunkCollisionMask()); + } + private static void assertDetachedVisualCadence(PhysicsSpaceSettings settings, int interestInterval, int candidateInterval, From 8c357a770b77a5d04fbbecf3f9332f58e6de78de Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 21:34:06 +0200 Subject: [PATCH 401/534] test(core): exclude chunk collision rows from persistence Signed-off-by: Blovien --- .../systems/PersistenceCaptureSystemTest.java | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java new file mode 100644 index 00000000..b854920d --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java @@ -0,0 +1,253 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentMaterialDto; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PersistenceCaptureSystemTest { + + @Test + void generatedChunkCollisionRowsAreExcludedFromPersistentDtoTables() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physics-capture-runtime-only-terrain-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(1); + UUID persistentBodyUuid = uuid(2); + UUID generatedBodyUuid = uuid(3); + Ref spaceRef = addSpace(store, spaceUuid); + addBody(store, + persistentBodyUuid, + body(spaceUuid, PhysicsBodyKind.BODY, PhysicsBodyPersistenceMode.PERSISTENT, spaceRef), + null); + Ref generatedRef = addBody(store, + generatedBodyUuid, + body(spaceUuid, PhysicsBodyKind.TERRAIN, PhysicsBodyPersistenceMode.RUNTIME_ONLY, spaceRef), + new ChunkCollisionSourceComponent("0:0:0", + 0, + 0, + 0, + "chunk-collision/0/0/0", + PartKind.BOX, + 0)); + + BodyComponent generatedBody = store.getComponent(generatedRef, + BodyComponent.getComponentType()); + assertNotNull(generatedBody); + assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, + generatedBody.getPersistenceMode()); + assertNotNull(store.getComponent(generatedRef, + ChunkCollisionSourceComponent.getComponentType())); + + capturePersistence(store); + + PersistentPhysicsStoreResource persistent = store.getResource( + PersistentPhysicsStoreResource.getResourceType()); + assertEquals(1, persistent.getSpaces().length); + assertEquals(1, persistent.getBodies().length); + assertEquals(1, persistent.getColliders().length); + assertEquals(1, persistent.getShapes().length); + assertEquals(1, persistent.getMaterials().length); + assertTrue(containsBody(persistent, persistentBodyUuid)); + assertFalse(containsBody(persistent, generatedBodyUuid)); + assertFalse(containsCollider(persistent, generatedBodyUuid)); + assertFalse(containsShape(persistent, generatedBodyUuid)); + assertFalse(containsMaterial(persistent, generatedBodyUuid)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static Ref addSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(new BackendId("test:persistence-capture"), + new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(ref); + return ref; + } + + private static void capturePersistence(@Nonnull Store store) { + try { + // Full system registration pulls in unrelated backend-binding dependencies. + Class captureType = Arrays.stream(PersistenceCaptureSystem.class.getDeclaredClasses()) + .filter(candidate -> candidate.getSimpleName().equals("Capture")) + .findFirst() + .orElseThrow(); + Constructor constructor = captureType.getDeclaredConstructor(Map.class); + constructor.setAccessible(true); + Object capture = constructor.newInstance(Map.of()); + Method collectChunk = captureType.getDeclaredMethod("collectChunk", + ArchetypeChunk.class); + collectChunk.setAccessible(true); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> invoke(collectChunk, capture, chunk); + store.forEachChunk(new PersistenceCaptureSystem().getQuery(), collector); + Method writeTo = captureType.getDeclaredMethod("writeTo", + PersistentPhysicsStoreResource.class); + writeTo.setAccessible(true); + invoke(writeTo, + capture, + store.getResource(PersistentPhysicsStoreResource.getResourceType())); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Could not run PersistenceCaptureSystem capture", exception); + } + } + + private static void invoke(@Nonnull Method method, + @Nonnull Object target, + @Nonnull Object argument) { + try { + method.invoke(target, argument); + } catch (IllegalAccessException exception) { + throw new AssertionError(exception); + } catch (InvocationTargetException exception) { + Throwable cause = exception.getCause(); + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + if (cause instanceof Error error) { + throw error; + } + throw new AssertionError(cause); + } + } + + @Nonnull + private static Ref addBody(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull BodyComponent body, + ChunkCollisionSourceComponent source) { + Holder holder = PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.STATIC, 0.0f, 0.0f, 0.0f, false), + target(), + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.6f, 0.1f), + new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL)); + if (source != null) { + holder.addComponent(ChunkCollisionSourceComponent.getComponentType(), source); + } + Ref ref = store.addEntity(holder, AddReason.SPAWN); + assertNotNull(ref); + return ref; + } + + @Nonnull + private static BodyComponent body(@Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyKind kind, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Ref spaceRef) { + BodyComponent body = new BodyComponent(spaceUuid, kind, persistenceMode); + body.setSpaceRef(spaceRef); + return body; + } + + @Nonnull + private static TargetComponent target() { + TargetComponent target = new TargetComponent(); + target.setPosition(new Vector3f(1.0f, 2.0f, 3.0f)); + return target; + } + + private static boolean containsBody(@Nonnull PersistentPhysicsStoreResource persistent, + @Nonnull UUID bodyUuid) { + return Arrays.stream(persistent.getBodies()) + .anyMatch(body -> bodyUuid.equals(body.getBodyUuid())); + } + + private static boolean containsCollider(@Nonnull PersistentPhysicsStoreResource persistent, + @Nonnull UUID colliderUuid) { + return Arrays.stream(persistent.getColliders()) + .map(PersistentColliderDto::getColliderUuid) + .anyMatch(colliderUuid::equals); + } + + private static boolean containsShape(@Nonnull PersistentPhysicsStoreResource persistent, + @Nonnull UUID shapeUuid) { + return Arrays.stream(persistent.getShapes()) + .map(PersistentShapeDto::getShapeUuid) + .anyMatch(shapeUuid::equals); + } + + private static boolean containsMaterial(@Nonnull PersistentPhysicsStoreResource persistent, + @Nonnull UUID materialUuid) { + return Arrays.stream(persistent.getMaterials()) + .map(PersistentMaterialDto::getMaterialUuid) + .anyMatch(materialUuid::equals); + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } +} From 9ec4f010e8c99efd08ce2d409611107b7354b268 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 21:41:52 +0200 Subject: [PATCH 402/534] test(core): cover chunk collision row generation Signed-off-by: Blovien --- ...ChunkCollisionMutationDrainSystemTest.java | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java new file mode 100644 index 00000000..2ce705d5 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -0,0 +1,248 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class ChunkCollisionMutationDrainSystemTest { + + private static final float DELTA = 0.000001f; + + @Test + void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-row-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(1); + BackendId backendId = new BackendId("test:chunk-collision-drain"); + Ref spaceRef = addBoundSpace(store, spaceUuid, backendId); + String sourceKey = "0:1:2"; + String payloadKey = "chunk-collision/0/1/2"; + BoxPayload fullCubeBox = new BoxPayload(10.0, 20.0, 30.0, 1.5, 2.5, 3.5); + BoxPayload detailBox = new BoxPayload(40.0, 50.0, 60.0, 0.25, 0.5, 0.75); + ChunkCollisionPayload payload = new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(fullCubeBox), + List.of(detailBox), + false, + 0.82f, + 0.18f, + 0x40, + 0x07, + List.of()); + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 0, + 1, + 2, + payloadKey, + payload)); + + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertSame(payload, + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); + assertSoftSkipsEmpty(store); + assertGeneratedBox(store, + spaceUuid, + spaceRef, + sourceKey, + payloadKey, + PartKind.BOX, + 0, + fullCubeBox, + payload); + assertGeneratedBox(store, + spaceUuid, + spaceRef, + sourceKey, + payloadKey, + PartKind.DETAIL_BOX, + 0, + detailBox, + payload); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static Ref addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + + PhysicsBackendRuntime backendRuntime = + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + int spaceHandle = backendRuntime.createSpace(new SpaceId(42)); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putRuntime(backendId, backendRuntime); + runtime.putSpaceBinding(spaceUuid, + spaceRef, + backendId, + new BackendSpaceHandle(spaceHandle)); + return spaceRef; + } + + private static void assertGeneratedBox(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull String sourceKey, + @Nonnull String payloadKey, + @Nonnull PartKind partKind, + int partIndex, + @Nonnull BoxPayload box, + @Nonnull ChunkCollisionPayload payload) { + UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + partKind, + partIndex); + Ref bodyRef = store + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(bodyUuid); + assertNotNull(bodyRef); + assertEquals(bodyUuid, + store.getComponent(bodyRef, UuidComponent.getComponentType()).getUuid()); + + BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); + assertNotNull(body); + assertEquals(spaceUuid, body.getSpaceUuid()); + assertNotNull(body.getSpaceRef()); + assertEquals(spaceRef.getIndex(), body.getSpaceRef().getIndex()); + assertEquals(PhysicsBodyKind.TERRAIN, body.getKind()); + assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, body.getPersistenceMode()); + + DynamicsComponent dynamics = store.getComponent(bodyRef, + DynamicsComponent.getComponentType()); + assertNotNull(dynamics); + assertEquals(PhysicsBodyType.STATIC, dynamics.getBodyType()); + assertEquals(0.0f, dynamics.getMass(), DELTA); + assertFalse(dynamics.isContinuousCollisionEnabled()); + + TargetComponent target = store.getComponent(bodyRef, TargetComponent.getComponentType()); + assertNotNull(target); + assertVectorEquals((float) box.centerX(), + (float) box.centerY(), + (float) box.centerZ(), + target.getPosition()); + + ShapeComponent shape = store.getComponent(bodyRef, ShapeComponent.getComponentType()); + assertNotNull(shape); + assertEquals(ShapeType.BOX, shape.getShapeType()); + assertEquals((float) box.halfX(), shape.getHalfExtentX(), DELTA); + assertEquals((float) box.halfY(), shape.getHalfExtentY(), DELTA); + assertEquals((float) box.halfZ(), shape.getHalfExtentZ(), DELTA); + assertEquals("", shape.getResourceKey()); + + MaterialComponent material = store.getComponent(bodyRef, + MaterialComponent.getComponentType()); + assertNotNull(material); + assertEquals(payload.friction(), material.getFriction(), DELTA); + assertEquals(payload.restitution(), material.getRestitution(), DELTA); + + CollisionFilterComponent filter = store.getComponent(bodyRef, + CollisionFilterComponent.getComponentType()); + assertNotNull(filter); + assertEquals(payload.collisionGroup(), filter.getCollisionGroup()); + assertEquals(payload.collisionMask(), filter.getCollisionMask()); + + ChunkCollisionSourceComponent source = store.getComponent(bodyRef, + ChunkCollisionSourceComponent.getComponentType()); + assertNotNull(source); + assertEquals(sourceKey, source.getSourceKey()); + assertEquals(0, source.getChunkX()); + assertEquals(1, source.getSectionY()); + assertEquals(2, source.getChunkZ()); + assertEquals(payloadKey, source.getPayloadResourceKey()); + assertEquals(partKind, source.getPartKind()); + assertEquals(partIndex, source.getPartIndex()); + } + + private static void assertVectorEquals(float expectedX, + float expectedY, + float expectedZ, + @Nonnull Vector3f actual) { + assertEquals(expectedX, actual.x, DELTA); + assertEquals(expectedY, actual.y, DELTA); + assertEquals(expectedZ, actual.z, DELTA); + } + + private static void assertSoftSkipsEmpty(@Nonnull Store store) { + assertEquals(0, + store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .getSoftSkipsByReason() + .size()); + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } +} From f360228bf8e0400c498d7418a118913f3b00e133 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 21:53:30 +0200 Subject: [PATCH 403/534] refactor(core): extract physics chunk terrain helpers Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkBuildStats.java | 70 +++++++ .../PhysicsChunkMutationCache.java | 54 +++-- .../PhysicsChunkSectionAccessCache.java | 67 +++++++ .../PhysicsChunkTerrainRuntime.java | 185 ------------------ .../PhysicsChunkTerrainStreamingResource.java | 33 +++- .../physicschunk/SectionBlockReader.java | 4 +- .../physicschunk/SectionColliderBuilder.java | 4 +- .../VoxelTerrainCollisionCache.java | 155 +++------------ .../PhysicsChunkProfilingResource.java | 4 +- .../PhysicsChunkTerrainProducerSystem.java | 6 +- .../PhysicsWorldRuntimeResource.java | 45 ----- .../PhysicsChunkProfilingResourceTest.java | 4 +- 12 files changed, 241 insertions(+), 390 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSectionAccessCache.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java new file mode 100644 index 00000000..c9a243d8 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java @@ -0,0 +1,70 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import javax.annotation.Nonnull; + +/** + * Aggregate statistics from a PhysicsChunk terrain build or rebuild operation. + */ +public record PhysicsChunkBuildStats(int scannedBlocks, + int solidBlocks, + int culledInteriorBlocks, + int fullCubeRuns, + int detailBoxes, + int colliderBodies, + int removedBodies, + int sectionsBuilt, + int sectionsRebuilt, + int voxelBodies) { + + @Nonnull + public static PhysicsChunkBuildStats empty() { + return new PhysicsChunkBuildStats(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + @Nonnull + static PhysicsChunkBuildStats from(@Nonnull SectionCollisionGeometry geometry, + int colliderBodies, + int removedBodies, + int sectionsBuilt, + int sectionsRebuilt, + int voxelBodies) { + return new PhysicsChunkBuildStats(geometry.scannedBlocks(), + geometry.solidBlocks(), + geometry.culledInteriorBlocks(), + geometry.mergedFullCubeBoxes().size(), + geometry.detailBoxCount(), + colliderBodies, + removedBodies, + sectionsBuilt, + sectionsRebuilt, + voxelBodies); + } + + @Nonnull + public PhysicsChunkBuildStats plus(@Nonnull PhysicsChunkBuildStats stats) { + return new PhysicsChunkBuildStats(scannedBlocks + stats.scannedBlocks, + solidBlocks + stats.solidBlocks, + culledInteriorBlocks + stats.culledInteriorBlocks, + fullCubeRuns + stats.fullCubeRuns, + detailBoxes + stats.detailBoxes, + colliderBodies + stats.colliderBodies, + removedBodies + stats.removedBodies, + sectionsBuilt + stats.sectionsBuilt, + sectionsRebuilt + stats.sectionsRebuilt, + voxelBodies + stats.voxelBodies); + } + + @Nonnull + PhysicsChunkBuildStats withRemovedBodies(int removedBodies) { + return new PhysicsChunkBuildStats(scannedBlocks, + solidBlocks, + culledInteriorBlocks, + fullCubeRuns, + detailBoxes, + colliderBodies, + removedBodies, + sectionsBuilt, + sectionsRebuilt, + voxelBodies); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java index d5c8a9df..8893b9ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java @@ -49,7 +49,7 @@ public final class PhysicsChunkMutationCache { private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); @Nonnull - public synchronized VoxelTerrainCollisionCache.BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Vector3d center, @@ -58,6 +58,7 @@ public synchronized VoxelTerrainCollisionCache.BuildStats ensureAround(@Nonnull @Nullable Snapshot profiling, @Nullable LongSet visitedSections, @Nullable StreamingTargetDiagnostic targetDiagnostic, + @Nullable PhysicsChunkSectionAccessCache accessCache, @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { @@ -78,7 +79,7 @@ public synchronized VoxelTerrainCollisionCache.BuildStats ensureAround(@Nonnull int minChunkZ = ChunkUtil.chunkCoordinate(minZ); int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); - VoxelTerrainCollisionCache.BuildStats total = VoxelTerrainCollisionCache.BuildStats.empty(); + PhysicsChunkBuildStats total = PhysicsChunkBuildStats.empty(); for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { @@ -98,6 +99,7 @@ public synchronized VoxelTerrainCollisionCache.BuildStats ensureAround(@Nonnull tick, profiling, targetDiagnostic, + accessCache, buildOptions)); } } @@ -147,6 +149,14 @@ public synchronized int pruneUnloaded(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nullable Snapshot profiling) { + return pruneUnloaded(world, spaceUuid, queue, profiling, null); + } + + public synchronized int pruneUnloaded(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, + @Nullable Snapshot profiling, + @Nullable PhysicsChunkSectionAccessCache accessCache) { long start = profiling != null ? System.nanoTime() : 0L; SpaceCollisionCache cache = spaces.get(spaceUuid); if (cache == null) { @@ -159,7 +169,7 @@ public synchronized int pruneUnloaded(@Nonnull World world, cache.sections.long2ObjectEntrySet().iterator(); while (iterator.hasNext()) { CachedSection section = iterator.next().getValue(); - if (blockChunk(world, section.chunkX, section.chunkZ) != null) { + if (blockChunk(world, section.chunkX, section.chunkZ, accessCache) != null) { continue; } removedBodies += removeSection(spaceUuid, queue, section); @@ -465,7 +475,7 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull UUID spaceUuid, } @Nonnull - private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world, + private PhysicsChunkBuildStats ensureSection(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, int chunkX, @@ -474,6 +484,7 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world long tick, @Nullable Snapshot profiling, @Nullable StreamingTargetDiagnostic targetDiagnostic, + @Nullable PhysicsChunkSectionAccessCache accessCache, @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { @@ -491,9 +502,9 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world chunkZ, targetDiagnostic, start); - return VoxelTerrainCollisionCache.BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } - if (blockChunk(world, chunkX, chunkZ) == null) { + if (blockChunk(world, chunkX, chunkZ, accessCache) == null) { cache.missingBlockChunkBackoffs.put(chunkKey, tick + MISSING_BLOCK_CHUNK_RETRY_TICKS); recordMissing(profiling, MissingSectionReason.BLOCK_CHUNK, @@ -502,7 +513,7 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world chunkZ, targetDiagnostic, start); - return VoxelTerrainCollisionCache.BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } cache.missingBlockChunkBackoffs.remove(chunkKey); @@ -514,9 +525,11 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world chunkZ, targetDiagnostic, start); - return VoxelTerrainCollisionCache.BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } - BlockSection section = ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); + BlockSection section = accessCache != null + ? accessCache.blockSection(world, chunkX, sectionY, chunkZ) + : ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); if (section == null) { cache.missingBlockSectionBackoffs.put(sectionKey, tick + MISSING_BLOCK_SECTION_RETRY_TICKS); @@ -527,7 +540,7 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world chunkZ, targetDiagnostic, start); - return VoxelTerrainCollisionCache.BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } cache.missingBlockSectionBackoffs.remove(sectionKey); @@ -535,7 +548,8 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world section, chunkX, sectionY, - chunkZ); + chunkZ, + accessCache); CachedSection cached = cache.sections.get(sectionKey); if (cached != null && cached.neighborhoodSignature == neighborhoodSignature @@ -545,14 +559,15 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world profiling.incrementSectionCacheHits(); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return VoxelTerrainCollisionCache.BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } SectionCollisionGeometry geometry = sectionBuilder.build(world, section, chunkX, sectionY, - chunkZ); + chunkZ, + accessCache); CachedSection built = new CachedSection(chunkX, sectionY, chunkZ, @@ -572,7 +587,7 @@ private VoxelTerrainCollisionCache.BuildStats ensureSection(@Nonnull World world buildOptions)); } cache.sections.put(sectionKey, built); - VoxelTerrainCollisionCache.BuildStats stats = new VoxelTerrainCollisionCache.BuildStats( + PhysicsChunkBuildStats stats = new PhysicsChunkBuildStats( geometry.scannedBlocks(), geometry.solidBlocks(), geometry.culledInteriorBlocks(), @@ -737,6 +752,17 @@ private static long packSectionKey(int chunkX, int sectionY, int chunkZ) { @Nullable private static BlockChunk blockChunk(@Nonnull World world, int chunkX, int chunkZ) { + return blockChunk(world, chunkX, chunkZ, null); + } + + @Nullable + private static BlockChunk blockChunk(@Nonnull World world, + int chunkX, + int chunkZ, + @Nullable PhysicsChunkSectionAccessCache accessCache) { + if (accessCache != null) { + return accessCache.blockChunk(world, chunkX, chunkZ); + } Ref chunkRef = world.getChunkStore() .getChunkReference(ChunkUtil.indexChunk(chunkX, chunkZ)); if (chunkRef == null || !chunkRef.isValid()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSectionAccessCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSectionAccessCache.java new file mode 100644 index 00000000..d5d837a1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSectionAccessCache.java @@ -0,0 +1,67 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Per-streaming-pass source-section cache for repeated Hytale chunk lookups. + */ +public final class PhysicsChunkSectionAccessCache { + + private final Long2ObjectMap blockChunks = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectMap blockSections = new Long2ObjectOpenHashMap<>(); + + public PhysicsChunkSectionAccessCache() { + } + + @Nullable + BlockChunk blockChunk(@Nonnull World world, int chunkX, int chunkZ) { + long key = ChunkUtil.indexChunk(chunkX, chunkZ); + if (blockChunks.containsKey(key)) { + return blockChunks.get(key); + } + + BlockChunk chunk = loadBlockChunk(world, chunkX, chunkZ); + blockChunks.put(key, chunk); + return chunk; + } + + @Nullable + BlockSection blockSection(@Nonnull World world, int chunkX, int sectionY, int chunkZ) { + long key = packSectionKey(chunkX, sectionY, chunkZ); + if (blockSections.containsKey(key)) { + return blockSections.get(key); + } + + BlockSection section = ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); + blockSections.put(key, section); + return section; + } + + @Nullable + private static BlockChunk loadBlockChunk(@Nonnull World world, int chunkX, int chunkZ) { + Ref chunkRef = world.getChunkStore() + .getChunkReference(ChunkUtil.indexChunk(chunkX, chunkZ)); + if (chunkRef == null || !chunkRef.isValid()) { + return null; + } + Store store = world.getChunkStore().getStore(); + return store.getComponentConcurrent(chunkRef, BlockChunk.getComponentType()); + } + + private static long packSectionKey(int chunkX, int sectionY, int chunkZ) { + long x = ((long) chunkX & 0x3FFFFFL) << 42; + long y = ((long) sectionY & 0x3FFL) << 32; + long z = (long) chunkZ & 0xFFFFFFFFL; + return x | y | z; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java deleted file mode 100644 index c401b8d3..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainRuntime.java +++ /dev/null @@ -1,185 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk; - -import com.hypixel.hytale.server.core.universe.world.World; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; -import it.unimi.dsi.fastutil.ints.Int2LongMap; -import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3d; - -/** - * PhysicsChunk terrain runtime state for one physics world. - */ -public final class PhysicsChunkTerrainRuntime { - - private final VoxelTerrainCollisionCache voxelTerrainCache = new VoxelTerrainCollisionCache(); - private final Int2LongMap streamingRevisions = new Int2LongOpenHashMap(); - - @Nonnull - public VoxelTerrainCollisionCache voxelTerrainCache() { - return voxelTerrainCache; - } - - public synchronized void registerSpace(@Nonnull SpaceId spaceId) { - streamingRevisions.putIfAbsent(spaceId.value(), 1L); - } - - public synchronized void unregisterSpace(@Nonnull SpaceId spaceId) { - streamingRevisions.remove(spaceId.value()); - } - - public synchronized long streamingRevision(@Nonnull SpaceId spaceId) { - return streamingRevisions.getOrDefault(spaceId.value(), 0L); - } - - public synchronized long incrementStreamingRevision(@Nonnull SpaceId spaceId) { - long revision = streamingRevisions.getOrDefault(spaceId.value(), 0L) + 1L; - streamingRevisions.put(spaceId.value(), revision); - return revision; - } - - @Nonnull - public PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius) { - return rebuildAround(world, - space, - center, - radius, - PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled( - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); - } - - @Nonnull - public PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - return terrainStats(voxelTerrainCache.rebuildAround(world, - space, - center, - radius, - buildOptions)); - } - - @Nonnull - public PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - VoxelTerrainCollisionCache.BuildStats stats = voxelTerrainCache.refreshAround(world, - space, - center, - radius, - buildOptions); - if (stats.removedBodies() > 0) { - incrementStreamingRevision(space.spaceId()); - } - return terrainStats(stats); - } - - @Nonnull - public PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Iterable centers, - int radius, - long tick) { - return ensureAround(world, - space, - centers, - radius, - tick, - PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled( - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED)); - } - - @Nonnull - public PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Iterable centers, - int radius, - long tick, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - Objects.requireNonNull(centers, "centers"); - LongSet visitedSections = new LongOpenHashSet(); - VoxelTerrainCollisionCache.BuildStats total = VoxelTerrainCollisionCache.BuildStats.empty(); - for (Vector3d center : centers) { - total = total.plus(voxelTerrainCache.ensureAround(world, - space, - center, - radius, - tick, - null, - visitedSections, - null, - null, - buildOptions)); - } - return new PhysicsChunkTerrainPrewarmStats(visitedSections.size(), - terrainStats(total)); - } - - public int clear(@Nonnull PhysicsSpaceBinding space) { - incrementStreamingRevision(space.spaceId()); - return voxelTerrainCache.clear(space); - } - - public void clear(@Nonnull SpaceId spaceId, @Nullable PhysicsSpaceBinding space) { - voxelTerrainCache.clear(spaceId, space); - unregisterSpace(spaceId); - } - - public synchronized void clearAll() { - voxelTerrainCache.copyFrom(new VoxelTerrainCollisionCache()); - for (int spaceId : streamingRevisions.keySet().toIntArray()) { - streamingRevisions.put(spaceId, streamingRevisions.get(spaceId) + 1L); - } - } - - public void clearRetainedTerrain(@Nonnull Iterable spaces) { - for (PhysicsSpaceBinding space : spaces) { - clear(space); - } - voxelTerrainCache.finishStreamingApply(); - } - - public synchronized void clearAllAndUnregisterSpaces() { - voxelTerrainCache.copyFrom(new VoxelTerrainCollisionCache()); - streamingRevisions.clear(); - } - - @Nonnull - public PhysicsChunkTerrainStats getStats() { - return new PhysicsChunkTerrainStats(voxelTerrainCache.spaceCount(), - voxelTerrainCache.sectionCount(), - voxelTerrainCache.bodyCount(), - voxelTerrainCache.shapeTemplateCount()); - } - - @Nonnull - private static PhysicsChunkTerrainBuildStats terrainStats( - @Nonnull VoxelTerrainCollisionCache.BuildStats stats) { - return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), - stats.solidBlocks(), - stats.culledInteriorBlocks(), - stats.fullCubeRuns(), - stats.detailBoxes(), - stats.colliderBodies(), - stats.removedBodies(), - stats.sectionsBuilt(), - stats.sectionsRebuilt(), - stats.voxelBodies()); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java index 1a66db73..3de0e9fc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelTerrainCollisionCache.BuildStats; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; @@ -74,7 +73,8 @@ public synchronized PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World @Nullable Snapshot profiling, @Nonnull PhysicsChunkBuildOptions buildOptions) { LongSet visitedSections = new LongOpenHashSet(); - BuildStats total = BuildStats.empty(); + PhysicsChunkBuildStats total = PhysicsChunkBuildStats.empty(); + PhysicsChunkSectionAccessCache accessCache = new PhysicsChunkSectionAccessCache(); for (Vector3d center : centers) { total = total.plus(ensureAround(world, spaceUuid, @@ -85,6 +85,7 @@ public synchronized PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World profiling, visitedSections, null, + accessCache, buildOptions)); } return new PhysicsChunkTerrainPrewarmStats(visitedSections.size(), terrainStats(total)); @@ -100,7 +101,8 @@ public synchronized PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World w @Nullable Snapshot profiling, @Nonnull PhysicsChunkBuildOptions buildOptions) { int removed = cache.clearSectionsAround(spaceUuid, queue, center, radius); - BuildStats stats = ensureAround(world, + PhysicsChunkSectionAccessCache accessCache = new PhysicsChunkSectionAccessCache(); + PhysicsChunkBuildStats stats = ensureAround(world, spaceUuid, queue, center, @@ -109,12 +111,13 @@ public synchronized PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World w profiling, null, null, + accessCache, buildOptions); return terrainStats(withRemovedBodies(stats, stats.removedBodies() + removed)); } @Nonnull - public synchronized BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Vector3d center, @@ -123,6 +126,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, @Nullable Snapshot profiling, @Nullable LongSet visitedSections, @Nullable StreamingTargetDiagnostic targetDiagnostic, + @Nullable PhysicsChunkSectionAccessCache accessCache, @Nonnull PhysicsChunkBuildOptions buildOptions) { return cache.ensureAround(world, spaceUuid, @@ -133,6 +137,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, profiling, visitedSections, targetDiagnostic, + accessCache, buildOptions); } @@ -197,7 +202,19 @@ public synchronized int pruneUnloaded(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nullable Snapshot profiling) { - return cache.pruneUnloaded(world, spaceUuid, queue, profiling); + return pruneUnloaded(world, + spaceUuid, + queue, + profiling, + new PhysicsChunkSectionAccessCache()); + } + + public synchronized int pruneUnloaded(@Nonnull World world, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsChunkCollisionMutationQueueResource queue, + @Nullable Snapshot profiling, + @Nullable PhysicsChunkSectionAccessCache accessCache) { + return cache.pruneUnloaded(world, spaceUuid, queue, profiling, accessCache); } public synchronized int pruneUnused(@Nonnull UUID spaceUuid, @@ -235,7 +252,7 @@ public synchronized PhysicsChunkTerrainStreamingResource clone() { } @Nonnull - private static PhysicsChunkTerrainBuildStats terrainStats(@Nonnull BuildStats stats) { + private static PhysicsChunkTerrainBuildStats terrainStats(@Nonnull PhysicsChunkBuildStats stats) { return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), @@ -249,8 +266,8 @@ private static PhysicsChunkTerrainBuildStats terrainStats(@Nonnull BuildStats st } @Nonnull - private static BuildStats withRemovedBodies(@Nonnull BuildStats stats, int removedBodies) { - return new BuildStats(stats.scannedBlocks(), + private static PhysicsChunkBuildStats withRemovedBodies(@Nonnull PhysicsChunkBuildStats stats, int removedBodies) { + return new PhysicsChunkBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), stats.fullCubeRuns(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java index b656b341..956baf3a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionBlockReader.java @@ -25,7 +25,7 @@ final class SectionBlockReader { private final int baseZ; private final Long2ObjectMap sectionCache = new Long2ObjectOpenHashMap<>(); @Nullable - private final VoxelTerrainCollisionCache.SectionAccessCache accessCache; + private final PhysicsChunkSectionAccessCache accessCache; SectionBlockReader(@Nonnull World world, @Nonnull ShapeTemplateCache templates, @@ -42,7 +42,7 @@ final class SectionBlockReader { int currentChunkX, int currentSectionY, int currentChunkZ, - @Nullable VoxelTerrainCollisionCache.SectionAccessCache accessCache) { + @Nullable PhysicsChunkSectionAccessCache accessCache) { this.world = world; this.templates = templates; this.currentSection = currentSection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java index abdab659..81a7ecc5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/SectionColliderBuilder.java @@ -39,7 +39,7 @@ long neighborhoodSignature(@Nonnull World world, int chunkX, int sectionY, int chunkZ, - @Nullable VoxelTerrainCollisionCache.SectionAccessCache accessCache) { + @Nullable PhysicsChunkSectionAccessCache accessCache) { return new SectionBlockReader(world, templates, section, chunkX, sectionY, chunkZ, accessCache) .neighborhoodSignature(); } @@ -59,7 +59,7 @@ SectionCollisionGeometry build(@Nonnull World world, int chunkX, int sectionY, int chunkZ, - @Nullable VoxelTerrainCollisionCache.SectionAccessCache accessCache) { + @Nullable PhysicsChunkSectionAccessCache accessCache) { SectionBlockReader reader = new SectionBlockReader(world, templates, section, chunkX, sectionY, chunkZ, accessCache); BitSet fullCubes = new BitSet(SECTION_VOLUME); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java index 4799680b..a7fcc430 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java @@ -83,8 +83,8 @@ public boolean isStreamingApplyPending() { } @Nonnull - public SectionAccessCache newSectionAccessCache() { - return new SectionAccessCache(); + public PhysicsChunkSectionAccessCache newSectionAccessCache() { + return new PhysicsChunkSectionAccessCache(); } /** @@ -213,7 +213,7 @@ public synchronized int pruneBodyStreamingTargets(@Nonnull SpaceId spaceId, * Wipes cached sections for the space, then rebuilds everything in the given radius. */ @Nonnull - public synchronized BuildStats rebuildAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats rebuildAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius) { @@ -228,13 +228,13 @@ public synchronized BuildStats rebuildAround(@Nonnull World world, * Wipes cached sections for the space, then rebuilds everything in the given radius. */ @Nonnull - public synchronized BuildStats rebuildAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats rebuildAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @Nonnull PhysicsChunkBuildOptions buildOptions) { int removed = clear(space); - BuildStats stats = ensureAround(world, + PhysicsChunkBuildStats stats = ensureAround(world, space, center, radius, @@ -251,13 +251,13 @@ public synchronized BuildStats rebuildAround(@Nonnull World world, * Wipes cached sections in the given radius, then rebuilds that same radius. */ @Nonnull - public synchronized BuildStats refreshAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats refreshAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @Nonnull PhysicsChunkBuildOptions buildOptions) { int removed = clearSectionsAround(space.spaceId(), space, center, radius); - BuildStats stats = ensureAround(world, + PhysicsChunkBuildStats stats = ensureAround(world, space, center, radius, @@ -274,7 +274,7 @@ public synchronized BuildStats refreshAround(@Nonnull World world, * Ensures all chunk sections within the block radius around {@code center} are cached. */ @Nonnull - public synchronized BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -286,7 +286,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, * Ensures all chunk sections within the block radius around {@code center} are cached. */ @Nonnull - public synchronized BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -299,7 +299,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, * Ensures all chunk sections within the block radius around {@code center} are cached. */ @Nonnull - public synchronized BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -313,7 +313,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, * Ensures all chunk sections within the block radius around {@code center} are cached. */ @Nonnull - public synchronized BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -336,7 +336,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, * Ensures all chunk sections within the block radius around {@code center} are cached. */ @Nonnull - public synchronized BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -344,7 +344,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, @Nullable Snapshot profiling, @Nullable LongSet visitedSections, @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nullable SectionAccessCache accessCache) { + @Nullable PhysicsChunkSectionAccessCache accessCache) { return ensureAround(world, space, center, @@ -361,7 +361,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, * Ensures all chunk sections within the block radius around {@code center} are cached. */ @Nonnull - public synchronized BuildStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, @Nonnull Vector3d center, int radius, @@ -369,7 +369,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, @Nullable Snapshot profiling, @Nullable LongSet visitedSections, @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nullable SectionAccessCache accessCache, + @Nullable PhysicsChunkSectionAccessCache accessCache, @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { @@ -390,7 +390,7 @@ public synchronized BuildStats ensureAround(@Nonnull World world, int minChunkZ = ChunkUtil.chunkCoordinate(minZ); int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); - BuildStats total = BuildStats.empty(); + PhysicsChunkBuildStats total = PhysicsChunkBuildStats.empty(); for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { @@ -541,7 +541,7 @@ public synchronized int pruneUnloaded(@Nonnull World world, @Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceBinding space, @Nullable Snapshot profiling, - @Nullable SectionAccessCache accessCache) { + @Nullable PhysicsChunkSectionAccessCache accessCache) { long start = profiling != null ? System.nanoTime() : 0L; SpaceCollisionCache cache = spaces.get(spaceId.value()); if (cache == null) { @@ -808,7 +808,7 @@ public synchronized void forEachDebugSection(@Nonnull SpaceId spaceId, } @Nonnull - private BuildStats ensureSection(@Nonnull World world, + private PhysicsChunkBuildStats ensureSection(@Nonnull World world, @Nonnull PhysicsSpaceBinding space, int chunkX, int sectionY, @@ -816,7 +816,7 @@ private BuildStats ensureSection(@Nonnull World world, long tick, @Nullable Snapshot profiling, @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nullable SectionAccessCache accessCache, + @Nullable PhysicsChunkSectionAccessCache accessCache, @Nonnull PhysicsChunkBuildOptions buildOptions) { long start = profiling != null ? System.nanoTime() : 0L; if (profiling != null) { @@ -837,7 +837,7 @@ private BuildStats ensureSection(@Nonnull World world, targetDiagnostic); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } BlockChunk blockChunk = blockChunk(world, chunkX, chunkZ, accessCache); @@ -851,7 +851,7 @@ private BuildStats ensureSection(@Nonnull World world, targetDiagnostic); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } cache.missingBlockChunkBackoffs.remove(chunkKey); @@ -865,7 +865,7 @@ private BuildStats ensureSection(@Nonnull World world, targetDiagnostic); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } BlockSection section = accessCache != null @@ -881,7 +881,7 @@ private BuildStats ensureSection(@Nonnull World world, targetDiagnostic); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } cache.missingBlockSectionBackoffs.remove(sectionKey); CachedSection cached = cache.sections.get(sectionKey); @@ -899,7 +899,7 @@ private BuildStats ensureSection(@Nonnull World world, profiling.incrementSectionCacheHits(); profiling.addEnsureSectionNanos(System.nanoTime() - start); } - return BuildStats.empty(); + return PhysicsChunkBuildStats.empty(); } SectionCollisionGeometry geometry = sectionBuilder.build(world, @@ -943,7 +943,7 @@ private BuildStats ensureSection(@Nonnull World world, throw exception; } - BuildStats stats = BuildStats.from(geometry, + PhysicsChunkBuildStats stats = PhysicsChunkBuildStats.from(geometry, built.backendBodyIds.size(), removed, rebuilt ? 0 : 1, @@ -1132,7 +1132,7 @@ private static BlockChunk blockChunk(@Nonnull World world, int chunkX, int chunk private static BlockChunk blockChunk(@Nonnull World world, int chunkX, int chunkZ, - @Nullable SectionAccessCache accessCache) { + @Nullable PhysicsChunkSectionAccessCache accessCache) { return accessCache != null ? accessCache.blockChunk(world, chunkX, chunkZ) : loadBlockChunk(world, chunkX, chunkZ); @@ -1362,43 +1362,6 @@ private static GroundProbe missing() { } } - /** - * Per-streaming-tick source-section cache. It avoids repeated Hytale chunk - * reference lookups while many bodies request overlapping neighborhoods. - */ - public static final class SectionAccessCache { - - private final Long2ObjectMap blockChunks = new Long2ObjectOpenHashMap<>(); - private final Long2ObjectMap blockSections = new Long2ObjectOpenHashMap<>(); - - private SectionAccessCache() { - } - - @Nullable - BlockChunk blockChunk(@Nonnull World world, int chunkX, int chunkZ) { - long key = ChunkUtil.indexChunk(chunkX, chunkZ); - if (blockChunks.containsKey(key)) { - return blockChunks.get(key); - } - - BlockChunk chunk = loadBlockChunk(world, chunkX, chunkZ); - blockChunks.put(key, chunk); - return chunk; - } - - @Nullable - BlockSection blockSection(@Nonnull World world, int chunkX, int sectionY, int chunkZ) { - long key = packSectionKey(chunkX, sectionY, chunkZ); - if (blockSections.containsKey(key)) { - return blockSections.get(key); - } - - BlockSection section = ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); - blockSections.put(key, section); - return section; - } - } - public enum TargetRefreshReason { FIRST_SEEN, BOUNDS_CHANGED, @@ -1421,70 +1384,4 @@ private static TargetRefreshDecision skip() { } } - /** - * Aggregate statistics from a cache build or rebuild operation. - */ - public record BuildStats(int scannedBlocks, - int solidBlocks, - int culledInteriorBlocks, - int fullCubeRuns, - int detailBoxes, - int colliderBodies, - int removedBodies, - int sectionsBuilt, - int sectionsRebuilt, - int voxelBodies) { - - @Nonnull - public static BuildStats empty() { - return new BuildStats(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - } - - @Nonnull - private static BuildStats from(@Nonnull SectionCollisionGeometry geometry, - int colliderBodies, - int removedBodies, - int sectionsBuilt, - int sectionsRebuilt, - int voxelBodies) { - return new BuildStats(geometry.scannedBlocks(), - geometry.solidBlocks(), - geometry.culledInteriorBlocks(), - geometry.mergedFullCubeBoxes().size(), - geometry.detailBoxCount(), - colliderBodies, - removedBodies, - sectionsBuilt, - sectionsRebuilt, - voxelBodies); - } - - @Nonnull - public BuildStats plus(@Nonnull BuildStats stats) { - return new BuildStats(scannedBlocks + stats.scannedBlocks, - solidBlocks + stats.solidBlocks, - culledInteriorBlocks + stats.culledInteriorBlocks, - fullCubeRuns + stats.fullCubeRuns, - detailBoxes + stats.detailBoxes, - colliderBodies + stats.colliderBodies, - removedBodies + stats.removedBodies, - sectionsBuilt + stats.sectionsBuilt, - sectionsRebuilt + stats.sectionsRebuilt, - voxelBodies + stats.voxelBodies); - } - - @Nonnull - private BuildStats withRemovedBodies(int removedBodies) { - return new BuildStats(scannedBlocks, - solidBlocks, - culledInteriorBlocks, - fullCubeRuns, - detailBoxes, - colliderBodies, - removedBodies, - sectionsBuilt, - sectionsRebuilt, - voxelBodies); - } - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java index 22c60c38..c9bd9b86 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelTerrainCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildStats; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.ArrayList; @@ -332,7 +332,7 @@ public void incrementDuplicateSkips() { duplicateSkips++; } - public void addBuildStats(@Nonnull BuildStats stats) { + public void addBuildStats(@Nonnull PhysicsChunkBuildStats stats) { scannedBlocks += stats.scannedBlocks(); solidBlocks += stats.solidBlocks(); culledInteriorBlocks += stats.culledInteriorBlocks(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java index e3dc6b2a..fd31895a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java @@ -18,6 +18,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkSectionAccessCache; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStreamingBounds; @@ -136,6 +137,7 @@ private static void processSpace(@Nonnull World world, long currentTick, @Nullable Snapshot snapshot) { LongSet visitedSections = new LongOpenHashSet(); + PhysicsChunkSectionAccessCache accessCache = new PhysicsChunkSectionAccessCache(); for (Vector3d position : playerPositions) { int sectionsBefore = visitedSections.size(); streaming.ensureAround(world, @@ -147,6 +149,7 @@ private static void processSpace(@Nonnull World world, snapshot, visitedSections, snapshot != null ? StreamingTargetDiagnostic.player(position) : null, + accessCache, settings.buildOptions()); if (snapshot != null) { snapshot.addPlayerSectionTargets(visitedSections.size() - sectionsBefore); @@ -168,6 +171,7 @@ private static void processSpace(@Nonnull World world, snapshot, visitedSections, null, + accessCache, settings.buildOptions()); for (BodyStreamingRefresh refresh : target.refreshes()) { streaming.recordBodyTargetRefresh(settings.spaceUuid(), @@ -181,7 +185,7 @@ private static void processSpace(@Nonnull World world, } } - streaming.pruneUnloaded(world, settings.spaceUuid(), queue, snapshot); + streaming.pruneUnloaded(world, settings.spaceUuid(), queue, snapshot, accessCache); streaming.pruneUnused(settings.spaceUuid(), queue, currentTick, settings.ttlTicks(), snapshot); streaming.pruneBodyStreamingTargets(settings.spaceUuid(), currentTick, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index ab55d2a2..f2da8816 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -30,14 +30,12 @@ import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -58,7 +56,6 @@ import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; @@ -90,9 +87,6 @@ public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { private final PhysicsBodyRegistry bodyRegistry = new PhysicsBodyRegistry(); - private final PhysicsChunkTerrainRuntime terrainRuntime = - new PhysicsChunkTerrainRuntime(); - @Nonnull private final PhysicsSimulationRuntime simulationRuntime = new PhysicsSimulationRuntime(); @@ -530,7 +524,6 @@ private PhysicsSpaceBinding createSpaceDirect(@Nonnull BackendId backendId, worldName, settings, simulationRuntime.getWorldSettings().getStepMode()); - terrainRuntime.registerSpace(spaceId); markWorldChanged(); return binding; } @@ -1109,7 +1102,6 @@ public void disablePhysicsChunkLifecycle() { } private void disablePhysicsChunkLifecycleDirect() { - terrainRuntime.clearRetainedTerrain(spaceRuntime.getBindings()); restoreCollisionLodFiltersDirect(); } @@ -1245,12 +1237,10 @@ public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldName) { PhysicsSpaceBinding removed = spaceRuntime.removeSpace(spaceId); if (removed == null) { - terrainRuntime.clear(spaceId, null); return; } try { - terrainRuntime.clear(spaceId, removed); jointRegistry.unregisterSpace(spaceId); for (PhysicsBodyRegistration registration : new ArrayList<>(bodyRegistry.getRegistrations())) { if (registration.spaceId().equals(spaceId)) { @@ -1363,7 +1353,6 @@ public CompletionStage resetRuntimeStateKeepingSpaces private PhysicsRuntimeResetResult resetRuntimeStateKeepingSpacesDirect(@Nonnull String worldName) { PhysicsRuntimeResetResult reset = spaceRuntime.resetKeepingSpaces(worldName, simulationRuntime.getWorldSettings().getStepMode()); - terrainRuntime.clearAll(); clearRuntimeTopologyDirect(false); markWorldChanged(); return reset; @@ -1453,38 +1442,7 @@ public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spa private void setSpaceSettingsDirect(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { - PhysicsChunkTerrainSettings previousTerrainSettings = - spaceRuntime.getLiveSpaceSettings(spaceId).getPhysicsChunkTerrainSettings(); - boolean terrainStreamingSettingsChanged = - terrainStreamingSettingsChanged(previousTerrainSettings, - settings.getPhysicsChunkTerrainSettings()); - boolean terrainRepresentationChanged = - previousTerrainSettings.isNativeVoxelTerrainEnabled() - != settings.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled(); - boolean terrainMaterialChanged = - Float.compare(previousTerrainSettings.getTerrainFriction(), - settings.getPhysicsChunkTerrainSettings().getTerrainFriction()) != 0 - || Float.compare(previousTerrainSettings.getTerrainRestitution(), - settings.getPhysicsChunkTerrainSettings().getTerrainRestitution()) != 0; - boolean terrainDisabled = - settings.getPhysicsChunkTerrainSettings().getTerrainMode() == PhysicsChunkTerrainMode.NONE - && previousTerrainSettings.getTerrainMode() != PhysicsChunkTerrainMode.NONE; spaceRuntime.setSpaceSettings(spaceId, settings); - if (terrainDisabled || terrainRepresentationChanged || terrainMaterialChanged) { - terrainRuntime.clear(requireSpaceBinding(spaceId)); - } else if (terrainStreamingSettingsChanged) { - terrainRuntime.incrementStreamingRevision(spaceId); - } - } - - private static boolean terrainStreamingSettingsChanged( - @Nonnull PhysicsChunkTerrainSettings previous, - @Nonnull PhysicsChunkTerrainSettings next) { - return previous.getTerrainMode() != next.getTerrainMode() - || previous.getTerrainRadius() != next.getTerrainRadius() - || previous.getBodyTerrainRadius() != next.getBodyTerrainRadius() - || previous.getTerrainTtlTicks() != next.getTerrainTtlTicks() - || previous.isNativeVoxelTerrainEnabled() != next.isNativeVoxelTerrainEnabled(); } private void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { @@ -1783,9 +1741,6 @@ private void copyFromDirect(@Nonnull PhysicsWorldResource other) { private void clearRuntimeTopologyDirect(boolean clearCollision) { bodyRuntime.clearBodyStateWithoutMarkingWorldChanged(); - if (clearCollision) { - terrainRuntime.clearAllAndUnregisterSpaces(); - } } private void markWorldChanged() { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java index d36c6254..7339e3be 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.VoxelTerrainCollisionCache.BuildStats; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildStats; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.MissingSectionReason; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.junit.jupiter.api.AfterEach; @@ -71,7 +71,7 @@ void finishTickTracksLatestCumulativeAndWorstSnapshots() { first.recordMissingSection(MissingSectionReason.BLOCK_CHUNK, 1, 2, 3, null); first.recordMissingSection(MissingSectionReason.BLOCK_SECTION, 1, 2, 3, null); first.incrementDuplicateSkips(); - first.addBuildStats(new BuildStats(10, 8, 2, 3, 4, 1, 1, 2, 1, 1)); + first.addBuildStats(new PhysicsChunkBuildStats(10, 8, 2, 3, 4, 1, 1, 2, 1, 1)); first.addUnloadedPrune(2, 3); first.addTtlPrune(1, 4); first.addEnsureAroundNanos(20L); From 65951b00c7ffc503434b2e61b78adb6115d3d185 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 21:56:46 +0200 Subject: [PATCH 404/534] test(core): cover chunk collision native rows Signed-off-by: Blovien --- ...ChunkCollisionMutationDrainSystemTest.java | 210 +++++++++++++++++- 1 file changed, 209 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 2ce705d5..0053831e 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.hypixel.hytale.component.AddReason; @@ -11,7 +12,9 @@ import com.hypixel.hytale.component.EmptyResourceStorage; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; @@ -43,6 +46,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -126,10 +131,145 @@ void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { } } + @Test + void upsertCreatesNativeVoxelRowWhenBackendSupportsVoxelTerrain() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-voxel-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(11); + BackendId backendId = new BackendId("test:chunk-collision-drain-voxel"); + Ref spaceRef = addBoundSpace(store, spaceUuid, backendId, true); + String sourceKey = "2:3:4"; + String payloadKey = "chunk-collision/2/3/4"; + ChunkCollisionPayload payload = new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[] {0, 0, 0, 1, 0, 0}, + List.of(), + List.of(), + true, + 0.7f, + 0.05f, + 0x20, + 0x03, + List.of()); + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 2, + 3, + 4, + payloadKey, + payload)); + + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertSame(payload, + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); + assertSoftSkipsEmpty(store); + assertGeneratedVoxel(store, + spaceUuid, + spaceRef, + sourceKey, + payloadKey, + payload); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void removeDeletesGeneratedRowsAndPayloadResource() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-remove-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(21); + BackendId backendId = new BackendId("test:chunk-collision-drain-remove"); + addBoundSpace(store, spaceUuid, backendId); + String sourceKey = "5:6:7"; + String payloadKey = "chunk-collision/5/6/7"; + ChunkCollisionPayload payload = new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(new BoxPayload(1.0, 2.0, 3.0, 0.5, 0.5, 0.5)), + List.of(new BoxPayload(4.0, 5.0, 6.0, 0.25, 0.25, 0.25)), + false, + 0.6f, + 0.1f, + 0x10, + 0x0F, + List.of()); + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 5, + 6, + 7, + payloadKey, + payload)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + UUID boxUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0); + UUID detailUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.DETAIL_BOX, + 0); + assertNotNull(identity.getByUuid(boxUuid)); + assertNotNull(identity.getByUuid(detailUuid)); + + queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, sourceKey, 5, 6, 7)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertNull(identity.getByUuid(boxUuid)); + assertNull(identity.getByUuid(detailUuid)); + assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Nonnull private static Ref addBoundSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull BackendId backendId) { + return addBoundSpace(store, spaceUuid, backendId, false); + } + + @Nonnull + private static Ref addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + boolean voxelTerrain) { Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, spaceUuid, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), @@ -140,7 +280,7 @@ private static Ref addBoundSpace(@Nonnull Store stor store.getExternalData().putRefForUUID(spaceUuid, spaceRef); PhysicsBackendRuntime backendRuntime = - new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + new FakePhysicsBackendRuntimeProvider(backendId, false, voxelTerrain).createRuntime(); int spaceHandle = backendRuntime.createSpace(new SpaceId(42)); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); runtime.putRuntime(backendId, backendRuntime); @@ -151,6 +291,62 @@ private static Ref addBoundSpace(@Nonnull Store stor return spaceRef; } + private static void assertGeneratedVoxel(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull String sourceKey, + @Nonnull String payloadKey, + @Nonnull ChunkCollisionPayload payload) { + UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.VOXEL_TERRAIN, + 0); + Ref bodyRef = store + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(bodyUuid); + assertNotNull(bodyRef); + + BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); + assertNotNull(body); + assertEquals(spaceUuid, body.getSpaceUuid()); + assertNotNull(body.getSpaceRef()); + assertEquals(spaceRef.getIndex(), body.getSpaceRef().getIndex()); + assertEquals(PhysicsBodyKind.TERRAIN, body.getKind()); + assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, body.getPersistenceMode()); + + ShapeComponent shape = store.getComponent(bodyRef, ShapeComponent.getComponentType()); + assertNotNull(shape); + assertEquals(ShapeType.VOXELS, shape.getShapeType()); + assertEquals(payloadKey, shape.getResourceKey()); + + TargetComponent target = store.getComponent(bodyRef, TargetComponent.getComponentType()); + assertNotNull(target); + assertVectorEquals(2 << ChunkUtil.BITS, + 3 << ChunkUtil.BITS, + 4 << ChunkUtil.BITS, + target.getPosition()); + + MaterialComponent material = store.getComponent(bodyRef, + MaterialComponent.getComponentType()); + assertNotNull(material); + assertEquals(payload.friction(), material.getFriction(), DELTA); + assertEquals(payload.restitution(), material.getRestitution(), DELTA); + + CollisionFilterComponent filter = store.getComponent(bodyRef, + CollisionFilterComponent.getComponentType()); + assertNotNull(filter); + assertEquals(payload.collisionGroup(), filter.getCollisionGroup()); + assertEquals(payload.collisionMask(), filter.getCollisionMask()); + + ChunkCollisionSourceComponent source = store.getComponent(bodyRef, + ChunkCollisionSourceComponent.getComponentType()); + assertNotNull(source); + assertEquals(sourceKey, source.getSourceKey()); + assertEquals(payloadKey, source.getPayloadResourceKey()); + assertEquals(PartKind.VOXEL_TERRAIN, source.getPartKind()); + assertEquals(0, source.getPartIndex()); + } + private static void assertGeneratedBox(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull Ref spaceRef, @@ -241,6 +437,18 @@ private static void assertSoftSkipsEmpty(@Nonnull Store store) { .size()); } + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", exception.getCause()); + } + } + @Nonnull private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); From 356f4fef5f141eb892eba50f3f662ea86146608d Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 22:04:33 +0200 Subject: [PATCH 405/534] refactor(core): remove legacy voxel terrain cache Signed-off-by: Blovien --- .../VoxelTerrainCollisionCache.java | 1387 ----------------- .../VoxelTerrainCollisionCacheTest.java | 718 --------- ...hunkCollisionVoxelStitchingSystemTest.java | 322 ++++ 3 files changed, 322 insertions(+), 2105 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java deleted file mode 100644 index a7fcc430..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCache.java +++ /dev/null @@ -1,1387 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; -import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.MissingSectionReason; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2LongMap; -import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3d; - -/** - * Section-keyed cache that generates static physics collision from Hytale world blocks. - * - *

        The cache is split per physics space so spaces can choose different PhysicsChunk terrain - * policies. Each cached section is rebuilt when Hytale's section change counter changes, - * and removed when it falls out of the streaming radius or when its chunk unloads.

        - */ -public final class VoxelTerrainCollisionCache { - - private static final int ACTIVE_BODY_STREAMING_INTERVAL_TICKS = 4; - private static final int SLEEPING_BODY_STREAMING_INTERVAL_TICKS = 20; - private static final int MISSING_BLOCK_CHUNK_RETRY_TICKS = 10; - private static final int MISSING_BLOCK_SECTION_RETRY_TICKS = 5; - - private static final int ADJACENT_SECTION_VOXEL_SHIFT = 16; - private static final long BODY_TARGET_REFRESH_PENDING = Long.MIN_VALUE; - - private final Int2ObjectMap spaces = new Int2ObjectOpenHashMap<>(); - private final ShapeTemplateCache shapeTemplates = new ShapeTemplateCache(); - private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); - private final AtomicBoolean streamingApplyPending = new AtomicBoolean(); - - public synchronized void copyFrom(@Nonnull VoxelTerrainCollisionCache other) { - if (other == this) { - streamingApplyPending.set(false); - return; - } - spaces.clear(); - synchronized (other) { - for (Int2ObjectMap.Entry entry : other.spaces.int2ObjectEntrySet()) { - spaces.put(entry.getIntKey(), new SpaceCollisionCache(entry.getValue())); - } - } - streamingApplyPending.set(false); - } - - public boolean tryBeginStreamingApply() { - return streamingApplyPending.compareAndSet(false, true); - } - - public void finishStreamingApply() { - streamingApplyPending.set(false); - } - - public boolean isStreamingApplyPending() { - return streamingApplyPending.get(); - } - - @Nonnull - public PhysicsChunkSectionAccessCache newSectionAccessCache() { - return new PhysicsChunkSectionAccessCache(); - } - - /** - * Returns whether a body target needs terrain work. Call - * {@link #recordBodyTargetRefresh(SpaceId, UUID, PhysicsChunkStreamingBounds, boolean, long)} - * only after the terrain apply path has actually attempted that work. - */ - @Nonnull - public synchronized TargetRefreshDecision shouldRefreshBodyTarget(@Nonnull SpaceId spaceId, - @Nonnull UUID bodyUuid, - @Nonnull PhysicsChunkStreamingBounds bounds, - boolean sleeping, - long currentTick, - int ttlTicks, - @Nullable Snapshot profiling) { - SpaceCollisionCache cache = spaces.computeIfAbsent(spaceId.value(), ignored -> new SpaceCollisionCache()); - CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyUuid); - if (target == null) { - cache.bodyTargets.put(bodyUuid, new CachedBodyStreamingTarget(bounds, - sleeping, - currentTick, - BODY_TARGET_REFRESH_PENDING)); - if (profiling != null) { - profiling.incrementBodyTargetFirstSeen(); - } - return TargetRefreshDecision.refresh(TargetRefreshReason.FIRST_SEEN); - } - - target.lastSeenTick = currentTick; - target.sleeping = sleeping; - if (profiling != null) { - profiling.incrementBodyTargetCacheHits(); - } - - if (!target.bounds.equals(bounds)) { - target.bounds = bounds; - if (profiling != null) { - profiling.incrementBodyTargetBoundsChanged(); - } - return TargetRefreshDecision.refresh(TargetRefreshReason.BOUNDS_CHANGED); - } - - if (target.lastRefreshTick == BODY_TARGET_REFRESH_PENDING) { - return TargetRefreshDecision.refresh(TargetRefreshReason.PENDING_APPLY); - } - - int interval = sleeping ? sleepingBodyStreamingInterval(ttlTicks) : ACTIVE_BODY_STREAMING_INTERVAL_TICKS; - if (currentTick == 1L || currentTick - target.lastRefreshTick >= interval) { - if (profiling != null) { - if (sleeping) { - profiling.incrementBodyTargetSleepingRefreshes(); - } else { - profiling.incrementBodyTargetActiveRefreshes(); - } - } - return TargetRefreshDecision.refresh(sleeping - ? TargetRefreshReason.SLEEPING_INTERVAL - : TargetRefreshReason.ACTIVE_INTERVAL); - } - - if (profiling != null) { - if (sleeping) { - profiling.incrementBodyTargetSleepingStableSkips(); - } else { - profiling.incrementBodyTargetActiveStableSkips(); - } - } - return TargetRefreshDecision.skip(); - } - - /** - * Records that a body target's terrain refresh was attempted by the apply loop. - */ - public synchronized void recordBodyTargetRefresh(@Nonnull SpaceId spaceId, - @Nonnull UUID bodyUuid, - @Nonnull PhysicsChunkStreamingBounds bounds, - boolean sleeping, - long currentTick) { - SpaceCollisionCache cache = spaces.computeIfAbsent(spaceId.value(), ignored -> new SpaceCollisionCache()); - CachedBodyStreamingTarget target = cache.bodyTargets.get(bodyUuid); - if (target == null) { - cache.bodyTargets.put(bodyUuid, new CachedBodyStreamingTarget(bounds, - sleeping, - currentTick, - currentTick)); - return; - } - target.bounds = bounds; - target.sleeping = sleeping; - target.lastSeenTick = currentTick; - target.lastRefreshTick = currentTick; - } - - public synchronized int pruneBodyStreamingTargets(@Nonnull SpaceId spaceId, - long currentTick, - int ttlTicks, - @Nullable Snapshot profiling) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return 0; - } - - long maxAge = Math.max(1L, ttlTicks) * 2L; - int removed = 0; - Iterator> iterator = - cache.bodyTargets.object2ObjectEntrySet().iterator(); - while (iterator.hasNext()) { - CachedBodyStreamingTarget target = iterator.next().getValue(); - if (currentTick - target.lastSeenTick <= maxAge) { - continue; - } - iterator.remove(); - removed++; - } - pruneExpiredMissingBackoffs(cache, currentTick); - if (cache.isEmpty()) { - spaces.remove(spaceId.value()); - } - if (removed > 0 && profiling != null) { - profiling.addBodyTargetsPruned(removed); - } - return removed; - } - - /** - * Wipes cached sections for the space, then rebuilds everything in the given radius. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats rebuildAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius) { - return rebuildAround(world, - space, - center, - radius, - PhysicsChunkBuildOptions.DEFAULT); - } - - /** - * Wipes cached sections for the space, then rebuilds everything in the given radius. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats rebuildAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - int removed = clear(space); - PhysicsChunkBuildStats stats = ensureAround(world, - space, - center, - radius, - 0L, - null, - null, - null, - null, - buildOptions); - return stats.withRemovedBodies(stats.removedBodies() + removed); - } - - /** - * Wipes cached sections in the given radius, then rebuilds that same radius. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats refreshAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - int removed = clearSectionsAround(space.spaceId(), space, center, radius); - PhysicsChunkBuildStats stats = ensureAround(world, - space, - center, - radius, - 0L, - null, - null, - null, - null, - buildOptions); - return stats.withRemovedBodies(stats.removedBodies() + removed); - } - - /** - * Ensures all chunk sections within the block radius around {@code center} are cached. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - long tick) { - return ensureAround(world, space, center, radius, tick, null, null); - } - - /** - * Ensures all chunk sections within the block radius around {@code center} are cached. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - long tick, - @Nullable Snapshot profiling) { - return ensureAround(world, space, center, radius, tick, profiling, null); - } - - /** - * Ensures all chunk sections within the block radius around {@code center} are cached. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - long tick, - @Nullable Snapshot profiling, - @Nullable LongSet visitedSections) { - return ensureAround(world, space, center, radius, tick, profiling, visitedSections, null); - } - - /** - * Ensures all chunk sections within the block radius around {@code center} are cached. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - long tick, - @Nullable Snapshot profiling, - @Nullable LongSet visitedSections, - @Nullable StreamingTargetDiagnostic targetDiagnostic) { - return ensureAround(world, - space, - center, - radius, - tick, - profiling, - visitedSections, - targetDiagnostic, - null); - } - - /** - * Ensures all chunk sections within the block radius around {@code center} are cached. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - long tick, - @Nullable Snapshot profiling, - @Nullable LongSet visitedSections, - @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nullable PhysicsChunkSectionAccessCache accessCache) { - return ensureAround(world, - space, - center, - radius, - tick, - profiling, - visitedSections, - targetDiagnostic, - accessCache, - PhysicsChunkBuildOptions.DEFAULT); - } - - /** - * Ensures all chunk sections within the block radius around {@code center} are cached. - */ - @Nonnull - public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius, - long tick, - @Nullable Snapshot profiling, - @Nullable LongSet visitedSections, - @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nullable PhysicsChunkSectionAccessCache accessCache, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - long start = profiling != null ? System.nanoTime() : 0L; - if (profiling != null) { - profiling.incrementEnsureCalls(); - } - - int minX = (int) Math.floor(center.x) - radius; - int maxX = (int) Math.floor(center.x) + radius; - int minY = Math.max(0, (int) Math.floor(center.y) - radius); - int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, (int) Math.floor(center.y) + radius); - int minZ = (int) Math.floor(center.z) - radius; - int maxZ = (int) Math.floor(center.z) + radius; - - int minChunkX = ChunkUtil.chunkCoordinate(minX); - int maxChunkX = ChunkUtil.chunkCoordinate(maxX); - int minSectionY = ChunkUtil.indexSection(minY); - int maxSectionY = ChunkUtil.indexSection(maxY); - int minChunkZ = ChunkUtil.chunkCoordinate(minZ); - int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); - - PhysicsChunkBuildStats total = PhysicsChunkBuildStats.empty(); - for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { - for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { - for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - long key = packSectionKey(chunkX, sectionY, chunkZ); - if (visitedSections != null) { - if (!visitedSections.add(key)) { - if (profiling != null) { - profiling.incrementDuplicateSkips(); - } - continue; - } - } - total = total.plus(ensureSection( - world, - space, - chunkX, - sectionY, - chunkZ, - tick, - profiling, - targetDiagnostic, - accessCache, - buildOptions)); - } - } - } - if (profiling != null) { - profiling.addEnsureAroundNanos(System.nanoTime() - start); - } - return total; - } - - /** - * Refreshes the TTL of already-cached sections around a sleeping body without building - * missing collision or touching chunk storage. - */ - public synchronized int touchAround(@Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius, - long tick) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return 0; - } - - int minX = (int) Math.floor(center.x) - radius; - int maxX = (int) Math.floor(center.x) + radius; - int minY = Math.max(0, (int) Math.floor(center.y) - radius); - int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, (int) Math.floor(center.y) + radius); - int minZ = (int) Math.floor(center.z) - radius; - int maxZ = (int) Math.floor(center.z) + radius; - - int minChunkX = ChunkUtil.chunkCoordinate(minX); - int maxChunkX = ChunkUtil.chunkCoordinate(maxX); - int minSectionY = ChunkUtil.indexSection(minY); - int maxSectionY = ChunkUtil.indexSection(maxY); - int minChunkZ = ChunkUtil.chunkCoordinate(minZ); - int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); - - int touched = 0; - for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { - for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) { - for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - CachedSection section = cache.section(chunkX, sectionY, chunkZ); - if (section == null || section.lastUsedTick >= tick) { - continue; - } - section.lastUsedTick = tick; - touched++; - } - } - } - return touched; - } - - /** - * Removes sections whose last use is older than the configured TTL. - */ - public synchronized int pruneUnused(@Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceBinding space, - long currentTick, - int ttlTicks) { - return pruneUnused(spaceId, space, currentTick, ttlTicks, null); - } - - /** - * Removes sections whose last use is older than the configured TTL. - */ - public synchronized int pruneUnused(@Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceBinding space, - long currentTick, - int ttlTicks, - @Nullable Snapshot profiling) { - long start = profiling != null ? System.nanoTime() : 0L; - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return 0; - } - - int removed = 0; - int removedSections = 0; - Iterator> iterator = cache.sections.long2ObjectEntrySet().iterator(); - while (iterator.hasNext()) { - Long2ObjectMap.Entry entry = iterator.next(); - CachedSection section = entry.getValue(); - if (currentTick - section.lastUsedTick <= ttlTicks) { - continue; - } - - removed += section.removeFrom(space); - removedSections++; - iterator.remove(); - } - pruneExpiredMissingBackoffs(cache, currentTick); - if (cache.isEmpty()) { - spaces.remove(spaceId.value()); - } - if (profiling != null) { - profiling.addTtlPrune(removedSections, removed); - profiling.addPruneUnusedNanos(System.nanoTime() - start); - } - return removed; - } - - /** - * Removes cached sections whose source chunk is no longer loaded. - */ - public synchronized int pruneUnloaded(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceBinding space) { - return pruneUnloaded(world, spaceId, space, null); - } - - /** - * Removes cached sections whose source chunk is no longer loaded. - */ - public synchronized int pruneUnloaded(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceBinding space, - @Nullable Snapshot profiling) { - return pruneUnloaded(world, spaceId, space, profiling, null); - } - - /** - * Removes cached sections whose source chunk is no longer loaded. - */ - public synchronized int pruneUnloaded(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceBinding space, - @Nullable Snapshot profiling, - @Nullable PhysicsChunkSectionAccessCache accessCache) { - long start = profiling != null ? System.nanoTime() : 0L; - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return 0; - } - - int removed = 0; - int removedSections = 0; - Iterator> iterator = cache.sections.long2ObjectEntrySet().iterator(); - while (iterator.hasNext()) { - CachedSection section = iterator.next().getValue(); - if (blockChunk(world, section.chunkX, section.chunkZ, accessCache) != null) { - continue; - } - - removed += section.removeFrom(space); - removedSections++; - iterator.remove(); - } - if (cache.isEmpty()) { - spaces.remove(spaceId.value()); - } - if (profiling != null) { - profiling.addUnloadedPrune(removedSections, removed); - profiling.addPruneUnloadedNanos(System.nanoTime() - start); - } - return removed; - } - - /** - * Removes all cached sections for the given space. - */ - public synchronized int clear(@Nonnull SpaceId spaceId, @Nullable PhysicsSpaceBinding space) { - SpaceCollisionCache cache = spaces.remove(spaceId.value()); - if (cache == null || space == null) { - return 0; - } - - int removed = 0; - for (CachedSection section : cache.sections.values()) { - removed += section.removeFrom(space); - } - return removed; - } - - /** - * Removes all cached sections for the given space. - */ - public synchronized int clear(@Nonnull PhysicsSpaceBinding space) { - return clear(space.spaceId(), space); - } - - /** - * Removes cached sections within the block radius around {@code center}. - */ - public synchronized int clearSectionsAround(@Nonnull SpaceId spaceId, - @Nullable PhysicsSpaceBinding space, - @Nonnull Vector3d center, - int radius) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return 0; - } - - int clampedRadius = Math.max(0, radius); - int minX = (int) Math.floor(center.x) - clampedRadius; - int maxX = (int) Math.floor(center.x) + clampedRadius; - int minY = Math.max(0, (int) Math.floor(center.y) - clampedRadius); - int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, (int) Math.floor(center.y) + clampedRadius); - int minZ = (int) Math.floor(center.z) - clampedRadius; - int maxZ = (int) Math.floor(center.z) + clampedRadius; - - int minChunkX = ChunkUtil.chunkCoordinate(minX); - int maxChunkX = ChunkUtil.chunkCoordinate(maxX); - int minSectionY = ChunkUtil.indexSection(minY); - int maxSectionY = ChunkUtil.indexSection(maxY); - int minChunkZ = ChunkUtil.chunkCoordinate(minZ); - int maxChunkZ = ChunkUtil.chunkCoordinate(maxZ); - - int removed = 0; - Iterator> iterator = cache.sections.long2ObjectEntrySet().iterator(); - while (iterator.hasNext()) { - CachedSection section = iterator.next().getValue(); - if (section.chunkX < minChunkX || section.chunkX > maxChunkX - || section.sectionY < minSectionY || section.sectionY > maxSectionY - || section.chunkZ < minChunkZ || section.chunkZ > maxChunkZ) { - continue; - } - if (space != null) { - removed += section.removeFrom(space); - } - iterator.remove(); - } - for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { - for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - cache.missingBlockChunkBackoffs.remove(ChunkUtil.indexChunk(chunkX, chunkZ)); - removeSectionBackoffsForChunk(cache, chunkX, chunkZ); - } - } - if (cache.isEmpty()) { - spaces.remove(spaceId.value()); - } - return removed; - } - - /** - * Removes cached sections for one chunk from a single physics space. - */ - public synchronized int clearChunk(@Nonnull SpaceId spaceId, - @Nullable PhysicsSpaceBinding space, - int chunkX, - int chunkZ) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return 0; - } - - int removed = 0; - Iterator> iterator = cache.sections.long2ObjectEntrySet().iterator(); - while (iterator.hasNext()) { - CachedSection section = iterator.next().getValue(); - if (section.chunkX != chunkX || section.chunkZ != chunkZ) { - continue; - } - if (space != null) { - removed += section.removeFrom(space); - } - iterator.remove(); - } - cache.missingBlockChunkBackoffs.remove(ChunkUtil.indexChunk(chunkX, chunkZ)); - removeSectionBackoffsForChunk(cache, chunkX, chunkZ); - if (cache.isEmpty()) { - spaces.remove(spaceId.value()); - } - return removed; - } - - public synchronized int bodyCount() { - int count = 0; - for (SpaceCollisionCache cache : spaces.values()) { - for (CachedSection section : cache.sections.values()) { - count += section.backendBodyIds.size(); - } - } - return count; - } - - public synchronized int bodyCount(@Nonnull SpaceId spaceId) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return 0; - } - - int count = 0; - for (CachedSection section : cache.sections.values()) { - count += section.backendBodyIds.size(); - } - return count; - } - - public synchronized int sectionCount() { - int count = 0; - for (SpaceCollisionCache cache : spaces.values()) { - count += cache.sections.size(); - } - return count; - } - - public synchronized int spaceCount() { - return spaces.size(); - } - - public synchronized int shapeTemplateCount() { - return shapeTemplates.size(); - } - - public synchronized boolean containsBody(@Nonnull SpaceId spaceId, long backendBodyId) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return false; - } - - for (CachedSection section : cache.sections.values()) { - if (section.backendBodyIds.contains(backendBodyId)) { - return true; - } - } - return false; - } - - /** - * Probes the highest cached PhysicsChunk terrain surface under a body footprint. - *

        - * This is intended for diagnostics, not simulation. It uses the collision geometry - * already built for a physics space so benchmark health checks can compare bodies to - * the streamed terrain actually present in the backend instead of a fixed Y plane.

        - */ - @Nonnull - public synchronized GroundProbe probeGround(@Nonnull SpaceId spaceId, - double x, - double z, - double horizontalHalfExtent) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return GroundProbe.missing(); - } - - double halfExtent = Math.max(0.0, horizontalHalfExtent); - double minX = x - halfExtent; - double maxX = x + halfExtent; - double minZ = z - halfExtent; - double maxZ = z + halfExtent; - int minChunkX = ChunkUtil.chunkCoordinate((int) Math.floor(minX)); - int maxChunkX = ChunkUtil.chunkCoordinate((int) Math.floor(maxX)); - int minChunkZ = ChunkUtil.chunkCoordinate((int) Math.floor(minZ)); - int maxChunkZ = ChunkUtil.chunkCoordinate((int) Math.floor(maxZ)); - - double groundTopY = Double.NEGATIVE_INFINITY; - for (CachedSection section : cache.sections.values()) { - if (section.chunkX < minChunkX || section.chunkX > maxChunkX - || section.chunkZ < minChunkZ || section.chunkZ > maxChunkZ) { - continue; - } - groundTopY = probeGroundTop(section.fullCubeBoxes, minX, maxX, minZ, maxZ, groundTopY); - groundTopY = probeGroundTop(section.detailBoxes, minX, maxX, minZ, maxZ, groundTopY); - } - if (groundTopY == Double.NEGATIVE_INFINITY) { - return GroundProbe.missing(); - } - return new GroundProbe(true, groundTopY); - } - - private static double probeGroundTop(@Nonnull List boxes, - double minX, - double maxX, - double minZ, - double maxZ, - double groundTopY) { - double topY = groundTopY; - for (BoxCollider box : boxes) { - if (!overlaps(minX, maxX, box.centerX() - box.halfX(), box.centerX() + box.halfX()) - || !overlaps(minZ, maxZ, box.centerZ() - box.halfZ(), box.centerZ() + box.halfZ())) { - continue; - } - topY = Math.max(topY, box.centerY() + box.halfY()); - } - return topY; - } - - private static boolean overlaps(double minA, double maxA, double minB, double maxB) { - return maxA >= minB && maxB >= minA; - } - - public synchronized void forEachDebugSection(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - SpaceCollisionCache cache = spaces.get(spaceId.value()); - if (cache == null) { - return; - } - - for (CachedSection section : cache.sections.values()) { - consumer.accept(section.debugSection()); - } - } - - @Nonnull - private PhysicsChunkBuildStats ensureSection(@Nonnull World world, - @Nonnull PhysicsSpaceBinding space, - int chunkX, - int sectionY, - int chunkZ, - long tick, - @Nullable Snapshot profiling, - @Nullable StreamingTargetDiagnostic targetDiagnostic, - @Nullable PhysicsChunkSectionAccessCache accessCache, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - long start = profiling != null ? System.nanoTime() : 0L; - if (profiling != null) { - profiling.incrementSectionRequests(); - } - - SpaceCollisionCache cache = - spaces.computeIfAbsent(space.spaceId().value(), ignored -> new SpaceCollisionCache()); - long chunkKey = ChunkUtil.indexChunk(chunkX, chunkZ); - long sectionKey = packSectionKey(chunkX, sectionY, chunkZ); - if (isBackedOff(cache.missingBlockChunkBackoffs, chunkKey, tick)) { - if (profiling != null) { - profiling.incrementMissingBackoffSkip(MissingSectionReason.BLOCK_CHUNK); - profiling.recordMissingSection(MissingSectionReason.BLOCK_CHUNK, - chunkX, - sectionY, - chunkZ, - targetDiagnostic); - profiling.addEnsureSectionNanos(System.nanoTime() - start); - } - return PhysicsChunkBuildStats.empty(); - } - - BlockChunk blockChunk = blockChunk(world, chunkX, chunkZ, accessCache); - if (blockChunk == null) { - cache.missingBlockChunkBackoffs.put(chunkKey, tick + MISSING_BLOCK_CHUNK_RETRY_TICKS); - if (profiling != null) { - profiling.recordMissingSection(MissingSectionReason.BLOCK_CHUNK, - chunkX, - sectionY, - chunkZ, - targetDiagnostic); - profiling.addEnsureSectionNanos(System.nanoTime() - start); - } - return PhysicsChunkBuildStats.empty(); - } - cache.missingBlockChunkBackoffs.remove(chunkKey); - - if (isBackedOff(cache.missingBlockSectionBackoffs, sectionKey, tick)) { - if (profiling != null) { - profiling.incrementMissingBackoffSkip(MissingSectionReason.BLOCK_SECTION); - profiling.recordMissingSection(MissingSectionReason.BLOCK_SECTION, - chunkX, - sectionY, - chunkZ, - targetDiagnostic); - profiling.addEnsureSectionNanos(System.nanoTime() - start); - } - return PhysicsChunkBuildStats.empty(); - } - - BlockSection section = accessCache != null - ? accessCache.blockSection(world, chunkX, sectionY, chunkZ) - : ChunkSectionAccess.blockSection(world, chunkX, sectionY, chunkZ); - if (section == null) { - cache.missingBlockSectionBackoffs.put(sectionKey, tick + MISSING_BLOCK_SECTION_RETRY_TICKS); - if (profiling != null) { - profiling.recordMissingSection(MissingSectionReason.BLOCK_SECTION, - chunkX, - sectionY, - chunkZ, - targetDiagnostic); - profiling.addEnsureSectionNanos(System.nanoTime() - start); - } - return PhysicsChunkBuildStats.empty(); - } - cache.missingBlockSectionBackoffs.remove(sectionKey); - CachedSection cached = cache.sections.get(sectionKey); - long neighborhoodSignature = sectionBuilder.neighborhoodSignature(world, - section, - chunkX, - sectionY, - chunkZ, - accessCache); - if (cached != null - && cached.neighborhoodSignature == neighborhoodSignature - && cached.buildOptions.equals(buildOptions)) { - cached.lastUsedTick = tick; - if (profiling != null) { - profiling.incrementSectionCacheHits(); - profiling.addEnsureSectionNanos(System.nanoTime() - start); - } - return PhysicsChunkBuildStats.empty(); - } - - SectionCollisionGeometry geometry = sectionBuilder.build(world, - section, - chunkX, - sectionY, - chunkZ, - accessCache); - CachedSection built = new CachedSection(chunkX, sectionY, chunkZ, tick, - neighborhoodSignature); - built.buildOptions = buildOptions; - try { - addGeometryBodies(space, - built, - geometry, - chunkX, - sectionY, - chunkZ, - buildOptions); - } catch (RuntimeException exception) { - removeBuiltSectionAfterFailure(space, built, exception); - throw exception; - } - - int removed = 0; - boolean rebuilt = cached != null; - try { - if (cached != null) { - removed = cached.removeFrom(space); - } - } catch (RuntimeException exception) { - removeBuiltSectionAfterFailure(space, built, exception); - throw exception; - } - cache.sections.put(sectionKey, built); - try { - stitchAdjacentVoxelTerrains(space, cache, built); - } catch (RuntimeException exception) { - cache.sections.remove(sectionKey); - removeBuiltSectionAfterFailure(space, built, exception); - throw exception; - } - - PhysicsChunkBuildStats stats = PhysicsChunkBuildStats.from(geometry, - built.backendBodyIds.size(), - removed, - rebuilt ? 0 : 1, - rebuilt ? 1 : 0, - built.voxelTerrain ? 1 : 0); - if (profiling != null) { - profiling.addBuildStats(stats); - profiling.addEnsureSectionNanos(System.nanoTime() - start); - } - return stats; - } - - private static void removeBuiltSectionAfterFailure(@Nonnull PhysicsSpaceBinding space, - @Nonnull CachedSection built, - @Nonnull RuntimeException failure) { - try { - built.removeFrom(space); - } catch (RuntimeException cleanupFailure) { - failure.addSuppressed(cleanupFailure); - } - } - - private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, - @Nonnull CachedSection target, - @Nonnull SectionCollisionGeometry geometry, - int chunkX, - int sectionY, - int chunkZ, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - target.fullCubeBoxes.addAll(geometry.mergedFullCubeBoxes()); - target.detailBoxes.addAll(geometry.detailBoxes()); - if (buildOptions.nativeVoxelTerrainEnabled() - && geometry.hasFullCubeVoxels() - && space.runtime().supportsVoxelTerrain(space.backendSpaceHandle().value())) { - addVoxelTerrain(space, target, geometry, chunkX, sectionY, chunkZ, buildOptions); - } else { - for (BoxCollider box : geometry.mergedFullCubeBoxes()) { - addStaticBox(space, target, box, buildOptions); - } - } - - for (BoxCollider box : geometry.detailBoxes()) { - addStaticBox(space, target, box, buildOptions); - } - } - - private static void addVoxelTerrain(@Nonnull PhysicsSpaceBinding space, - @Nonnull CachedSection section, - @Nonnull SectionCollisionGeometry geometry, - int chunkX, - int sectionY, - int chunkZ, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - long backendBodyId = space.runtime().createVoxelTerrain(space.backendSpaceHandle().value(), - 1.0f, - 1.0f, - 1.0f, - geometry.fullCubeVoxels(), - chunkX << ChunkUtil.BITS, - sectionY << ChunkUtil.BITS, - chunkZ << ChunkUtil.BITS, - buildOptions.terrainFriction(), - buildOptions.terrainRestitution(), - buildOptions.collisionGroup(), - buildOptions.collisionMask()); - section.backendBodyIds.add(backendBodyId); - section.voxelTerrainBodyId = backendBodyId; - section.voxelTerrain = true; - } - - private static void addStaticBox(@Nonnull PhysicsSpaceBinding space, - @Nonnull CachedSection section, - @Nonnull BoxCollider box, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - if (box.halfX() <= 0.0 || box.halfY() <= 0.0 || box.halfZ() <= 0.0) { - return; - } - - long backendBodyId = space.runtime().createBody(space.backendSpaceHandle().value(), - BackendRuntimeCodes.SHAPE_BOX, - (float) box.halfX(), - (float) box.halfY(), - (float) box.halfZ(), - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 0.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC), - (float) box.centerX(), - (float) box.centerY(), - (float) box.centerZ(), - 0.0f, - 0.0f, - 0.0f, - 1.0f); - section.backendBodyIds.add(backendBodyId); - applyTerrainMaterial(space, backendBodyId, buildOptions); - space.runtime() - .setBodyCollisionFilter(space.backendSpaceHandle().value(), - backendBodyId, - buildOptions.collisionGroup(), - buildOptions.collisionMask()); - } - - private static void applyTerrainMaterial(@Nonnull PhysicsSpaceBinding space, - long backendBodyId, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - // TODO: Replace coarse terrain settings with real per-block material lookup. - space.runtime().setBodyFriction(space.backendSpaceHandle().value(), - backendBodyId, - buildOptions.terrainFriction()); - space.runtime().setBodyRestitution(space.backendSpaceHandle().value(), - backendBodyId, - buildOptions.terrainRestitution()); - } - - private static void stitchAdjacentVoxelTerrains(@Nonnull PhysicsSpaceBinding space, - @Nonnull SpaceCollisionCache cache, - @Nonnull CachedSection built) { - if (!built.hasVoxelTerrainBody()) { - return; - } - - stitchVoxelTerrain(space, - built, - cache.section(built.chunkX - 1, built.sectionY, built.chunkZ), - -ADJACENT_SECTION_VOXEL_SHIFT, - 0, - 0); - stitchVoxelTerrain(space, - built, - cache.section(built.chunkX + 1, built.sectionY, built.chunkZ), - ADJACENT_SECTION_VOXEL_SHIFT, - 0, - 0); - stitchVoxelTerrain(space, - built, - cache.section(built.chunkX, built.sectionY - 1, built.chunkZ), - 0, - -ADJACENT_SECTION_VOXEL_SHIFT, - 0); - stitchVoxelTerrain(space, - built, - cache.section(built.chunkX, built.sectionY + 1, built.chunkZ), - 0, - ADJACENT_SECTION_VOXEL_SHIFT, - 0); - stitchVoxelTerrain(space, - built, - cache.section(built.chunkX, built.sectionY, built.chunkZ - 1), - 0, - 0, - -ADJACENT_SECTION_VOXEL_SHIFT); - stitchVoxelTerrain(space, - built, - cache.section(built.chunkX, built.sectionY, built.chunkZ + 1), - 0, - 0, - ADJACENT_SECTION_VOXEL_SHIFT); - } - - private static void stitchVoxelTerrain(@Nonnull PhysicsSpaceBinding space, - @Nonnull CachedSection built, - @Nullable CachedSection neighbor, - int shiftX, - int shiftY, - int shiftZ) { - if (neighbor == null || !neighbor.hasVoxelTerrainBody()) { - return; - } - space.runtime().combineVoxelTerrains(space.backendSpaceHandle().value(), - built.voxelTerrainBodyId, - neighbor.voxelTerrainBodyId, - shiftX, - shiftY, - shiftZ); - } - - @Nullable - private static BlockChunk blockChunk(@Nonnull World world, int chunkX, int chunkZ) { - return blockChunk(world, chunkX, chunkZ, null); - } - - @Nullable - private static BlockChunk blockChunk(@Nonnull World world, - int chunkX, - int chunkZ, - @Nullable PhysicsChunkSectionAccessCache accessCache) { - return accessCache != null - ? accessCache.blockChunk(world, chunkX, chunkZ) - : loadBlockChunk(world, chunkX, chunkZ); - } - - @Nullable - private static BlockChunk loadBlockChunk(@Nonnull World world, int chunkX, int chunkZ) { - Ref chunkRef = world.getChunkStore() - .getChunkReference(ChunkUtil.indexChunk(chunkX, chunkZ)); - if (chunkRef == null || !chunkRef.isValid()) { - return null; - } - Store store = world.getChunkStore().getStore(); - return store.getComponentConcurrent(chunkRef, BlockChunk.getComponentType()); - } - - private static int sleepingBodyStreamingInterval(int ttlTicks) { - return Math.clamp(ttlTicks / 2, 1, SLEEPING_BODY_STREAMING_INTERVAL_TICKS); - } - - private static boolean isBackedOff(@Nonnull Long2LongMap backoffs, long key, long currentTick) { - if (!backoffs.containsKey(key)) { - return false; - } - - long retryTick = backoffs.get(key); - if (currentTick < retryTick) { - return true; - } - - backoffs.remove(key); - return false; - } - - private static void pruneExpiredMissingBackoffs(@Nonnull SpaceCollisionCache cache, long currentTick) { - pruneExpiredMissingBackoffs(cache.missingBlockChunkBackoffs, currentTick); - pruneExpiredMissingBackoffs(cache.missingBlockSectionBackoffs, currentTick); - } - - private static void pruneExpiredMissingBackoffs(@Nonnull Long2LongMap backoffs, long currentTick) { - backoffs.long2LongEntrySet().removeIf(entry -> currentTick >= entry.getLongValue()); - } - - private static void removeSectionBackoffsForChunk(@Nonnull SpaceCollisionCache cache, - int chunkX, - int chunkZ) { - Iterator iterator = cache.missingBlockSectionBackoffs.long2LongEntrySet().iterator(); - while (iterator.hasNext()) { - long key = iterator.next().getLongKey(); - if (unpackChunkX(key) == chunkX && unpackChunkZ(key) == chunkZ) { - iterator.remove(); - } - } - } - - private static long packSectionKey(int chunkX, int sectionY, int chunkZ) { - return ((long) chunkX & 0x3FF_FFFFL) << 38 - | ((long) chunkZ & 0x3FF_FFFFL) << 12 - | (sectionY & 0xFFFL); - } - - private static int unpackChunkX(long sectionKey) { - return signExtend26((int) (sectionKey >>> 38)); - } - - private static int unpackChunkZ(long sectionKey) { - return signExtend26((int) ((sectionKey >>> 12) & 0x3FF_FFFFL)); - } - - private static int signExtend26(int value) { - int signBit = 1 << 25; - return (value ^ signBit) - signBit; - } - - /** - * Per-space cache so pruning and clearing do not scan unrelated spaces. - */ - private static final class SpaceCollisionCache { - - private final Long2ObjectMap sections = new Long2ObjectOpenHashMap<>(); - private final Object2ObjectMap bodyTargets = - new Object2ObjectOpenHashMap<>(); - private final Long2LongMap missingBlockChunkBackoffs = new Long2LongOpenHashMap(); - private final Long2LongMap missingBlockSectionBackoffs = new Long2LongOpenHashMap(); - - private SpaceCollisionCache() { - } - - private SpaceCollisionCache(@Nonnull SpaceCollisionCache other) { - for (Long2ObjectMap.Entry entry : other.sections.long2ObjectEntrySet()) { - sections.put(entry.getLongKey(), new CachedSection(entry.getValue())); - } - for (Object2ObjectMap.Entry entry - : other.bodyTargets.object2ObjectEntrySet()) { - bodyTargets.put(entry.getKey(), new CachedBodyStreamingTarget(entry.getValue())); - } - missingBlockChunkBackoffs.putAll(other.missingBlockChunkBackoffs); - missingBlockSectionBackoffs.putAll(other.missingBlockSectionBackoffs); - } - - @Nullable - private CachedSection section(int chunkX, int sectionY, int chunkZ) { - return sections.get(packSectionKey(chunkX, sectionY, chunkZ)); - } - - private boolean isEmpty() { - return sections.isEmpty() - && bodyTargets.isEmpty() - && missingBlockChunkBackoffs.isEmpty() - && missingBlockSectionBackoffs.isEmpty(); - } - } - - private static final class CachedBodyStreamingTarget { - - private PhysicsChunkStreamingBounds bounds; - private boolean sleeping; - private long lastSeenTick; - private long lastRefreshTick; - - private CachedBodyStreamingTarget(@Nonnull PhysicsChunkStreamingBounds bounds, - boolean sleeping, - long lastSeenTick, - long lastRefreshTick) { - this.bounds = bounds; - this.sleeping = sleeping; - this.lastSeenTick = lastSeenTick; - this.lastRefreshTick = lastRefreshTick; - } - - private CachedBodyStreamingTarget(@Nonnull CachedBodyStreamingTarget other) { - this(other.bounds, other.sleeping, other.lastSeenTick, other.lastRefreshTick); - } - } - - /** - * Generated static backend bodies for one chunk section. - */ - private static final class CachedSection { - - private final int chunkX; - private final int sectionY; - private final int chunkZ; - private final List backendBodyIds = new ArrayList<>(); - private final List fullCubeBoxes = new ArrayList<>(); - private final List detailBoxes = new ArrayList<>(); - private final long neighborhoodSignature; - @Nonnull - private PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.DEFAULT; - private boolean voxelTerrain; - private long voxelTerrainBodyId; - private long lastUsedTick; - - private CachedSection(int chunkX, - int sectionY, - int chunkZ, - long lastUsedTick, - long neighborhoodSignature) { - this.chunkX = chunkX; - this.sectionY = sectionY; - this.chunkZ = chunkZ; - this.lastUsedTick = lastUsedTick; - this.neighborhoodSignature = neighborhoodSignature; - } - - private CachedSection(@Nonnull CachedSection other) { - this(other.chunkX, - other.sectionY, - other.chunkZ, - other.lastUsedTick, - other.neighborhoodSignature); - backendBodyIds.addAll(other.backendBodyIds); - fullCubeBoxes.addAll(other.fullCubeBoxes); - detailBoxes.addAll(other.detailBoxes); - buildOptions = other.buildOptions; - voxelTerrain = other.voxelTerrain; - voxelTerrainBodyId = other.voxelTerrainBodyId; - } - - private int removeFrom(@Nonnull PhysicsSpaceBinding space) { - int removed = backendBodyIds.size(); - for (long backendBodyId : backendBodyIds) { - space.runtime().removeBody(space.backendSpaceHandle().value(), backendBodyId); - } - backendBodyIds.clear(); - voxelTerrainBodyId = 0L; - voxelTerrain = false; - return removed; - } - - private boolean hasVoxelTerrainBody() { - return voxelTerrain && voxelTerrainBodyId != 0L; - } - - @Nonnull - private DebugSection debugSection() { - return new DebugSection(chunkX, - sectionY, - chunkZ, - backendBodyIds.size(), - voxelTerrain, - List.copyOf(fullCubeBoxes), - List.copyOf(detailBoxes)); - } - } - - /** - * Immutable debug snapshot for one cached PhysicsChunk terrain section. - */ - public record DebugSection(int chunkX, - int sectionY, - int chunkZ, - int bodyCount, - boolean voxelTerrain, - @Nonnull List fullCubeBoxes, - @Nonnull List detailBoxes) { - } - - /** - * Highest cached terrain surface found under a queried body footprint. - */ - public record GroundProbe(boolean found, double topY) { - - @Nonnull - private static GroundProbe missing() { - return new GroundProbe(false, Double.NaN); - } - } - - public enum TargetRefreshReason { - FIRST_SEEN, - BOUNDS_CHANGED, - PENDING_APPLY, - ACTIVE_INTERVAL, - SLEEPING_INTERVAL, - STABLE_SKIP - } - - public record TargetRefreshDecision(boolean refresh, @Nonnull TargetRefreshReason reason) { - - @Nonnull - private static TargetRefreshDecision refresh(@Nonnull TargetRefreshReason reason) { - return new TargetRefreshDecision(true, reason); - } - - @Nonnull - private static TargetRefreshDecision skip() { - return new TargetRefreshDecision(false, TargetRefreshReason.STABLE_SKIP); - } - } - -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java deleted file mode 100644 index 972a664f..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/VoxelTerrainCollisionCacheTest.java +++ /dev/null @@ -1,718 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.hypixel.hytale.math.util.ChunkUtil; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.CombineCall; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.VoxelTerrainCall; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Vector3d; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class VoxelTerrainCollisionCacheTest { - - @Test - void streamingApplyGateAllowsOnlyOnePendingMutation() { - VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); - - assertFalse(cache.isStreamingApplyPending()); - assertTrue(cache.tryBeginStreamingApply()); - assertTrue(cache.isStreamingApplyPending()); - assertFalse(cache.tryBeginStreamingApply()); - - cache.finishStreamingApply(); - - assertFalse(cache.isStreamingApplyPending()); - assertTrue(cache.tryBeginStreamingApply()); - } - - @Test - void copyFromDoesNotInheritPendingStreamingApply() { - VoxelTerrainCollisionCache source = new VoxelTerrainCollisionCache(); - VoxelTerrainCollisionCache target = new VoxelTerrainCollisionCache(); - - assertTrue(source.tryBeginStreamingApply()); - - target.copyFrom(source); - - assertFalse(target.isStreamingApplyPending()); - } - - @Test - void copyFromDeepCopiesCachedSectionBodyIds() throws Exception { - RuntimeFixture fixture = runtimeFixture("test:copy-section-isolation", true); - VoxelTerrainCollisionCache source = new VoxelTerrainCollisionCache(); - VoxelTerrainCollisionCache target = new VoxelTerrainCollisionCache(); - Object spaceCache = newSpaceCollisionCache(); - Object sourceSection = newCachedSection(1, 2, 3); - long copiedBodyId = createVoxelTerrain(fixture); - markVoxelTerrain(sourceSection, copiedBodyId); - putCachedSection(spaceCache, sourceSection); - putSpaceCache(source, fixture.binding().spaceId(), spaceCache); - - target.copyFrom(source); - long sourceOnlyBodyId = createVoxelTerrain(fixture); - addBackendBodyId(sourceSection, sourceOnlyBodyId); - - assertEquals(2, source.bodyCount(fixture.binding().spaceId())); - assertEquals(1, target.bodyCount(fixture.binding().spaceId())); - assertTrue(target.containsBody(fixture.binding().spaceId(), copiedBodyId)); - assertFalse(target.containsBody(fixture.binding().spaceId(), sourceOnlyBodyId)); - } - - @Test - void bodyTargetCacheRefreshesActiveBodiesEveryFourTicks() { - VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); - PhysicsChunkProfilingResource.Snapshot snapshot = - new PhysicsChunkProfilingResource.Snapshot(); - SpaceId spaceId = new SpaceId(1001); - UUID bodyId = bodyId(1); - PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); - - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, snapshot) - .refresh()); - cache.recordBodyTargetRefresh(spaceId, bodyId, bounds, false, 1L); - assertFalse(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 2L, 100, snapshot) - .refresh()); - assertFalse(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 3L, 100, snapshot) - .refresh()); - assertFalse(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 4L, 100, snapshot) - .refresh()); - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 5L, 100, snapshot) - .refresh()); - cache.recordBodyTargetRefresh(spaceId, bodyId, bounds, false, 5L); - - assertEquals(1, snapshot.getBodyTargetFirstSeen()); - assertEquals(4, snapshot.getBodyTargetCacheHits()); - assertEquals(3, snapshot.getBodyTargetActiveStableSkips()); - assertEquals(1, snapshot.getBodyTargetActiveRefreshes()); - } - - @Test - void bodyTargetCacheRefreshesSleepingBodiesOnTtlBoundedInterval() { - VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); - PhysicsChunkProfilingResource.Snapshot snapshot = - new PhysicsChunkProfilingResource.Snapshot(); - SpaceId spaceId = new SpaceId(1002); - UUID bodyId = bodyId(2); - PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); - - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, true, 1L, 100, snapshot) - .refresh()); - cache.recordBodyTargetRefresh(spaceId, bodyId, bounds, true, 1L); - assertFalse(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, true, 2L, 100, snapshot) - .refresh()); - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, true, 21L, 100, snapshot) - .refresh()); - cache.recordBodyTargetRefresh(spaceId, bodyId, bounds, true, 21L); - - assertEquals(1, snapshot.getBodyTargetFirstSeen()); - assertEquals(2, snapshot.getBodyTargetCacheHits()); - assertEquals(1, snapshot.getBodyTargetSleepingStableSkips()); - assertEquals(1, snapshot.getBodyTargetSleepingRefreshes()); - } - - @Test - void bodyTargetCacheRefreshesImmediatelyWhenBoundsChange() { - VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); - PhysicsChunkProfilingResource.Snapshot snapshot = - new PhysicsChunkProfilingResource.Snapshot(); - SpaceId spaceId = new SpaceId(1003); - UUID bodyId = bodyId(3); - - assertTrue(cache.shouldRefreshBodyTarget(spaceId, - bodyId, - boundsAt(10.0f, 65.0f, 10.0f), - false, - 1L, - 100, - snapshot).refresh()); - cache.recordBodyTargetRefresh(spaceId, - bodyId, - boundsAt(10.0f, 65.0f, 10.0f), - false, - 1L); - assertTrue(cache.shouldRefreshBodyTarget(spaceId, - bodyId, - boundsAt(40.0f, 65.0f, 10.0f), - false, - 2L, - 100, - snapshot).refresh()); - cache.recordBodyTargetRefresh(spaceId, - bodyId, - boundsAt(40.0f, 65.0f, 10.0f), - false, - 2L); - - assertEquals(1, snapshot.getBodyTargetFirstSeen()); - assertEquals(1, snapshot.getBodyTargetCacheHits()); - assertEquals(1, snapshot.getBodyTargetBoundsChanged()); - } - - @Test - void bodyTargetRefreshIsNotConsumedUntilTerrainApplyRecordsIt() { - VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); - SpaceId spaceId = new SpaceId(1005); - UUID bodyId = bodyId(5); - PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); - - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, null) - .refresh()); - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 2L, 100, null) - .refresh()); - - cache.recordBodyTargetRefresh(spaceId, bodyId, bounds, false, 2L); - - assertFalse(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 3L, 100, null) - .refresh()); - } - - @Test - void bodyTargetCachePrunesBodiesThatDisappearPastDoubleTtl() { - VoxelTerrainCollisionCache cache = new VoxelTerrainCollisionCache(); - PhysicsChunkProfilingResource.Snapshot snapshot = - new PhysicsChunkProfilingResource.Snapshot(); - SpaceId spaceId = new SpaceId(1004); - UUID bodyId = bodyId(4); - PhysicsChunkStreamingBounds bounds = boundsAt(10.0f, 65.0f, 10.0f); - - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 1L, 100, snapshot) - .refresh()); - cache.recordBodyTargetRefresh(spaceId, bodyId, bounds, false, 1L); - assertEquals(1, cache.pruneBodyStreamingTargets(spaceId, 202L, 100, snapshot)); - assertTrue(cache.shouldRefreshBodyTarget(spaceId, bodyId, bounds, false, 203L, 100, snapshot) - .refresh()); - cache.recordBodyTargetRefresh(spaceId, bodyId, bounds, false, 203L); - - assertEquals(2, snapshot.getBodyTargetFirstSeen()); - assertEquals(1, snapshot.getBodyTargetsPruned()); - } - - @Test - void absentVoxelCapabilityFallsBackToMergedFullCubeBoxes() throws Exception { - RuntimeFixture fixture = runtimeFixture("test:voxel-runtime-fallback", false); - SectionCollisionGeometry geometry = new SectionCollisionGeometry(new int[] {0, 0, 0, 1, 0, 0}, - List.of(new BoxCollider(1.0, 0.5, 0.5, 1.0, 0.5, 0.5)), - List.of(new BoxCollider(4.25, 2.25, 4.25, 0.25, 0.25, 0.25)), - 4096, - 2, - 0, - 1); - Object cachedSection = newCachedSection(); - - assertDoesNotThrow(() -> addGeometryBodies(fixture.binding(), - cachedSection, - geometry, - 0, - 0, - 0)); - - VoxelTerrainCollisionCache.DebugSection debugSection = debugSection(cachedSection); - assertEquals(2, fixture.runtime().bodyCount(fixture.backendSpaceId())); - assertEquals(0, fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()).size()); - assertFalse(debugSection.voxelTerrain()); - assertEquals(1, debugSection.fullCubeBoxes().size()); - assertEquals(1, debugSection.detailBoxes().size()); - } - - @Test - void supportedRuntimeCreatesVoxelTerrainAndKeepsDebugBoxes() throws Throwable { - RuntimeFixture fixture = runtimeFixture("test:voxel-runtime-native", true); - SectionCollisionGeometry geometry = new SectionCollisionGeometry(new int[] {0, 0, 0, 1, 0, 0}, - List.of(new BoxCollider(40.0, 48.5, 64.0, 1.0, 0.5, 0.5)), - List.of(new BoxCollider(36.25, 50.25, 68.25, 0.25, 0.25, 0.25)), - 4096, - 2, - 0, - 1); - Object cachedSection = newCachedSection(2, 3, 4); - - addGeometryBodies(fixture.binding(), cachedSection, geometry, 2, 3, 4); - - List calls = fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()); - VoxelTerrainCollisionCache.DebugSection debugSection = debugSection(cachedSection); - assertEquals(1, calls.size()); - assertArrayEquals(new int[] {0, 0, 0, 1, 0, 0}, calls.getFirst().voxelCoordinates()); - assertEquals((float) (2 << ChunkUtil.BITS), calls.getFirst().positionX()); - assertEquals((float) (3 << ChunkUtil.BITS), calls.getFirst().positionY()); - assertEquals((float) (4 << ChunkUtil.BITS), calls.getFirst().positionZ()); - assertEquals(0.75f, calls.getFirst().friction()); - assertEquals(0.0f, calls.getFirst().restitution()); - assertEquals(PhysicsCollisionFilters.TERRAIN, calls.getFirst().collisionGroup()); - assertEquals(PhysicsCollisionFilters.ALL, calls.getFirst().collisionMask()); - assertEquals(2, fixture.runtime().bodyCount(fixture.backendSpaceId())); - assertTrue(debugSection.voxelTerrain()); - assertEquals(1, debugSection.fullCubeBoxes().size()); - assertEquals(1, debugSection.detailBoxes().size()); - } - - @Test - void terrainMaterialAndCollisionFilterApplyToNativeVoxelAndFallbackBoxes() throws Throwable { - PhysicsChunkTerrainSettings nativeSettings = new PhysicsChunkTerrainSettings(); - nativeSettings.setNativeVoxelTerrainEnabled(true); - nativeSettings.setTerrainMaterial(0.9f, 0.25f); - RuntimeFixture nativeFixture = runtimeFixture("test:voxel-runtime-custom-material", true); - SectionCollisionGeometry nativeGeometry = new SectionCollisionGeometry(new int[] {0, 0, 0}, - List.of(new BoxCollider(1.0, 0.5, 0.5, 1.0, 0.5, 0.5)), - List.of(), - 4096, - 1, - 0, - 0); - Object nativeSection = newCachedSection(); - - addGeometryBodies(nativeFixture.binding(), - nativeSection, - nativeGeometry, - 0, - 0, - 0, - buildOptions(nativeSettings, 0x40, 0x07)); - - List calls = - nativeFixture.runtime().voxelTerrainCalls(nativeFixture.backendSpaceId()); - assertEquals(1, calls.size()); - assertEquals(0.9f, calls.getFirst().friction(), 0.0001f); - assertEquals(0.25f, calls.getFirst().restitution(), 0.0001f); - assertEquals(0x40, calls.getFirst().collisionGroup()); - assertEquals(0x07, calls.getFirst().collisionMask()); - - PhysicsChunkTerrainSettings fallbackSettings = new PhysicsChunkTerrainSettings(); - fallbackSettings.setTerrainMaterial(0.8f, 0.1f); - RuntimeFixture fallbackFixture = runtimeFixture("test:box-runtime-custom-material", false); - Object fallbackSection = newCachedSection(); - - addGeometryBodies(fallbackFixture.binding(), - fallbackSection, - nativeGeometry, - 0, - 0, - 0, - buildOptions(fallbackSettings, 0x20, 0x05)); - - long fallbackBodyId = firstBackendBodyId(fallbackSection); - var snapshot = PhysicsBodySnapshots.read(fallbackFixture.binding(), fallbackBodyId); - assertEquals(0.8f, snapshot.friction(), 0.0001f); - assertEquals(0.1f, snapshot.restitution(), 0.0001f); - assertEquals(0x20, snapshot.collisionGroup()); - assertEquals(0x05, snapshot.collisionMask()); - } - - @Test - void disabledNativeVoxelTerrainFallsBackToMergedFullCubeBoxes() throws Throwable { - RuntimeFixture fixture = runtimeFixture("test:voxel-runtime-disabled", true); - SectionCollisionGeometry geometry = new SectionCollisionGeometry(new int[] {0, 0, 0, 1, 0, 0}, - List.of(new BoxCollider(1.0, 0.5, 0.5, 1.0, 0.5, 0.5)), - List.of(), - 4096, - 2, - 0, - 0); - Object cachedSection = newCachedSection(); - - addGeometryBodies(fixture.binding(), cachedSection, geometry, 0, 0, 0, false); - - VoxelTerrainCollisionCache.DebugSection debugSection = debugSection(cachedSection); - assertEquals(1, fixture.runtime().bodyCount(fixture.backendSpaceId())); - assertEquals(0, fixture.runtime().voxelTerrainCalls(fixture.backendSpaceId()).size()); - assertFalse(debugSection.voxelTerrain()); - assertEquals(1, debugSection.fullCubeBoxes().size()); - } - - @Test - void boxFallbackConfigurationFailureRemovesCreatedBackendBody() throws Throwable { - RuntimeFixture fixture = runtimeFixture("test:box-runtime-cleanup", false); - fixture.provider().failNextBodyFriction(new IllegalStateException("forced friction failure")); - SectionCollisionGeometry geometry = new SectionCollisionGeometry(new int[] {0, 0, 0}, - List.of(new BoxCollider(1.0, 0.5, 0.5, 1.0, 0.5, 0.5)), - List.of(), - 4096, - 1, - 0, - 0); - Object cachedSection = newCachedSection(); - - IllegalStateException failure = assertThrows(IllegalStateException.class, - () -> addGeometryBodies(fixture.binding(), cachedSection, geometry, 0, 0, 0)); - removeBuiltSectionAfterFailure(fixture.binding(), cachedSection, failure); - - assertEquals(0, fixture.runtime().bodyCount(fixture.backendSpaceId())); - assertEquals(0, debugSection(cachedSection).bodyCount()); - } - - @Test - void stitchesVoxelTerrainToSixAdjacentSections() throws Throwable { - RuntimeFixture fixture = runtimeFixture("test:voxel-runtime-stitch", true); - Object cache = newSpaceCollisionCache(); - Object center = newCachedSection(10, 5, 20); - long centerBody = createVoxelTerrain(fixture); - markVoxelTerrain(center, centerBody); - putCachedSection(cache, center); - - Object west = cachedVoxelNeighbor(fixture, cache, 9, 5, 20); - Object east = cachedVoxelNeighbor(fixture, cache, 11, 5, 20); - Object down = cachedVoxelNeighbor(fixture, cache, 10, 4, 20); - Object up = cachedVoxelNeighbor(fixture, cache, 10, 6, 20); - Object north = cachedVoxelNeighbor(fixture, cache, 10, 5, 19); - Object south = cachedVoxelNeighbor(fixture, cache, 10, 5, 21); - - stitchAdjacentVoxelTerrains(fixture.binding(), cache, center); - - assertEquals(List.of(new CombineCall(centerBody, voxelTerrainBodyId(west), -16, 0, 0), - new CombineCall(centerBody, voxelTerrainBodyId(east), 16, 0, 0), - new CombineCall(centerBody, voxelTerrainBodyId(down), 0, -16, 0), - new CombineCall(centerBody, voxelTerrainBodyId(up), 0, 16, 0), - new CombineCall(centerBody, voxelTerrainBodyId(north), 0, 0, -16), - new CombineCall(centerBody, voxelTerrainBodyId(south), 0, 0, 16)), - fixture.runtime().combineCalls(fixture.backendSpaceId())); - } - - @Test - void clearSectionsAroundKeepsDistantCachedTerrain() throws Exception { - RuntimeFixture fixture = runtimeFixture("test:section-radius-clear", true); - VoxelTerrainCollisionCache worldCache = new VoxelTerrainCollisionCache(); - Object spaceCache = newSpaceCollisionCache(); - Object near = newCachedSection(0, 2, 0); - long nearBody = createVoxelTerrain(fixture); - markVoxelTerrain(near, nearBody); - putCachedSection(spaceCache, near); - Object far = newCachedSection(8, 2, 8); - long farBody = createVoxelTerrain(fixture); - markVoxelTerrain(far, farBody); - putCachedSection(spaceCache, far); - putSpaceCache(worldCache, fixture.binding().spaceId(), spaceCache); - - int removed = worldCache.clearSectionsAround(fixture.binding().spaceId(), - fixture.binding(), - new Vector3d(8.0, 65.0, 8.0), - 8); - - assertEquals(1, removed); - assertEquals(1, worldCache.sectionCount()); - assertFalse(worldCache.containsBody(fixture.binding().spaceId(), nearBody)); - assertTrue(worldCache.containsBody(fixture.binding().spaceId(), farBody)); - assertEquals(1, fixture.runtime().bodyCount(fixture.backendSpaceId())); - } - - private static UUID bodyId(long leastSignificantBits) { - return new UUID(0L, leastSignificantBits); - } - - private static PhysicsChunkStreamingBounds boundsAt(float x, float y, float z) { - return PhysicsChunkStreamingBounds.from(new Vector3f(x, y, z), 4); - } - - private static Object newCachedSection() throws Exception { - return newCachedSection(0, 0, 0); - } - - private static Object newCachedSection(int chunkX, int sectionY, int chunkZ) throws Exception { - Class sectionType = nestedClass("CachedSection"); - Constructor constructor = sectionType.getDeclaredConstructor(int.class, - int.class, - int.class, - long.class, - long.class); - constructor.setAccessible(true); - return constructor.newInstance(chunkX, sectionY, chunkZ, 0L, 1L); - } - - @Nonnull - private static PhysicsChunkBuildOptions buildOptions(@Nonnull PhysicsChunkTerrainSettings settings, - int collisionGroup, - int collisionMask) { - return new PhysicsChunkBuildOptions( - ChunkCollisionMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), - settings.getTerrainFriction(), - settings.getTerrainRestitution(), - collisionGroup, - collisionMask); - } - - private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, - @Nonnull Object target, - @Nonnull SectionCollisionGeometry geometry, - int chunkX, - int sectionY, - int chunkZ) throws Throwable { - addGeometryBodies(space, target, geometry, chunkX, sectionY, chunkZ, true); - } - - private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, - @Nonnull Object target, - @Nonnull SectionCollisionGeometry geometry, - int chunkX, - int sectionY, - int chunkZ, - boolean nativeVoxelTerrainEnabled) throws Throwable { - addGeometryBodies(space, - target, - geometry, - chunkX, - sectionY, - chunkZ, - PhysicsChunkBuildOptions.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled)); - } - - private static void addGeometryBodies(@Nonnull PhysicsSpaceBinding space, - @Nonnull Object target, - @Nonnull SectionCollisionGeometry geometry, - int chunkX, - int sectionY, - int chunkZ, - @Nonnull PhysicsChunkBuildOptions buildOptions) throws Throwable { - Method method = Arrays.stream(VoxelTerrainCollisionCache.class.getDeclaredMethods()) - .filter(candidate -> candidate.getName().equals("addGeometryBodies")) - .findFirst() - .orElseThrow(); - method.setAccessible(true); - try { - method.invoke(null, addGeometryBodiesArguments(method, - space, - target, - geometry, - chunkX, - sectionY, - chunkZ, - buildOptions)); - } catch (InvocationTargetException exception) { - throw exception.getCause(); - } - } - - @Nonnull - private static Object[] addGeometryBodiesArguments(@Nonnull Method method, - @Nonnull PhysicsSpaceBinding space, - @Nonnull Object target, - @Nonnull SectionCollisionGeometry geometry, - int chunkX, - int sectionY, - int chunkZ, - @Nonnull PhysicsChunkBuildOptions buildOptions) { - Object[] arguments = new Object[method.getParameterCount()]; - int integerIndex = 0; - for (int index = 0; index < arguments.length; index++) { - Class type = method.getParameterTypes()[index]; - if (type == PhysicsSpaceBinding.class) { - arguments[index] = space; - } else if (type == SectionCollisionGeometry.class) { - arguments[index] = geometry; - } else if (type.getSimpleName().equals("CachedSection")) { - arguments[index] = target; - } else if (type == int.class) { - arguments[index] = switch (integerIndex++) { - case 0 -> chunkX; - case 1 -> sectionY; - case 2 -> chunkZ; - default -> throw new IllegalStateException("Unexpected integer parameter"); - }; - } else if (type == boolean.class) { - arguments[index] = buildOptions.nativeVoxelTerrainEnabled(); - } else if (type == PhysicsChunkBuildOptions.class) { - arguments[index] = buildOptions; - } else { - arguments[index] = null; - } - } - return arguments; - } - - private static void stitchAdjacentVoxelTerrains(@Nonnull PhysicsSpaceBinding space, - @Nonnull Object cache, - @Nonnull Object built) throws Throwable { - Method method = Arrays.stream(VoxelTerrainCollisionCache.class.getDeclaredMethods()) - .filter(candidate -> candidate.getName().equals("stitchAdjacentVoxelTerrains")) - .findFirst() - .orElseThrow(); - method.setAccessible(true); - try { - method.invoke(null, space, cache, built); - } catch (InvocationTargetException exception) { - throw exception.getCause(); - } - } - - private static void removeBuiltSectionAfterFailure(@Nonnull PhysicsSpaceBinding space, - @Nonnull Object built, - @Nonnull RuntimeException failure) throws Throwable { - Method method = VoxelTerrainCollisionCache.class.getDeclaredMethod("removeBuiltSectionAfterFailure", - PhysicsSpaceBinding.class, - nestedClass("CachedSection"), - RuntimeException.class); - method.setAccessible(true); - try { - method.invoke(null, space, built, failure); - } catch (InvocationTargetException exception) { - throw exception.getCause(); - } - } - - private static Object newSpaceCollisionCache() throws Exception { - Class cacheType = nestedClass("SpaceCollisionCache"); - Constructor constructor = cacheType.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor.newInstance(); - } - - @SuppressWarnings("unchecked") - private static void putCachedSection(@Nonnull Object cache, @Nonnull Object section) throws Exception { - Method keyMethod = VoxelTerrainCollisionCache.class.getDeclaredMethod("packSectionKey", - int.class, - int.class, - int.class); - keyMethod.setAccessible(true); - long key = (long) keyMethod.invoke(null, - intField(section, "chunkX"), - intField(section, "sectionY"), - intField(section, "chunkZ")); - Field sectionsField = cache.getClass().getDeclaredField("sections"); - sectionsField.setAccessible(true); - ((Map) sectionsField.get(cache)).put(key, section); - } - - @SuppressWarnings("unchecked") - private static void putSpaceCache(@Nonnull VoxelTerrainCollisionCache worldCache, - @Nonnull SpaceId spaceId, - @Nonnull Object cache) throws Exception { - Field spacesField = VoxelTerrainCollisionCache.class.getDeclaredField("spaces"); - spacesField.setAccessible(true); - ((Map) spacesField.get(worldCache)).put(spaceId.value(), cache); - } - - private static Object cachedVoxelNeighbor(@Nonnull RuntimeFixture fixture, - @Nonnull Object cache, - int chunkX, - int sectionY, - int chunkZ) throws Exception { - Object section = newCachedSection(chunkX, sectionY, chunkZ); - markVoxelTerrain(section, createVoxelTerrain(fixture)); - putCachedSection(cache, section); - return section; - } - - private static long createVoxelTerrain(@Nonnull RuntimeFixture fixture) { - return fixture.runtime().createVoxelTerrain(fixture.backendSpaceId(), - 1.0f, - 1.0f, - 1.0f, - new int[] {0, 0, 0}, - 0.0f, - 0.0f, - 0.0f, - 0.75f, - 0.0f, - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); - } - - @SuppressWarnings("unchecked") - private static void markVoxelTerrain(@Nonnull Object section, long backendBodyId) throws Exception { - addBackendBodyId(section, backendBodyId); - setBooleanField(section, "voxelTerrain", true); - setLongField(section, "voxelTerrainBodyId", backendBodyId); - } - - @SuppressWarnings("unchecked") - private static void addBackendBodyId(@Nonnull Object section, long backendBodyId) throws Exception { - Field backendBodyIds = section.getClass().getDeclaredField("backendBodyIds"); - backendBodyIds.setAccessible(true); - ((List) backendBodyIds.get(section)).add(backendBodyId); - } - - @SuppressWarnings("unchecked") - private static long firstBackendBodyId(@Nonnull Object section) throws Exception { - Field backendBodyIds = section.getClass().getDeclaredField("backendBodyIds"); - backendBodyIds.setAccessible(true); - return ((List) backendBodyIds.get(section)).getFirst(); - } - - private static long voxelTerrainBodyId(@Nonnull Object section) throws Exception { - Field field = section.getClass().getDeclaredField("voxelTerrainBodyId"); - field.setAccessible(true); - return field.getLong(section); - } - - private static VoxelTerrainCollisionCache.DebugSection debugSection(@Nonnull Object section) throws Exception { - Method method = section.getClass().getDeclaredMethod("debugSection"); - method.setAccessible(true); - return (VoxelTerrainCollisionCache.DebugSection) method.invoke(section); - } - - private static int intField(@Nonnull Object target, @Nonnull String name) throws Exception { - Field field = target.getClass().getDeclaredField(name); - field.setAccessible(true); - return field.getInt(target); - } - - private static void setBooleanField(@Nonnull Object target, - @Nonnull String name, - boolean value) throws Exception { - Field field = target.getClass().getDeclaredField(name); - field.setAccessible(true); - field.setBoolean(target, value); - } - - private static void setLongField(@Nonnull Object target, - @Nonnull String name, - long value) throws Exception { - Field field = target.getClass().getDeclaredField(name); - field.setAccessible(true); - field.setLong(target, value); - } - - @Nonnull - private static Class nestedClass(@Nonnull String simpleName) { - return Arrays.stream(VoxelTerrainCollisionCache.class.getDeclaredClasses()) - .filter(candidate -> candidate.getSimpleName().equals(simpleName)) - .findFirst() - .orElseThrow(); - } - - private static RuntimeFixture runtimeFixture(@Nonnull String backendId, boolean voxelTerrain) { - BackendId id = new BackendId(backendId); - FakePhysicsBackendRuntimeProvider provider = - new FakePhysicsBackendRuntimeProvider(id, false, voxelTerrain); - FakePhysicsBackendRuntime runtime = (FakePhysicsBackendRuntime) provider.createRuntime(); - SpaceId spaceId = new SpaceId(42); - int backendSpaceId = runtime.createSpace(spaceId); - return new RuntimeFixture(provider, - new PhysicsSpaceBinding(id, - spaceId, - new BackendSpaceHandle(backendSpaceId), - runtime), - runtime, - backendSpaceId); - } - - private record RuntimeFixture(@Nonnull FakePhysicsBackendRuntimeProvider provider, - @Nonnull PhysicsSpaceBinding binding, - @Nonnull FakePhysicsBackendRuntime runtime, - int backendSpaceId) { - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java new file mode 100644 index 00000000..52b4a691 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -0,0 +1,322 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.CombineCall; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.Neighbor; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class ChunkCollisionVoxelStitchingSystemTest { + + @Test + void voxelRowsAreStitchedThroughBodyRuntimeHandlesOncePerPayload() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-stitch-row-test")), + EmptyResourceStorage.get()); + try { + BackendId backendId = new BackendId("test:chunk-collision-stitch"); + UUID spaceUuid = uuid(1); + RuntimeFixture runtime = addBoundSpace(store, spaceUuid, backendId); + String firstSourceKey = "0:0:0"; + String secondSourceKey = "1:0:0"; + String firstPayloadKey = "chunk-collision/0/0/0"; + String secondPayloadKey = "chunk-collision/1/0/0"; + Ref firstRef = addVoxelRow(store, + runtime, + spaceUuid, + firstSourceKey, + firstPayloadKey, + 0, + 0, + 0); + Ref secondRef = addVoxelRow(store, + runtime, + spaceUuid, + secondSourceKey, + secondPayloadKey, + 1, + 0, + 0); + long firstBodyId = store.getResource(PhysicsRuntimeResource.getResourceType()) + .getBodyHandle(firstRef) + .value(); + long secondBodyId = store.getResource(PhysicsRuntimeResource.getResourceType()) + .getBodyHandle(secondRef) + .value(); + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .put(firstPayloadKey, + payloadWithNeighbors(List.of(new Neighbor(secondSourceKey, 16, 0, 0)))); + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .put(secondPayloadKey, + payloadWithNeighbors(List.of(new Neighbor(firstSourceKey, -16, 0, 0)))); + + runStitchingSystem(store); + runStitchingSystem(store); + + List calls = runtime.backendRuntime().combineCalls(runtime.spaceHandle() + .value()); + assertEquals(1, calls.size()); + CombineCall call = calls.getFirst(); + assertEquals(firstBodyId, call.bodyAId()); + assertEquals(secondBodyId, call.bodyBId()); + assertEquals(16, call.shiftX()); + assertEquals(0, call.shiftY()); + assertEquals(0, call.shiftZ()); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static RuntimeFixture addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + + FakePhysicsBackendRuntime runtime = (FakePhysicsBackendRuntime) + new FakePhysicsBackendRuntimeProvider(backendId, false, true).createRuntime(); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(runtime.createSpace(new SpaceId(42))); + PhysicsRuntimeResource runtimeResource = store.getResource( + PhysicsRuntimeResource.getResourceType()); + runtimeResource.putRuntime(backendId, runtime); + runtimeResource.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + identity.putSpaceHandle(spaceHandle, spaceRef); + return new RuntimeFixture(spaceRef, spaceHandle, runtime); + } + + @Nonnull + private static Ref addVoxelRow(@Nonnull Store store, + @Nonnull RuntimeFixture runtime, + @Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + @Nonnull String payloadKey, + int chunkX, + int sectionY, + int chunkZ) { + UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.VOXEL_TERRAIN, + 0); + BodyComponent body = new BodyComponent(spaceUuid, + PhysicsBodyKind.TERRAIN, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + body.setSpaceRef(runtime.spaceRef()); + Holder holder = PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.STATIC, 0.0f, 0.0f, 0.0f, false), + target(chunkX, sectionY, chunkZ), + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.VOXELS, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + payloadKey), + new MaterialComponent(0.7f, 0.05f), + new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL)); + holder.addComponent(ChunkCollisionSourceComponent.getComponentType(), + new ChunkCollisionSourceComponent(sourceKey, + chunkX, + sectionY, + chunkZ, + payloadKey, + PartKind.VOXEL_TERRAIN, + 0)); + Ref bodyRef = store.addEntity(holder, AddReason.SPAWN); + assertNotNull(bodyRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(bodyUuid, bodyRef); + store.getExternalData().putRefForUUID(bodyUuid, bodyRef); + + long bodyHandle = runtime.backendRuntime() + .createVoxelTerrain(runtime.spaceHandle().value(), + 1.0f, + 1.0f, + 1.0f, + new int[] {0, 0, 0}, + chunkX * 16.0f, + sectionY * 16.0f, + chunkZ * 16.0f, + 0.7f, + 0.05f, + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL); + BackendBodyHandle backendBodyHandle = new BackendBodyHandle(bodyHandle); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .putBodyHandle(bodyUuid, + bodyRef, + spaceUuid, + runtime.spaceHandle(), + backendBodyHandle); + identity.putBodyHandle(backendBodyHandle, bodyRef); + return bodyRef; + } + + @Nonnull + private static TargetComponent target(int chunkX, int sectionY, int chunkZ) { + TargetComponent target = new TargetComponent(); + target.setPosition(new Vector3f(chunkX * 16.0f, sectionY * 16.0f, chunkZ * 16.0f)); + return target; + } + + @Nonnull + private static ChunkCollisionPayload payloadWithNeighbors( + @Nonnull List neighbors) { + return new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[] {0, 0, 0}, + List.of(), + List.of(), + true, + 0.7f, + 0.05f, + PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL, + neighbors); + } + + private static void runStitchingSystem(@Nonnull Store store) { + try { + ChunkCollisionVoxelStitchingSystem system = new ChunkCollisionVoxelStitchingSystem(); + Method stitchChunk = ChunkCollisionVoxelStitchingSystem.class.getDeclaredMethod( + "stitchChunk", + PhysicsRuntimeResource.class, + PhysicsIdentityIndexResource.class, + PhysicsChunkCollisionPayloadResource.class, + PhysicsRestoreStatusResource.class, + Set.class, + ArchetypeChunk.class); + stitchChunk.setAccessible(true); + Set stitchedPairs = new HashSet<>(); + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsChunkCollisionPayloadResource payloads = store.getResource( + PhysicsChunkCollisionPayloadResource.getResourceType()); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> invoke(stitchChunk, + runtime, + identity, + payloads, + restore, + stitchedPairs, + chunk); + store.forEachChunk(system.getQuery(), collector); + } catch (NoSuchMethodException exception) { + throw new AssertionError("Could not run ChunkCollisionVoxelStitchingSystem", + exception); + } + } + + private static void invoke(@Nonnull Method method, @Nonnull Object... arguments) { + try { + method.invoke(null, arguments); + } catch (IllegalAccessException exception) { + throw new AssertionError(exception); + } catch (InvocationTargetException exception) { + Throwable cause = exception.getCause(); + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + if (cause instanceof Error error) { + throw error; + } + throw new AssertionError(cause); + } + } + + private static void assertSoftSkipsEmpty(@Nonnull Store store) { + assertEquals(0, + store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .getSoftSkipsByReason() + .size()); + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private record RuntimeFixture(@Nonnull Ref spaceRef, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull FakePhysicsBackendRuntime backendRuntime) { + } +} From df461cd065c1b7082f11188451cdb2b448eadb64 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 22:12:08 +0200 Subject: [PATCH 406/534] refactor(core): align chunk collision settings naming Signed-off-by: Blovien --- .../PhysicsStoreSpaceMutations.java | 12 ++-- .../PhysicsWorldRuntimeResource.java | 6 +- .../systems/PersistenceCaptureSystem.java | 18 +++--- .../PhysicsChunkSettingsIndexSystem.java | 10 ++-- .../physicschunk/PhysicsChunkTerrain.java | 6 +- .../ChunkCollisionSettingsComponent.java | 58 +++++++++---------- .../plugin/physicsstore/PhysicsEntities.java | 12 ++-- .../plugin/physicsstore/PhysicsSpaces.java | 6 +- 8 files changed, 64 insertions(+), 64 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index adebeb04..defe6b3e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -149,12 +149,12 @@ public static void putSpaceSettings(@Nonnull Store store, private static void addSpaceSettingsComponents(@Nonnull Holder holder, @Nonnull PhysicsSpaceSettings settings) { - ChunkCollisionSettingsComponent terrain = + ChunkCollisionSettingsComponent chunkCollision = new ChunkCollisionSettingsComponent(settings.getPhysicsChunkTerrainSettings()); addIfNonDefault(holder, ChunkCollisionSettingsComponent.getComponentType(), - terrain, - terrain.isDefault()); + chunkCollision, + chunkCollision.isDefault()); MaterialComponent material = chunkMaterial(settings.getPhysicsChunkTerrainSettings()); addIfNonDefault(holder, MaterialComponent.getComponentType(), @@ -205,13 +205,13 @@ private static > void addIfNonDefault( private static void putSpaceSettingsComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PhysicsSpaceSettings settings) { - ChunkCollisionSettingsComponent terrain = + ChunkCollisionSettingsComponent chunkCollision = new ChunkCollisionSettingsComponent(settings.getPhysicsChunkTerrainSettings()); putOrRemoveDefault(store, ref, ChunkCollisionSettingsComponent.getComponentType(), - terrain, - terrain.isDefault()); + chunkCollision, + chunkCollision.isDefault()); MaterialComponent material = chunkMaterial(settings.getPhysicsChunkTerrainSettings()); putOrRemoveDefault(store, ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index f2da8816..ee250b4f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -253,7 +253,7 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( if (space == null) { return null; } - ChunkCollisionSettingsComponent terrainSettings = store.getComponent(ref, + ChunkCollisionSettingsComponent chunkCollisionSettings = store.getComponent(ref, ChunkCollisionSettingsComponent.getComponentType()); SolverSettingsComponent solverSettings = store.getComponent(ref, SolverSettingsComponent.getComponentType()); @@ -266,8 +266,8 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( ExtensionSettingsComponent extensionSettings = store.getComponent(ref, ExtensionSettingsComponent.getComponentType()); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - if (terrainSettings != null) { - terrainSettings.copyTo(settings); + if (chunkCollisionSettings != null) { + chunkCollisionSettings.copyTo(settings); } MaterialComponent material = store.getComponent(ref, MaterialComponent.getComponentType()); if (material != null) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index fdb882dd..bf2c3b99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -192,8 +192,8 @@ private PersistentSpaceDto[] spaceDtos() { @Nonnull private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { - ChunkCollisionSettingsComponent terrain = row.physicsChunkTerrain() != null - ? row.physicsChunkTerrain() + ChunkCollisionSettingsComponent chunkCollision = row.chunkCollisionSettings() != null + ? row.chunkCollisionSettings() : new ChunkCollisionSettingsComponent(); MaterialComponent material = row.material() != null ? row.material() @@ -206,12 +206,12 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { return new PersistentSpaceDto(row.uuid(), row.space().getBackendIdValue(), row.space().getGravity(), - terrain.getTerrainMode(), - terrain.getEntityChunkBoundaryMode(), - terrain.isNativeVoxelTerrainEnabled(), - terrain.getRadius(), - terrain.getBodyRadius(), - terrain.getTtlTicks(), + chunkCollision.getMode(), + chunkCollision.getEntityChunkBoundaryMode(), + chunkCollision.isNativeVoxelCollisionEnabled(), + chunkCollision.getRadius(), + chunkCollision.getBodyRadius(), + chunkCollision.getTtlTicks(), material.getFriction(), material.getRestitution(), filter.getCollisionGroup(), @@ -373,7 +373,7 @@ private PersistentJointDto[] jointDtos(@Nonnull Set bodyUuids) { private record SpaceRow(@Nonnull UUID uuid, @Nonnull SpaceComponent space, - @Nullable ChunkCollisionSettingsComponent physicsChunkTerrain, + @Nullable ChunkCollisionSettingsComponent chunkCollisionSettings, @Nullable MaterialComponent material, @Nullable CollisionFilterComponent filter, @Nullable SolverSettingsComponent solverSettings, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index ade17465..4f60e313 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -58,19 +58,19 @@ private static void collectChunk( if (PhysicsStoreSystemSupport.isNil(spaceUuid)) { continue; } - ChunkCollisionSettingsComponent terrain = chunk.getComponent(index, + ChunkCollisionSettingsComponent chunkCollision = chunk.getComponent(index, ChunkCollisionSettingsComponent.getComponentType()); - ChunkCollisionSettingsComponent settings = terrain != null - ? terrain + ChunkCollisionSettingsComponent settings = chunkCollision != null + ? chunkCollision : new ChunkCollisionSettingsComponent(); MaterialComponent material = chunk.getComponent(index, MaterialComponent.getComponentType()); CollisionFilterComponent filter = chunk.getComponent(index, CollisionFilterComponent.getComponentType()); settingsBySpaceUuid.put(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, - settings.getTerrainMode(), + settings.getMode(), settings.getEntityChunkBoundaryMode(), - settings.isNativeVoxelTerrainEnabled(), + settings.isNativeVoxelCollisionEnabled(), settings.getRadius(), settings.getBodyRadius(), settings.getTtlTicks(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 5fbfa4b6..3f9e868b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -165,7 +165,7 @@ private static PhysicsChunkSpaceSettings requireSettings( store.getComponent(spaceRef, ChunkCollisionSettingsComponent.getComponentType()); ChunkCollisionSettingsComponent settings = component != null ? component : new ChunkCollisionSettingsComponent(); - if (settings.getTerrainMode() == PhysicsChunkTerrainMode.NONE) { + if (settings.getMode() == PhysicsChunkTerrainMode.NONE) { throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); } @@ -174,9 +174,9 @@ private static PhysicsChunkSpaceSettings requireSettings( CollisionFilterComponent filter = store.getComponent(spaceRef, CollisionFilterComponent.getComponentType()); return new PhysicsChunkSpaceSettings(spaceUuid, - settings.getTerrainMode(), + settings.getMode(), settings.getEntityChunkBoundaryMode(), - settings.isNativeVoxelTerrainEnabled(), + settings.isNativeVoxelCollisionEnabled(), settings.getRadius(), settings.getBodyRadius(), settings.getTtlTicks(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java index ded85a2c..3794f24d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java @@ -25,14 +25,14 @@ public class ChunkCollisionSettingsComponent implements Component ChunkCollisionSettingsComponent.class, ChunkCollisionSettingsComponent::new) .append(new KeyedCodec<>("Mode", new EnumCodec<>(PhysicsChunkTerrainMode.class), false), - (component, value) -> component.terrainMode = value != null + (component, value) -> component.mode = value != null ? value : PhysicsChunkTerrainMode.NONE, - ChunkCollisionSettingsComponent::getTerrainMode) + ChunkCollisionSettingsComponent::getMode) .add() - .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), - (component, value) -> component.nativeVoxelTerrainEnabled = value != null && value, - ChunkCollisionSettingsComponent::isNativeVoxelTerrainEnabled) + .append(new KeyedCodec<>("NativeVoxelCollision", Codec.BOOLEAN, false), + (component, value) -> component.nativeVoxelCollisionEnabled = value != null && value, + ChunkCollisionSettingsComponent::isNativeVoxelCollisionEnabled) .add() .append(new KeyedCodec<>("EntityChunkBoundaryMode", new EnumCodec<>(EntityChunkBoundaryMode.class), @@ -63,11 +63,11 @@ public class ChunkCollisionSettingsComponent implements Component .build(); @Nonnull - private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; + private PhysicsChunkTerrainMode mode = PhysicsChunkTerrainMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - private boolean nativeVoxelTerrainEnabled = + private boolean nativeVoxelCollisionEnabled = PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; private int radius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; private int bodyRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; @@ -85,41 +85,41 @@ public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainSettings sett settings.getTerrainTtlTicks()); } - public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, - boolean nativeVoxelTerrainEnabled, + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, + boolean nativeVoxelCollisionEnabled, int radius, int bodyRadius, int ttlTicks) { - this(terrainMode, + this(mode, PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - nativeVoxelTerrainEnabled, + nativeVoxelCollisionEnabled, radius, bodyRadius, ttlTicks); } - public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode terrainMode, + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, + boolean nativeVoxelCollisionEnabled, int radius, int bodyRadius, int ttlTicks) { - this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + this.mode = Objects.requireNonNull(mode, "mode"); this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, "entityChunkBoundaryMode"); - this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + this.nativeVoxelCollisionEnabled = nativeVoxelCollisionEnabled; this.radius = radius; this.bodyRadius = bodyRadius; this.ttlTicks = ttlTicks; } @Nonnull - public PhysicsChunkTerrainMode getTerrainMode() { - return terrainMode; + public PhysicsChunkTerrainMode getMode() { + return mode; } - public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { - this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + public void setMode(@Nonnull PhysicsChunkTerrainMode mode) { + this.mode = Objects.requireNonNull(mode, "mode"); } @Nonnull @@ -133,12 +133,12 @@ public void setEntityChunkBoundaryMode( "entityChunkBoundaryMode"); } - public boolean isNativeVoxelTerrainEnabled() { - return nativeVoxelTerrainEnabled; + public boolean isNativeVoxelCollisionEnabled() { + return nativeVoxelCollisionEnabled; } - public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { - this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + public void setNativeVoxelCollisionEnabled(boolean nativeVoxelCollisionEnabled) { + this.nativeVoxelCollisionEnabled = nativeVoxelCollisionEnabled; } public int getRadius() { @@ -170,19 +170,19 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { } public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { - settings.setTerrainMode(terrainMode); + settings.setTerrainMode(mode); settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); - settings.setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); + settings.setNativeVoxelTerrainEnabled(nativeVoxelCollisionEnabled); settings.setTerrainRadius(radius); settings.setBodyTerrainRadius(bodyRadius); settings.setTerrainTtlTicks(ttlTicks); } public boolean isDefault() { - return terrainMode == PhysicsChunkTerrainMode.NONE + return mode == PhysicsChunkTerrainMode.NONE && entityChunkBoundaryMode == PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE - && nativeVoxelTerrainEnabled + && nativeVoxelCollisionEnabled == PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED && radius == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS && bodyRadius == PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS @@ -197,9 +197,9 @@ public static ComponentType getCo @Nonnull @Override public ChunkCollisionSettingsComponent clone() { - return new ChunkCollisionSettingsComponent(terrainMode, + return new ChunkCollisionSettingsComponent(mode, entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, + nativeVoxelCollisionEnabled, radius, bodyRadius, ttlTicks); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index 32b651a4..0096d0cf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -73,7 +73,7 @@ public static Holder spaceHolder(@Nonnull Store stor public static Holder spaceHolder(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull SpaceComponent space, - @Nonnull ChunkCollisionSettingsComponent terrainSettings, + @Nonnull ChunkCollisionSettingsComponent chunkCollisionSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -82,7 +82,7 @@ public static Holder spaceHolder(@Nonnull Store stor Holder holder = entityHolder(store, spaceUuid); addSpaceComponents(holder, space, - terrainSettings, + chunkCollisionSettings, solverSettings, visualSyncSettings, visualMaterializationSettings, @@ -125,7 +125,7 @@ public static Holder jointHolder(@Nonnull Store stor public static void addSpaceComponents(@Nonnull Holder holder, @Nonnull SpaceComponent space, - @Nonnull ChunkCollisionSettingsComponent terrainSettings, + @Nonnull ChunkCollisionSettingsComponent chunkCollisionSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -133,7 +133,7 @@ public static void addSpaceComponents(@Nonnull Holder holder, @Nonnull ExtensionSettingsComponent extensionSettings) { addSpaceComponent(holder, space); holder.addComponent(ChunkCollisionSettingsComponent.getComponentType(), - Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); + Objects.requireNonNull(chunkCollisionSettings, "chunkCollisionSettings").clone()); addSpaceSettingsComponents(holder, solverSettings, visualSyncSettings, @@ -191,7 +191,7 @@ public static void addBodyComponents(@Nonnull Holder holder, public static void putSpaceComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull SpaceComponent space, - @Nonnull ChunkCollisionSettingsComponent terrainSettings, + @Nonnull ChunkCollisionSettingsComponent chunkCollisionSettings, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -205,7 +205,7 @@ public static void putSpaceComponents(@Nonnull Store store, Objects.requireNonNull(space, "space").clone()); checkedStore.putComponent(ref, ChunkCollisionSettingsComponent.getComponentType(), - Objects.requireNonNull(terrainSettings, "terrainSettings").clone()); + Objects.requireNonNull(chunkCollisionSettings, "chunkCollisionSettings").clone()); putSpaceSettingsComponents(checkedStore, ref, solverSettings, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 4e454826..5f80fe99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -138,10 +138,10 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, return null; } PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - ChunkCollisionSettingsComponent terrainSettings = checkedStore.getComponent(checkedRef, + ChunkCollisionSettingsComponent chunkCollisionSettings = checkedStore.getComponent(checkedRef, ChunkCollisionSettingsComponent.getComponentType()); - if (terrainSettings != null) { - terrainSettings.copyTo(settings); + if (chunkCollisionSettings != null) { + chunkCollisionSettings.copyTo(settings); } MaterialComponent material = checkedStore.getComponent(checkedRef, MaterialComponent.getComponentType()); From 7fa3c60ab6235e552c1bf1595eff91f93e488e15 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 22:19:38 +0200 Subject: [PATCH 407/534] fix(core): retain chunk collision payloads only for voxels Signed-off-by: Blovien --- .../ChunkCollisionMutationDrainSystem.java | 27 +++- .../debug/PhysicsStoreDebugQueries.java | 125 +++++++++++++----- ...ChunkCollisionMutationDrainSystemTest.java | 87 +++++++++++- 3 files changed, 197 insertions(+), 42 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 1aa6df0b..2512855d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -132,13 +132,13 @@ private static void applyUpsert(@Nonnull Store store, restore.recordSoftSkip("Terrain references unbound space: " + mutation.sourceKey()); return; } - removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); - chunkCollisionPayloads.put(mutation.payloadResourceKey(), payload); - boolean nativeVoxel = payload.nativeVoxelTerrainEnabled() && payload.hasFullCubeVoxels() && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); + removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); + removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); if (nativeVoxel) { + chunkCollisionPayloads.put(mutation.payloadResourceKey(), voxelPayload(payload)); addVoxelBody(store, identity, spaceRef, mutation, payload); } else { addBoxBodies(store, @@ -158,6 +158,22 @@ private static void applyUpsert(@Nonnull Store store, PartKind.DETAIL_BOX); } + @Nonnull + private static ChunkCollisionPayload voxelPayload(@Nonnull ChunkCollisionPayload payload) { + return new ChunkCollisionPayload(payload.voxelSizeX(), + payload.voxelSizeY(), + payload.voxelSizeZ(), + payload.voxelCoordinates(), + List.of(), + List.of(), + true, + 0.0f, + 0.0f, + 0, + 0, + payload.neighbors()); + } + private static void addVoxelBody(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @@ -183,6 +199,7 @@ private static void addVoxelBody(@Nonnull Store store, mutation.payloadResourceKey()), material(payload), filter(payload), + mutation.payloadResourceKey(), PartKind.VOXEL_TERRAIN, 0); } @@ -219,6 +236,7 @@ private static void addBoxBodies(@Nonnull Store store, ""), material(payload), filter(payload), + "", partKind, index); } @@ -232,6 +250,7 @@ private static void addTerrainBody(@Nonnull Store store, @Nonnull ShapeComponent shape, @Nonnull MaterialComponent material, @Nonnull CollisionFilterComponent filter, + @Nonnull String payloadResourceKey, @Nonnull PartKind partKind, int partIndex) { UUID bodyUuid = chunkCollisionBodyUuid(mutation.spaceUuid(), @@ -256,7 +275,7 @@ private static void addTerrainBody(@Nonnull Store store, mutation.chunkX(), mutation.sectionY(), mutation.chunkZ(), - mutation.payloadResourceKey(), + payloadResourceKey, partKind, partIndex)); Ref ref = store.addEntity(holder, AddReason.SPAWN); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 3dee29d9..d5c662ef 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -7,6 +7,7 @@ import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; @@ -20,10 +21,15 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.BiConsumer; @@ -223,7 +229,8 @@ private static List physicsChunkSections( PhysicsChunkCollisionPayloadResource payloads = store.getResource( PhysicsChunkCollisionPayloadResource.getResourceType()); double maxDistanceSquared = viewRadius * viewRadius; - List visible = new ArrayList<>(); + Map sections = + new Object2ObjectOpenHashMap<>(); BiConsumer, CommandBuffer> collector = (chunk, _) -> collectPhysicsChunkSourceChunk(chunk, payloads, @@ -234,8 +241,11 @@ private static List physicsChunkSections( viewerY, viewerZ, maxDistanceSquared, - visible); + sections); store.forEachChunk(ChunkCollisionSourceComponent.getComponentType(), collector); + List visible = new ArrayList<>(sections.size()); + sections.values() + .forEach(builder -> visible.add(builder.toView())); return List.copyOf(visible); } @@ -248,7 +258,7 @@ private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk visible) { + @Nonnull Map sections) { for (int index = 0; index < chunk.size(); index++) { ChunkCollisionSourceComponent source = chunk.getComponent(index, ChunkCollisionSourceComponent.getComponentType()); @@ -260,47 +270,72 @@ private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk maxDistanceSquared) { continue; } - ChunkCollisionPayload payload = payloads.get(source.getPayloadResourceKey()); - if (payload == null || payload.isEmpty()) { - continue; + if (source.getPartKind() == PartKind.VOXEL_TERRAIN) { + collectVoxelTerrain(payloads, spaceContext, source, sections); + } else { + collectBoxTerrain(chunk, index, source, sections); } - visible.add(toPhysicsChunkSectionView(source, payload, spaceContext)); } } - @Nonnull - private static PhysicsChunkDebugSectionView toPhysicsChunkSectionView( + private static void collectVoxelTerrain( + @Nonnull PhysicsChunkCollisionPayloadResource payloads, + @Nonnull SpaceContext spaceContext, @Nonnull ChunkCollisionSourceComponent source, - @Nonnull ChunkCollisionPayload payload, - @Nonnull SpaceContext spaceContext) { + @Nonnull Map sections) { + ChunkCollisionPayload payload = payloads.get(source.getPayloadResourceKey()); + if (payload == null || payload.isEmpty()) { + return; + } boolean voxelTerrain = payload.nativeVoxelTerrainEnabled() && payload.hasFullCubeVoxels() && spaceContext.backendRuntime() .supportsVoxelTerrain(spaceContext.spaceHandle().value()); - return new PhysicsChunkDebugSectionView(source.getChunkX(), - source.getSectionY(), - source.getChunkZ(), - voxelTerrain, - boxes(payload.mergedFullCubeBoxes()), - boxes(payload.detailBoxes())); + if (voxelTerrain) { + section(sections, source).voxelTerrain = true; + } } - @Nonnull - private static List boxes( - @Nonnull List payloadBoxes) { - if (payloadBoxes.isEmpty()) { - return List.of(); + private static void collectBoxTerrain(@Nonnull ArchetypeChunk chunk, + int index, + @Nonnull ChunkCollisionSourceComponent source, + @Nonnull Map sections) { + BoxCollider box = rowBox(chunk, index); + if (box == null) { + return; } - List boxes = new ArrayList<>(payloadBoxes.size()); - for (ChunkCollisionPayload.BoxPayload box : payloadBoxes) { - boxes.add(new BoxCollider(box.centerX(), - box.centerY(), - box.centerZ(), - box.halfX(), - box.halfY(), - box.halfZ())); + PhysicsChunkDebugSectionBuilder section = section(sections, source); + if (source.getPartKind() == PartKind.BOX) { + section.fullCubeBoxes.add(box); + } else if (source.getPartKind() == PartKind.DETAIL_BOX) { + section.detailBoxes.add(box); } - return boxes; + } + + @Nullable + private static BoxCollider rowBox(@Nonnull ArchetypeChunk chunk, int index) { + ShapeComponent shape = chunk.getComponent(index, ShapeComponent.getComponentType()); + TargetComponent target = chunk.getComponent(index, TargetComponent.getComponentType()); + if (shape == null || target == null || shape.getShapeType() != ShapeType.BOX) { + return null; + } + Vector3f position = target.getPosition(); + return new BoxCollider(position.x, + position.y, + position.z, + shape.getHalfExtentX(), + shape.getHalfExtentY(), + shape.getHalfExtentZ()); + } + + @Nonnull + private static PhysicsChunkDebugSectionBuilder section( + @Nonnull Map sections, + @Nonnull ChunkCollisionSourceComponent source) { + SectionKey key = new SectionKey(source.getChunkX(), + source.getSectionY(), + source.getChunkZ()); + return sections.computeIfAbsent(key, _ -> new PhysicsChunkDebugSectionBuilder(key)); } private static void collectJointChunk(@Nonnull ArchetypeChunk chunk, @@ -533,4 +568,32 @@ private static double axisDistance(double value, double min, double max) { private record SpaceContext(@Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } + + private record SectionKey(int chunkX, int sectionY, int chunkZ) { + } + + private static final class PhysicsChunkDebugSectionBuilder { + + @Nonnull + private final SectionKey key; + @Nonnull + private final List fullCubeBoxes = new ArrayList<>(); + @Nonnull + private final List detailBoxes = new ArrayList<>(); + private boolean voxelTerrain; + + private PhysicsChunkDebugSectionBuilder(@Nonnull SectionKey key) { + this.key = key; + } + + @Nonnull + private PhysicsChunkDebugSectionView toView() { + return new PhysicsChunkDebugSectionView(key.chunkX(), + key.sectionY(), + key.chunkZ(), + voxelTerrain, + fullCubeBoxes, + detailBoxes); + } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 0053831e..3adc961a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; @@ -24,6 +25,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; @@ -52,6 +54,7 @@ import java.util.List; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -103,9 +106,8 @@ void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); assertEquals(0, queue.size()); - assertSame(payload, - store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) - .get(payloadKey)); + assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); assertSoftSkipsEmpty(store); assertGeneratedBox(store, spaceUuid, @@ -173,9 +175,8 @@ void upsertCreatesNativeVoxelRowWhenBackendSupportsVoxelTerrain() { new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); assertEquals(0, queue.size()); - assertSame(payload, - store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) - .get(payloadKey)); + assertVoxelOnlyPayload(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); assertSoftSkipsEmpty(store); assertGeneratedVoxel(store, spaceUuid, @@ -189,6 +190,78 @@ void upsertCreatesNativeVoxelRowWhenBackendSupportsVoxelTerrain() { } } + @Test + void destroyingDetailRowDoesNotRemoveNativeVoxelPayloadForSiblingRow() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-payload-lifetime-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(31); + BackendId backendId = new BackendId("test:chunk-collision-drain-payload-lifetime"); + Ref spaceRef = addBoundSpace(store, spaceUuid, backendId, true); + String sourceKey = "2:3:4"; + String payloadKey = "chunk-collision/2/3/4"; + ChunkCollisionPayload payload = new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[] {0, 0, 0}, + List.of(), + List.of(new BoxPayload(10.0, 20.0, 30.0, 0.25, 0.5, 0.75)), + true, + 0.7f, + 0.05f, + 0x20, + 0x03, + List.of()); + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 2, + 3, + 4, + payloadKey, + payload)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertGeneratedVoxel(store, spaceUuid, spaceRef, sourceKey, payloadKey, payload); + UUID detailUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.DETAIL_BOX, + 0); + assertNotNull(store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(detailUuid)); + ChunkCollisionPayload retainedPayload = store.getResource( + PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey); + assertVoxelOnlyPayload(retainedPayload); + + PhysicsStoreTopologyMutations.destroyBody(store, detailUuid); + + assertSame(retainedPayload, + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + private static void assertVoxelOnlyPayload(@Nullable ChunkCollisionPayload payload) { + assertNotNull(payload); + assertTrue(payload.hasFullCubeVoxels()); + assertTrue(payload.mergedFullCubeBoxes().isEmpty()); + assertTrue(payload.detailBoxes().isEmpty()); + } + @Test void removeDeletesGeneratedRowsAndPayloadResource() { ComponentRegistry registry = new ComponentRegistry<>(); @@ -416,7 +489,7 @@ private static void assertGeneratedBox(@Nonnull Store store, assertEquals(0, source.getChunkX()); assertEquals(1, source.getSectionY()); assertEquals(2, source.getChunkZ()); - assertEquals(payloadKey, source.getPayloadResourceKey()); + assertEquals("", source.getPayloadResourceKey()); assertEquals(partKind, source.getPartKind()); assertEquals(partIndex, source.getPartIndex()); } From 2976fcb834b59c1f8b47e447f397468cffda0bc3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 22:33:31 +0200 Subject: [PATCH 408/534] refactor(core): align chunk collision row material naming Signed-off-by: Blovien --- .../physicschunk/ChunkCollisionMode.java | 6 +-- .../physicschunk/ChunkCollisionPayload.java | 2 +- .../PhysicsChunkBuildOptions.java | 26 +++++----- .../PhysicsChunkMutationCache.java | 4 +- .../PhysicsStoreChunkCollisionMutations.java | 8 +-- .../PersistentPhysicsStorePreflight.java | 12 ++--- .../persistence/PersistentSpaceDto.java | 49 ++++++++++--------- .../PhysicsChunkSettingsIndexResource.java | 14 +++--- .../ChunkCollisionMutationDrainSystem.java | 20 ++++---- .../ChunkCollisionVoxelStitchingSystem.java | 4 +- .../debug/PhysicsStoreDebugQueries.java | 14 +++--- .../ChunkCollisionSourceComponent.java | 2 +- .../PersistentSpaceDtoSettingsTest.java | 12 ++--- ...ChunkCollisionMutationDrainSystemTest.java | 4 +- ...hunkCollisionVoxelStitchingSystemTest.java | 4 +- 15 files changed, 92 insertions(+), 89 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMode.java index 86049b1d..c9b3a01d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMode.java @@ -1,17 +1,17 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; /** - * Runtime representation used for full-cube terrain collision. + * Runtime representation used for full-cube chunk collision. */ public enum ChunkCollisionMode { MERGED_BOXES, NATIVE_VOXELS_WHEN_SUPPORTED; - public static ChunkCollisionMode fromNativeVoxelTerrainEnabled(boolean enabled) { + public static ChunkCollisionMode fromNativeVoxelCollisionEnabled(boolean enabled) { return enabled ? NATIVE_VOXELS_WHEN_SUPPORTED : MERGED_BOXES; } - public boolean nativeVoxelTerrainEnabled() { + public boolean nativeVoxelCollisionEnabled() { return this == NATIVE_VOXELS_WHEN_SUPPORTED; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java index 02477d20..066d85c6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java @@ -14,7 +14,7 @@ public record ChunkCollisionPayload(float voxelSizeX, @Nonnull int[] voxelCoordinates, @Nonnull List mergedFullCubeBoxes, @Nonnull List detailBoxes, - boolean nativeVoxelTerrainEnabled, + boolean nativeVoxelCollisionEnabled, float friction, float restitution, int collisionGroup, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index e0ea12c2..2d0689e3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -6,31 +6,31 @@ import javax.annotation.Nonnull; /** - * Options that control generated PhysicsChunk terrain backend geometry. + * Options that control generated PhysicsChunk backend collision geometry. */ public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisionMode, - float terrainFriction, - float terrainRestitution, + float friction, + float restitution, int collisionGroup, int collisionMask) { public static final PhysicsChunkBuildOptions DEFAULT = - fromNativeVoxelTerrainEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); + fromNativeVoxelCollisionEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); public PhysicsChunkBuildOptions { Objects.requireNonNull(chunkCollisionMode, "chunkCollisionMode"); - if (!Float.isFinite(terrainFriction) || terrainFriction < 0.0f) { - throw new IllegalArgumentException("terrainFriction must be finite and >= 0"); + if (!Float.isFinite(friction) || friction < 0.0f) { + throw new IllegalArgumentException("friction must be finite and >= 0"); } - if (!Float.isFinite(terrainRestitution) || terrainRestitution < 0.0f) { - throw new IllegalArgumentException("terrainRestitution must be finite and >= 0"); + if (!Float.isFinite(restitution) || restitution < 0.0f) { + throw new IllegalArgumentException("restitution must be finite and >= 0"); } } @Nonnull public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrainSettings settings) { return new PhysicsChunkBuildOptions( - ChunkCollisionMode.fromNativeVoxelTerrainEnabled(settings.isNativeVoxelTerrainEnabled()), + ChunkCollisionMode.fromNativeVoxelCollisionEnabled(settings.isNativeVoxelTerrainEnabled()), settings.getTerrainFriction(), settings.getTerrainRestitution(), PhysicsCollisionFilters.TERRAIN, @@ -38,15 +38,15 @@ public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrain } @Nonnull - public static PhysicsChunkBuildOptions fromNativeVoxelTerrainEnabled(boolean enabled) { - return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelTerrainEnabled(enabled), + public static PhysicsChunkBuildOptions fromNativeVoxelCollisionEnabled(boolean enabled) { + return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelCollisionEnabled(enabled), PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL); } - public boolean nativeVoxelTerrainEnabled() { - return chunkCollisionMode.nativeVoxelTerrainEnabled(); + public boolean nativeVoxelCollisionEnabled() { + return chunkCollisionMode.nativeVoxelCollisionEnabled(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java index 8893b9ff..089eacb6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java @@ -575,7 +575,7 @@ private PhysicsChunkBuildStats ensureSection(@Nonnull World world, neighborhoodSignature, buildOptions, bodyCount(geometry, buildOptions), - buildOptions.nativeVoxelTerrainEnabled() && geometry.hasFullCubeVoxels()); + buildOptions.nativeVoxelCollisionEnabled() && geometry.hasFullCubeVoxels()); int removedBodies = cached != null ? removeSection(spaceUuid, queue, cached) : 0; if (built.bodyCount > 0) { queue.enqueue(PhysicsStoreChunkCollisionMutations.upsert(spaceUuid, @@ -607,7 +607,7 @@ private PhysicsChunkBuildStats ensureSection(@Nonnull World world, private static int bodyCount(@Nonnull SectionCollisionGeometry geometry, @Nonnull PhysicsChunkBuildOptions buildOptions) { - int fullCubeBodyCount = buildOptions.nativeVoxelTerrainEnabled() + int fullCubeBodyCount = buildOptions.nativeVoxelCollisionEnabled() && geometry.hasFullCubeVoxels() ? 1 : geometry.mergedFullCubeBoxes().size(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java index adbe8217..1b03aaf8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java @@ -10,7 +10,7 @@ import javax.annotation.Nonnull; /** - * Converts generated PhysicsChunk terrain sections into copied PhysicsStore chunk collision mutations. + * Converts generated PhysicsChunk sections into copied PhysicsStore chunk collision mutations. */ public final class PhysicsStoreChunkCollisionMutations { @@ -74,9 +74,9 @@ private static ChunkCollisionPayload payload(@Nonnull SectionCollisionGeometry g geometry.fullCubeVoxels(), boxes(geometry.mergedFullCubeBoxes()), boxes(geometry.detailBoxes()), - buildOptions.nativeVoxelTerrainEnabled(), - buildOptions.terrainFriction(), - buildOptions.terrainRestitution(), + buildOptions.nativeVoxelCollisionEnabled(), + buildOptions.friction(), + buildOptions.restitution(), buildOptions.collisionGroup(), buildOptions.collisionMask(), neighbors); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index 8808d67c..9dd7827d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -82,15 +82,15 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, errors.add("PhysicsStore space " + uuid + " has invalid PhysicsChunk terrain TTL"); } - if (!Float.isFinite(space.getTerrainFriction()) - || space.getTerrainFriction() < 0.0f) { + if (!Float.isFinite(space.getChunkCollisionFriction()) + || space.getChunkCollisionFriction() < 0.0f) { errors.add("PhysicsStore space " + uuid - + " has invalid terrain friction"); + + " has invalid chunk collision friction"); } - if (!Float.isFinite(space.getTerrainRestitution()) - || space.getTerrainRestitution() < 0.0f) { + if (!Float.isFinite(space.getChunkCollisionRestitution()) + || space.getChunkCollisionRestitution() < 0.0f) { errors.add("PhysicsStore space " + uuid - + " has invalid terrain restitution"); + + " has invalid chunk collision restitution"); } try { space.toSettings(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index cb6e4028..3b9943bc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -62,39 +62,39 @@ public final class PersistentSpaceDto { : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, PersistentSpaceDto::getEntityChunkBoundaryMode) .add() - .append(new KeyedCodec<>("NativeVoxelTerrain", Codec.BOOLEAN, false), + .append(new KeyedCodec<>("NativeVoxelCollision", Codec.BOOLEAN, false), (dto, value) -> dto.nativeVoxelTerrainEnabled = value != null && value, PersistentSpaceDto::isNativeVoxelTerrainEnabled) .add() - .append(new KeyedCodec<>("TerrainRadius", Codec.INTEGER, false), + .append(new KeyedCodec<>("ChunkCollisionRadius", Codec.INTEGER, false), (dto, value) -> dto.terrainRadius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, PersistentSpaceDto::getTerrainRadius) .add() - .append(new KeyedCodec<>("BodyTerrainRadius", Codec.INTEGER, false), + .append(new KeyedCodec<>("BodyChunkCollisionRadius", Codec.INTEGER, false), (dto, value) -> dto.bodyTerrainRadius = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, PersistentSpaceDto::getBodyTerrainRadius) .add() - .append(new KeyedCodec<>("TerrainTtlTicks", Codec.INTEGER, false), + .append(new KeyedCodec<>("ChunkCollisionTtlTicks", Codec.INTEGER, false), (dto, value) -> dto.terrainTtlTicks = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, PersistentSpaceDto::getTerrainTtlTicks) .add() - .append(new KeyedCodec<>("TerrainFriction", Codec.FLOAT, false), - (dto, value) -> dto.terrainFriction = value != null + .append(new KeyedCodec<>("ChunkCollisionFriction", Codec.FLOAT, false), + (dto, value) -> dto.chunkCollisionFriction = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - PersistentSpaceDto::getTerrainFriction) + PersistentSpaceDto::getChunkCollisionFriction) .add() - .append(new KeyedCodec<>("TerrainRestitution", Codec.FLOAT, false), - (dto, value) -> dto.terrainRestitution = value != null + .append(new KeyedCodec<>("ChunkCollisionRestitution", Codec.FLOAT, false), + (dto, value) -> dto.chunkCollisionRestitution = value != null ? value : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, - PersistentSpaceDto::getTerrainRestitution) + PersistentSpaceDto::getChunkCollisionRestitution) .add() .append(new KeyedCodec<>("ChunkCollisionFilter", CollisionFilterComponent.CODEC, @@ -161,8 +161,9 @@ public final class PersistentSpaceDto { PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; private int terrainTtlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; - private float terrainFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; + private float chunkCollisionFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; + private float chunkCollisionRestitution = + PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; @Nonnull private CollisionFilterComponent chunkCollisionFilter = defaultChunkCollisionFilter(); @Nonnull @@ -298,8 +299,8 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this.terrainRadius = terrainRadius; this.bodyTerrainRadius = bodyTerrainRadius; this.terrainTtlTicks = terrainTtlTicks; - this.terrainFriction = terrainFriction; - this.terrainRestitution = terrainRestitution; + this.chunkCollisionFriction = terrainFriction; + this.chunkCollisionRestitution = terrainRestitution; this.chunkCollisionFilter = new CollisionFilterComponent(chunkCollisionGroup, chunkCollisionMask); this.solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); @@ -354,12 +355,12 @@ public int getTerrainTtlTicks() { return terrainTtlTicks; } - public float getTerrainFriction() { - return terrainFriction; + public float getChunkCollisionFriction() { + return chunkCollisionFriction; } - public float getTerrainRestitution() { - return terrainRestitution; + public float getChunkCollisionRestitution() { + return chunkCollisionRestitution; } public int getChunkCollisionGroup() { @@ -382,13 +383,13 @@ public ChunkCollisionSettingsComponent getChunkCollisionSettings() { @Nonnull public MaterialComponent getChunkCollisionMaterial() { - return new MaterialComponent(terrainFriction, terrainRestitution); + return new MaterialComponent(chunkCollisionFriction, chunkCollisionRestitution); } public boolean isDefaultChunkCollisionMaterial() { - return Float.compare(terrainFriction, + return Float.compare(chunkCollisionFriction, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION) == 0 - && Float.compare(terrainRestitution, + && Float.compare(chunkCollisionRestitution, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; } @@ -432,7 +433,7 @@ public PhysicsSpaceSettings toSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); getChunkCollisionSettings().copyTo(settings); settings.getPhysicsChunkTerrainSettings() - .setTerrainMaterial(terrainFriction, terrainRestitution); + .setTerrainMaterial(chunkCollisionFriction, chunkCollisionRestitution); solverSettings.copyTo(settings); visualSyncSettings.copyTo(settings); visualMaterializationSettings.copyTo(settings); @@ -452,8 +453,8 @@ public PersistentSpaceDto copy() { terrainRadius, bodyTerrainRadius, terrainTtlTicks, - terrainFriction, - terrainRestitution, + chunkCollisionFriction, + chunkCollisionRestitution, getChunkCollisionGroup(), getChunkCollisionMask(), solverSettings, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index bc5cab03..be62cee6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -15,7 +15,7 @@ import javax.annotation.Nullable; /** - * Runtime-only copied PhysicsChunk terrain settings indexed by PhysicsStore space UUID. + * Runtime-only copied PhysicsChunk settings indexed by PhysicsStore space UUID. */ public final class PhysicsChunkSettingsIndexResource implements Resource { @@ -70,21 +70,21 @@ public static void setResourceType( public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, @Nonnull PhysicsChunkTerrainMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, + boolean nativeVoxelCollisionEnabled, int radius, int bodyRadius, int ttlTicks, - float terrainFriction, - float terrainRestitution, + float friction, + float restitution, int collisionGroup, int collisionMask) { @Nonnull public PhysicsChunkBuildOptions buildOptions() { return new PhysicsChunkBuildOptions( - ChunkCollisionMode.fromNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled), - terrainFriction, - terrainRestitution, + ChunkCollisionMode.fromNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled), + friction, + restitution, collisionGroup, collisionMask); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 2512855d..dc081295 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -119,7 +119,8 @@ private static void applyUpsert(@Nonnull Store store, @Nonnull ChunkCollisionMutation mutation) { ChunkCollisionPayload payload = mutation.payload(); if (payload == null || payload.isEmpty()) { - restore.recordSoftSkip("Terrain upsert payload is missing: " + mutation.sourceKey()); + restore.recordSoftSkip("Chunk collision upsert payload is missing: " + + mutation.sourceKey()); return; } Ref spaceRef = PhysicsStoreSystemSupport.refForUuid(identity, @@ -129,17 +130,18 @@ private static void applyUpsert(@Nonnull Store store, ? runtime.runtimeForSpaceHandle(spaceHandle) : null; if (spaceRef == null || spaceHandle == null || backendRuntime == null) { - restore.recordSoftSkip("Terrain references unbound space: " + mutation.sourceKey()); + restore.recordSoftSkip("Chunk collision references unbound space: " + + mutation.sourceKey()); return; } - boolean nativeVoxel = payload.nativeVoxelTerrainEnabled() + boolean nativeVoxel = payload.nativeVoxelCollisionEnabled() && payload.hasFullCubeVoxels() && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); if (nativeVoxel) { chunkCollisionPayloads.put(mutation.payloadResourceKey(), voxelPayload(payload)); - addVoxelBody(store, identity, spaceRef, mutation, payload); + addNativeVoxelBody(store, identity, spaceRef, mutation, payload); } else { addBoxBodies(store, identity, @@ -174,7 +176,7 @@ private static ChunkCollisionPayload voxelPayload(@Nonnull ChunkCollisionPayload payload.neighbors()); } - private static void addVoxelBody(@Nonnull Store store, + private static void addNativeVoxelBody(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @Nonnull ChunkCollisionMutation mutation, @@ -183,7 +185,7 @@ private static void addVoxelBody(@Nonnull Store store, target.setPosition(new Vector3f(mutation.chunkX() << ChunkUtil.BITS, mutation.sectionY() << ChunkUtil.BITS, mutation.chunkZ() << ChunkUtil.BITS)); - addTerrainBody(store, + addChunkCollisionBody(store, identity, spaceRef, mutation, @@ -200,7 +202,7 @@ private static void addVoxelBody(@Nonnull Store store, material(payload), filter(payload), mutation.payloadResourceKey(), - PartKind.VOXEL_TERRAIN, + PartKind.NATIVE_VOXELS, 0); } @@ -220,7 +222,7 @@ private static void addBoxBodies(@Nonnull Store store, target.setPosition(new Vector3f((float) box.centerX(), (float) box.centerY(), (float) box.centerZ())); - addTerrainBody(store, + addChunkCollisionBody(store, identity, spaceRef, mutation, @@ -242,7 +244,7 @@ private static void addBoxBodies(@Nonnull Store store, } } - private static void addTerrainBody(@Nonnull Store store, + private static void addChunkCollisionBody(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @Nonnull ChunkCollisionMutation mutation, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java index 4c179fe4..0836883d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java @@ -66,7 +66,7 @@ private static void stitchChunk(@Nonnull PhysicsRuntimeResource runtime, for (int index = 0; index < chunk.size(); index++) { ChunkCollisionSourceComponent source = chunk.getComponent(index, ChunkCollisionSourceComponent.getComponentType()); - if (source == null || source.getPartKind() != PartKind.VOXEL_TERRAIN) { + if (source == null || source.getPartKind() != PartKind.NATIVE_VOXELS) { continue; } Ref bodyRef = chunk.getReferenceTo(index); @@ -162,7 +162,7 @@ private static Ref neighborRef(@Nonnull PhysicsIdentityIndexResour @Nonnull String sourceKey) { UUID neighborUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, - PartKind.VOXEL_TERRAIN, + PartKind.NATIVE_VOXELS, 0); return PhysicsStoreSystemSupport.refForUuid(identity, neighborUuid); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index d5c662ef..97c3da99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -270,15 +270,15 @@ private static void collectPhysicsChunkSourceChunk(@Nonnull ArchetypeChunk maxDistanceSquared) { continue; } - if (source.getPartKind() == PartKind.VOXEL_TERRAIN) { - collectVoxelTerrain(payloads, spaceContext, source, sections); + if (source.getPartKind() == PartKind.NATIVE_VOXELS) { + collectNativeVoxels(payloads, spaceContext, source, sections); } else { - collectBoxTerrain(chunk, index, source, sections); + collectBoxCollision(chunk, index, source, sections); } } } - private static void collectVoxelTerrain( + private static void collectNativeVoxels( @Nonnull PhysicsChunkCollisionPayloadResource payloads, @Nonnull SpaceContext spaceContext, @Nonnull ChunkCollisionSourceComponent source, @@ -287,16 +287,16 @@ private static void collectVoxelTerrain( if (payload == null || payload.isEmpty()) { return; } - boolean voxelTerrain = payload.nativeVoxelTerrainEnabled() + boolean nativeVoxels = payload.nativeVoxelCollisionEnabled() && payload.hasFullCubeVoxels() && spaceContext.backendRuntime() .supportsVoxelTerrain(spaceContext.spaceHandle().value()); - if (voxelTerrain) { + if (nativeVoxels) { section(sections, source).voxelTerrain = true; } } - private static void collectBoxTerrain(@Nonnull ArchetypeChunk chunk, + private static void collectBoxCollision(@Nonnull ArchetypeChunk chunk, int index, @Nonnull ChunkCollisionSourceComponent source, @Nonnull Map sections) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java index 2e191aae..b8fa02c0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java @@ -134,6 +134,6 @@ public ChunkCollisionSourceComponent clone() { public enum PartKind { BOX, DETAIL_BOX, - VOXEL_TERRAIN + NATIVE_VOXELS } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index a27ab324..24f78f2d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -49,12 +49,12 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); assertTrue(encoded.containsKey("PhysicsChunkTerrainMode")); - assertTrue(encoded.containsKey("TerrainRadius")); - assertTrue(encoded.containsKey("BodyTerrainRadius")); - assertTrue(encoded.containsKey("TerrainTtlTicks")); - assertTrue(encoded.containsKey("NativeVoxelTerrain")); - assertTrue(encoded.containsKey("TerrainFriction")); - assertTrue(encoded.containsKey("TerrainRestitution")); + assertTrue(encoded.containsKey("ChunkCollisionRadius")); + assertTrue(encoded.containsKey("BodyChunkCollisionRadius")); + assertTrue(encoded.containsKey("ChunkCollisionTtlTicks")); + assertTrue(encoded.containsKey("NativeVoxelCollision")); + assertTrue(encoded.containsKey("ChunkCollisionFriction")); + assertTrue(encoded.containsKey("ChunkCollisionRestitution")); assertTrue(encoded.containsKey("VisualMaterializationSettings")); PhysicsSpaceSettings decoded = Objects.requireNonNull( PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())).toSettings(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 3adc961a..106cf848 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -372,7 +372,7 @@ private static void assertGeneratedVoxel(@Nonnull Store store, @Nonnull ChunkCollisionPayload payload) { UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, - PartKind.VOXEL_TERRAIN, + PartKind.NATIVE_VOXELS, 0); Ref bodyRef = store .getResource(PhysicsIdentityIndexResource.getResourceType()) @@ -416,7 +416,7 @@ private static void assertGeneratedVoxel(@Nonnull Store store, assertNotNull(source); assertEquals(sourceKey, source.getSourceKey()); assertEquals(payloadKey, source.getPayloadResourceKey()); - assertEquals(PartKind.VOXEL_TERRAIN, source.getPartKind()); + assertEquals(PartKind.NATIVE_VOXELS, source.getPartKind()); assertEquals(0, source.getPartIndex()); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index 52b4a691..cdf83147 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -163,7 +163,7 @@ private static Ref addVoxelRow(@Nonnull Store store, int chunkZ) { UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, - PartKind.VOXEL_TERRAIN, + PartKind.NATIVE_VOXELS, 0); BodyComponent body = new BodyComponent(spaceUuid, PhysicsBodyKind.TERRAIN, @@ -193,7 +193,7 @@ private static Ref addVoxelRow(@Nonnull Store store, sectionY, chunkZ, payloadKey, - PartKind.VOXEL_TERRAIN, + PartKind.NATIVE_VOXELS, 0)); Ref bodyRef = store.addEntity(holder, AddReason.SPAWN); assertNotNull(bodyRef); From 0edb709b0ce15ed181051b787135fe1c420a0588 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 22:33:49 +0200 Subject: [PATCH 409/534] fix(core): clean stale physics entity projections Signed-off-by: Blovien --- .../PhysicsEntityTypeRegistry.java | 4 +- .../PhysicsWorldResourceAttachmentSystem.java | 4 +- .../PhysicsStoreEventPublicationSystem.java | 4 +- .../systems/sync/PhysicsSyncSystem.java | 4 +- .../visual/GeneratedProxyLifecycle.java | 5 + ...va => PhysicsProjectionCleanupSystem.java} | 31 +++- ...hysicsGeneratedProxyCleanupSystemTest.java | 22 --- .../PhysicsProjectionCleanupSystemTest.java | 175 ++++++++++++++++++ 8 files changed, 217 insertions(+), 32 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/{PhysicsGeneratedProxyCleanupSystem.java => PhysicsProjectionCleanupSystem.java} (65%) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java index 5c65639d..b57d9067 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; @@ -80,7 +80,7 @@ public static void registerSystemGroups(@Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new PhysicsBodyAttachmentIndexSystem()); - registry.registerSystem(new PhysicsGeneratedProxyCleanupSystem()); + registry.registerSystem(new PhysicsProjectionCleanupSystem()); registry.registerSystem(new PhysicsSyncSystem()); registry.registerSystem(new PhysicsDebugSystem()); registry.registerSystem(new PhysicsStoreEventPublicationSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java index 768f1278..8edcb762 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; import java.util.Set; import javax.annotation.Nonnull; @@ -21,7 +21,7 @@ public final class PhysicsWorldResourceAttachmentSystem extends TickingSystem> DEPENDENCIES = Set.of( new SystemDependency<>(Order.BEFORE, PhysicsStoreEventPublicationSystem.class), - new SystemDependency<>(Order.BEFORE, PhysicsGeneratedProxyCleanupSystem.class), + new SystemDependency<>(Order.BEFORE, PhysicsProjectionCleanupSystem.class), new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class), new SystemDependency<>(Order.BEFORE, PhysicsDebugSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index b5051d5c..a69617fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource.StepSample; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import java.util.Collections; @@ -29,7 +29,7 @@ public final class PhysicsStoreEventPublicationSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.BEFORE, PhysicsGeneratedProxyCleanupSystem.class), + new SystemDependency<>(Order.BEFORE, PhysicsProjectionCleanupSystem.class), new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 15595d79..3980996f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsGeneratedProxyCleanupSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; @@ -60,7 +60,7 @@ public class PhysicsSyncSystem extends EntityTickingSystem { private final Query query; private final Set> dependencies = Set.of( new SystemGroupDependency<>(Order.AFTER, PhysicsEntityTypes.persistenceRestoreGroup()), - new SystemDependency<>(Order.AFTER, PhysicsGeneratedProxyCleanupSystem.class), + new SystemDependency<>(Order.AFTER, PhysicsProjectionCleanupSystem.class), new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class), new SystemDependency<>(Order.BEFORE, UpdateLocationSystems.TickingSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index c6349adb..65ad10a5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -8,6 +8,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; @@ -41,9 +42,13 @@ public static void clearMissingAttachment(@Nonnull Ref entityRef, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull CommandBuffer commandBuffer) { UUID bodyUuid = attachment.getBodyUuid(); + PhysicsProjectionIndexResource projection = commandBuffer.getResource( + PhysicsProjectionIndexResource.getResourceType()); + projection.unregisterAttachment(bodyUuid, attachment.getBodyRef(), entityRef); resource.unregisterBodyAttachment(bodyUuid, attachment.getBodyRef(), entityRef); resource.clearBodySyncState(entityRef); if (attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { + projection.clearGeneratedVisualProxy(bodyUuid, attachment.getBodyRef(), entityRef); resource.clearGeneratedVisualProxy(bodyUuid, attachment.getBodyRef(), entityRef); removeEntity(commandBuffer, entityRef); } else if (attachment.shouldRemoveEntityWhenBodyMissing()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java similarity index 65% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java index ddf7880d..780892d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.internal.systems.visual; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; @@ -8,6 +9,8 @@ import com.hypixel.hytale.component.dependency.SystemGroupDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; @@ -18,9 +21,9 @@ import javax.annotation.Nonnull; /** - * Removes incomplete generated visual proxy markers left by transition-era saves. + * Cleans EntityStore projections whose authoritative PhysicsStore row is gone. */ -public class PhysicsGeneratedProxyCleanupSystem extends TickingSystem { +public class PhysicsProjectionCleanupSystem extends TickingSystem { private static final int CLEANUP_INTERVAL_TICKS = 40; @@ -33,6 +36,7 @@ public class PhysicsGeneratedProxyCleanupSystem extends TickingSystem store) { + clearDestroyedBodyAttachments(store); if (shouldSkipCleanup(store)) { return; } @@ -51,6 +55,29 @@ private boolean shouldSkipCleanup(@Nonnull Store store) { } } + private static void clearDestroyedBodyAttachments(@Nonnull Store store) { + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); + PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); + store.forEachEntityParallel(attachmentType, + (index, archetypeChunk, commandBuffer) -> { + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + attachmentType); + if (attachment == null || !hasDestroyedBodyRef(attachment)) { + return; + } + GeneratedProxyLifecycle.clearMissingAttachment(archetypeChunk.getReferenceTo(index), + attachment, + resource, + commandBuffer); + }); + } + + private static boolean hasDestroyedBodyRef(@Nonnull BodyAttachmentComponent attachment) { + Ref bodyRef = attachment.getBodyRef(); + return bodyRef != null && !bodyRef.isValid(); + } + private static void removeOrphanGeneratedVisualProxyMarkers(@Nonnull Store store) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java deleted file mode 100644 index 1735cf88..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsGeneratedProxyCleanupSystemTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; - -class PhysicsGeneratedProxyCleanupSystemTest { - - @Test - void cleanupDoesNotRemoveDurableGeneratedProxyAttachments() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/" - + "PhysicsGeneratedProxyCleanupSystem.java")); - - assertTrue(source.contains("removeOrphanGeneratedVisualProxyMarkers")); - assertFalse(source.contains("AttachmentLifecycle.GENERATED_PROXY")); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java new file mode 100644 index 00000000..bec2f146 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java @@ -0,0 +1,175 @@ +package dev.hytalemodding.impulse.core.internal.systems.visual; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import java.util.ArrayList; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class PhysicsProjectionCleanupSystemTest { + + @AfterEach + void clearTypes() { + PhysicsEntityTypeRegistry.clearEntityStoreTypes(); + } + + @Test + void externalAttachmentWithDestroyedBodyRefIsDetachedButEntityRemains() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = store(registry, "projection-cleanup-external"); + try { + UUID bodyUuid = UUID.randomUUID(); + Ref bodyRef = new TestPhysicsRef(11, false); + Ref entityRef = addAttachment(store, + bodyUuid, + bodyRef, + AttachmentLifecycle.EXTERNAL_ENTITY, + false); + PhysicsProjectionIndexResource projection = projection(store); + projection.registerAttachment(bodyUuid, bodyRef, entityRef); + + new PhysicsProjectionCleanupSystem().tick(0.0f, 0, store); + + assertTrue(entityRef.isValid()); + assertNull(store.getComponent(entityRef, BodyAttachmentComponent.getComponentType())); + assertFalse(projection.hasAttachments(bodyUuid)); + assertFalse(projection.hasAttachments(bodyRef)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void generatedProxyWithDestroyedBodyRefIsRemovedAndUnindexed() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = store(registry, "projection-cleanup-generated"); + try { + UUID bodyUuid = UUID.randomUUID(); + Ref bodyRef = new TestPhysicsRef(12, false); + Ref proxyRef = addAttachment(store, + bodyUuid, + bodyRef, + AttachmentLifecycle.GENERATED_PROXY, + true); + PhysicsProjectionIndexResource projection = projection(store); + projection.registerAttachment(bodyUuid, bodyRef, proxyRef); + projection.setGeneratedVisualProxy(bodyUuid, bodyRef, proxyRef); + + new PhysicsProjectionCleanupSystem().tick(0.0f, 0, store); + + assertFalse(proxyRef.isValid()); + assertFalse(projection.hasAttachments(bodyUuid)); + assertFalse(projection.hasAttachments(bodyRef)); + assertNull(projection.getGeneratedVisualProxy(bodyUuid)); + assertNull(projection.getGeneratedVisualProxy(bodyRef)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void validBodyRefIsPreservedWhenSnapshotPublicationLags() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = store(registry, "projection-cleanup-valid-ref"); + try { + UUID bodyUuid = UUID.randomUUID(); + Ref bodyRef = new TestPhysicsRef(13, true); + Ref entityRef = addAttachment(store, + bodyUuid, + bodyRef, + AttachmentLifecycle.EXTERNAL_ENTITY, + false); + PhysicsProjectionIndexResource projection = projection(store); + projection.registerAttachment(bodyUuid, bodyRef, entityRef); + + new PhysicsProjectionCleanupSystem().tick(0.0f, 0, store); + + assertTrue(entityRef.isValid()); + assertNotNull(store.getComponent(entityRef, BodyAttachmentComponent.getComponentType())); + assertTrue(projection.hasAttachments(bodyUuid)); + assertTrue(projection.hasAttachments(bodyRef)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static Store store(@Nonnull ComponentRegistry registry, + @Nonnull String worldName) { + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsEntityTypeRegistry.registerComponentTypes(proxy); + PhysicsEntityTypeRegistry.registerResourceTypes(proxy); + PhysicsEntityTypeRegistry.registerSystemGroups(proxy); + Store store = registry.addStore( + new EntityStore(TestInstanceFactory.world(worldName)), + EmptyResourceStorage.get()); + PhysicsWorldRuntimeResource.require(store); + return store; + } + + @Nonnull + private static Ref addAttachment(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nonnull AttachmentLifecycle lifecycle, + boolean generatedProxy) { + Holder holder = store.getRegistry().newHolder(); + BodyAttachmentComponent attachment = new BodyAttachmentComponent(bodyUuid, + BodyAttachmentComponent.TransformAuthority.BODY, + lifecycle); + attachment.setBodyRef(bodyRef); + holder.addComponent(BodyAttachmentComponent.getComponentType(), attachment); + if (generatedProxy) { + holder.addComponent(GeneratedVisualProxyComponent.getComponentType(), + new GeneratedVisualProxyComponent()); + } + Ref ref = store.addEntity(holder, AddReason.SPAWN); + assertNotNull(ref); + return ref; + } + + @Nonnull + private static PhysicsProjectionIndexResource projection(@Nonnull Store store) { + return store.getResource(PhysicsProjectionIndexResource.getResourceType()); + } + + private static final class TestPhysicsRef extends Ref { + + private final boolean valid; + + private TestPhysicsRef(int index, boolean valid) { + super(null, index); + this.valid = valid; + } + + @Override + public boolean isValid() { + return valid; + } + } +} From 4f2600b3b748f89dbb46765e2b99df2ff9a2ff41 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 18 Jun 2026 22:44:12 +0200 Subject: [PATCH 410/534] refactor(core): align chunk collision settings api Signed-off-by: Blovien --- .../PhysicsChunkBuildOptions.java | 12 ++-- .../commands/PhysicsChunkSettingsCommand.java | 12 ++-- .../PersistentPhysicsStoreResource.java | 2 +- .../persistence/PersistentSpaceDto.java | 72 +++++++++---------- .../PhysicsStoreSpaceMutations.java | 8 +-- .../PhysicsWorldRuntimeResource.java | 2 +- .../systems/PersistenceCaptureSystem.java | 4 +- .../PhysicsChunkSettingsIndexSystem.java | 4 +- .../physicschunk/PhysicsChunkTerrain.java | 4 +- .../ChunkCollisionSettingsComponent.java | 8 +-- .../settings/PhysicsChunkTerrainSettings.java | 66 ++++++++--------- .../plugin/physicsstore/PhysicsSpaces.java | 2 +- .../PersistentSpaceDtoSettingsTest.java | 26 +++---- .../PersistentPhysicsStoreResourceTest.java | 14 ++++ .../settings/PhysicsSpaceSettingsTest.java | 38 +++++----- 15 files changed, 144 insertions(+), 130 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index 2d0689e3..5214326f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -15,7 +15,7 @@ public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisio int collisionMask) { public static final PhysicsChunkBuildOptions DEFAULT = - fromNativeVoxelCollisionEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED); + fromNativeVoxelCollisionEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED); public PhysicsChunkBuildOptions { Objects.requireNonNull(chunkCollisionMode, "chunkCollisionMode"); @@ -30,9 +30,9 @@ public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisio @Nonnull public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrainSettings settings) { return new PhysicsChunkBuildOptions( - ChunkCollisionMode.fromNativeVoxelCollisionEnabled(settings.isNativeVoxelTerrainEnabled()), - settings.getTerrainFriction(), - settings.getTerrainRestitution(), + ChunkCollisionMode.fromNativeVoxelCollisionEnabled(settings.isNativeVoxelCollisionEnabled()), + settings.getChunkCollisionFriction(), + settings.getChunkCollisionRestitution(), PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL); } @@ -40,8 +40,8 @@ public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrain @Nonnull public static PhysicsChunkBuildOptions fromNativeVoxelCollisionEnabled(boolean enabled) { return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelCollisionEnabled(enabled), - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index 6a6093c4..b066bb81 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -109,14 +109,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } } - boolean nativeVoxelTerrainEnabled = settings.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled(); + boolean nativeVoxelCollisionEnabled = settings.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled(); if (terrainArg.provided(ctx)) { - Boolean parsedTerrain = parseTerrain(terrainArg.get(ctx)); - if (parsedTerrain == null) { + Boolean parsedNativeVoxelCollision = parseTerrain(terrainArg.get(ctx)); + if (parsedNativeVoxelCollision == null) { ctx.sender().sendMessage(Message.raw("terrain must be boxes or native_voxels.")); return CompletableFuture.completedFuture(null); } - nativeVoxelTerrainEnabled = parsedTerrain; + nativeVoxelCollisionEnabled = parsedNativeVoxelCollision; } int playerRadius = playerRadiusArg.provided(ctx) @@ -141,7 +141,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, settings.getPhysicsChunkTerrainSettings().setTerrainMode(mode); settings.getPhysicsChunkTerrainSettings().setEntityChunkBoundaryMode(chunkBoundaryMode); - settings.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(nativeVoxelTerrainEnabled); + settings.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled); settings.getPhysicsChunkTerrainSettings().setTerrainRadius(playerRadius); settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(bodyRadius); settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(ttl); @@ -175,7 +175,7 @@ private static void sendSummary(@Nonnull CommandContext ctx, + " chunkBoundary=" + settings.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode().name().toLowerCase(Locale.ROOT) + " terrain=" - + (settings.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled() + + (settings.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled() ? "native_voxels" : "boxes"))); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java index 3ab9f9b0..85a52129 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java @@ -17,7 +17,7 @@ */ public final class PersistentPhysicsStoreResource implements Resource { - public static final int CURRENT_SCHEMA_VERSION = 1; + public static final int CURRENT_SCHEMA_VERSION = 2; private static final PersistentSpaceDto[] EMPTY_SPACES = new PersistentSpaceDto[0]; private static final PersistentBodyDto[] EMPTY_BODIES = new PersistentBodyDto[0]; private static final PersistentColliderDto[] EMPTY_COLLIDERS = new PersistentColliderDto[0]; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index 3b9943bc..ec66d4b3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -63,8 +63,8 @@ public final class PersistentSpaceDto { PersistentSpaceDto::getEntityChunkBoundaryMode) .add() .append(new KeyedCodec<>("NativeVoxelCollision", Codec.BOOLEAN, false), - (dto, value) -> dto.nativeVoxelTerrainEnabled = value != null && value, - PersistentSpaceDto::isNativeVoxelTerrainEnabled) + (dto, value) -> dto.nativeVoxelCollisionEnabled = value != null && value, + PersistentSpaceDto::isNativeVoxelCollisionEnabled) .add() .append(new KeyedCodec<>("ChunkCollisionRadius", Codec.INTEGER, false), (dto, value) -> dto.terrainRadius = value != null @@ -87,13 +87,13 @@ public final class PersistentSpaceDto { .append(new KeyedCodec<>("ChunkCollisionFriction", Codec.FLOAT, false), (dto, value) -> dto.chunkCollisionFriction = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, PersistentSpaceDto::getChunkCollisionFriction) .add() .append(new KeyedCodec<>("ChunkCollisionRestitution", Codec.FLOAT, false), (dto, value) -> dto.chunkCollisionRestitution = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, PersistentSpaceDto::getChunkCollisionRestitution) .add() .append(new KeyedCodec<>("ChunkCollisionFilter", @@ -153,17 +153,17 @@ public final class PersistentSpaceDto { @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - private boolean nativeVoxelTerrainEnabled = - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private boolean nativeVoxelCollisionEnabled = + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; private int terrainRadius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; private int bodyTerrainRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; private int terrainTtlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; - private float chunkCollisionFriction = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION; + private float chunkCollisionFriction = PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION; private float chunkCollisionRestitution = - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION; + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION; @Nonnull private CollisionFilterComponent chunkCollisionFilter = defaultChunkCollisionFilter(); @Nonnull @@ -190,12 +190,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, gravity, PhysicsChunkTerrainMode.NONE, PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED, + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL, new SolverSettingsComponent(), @@ -209,23 +209,23 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, @Nonnull PhysicsChunkTerrainMode terrainMode, - boolean nativeVoxelTerrainEnabled, + boolean nativeVoxelCollisionEnabled, int terrainRadius, int bodyTerrainRadius, int terrainTtlTicks, - float terrainFriction, - float terrainRestitution) { + float chunkCollisionFriction, + float chunkCollisionRestitution) { this(spaceUuid, backendId, gravity, terrainMode, PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - nativeVoxelTerrainEnabled, + nativeVoxelCollisionEnabled, terrainRadius, bodyTerrainRadius, terrainTtlTicks, - terrainFriction, - terrainRestitution, + chunkCollisionFriction, + chunkCollisionRestitution, PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL, new SolverSettingsComponent(), @@ -240,12 +240,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull Vector3f gravity, @Nonnull PhysicsChunkTerrainMode terrainMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, + boolean nativeVoxelCollisionEnabled, int terrainRadius, int bodyTerrainRadius, int terrainTtlTicks, - float terrainFriction, - float terrainRestitution, + float chunkCollisionFriction, + float chunkCollisionRestitution, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @@ -256,12 +256,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, gravity, terrainMode, entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, + nativeVoxelCollisionEnabled, terrainRadius, bodyTerrainRadius, terrainTtlTicks, - terrainFriction, - terrainRestitution, + chunkCollisionFriction, + chunkCollisionRestitution, PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL, solverSettings, @@ -276,12 +276,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull Vector3f gravity, @Nonnull PhysicsChunkTerrainMode terrainMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelTerrainEnabled, + boolean nativeVoxelCollisionEnabled, int terrainRadius, int bodyTerrainRadius, int terrainTtlTicks, - float terrainFriction, - float terrainRestitution, + float chunkCollisionFriction, + float chunkCollisionRestitution, int chunkCollisionGroup, int chunkCollisionMask, @Nonnull SolverSettingsComponent solverSettings, @@ -295,12 +295,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, "entityChunkBoundaryMode"); - this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + this.nativeVoxelCollisionEnabled = nativeVoxelCollisionEnabled; this.terrainRadius = terrainRadius; this.bodyTerrainRadius = bodyTerrainRadius; this.terrainTtlTicks = terrainTtlTicks; - this.chunkCollisionFriction = terrainFriction; - this.chunkCollisionRestitution = terrainRestitution; + this.chunkCollisionFriction = chunkCollisionFriction; + this.chunkCollisionRestitution = chunkCollisionRestitution; this.chunkCollisionFilter = new CollisionFilterComponent(chunkCollisionGroup, chunkCollisionMask); this.solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); @@ -339,8 +339,8 @@ public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { return entityChunkBoundaryMode; } - public boolean isNativeVoxelTerrainEnabled() { - return nativeVoxelTerrainEnabled; + public boolean isNativeVoxelCollisionEnabled() { + return nativeVoxelCollisionEnabled; } public int getTerrainRadius() { @@ -375,7 +375,7 @@ public int getChunkCollisionMask() { public ChunkCollisionSettingsComponent getChunkCollisionSettings() { return new ChunkCollisionSettingsComponent(getTerrainMode(), entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, + nativeVoxelCollisionEnabled, terrainRadius, bodyTerrainRadius, terrainTtlTicks); @@ -388,9 +388,9 @@ public MaterialComponent getChunkCollisionMaterial() { public boolean isDefaultChunkCollisionMaterial() { return Float.compare(chunkCollisionFriction, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION) == 0 + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION) == 0 && Float.compare(chunkCollisionRestitution, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION) == 0; } @Nonnull @@ -433,7 +433,7 @@ public PhysicsSpaceSettings toSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); getChunkCollisionSettings().copyTo(settings); settings.getPhysicsChunkTerrainSettings() - .setTerrainMaterial(chunkCollisionFriction, chunkCollisionRestitution); + .setChunkCollisionMaterial(chunkCollisionFriction, chunkCollisionRestitution); solverSettings.copyTo(settings); visualSyncSettings.copyTo(settings); visualMaterializationSettings.copyTo(settings); @@ -449,7 +449,7 @@ public PersistentSpaceDto copy() { gravity, terrainMode, entityChunkBoundaryMode, - nativeVoxelTerrainEnabled, + nativeVoxelCollisionEnabled, terrainRadius, bodyTerrainRadius, terrainTtlTicks, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index defe6b3e..e12f37e9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -258,15 +258,15 @@ private static void putSpaceSettingsComponents(@Nonnull Store stor @Nonnull private static MaterialComponent chunkMaterial( @Nonnull PhysicsChunkTerrainSettings settings) { - return new MaterialComponent(settings.getTerrainFriction(), - settings.getTerrainRestitution()); + return new MaterialComponent(settings.getChunkCollisionFriction(), + settings.getChunkCollisionRestitution()); } private static boolean isDefaultChunkMaterial(@Nonnull MaterialComponent material) { return Float.compare(material.getFriction(), - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION) == 0 + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION) == 0 && Float.compare(material.getRestitution(), - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION) == 0; + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION) == 0; } private static > void putOrRemoveDefault( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index ee250b4f..777dce0c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -272,7 +272,7 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( MaterialComponent material = store.getComponent(ref, MaterialComponent.getComponentType()); if (material != null) { settings.getPhysicsChunkTerrainSettings() - .setTerrainMaterial(material.getFriction(), material.getRestitution()); + .setChunkCollisionMaterial(material.getFriction(), material.getRestitution()); } if (solverSettings != null) { solverSettings.copyTo(settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index bf2c3b99..9d5995c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -197,8 +197,8 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { : new ChunkCollisionSettingsComponent(); MaterialComponent material = row.material() != null ? row.material() - : new MaterialComponent(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION); + : new MaterialComponent(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION); CollisionFilterComponent filter = row.filter() != null ? row.filter() : new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index 4f60e313..387fce1a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -76,10 +76,10 @@ private static void collectChunk( settings.getTtlTicks(), material != null ? material.getFriction() - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, material != null ? material.getRestitution() - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, filter != null ? filter.getCollisionGroup() : PhysicsCollisionFilters.TERRAIN, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 3f9e868b..0a699bee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -182,10 +182,10 @@ private static PhysicsChunkSpaceSettings requireSettings( settings.getTtlTicks(), material != null ? material.getFriction() - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, + : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, material != null ? material.getRestitution() - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, filter != null ? filter.getCollisionGroup() : PhysicsCollisionFilters.TERRAIN, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java index 3794f24d..5493bae1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java @@ -68,7 +68,7 @@ public class ChunkCollisionSettingsComponent implements Component private EntityChunkBoundaryMode entityChunkBoundaryMode = PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelCollisionEnabled = - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; private int radius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; private int bodyRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; private int ttlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; @@ -79,7 +79,7 @@ public ChunkCollisionSettingsComponent() { public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainSettings settings) { this(settings.getTerrainMode(), settings.getEntityChunkBoundaryMode(), - settings.isNativeVoxelTerrainEnabled(), + settings.isNativeVoxelCollisionEnabled(), settings.getTerrainRadius(), settings.getBodyTerrainRadius(), settings.getTerrainTtlTicks()); @@ -172,7 +172,7 @@ public void copyTo(@Nonnull PhysicsSpaceSettings settings) { public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { settings.setTerrainMode(mode); settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); - settings.setNativeVoxelTerrainEnabled(nativeVoxelCollisionEnabled); + settings.setNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled); settings.setTerrainRadius(radius); settings.setBodyTerrainRadius(bodyRadius); settings.setTerrainTtlTicks(ttlTicks); @@ -183,7 +183,7 @@ public boolean isDefault() { && entityChunkBoundaryMode == PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE && nativeVoxelCollisionEnabled - == PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED + == PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED && radius == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS && bodyRadius == PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS && ttlTicks == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java index 830e6fab..04cba35a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java @@ -48,30 +48,30 @@ public class PhysicsChunkTerrainSettings { EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED; /** - * Whether full-cube world sections should use native backend voxel terrain when available. + * Whether full-cube world sections should use native backend voxel collision when available. */ - public static final boolean DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED = false; + public static final boolean DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED = false; /** * Default friction applied to generated chunk collision bodies. */ - public static final float DEFAULT_TERRAIN_FRICTION = 0.75f; + public static final float DEFAULT_CHUNK_COLLISION_FRICTION = 0.75f; /** * Default restitution applied to generated chunk collision bodies. */ - public static final float DEFAULT_TERRAIN_RESTITUTION = 0.0f; + public static final float DEFAULT_CHUNK_COLLISION_RESTITUTION = 0.0f; @Nonnull private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - private boolean nativeVoxelTerrainEnabled = DEFAULT_NATIVE_VOXEL_TERRAIN_ENABLED; + private boolean nativeVoxelCollisionEnabled = DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; private int terrainRadius = DEFAULT_TERRAIN_RADIUS; private int bodyTerrainRadius = DEFAULT_BODY_TERRAIN_RADIUS; private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; - private float terrainFriction = DEFAULT_TERRAIN_FRICTION; - private float terrainRestitution = DEFAULT_TERRAIN_RESTITUTION; + private float chunkCollisionFriction = DEFAULT_CHUNK_COLLISION_FRICTION; + private float chunkCollisionRestitution = DEFAULT_CHUNK_COLLISION_RESTITUTION; public PhysicsChunkTerrainSettings() { } @@ -79,12 +79,12 @@ public PhysicsChunkTerrainSettings() { public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings) { terrainMode = settings.terrainMode; entityChunkBoundaryMode = settings.entityChunkBoundaryMode; - nativeVoxelTerrainEnabled = settings.nativeVoxelTerrainEnabled; + nativeVoxelCollisionEnabled = settings.nativeVoxelCollisionEnabled; terrainRadius = settings.terrainRadius; bodyTerrainRadius = settings.bodyTerrainRadius; terrainTtlTicks = settings.terrainTtlTicks; - terrainFriction = settings.terrainFriction; - terrainRestitution = settings.terrainRestitution; + chunkCollisionFriction = settings.chunkCollisionFriction; + chunkCollisionRestitution = settings.chunkCollisionRestitution; } @Nonnull @@ -107,12 +107,12 @@ public void setEntityChunkBoundaryMode( "entityChunkBoundaryMode"); } - public boolean isNativeVoxelTerrainEnabled() { - return nativeVoxelTerrainEnabled; + public boolean isNativeVoxelCollisionEnabled() { + return nativeVoxelCollisionEnabled; } - public void setNativeVoxelTerrainEnabled(boolean nativeVoxelTerrainEnabled) { - this.nativeVoxelTerrainEnabled = nativeVoxelTerrainEnabled; + public void setNativeVoxelCollisionEnabled(boolean nativeVoxelCollisionEnabled) { + this.nativeVoxelCollisionEnabled = nativeVoxelCollisionEnabled; } public int getTerrainRadius() { @@ -148,39 +148,39 @@ public void setTerrainTtlTicks(int terrainTtlTicks) { MAX_TERRAIN_TTL_TICKS); } - public float getTerrainFriction() { - return terrainFriction; + public float getChunkCollisionFriction() { + return chunkCollisionFriction; } - public void setTerrainFriction(float terrainFriction) { - this.terrainFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Terrain friction", - terrainFriction, + public void setChunkCollisionFriction(float chunkCollisionFriction) { + this.chunkCollisionFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( + "Chunk collision friction", + chunkCollisionFriction, 0.0f); } - public float getTerrainRestitution() { - return terrainRestitution; + public float getChunkCollisionRestitution() { + return chunkCollisionRestitution; } - public void setTerrainRestitution(float terrainRestitution) { - this.terrainRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Terrain restitution", - terrainRestitution, + public void setChunkCollisionRestitution(float chunkCollisionRestitution) { + this.chunkCollisionRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( + "Chunk collision restitution", + chunkCollisionRestitution, 0.0f); } - public void setTerrainMaterial(float terrainFriction, float terrainRestitution) { + public void setChunkCollisionMaterial(float friction, float restitution) { float validatedFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Terrain friction", - terrainFriction, + "Chunk collision friction", + friction, 0.0f); float validatedRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Terrain restitution", - terrainRestitution, + "Chunk collision restitution", + restitution, 0.0f); - this.terrainFriction = validatedFriction; - this.terrainRestitution = validatedRestitution; + this.chunkCollisionFriction = validatedFriction; + this.chunkCollisionRestitution = validatedRestitution; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 5f80fe99..cb37e495 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -147,7 +147,7 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, MaterialComponent.getComponentType()); if (material != null) { settings.getPhysicsChunkTerrainSettings() - .setTerrainMaterial(material.getFriction(), material.getRestitution()); + .setChunkCollisionMaterial(material.getFriction(), material.getRestitution()); } SolverSettingsComponent solverSettings = checkedStore.getComponent(checkedRef, SolverSettingsComponent.getComponentType()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index 24f78f2d..97a8b5d6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -22,8 +22,8 @@ class PersistentSpaceDtoSettingsTest { @Test void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); - original.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(true); - original.getPhysicsChunkTerrainSettings().setTerrainMaterial(0.85f, 0.2f); + original.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(true); + original.getPhysicsChunkTerrainSettings().setChunkCollisionMaterial(0.85f, 0.2f); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); @@ -34,12 +34,12 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { new Vector3f(0.0f, -9.81f, 0.0f), terrain.getTerrainMode(), terrain.getEntityChunkBoundaryMode(), - terrain.isNativeVoxelTerrainEnabled(), + terrain.isNativeVoxelCollisionEnabled(), terrain.getTerrainRadius(), terrain.getBodyTerrainRadius(), terrain.getTerrainTtlTicks(), - terrain.getTerrainFriction(), - terrain.getTerrainRestitution(), + terrain.getChunkCollisionFriction(), + terrain.getChunkCollisionRestitution(), new SolverSettingsComponent(original.getSolverSettings()), new VisualSyncSettingsComponent(original.getVisualSyncSettings()), new VisualMaterializationSettingsComponent(original.getVisualMaterializationSettings()), @@ -58,15 +58,15 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertTrue(encoded.containsKey("VisualMaterializationSettings")); PhysicsSpaceSettings decoded = Objects.requireNonNull( PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())).toSettings(); - assertTrue(decoded.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.85f, decoded.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.2f, decoded.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); + assertTrue(decoded.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertEquals(0.85f, decoded.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); + assertEquals(0.2f, decoded.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); assertDetachedVisualCadence(decoded, 7, 9, 11); PhysicsSpaceSettings copied = state.copy().toSettings(); - assertTrue(copied.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.85f, copied.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.2f, copied.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); + assertTrue(copied.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertEquals(0.85f, copied.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); + assertEquals(0.2f, copied.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); assertDetachedVisualCadence(copied, 7, 9, 11); } @@ -83,8 +83,8 @@ void roundTripPreservesChunkCollisionFilter() { PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, 0x40, 0x03, new SolverSettingsComponent(), diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java index 51d5a3b7..4ccc8b8d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java @@ -17,6 +17,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.bson.BsonDocument; import org.bson.BsonDouble; +import org.bson.BsonInt32; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -57,6 +58,19 @@ void storeResourceCodecPreservesDtoRows() { assertEquals(COLLIDER_UUID, decoded.getBodies()[0].getColliderUuids()[0]); } + @Test + void storeResourceCodecRejectsOutdatedSchemaVersion() { + PersistentPhysicsStoreResource resource = validResource(registeredBackendId("old-schema")); + BsonDocument encoded = PersistentPhysicsStoreResource.CODEC.encode(resource, + new ExtraInfo()).asDocument(); + encoded.put("SchemaVersion", new BsonInt32(1)); + + assertValidationFails( + () -> PersistentPhysicsStoreResource.CODEC.decode(encoded, new ExtraInfo()), + "Must be greater than or equal to " + + PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION); + } + @Test void preflightAcceptsAvailableRuntimeProviderWithoutLegacyBackendRegistration() { PersistentPhysicsStoreResource resource = validResource(registeredBackendId("preflight")); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index 30ce0723..37ecbf70 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -97,12 +97,12 @@ void rejectsNonPositivePhysicsChunkTerrainValues() { + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS, assertThrows(IllegalArgumentException.class, () -> settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(0)).getMessage()); - assertEquals("Terrain friction must be finite and >= 0.0", + assertEquals("Chunk collision friction must be finite and >= 0.0", assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkTerrainSettings().setTerrainFriction(-0.1f)).getMessage()); - assertEquals("Terrain restitution must be finite and >= 0.0", + () -> settings.getPhysicsChunkTerrainSettings().setChunkCollisionFriction(-0.1f)).getMessage()); + assertEquals("Chunk collision restitution must be finite and >= 0.0", assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkTerrainSettings().setTerrainRestitution(Float.NaN)).getMessage()); + () -> settings.getPhysicsChunkTerrainSettings().setChunkCollisionRestitution(Float.NaN)).getMessage()); assertEquals("Visual full sync radius must be between 1 and " + PhysicsVisualSyncSettings.MAX_VISUAL_FULL_SYNC_RADIUS, assertThrows(IllegalArgumentException.class, @@ -173,12 +173,12 @@ void defaultsFactoryReturnsFreshDefaultSettings() { assertEquals(PhysicsChunkTerrainMode.NONE, first.getPhysicsChunkTerrainSettings().getTerrainMode()); assertSame(PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, first.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode()); - assertFalse(first.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); - assertEquals(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_FRICTION, - first.getPhysicsChunkTerrainSettings().getTerrainFriction(), + assertFalse(first.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + first.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); - assertEquals(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RESTITUTION, - first.getPhysicsChunkTerrainSettings().getTerrainRestitution(), + assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, + first.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); } @@ -251,11 +251,11 @@ void terrainSettingsCopyConstructorCopiesValues() { canonical.setTerrainMode(PhysicsChunkTerrainMode.STREAMING); canonical.setEntityChunkBoundaryMode(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK); - canonical.setNativeVoxelTerrainEnabled(true); + canonical.setNativeVoxelCollisionEnabled(true); canonical.setTerrainRadius(18); canonical.setBodyTerrainRadius(7); canonical.setTerrainTtlTicks(240); - canonical.setTerrainMaterial(0.85f, 0.2f); + canonical.setChunkCollisionMaterial(0.85f, 0.2f); PhysicsChunkTerrainSettings canonicalCopy = new PhysicsChunkTerrainSettings(canonical); @@ -267,12 +267,12 @@ void terrainSettingsCopyConstructorCopiesValues() { assertEquals(PhysicsChunkTerrainMode.STREAMING, canonicalCopy.getTerrainMode()); assertEquals(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK, canonicalCopy.getEntityChunkBoundaryMode()); - assertTrue(canonicalCopy.isNativeVoxelTerrainEnabled()); + assertTrue(canonicalCopy.isNativeVoxelCollisionEnabled()); assertEquals(30, canonicalCopy.getTerrainRadius()); assertEquals(7, canonicalCopy.getBodyTerrainRadius()); assertEquals(240, canonicalCopy.getTerrainTtlTicks()); - assertEquals(0.85f, canonicalCopy.getTerrainFriction(), 0.0001f); - assertEquals(0.2f, canonicalCopy.getTerrainRestitution(), 0.0001f); + assertEquals(0.85f, canonicalCopy.getChunkCollisionFriction(), 0.0001f); + assertEquals(0.2f, canonicalCopy.getChunkCollisionRestitution(), 0.0001f); assertEquals(18, secondCopy.getTerrainRadius()); assertEquals(7, secondCopy.getBodyTerrainRadius()); assertEquals(240, secondCopy.getTerrainTtlTicks()); @@ -323,8 +323,8 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { original.getPhysicsChunkTerrainSettings().setTerrainRadius(12); original.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(6); original.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(180); - original.getPhysicsChunkTerrainSettings().setNativeVoxelTerrainEnabled(true); - original.getPhysicsChunkTerrainSettings().setTerrainMaterial(0.9f, 0.15f); + original.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(true); + original.getPhysicsChunkTerrainSettings().setChunkCollisionMaterial(0.9f, 0.15f); original.getVisualSyncSettings().setVisualMaxSyncRadius(160); original.getVisualSyncSettings().setVisualFullSyncRadius(80); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(2); @@ -356,9 +356,9 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { assertEquals(12, copy.getPhysicsChunkTerrainSettings().getTerrainRadius()); assertEquals(6, copy.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); assertEquals(180, copy.getPhysicsChunkTerrainSettings().getTerrainTtlTicks()); - assertTrue(copy.getPhysicsChunkTerrainSettings().isNativeVoxelTerrainEnabled()); - assertEquals(0.9f, copy.getPhysicsChunkTerrainSettings().getTerrainFriction(), 0.0001f); - assertEquals(0.15f, copy.getPhysicsChunkTerrainSettings().getTerrainRestitution(), 0.0001f); + assertTrue(copy.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertEquals(0.9f, copy.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); + assertEquals(0.15f, copy.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); assertEquals(160, copy.getVisualSyncSettings().getVisualMaxSyncRadius()); assertEquals(80, copy.getVisualSyncSettings().getVisualFullSyncRadius()); assertEquals(2, copy.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); From 9738bb7c6f591871c86dddac97089f16cdcac99d Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:04:18 +0200 Subject: [PATCH 411/534] fix(core): bridge physics chunk collision filters Signed-off-by: Blovien --- .../PhysicsChunkBuildOptions.java | 8 ++--- .../persistence/PersistentSpaceDto.java | 2 ++ .../PhysicsStoreSpaceMutations.java | 34 ++++++++++++++++--- .../PhysicsWorldRuntimeResource.java | 6 ++++ .../settings/PhysicsChunkTerrainSettings.java | 28 +++++++++++++++ .../plugin/physicsstore/PhysicsSpaces.java | 9 ++++- .../PersistentSpaceDtoSettingsTest.java | 5 +++ .../systems/PersistenceCaptureSystemTest.java | 5 +++ .../settings/PhysicsSpaceSettingsTest.java | 12 +++++++ 9 files changed, 100 insertions(+), 9 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index 5214326f..d5c8d505 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -33,8 +33,8 @@ public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrain ChunkCollisionMode.fromNativeVoxelCollisionEnabled(settings.isNativeVoxelCollisionEnabled()), settings.getChunkCollisionFriction(), settings.getChunkCollisionRestitution(), - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); + settings.getChunkCollisionGroup(), + settings.getChunkCollisionMask()); } @Nonnull @@ -42,8 +42,8 @@ public static PhysicsChunkBuildOptions fromNativeVoxelCollisionEnabled(boolean e return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelCollisionEnabled(enabled), PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_GROUP, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_MASK); } public boolean nativeVoxelCollisionEnabled() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index ec66d4b3..bc632fda 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -434,6 +434,8 @@ public PhysicsSpaceSettings toSettings() { getChunkCollisionSettings().copyTo(settings); settings.getPhysicsChunkTerrainSettings() .setChunkCollisionMaterial(chunkCollisionFriction, chunkCollisionRestitution); + settings.getPhysicsChunkTerrainSettings() + .setChunkCollisionFilter(getChunkCollisionGroup(), getChunkCollisionMask()); solverSettings.copyTo(settings); visualSyncSettings.copyTo(settings); visualMaterializationSettings.copyTo(settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index e12f37e9..74451449 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -11,13 +11,11 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; @@ -26,7 +24,10 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; @@ -160,6 +161,11 @@ private static void addSpaceSettingsComponents(@Nonnull Holder hol MaterialComponent.getComponentType(), material, isDefaultChunkMaterial(material)); + CollisionFilterComponent filter = chunkFilter(settings.getPhysicsChunkTerrainSettings()); + addIfNonDefault(holder, + CollisionFilterComponent.getComponentType(), + filter, + isDefaultChunkFilter(filter)); SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); addIfNonDefault(holder, SolverSettingsComponent.getComponentType(), @@ -218,6 +224,12 @@ private static void putSpaceSettingsComponents(@Nonnull Store stor MaterialComponent.getComponentType(), material, isDefaultChunkMaterial(material)); + CollisionFilterComponent filter = chunkFilter(settings.getPhysicsChunkTerrainSettings()); + putOrRemoveDefault(store, + ref, + CollisionFilterComponent.getComponentType(), + filter, + isDefaultChunkFilter(filter)); SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); putOrRemoveDefault(store, ref, @@ -269,6 +281,20 @@ private static boolean isDefaultChunkMaterial(@Nonnull MaterialComponent materia PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION) == 0; } + @Nonnull + private static CollisionFilterComponent chunkFilter( + @Nonnull PhysicsChunkTerrainSettings settings) { + return new CollisionFilterComponent(settings.getChunkCollisionGroup(), + settings.getChunkCollisionMask()); + } + + private static boolean isDefaultChunkFilter(@Nonnull CollisionFilterComponent filter) { + return filter.getCollisionGroup() + == PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_GROUP + && filter.getCollisionMask() + == PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_MASK; + } + private static > void putOrRemoveDefault( @Nonnull Store store, @Nonnull Ref ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 777dce0c..dc488e37 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -274,6 +274,12 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( settings.getPhysicsChunkTerrainSettings() .setChunkCollisionMaterial(material.getFriction(), material.getRestitution()); } + CollisionFilterComponent filter = store.getComponent(ref, + CollisionFilterComponent.getComponentType()); + if (filter != null) { + settings.getPhysicsChunkTerrainSettings() + .setChunkCollisionFilter(filter.getCollisionGroup(), filter.getCollisionMask()); + } if (solverSettings != null) { solverSettings.copyTo(settings); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java index 04cba35a..4d0e9f79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import java.util.Objects; @@ -62,6 +63,16 @@ public class PhysicsChunkTerrainSettings { */ public static final float DEFAULT_CHUNK_COLLISION_RESTITUTION = 0.0f; + /** + * Default collision group applied to generated chunk collision bodies. + */ + public static final int DEFAULT_CHUNK_COLLISION_GROUP = PhysicsCollisionFilters.TERRAIN; + + /** + * Default collision mask applied to generated chunk collision bodies. + */ + public static final int DEFAULT_CHUNK_COLLISION_MASK = PhysicsCollisionFilters.ALL; + @Nonnull private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull @@ -72,6 +83,8 @@ public class PhysicsChunkTerrainSettings { private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; private float chunkCollisionFriction = DEFAULT_CHUNK_COLLISION_FRICTION; private float chunkCollisionRestitution = DEFAULT_CHUNK_COLLISION_RESTITUTION; + private int chunkCollisionGroup = DEFAULT_CHUNK_COLLISION_GROUP; + private int chunkCollisionMask = DEFAULT_CHUNK_COLLISION_MASK; public PhysicsChunkTerrainSettings() { } @@ -85,6 +98,8 @@ public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings terrainTtlTicks = settings.terrainTtlTicks; chunkCollisionFriction = settings.chunkCollisionFriction; chunkCollisionRestitution = settings.chunkCollisionRestitution; + chunkCollisionGroup = settings.chunkCollisionGroup; + chunkCollisionMask = settings.chunkCollisionMask; } @Nonnull @@ -183,4 +198,17 @@ public void setChunkCollisionMaterial(float friction, float restitution) { this.chunkCollisionRestitution = validatedRestitution; } + public int getChunkCollisionGroup() { + return chunkCollisionGroup; + } + + public int getChunkCollisionMask() { + return chunkCollisionMask; + } + + public void setChunkCollisionFilter(int collisionGroup, int collisionMask) { + this.chunkCollisionGroup = collisionGroup; + this.chunkCollisionMask = collisionMask; + } + } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index cb37e495..7a985371 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -12,7 +12,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; @@ -20,6 +20,7 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Collection; import java.util.List; @@ -149,6 +150,12 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, settings.getPhysicsChunkTerrainSettings() .setChunkCollisionMaterial(material.getFriction(), material.getRestitution()); } + CollisionFilterComponent filter = checkedStore.getComponent(checkedRef, + CollisionFilterComponent.getComponentType()); + if (filter != null) { + settings.getPhysicsChunkTerrainSettings() + .setChunkCollisionFilter(filter.getCollisionGroup(), filter.getCollisionMask()); + } SolverSettingsComponent solverSettings = checkedStore.getComponent(checkedRef, SolverSettingsComponent.getComponentType()); if (solverSettings != null) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index 97a8b5d6..ef5f21a4 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -102,6 +102,11 @@ void roundTripPreservesChunkCollisionFilter() { assertEquals(0x03, decoded.getChunkCollisionMask()); assertEquals(0x40, state.copy().getChunkCollisionGroup()); assertEquals(0x03, state.copy().getChunkCollisionMask()); + PhysicsSpaceSettings decodedSettings = decoded.toSettings(); + assertEquals(0x40, + decodedSettings.getPhysicsChunkTerrainSettings().getChunkCollisionGroup()); + assertEquals(0x03, + decodedSettings.getPhysicsChunkTerrainSettings().getChunkCollisionMask()); } private static void assertDetachedVisualCadence(PhysicsSpaceSettings settings, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java index b854920d..663ab93d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java @@ -70,6 +70,9 @@ void generatedChunkCollisionRowsAreExcludedFromPersistentDtoTables() { UUID persistentBodyUuid = uuid(2); UUID generatedBodyUuid = uuid(3); Ref spaceRef = addSpace(store, spaceUuid); + store.putComponent(spaceRef, + CollisionFilterComponent.getComponentType(), + new CollisionFilterComponent(0x40, 0x03)); addBody(store, persistentBodyUuid, body(spaceUuid, PhysicsBodyKind.BODY, PhysicsBodyPersistenceMode.PERSISTENT, spaceRef), @@ -102,6 +105,8 @@ void generatedChunkCollisionRowsAreExcludedFromPersistentDtoTables() { assertEquals(1, persistent.getColliders().length); assertEquals(1, persistent.getShapes().length); assertEquals(1, persistent.getMaterials().length); + assertEquals(0x40, persistent.getSpaces()[0].getChunkCollisionGroup()); + assertEquals(0x03, persistent.getSpaces()[0].getChunkCollisionMask()); assertTrue(containsBody(persistent, persistentBodyUuid)); assertFalse(containsBody(persistent, generatedBodyUuid)); assertFalse(containsCollider(persistent, generatedBodyUuid)); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index 37ecbf70..98a89dd0 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -180,6 +180,10 @@ void defaultsFactoryReturnsFreshDefaultSettings() { assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, first.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); + assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_GROUP, + first.getPhysicsChunkTerrainSettings().getChunkCollisionGroup()); + assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_MASK, + first.getPhysicsChunkTerrainSettings().getChunkCollisionMask()); } @Test @@ -256,6 +260,7 @@ void terrainSettingsCopyConstructorCopiesValues() { canonical.setBodyTerrainRadius(7); canonical.setTerrainTtlTicks(240); canonical.setChunkCollisionMaterial(0.85f, 0.2f); + canonical.setChunkCollisionFilter(0x40, 0x03); PhysicsChunkTerrainSettings canonicalCopy = new PhysicsChunkTerrainSettings(canonical); @@ -273,9 +278,13 @@ void terrainSettingsCopyConstructorCopiesValues() { assertEquals(240, canonicalCopy.getTerrainTtlTicks()); assertEquals(0.85f, canonicalCopy.getChunkCollisionFriction(), 0.0001f); assertEquals(0.2f, canonicalCopy.getChunkCollisionRestitution(), 0.0001f); + assertEquals(0x40, canonicalCopy.getChunkCollisionGroup()); + assertEquals(0x03, canonicalCopy.getChunkCollisionMask()); assertEquals(18, secondCopy.getTerrainRadius()); assertEquals(7, secondCopy.getBodyTerrainRadius()); assertEquals(240, secondCopy.getTerrainTtlTicks()); + assertEquals(0x40, secondCopy.getChunkCollisionGroup()); + assertEquals(0x03, secondCopy.getChunkCollisionMask()); } @Test @@ -325,6 +334,7 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { original.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(180); original.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(true); original.getPhysicsChunkTerrainSettings().setChunkCollisionMaterial(0.9f, 0.15f); + original.getPhysicsChunkTerrainSettings().setChunkCollisionFilter(0x40, 0x07); original.getVisualSyncSettings().setVisualMaxSyncRadius(160); original.getVisualSyncSettings().setVisualFullSyncRadius(80); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(2); @@ -359,6 +369,8 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { assertTrue(copy.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); assertEquals(0.9f, copy.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); assertEquals(0.15f, copy.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); + assertEquals(0x40, copy.getPhysicsChunkTerrainSettings().getChunkCollisionGroup()); + assertEquals(0x07, copy.getPhysicsChunkTerrainSettings().getChunkCollisionMask()); assertEquals(160, copy.getVisualSyncSettings().getVisualMaxSyncRadius()); assertEquals(80, copy.getVisualSyncSettings().getVisualFullSyncRadius()); assertEquals(2, copy.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); From 5d2667d5ba242e1a84ffe68dbff11b5800e423c3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:04:42 +0200 Subject: [PATCH 412/534] refactor(core): centralize physics body row helpers Signed-off-by: Blovien --- .../PhysicsStoreControlSessionMutations.java | 16 +++----- .../plugin/physicsstore/PhysicsBodies.java | 18 +++++++++ .../examples/commands/DropCommand.java | 2 +- .../examples/commands/GrabCommand.java | 2 +- .../commands/PhysicsStoreExampleCommands.java | 2 +- .../stress/StressBenchmarkCommand.java | 8 ++-- .../stress/StressRawBodiesCommand.java | 9 ++--- .../examples/utils/ExamplePhysicsUtils.java | 37 +++++++++++-------- 8 files changed, 55 insertions(+), 39 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index ad30ae99..244db719 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -61,21 +62,14 @@ private static void restoreControlledBody(@Nonnull Store store, || store.getComponent(bodyRef, BodyComponent.getComponentType()) == null) { return; } - appendBodyCommand(store, bodyRef, BodyCommandComponent.setType(originalBodyType, true)); - appendBodyCommand(store, + PhysicsBodies.appendCommand(store, + bodyRef, + BodyCommandComponent.setType(originalBodyType, true)); + PhysicsBodies.appendCommand(store, bodyRef, BodyCommandComponent.setVelocity(releaseVelocity, ZERO, true)); } - private static void appendBodyCommand(@Nonnull Store store, - @Nonnull Ref bodyRef, - @Nonnull BodyCommandComponent command) { - BodyCommandComponent existing = store.getComponent(bodyRef, - BodyCommandComponent.getComponentType()); - BodyCommandComponent merged = existing != null ? existing.append(command) : command; - store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); - } - private static void disableJoint(@Nonnull Store store, @Nonnull Ref ref) { if (!isValidStoreRef(store, ref)) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java index 16cac486..81601d6d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java @@ -10,6 +10,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.util.Collection; @@ -117,6 +118,23 @@ public static int snapshotCount(@Nonnull Store store) { return snapshotFrame(store).bodies().size(); } + public static void appendCommand(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull BodyCommandComponent command) { + Store checkedStore = requireWorldThread(store, + "append a PhysicsStore body command"); + Ref checkedRef = requireSameValidStore(checkedStore, + bodyRef, + "bodyRef"); + BodyCommandComponent checkedCommand = Objects.requireNonNull(command, "command"); + BodyCommandComponent existing = checkedStore.getComponent(checkedRef, + BodyCommandComponent.getComponentType()); + BodyCommandComponent merged = existing != null + ? existing.append(checkedCommand) + : checkedCommand.clone(); + checkedStore.putComponent(checkedRef, BodyCommandComponent.getComponentType(), merged); + } + public static void destroy(@Nonnull Store store, @Nonnull UUID bodyUuid) { Store checkedStore = requireWorldThread(store, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index a7fddb93..70394744 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -20,7 +20,7 @@ import org.joml.Vector3d; /** - * Spawn a visible block entity that falls under Bullet physics. + * Spawn a PhysicsStore body row with an attached visible block entity. */ public class DropCommand extends AbstractAsyncPlayerCommand { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 76f057ce..bcc26641 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -200,7 +200,7 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, if (!selectedBodyRef.isValid()) { return null; } - ExamplePhysicsUtils.appendBodyCommand(selectedBodyRef.getStore(), + PhysicsBodies.appendCommand(selectedBodyRef.getStore(), selectedBodyRef, BodyCommandComponent.wake()); try { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 9eb11b72..6a9015ae 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -124,7 +124,7 @@ private void applyImpulse(@Nonnull CommandContext ctx, .getDirection()).mul(strength); Ref bodyRef = hit.bodyRef(); Store physicsStore = bodyRef.getStore(); - ExamplePhysicsUtils.appendBodyCommand(physicsStore, + PhysicsBodies.appendCommand(physicsStore, bodyRef, BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, (float) impulse.x, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 3a47e1b8..0a76b6c1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -39,7 +39,7 @@ public class StressBenchmarkCommand extends AbstractAsyncPlayerCommand { private final OptionalArg modeArg = this.withOptionalArg( "mode", - "Benchmark mode: raw or entity", + "Benchmark mode: raw/physics-only or entity", ArgTypes.STRING); private final OptionalArg countArg = this.withOptionalArg( "count", @@ -55,7 +55,7 @@ public class StressBenchmarkCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); public StressBenchmarkCommand() { - super("benchmark", "Spawn repeatable raw or entity-backed benchmark body grids"); + super("benchmark", "Spawn repeatable physics-only or entity-backed body grids"); } @Nonnull @@ -127,7 +127,7 @@ private static void spawnBenchmark(@Nonnull CommandContext ctx, + " us/body). Space bodies before add: " + beforeBodies + (request.mode() == BenchmarkMode.ENTITY ? ". blockType=" + request.blockType() : "") + ". Body-count updates are visible after PhysicsStore binds the new entities" - + ". This command measures raw setup/entity attachment; use /impulse-examples stress bodies" + + ". This command measures PhysicsStore row setup/entity attachment; use /impulse-examples stress bodies" + " for detached/detached-view scalability scenarios" + ". For clean comparisons run /impulse clean, /impulse physicschunk perf reset," + " /impulse physicschunk perf toggle before spawning," @@ -257,7 +257,7 @@ private static Integer tryParseCount(@Nonnull String value) { } private enum BenchmarkMode { - RAW("backend-only raw"), + RAW("physics-only PhysicsStore rows"), ENTITY("entity-backed Hytale"); private final String label; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index 2d35a263..40d46a98 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -24,9 +24,8 @@ import org.joml.Vector3d; /** - * Creates backend bodies without entity components. - * Compare this with the visible body stress test to separate physics cost from Hytale entity, - * networking, and rendering cost. + * Creates PhysicsStore body rows without EntityStore visual rows. Compare this with the visible + * body stress test to separate physics cost from Hytale entity, networking, and rendering cost. */ public class StressRawBodiesCommand extends AbstractAsyncPlayerCommand { @@ -44,7 +43,7 @@ public class StressRawBodiesCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); public StressRawBodiesCommand() { - super("raw-bodies", "Spawn physics bodies without Hytale entities"); + super("raw-bodies", "Spawn physics-only PhysicsStore body rows"); } @Nonnull @@ -109,7 +108,7 @@ private static String millis(long nanos) { @Nonnull private static String successMessage(@Nonnull BodyEntityBatchTiming timing, long totalWallNanos) { - return "PhysicsStore added raw body entities for " + timing.count() + return "PhysicsStore added body rows for " + timing.count() + " physics-only bodies: setupWallMs=" + millis(timing.setupWallNanos()) + " entityApplyMs=" + millis(timing.entityApplyNanos()) + " totalWallMs=" + millis(totalWallNanos) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 569a2a69..487334be 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; @@ -114,7 +115,7 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, @Nonnull BodyCommandComponent command) { Store store = PhysicsThreading.store(world); Ref bodyRef = addPhysicsStoreBody(store, descriptor); - appendBodyCommand(store, bodyRef, command); + PhysicsBodies.appendCommand(store, bodyRef, command); return bodyRef; } @@ -132,11 +133,17 @@ public static void addPhysicsStoreBodies(@Nonnull World world, Objects.requireNonNull(descriptors, "descriptors"); Store store = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(store, "add PhysicsStore body entities"); + List> holders = new ArrayList<>(); for (BodyEntityDescriptor descriptor : descriptors) { - addPhysicsStoreBodyUnchecked(store, - descriptor, + holders.add(bodyHolder(store, + Objects.requireNonNull(descriptor, "descriptor"), descriptor.dynamics(), - descriptor.target()); + descriptor.target())); + } + if (!holders.isEmpty()) { + @SuppressWarnings("unchecked") + Holder[] holderArray = holders.toArray(Holder[]::new); + store.addEntities(holderArray, AddReason.SPAWN); } } @@ -164,7 +171,15 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store bodyHolder(@Nonnull Store store, + @Nonnull BodyEntityDescriptor descriptor, + @Nonnull DynamicsComponent dynamics, + @Nullable TargetComponent target) { + return PhysicsEntities.bodyHolder(store, descriptor.bodyUuid(), descriptor.body(), Objects.requireNonNull(dynamics, "dynamics"), @@ -172,7 +187,7 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store addJoint(@Nonnull World world, joint), AddReason.SPAWN); } - public static void appendBodyCommand(@Nonnull Store store, - @Nonnull Ref bodyRef, - @Nonnull BodyCommandComponent command) { - PhysicsThreading.requireWorldThread(store, "append a PhysicsStore body command"); - BodyCommandComponent existing = store.getComponent(bodyRef, - BodyCommandComponent.getComponentType()); - BodyCommandComponent merged = existing != null ? existing.append(command) : command; - store.putComponent(bodyRef, BodyCommandComponent.getComponentType(), merged); - } - @Nullable public static SpaceId spaceId(@Nonnull CommandContext ctx, @Nonnull World world, From 37682c03efee9107a1c9f34ce12be0887cdcfae8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:12:50 +0200 Subject: [PATCH 413/534] refactor(examples): keep runtime refs on body attachments Signed-off-by: Blovien --- .../commands/PhysicsStoreExampleCommands.java | 4 +- .../explosive/ExplosiveBlockRuntime.java | 1 + .../examples/utils/ExamplePhysicsUtils.java | 73 +++++++++++++++++-- .../utils/ExamplePhysicsUtilsTest.java | 44 ++++++++++- 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 6a9015ae..76c76096 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -248,6 +248,7 @@ private static void attachView(@Nonnull CommandContext ctx, TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.spawnExternalBodyViewBlockEntity(store, time, + hit.bodyRef(), bodyUuid, point, ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE); @@ -351,7 +352,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Math.max(0L, world.getTick())); UUID bodyUuid = UUID.randomUUID(); - ExamplePhysicsUtils.addPhysicsStoreBody(world, + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, vector(spawn), @@ -368,6 +369,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, strength, verticalLift); Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, + bodyRef, bodyUuid, blockType, spawn, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 466af1f7..92d9e2b2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -197,6 +197,7 @@ private static void spawnGroupVisuals(@Nonnull TimeResource time, for (FragmentVisual visual : group.visualBlocks()) { boolean controllable = body.controllable() && !controllableAssigned; Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, + body.bodyRef(), body.bodyUuid(), visual.blockType(), visual.position(), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 487334be..74d24e9b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -600,6 +600,7 @@ public static SpawnedBlockBody attachBlockBody(@Nonnull Store store } Ref entity = spawnAttachedBlockEntity(store, time, + bodyRef, created.bodyUuid(), created.blockType(), new Vector3d(created.positionX(), created.positionY(), created.positionZ()), @@ -833,6 +834,7 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store entity = spawnAttachedBlockEntity(store, time, + null, bodyUuid, blockType, new Vector3d(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), @@ -865,21 +867,38 @@ public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store spawnExternalBodyViewBlockEntity(@Nonnull Store store, + @Nonnull TimeResource time, + @Nullable Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull Vector3d visualPosition, + @Nullable String blockType) { requirePhysicsEntityVisuals(); Holder holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(BodyAttachmentComponent.getComponentType(), - BodyAttachmentComponent.externalEntity(bodyUuid)); + externalBodyAttachment(bodyUuid, bodyRef)); return store.addEntity(holder, AddReason.SPAWN); } @Nullable private static Ref spawnAttachedBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, + @Nullable Ref bodyRef, @Nonnull UUID physicsBodyUuid, @Nullable String blockType, @Nonnull Vector3d visualPosition, boolean controllable) { Holder holder = attachedPhysicsStoreBlockEntityHolder(time, + bodyRef, physicsBodyUuid, blockType, visualPosition, @@ -892,6 +911,27 @@ private static Ref spawnAttachedBlockEntity(@Nonnull Store attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, + @Nonnull UUID physicsBodyUuid, + @Nullable String blockType, + @Nonnull Vector3d visualPosition, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY, + boolean controllable) { + return attachedPhysicsStoreBlockEntityHolder(time, + null, + physicsBodyUuid, + blockType, + visualPosition, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY, + controllable); + } + + @Nonnull + public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, + @Nullable Ref bodyRef, @Nonnull UUID physicsBodyUuid, @Nullable String blockType, @Nonnull Vector3d visualPosition, @@ -902,10 +942,11 @@ public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull requirePhysicsEntityVisuals(); Holder holder = blockEntityHolder(time, blockType, visualPosition); holder.addComponent(BodyAttachmentComponent.getComponentType(), - BodyAttachmentComponent.impulseOwnedVisual(physicsBodyUuid, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY)); + impulseOwnedBodyAttachment(physicsBodyUuid, + bodyRef, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY)); if (controllable && PhysicsControlSessions.isAvailable()) { holder.addComponent(ImpulseControllableComponent.getComponentType(), new ImpulseControllableComponent()); @@ -913,6 +954,28 @@ public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull return holder; } + @Nonnull + static BodyAttachmentComponent externalBodyAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity(bodyUuid); + attachment.setBodyRef(bodyRef); + return attachment; + } + + @Nonnull + static BodyAttachmentComponent impulseOwnedBodyAttachment(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY) { + BodyAttachmentComponent attachment = BodyAttachmentComponent.impulseOwnedVisual(bodyUuid, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); + attachment.setBodyRef(bodyRef); + return attachment; + } + @Nonnull private static Holder blockEntityHolder(@Nonnull TimeResource time, @Nullable String blockType, diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java index 2d53f038..5e5052b5 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java @@ -1,11 +1,13 @@ package dev.hytalemodding.impulse.examples.utils; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.modules.entity.EntityModule; import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; @@ -18,9 +20,11 @@ import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.examples.testsupport.ExampleControlTestSupport; import java.lang.reflect.Field; +import java.util.UUID; import javax.annotation.Nonnull; - +import org.joml.Quaternionf; import org.joml.Vector3d; +import org.joml.Vector3f; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -93,6 +97,27 @@ void ecsAuthoredDynamicBodyHolderAddsControllableMarkerWhenControlIsAvailable() assertTrue(holder.getArchetype().contains(ImpulseControllableComponent.getComponentType())); } + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void refAwareAttachmentsKeepDurableUuidAndRuntimeRef() throws Exception { + UUID bodyUuid = UUID.randomUUID(); + Ref bodyRef = new TestPhysicsRef(7); + + BodyAttachmentComponent external = ExamplePhysicsUtils.externalBodyAttachment(bodyUuid, + bodyRef); + BodyAttachmentComponent impulseOwned = ExamplePhysicsUtils.impulseOwnedBodyAttachment( + bodyUuid, + bodyRef, + new Vector3f(1.0f, 2.0f, 3.0f), + new Quaternionf(), + 0.25f); + + assertEquals(bodyUuid, external.getBodyUuid()); + assertSame(bodyRef, bodyRef(external)); + assertEquals(bodyUuid, impulseOwned.getBodyUuid()); + assertSame(bodyRef, bodyRef(impulseOwned)); + } + @Nonnull private static T allocate(@Nonnull Class type) throws Exception { Class unsafeType = Class.forName("sun.misc.Unsafe"); @@ -116,4 +141,21 @@ private static Field staticField(@Nonnull Class owner, @Nonnull String name) field.setAccessible(true); return field; } + + private static Object bodyRef(@Nonnull BodyAttachmentComponent attachment) throws Exception { + return BodyAttachmentComponent.class.getMethod("getBodyRef").invoke(attachment); + } + + @SuppressWarnings("rawtypes") + private static final class TestPhysicsRef extends Ref { + + private TestPhysicsRef(int index) { + super(null, index); + } + + @Override + public boolean isValid() { + return true; + } + } } From 1cda75fa89339320d77224e7420abb87faab1849 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:15:16 +0200 Subject: [PATCH 414/534] fix(core): preserve last chunk collision mutation Signed-off-by: Blovien --- .../ChunkCollisionMutationDrainSystem.java | 24 ++- ...ChunkCollisionMutationDrainSystemTest.java | 143 ++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index dc081295..fd91e0c3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -42,7 +42,9 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -71,7 +73,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } PhysicsChunkCollisionMutationQueueResource queue = store.getResource( PhysicsChunkCollisionMutationQueueResource.getResourceType()); - List mutations = queue.drain(); + List mutations = coalesceLastMutationPerSource(queue.drain()); if (mutations.isEmpty()) { return; } @@ -85,6 +87,18 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) applyUpserts(store, runtime, identity, chunkCollisionPayloads, restore, mutations); } + @Nonnull + private static List coalesceLastMutationPerSource( + @Nonnull List mutations) { + Map latest = new LinkedHashMap<>(); + for (ChunkCollisionMutation mutation : mutations) { + MutationKey key = new MutationKey(mutation.spaceUuid(), mutation.sourceKey()); + latest.remove(key); + latest.put(key, mutation); + } + return new ArrayList<>(latest.values()); + } + private static void applyRemovals(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @@ -395,4 +409,12 @@ private record GeneratedRow(@Nonnull Ref ref, Objects.requireNonNull(uuid, "uuid"); } } + + private record MutationKey(@Nonnull UUID spaceUuid, + @Nonnull String sourceKey) { + private MutationKey { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(sourceKey, "sourceKey"); + } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 106cf848..3016388f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -331,6 +331,131 @@ void removeDeletesGeneratedRowsAndPayloadResource() { } } + @Test + void sameDrainUpsertThenRemoveKeepsRemoveIntent() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-upsert-remove-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(41); + BackendId backendId = new BackendId("test:chunk-collision-drain-upsert-remove"); + addBoundSpace(store, spaceUuid, backendId); + String sourceKey = "8:9:10"; + String payloadKey = "chunk-collision/8/9/10"; + ChunkCollisionPayload payload = boxPayload(1.0, 2.0, 3.0); + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 8, + 9, + 10, + payloadKey, + payload)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + UUID boxUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0); + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + assertNotNull(identity.getByUuid(boxUuid)); + + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 8, + 9, + 10, + payloadKey, + boxPayload(4.0, 5.0, 6.0))); + queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, sourceKey, 8, 9, 10)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertNull(identity.getByUuid(boxUuid)); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void sameDrainRemoveThenUpsertKeepsUpsertIntent() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-remove-upsert-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(42); + BackendId backendId = new BackendId("test:chunk-collision-drain-remove-upsert"); + Ref spaceRef = addBoundSpace(store, spaceUuid, backendId); + String sourceKey = "0:1:2"; + String payloadKey = "chunk-collision/0/1/2"; + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 0, + 1, + 2, + payloadKey, + boxPayload(1.0, 2.0, 3.0))); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + BoxPayload updatedBox = new BoxPayload(4.0, 5.0, 6.0, 0.5, 0.5, 0.5); + ChunkCollisionPayload updatedPayload = new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(updatedBox), + List.of(), + false, + 0.6f, + 0.1f, + 0x10, + 0x0F, + List.of()); + queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, sourceKey, 0, 1, 2)); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 0, + 1, + 2, + payloadKey, + updatedPayload)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertGeneratedBox(store, + spaceUuid, + spaceRef, + sourceKey, + payloadKey, + PartKind.BOX, + 0, + updatedBox, + updatedPayload); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Nonnull private static Ref addBoundSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -364,6 +489,24 @@ private static Ref addBoundSpace(@Nonnull Store stor return spaceRef; } + @Nonnull + private static ChunkCollisionPayload boxPayload(double centerX, + double centerY, + double centerZ) { + return new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(new BoxPayload(centerX, centerY, centerZ, 0.5, 0.5, 0.5)), + List.of(), + false, + 0.6f, + 0.1f, + 0x10, + 0x0F, + List.of()); + } + private static void assertGeneratedVoxel(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull Ref spaceRef, From caadcf693e6162d821bc1796cd008d28d2dc015f Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:17:13 +0200 Subject: [PATCH 415/534] fix(core): clean restored orphan visual proxies Signed-off-by: Blovien --- .../PhysicsProjectionCleanupSystem.java | 13 ++++- .../PhysicsProjectionCleanupSystemTest.java | 54 ++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java index 780892d9..edb7265b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import java.util.Collections; import java.util.Map; @@ -63,7 +64,7 @@ private static void clearDestroyedBodyAttachments(@Nonnull Store st (index, archetypeChunk, commandBuffer) -> { BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); - if (attachment == null || !hasDestroyedBodyRef(attachment)) { + if (attachment == null || !hasMissingBody(attachment, resource)) { return; } GeneratedProxyLifecycle.clearMissingAttachment(archetypeChunk.getReferenceTo(index), @@ -78,6 +79,16 @@ private static boolean hasDestroyedBodyRef(@Nonnull BodyAttachmentComponent atta return bodyRef != null && !bodyRef.isValid(); } + private static boolean hasMissingBody(@Nonnull BodyAttachmentComponent attachment, + @Nonnull PhysicsWorldRuntimeResource resource) { + if (hasDestroyedBodyRef(attachment)) { + return true; + } + return attachment.getBodyRef() == null + && attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY + && resource.getBodyRegistrationView(attachment.getBodyUuid()) == null; + } + private static void removeOrphanGeneratedVisualProxyMarkers(@Nonnull Store store) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java index bec2f146..9701a2e8 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -90,6 +91,57 @@ void generatedProxyWithDestroyedBodyRefIsRemovedAndUnindexed() { } } + @Test + void generatedProxyWithMissingBodyRefIsRemovedAndUnindexed() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = store(registry, "projection-cleanup-generated-missing-ref"); + try { + UUID bodyUuid = UUID.randomUUID(); + Ref proxyRef = addAttachment(store, + bodyUuid, + null, + AttachmentLifecycle.GENERATED_PROXY, + true); + PhysicsProjectionIndexResource projection = projection(store); + projection.registerAttachment(bodyUuid, proxyRef); + projection.setGeneratedVisualProxy(bodyUuid, proxyRef); + + new PhysicsProjectionCleanupSystem().tick(0.0f, 0, store); + + assertFalse(proxyRef.isValid()); + assertFalse(projection.hasAttachments(bodyUuid)); + assertNull(projection.getGeneratedVisualProxy(bodyUuid)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void externalAttachmentWithMissingBodyRefIsPreservedForUuidResolution() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = store(registry, "projection-cleanup-external-missing-ref"); + try { + UUID bodyUuid = UUID.randomUUID(); + Ref entityRef = addAttachment(store, + bodyUuid, + null, + AttachmentLifecycle.EXTERNAL_ENTITY, + false); + PhysicsProjectionIndexResource projection = projection(store); + projection.registerAttachment(bodyUuid, entityRef); + + new PhysicsProjectionCleanupSystem().tick(0.0f, 0, store); + + assertTrue(entityRef.isValid()); + assertNotNull(store.getComponent(entityRef, BodyAttachmentComponent.getComponentType())); + assertTrue(projection.hasAttachments(bodyUuid)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Test void validBodyRefIsPreservedWhenSnapshotPublicationLags() { ComponentRegistry registry = new ComponentRegistry<>(); @@ -135,7 +187,7 @@ private static Store store(@Nonnull ComponentRegistry @Nonnull private static Ref addAttachment(@Nonnull Store store, @Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, + @Nullable Ref bodyRef, @Nonnull AttachmentLifecycle lifecycle, boolean generatedProxy) { Holder holder = store.getRegistry().newHolder(); From ddd883590661227fae987dc47e2bd2529a2d34b2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:19:12 +0200 Subject: [PATCH 416/534] fix(core): keep terrain out of radius clean Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 7 + .../CleanCommandLifecycleGuardTest.java | 150 ++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 2596e934..cbacfe44 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -19,6 +19,8 @@ import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; @@ -381,6 +383,11 @@ private static SelectedBodies selectBodiesNear(@Nonnull Store stor Set bodyUuids = new ObjectOpenHashSet<>(); double radiusSquared = (double) radius * radius; for (PhysicsBodySnapshot snapshot : PhysicsBodies.snapshotFrame(store).bodies()) { + PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView(store, + snapshot.bodyUuid()); + if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { + continue; + } Vector3f position = snapshot.position(); double dx = position.x - center.x; double dy = position.y - center.y; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java index 0224b4c7..687521e5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -1,11 +1,39 @@ package dev.hytalemodding.impulse.core.internal.commands; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3d; import org.junit.jupiter.api.Test; class CleanCommandLifecycleGuardTest { @@ -21,4 +49,126 @@ void cleanCommandDoesNotRemoveEveryBodyAttachmentEntity() throws IOException { + " commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), " + "RemoveReason.REMOVE);")); } + + @Test + void radiusCleanSelectsOnlyNormalBodySnapshots() throws Exception { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("clean-radius-kind-filter")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID bodyUuid = UUID.randomUUID(); + UUID terrainUuid = UUID.randomUUID(); + UUID spaceUuid = UUID.randomUUID(); + publishSnapshots(store, bodyUuid, terrainUuid, spaceUuid); + publishRegistrations(store, bodyUuid, terrainUuid); + + Set selected = selectBodyUuidsNear(store, new Vector3d(), 10.0f); + + assertEquals(Set.of(bodyUuid), selected); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + private static void publishSnapshots(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull UUID terrainUuid, + @Nonnull UUID spaceUuid) { + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(bodyUuid, spaceUuid), snapshot(terrainUuid, spaceUuid)))); + } + + private static void publishRegistrations(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull UUID terrainUuid) { + store.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .publish(1L, + List.of(publication(1, bodyUuid, PhysicsBodyKind.BODY), + publication(2, terrainUuid, PhysicsBodyKind.TERRAIN))); + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + return PhysicsBodySnapshot.of(bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + + @Nonnull + private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( + int rowIndex, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyKind kind) { + return new PhysicsBodyRegistrationResource.BodyRegistrationPublication( + new TestPhysicsRef(rowIndex), + new PhysicsBodyRegistrationView(bodyUuid, + new SpaceId(1), + kind, + PhysicsBodyPersistenceMode.RUNTIME_ONLY)); + } + + @Nonnull + private static Set selectBodyUuidsNear(@Nonnull Store store, + @Nonnull Vector3d center, + float radius) throws Exception { + Method selectBodiesNear = CleanCommand.class.getDeclaredMethod("selectBodiesNear", + Store.class, + Vector3d.class, + float.class); + selectBodiesNear.setAccessible(true); + Object selected = selectBodiesNear.invoke(null, store, center, radius); + Method bodyUuids = selected.getClass().getDeclaredMethod("bodyUuids"); + bodyUuids.setAccessible(true); + return (Set) bodyUuids.invoke(selected); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } + + private static final class TestPhysicsRef extends Ref { + + private TestPhysicsRef(int index) { + super(null, index); + } + + @Override + public boolean isValid() { + return true; + } + } } From c30443033e0d5aa1ad4ebb9b1d9b73cb2d0127c2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:27:11 +0200 Subject: [PATCH 417/534] refactor(core): internalize chunk collision source rows Signed-off-by: Blovien --- .../components/ChunkCollisionSourceComponent.java | 8 ++++---- .../physicsstore/PhysicsStoreTopologyMutations.java | 2 +- .../registration/PhysicsComponentTypeRegistry.java | 2 +- .../systems/ChunkCollisionMutationDrainSystem.java | 4 ++-- .../systems/ChunkCollisionVoxelStitchingSystem.java | 4 ++-- .../internal/systems/debug/PhysicsStoreDebugQueries.java | 4 ++-- .../core/plugin/components/PhysicsComponentTypes.java | 7 ------- .../systems/ChunkCollisionMutationDrainSystemTest.java | 4 ++-- .../systems/ChunkCollisionVoxelStitchingSystemTest.java | 4 ++-- .../internal/systems/PersistenceCaptureSystemTest.java | 4 ++-- 10 files changed, 18 insertions(+), 25 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/{plugin => internal}/modules/physicschunk/components/ChunkCollisionSourceComponent.java (92%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java similarity index 92% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java index b8fa02c0..291c39dc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSourceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -7,12 +7,12 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import java.util.Objects; import javax.annotation.Nonnull; /** - * PhysicsChunk source metadata for generated runtime-only chunk collision body rows. + * Internal PhysicsChunk source metadata for generated runtime-only chunk collision body rows. */ public final class ChunkCollisionSourceComponent implements Component { @@ -116,7 +116,7 @@ public int getPartIndex() { @Nonnull public static ComponentType getComponentType() { - return PhysicsComponentTypes.chunkCollisionSourceComponentType(); + return PhysicsComponentTypeRegistry.chunkCollisionSourceComponentType(); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 52581823..2c420b99 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java index cc709335..738d4325 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index fd91e0c3..8d5a7838 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -37,8 +37,8 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.nio.charset.StandardCharsets; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java index 0836883d..feec0b3f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java @@ -20,8 +20,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 97c3da99..670991ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -23,8 +23,8 @@ import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index 18bf2752..c2b1f851 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import javax.annotation.Nonnull; @@ -36,12 +35,6 @@ public static ComponentType bodyCommandCompo return PhysicsComponentTypeRegistry.bodyCommandComponentType(); } - @Nonnull - public static ComponentType - chunkCollisionSourceComponentType() { - return PhysicsComponentTypeRegistry.chunkCollisionSourceComponentType(); - } - @Nonnull public static ComponentType chunkCollisionSettingsComponentType() { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 3016388f..cb9b662f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -45,8 +45,8 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index cdf83147..55b5a6a1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -43,8 +43,8 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java index 663ab93d..d3787571 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java @@ -37,8 +37,8 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; From a348736d7f9f1bb362a937b33d72869bc36c2a3f Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:31:50 +0200 Subject: [PATCH 418/534] refactor(core): narrow physics world resource api Signed-off-by: Blovien --- .../crucible/ImpulseApiCrucibleTests.java | 43 ++-- .../crucible/ImpulseLiveCrucibleTests.java | 15 +- .../PhysicsWorldRuntimeResource.java | 25 --- .../resources/PhysicsWorldResource.java | 212 +----------------- 4 files changed, 34 insertions(+), 261 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index f1d3fa34..2350a2f3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -22,6 +22,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -184,30 +185,31 @@ private static boolean createSpaceAndBody() { private static CompletionStage spaceCountRoundTrip(@Nonnull CrucibleContext context) { return callWhenPhysicsStoreIdle(context, "run Crucible space count round trip", world -> { - PhysicsWorldResource resource = physicsResource(world); Store store = physicsStore(world); - int previousCount = resource.getSpaceCount(); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", + int previousCount = PhysicsSpaces.count(store); + SpaceId spaceId = PhysicsSpaces.create(store, + CrucibleBackends.requireBackendId(), PhysicsSpaceSettings.defaults()); PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); - return resource.getSpaceCount() == previousCount && !resource.hasSpace(spaceId); + return PhysicsSpaces.count(store) == previousCount + && !PhysicsSpaces.hasSpace(store, spaceId); }); } private static CompletionStage createdExplicitSpaceLifecycleWorks( @Nonnull CrucibleContext context) { return callWhenPhysicsStoreIdle(context, "run Crucible explicit space lifecycle", world -> { - PhysicsWorldResource resource = physicsResource(world); Store store = physicsStore(world); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", + SpaceId spaceId = PhysicsSpaces.create(store, + CrucibleBackends.requireBackendId(), PhysicsSpaceSettings.streamingPhysicsChunk()); - boolean registered = resource.hasSpace(spaceId) - && resource.getSpaceSettings(spaceId).getPhysicsChunkTerrainSettings().getTerrainMode() - == PhysicsChunkTerrainMode.STREAMING; + PhysicsSpaceSettings spaceSettings = PhysicsSpaces.settings(store, spaceId); + boolean registered = PhysicsSpaces.hasSpace(store, spaceId) + && spaceSettings != null + && spaceSettings.getPhysicsChunkTerrainSettings().getTerrainMode() + == PhysicsChunkTerrainMode.STREAMING; PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); - return registered && !resource.hasSpace(spaceId); + return registered && !PhysicsSpaces.hasSpace(store, spaceId); }); } @@ -240,7 +242,7 @@ private static CompletionStage populatedBodyCleanup( PhysicsStoreSpaceMutations.removeEmptySpace( state.store(), state.spaceId()); - removedSpace = !resource.hasSpace(state.spaceId()); + removedSpace = !PhysicsSpaces.hasSpace(state.store(), state.spaceId()); } return spaceEmpty && noRegistrations && removedSpace; }))); @@ -249,10 +251,9 @@ private static CompletionStage populatedBodyCleanup( private static CompletionStage createPopulatedBodyCleanupState( @Nonnull CrucibleContext context) { return callWhenPhysicsStoreIdle(context, "create Crucible body cleanup state", world -> { - PhysicsWorldResource resource = physicsResource(world); Store store = physicsStore(world); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", + SpaceId spaceId = PhysicsSpaces.create(store, + CrucibleBackends.requireBackendId(), PhysicsSpaceSettings.defaults()); Ref bodyRef = addCrucibleBox(store, spaceId, UUID.randomUUID()); return new PopulatedBodyCleanupState(world, store, spaceId, bodyRef); @@ -324,13 +325,15 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte PhysicsSpaceSettings settings = populatedSettings(); return callWhenPhysicsStoreIdle(context, "run Crucible settings round trip", world -> { - PhysicsWorldResource resource = physicsResource(world); Store store = physicsStore(world); - SpaceId spaceId = resource.createSpace(CrucibleBackends.requireBackendId(), - "crucible", + SpaceId spaceId = PhysicsSpaces.create(store, + CrucibleBackends.requireBackendId(), settings); try { - PhysicsSpaceSettings copy = resource.getSpaceSettings(spaceId); + PhysicsSpaceSettings copy = PhysicsSpaces.settings(store, spaceId); + if (copy == null) { + return false; + } return copy.getPhysicsChunkTerrainSettings().getTerrainMode() == PhysicsChunkTerrainMode.STREAMING && copy.getPhysicsChunkTerrainSettings().getTerrainRadius() == 9 && copy.getPhysicsChunkTerrainSettings().getBodyTerrainRadius() == 5 diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 5f282206..27547b66 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; @@ -26,7 +27,6 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.Comparator; @@ -73,14 +73,13 @@ private static CompletionStage entityBodyFallsThroughEcs(CrucibleContex try { World world = context.world(); Store store = world.getEntityStore().getStore(); - PhysicsWorldResource resource = store.getResource(PhysicsWorldResource.getResourceType()); - SpaceId spaceId = liveTestSpaceId(resource, world); + Store physicsStore = physicsStore(world); + SpaceId spaceId = liveTestSpaceId(physicsStore, world); Vector3d visualPosition = new Vector3d( context.wx(0), context.wy(20), context.wz(0)); - Store physicsStore = physicsStore(world); PhysicsStoreSpaceMutations.putSpaceGravity(physicsStore, spaceId, new Vector3f(0.0f, -9.81f, 0.0f)); @@ -123,16 +122,16 @@ private static boolean bodyAndEntityMovedDown(Store store, return transformY < startY - 0.05 && bodyY < startY - 0.05f; } - private static SpaceId liveTestSpaceId(PhysicsWorldResource resource, World world) { - SpaceId existingSpaceId = resource.getSpaceIds() + private static SpaceId liveTestSpaceId(Store store, World world) { + SpaceId existingSpaceId = PhysicsSpaces.spaceIds(store) .stream() .min(Comparator.comparingInt(SpaceId::value)) .orElse(null); if (existingSpaceId != null) { return existingSpaceId; } - return resource.createSpace(CrucibleBackends.requireBackendId(), - world.getName(), + return PhysicsSpaces.create(store, + CrucibleBackends.requireBackendId(), PhysicsSpaceSettings.defaults()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index dc488e37..44db0a30 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -141,7 +141,6 @@ public void detachEntityStore(@Nonnull Store store) { } @Nonnull - @Override public PhysicsEventFrame getLatestEventFrame() { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativePhysicsStore("read latest physics event frame") @@ -388,7 +387,6 @@ private T callDirectRuntime(@Nonnull String operation, @Nonnull - @Override public PhysicsWorldSettings getWorldSettings() { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativePhysicsStore("read physics world settings") @@ -398,7 +396,6 @@ public PhysicsWorldSettings getWorldSettings() { return simulationRuntime.getWorldSettings(); } - @Override public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); if (hasAttachedAuthoritativePhysicsStore()) { @@ -416,7 +413,6 @@ public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { } @Nonnull - @Override public PhysicsMutationHandle setWorldSettingsAsync( @Nonnull PhysicsWorldSettings settings) { PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); @@ -448,19 +444,16 @@ private void setAuthoritativeWorldSettings(@Nonnull Store store, } @Nonnull - @Override public SpaceId createSpace(@Nonnull BackendId backendId) { return createSpace(backendId, "", PhysicsSpaceSettings.defaults()); } @Nonnull - @Override public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull String worldName) { return createSpace(backendId, worldName, PhysicsSpaceSettings.defaults()); } @Nonnull - @Override public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { @@ -468,7 +461,6 @@ public SpaceId createSpace(@Nonnull BackendId backendId, } @Nonnull - @Override public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull SpaceId spaceId, @Nonnull String worldName, @@ -489,7 +481,6 @@ public SpaceId createSpace(@Nonnull BackendId backendId, } @Nonnull - @Override public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backendId, @Nonnull String worldName, @Nonnull PhysicsSpaceSettings settings) { @@ -498,7 +489,6 @@ public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backen } @Nonnull - @Override public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backendId, @Nonnull SpaceId spaceId, @Nonnull String worldName, @@ -539,7 +529,6 @@ private PhysicsSpaceBinding getSpaceBinding(@Nonnull SpaceId spaceId) { return spaceRuntime.getBinding(spaceId); } - @Override public boolean hasSpace(@Nonnull SpaceId spaceId) { if (isAuthoritativePhysicsStoreActive()) { return authoritativePhysicsStore("check physics space") @@ -555,7 +544,6 @@ private PhysicsSpaceBinding requireSpaceBinding(@Nonnull SpaceId spaceId) { } @Nonnull - @Override public Collection getSpaceIds() { if (isAuthoritativePhysicsStoreActive()) { return List.copyOf(authoritativePhysicsStore("list physics spaces") @@ -565,7 +553,6 @@ public Collection getSpaceIds() { return spaceRuntime.getSpaceIds(); } - @Override public int getSpaceCount() { if (isAuthoritativePhysicsStoreActive()) { return authoritativePhysicsStore("count physics spaces") @@ -1203,12 +1190,10 @@ public int forEachIndexedBodySnapshotNearWithRefs(@Nonnull SpaceId spaceId, visitor.accept(bodyUuid, null, snapshot, bodySpaceId, kind, persistenceMode)); } - @Override public void removeSpace(@Nonnull SpaceId spaceId) { removeSpace(spaceId, ""); } - @Override public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("remove physics space"); @@ -1222,7 +1207,6 @@ public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { } @Nonnull - @Override public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { @@ -1264,7 +1248,6 @@ private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldNa } } - @Override public void clearAllSpaces(@Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("clear physics spaces"); @@ -1277,7 +1260,6 @@ public void clearAllSpaces(@Nonnull String worldName) { } @Nonnull - @Override public PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { return enqueueAuthoritativePhysicsStoreMutation("clear physics spaces", @@ -1365,7 +1347,6 @@ private PhysicsRuntimeResetResult resetRuntimeStateKeepingSpacesDirect(@Nonnull } @Nonnull - @Override public PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { if (isAuthoritativePhysicsStoreActive()) { PhysicsSpaceSettings settings = getPhysicsStoreSpaceSettings( @@ -1381,7 +1362,6 @@ public PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { } @Nonnull - @Override public PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef) { if (!isAuthoritativePhysicsStoreActive()) { throw new IllegalStateException("Cannot read PhysicsStore space settings by entity ref " @@ -1402,7 +1382,6 @@ public PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { return spaceRuntime.getLiveSpaceSettings(spaceId); } - @Override public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { PhysicsStoreSpaceMutations.putSpaceSettings( @@ -1416,7 +1395,6 @@ public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSett runDirectRuntimeMutation("set physics space settings", () -> setSpaceSettingsDirect(spaceId, requested)); } - @Override public void setSpaceSettings(@Nonnull Ref spaceRef, @Nonnull PhysicsSpaceSettings settings) { if (!isAuthoritativePhysicsStoreActive()) { @@ -1430,7 +1408,6 @@ public void setSpaceSettings(@Nonnull Ref spaceRef, } @Nonnull - @Override public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { if (isAuthoritativePhysicsStoreActive()) { @@ -1455,7 +1432,6 @@ private void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { spaceRuntime.validateStepModeSupported(stepMode); } - @Override public void destroyBody(@Nonnull UUID bodyUuid) { UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { @@ -1469,7 +1445,6 @@ public void destroyBody(@Nonnull UUID bodyUuid) { } @Nonnull - @Override public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid) { UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index 973c562a..ca193fdc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -5,18 +5,12 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import java.util.Collection; import java.util.UUID; @@ -26,13 +20,11 @@ import org.joml.Vector3f; /** - * Public alpha facade for a world's physics runtime resource. + * Public legacy read facade for copied physics snapshots, registrations, and attachments. * - *

        The concrete Impulse runtime lives in the internal package. This facade remains for - * compatibility body lifetime by durable UUID, immutable snapshots, read-only registration views, - * and public attachment/control hooks. New code that already has the real PhysicsStore should use - * {@link PhysicsWorlds} for world settings/event-frame reads and {@link PhysicsSpaces} for space - * lifecycle and per-space settings.

        + *

        The concrete Impulse runtime lives in the internal package. New authoring code should mutate + * PhysicsStore entities and resources through {@code core.plugin.physicsstore} helpers and direct + * {@code Store} ECS operations.

        * *

        No physics space is created implicitly. Consumers choose which explicit {@link SpaceId} to * target for each operation.

        @@ -46,121 +38,6 @@ public abstract class PhysicsWorldResource implements Resource { protected PhysicsWorldResource() { } - /** - * Returns the latest value-only physics store event frame. - * - *

        Event frames describe store tick lane outcomes. They do not expose live - * backend handles and do not imply that command completion has been - * included in a captured or reader-applied body snapshot.

        - */ - @Nonnull - public abstract PhysicsEventFrame getLatestEventFrame(); - - /** - * Returns a defensive copy of the world-level simulation settings. - * - *

        Changing the returned copy has no effect until it is passed to - * {@link #setWorldSettings(PhysicsWorldSettings)} or - * {@link #setWorldSettingsAsync(PhysicsWorldSettings)}.

        - */ - @Nonnull - public abstract PhysicsWorldSettings getWorldSettings(); - - /** - * Applies world-level simulation settings on the store tick lane. - */ - public abstract void setWorldSettings(@Nonnull PhysicsWorldSettings settings); - - /** - * Queues a world-level simulation settings update. - */ - @Nonnull - public abstract PhysicsMutationHandle setWorldSettingsAsync( - @Nonnull PhysicsWorldSettings settings); - - /** - * Creates a physics space using default settings and returns its id. - * - *

        Creation is serialized through this world's logical store tick lane. Callers must not - * infer a stable Java thread identity from the synchronous return path.

        - */ - @Nonnull - public abstract SpaceId createSpace(@Nonnull BackendId backendId); - - /** - * Creates a physics space for logging under the supplied world name and returns its id. - * - *

        No default space is created implicitly; the returned id is the explicit space handle for - * later world-resource operations.

        - */ - @Nonnull - public abstract SpaceId createSpace(@Nonnull BackendId backendId, - @Nonnull String worldName); - - /** - * Creates a physics space with generated logical id and supplied settings. - * - *

        The live backend space is created inside the serialized store tick lane. Use the async - * variant when the caller should not block on store tick execution.

        - */ - @Nonnull - public abstract SpaceId createSpace(@Nonnull BackendId backendId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings); - - /** - * Creates a physics space with an explicit logical id and supplied settings. - * - *

        The explicit id is reserved by the caller, but live backend creation still runs inside the - * serialized store tick lane.

        - */ - @Nonnull - public abstract SpaceId createSpace(@Nonnull BackendId backendId, - @Nonnull SpaceId spaceId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings); - - /** - * Queues physics-space creation and returns the reserved generated space id. - * - *

        The returned mutation handle completes when the store tick lane creates the live backend - * space, not when a later snapshot or ECS reader has consumed any resulting state.

        - */ - @Nonnull - public abstract PhysicsMutationHandle createSpaceAsync( - @Nonnull BackendId backendId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings); - - /** - * Queues physics-space creation and returns the requested explicit space id. - * - *

        Different worlds may queue work concurrently, but this world's spaces remain serialized by - * its store tick lane.

        - */ - @Nonnull - public abstract PhysicsMutationHandle createSpaceAsync( - @Nonnull BackendId backendId, - @Nonnull SpaceId spaceId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings); - - /** - * Returns whether a physics space id is currently registered. - */ - public abstract boolean hasSpace(@Nonnull SpaceId spaceId); - - /** - * Returns a snapshot collection of registered physics space ids. - */ - @Nonnull - public abstract Collection getSpaceIds(); - - /** - * Returns the number of registered physics spaces. - */ - public abstract int getSpaceCount(); - /** * Captures and publishes body snapshots from the live backend state in the legacy runtime, or * returns the latest copied PhysicsStore snapshot count when authoritative PhysicsStore is @@ -213,87 +90,6 @@ public abstract int forEachBodySnapshotNear(@Nonnull SpaceId spaceId, float radius, @Nonnull Consumer consumer); - /** - * Removes a physics space and destroys its registered bodies. - */ - public abstract void removeSpace(@Nonnull SpaceId spaceId); - - /** - * Removes a physics space and destroys its registered bodies, using the world name for logging. - */ - public abstract void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName); - - /** - * Queues physics-space removal and returns the removed space id. - */ - @Nonnull - public abstract PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, - @Nonnull String worldName); - - /** - * Removes all physics spaces and destroys their registered bodies. - */ - public abstract void clearAllSpaces(@Nonnull String worldName); - - /** - * Queues removal of all physics spaces. - */ - @Nonnull - public abstract PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName); - - /** - * Returns the current settings for a registered physics space. - */ - @Nonnull - public abstract PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId); - - /** - * Returns the current settings for a live PhysicsStore space entity. - * - *

        Prefer this overload when command or gameplay code already resolved the target - * space entity.

        - */ - @Nonnull - public abstract PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef); - - /** - * Applies settings to a registered physics space on the store tick lane. - */ - public abstract void setSpaceSettings(@Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings); - - /** - * Applies settings to a live PhysicsStore space entity. - * - *

        Prefer this overload when command or gameplay code already resolved the target - * space entity.

        - */ - public abstract void setSpaceSettings(@Nonnull Ref spaceRef, - @Nonnull PhysicsSpaceSettings settings); - - /** - * Queues settings replacement for a registered physics space. - */ - @Nonnull - public abstract PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings); - - /** - * Destroys a registered body by durable body UUID. - * - *

        Prefer {@code PhysicsBodies.destroy(...)} for PhysicsStore-aware code. Keep this - * facade for compatibility callers crossing a durable identity boundary.

        - */ - public abstract void destroyBody(@Nonnull UUID bodyUuid); - - /** - * Queues body destruction by durable body UUID. - * - *

        Prefer {@code PhysicsBodies.destroyAsync(...)} for PhysicsStore-aware code.

        - */ - @Nonnull - public abstract PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid); - /** * Returns immutable registration metadata for a body UUID. * From 5efac580eed1fce8e81309b6fb579c5063da03ef Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:35:51 +0200 Subject: [PATCH 419/534] refactor(core): trim physics chunk settings accessors Signed-off-by: Blovien --- .../settings/PhysicsChunkTerrainSettings.java | 47 +++++-------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java index 4d0e9f79..dc1f6514 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java @@ -3,6 +3,8 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import lombok.Getter; +import lombok.Setter; import java.util.Objects; import javax.annotation.Nonnull; @@ -77,13 +79,22 @@ public class PhysicsChunkTerrainSettings { private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + @Setter + @Getter private boolean nativeVoxelCollisionEnabled = DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; + @Getter private int terrainRadius = DEFAULT_TERRAIN_RADIUS; + @Getter private int bodyTerrainRadius = DEFAULT_BODY_TERRAIN_RADIUS; + @Getter private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; + @Getter private float chunkCollisionFriction = DEFAULT_CHUNK_COLLISION_FRICTION; + @Getter private float chunkCollisionRestitution = DEFAULT_CHUNK_COLLISION_RESTITUTION; + @Getter private int chunkCollisionGroup = DEFAULT_CHUNK_COLLISION_GROUP; + @Getter private int chunkCollisionMask = DEFAULT_CHUNK_COLLISION_MASK; public PhysicsChunkTerrainSettings() { @@ -122,18 +133,6 @@ public void setEntityChunkBoundaryMode( "entityChunkBoundaryMode"); } - public boolean isNativeVoxelCollisionEnabled() { - return nativeVoxelCollisionEnabled; - } - - public void setNativeVoxelCollisionEnabled(boolean nativeVoxelCollisionEnabled) { - this.nativeVoxelCollisionEnabled = nativeVoxelCollisionEnabled; - } - - public int getTerrainRadius() { - return terrainRadius; - } - public void setTerrainRadius(int terrainRadius) { this.terrainRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( "PhysicsChunk terrain radius", @@ -141,10 +140,6 @@ public void setTerrainRadius(int terrainRadius) { MAX_TERRAIN_RADIUS); } - public int getBodyTerrainRadius() { - return bodyTerrainRadius; - } - public void setBodyTerrainRadius(int bodyTerrainRadius) { this.bodyTerrainRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( "PhysicsChunk terrain body radius", @@ -152,10 +147,6 @@ public void setBodyTerrainRadius(int bodyTerrainRadius) { MAX_BODY_TERRAIN_RADIUS); } - public int getTerrainTtlTicks() { - return terrainTtlTicks; - } - public void setTerrainTtlTicks(int terrainTtlTicks) { this.terrainTtlTicks = PhysicsChunkSettingsValidation.requirePositiveAtMost( "PhysicsChunk terrain TTL", @@ -163,10 +154,6 @@ public void setTerrainTtlTicks(int terrainTtlTicks) { MAX_TERRAIN_TTL_TICKS); } - public float getChunkCollisionFriction() { - return chunkCollisionFriction; - } - public void setChunkCollisionFriction(float chunkCollisionFriction) { this.chunkCollisionFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( "Chunk collision friction", @@ -174,10 +161,6 @@ public void setChunkCollisionFriction(float chunkCollisionFriction) { 0.0f); } - public float getChunkCollisionRestitution() { - return chunkCollisionRestitution; - } - public void setChunkCollisionRestitution(float chunkCollisionRestitution) { this.chunkCollisionRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( "Chunk collision restitution", @@ -198,14 +181,6 @@ public void setChunkCollisionMaterial(float friction, float restitution) { this.chunkCollisionRestitution = validatedRestitution; } - public int getChunkCollisionGroup() { - return chunkCollisionGroup; - } - - public int getChunkCollisionMask() { - return chunkCollisionMask; - } - public void setChunkCollisionFilter(int collisionGroup, int collisionMask) { this.chunkCollisionGroup = collisionGroup; this.chunkCollisionMask = collisionMask; From 54b22222ebd9b1a27b07230e521667c61e84473c Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:46:29 +0200 Subject: [PATCH 420/534] refactor(core): move body reads off world resource Signed-off-by: Blovien --- .../crucible/ImpulseApiCrucibleTests.java | 11 +- .../PhysicsWorldRuntimeResource.java | 128 ++------------- .../systems/debug/PhysicsDebugSystem.java | 14 +- .../PhysicsProjectionCleanupSystem.java | 22 ++- .../resources/PhysicsWorldResource.java | 148 +----------------- 5 files changed, 44 insertions(+), 279 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 2350a2f3..18bc4941 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; @@ -28,7 +29,6 @@ import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import java.util.UUID; @@ -234,9 +234,9 @@ private static CompletionStage populatedBodyCleanup( state.world(), "check Crucible body cleanup", _ -> { - PhysicsWorldResource resource = physicsResource(state.world()); boolean spaceEmpty = bodyCount == 0; - boolean noRegistrations = resource.getBodyRegistrationViews().isEmpty(); + boolean noRegistrations = + PhysicsBodies.registrationViews(state.store()).isEmpty(); boolean removedSpace = true; if (checkSpaceRemoval || spaceEmpty) { PhysicsStoreSpaceMutations.removeEmptySpace( @@ -404,11 +404,6 @@ private static PhysicsSpaceSettings populatedSettings() { return settings; } - private static PhysicsWorldResource physicsResource(@Nonnull World world) { - Store store = world.getEntityStore().getStore(); - return store.getResource(PhysicsWorldResource.getResourceType()); - } - private static Store physicsStore(@Nonnull World world) { return PhysicsStoreCrucibleSupport.physicsStore(world); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 44db0a30..92e6d0d4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -33,7 +33,6 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; @@ -562,7 +561,6 @@ public int getSpaceCount() { return spaceRuntime.getSpaceCount(); } - @Override public int refreshBodySnapshots() { if (isAuthoritativePhysicsStoreActive()) { return authoritativePhysicsStore("refresh copied physics body snapshots") @@ -582,7 +580,6 @@ public int refreshBodySnapshots() { } @Nonnull - @Override public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { Objects.requireNonNull(bodyUuid, "bodyUuid"); if (isAuthoritativePhysicsStoreActive()) { @@ -637,6 +634,16 @@ public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegist return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; } + public boolean hasPublishedBodyRegistration(@Nonnull UUID bodyUuid) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + if (hasAttachedAuthoritativePhysicsStore()) { + return authoritativePhysicsStore("check copied physics body registration") + .getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodyRegistrationView(bodyUuid) != null; + } + return bodyRegistry.getPublishedRegistrationView(bodyUuid) != null; + } + @Nonnull private dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull UUID bodyUuid) { dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); @@ -1017,7 +1024,6 @@ private int applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame f return lifecycleState.applyPublishedSnapshotFrame(frame, bodyRegistry, 0L); } - @Override public int getBodySnapshotCount() { if (isAuthoritativePhysicsStoreActive()) { return authoritativePhysicsStore("count copied physics body snapshots") @@ -1029,7 +1035,6 @@ public int getBodySnapshotCount() { return lifecycleState.bodySnapshotCount(); } - @Override public int getBodySnapshotCount(@Nonnull SpaceId spaceId) { if (isAuthoritativePhysicsStoreActive()) { return countAuthoritativeBodySnapshots( @@ -1039,7 +1044,6 @@ public int getBodySnapshotCount(@Nonnull SpaceId spaceId) { return lifecycleState.bodySnapshotCount(spaceId); } - @Override public int getBodySnapshotCellCount() { if (isAuthoritativePhysicsStoreActive()) { return 0; @@ -1115,7 +1119,6 @@ private void restoreCollisionLodFiltersDirect() { } } - @Override public void forEachBodySnapshot(@Nonnull SpaceId spaceId, @Nonnull Consumer consumer) { if (isAuthoritativePhysicsStoreActive()) { @@ -1140,7 +1143,6 @@ public void forEachIndexedBodySnapshot(@Nonnull SpaceId spaceId, lifecycleState.forEachIndexedBodySnapshot(spaceId, visitor); } - @Override public int forEachBodySnapshotNear(@Nonnull SpaceId spaceId, @Nonnull Vector3f center, float radius, @@ -1473,116 +1475,6 @@ private void destroyBodyDirect(@Nonnull UUID bodyUuid, boolean removeFromSpace) bodyRuntime.destroyBody(bodyUuid, removeFromSpace); } - @Nullable - @Override - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics body registration view") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(bodyUuid); - } - return bodyRegistry.getPublishedRegistrationView(bodyUuid); - } - - @Nullable - @Override - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics body registration view") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(bodyRef); - } - return null; - } - - @Nonnull - @Override - public Collection getBodyRegistrationViews() { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics body registration views") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationViews(); - } - return bodyRegistry.getPublishedRegistrationViews(); - } - - @Override - public int getBodyRegistrationCount() { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics body registration count") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationCount(); - } - return bodyRegistry.getPublishedRegistrationCount(); - } - - @Override - public int getBodyRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics body registration count") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationCount(persistenceMode); - } - return bodyRegistry.getPublishedRegistrationCount(persistenceMode); - } - - @Nonnull - @Override - public Collection getBodyRegistrationViews(@Nonnull PhysicsBodyKind kind) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics body registration views") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationViews(kind); - } - return bodyRegistry.getPublishedRegistrationViews(kind); - } - - @Nonnull - @Override - public Collection> getBodyAttachments(@Nonnull Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativeProjectionIndex("read physics body attachments") - .getAttachments(bodyRef); - } - return visualRuntime.getAttachments(bodyRef); - } - - @Nonnull - @Override - public Collection> getBodyAttachments(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("read physics body attachments"); - return bodyRef != null && bodyRef.isValid() - ? projection.getAttachments(bodyRef) - : projection.getAttachments(bodyUuid); - } - return visualRuntime.getAttachments(bodyUuid, bodyRef); - } - - @Override - public boolean hasBodyAttachments(@Nonnull Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativeProjectionIndex("check physics body attachments") - .hasAttachments(bodyRef); - } - return visualRuntime.hasAttachments(bodyRef); - } - - @Override - public boolean hasBodyAttachments(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("check physics body attachments"); - return bodyRef != null && bodyRef.isValid() - ? projection.hasAttachments(bodyRef) - : projection.hasAttachments(bodyUuid); - } - return visualRuntime.hasAttachments(bodyUuid, bodyRef); - } - public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull Ref attachment) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index f5dc7021..31f33440 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -18,11 +18,13 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; @@ -130,6 +132,7 @@ public void tick(float dt, int index, @Nonnull Store store) { if (overlayDue && (debugShapes || debugMotion)) { int renderedBodies = renderEntityBodies(target, store, + physicsStore, resource, viewerPosition, debug.getViewRadius(), @@ -138,6 +141,7 @@ public void tick(float dt, int index, @Nonnull Store store) { debug.getMaxBodies(), overlayLifetime); renderDetachedBodies(target, + store, resource, viewerPosition, debug.getViewRadius(), @@ -215,6 +219,7 @@ private static List resolveSubscribers(@Nonnull World world, private int renderEntityBodies(@Nonnull Collection viewers, @Nonnull Store store, + @Nonnull Store physicsStore, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d viewerPosition, double viewRadius, @@ -227,8 +232,10 @@ private int renderEntityBodies(@Nonnull Collection viewers, return 0; } double maxDistanceSquared = viewRadius * viewRadius; - for (PhysicsBodyRegistrationView registration : resource.getBodyRegistrationViews(PhysicsBodyKind.BODY)) { - Collection> attachments = resource.getBodyAttachments(registration.bodyUuid(), + for (PhysicsBodyRegistrationView registration : PhysicsBodies.registrationViews(physicsStore, + PhysicsBodyKind.BODY)) { + Collection> attachments = PhysicsEntityAttachments.attachments(store, + registration.bodyUuid(), null); if (attachments.isEmpty()) { continue; @@ -279,6 +286,7 @@ private int renderEntityBodies(@Nonnull Collection viewers, } private static int renderDetachedBodies(@Nonnull Collection viewers, + @Nonnull Store store, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d viewerPosition, double viewRadius, @@ -299,7 +307,7 @@ private static int renderDetachedBodies(@Nonnull Collection viewers, } if (kind != PhysicsBodyKind.BODY - || resource.hasBodyAttachments(bodyUuid, null)) { + || PhysicsEntityAttachments.hasAttachments(store, bodyUuid, null)) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java index edb7265b..e84cb1d7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java @@ -15,11 +15,14 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Cleans EntityStore projections whose authoritative PhysicsStore row is gone. @@ -60,11 +63,13 @@ private static void clearDestroyedBodyAttachments(@Nonnull Store st ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); + Store physicsStore = PhysicsThreading.storeOrNull( + store.getExternalData().getWorld()); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, commandBuffer) -> { BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); - if (attachment == null || !hasMissingBody(attachment, resource)) { + if (attachment == null || !hasMissingBody(resource, physicsStore, attachment)) { return; } GeneratedProxyLifecycle.clearMissingAttachment(archetypeChunk.getReferenceTo(index), @@ -79,14 +84,19 @@ private static boolean hasDestroyedBodyRef(@Nonnull BodyAttachmentComponent atta return bodyRef != null && !bodyRef.isValid(); } - private static boolean hasMissingBody(@Nonnull BodyAttachmentComponent attachment, - @Nonnull PhysicsWorldRuntimeResource resource) { + private static boolean hasMissingBody(@Nonnull PhysicsWorldRuntimeResource resource, + @Nullable Store store, + @Nonnull BodyAttachmentComponent attachment) { if (hasDestroyedBodyRef(attachment)) { return true; } - return attachment.getBodyRef() == null - && attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY - && resource.getBodyRegistrationView(attachment.getBodyUuid()) == null; + if (attachment.getBodyRef() != null + || attachment.getLifecycle() != AttachmentLifecycle.GENERATED_PROXY) { + return false; + } + return store != null + ? PhysicsBodies.registrationView(store, attachment.getBodyUuid()) == null + : !resource.hasPublishedBodyRegistration(attachment.getBodyUuid()); } private static void removeOrphanGeneratedVisualProxyMarkers(@Nonnull Store store) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java index ca193fdc..601ad785 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java @@ -2,162 +2,22 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import java.util.Collection; -import java.util.UUID; -import java.util.function.Consumer; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; /** - * Public legacy read facade for copied physics snapshots, registrations, and attachments. + * Public resource type for the EntityStore-side physics runtime. * - *

        The concrete Impulse runtime lives in the internal package. New authoring code should mutate - * PhysicsStore entities and resources through {@code core.plugin.physicsstore} helpers and direct - * {@code Store} ECS operations.

        - * - *

        No physics space is created implicitly. Consumers choose which explicit {@link SpaceId} to - * target for each operation.

        - * - *

        This facade does not directly return live backend spaces or bodies. Gameplay code should use - * PhysicsStore entities for authoring, copied snapshots for body state, and explicit PhysicsStore - * diagnostics/raycast helpers for store tick lane backend reads.

        + *

        The concrete Impulse runtime lives in the internal package. Plugin code should use + * {@code world.getPhysicsStore().getStore()} and {@code core.plugin.physicsstore} helpers for + * PhysicsStore ECS reads and writes.

        */ public abstract class PhysicsWorldResource implements Resource { protected PhysicsWorldResource() { } - /** - * Captures and publishes body snapshots from the live backend state in the legacy runtime, or - * returns the latest copied PhysicsStore snapshot count when authoritative PhysicsStore is - * active. - * - * @return number of copied body snapshots - */ - public abstract int refreshBodySnapshots(); - - /** - * Returns the latest published snapshot for a body. - * - *

        The legacy runtime may capture a copied live snapshot from live backend state when the body - * is registered but missing from the published frame. Authoritative PhysicsStore mode reads - * only the copied {@code PhysicsSnapshotResource} frame and does not synchronously touch the - * live backend.

        - */ - @Nonnull - public abstract PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid); - - /** - * Returns the number of body snapshots in the latest published frame. - */ - public abstract int getBodySnapshotCount(); - - /** - * Returns the number of body snapshots in the latest published frame for one space. - */ - public abstract int getBodySnapshotCount(@Nonnull SpaceId spaceId); - - /** - * Returns the number of occupied legacy snapshot broad-phase cells, or {@code 0} for the flat - * authoritative PhysicsStore snapshot frame. - */ - public abstract int getBodySnapshotCellCount(); - - /** - * Iterates published body snapshots for one space. - */ - public abstract void forEachBodySnapshot(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer); - - /** - * Iterates published body snapshots near a point. - * - * @return number of matching snapshot entries - */ - public abstract int forEachBodySnapshotNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull Consumer consumer); - - /** - * Returns immutable registration metadata for a body UUID. - * - *

        Prefer this overload when the caller is crossing a durable identity boundary.

        - */ - @Nullable - public abstract PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid); - - /** - * Returns immutable registration metadata for a live PhysicsStore body ref. - */ - @Nullable - public abstract PhysicsBodyRegistrationView getBodyRegistrationView( - @Nonnull Ref bodyRef); - - /** - * Returns immutable registration metadata for every registered body. - */ - @Nonnull - public abstract Collection getBodyRegistrationViews(); - - /** - * Returns the number of registered bodies. - */ - public abstract int getBodyRegistrationCount(); - - /** - * Returns the number of registered bodies with a persistence mode. - */ - public abstract int getBodyRegistrationCount( - @Nonnull PhysicsBodyPersistenceMode persistenceMode); - - /** - * Returns immutable registration metadata for bodies of a kind. - */ - @Nonnull - public abstract Collection getBodyRegistrationViews( - @Nonnull PhysicsBodyKind kind); - - /** - * Returns ECS attachments associated with a durable body UUID and optional live body ref. - */ - @Nonnull - public abstract Collection> getBodyAttachments(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef); - - /** - * Returns ECS attachments associated with a live PhysicsStore body ref. - * - *

        Prefer this overload when a caller already has a body entity ref, such as from a PhysicsStore - * raycast or copied registration.

        - */ - @Nonnull - public abstract Collection> getBodyAttachments( - @Nonnull Ref bodyRef); - - /** - * Returns whether a durable body UUID and optional live body ref have one or more ECS attachments. - */ - public abstract boolean hasBodyAttachments(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef); - - /** - * Returns whether a live PhysicsStore body ref has one or more ECS attachments without - * materializing the attachment collection. - */ - public abstract boolean hasBodyAttachments(@Nonnull Ref bodyRef); - public static ResourceType getResourceType() { return PhysicsEntityTypes.physicsWorldResourceType(); } From 2e4f163d95692253419eb14b86787b1a03e7d4db Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 07:56:01 +0200 Subject: [PATCH 421/534] refactor(core): reuse space material for chunk collision Signed-off-by: Blovien --- .../physicschunk/ChunkCollisionPayload.java | 4 -- .../PhysicsStoreChunkCollisionMutations.java | 4 -- .../ChunkCollisionMutationDrainSystem.java | 58 ++++++++++++------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java index 066d85c6..4091203e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionPayload.java @@ -15,10 +15,6 @@ public record ChunkCollisionPayload(float voxelSizeX, @Nonnull List mergedFullCubeBoxes, @Nonnull List detailBoxes, boolean nativeVoxelCollisionEnabled, - float friction, - float restitution, - int collisionGroup, - int collisionMask, @Nonnull List neighbors) { public ChunkCollisionPayload { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java index 1b03aaf8..eec7eb45 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsStoreChunkCollisionMutations.java @@ -75,10 +75,6 @@ private static ChunkCollisionPayload payload(@Nonnull SectionCollisionGeometry g boxes(geometry.mergedFullCubeBoxes()), boxes(geometry.detailBoxes()), buildOptions.nativeVoxelCollisionEnabled(), - buildOptions.friction(), - buildOptions.restitution(), - buildOptions.collisionGroup(), - buildOptions.collisionMask(), neighbors); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 8d5a7838..7d817011 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -12,6 +12,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; @@ -39,6 +40,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -151,17 +153,20 @@ private static void applyUpsert(@Nonnull Store store, boolean nativeVoxel = payload.nativeVoxelCollisionEnabled() && payload.hasFullCubeVoxels() && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); + MaterialComponent material = material(store, spaceRef); + CollisionFilterComponent filter = filter(store, spaceRef); removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); if (nativeVoxel) { chunkCollisionPayloads.put(mutation.payloadResourceKey(), voxelPayload(payload)); - addNativeVoxelBody(store, identity, spaceRef, mutation, payload); + addNativeVoxelBody(store, identity, spaceRef, mutation, material, filter); } else { addBoxBodies(store, identity, spaceRef, mutation, - payload, + material, + filter, payload.mergedFullCubeBoxes(), PartKind.BOX); } @@ -169,7 +174,8 @@ private static void applyUpsert(@Nonnull Store store, identity, spaceRef, mutation, - payload, + material, + filter, payload.detailBoxes(), PartKind.DETAIL_BOX); } @@ -183,10 +189,6 @@ private static ChunkCollisionPayload voxelPayload(@Nonnull ChunkCollisionPayload List.of(), List.of(), true, - 0.0f, - 0.0f, - 0, - 0, payload.neighbors()); } @@ -194,7 +196,8 @@ private static void addNativeVoxelBody(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @Nonnull ChunkCollisionMutation mutation, - @Nonnull ChunkCollisionPayload payload) { + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter) { TargetComponent target = new TargetComponent(); target.setPosition(new Vector3f(mutation.chunkX() << ChunkUtil.BITS, mutation.sectionY() << ChunkUtil.BITS, @@ -209,12 +212,12 @@ private static void addNativeVoxelBody(@Nonnull Store store, 0.0f, 0.0f, 0.0f, - 0.0f, - PhysicsAxis.Y, - 0.0f, - mutation.payloadResourceKey()), - material(payload), - filter(payload), + 0.0f, + PhysicsAxis.Y, + 0.0f, + mutation.payloadResourceKey()), + material, + filter, mutation.payloadResourceKey(), PartKind.NATIVE_VOXELS, 0); @@ -224,7 +227,8 @@ private static void addBoxBodies(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @Nonnull ChunkCollisionMutation mutation, - @Nonnull ChunkCollisionPayload payload, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter, @Nonnull List boxes, @Nonnull PartKind partKind) { for (int index = 0; index < boxes.size(); index++) { @@ -250,8 +254,8 @@ private static void addBoxBodies(@Nonnull Store store, PhysicsAxis.Y, 0.0f, ""), - material(payload), - filter(payload), + material, + filter, "", partKind, index); @@ -301,13 +305,25 @@ private static void addChunkCollisionBody(@Nonnull Store store, } @Nonnull - private static MaterialComponent material(@Nonnull ChunkCollisionPayload payload) { - return new MaterialComponent(payload.friction(), payload.restitution()); + private static MaterialComponent material(@Nonnull Store store, + @Nonnull Ref spaceRef) { + MaterialComponent material = + store.getComponent(spaceRef, MaterialComponent.getComponentType()); + return material != null + ? material.clone() + : new MaterialComponent(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION); } @Nonnull - private static CollisionFilterComponent filter(@Nonnull ChunkCollisionPayload payload) { - return new CollisionFilterComponent(payload.collisionGroup(), payload.collisionMask()); + private static CollisionFilterComponent filter(@Nonnull Store store, + @Nonnull Ref spaceRef) { + CollisionFilterComponent filter = + store.getComponent(spaceRef, CollisionFilterComponent.getComponentType()); + return filter != null + ? filter.clone() + : new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL); } private static void removeGeneratedRows(@Nonnull Store store, From ed3630c422ccb95f7a0c24f64acdd9887006623b Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 08:05:35 +0200 Subject: [PATCH 422/534] refactor(core): remove terrain material settings Signed-off-by: Blovien --- .../PhysicsChunkBuildOptions.java | 17 +++-- .../PhysicsChunkCollisionDefaults.java | 17 +++++ .../persistence/PersistentSpaceDto.java | 42 ++++++------ .../PhysicsStoreSpaceMutations.java | 53 --------------- .../PhysicsWorldRuntimeResource.java | 11 ---- .../ChunkCollisionMutationDrainSystem.java | 11 ++-- .../systems/PersistenceCaptureSystem.java | 11 ++-- .../PhysicsChunkSettingsIndexSystem.java | 11 ++-- .../physicschunk/PhysicsChunkTerrain.java | 11 ++-- .../settings/PhysicsChunkTerrainSettings.java | 65 ------------------- .../plugin/physicsstore/PhysicsSpaces.java | 14 ---- 11 files changed, 64 insertions(+), 199 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionDefaults.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index d5c8d505..157466c0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -31,19 +30,19 @@ public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisio public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrainSettings settings) { return new PhysicsChunkBuildOptions( ChunkCollisionMode.fromNativeVoxelCollisionEnabled(settings.isNativeVoxelCollisionEnabled()), - settings.getChunkCollisionFriction(), - settings.getChunkCollisionRestitution(), - settings.getChunkCollisionGroup(), - settings.getChunkCollisionMask()); + PhysicsChunkCollisionDefaults.FRICTION, + PhysicsChunkCollisionDefaults.RESTITUTION, + PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK); } @Nonnull public static PhysicsChunkBuildOptions fromNativeVoxelCollisionEnabled(boolean enabled) { return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelCollisionEnabled(enabled), - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_GROUP, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_MASK); + PhysicsChunkCollisionDefaults.FRICTION, + PhysicsChunkCollisionDefaults.RESTITUTION, + PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK); } public boolean nativeVoxelCollisionEnabled() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionDefaults.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionDefaults.java new file mode 100644 index 00000000..85275cd1 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionDefaults.java @@ -0,0 +1,17 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; + +/** + * Internal defaults for generated PhysicsChunk collision bodies. + */ +public final class PhysicsChunkCollisionDefaults { + + public static final float FRICTION = 0.75f; + public static final float RESTITUTION = 0.0f; + public static final int COLLISION_GROUP = PhysicsCollisionFilters.TERRAIN; + public static final int COLLISION_MASK = PhysicsCollisionFilters.ALL; + + private PhysicsChunkCollisionDefaults() { + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index bc632fda..752b7f33 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -87,13 +87,13 @@ public final class PersistentSpaceDto { .append(new KeyedCodec<>("ChunkCollisionFriction", Codec.FLOAT, false), (dto, value) -> dto.chunkCollisionFriction = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + : PhysicsChunkCollisionDefaults.FRICTION, PersistentSpaceDto::getChunkCollisionFriction) .add() .append(new KeyedCodec<>("ChunkCollisionRestitution", Codec.FLOAT, false), (dto, value) -> dto.chunkCollisionRestitution = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, + : PhysicsChunkCollisionDefaults.RESTITUTION, PersistentSpaceDto::getChunkCollisionRestitution) .add() .append(new KeyedCodec<>("ChunkCollisionFilter", @@ -161,9 +161,9 @@ public final class PersistentSpaceDto { PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; private int terrainTtlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; - private float chunkCollisionFriction = PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION; + private float chunkCollisionFriction = PhysicsChunkCollisionDefaults.FRICTION; private float chunkCollisionRestitution = - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION; + PhysicsChunkCollisionDefaults.RESTITUTION; @Nonnull private CollisionFilterComponent chunkCollisionFilter = defaultChunkCollisionFilter(); @Nonnull @@ -194,10 +194,10 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL, + PhysicsChunkCollisionDefaults.FRICTION, + PhysicsChunkCollisionDefaults.RESTITUTION, + PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK, new SolverSettingsComponent(), new VisualSyncSettingsComponent(), new VisualMaterializationSettingsComponent(), @@ -226,8 +226,8 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, terrainTtlTicks, chunkCollisionFriction, chunkCollisionRestitution, - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL, + PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK, new SolverSettingsComponent(), new VisualSyncSettingsComponent(), new VisualMaterializationSettingsComponent(), @@ -262,8 +262,8 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, terrainTtlTicks, chunkCollisionFriction, chunkCollisionRestitution, - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL, + PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK, solverSettings, visualSyncSettings, visualMaterializationSettings, @@ -388,9 +388,9 @@ public MaterialComponent getChunkCollisionMaterial() { public boolean isDefaultChunkCollisionMaterial() { return Float.compare(chunkCollisionFriction, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION) == 0 + PhysicsChunkCollisionDefaults.FRICTION) == 0 && Float.compare(chunkCollisionRestitution, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION) == 0; + PhysicsChunkCollisionDefaults.RESTITUTION) == 0; } @Nonnull @@ -399,8 +399,8 @@ public CollisionFilterComponent getChunkCollisionFilter() { } public boolean isDefaultChunkCollisionFilter() { - return getChunkCollisionGroup() == PhysicsCollisionFilters.TERRAIN - && getChunkCollisionMask() == PhysicsCollisionFilters.ALL; + return getChunkCollisionGroup() == PhysicsChunkCollisionDefaults.COLLISION_GROUP + && getChunkCollisionMask() == PhysicsChunkCollisionDefaults.COLLISION_MASK; } @Nonnull @@ -432,10 +432,6 @@ public ExtensionSettingsComponent getExtensionSettings() { public PhysicsSpaceSettings toSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); getChunkCollisionSettings().copyTo(settings); - settings.getPhysicsChunkTerrainSettings() - .setChunkCollisionMaterial(chunkCollisionFriction, chunkCollisionRestitution); - settings.getPhysicsChunkTerrainSettings() - .setChunkCollisionFilter(getChunkCollisionGroup(), getChunkCollisionMask()); solverSettings.copyTo(settings); visualSyncSettings.copyTo(settings); visualMaterializationSettings.copyTo(settings); @@ -468,7 +464,7 @@ public PersistentSpaceDto copy() { @Nonnull private static CollisionFilterComponent defaultChunkCollisionFilter() { - return new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); + return new CollisionFilterComponent(PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 74451449..59353cf1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -15,9 +15,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; @@ -25,7 +23,6 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -156,16 +153,6 @@ private static void addSpaceSettingsComponents(@Nonnull Holder hol ChunkCollisionSettingsComponent.getComponentType(), chunkCollision, chunkCollision.isDefault()); - MaterialComponent material = chunkMaterial(settings.getPhysicsChunkTerrainSettings()); - addIfNonDefault(holder, - MaterialComponent.getComponentType(), - material, - isDefaultChunkMaterial(material)); - CollisionFilterComponent filter = chunkFilter(settings.getPhysicsChunkTerrainSettings()); - addIfNonDefault(holder, - CollisionFilterComponent.getComponentType(), - filter, - isDefaultChunkFilter(filter)); SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); addIfNonDefault(holder, SolverSettingsComponent.getComponentType(), @@ -218,18 +205,6 @@ private static void putSpaceSettingsComponents(@Nonnull Store stor ChunkCollisionSettingsComponent.getComponentType(), chunkCollision, chunkCollision.isDefault()); - MaterialComponent material = chunkMaterial(settings.getPhysicsChunkTerrainSettings()); - putOrRemoveDefault(store, - ref, - MaterialComponent.getComponentType(), - material, - isDefaultChunkMaterial(material)); - CollisionFilterComponent filter = chunkFilter(settings.getPhysicsChunkTerrainSettings()); - putOrRemoveDefault(store, - ref, - CollisionFilterComponent.getComponentType(), - filter, - isDefaultChunkFilter(filter)); SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); putOrRemoveDefault(store, ref, @@ -267,34 +242,6 @@ private static void putSpaceSettingsComponents(@Nonnull Store stor extension.isDefault()); } - @Nonnull - private static MaterialComponent chunkMaterial( - @Nonnull PhysicsChunkTerrainSettings settings) { - return new MaterialComponent(settings.getChunkCollisionFriction(), - settings.getChunkCollisionRestitution()); - } - - private static boolean isDefaultChunkMaterial(@Nonnull MaterialComponent material) { - return Float.compare(material.getFriction(), - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION) == 0 - && Float.compare(material.getRestitution(), - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION) == 0; - } - - @Nonnull - private static CollisionFilterComponent chunkFilter( - @Nonnull PhysicsChunkTerrainSettings settings) { - return new CollisionFilterComponent(settings.getChunkCollisionGroup(), - settings.getChunkCollisionMask()); - } - - private static boolean isDefaultChunkFilter(@Nonnull CollisionFilterComponent filter) { - return filter.getCollisionGroup() - == PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_GROUP - && filter.getCollisionMask() - == PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_MASK; - } - private static > void putOrRemoveDefault( @Nonnull Store store, @Nonnull Ref ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 92e6d0d4..9fe9d4cd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -267,17 +267,6 @@ private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( if (chunkCollisionSettings != null) { chunkCollisionSettings.copyTo(settings); } - MaterialComponent material = store.getComponent(ref, MaterialComponent.getComponentType()); - if (material != null) { - settings.getPhysicsChunkTerrainSettings() - .setChunkCollisionMaterial(material.getFriction(), material.getRestitution()); - } - CollisionFilterComponent filter = store.getComponent(ref, - CollisionFilterComponent.getComponentType()); - if (filter != null) { - settings.getPhysicsChunkTerrainSettings() - .setChunkCollisionFilter(filter.getCollisionGroup(), filter.getCollisionMask()); - } if (solverSettings != null) { solverSettings.copyTo(settings); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 7d817011..ef827177 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -12,7 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; @@ -28,6 +27,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -40,7 +40,6 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -311,8 +310,8 @@ private static MaterialComponent material(@Nonnull Store store, store.getComponent(spaceRef, MaterialComponent.getComponentType()); return material != null ? material.clone() - : new MaterialComponent(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION); + : new MaterialComponent(PhysicsChunkCollisionDefaults.FRICTION, + PhysicsChunkCollisionDefaults.RESTITUTION); } @Nonnull @@ -322,8 +321,8 @@ private static CollisionFilterComponent filter(@Nonnull Store stor store.getComponent(spaceRef, CollisionFilterComponent.getComponentType()); return filter != null ? filter.clone() - : new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); + : new CollisionFilterComponent(PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK); } private static void removeGeneratedRows(@Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index 9d5995c2..f184d2bb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -10,7 +10,7 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyRuntimeStateDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; @@ -37,7 +37,6 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -197,12 +196,12 @@ private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { : new ChunkCollisionSettingsComponent(); MaterialComponent material = row.material() != null ? row.material() - : new MaterialComponent(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION); + : new MaterialComponent(PhysicsChunkCollisionDefaults.FRICTION, + PhysicsChunkCollisionDefaults.RESTITUTION); CollisionFilterComponent filter = row.filter() != null ? row.filter() - : new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL); + : new CollisionFilterComponent(PhysicsChunkCollisionDefaults.COLLISION_GROUP, + PhysicsChunkCollisionDefaults.COLLISION_MASK); return new PersistentSpaceDto(row.uuid(), row.space().getBackendIdValue(), row.space().getGravity(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index 387fce1a..bad35b91 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -12,12 +12,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Set; @@ -76,16 +75,16 @@ private static void collectChunk( settings.getTtlTicks(), material != null ? material.getFriction() - : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + : PhysicsChunkCollisionDefaults.FRICTION, material != null ? material.getRestitution() - : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, + : PhysicsChunkCollisionDefaults.RESTITUTION, filter != null ? filter.getCollisionGroup() - : PhysicsCollisionFilters.TERRAIN, + : PhysicsChunkCollisionDefaults.COLLISION_GROUP, filter != null ? filter.getCollisionMask() - : PhysicsCollisionFilters.ALL)); + : PhysicsChunkCollisionDefaults.COLLISION_MASK)); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index 0a699bee..b4f64322 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -6,6 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; @@ -13,11 +14,9 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Objects; @@ -182,16 +181,16 @@ private static PhysicsChunkSpaceSettings requireSettings( settings.getTtlTicks(), material != null ? material.getFriction() - : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, + : PhysicsChunkCollisionDefaults.FRICTION, material != null ? material.getRestitution() - : PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, + : PhysicsChunkCollisionDefaults.RESTITUTION, filter != null ? filter.getCollisionGroup() - : PhysicsCollisionFilters.TERRAIN, + : PhysicsChunkCollisionDefaults.COLLISION_GROUP, filter != null ? filter.getCollisionMask() - : PhysicsCollisionFilters.ALL); + : PhysicsChunkCollisionDefaults.COLLISION_MASK); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java index dc1f6514..a4f935e9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import lombok.Getter; @@ -55,26 +54,6 @@ public class PhysicsChunkTerrainSettings { */ public static final boolean DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED = false; - /** - * Default friction applied to generated chunk collision bodies. - */ - public static final float DEFAULT_CHUNK_COLLISION_FRICTION = 0.75f; - - /** - * Default restitution applied to generated chunk collision bodies. - */ - public static final float DEFAULT_CHUNK_COLLISION_RESTITUTION = 0.0f; - - /** - * Default collision group applied to generated chunk collision bodies. - */ - public static final int DEFAULT_CHUNK_COLLISION_GROUP = PhysicsCollisionFilters.TERRAIN; - - /** - * Default collision mask applied to generated chunk collision bodies. - */ - public static final int DEFAULT_CHUNK_COLLISION_MASK = PhysicsCollisionFilters.ALL; - @Nonnull private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull @@ -88,14 +67,6 @@ public class PhysicsChunkTerrainSettings { private int bodyTerrainRadius = DEFAULT_BODY_TERRAIN_RADIUS; @Getter private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; - @Getter - private float chunkCollisionFriction = DEFAULT_CHUNK_COLLISION_FRICTION; - @Getter - private float chunkCollisionRestitution = DEFAULT_CHUNK_COLLISION_RESTITUTION; - @Getter - private int chunkCollisionGroup = DEFAULT_CHUNK_COLLISION_GROUP; - @Getter - private int chunkCollisionMask = DEFAULT_CHUNK_COLLISION_MASK; public PhysicsChunkTerrainSettings() { } @@ -107,10 +78,6 @@ public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings terrainRadius = settings.terrainRadius; bodyTerrainRadius = settings.bodyTerrainRadius; terrainTtlTicks = settings.terrainTtlTicks; - chunkCollisionFriction = settings.chunkCollisionFriction; - chunkCollisionRestitution = settings.chunkCollisionRestitution; - chunkCollisionGroup = settings.chunkCollisionGroup; - chunkCollisionMask = settings.chunkCollisionMask; } @Nonnull @@ -154,36 +121,4 @@ public void setTerrainTtlTicks(int terrainTtlTicks) { MAX_TERRAIN_TTL_TICKS); } - public void setChunkCollisionFriction(float chunkCollisionFriction) { - this.chunkCollisionFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Chunk collision friction", - chunkCollisionFriction, - 0.0f); - } - - public void setChunkCollisionRestitution(float chunkCollisionRestitution) { - this.chunkCollisionRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Chunk collision restitution", - chunkCollisionRestitution, - 0.0f); - } - - public void setChunkCollisionMaterial(float friction, float restitution) { - float validatedFriction = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Chunk collision friction", - friction, - 0.0f); - float validatedRestitution = PhysicsChunkSettingsValidation.requireFiniteAtLeast( - "Chunk collision restitution", - restitution, - 0.0f); - this.chunkCollisionFriction = validatedFriction; - this.chunkCollisionRestitution = validatedRestitution; - } - - public void setChunkCollisionFilter(int collisionGroup, int collisionMask) { - this.chunkCollisionGroup = collisionGroup; - this.chunkCollisionMask = collisionMask; - } - } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 7a985371..625c66a7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -12,9 +12,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; @@ -144,18 +142,6 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, if (chunkCollisionSettings != null) { chunkCollisionSettings.copyTo(settings); } - MaterialComponent material = checkedStore.getComponent(checkedRef, - MaterialComponent.getComponentType()); - if (material != null) { - settings.getPhysicsChunkTerrainSettings() - .setChunkCollisionMaterial(material.getFriction(), material.getRestitution()); - } - CollisionFilterComponent filter = checkedStore.getComponent(checkedRef, - CollisionFilterComponent.getComponentType()); - if (filter != null) { - settings.getPhysicsChunkTerrainSettings() - .setChunkCollisionFilter(filter.getCollisionGroup(), filter.getCollisionMask()); - } SolverSettingsComponent solverSettings = checkedStore.getComponent(checkedRef, SolverSettingsComponent.getComponentType()); if (solverSettings != null) { From 88a0880b0614c98f8cf15688228e2fa33c316f4e Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 08:12:28 +0200 Subject: [PATCH 423/534] test(core): update chunk collision component contract Signed-off-by: Blovien --- .../PersistentSpaceDtoSettingsTest.java | 34 +++--- ...ChunkCollisionMutationDrainSystemTest.java | 104 ++++++++++-------- ...hunkCollisionVoxelStitchingSystemTest.java | 4 - .../settings/PhysicsSpaceSettingsTest.java | 30 ----- 4 files changed, 77 insertions(+), 95 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index ef5f21a4..50ed73bd 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.codec.ExtraInfo; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; @@ -23,7 +24,6 @@ class PersistentSpaceDtoSettingsTest { void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); original.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(true); - original.getPhysicsChunkTerrainSettings().setChunkCollisionMaterial(0.85f, 0.2f); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); @@ -38,8 +38,8 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { terrain.getTerrainRadius(), terrain.getBodyTerrainRadius(), terrain.getTerrainTtlTicks(), - terrain.getChunkCollisionFriction(), - terrain.getChunkCollisionRestitution(), + 0.85f, + 0.2f, new SolverSettingsComponent(original.getSolverSettings()), new VisualSyncSettingsComponent(original.getVisualSyncSettings()), new VisualMaterializationSettingsComponent(original.getVisualMaterializationSettings()), @@ -56,17 +56,21 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertTrue(encoded.containsKey("ChunkCollisionFriction")); assertTrue(encoded.containsKey("ChunkCollisionRestitution")); assertTrue(encoded.containsKey("VisualMaterializationSettings")); - PhysicsSpaceSettings decoded = Objects.requireNonNull( - PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())).toSettings(); + PersistentSpaceDto decodedState = Objects.requireNonNull( + PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())); + assertEquals(0.85f, decodedState.getChunkCollisionFriction(), 0.0001f); + assertEquals(0.2f, decodedState.getChunkCollisionRestitution(), 0.0001f); + + PhysicsSpaceSettings decoded = decodedState.toSettings(); assertTrue(decoded.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); - assertEquals(0.85f, decoded.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); - assertEquals(0.2f, decoded.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); assertDetachedVisualCadence(decoded, 7, 9, 11); - PhysicsSpaceSettings copied = state.copy().toSettings(); + PersistentSpaceDto copiedState = state.copy(); + assertEquals(0.85f, copiedState.getChunkCollisionFriction(), 0.0001f); + assertEquals(0.2f, copiedState.getChunkCollisionRestitution(), 0.0001f); + + PhysicsSpaceSettings copied = copiedState.toSettings(); assertTrue(copied.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); - assertEquals(0.85f, copied.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); - assertEquals(0.2f, copied.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); assertDetachedVisualCadence(copied, 7, 9, 11); } @@ -83,8 +87,8 @@ void roundTripPreservesChunkCollisionFilter() { PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, - PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, + PhysicsChunkCollisionDefaults.FRICTION, + PhysicsChunkCollisionDefaults.RESTITUTION, 0x40, 0x03, new SolverSettingsComponent(), @@ -103,10 +107,8 @@ void roundTripPreservesChunkCollisionFilter() { assertEquals(0x40, state.copy().getChunkCollisionGroup()); assertEquals(0x03, state.copy().getChunkCollisionMask()); PhysicsSpaceSettings decodedSettings = decoded.toSettings(); - assertEquals(0x40, - decodedSettings.getPhysicsChunkTerrainSettings().getChunkCollisionGroup()); - assertEquals(0x03, - decodedSettings.getPhysicsChunkTerrainSettings().getChunkCollisionMask()); + assertEquals(terrain.getEntityChunkBoundaryMode(), + decodedSettings.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode()); } private static void assertDetachedVisualCadence(PhysicsSpaceSettings settings, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index cb9b662f..1b7d4b6b 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -18,6 +18,7 @@ import com.hypixel.hytale.server.core.util.thread.TickingThread; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; @@ -25,6 +26,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; @@ -76,6 +78,7 @@ void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { UUID spaceUuid = uuid(1); BackendId backendId = new BackendId("test:chunk-collision-drain"); Ref spaceRef = addBoundSpace(store, spaceUuid, backendId); + putChunkCollisionMaterialAndFilter(store, spaceRef, 0.82f, 0.18f, 0x40, 0x07); String sourceKey = "0:1:2"; String payloadKey = "chunk-collision/0/1/2"; BoxPayload fullCubeBox = new BoxPayload(10.0, 20.0, 30.0, 1.5, 2.5, 3.5); @@ -87,10 +90,6 @@ void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { List.of(fullCubeBox), List.of(detailBox), false, - 0.82f, - 0.18f, - 0x40, - 0x07, List.of()); PhysicsChunkCollisionMutationQueueResource queue = store.getResource( @@ -116,8 +115,7 @@ void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { payloadKey, PartKind.BOX, 0, - fullCubeBox, - payload); + fullCubeBox); assertGeneratedBox(store, spaceUuid, spaceRef, @@ -125,8 +123,7 @@ void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { payloadKey, PartKind.DETAIL_BOX, 0, - detailBox, - payload); + detailBox); } finally { registry.removeStore(store); registry.shutdown(); @@ -147,6 +144,7 @@ void upsertCreatesNativeVoxelRowWhenBackendSupportsVoxelTerrain() { UUID spaceUuid = uuid(11); BackendId backendId = new BackendId("test:chunk-collision-drain-voxel"); Ref spaceRef = addBoundSpace(store, spaceUuid, backendId, true); + putChunkCollisionMaterialAndFilter(store, spaceRef, 0.7f, 0.05f, 0x20, 0x03); String sourceKey = "2:3:4"; String payloadKey = "chunk-collision/2/3/4"; ChunkCollisionPayload payload = new ChunkCollisionPayload(1.0f, @@ -156,10 +154,6 @@ void upsertCreatesNativeVoxelRowWhenBackendSupportsVoxelTerrain() { List.of(), List.of(), true, - 0.7f, - 0.05f, - 0x20, - 0x03, List.of()); PhysicsChunkCollisionMutationQueueResource queue = store.getResource( @@ -182,8 +176,7 @@ void upsertCreatesNativeVoxelRowWhenBackendSupportsVoxelTerrain() { spaceUuid, spaceRef, sourceKey, - payloadKey, - payload); + payloadKey); } finally { registry.removeStore(store); registry.shutdown(); @@ -214,10 +207,6 @@ void destroyingDetailRowDoesNotRemoveNativeVoxelPayloadForSiblingRow() { List.of(), List.of(new BoxPayload(10.0, 20.0, 30.0, 0.25, 0.5, 0.75)), true, - 0.7f, - 0.05f, - 0x20, - 0x03, List.of()); PhysicsChunkCollisionMutationQueueResource queue = store.getResource( @@ -231,7 +220,7 @@ void destroyingDetailRowDoesNotRemoveNativeVoxelPayloadForSiblingRow() { payload)); new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); - assertGeneratedVoxel(store, spaceUuid, spaceRef, sourceKey, payloadKey, payload); + assertGeneratedVoxel(store, spaceUuid, spaceRef, sourceKey, payloadKey); UUID detailUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, PartKind.DETAIL_BOX, @@ -286,10 +275,6 @@ void removeDeletesGeneratedRowsAndPayloadResource() { List.of(new BoxPayload(1.0, 2.0, 3.0, 0.5, 0.5, 0.5)), List.of(new BoxPayload(4.0, 5.0, 6.0, 0.25, 0.25, 0.25)), false, - 0.6f, - 0.1f, - 0x10, - 0x0F, List.of()); PhysicsChunkCollisionMutationQueueResource queue = store.getResource( @@ -424,10 +409,6 @@ void sameDrainRemoveThenUpsertKeepsUpsertIntent() { List.of(updatedBox), List.of(), false, - 0.6f, - 0.1f, - 0x10, - 0x0F, List.of()); queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, sourceKey, 0, 1, 2)); queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, @@ -447,8 +428,7 @@ void sameDrainRemoveThenUpsertKeepsUpsertIntent() { payloadKey, PartKind.BOX, 0, - updatedBox, - updatedPayload); + updatedBox); assertSoftSkipsEmpty(store); } finally { registry.removeStore(store); @@ -500,19 +480,28 @@ private static ChunkCollisionPayload boxPayload(double centerX, List.of(new BoxPayload(centerX, centerY, centerZ, 0.5, 0.5, 0.5)), List.of(), false, - 0.6f, - 0.1f, - 0x10, - 0x0F, List.of()); } + private static void putChunkCollisionMaterialAndFilter(@Nonnull Store store, + @Nonnull Ref spaceRef, + float friction, + float restitution, + int collisionGroup, + int collisionMask) { + store.putComponent(spaceRef, + MaterialComponent.getComponentType(), + new MaterialComponent(friction, restitution)); + store.putComponent(spaceRef, + CollisionFilterComponent.getComponentType(), + new CollisionFilterComponent(collisionGroup, collisionMask)); + } + private static void assertGeneratedVoxel(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull Ref spaceRef, @Nonnull String sourceKey, - @Nonnull String payloadKey, - @Nonnull ChunkCollisionPayload payload) { + @Nonnull String payloadKey) { UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, PartKind.NATIVE_VOXELS, @@ -545,14 +534,12 @@ private static void assertGeneratedVoxel(@Nonnull Store store, MaterialComponent material = store.getComponent(bodyRef, MaterialComponent.getComponentType()); assertNotNull(material); - assertEquals(payload.friction(), material.getFriction(), DELTA); - assertEquals(payload.restitution(), material.getRestitution(), DELTA); + assertMaterialMatchesSpace(store, spaceRef, material); CollisionFilterComponent filter = store.getComponent(bodyRef, CollisionFilterComponent.getComponentType()); assertNotNull(filter); - assertEquals(payload.collisionGroup(), filter.getCollisionGroup()); - assertEquals(payload.collisionMask(), filter.getCollisionMask()); + assertFilterMatchesSpace(store, spaceRef, filter); ChunkCollisionSourceComponent source = store.getComponent(bodyRef, ChunkCollisionSourceComponent.getComponentType()); @@ -570,8 +557,7 @@ private static void assertGeneratedBox(@Nonnull Store store, @Nonnull String payloadKey, @Nonnull PartKind partKind, int partIndex, - @Nonnull BoxPayload box, - @Nonnull ChunkCollisionPayload payload) { + @Nonnull BoxPayload box) { UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, partKind, @@ -616,14 +602,12 @@ private static void assertGeneratedBox(@Nonnull Store store, MaterialComponent material = store.getComponent(bodyRef, MaterialComponent.getComponentType()); assertNotNull(material); - assertEquals(payload.friction(), material.getFriction(), DELTA); - assertEquals(payload.restitution(), material.getRestitution(), DELTA); + assertMaterialMatchesSpace(store, spaceRef, material); CollisionFilterComponent filter = store.getComponent(bodyRef, CollisionFilterComponent.getComponentType()); assertNotNull(filter); - assertEquals(payload.collisionGroup(), filter.getCollisionGroup()); - assertEquals(payload.collisionMask(), filter.getCollisionMask()); + assertFilterMatchesSpace(store, spaceRef, filter); ChunkCollisionSourceComponent source = store.getComponent(bodyRef, ChunkCollisionSourceComponent.getComponentType()); @@ -637,6 +621,36 @@ private static void assertGeneratedBox(@Nonnull Store store, assertEquals(partIndex, source.getPartIndex()); } + private static void assertMaterialMatchesSpace(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull MaterialComponent material) { + MaterialComponent spaceMaterial = store.getComponent(spaceRef, + MaterialComponent.getComponentType()); + float expectedFriction = spaceMaterial != null + ? spaceMaterial.getFriction() + : PhysicsChunkCollisionDefaults.FRICTION; + float expectedRestitution = spaceMaterial != null + ? spaceMaterial.getRestitution() + : PhysicsChunkCollisionDefaults.RESTITUTION; + assertEquals(expectedFriction, material.getFriction(), DELTA); + assertEquals(expectedRestitution, material.getRestitution(), DELTA); + } + + private static void assertFilterMatchesSpace(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull CollisionFilterComponent filter) { + CollisionFilterComponent spaceFilter = store.getComponent(spaceRef, + CollisionFilterComponent.getComponentType()); + int expectedGroup = spaceFilter != null + ? spaceFilter.getCollisionGroup() + : PhysicsCollisionFilters.TERRAIN; + int expectedMask = spaceFilter != null + ? spaceFilter.getCollisionMask() + : PhysicsCollisionFilters.ALL; + assertEquals(expectedGroup, filter.getCollisionGroup()); + assertEquals(expectedMask, filter.getCollisionMask()); + } + private static void assertVectorEquals(float expectedX, float expectedY, float expectedZ, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index 55b5a6a1..675d1475 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -243,10 +243,6 @@ private static ChunkCollisionPayload payloadWithNeighbors( List.of(), List.of(), true, - 0.7f, - 0.05f, - PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL, neighbors); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index 98a89dd0..97f47d6a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -97,12 +97,6 @@ void rejectsNonPositivePhysicsChunkTerrainValues() { + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS, assertThrows(IllegalArgumentException.class, () -> settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(0)).getMessage()); - assertEquals("Chunk collision friction must be finite and >= 0.0", - assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkTerrainSettings().setChunkCollisionFriction(-0.1f)).getMessage()); - assertEquals("Chunk collision restitution must be finite and >= 0.0", - assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkTerrainSettings().setChunkCollisionRestitution(Float.NaN)).getMessage()); assertEquals("Visual full sync radius must be between 1 and " + PhysicsVisualSyncSettings.MAX_VISUAL_FULL_SYNC_RADIUS, assertThrows(IllegalArgumentException.class, @@ -174,16 +168,6 @@ void defaultsFactoryReturnsFreshDefaultSettings() { assertSame(PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, first.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode()); assertFalse(first.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); - assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_FRICTION, - first.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), - 0.0001f); - assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_RESTITUTION, - first.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), - 0.0001f); - assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_GROUP, - first.getPhysicsChunkTerrainSettings().getChunkCollisionGroup()); - assertEquals(PhysicsChunkTerrainSettings.DEFAULT_CHUNK_COLLISION_MASK, - first.getPhysicsChunkTerrainSettings().getChunkCollisionMask()); } @Test @@ -259,8 +243,6 @@ void terrainSettingsCopyConstructorCopiesValues() { canonical.setTerrainRadius(18); canonical.setBodyTerrainRadius(7); canonical.setTerrainTtlTicks(240); - canonical.setChunkCollisionMaterial(0.85f, 0.2f); - canonical.setChunkCollisionFilter(0x40, 0x03); PhysicsChunkTerrainSettings canonicalCopy = new PhysicsChunkTerrainSettings(canonical); @@ -276,15 +258,9 @@ void terrainSettingsCopyConstructorCopiesValues() { assertEquals(30, canonicalCopy.getTerrainRadius()); assertEquals(7, canonicalCopy.getBodyTerrainRadius()); assertEquals(240, canonicalCopy.getTerrainTtlTicks()); - assertEquals(0.85f, canonicalCopy.getChunkCollisionFriction(), 0.0001f); - assertEquals(0.2f, canonicalCopy.getChunkCollisionRestitution(), 0.0001f); - assertEquals(0x40, canonicalCopy.getChunkCollisionGroup()); - assertEquals(0x03, canonicalCopy.getChunkCollisionMask()); assertEquals(18, secondCopy.getTerrainRadius()); assertEquals(7, secondCopy.getBodyTerrainRadius()); assertEquals(240, secondCopy.getTerrainTtlTicks()); - assertEquals(0x40, secondCopy.getChunkCollisionGroup()); - assertEquals(0x03, secondCopy.getChunkCollisionMask()); } @Test @@ -333,8 +309,6 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { original.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(6); original.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(180); original.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(true); - original.getPhysicsChunkTerrainSettings().setChunkCollisionMaterial(0.9f, 0.15f); - original.getPhysicsChunkTerrainSettings().setChunkCollisionFilter(0x40, 0x07); original.getVisualSyncSettings().setVisualMaxSyncRadius(160); original.getVisualSyncSettings().setVisualFullSyncRadius(80); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(2); @@ -367,10 +341,6 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { assertEquals(6, copy.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); assertEquals(180, copy.getPhysicsChunkTerrainSettings().getTerrainTtlTicks()); assertTrue(copy.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); - assertEquals(0.9f, copy.getPhysicsChunkTerrainSettings().getChunkCollisionFriction(), 0.0001f); - assertEquals(0.15f, copy.getPhysicsChunkTerrainSettings().getChunkCollisionRestitution(), 0.0001f); - assertEquals(0x40, copy.getPhysicsChunkTerrainSettings().getChunkCollisionGroup()); - assertEquals(0x07, copy.getPhysicsChunkTerrainSettings().getChunkCollisionMask()); assertEquals(160, copy.getVisualSyncSettings().getVisualMaxSyncRadius()); assertEquals(80, copy.getVisualSyncSettings().getVisualFullSyncRadius()); assertEquals(2, copy.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); From fc5ca4ba8d43b7bf359d388706e80125cb363d11 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 08:29:55 +0200 Subject: [PATCH 424/534] refactor(core): sync chunk collision body surfaces Signed-off-by: Blovien --- .../PhysicsChunkBuildOptions.java | 26 +-- .../PhysicsStoreRegistration.java | 7 + .../PhysicsChunkComponentSyncResource.java | 99 +++++++++ .../PhysicsChunkSettingsIndexResource.java | 12 +- .../resources/PhysicsResourceTypes.java | 3 + .../ChunkCollisionComponentSyncSystem.java | 192 ++++++++++++++++++ .../PhysicsChunkSettingsIndexSystem.java | 21 +- .../physicschunk/PhysicsChunkTerrain.java | 21 +- 8 files changed, 309 insertions(+), 72 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index 157466c0..c9f98735 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -7,42 +7,24 @@ /** * Options that control generated PhysicsChunk backend collision geometry. */ -public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisionMode, - float friction, - float restitution, - int collisionGroup, - int collisionMask) { +public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisionMode) { public static final PhysicsChunkBuildOptions DEFAULT = fromNativeVoxelCollisionEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED); public PhysicsChunkBuildOptions { Objects.requireNonNull(chunkCollisionMode, "chunkCollisionMode"); - if (!Float.isFinite(friction) || friction < 0.0f) { - throw new IllegalArgumentException("friction must be finite and >= 0"); - } - if (!Float.isFinite(restitution) || restitution < 0.0f) { - throw new IllegalArgumentException("restitution must be finite and >= 0"); - } } @Nonnull public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrainSettings settings) { - return new PhysicsChunkBuildOptions( - ChunkCollisionMode.fromNativeVoxelCollisionEnabled(settings.isNativeVoxelCollisionEnabled()), - PhysicsChunkCollisionDefaults.FRICTION, - PhysicsChunkCollisionDefaults.RESTITUTION, - PhysicsChunkCollisionDefaults.COLLISION_GROUP, - PhysicsChunkCollisionDefaults.COLLISION_MASK); + return fromNativeVoxelCollisionEnabled(settings.isNativeVoxelCollisionEnabled()); } @Nonnull public static PhysicsChunkBuildOptions fromNativeVoxelCollisionEnabled(boolean enabled) { - return new PhysicsChunkBuildOptions(ChunkCollisionMode.fromNativeVoxelCollisionEnabled(enabled), - PhysicsChunkCollisionDefaults.FRICTION, - PhysicsChunkCollisionDefaults.RESTITUTION, - PhysicsChunkCollisionDefaults.COLLISION_GROUP, - PhysicsChunkCollisionDefaults.COLLISION_MASK); + return new PhysicsChunkBuildOptions( + ChunkCollisionMode.fromNativeVoxelCollisionEnabled(enabled)); } public boolean nativeVoxelCollisionEnabled() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 71a071ce..532b4336 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -22,10 +22,12 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionComponentSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.ColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.CompletedStepPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; @@ -78,6 +80,7 @@ public static void register(@Nonnull ComponentRegistryProxy regist registry.registerSystem(new SpaceSettingsApplicationSystem()); registry.registerSystem(new ChunkCollisionMutationDrainSystem()); registry.registerSystem(new BodyBindingSystem()); + registry.registerSystem(new ChunkCollisionComponentSyncSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); @@ -118,6 +121,10 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic () -> cleanupResource(store, PhysicsChunkSettingsIndexResource.getResourceType(), PhysicsChunkSettingsIndexResource::clear)); + failure = runShutdownCleanup(failure, + () -> cleanupResource(store, + PhysicsChunkComponentSyncResource.getResourceType(), + PhysicsChunkComponentSyncResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsIdentityIndexResource.getResourceType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java new file mode 100644 index 00000000..b0cbc12c --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java @@ -0,0 +1,99 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Tracks space-level components that generated PhysicsChunk body rows inherit at runtime. + */ +public final class PhysicsChunkComponentSyncResource implements Resource { + + @Nullable + private static ResourceType resourceType; + @Nonnull + private final Map surfacesBySpaceUuid = + new Object2ObjectOpenHashMap<>(); + + public PhysicsChunkComponentSyncResource() { + } + + @Nonnull + public synchronized Set replaceAll( + @Nonnull Map surfaces) { + Set changed = new ObjectOpenHashSet<>(); + for (Map.Entry entry : surfaces.entrySet()) { + ChunkCollisionSurfaceComponents previous = surfacesBySpaceUuid.get(entry.getKey()); + if (!entry.getValue().equals(previous)) { + changed.add(entry.getKey()); + } + } + surfacesBySpaceUuid.clear(); + surfacesBySpaceUuid.putAll(surfaces); + return changed; + } + + public synchronized void clear() { + surfacesBySpaceUuid.clear(); + } + + @Nonnull + @Override + public synchronized PhysicsChunkComponentSyncResource clone() { + PhysicsChunkComponentSyncResource copy = new PhysicsChunkComponentSyncResource(); + copy.surfacesBySpaceUuid.putAll(surfacesBySpaceUuid); + return copy; + } + + @Nonnull + public static ResourceType getResourceType() { + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; + } + + public record ChunkCollisionSurfaceComponents(float friction, + float restitution, + int collisionGroup, + int collisionMask) { + + @Nonnull + public static ChunkCollisionSurfaceComponents of(@Nullable MaterialComponent material, + @Nullable CollisionFilterComponent filter) { + return new ChunkCollisionSurfaceComponents( + material != null ? material.getFriction() : PhysicsChunkCollisionDefaults.FRICTION, + material != null + ? material.getRestitution() + : PhysicsChunkCollisionDefaults.RESTITUTION, + filter != null + ? filter.getCollisionGroup() + : PhysicsChunkCollisionDefaults.COLLISION_GROUP, + filter != null + ? filter.getCollisionMask() + : PhysicsChunkCollisionDefaults.COLLISION_MASK); + } + + @Nonnull + public MaterialComponent material() { + return new MaterialComponent(friction, restitution); + } + + @Nonnull + public CollisionFilterComponent filter() { + return new CollisionFilterComponent(collisionGroup, collisionMask); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index be62cee6..d0d1b628 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -73,20 +73,12 @@ public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, boolean nativeVoxelCollisionEnabled, int radius, int bodyRadius, - int ttlTicks, - float friction, - float restitution, - int collisionGroup, - int collisionMask) { + int ttlTicks) { @Nonnull public PhysicsChunkBuildOptions buildOptions() { return new PhysicsChunkBuildOptions( - ChunkCollisionMode.fromNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled), - friction, - restitution, - collisionGroup, - collisionMask); + ChunkCollisionMode.fromNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled)); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 4dc62c86..f767f610 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -96,6 +96,9 @@ public static void registerResourceTypes( PhysicsChunkSettingsIndexResource.setResourceType(registry.registerResource( PhysicsChunkSettingsIndexResource.class, PhysicsChunkSettingsIndexResource::new)); + PhysicsChunkComponentSyncResource.setResourceType(registry.registerResource( + PhysicsChunkComponentSyncResource.class, + PhysicsChunkComponentSyncResource::new)); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java new file mode 100644 index 00000000..de28ea45 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java @@ -0,0 +1,192 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource.ChunkCollisionSurfaceComponents; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +/** + * Propagates space-row material and collision-filter component changes to generated chunk bodies. + */ +public final class ChunkCollisionComponentSyncSystem extends TickingSystem + implements QuerySystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), + new SystemDependency<>(Order.AFTER, ChunkCollisionMutationDrainSystem.class), + new SystemDependency<>(Order.BEFORE, BodyBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + Map surfaces = collectSpaceSurfaces(store, + systemIndex); + Set changedSpaces = store + .getResource(PhysicsChunkComponentSyncResource.getResourceType()) + .replaceAll(surfaces); + if (changedSpaces.isEmpty()) { + return; + } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + BiConsumer, CommandBuffer> collector = + (chunk, commandBuffer) -> syncChunk(runtime, + changedSpaces, + surfaces, + chunk, + commandBuffer); + store.forEachChunk(systemIndex, collector); + } + + @Nonnull + private static Map collectSpaceSurfaces( + @Nonnull Store store, + int systemIndex) { + Map surfaces = new Object2ObjectOpenHashMap<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectSpaceSurfaces(surfaces, chunk); + store.forEachChunk(systemIndex, collector); + return surfaces; + } + + private static void collectSpaceSurfaces( + @Nonnull Map surfaces, + @Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + if (chunk.getComponent(index, SpaceComponent.getComponentType()) == null) { + continue; + } + UUID spaceUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); + if (PhysicsStoreSystemSupport.isNil(spaceUuid)) { + continue; + } + surfaces.put(spaceUuid, + ChunkCollisionSurfaceComponents.of( + chunk.getComponent(index, MaterialComponent.getComponentType()), + chunk.getComponent(index, CollisionFilterComponent.getComponentType()))); + } + } + + private static void syncChunk(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull Set changedSpaces, + @Nonnull Map surfaces, + @Nonnull ArchetypeChunk chunk, + @Nonnull CommandBuffer commandBuffer) { + for (int index = 0; index < chunk.size(); index++) { + if (chunk.getComponent(index, ChunkCollisionSourceComponent.getComponentType()) == null) { + continue; + } + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body == null || !changedSpaces.contains(body.getSpaceUuid())) { + continue; + } + ChunkCollisionSurfaceComponents surface = surfaces.get(body.getSpaceUuid()); + if (surface == null) { + continue; + } + Ref bodyRef = chunk.getReferenceTo(index); + boolean materialChanged = syncMaterial(commandBuffer, bodyRef, surface, + chunk.getComponent(index, MaterialComponent.getComponentType())); + boolean filterChanged = syncFilter(commandBuffer, bodyRef, surface, + chunk.getComponent(index, CollisionFilterComponent.getComponentType())); + if (materialChanged || filterChanged) { + syncBackend(runtime, bodyRef, surface, materialChanged, filterChanged); + } + } + } + + private static boolean syncMaterial(@Nonnull CommandBuffer commandBuffer, + @Nonnull Ref bodyRef, + @Nonnull ChunkCollisionSurfaceComponents surface, + MaterialComponent material) { + if (material != null + && Float.compare(material.getFriction(), surface.friction()) == 0 + && Float.compare(material.getRestitution(), surface.restitution()) == 0) { + return false; + } + commandBuffer.putComponent(bodyRef, + MaterialComponent.getComponentType(), + surface.material()); + return true; + } + + private static boolean syncFilter(@Nonnull CommandBuffer commandBuffer, + @Nonnull Ref bodyRef, + @Nonnull ChunkCollisionSurfaceComponents surface, + CollisionFilterComponent filter) { + if (filter != null + && filter.getCollisionGroup() == surface.collisionGroup() + && filter.getCollisionMask() == surface.collisionMask()) { + return false; + } + commandBuffer.putComponent(bodyRef, + CollisionFilterComponent.getComponentType(), + surface.filter()); + return true; + } + + private static void syncBackend(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull Ref bodyRef, + @Nonnull ChunkCollisionSurfaceComponents surface, + boolean materialChanged, + boolean filterChanged) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyRef); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyRef); + if (bodyHandle == null || spaceHandle == null) { + return; + } + PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + if (backendRuntime == null) { + return; + } + if (materialChanged) { + backendRuntime.setBodyFriction(spaceHandle.value(), + bodyHandle.value(), + surface.friction()); + backendRuntime.setBodyRestitution(spaceHandle.value(), + bodyHandle.value(), + surface.restitution()); + } + if (filterChanged) { + backendRuntime.setBodyCollisionFilter(spaceHandle.value(), + bodyHandle.value(), + surface.collisionGroup(), + surface.collisionMask()); + } + } + + @Nonnull + @Override + public Query getQuery() { + return PhysicsStoreSystemSupport.uuidQuery(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index bad35b91..7ba4dfa4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -12,9 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -62,29 +59,13 @@ private static void collectChunk( ChunkCollisionSettingsComponent settings = chunkCollision != null ? chunkCollision : new ChunkCollisionSettingsComponent(); - MaterialComponent material = chunk.getComponent(index, - MaterialComponent.getComponentType()); - CollisionFilterComponent filter = chunk.getComponent(index, - CollisionFilterComponent.getComponentType()); settingsBySpaceUuid.put(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, settings.getMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelCollisionEnabled(), settings.getRadius(), settings.getBodyRadius(), - settings.getTtlTicks(), - material != null - ? material.getFriction() - : PhysicsChunkCollisionDefaults.FRICTION, - material != null - ? material.getRestitution() - : PhysicsChunkCollisionDefaults.RESTITUTION, - filter != null - ? filter.getCollisionGroup() - : PhysicsChunkCollisionDefaults.COLLISION_GROUP, - filter != null - ? filter.getCollisionMask() - : PhysicsChunkCollisionDefaults.COLLISION_MASK)); + settings.getTtlTicks())); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index b4f64322..d4442654 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; @@ -14,8 +13,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; @@ -168,29 +165,13 @@ private static PhysicsChunkSpaceSettings requireSettings( throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); } - MaterialComponent material = - store.getComponent(spaceRef, MaterialComponent.getComponentType()); - CollisionFilterComponent filter = - store.getComponent(spaceRef, CollisionFilterComponent.getComponentType()); return new PhysicsChunkSpaceSettings(spaceUuid, settings.getMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelCollisionEnabled(), settings.getRadius(), settings.getBodyRadius(), - settings.getTtlTicks(), - material != null - ? material.getFriction() - : PhysicsChunkCollisionDefaults.FRICTION, - material != null - ? material.getRestitution() - : PhysicsChunkCollisionDefaults.RESTITUTION, - filter != null - ? filter.getCollisionGroup() - : PhysicsChunkCollisionDefaults.COLLISION_GROUP, - filter != null - ? filter.getCollisionMask() - : PhysicsChunkCollisionDefaults.COLLISION_MASK); + settings.getTtlTicks()); } @Nonnull From 3ef04b258b3aa03c59117a60c1755b91ae1da614 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 08:30:29 +0200 Subject: [PATCH 425/534] test(core): cover chunk collision surface sync Signed-off-by: Blovien --- .../FakePhysicsBackendRuntimeProvider.java | 8 + ...ChunkCollisionComponentSyncSystemTest.java | 283 ++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java diff --git a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java index 72427e4f..9d7ae760 100644 --- a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java +++ b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java @@ -356,11 +356,19 @@ public void setBodyFriction(int spaceId, long bodyId, float friction) { requireBody(requireSpace(spaceId), bodyId).friction = friction; } + public float bodyFriction(int spaceId, long bodyId) { + return requireBody(requireSpace(spaceId), bodyId).friction; + } + @Override public void setBodyRestitution(int spaceId, long bodyId, float restitution) { requireBody(requireSpace(spaceId), bodyId).restitution = restitution; } + public float bodyRestitution(int spaceId, long bodyId) { + return requireBody(requireSpace(spaceId), bodyId).restitution; + } + @Override public void setBodyCollisionFilter(int spaceId, long bodyId, int group, int mask) { BodyState body = requireBody(requireSpace(spaceId), bodyId); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java new file mode 100644 index 00000000..62fcad24 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java @@ -0,0 +1,283 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import java.util.ArrayList; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class ChunkCollisionComponentSyncSystemTest { + + private static final float DELTA = 0.000001f; + + @Test + void spaceSurfaceComponentsSyncGeneratedRowsAndBoundBackends() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new PhysicsChunkSettingsIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + proxy.registerSystem(new ChunkCollisionMutationDrainSystem()); + proxy.registerSystem(new BodyBindingSystem()); + proxy.registerSystem(new ChunkCollisionComponentSyncSystem()); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-component-sync-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(1); + RuntimeFixture runtime = addBoundSpace(store, + spaceUuid, + new BackendId("test:chunk-collision-component-sync")); + MaterialComponent expectedMaterial = new MaterialComponent(0.84f, 0.16f); + CollisionFilterComponent expectedFilter = new CollisionFilterComponent(0x40, 0x07); + putSpaceSurface(store, runtime.spaceRef(), expectedMaterial, expectedFilter); + + GeneratedRow boundRow = addGeneratedBoxRow(store, runtime, spaceUuid, "0:0:0", true); + GeneratedRow unboundRow = addGeneratedBoxRow(store, runtime, spaceUuid, "1:0:0", false); + + store.tick(0.0f); + + assertSurface(store, boundRow.ref(), expectedMaterial, expectedFilter); + assertSurface(store, unboundRow.ref(), expectedMaterial, expectedFilter); + assertEquals(expectedMaterial.getFriction(), + runtime.backendRuntime().bodyFriction(runtime.spaceHandle().value(), + boundRow.bodyHandle().value()), + DELTA); + assertEquals(expectedMaterial.getRestitution(), + runtime.backendRuntime().bodyRestitution(runtime.spaceHandle().value(), + boundRow.bodyHandle().value()), + DELTA); + assertEquals(expectedFilter.getCollisionGroup(), + runtime.backendRuntime().bodyCollisionGroup(runtime.spaceHandle().value(), + boundRow.bodyHandle().value())); + assertEquals(expectedFilter.getCollisionMask(), + runtime.backendRuntime().bodyCollisionMask(runtime.spaceHandle().value(), + boundRow.bodyHandle().value())); + BackendBodyHandle newlyBoundHandle = store + .getResource(PhysicsRuntimeResource.getResourceType()) + .getBodyHandle(unboundRow.ref()); + assertNotNull(newlyBoundHandle); + assertEquals(expectedMaterial.getFriction(), + runtime.backendRuntime().bodyFriction(runtime.spaceHandle().value(), + newlyBoundHandle.value()), + DELTA); + assertEquals(expectedMaterial.getRestitution(), + runtime.backendRuntime().bodyRestitution(runtime.spaceHandle().value(), + newlyBoundHandle.value()), + DELTA); + assertEquals(expectedFilter.getCollisionGroup(), + runtime.backendRuntime().bodyCollisionGroup(runtime.spaceHandle().value(), + newlyBoundHandle.value())); + assertEquals(expectedFilter.getCollisionMask(), + runtime.backendRuntime().bodyCollisionMask(runtime.spaceHandle().value(), + newlyBoundHandle.value())); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static RuntimeFixture addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + + FakePhysicsBackendRuntime runtime = (FakePhysicsBackendRuntime) + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(runtime.createSpace(new SpaceId(42))); + PhysicsRuntimeResource runtimeResource = store.getResource( + PhysicsRuntimeResource.getResourceType()); + runtimeResource.putRuntime(backendId, runtime); + runtimeResource.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + identity.putSpaceHandle(spaceHandle, spaceRef); + return new RuntimeFixture(spaceRef, spaceHandle, runtime); + } + + private static void putSpaceSurface(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter) { + store.putComponent(spaceRef, MaterialComponent.getComponentType(), material); + store.putComponent(spaceRef, CollisionFilterComponent.getComponentType(), filter); + } + + @Nonnull + private static GeneratedRow addGeneratedBoxRow(@Nonnull Store store, + @Nonnull RuntimeFixture runtime, + @Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + boolean bindRuntime) { + UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0); + BodyComponent body = new BodyComponent(spaceUuid, + PhysicsBodyKind.TERRAIN, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + body.setSpaceRef(runtime.spaceRef()); + Holder holder = PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.STATIC, 0.0f, 0.0f, 0.0f, false), + target(), + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.11f, 0.01f), + new CollisionFilterComponent(0x01, 0x02)); + holder.addComponent(ChunkCollisionSourceComponent.getComponentType(), + new ChunkCollisionSourceComponent(sourceKey, 0, 0, 0, "", PartKind.BOX, 0)); + Ref bodyRef = store.addEntity(holder, AddReason.SPAWN); + assertNotNull(bodyRef); + + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(bodyUuid, bodyRef); + store.getExternalData().putRefForUUID(bodyUuid, bodyRef); + + if (!bindRuntime) { + return new GeneratedRow(bodyRef, new BackendBodyHandle(Long.MIN_VALUE)); + } + long bodyHandle = runtime.backendRuntime() + .createBody(runtime.spaceHandle().value(), + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.axisCode(PhysicsAxis.Y), + 0.0f, + 0.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + runtime.backendRuntime().setBodyFriction(runtime.spaceHandle().value(), bodyHandle, 0.11f); + runtime.backendRuntime().setBodyRestitution(runtime.spaceHandle().value(), bodyHandle, 0.01f); + runtime.backendRuntime().setBodyCollisionFilter(runtime.spaceHandle().value(), + bodyHandle, + 0x01, + 0x02); + BackendBodyHandle backendBodyHandle = new BackendBodyHandle(bodyHandle); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .putBodyHandle(bodyUuid, + bodyRef, + spaceUuid, + runtime.spaceHandle(), + backendBodyHandle); + identity.putBodyHandle(backendBodyHandle, bodyRef); + return new GeneratedRow(bodyRef, backendBodyHandle); + } + + @Nonnull + private static TargetComponent target() { + TargetComponent target = new TargetComponent(); + target.setPosition(new Vector3f(1.0f, 2.0f, 3.0f)); + return target; + } + + private static void assertSurface(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull MaterialComponent expectedMaterial, + @Nonnull CollisionFilterComponent expectedFilter) { + MaterialComponent material = store.getComponent(bodyRef, + MaterialComponent.getComponentType()); + assertNotNull(material); + assertEquals(expectedMaterial.getFriction(), material.getFriction(), DELTA); + assertEquals(expectedMaterial.getRestitution(), material.getRestitution(), DELTA); + CollisionFilterComponent filter = store.getComponent(bodyRef, + CollisionFilterComponent.getComponentType()); + assertNotNull(filter); + assertEquals(expectedFilter.getCollisionGroup(), filter.getCollisionGroup()); + assertEquals(expectedFilter.getCollisionMask(), filter.getCollisionMask()); + } + + private static void assertSoftSkipsEmpty(@Nonnull Store store) { + assertEquals(0, + store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .getSoftSkipsByReason() + .size()); + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private record RuntimeFixture(@Nonnull Ref spaceRef, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull FakePhysicsBackendRuntime backendRuntime) { + } + + private record GeneratedRow(@Nonnull Ref ref, + @Nonnull BackendBodyHandle bodyHandle) { + } +} From 81b315426cdeb61c7b4487027fe7593436ee1e8f Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 08:50:03 +0200 Subject: [PATCH 426/534] refactor(core): write space settings by component domain Signed-off-by: Blovien --- .../settings/SolverSettingsCommand.java | 37 ++- .../commands/CollisionLodSettingsCommand.java | 44 ++- .../commands/PhysicsChunkSettingsCommand.java | 46 ++- .../VisualMaterializationSettingsCommand.java | 53 ++-- .../commands/VisualSyncSettingsCommand.java | 68 ++-- .../PhysicsStoreSpaceMutations.java | 153 +++++++++ ...isualMaterializationSettingsComponent.java | 6 +- .../VisualSyncSettingsComponent.java | 5 +- .../CollisionLodSettingsComponent.java | 5 +- .../plugin/physicsstore/PhysicsEntities.java | 148 +++++++-- .../plugin/physicsstore/PhysicsSpaces.java | 294 ++++++++++++++++++ .../commands/stress/StressBodiesCommand.java | 14 +- 12 files changed, 712 insertions(+), 161 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index ec910cbb..1bc4f8db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -74,13 +74,12 @@ private void applySettings(@Nonnull CommandContext ctx, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull SolverCapabilitySummary summary) { - PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, spaceRef); - if (currentSettings == null) { + PhysicsSolverSettings settings = PhysicsSpaces.solverSettings(physicsStore, spaceRef); + if (settings == null) { ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() + " no longer exists.")); return; } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, summary, settings); return; @@ -88,19 +87,19 @@ private void applySettings(@Nonnull CommandContext ctx, int solverIterations = solverIterationsArg.provided(ctx) ? solverIterationsArg.get(ctx) - : settings.getSolverSettings().getSolverIterations(); + : settings.getSolverIterations(); int stabilizationIterations = stabilizationIterationsArg.provided(ctx) ? stabilizationIterationsArg.get(ctx) - : settings.getSolverSettings().getStabilizationIterations(); + : settings.getStabilizationIterations(); float sleepLinearThreshold = sleepLinearThresholdArg.provided(ctx) ? sleepLinearThresholdArg.get(ctx) - : settings.getSolverSettings().getDynamicSleepLinearThreshold(); + : settings.getDynamicSleepLinearThreshold(); float sleepAngularThreshold = sleepAngularThresholdArg.provided(ctx) ? sleepAngularThresholdArg.get(ctx) - : settings.getSolverSettings().getDynamicSleepAngularThreshold(); + : settings.getDynamicSleepAngularThreshold(); float sleepTime = sleepTimeArg.provided(ctx) ? sleepTimeArg.get(ctx) - : settings.getSolverSettings().getDynamicSleepTimeUntilSleep(); + : settings.getDynamicSleepTimeUntilSleep(); if (solverIterations < 1 || stabilizationIterations < 0 @@ -115,10 +114,10 @@ private void applySettings(@Nonnull CommandContext ctx, return; } - settings.getSolverSettings().setSolverIterations(solverIterations); - settings.getSolverSettings().setStabilizationIterations(stabilizationIterations); - settings.getSolverSettings().setDynamicSleepTuning(sleepLinearThreshold, sleepAngularThreshold, sleepTime); - PhysicsSpaces.putSettings(physicsStore, spaceRef, settings); + settings.setSolverIterations(solverIterations); + settings.setStabilizationIterations(stabilizationIterations); + settings.setDynamicSleepTuning(sleepLinearThreshold, sleepAngularThreshold, sleepTime); + PhysicsSpaces.putSolverSettings(physicsStore, spaceRef, settings); sendSummary(ctx, spaceId, summary, settings); } @@ -133,17 +132,17 @@ private boolean anyArgProvided(@Nonnull CommandContext ctx) { private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull SpaceId spaceId, @Nonnull SolverCapabilitySummary summary, - @Nonnull PhysicsSpaceSettings settings) { + @Nonnull PhysicsSolverSettings settings) { ctx.sender().sendMessage(Message.raw("Impulse solver settings for space " + spaceId.value() + " backend=" + summary.backendId() + " solverApplied=" + summary.solverTuningSupported() + " sleepApplied=" + summary.activationTuningSupported() - + ": solverIterations=" + settings.getSolverSettings().getSolverIterations() - + " stabilizationIterations=" + settings.getSolverSettings().getStabilizationIterations() - + " sleepLinearThreshold=" + settings.getSolverSettings().getDynamicSleepLinearThreshold() - + " sleepAngularThreshold=" + settings.getSolverSettings().getDynamicSleepAngularThreshold() - + " sleepTime=" + settings.getSolverSettings().getDynamicSleepTimeUntilSleep())); + + ": solverIterations=" + settings.getSolverIterations() + + " stabilizationIterations=" + settings.getStabilizationIterations() + + " sleepLinearThreshold=" + settings.getDynamicSleepLinearThreshold() + + " sleepAngularThreshold=" + settings.getDynamicSleepAngularThreshold() + + " sleepTime=" + settings.getDynamicSleepTimeUntilSleep())); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java index 6329f09c..5916fc42 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -78,20 +77,19 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, + PhysicsCollisionLodSettings settings = PhysicsSpaces.collisionLodSettings(physicsStore, selection.spaceRef()); - if (currentSettings == null) { + if (settings == null) { ctx.sender().sendMessage(Message.raw("Physics space id=" + selection.spaceId().value() + " no longer exists.")); return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } - Boolean enabled = settings.getCollisionLodSettings().isCollisionLodEnabled(); + Boolean enabled = settings.isCollisionLodEnabled(); if (enabledArg.provided(ctx)) { enabled = parseBoolean(enabledArg.get(ctx)); if (enabled == null) { @@ -100,7 +98,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } } - Boolean farSleep = settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled(); + Boolean farSleep = settings.isCollisionLodFarSleepEnabled(); if (farSleepArg.provided(ctx)) { farSleep = parseBoolean(farSleepArg.get(ctx)); if (farSleep == null) { @@ -111,16 +109,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int nearRadius = nearRadiusArg.provided(ctx) ? nearRadiusArg.get(ctx) - : settings.getCollisionLodSettings().getCollisionLodNearRadius(); + : settings.getCollisionLodNearRadius(); int midRadius = midRadiusArg.provided(ctx) ? midRadiusArg.get(ctx) - : settings.getCollisionLodSettings().getCollisionLodMidRadius(); + : settings.getCollisionLodMidRadius(); int hysteresis = hysteresisArg.provided(ctx) ? hysteresisArg.get(ctx) - : settings.getCollisionLodSettings().getCollisionLodHysteresis(); + : settings.getCollisionLodHysteresis(); int interval = intervalArg.provided(ctx) ? intervalArg.get(ctx) - : settings.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks(); + : settings.getCollisionLodRefreshIntervalTicks(); if (outOfRange(nearRadius, PhysicsCollisionLodSettings.MAX_COLLISION_LOD_RADIUS) || outOfRange(midRadius, PhysicsCollisionLodSettings.MAX_COLLISION_LOD_RADIUS) || nearRadius > midRadius @@ -138,12 +136,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - settings.getCollisionLodSettings().setCollisionLodEnabled(enabled); - settings.getCollisionLodSettings().setCollisionLodRadii(nearRadius, midRadius); - settings.getCollisionLodSettings().setCollisionLodHysteresis(hysteresis); - settings.getCollisionLodSettings().setCollisionLodRefreshIntervalTicks(interval); - settings.getCollisionLodSettings().setCollisionLodFarSleepEnabled(farSleep); - PhysicsSpaces.putSettings(physicsStore, selection.spaceRef(), settings); + settings.setCollisionLodEnabled(enabled); + settings.setCollisionLodRadii(nearRadius, midRadius); + settings.setCollisionLodHysteresis(hysteresis); + settings.setCollisionLodRefreshIntervalTicks(interval); + settings.setCollisionLodFarSleepEnabled(farSleep); + PhysicsSpaces.putCollisionLodSettings(physicsStore, selection.spaceRef(), settings); sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } @@ -163,15 +161,15 @@ private static boolean outOfRange(int value, int maxValue) { private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { + @Nonnull PhysicsCollisionLodSettings settings) { ctx.sender().sendMessage(Message.raw("Impulse collision LOD settings for space " + spaceId.value() - + ": enabled=" + settings.getCollisionLodSettings().isCollisionLodEnabled() - + " nearRadius=" + settings.getCollisionLodSettings().getCollisionLodNearRadius() - + " midRadius=" + settings.getCollisionLodSettings().getCollisionLodMidRadius() - + " hysteresis=" + settings.getCollisionLodSettings().getCollisionLodHysteresis() - + " interval=" + settings.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks() - + " farSleep=" + settings.getCollisionLodSettings().isCollisionLodFarSleepEnabled() + + ": enabled=" + settings.isCollisionLodEnabled() + + " nearRadius=" + settings.getCollisionLodNearRadius() + + " midRadius=" + settings.getCollisionLodMidRadius() + + " hysteresis=" + settings.getCollisionLodHysteresis() + + " interval=" + settings.getCollisionLodRefreshIntervalTicks() + + " farSleep=" + settings.isCollisionLodFarSleepEnabled() + " tiers=near:terrain+body mid:terrain far:terrain+sleep")); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index b066bb81..704af228 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -16,7 +16,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -78,20 +77,19 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, + PhysicsChunkTerrainSettings settings = PhysicsSpaces.chunkTerrainSettings(physicsStore, selection.spaceRef()); - if (currentSettings == null) { + if (settings == null) { ctx.sender().sendMessage(Message.raw("Physics space id=" + selection.spaceId().value() + " no longer exists.")); return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } - PhysicsChunkTerrainMode mode = settings.getPhysicsChunkTerrainSettings().getTerrainMode(); + PhysicsChunkTerrainMode mode = settings.getTerrainMode(); if (modeArg.provided(ctx)) { mode = parseMode(modeArg.get(ctx)); if (mode == null) { @@ -100,7 +98,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } } - EntityChunkBoundaryMode chunkBoundaryMode = settings.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode(); + EntityChunkBoundaryMode chunkBoundaryMode = settings.getEntityChunkBoundaryMode(); if (chunkBoundaryArg.provided(ctx)) { chunkBoundaryMode = parseChunkBoundaryMode(chunkBoundaryArg.get(ctx)); if (chunkBoundaryMode == null) { @@ -109,7 +107,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } } - boolean nativeVoxelCollisionEnabled = settings.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled(); + boolean nativeVoxelCollisionEnabled = settings.isNativeVoxelCollisionEnabled(); if (terrainArg.provided(ctx)) { Boolean parsedNativeVoxelCollision = parseTerrain(terrainArg.get(ctx)); if (parsedNativeVoxelCollision == null) { @@ -121,11 +119,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int playerRadius = playerRadiusArg.provided(ctx) ? playerRadiusArg.get(ctx) - : settings.getPhysicsChunkTerrainSettings().getTerrainRadius(); + : settings.getTerrainRadius(); int bodyRadius = bodyRadiusArg.provided(ctx) ? bodyRadiusArg.get(ctx) - : settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius(); - int ttl = ttlArg.provided(ctx) ? ttlArg.get(ctx) : settings.getPhysicsChunkTerrainSettings().getTerrainTtlTicks(); + : settings.getBodyTerrainRadius(); + int ttl = ttlArg.provided(ctx) ? ttlArg.get(ctx) : settings.getTerrainTtlTicks(); if (outOfRange(playerRadius, PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS) || outOfRange(bodyRadius, PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS) || outOfRange(ttl, PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS)) { @@ -139,13 +137,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - settings.getPhysicsChunkTerrainSettings().setTerrainMode(mode); - settings.getPhysicsChunkTerrainSettings().setEntityChunkBoundaryMode(chunkBoundaryMode); - settings.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled); - settings.getPhysicsChunkTerrainSettings().setTerrainRadius(playerRadius); - settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(bodyRadius); - settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(ttl); - PhysicsSpaces.putSettings(physicsStore, selection.spaceRef(), settings); + settings.setTerrainMode(mode); + settings.setEntityChunkBoundaryMode(chunkBoundaryMode); + settings.setNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled); + settings.setTerrainRadius(playerRadius); + settings.setBodyTerrainRadius(bodyRadius); + settings.setTerrainTtlTicks(ttl); + PhysicsSpaces.putChunkTerrainSettings(physicsStore, selection.spaceRef(), settings); sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } @@ -165,17 +163,17 @@ private static boolean outOfRange(int value, int maxValue) { private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { + @Nonnull PhysicsChunkTerrainSettings settings) { ctx.sender().sendMessage(Message.raw("Impulse PhysicsChunk settings for space " + spaceId.value() - + ": mode=" + settings.getPhysicsChunkTerrainSettings().getTerrainMode().name().toLowerCase(Locale.ROOT) - + " playerRadius=" + settings.getPhysicsChunkTerrainSettings().getTerrainRadius() - + " bodyRadius=" + settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius() - + " ttl=" + settings.getPhysicsChunkTerrainSettings().getTerrainTtlTicks() + + ": mode=" + settings.getTerrainMode().name().toLowerCase(Locale.ROOT) + + " playerRadius=" + settings.getTerrainRadius() + + " bodyRadius=" + settings.getBodyTerrainRadius() + + " ttl=" + settings.getTerrainTtlTicks() + " chunkBoundary=" - + settings.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode().name().toLowerCase(Locale.ROOT) + + settings.getEntityChunkBoundaryMode().name().toLowerCase(Locale.ROOT) + " terrain=" - + (settings.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled() + + (settings.isNativeVoxelCollisionEnabled() ? "native_voxels" : "boxes"))); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java index d378b9ea..38ae90bb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -98,14 +97,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, - selectedSpace.spaceRef()); - if (currentSettings == null) { + PhysicsVisualMaterializationSettings settings = + PhysicsSpaces.visualMaterializationSettings(physicsStore, + selectedSpace.spaceRef()); + if (settings == null) { ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() + " no longer exists.")); return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -117,44 +116,46 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("enabled must be true or false.")); return CompletableFuture.completedFuture(null); } - settings.getVisualMaterializationSettings().setDetachedVisualMaterializationEnabled(enabled); + settings.setDetachedVisualMaterializationEnabled(enabled); } int materializationRadius = materializationRadiusArg.provided(ctx) ? materializationRadiusArg.get(ctx) - : settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius(); + : settings.getDetachedVisualMaterializationRadius(); int dematerializationRadius = dematerializationRadiusArg.provided(ctx) ? dematerializationRadiusArg.get(ctx) - : settings.getVisualMaterializationSettings().getDetachedVisualDematerializationRadius(); + : settings.getDetachedVisualDematerializationRadius(); try { - settings.getVisualMaterializationSettings().setDetachedVisualRadii(materializationRadius, dematerializationRadius); + settings.setDetachedVisualRadii(materializationRadius, dematerializationRadius); if (interestIntervalArg.provided(ctx)) { - settings.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks( + settings.setDetachedVisualInterestRefreshIntervalTicks( interestIntervalArg.get(ctx)); } if (candidateIntervalArg.provided(ctx)) { - settings.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks( + settings.setDetachedVisualCandidateRefreshIntervalTicks( candidateIntervalArg.get(ctx)); } if (visibilityIntervalArg.provided(ctx)) { - settings.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks( + settings.setDetachedVisualVisibilityCheckIntervalTicks( visibilityIntervalArg.get(ctx)); } if (spawnRateArg.provided(ctx)) { - settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(spawnRateArg.get(ctx)); + settings.setDetachedVisualMaxSpawnsPerTick(spawnRateArg.get(ctx)); } if (capArg.provided(ctx)) { - settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(capArg.get(ctx)); + settings.setDetachedVisualMaxMaterialized(capArg.get(ctx)); } if (blockTypeArg.provided(ctx)) { - settings.getVisualMaterializationSettings().setDetachedVisualBlockType(blockTypeArg.get(ctx)); + settings.setDetachedVisualBlockType(blockTypeArg.get(ctx)); } } catch (IllegalArgumentException exception) { ctx.sender().sendMessage(Message.raw(exception.getMessage())); return CompletableFuture.completedFuture(null); } - PhysicsSpaces.putSettings(physicsStore, selectedSpace.spaceRef(), settings); + PhysicsSpaces.putVisualMaterializationSettings(physicsStore, + selectedSpace.spaceRef(), + settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } @@ -173,18 +174,18 @@ private boolean anyArgProvided(@Nonnull CommandContext ctx) { private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { + @Nonnull PhysicsVisualMaterializationSettings settings) { ctx.sender().sendMessage(Message.raw("Impulse visual materialization settings for space " + spaceId.value() - + ": enabled=" + settings.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled() - + " materializationRadius=" + settings.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius() - + " dematerializationRadius=" + settings.getVisualMaterializationSettings().getDetachedVisualDematerializationRadius() - + " interestInterval=" + settings.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks() - + " candidateInterval=" + settings.getVisualMaterializationSettings().getDetachedVisualCandidateRefreshIntervalTicks() - + " visibilityInterval=" + settings.getVisualMaterializationSettings().getDetachedVisualVisibilityCheckIntervalTicks() - + " spawnRate=" + settings.getVisualMaterializationSettings().getDetachedVisualMaxSpawnsPerTick() - + " cap=" + settings.getVisualMaterializationSettings().getDetachedVisualMaxMaterialized() - + " blockType=" + settings.getVisualMaterializationSettings().getDetachedVisualBlockType())); + + ": enabled=" + settings.isDetachedVisualMaterializationEnabled() + + " materializationRadius=" + settings.getDetachedVisualMaterializationRadius() + + " dematerializationRadius=" + settings.getDetachedVisualDematerializationRadius() + + " interestInterval=" + settings.getDetachedVisualInterestRefreshIntervalTicks() + + " candidateInterval=" + settings.getDetachedVisualCandidateRefreshIntervalTicks() + + " visibilityInterval=" + settings.getDetachedVisualVisibilityCheckIntervalTicks() + + " spawnRate=" + settings.getDetachedVisualMaxSpawnsPerTick() + + " cap=" + settings.getDetachedVisualMaxMaterialized() + + " blockType=" + settings.getDetachedVisualBlockType())); } private static Boolean parseBoolean(@Nonnull String value) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java index 48624eca..22fbd825 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import java.util.Locale; @@ -121,14 +120,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } SpaceId spaceId = selectedSpace.spaceId(); - PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, + PhysicsVisualSyncSettings settings = PhysicsSpaces.visualSyncSettings(physicsStore, selectedSpace.spaceRef()); - if (currentSettings == null) { + if (settings == null) { ctx.sender().sendMessage(Message.raw("Physics space id=" + spaceId.value() + " no longer exists.")); return CompletableFuture.completedFuture(null); } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); if (!anyArgProvided(ctx)) { sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); @@ -136,10 +134,10 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int fullRadius = fullRadiusArg.provided(ctx) ? fullRadiusArg.get(ctx) - : settings.getVisualSyncSettings().getVisualFullSyncRadius(); + : settings.getVisualFullSyncRadius(); int maxRadius = maxRadiusArg.provided(ctx) ? maxRadiusArg.get(ctx) - : settings.getVisualSyncSettings().getVisualMaxSyncRadius(); + : settings.getVisualMaxSyncRadius(); if (outOfRange(fullRadius, PhysicsVisualSyncSettings.MAX_VISUAL_FULL_SYNC_RADIUS) || outOfRange(maxRadius, PhysicsVisualSyncSettings.MAX_VISUAL_MAX_SYNC_RADIUS) || fullRadius > maxRadius) { @@ -205,7 +203,7 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), ctx.sender().sendMessage(Message.raw("farMode must be cutoff or lod.")); return CompletableFuture.completedFuture(null); } - settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(cutoff); + settings.setVisualFarSyncCutoffEnabled(cutoff); } if (occlusionArg.provided(ctx)) { VisualOcclusionMode occlusionMode = parseOcclusionMode(occlusionArg.get(ctx)); @@ -213,7 +211,7 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), ctx.sender().sendMessage(Message.raw("occlusion must be off, priority, or cull.")); return CompletableFuture.completedFuture(null); } - settings.getVisualSyncSettings().setVisualOcclusionMode(occlusionMode); + settings.setVisualOcclusionMode(occlusionMode); } if (entityCullingArg.provided(ctx)) { Boolean entityCulling = parseBoolean(entityCullingArg.get(ctx)); @@ -221,7 +219,7 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), ctx.sender().sendMessage(Message.raw("entityCulling must be true or false.")); return CompletableFuture.completedFuture(null); } - settings.getVisualSyncSettings().setEntityVisualSyncCullingEnabled(entityCulling); + settings.setEntityVisualSyncCullingEnabled(entityCulling); } if (visibilityCullingArg.provided(ctx)) { Boolean visibilityCulling = parseBoolean(visibilityCullingArg.get(ctx)); @@ -229,7 +227,7 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), ctx.sender().sendMessage(Message.raw("visibilityCulling must be true or false.")); return CompletableFuture.completedFuture(null); } - settings.getVisualSyncSettings().setVisualVisibilityCullingEnabled(visibilityCulling); + settings.setVisualVisibilityCullingEnabled(visibilityCulling); } if (predictionArg.provided(ctx)) { Boolean prediction = parseBoolean(predictionArg.get(ctx)); @@ -237,7 +235,7 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), ctx.sender().sendMessage(Message.raw("prediction must be true or false.")); return CompletableFuture.completedFuture(null); } - settings.getVisualSyncSettings().setVisualSnapshotPredictionEnabled(prediction); + settings.setVisualSnapshotPredictionEnabled(prediction); } if (smoothingArg.provided(ctx)) { Boolean smoothing = parseBoolean(smoothingArg.get(ctx)); @@ -245,29 +243,29 @@ && smoothingRateOutOfRange(smoothingRateArg.get(ctx), ctx.sender().sendMessage(Message.raw("smoothing must be true or false.")); return CompletableFuture.completedFuture(null); } - settings.getVisualSyncSettings().setVisualSnapshotSmoothingEnabled(smoothing); + settings.setVisualSnapshotSmoothingEnabled(smoothing); } - settings.getVisualSyncSettings().setVisualSyncRadii(fullRadius, maxRadius); + settings.setVisualSyncRadii(fullRadius, maxRadius); if (midIntervalArg.provided(ctx)) { - settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(midIntervalArg.get(ctx)); + settings.setVisualMidSyncIntervalTicks(midIntervalArg.get(ctx)); } if (farIntervalArg.provided(ctx)) { - settings.getVisualSyncSettings().setVisualFarSyncIntervalTicks(farIntervalArg.get(ctx)); + settings.setVisualFarSyncIntervalTicks(farIntervalArg.get(ctx)); } if (occlusionRaycastsArg.provided(ctx)) { - settings.getVisualSyncSettings().setVisualOcclusionRaycastsPerTick(occlusionRaycastsArg.get(ctx)); + settings.setVisualOcclusionRaycastsPerTick(occlusionRaycastsArg.get(ctx)); } if (occlusionCacheArg.provided(ctx)) { - settings.getVisualSyncSettings().setVisualOcclusionCacheTicks(occlusionCacheArg.get(ctx)); + settings.setVisualOcclusionCacheTicks(occlusionCacheArg.get(ctx)); } if (predictionMaxSecondsArg.provided(ctx)) { - settings.getVisualSyncSettings().setVisualSnapshotPredictionMaxSeconds(predictionMaxSecondsArg.get(ctx)); + settings.setVisualSnapshotPredictionMaxSeconds(predictionMaxSecondsArg.get(ctx)); } if (smoothingRateArg.provided(ctx)) { - settings.getVisualSyncSettings().setVisualSnapshotSmoothingRate(smoothingRateArg.get(ctx)); + settings.setVisualSnapshotSmoothingRate(smoothingRateArg.get(ctx)); } - PhysicsSpaces.putSettings(physicsStore, selectedSpace.spaceRef(), settings); + PhysicsSpaces.putVisualSyncSettings(physicsStore, selectedSpace.spaceRef(), settings); sendSummary(ctx, spaceId, settings); return CompletableFuture.completedFuture(null); } @@ -303,23 +301,23 @@ private static boolean smoothingRateOutOfRange(float value, float maxValue) { private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { + @Nonnull PhysicsVisualSyncSettings settings) { ctx.sender().sendMessage(Message.raw("Impulse visual sync settings for space " + spaceId.value() - + ": fullRadius=" + settings.getVisualSyncSettings().getVisualFullSyncRadius() - + " maxRadius=" + settings.getVisualSyncSettings().getVisualMaxSyncRadius() - + " farMode=" + (settings.getVisualSyncSettings().isVisualFarSyncCutoffEnabled() ? "cutoff" : "lod") - + " midInterval=" + settings.getVisualSyncSettings().getVisualMidSyncIntervalTicks() - + " farInterval=" + settings.getVisualSyncSettings().getVisualFarSyncIntervalTicks() - + " occlusion=" + settings.getVisualSyncSettings().getVisualOcclusionMode().name().toLowerCase(Locale.ROOT) - + " occlusionRaycasts=" + settings.getVisualSyncSettings().getVisualOcclusionRaycastsPerTick() - + " occlusionCache=" + settings.getVisualSyncSettings().getVisualOcclusionCacheTicks() - + " prediction=" + settings.getVisualSyncSettings().isVisualSnapshotPredictionEnabled() - + " predictionMaxSeconds=" + settings.getVisualSyncSettings().getVisualSnapshotPredictionMaxSeconds() - + " smoothing=" + settings.getVisualSyncSettings().isVisualSnapshotSmoothingEnabled() - + " smoothingRate=" + settings.getVisualSyncSettings().getVisualSnapshotSmoothingRate() - + " entityCulling=" + settings.getVisualSyncSettings().isEntityVisualSyncCullingEnabled() - + " visibilityCulling=" + settings.getVisualSyncSettings().isVisualVisibilityCullingEnabled())); + + ": fullRadius=" + settings.getVisualFullSyncRadius() + + " maxRadius=" + settings.getVisualMaxSyncRadius() + + " farMode=" + (settings.isVisualFarSyncCutoffEnabled() ? "cutoff" : "lod") + + " midInterval=" + settings.getVisualMidSyncIntervalTicks() + + " farInterval=" + settings.getVisualFarSyncIntervalTicks() + + " occlusion=" + settings.getVisualOcclusionMode().name().toLowerCase(Locale.ROOT) + + " occlusionRaycasts=" + settings.getVisualOcclusionRaycastsPerTick() + + " occlusionCache=" + settings.getVisualOcclusionCacheTicks() + + " prediction=" + settings.isVisualSnapshotPredictionEnabled() + + " predictionMaxSeconds=" + settings.getVisualSnapshotPredictionMaxSeconds() + + " smoothing=" + settings.isVisualSnapshotSmoothingEnabled() + + " smoothingRate=" + settings.getVisualSnapshotSmoothingRate() + + " entityCulling=" + settings.isEntityVisualSyncCullingEnabled() + + " visibilityCulling=" + settings.isVisualVisibilityCullingEnabled())); } private static Boolean parseFarCutoff(@Nonnull String value) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 59353cf1..42bd210b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -23,8 +23,14 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; @@ -145,6 +151,153 @@ public static void putSpaceSettings(@Nonnull Store store, .markSpaceSettingsPending(ref); } + public static void putChunkCollisionSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsChunkTerrainSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putChunkCollisionSettings(store, ref, settings); + } + + public static void putChunkCollisionSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsChunkTerrainSettings settings) { + requireSpaceUuid(store, ref); + PhysicsThreading.requireWorldThread(store, + "update PhysicsStore chunk collision settings"); + ChunkCollisionSettingsComponent component = + new ChunkCollisionSettingsComponent(Objects.requireNonNull(settings, "settings")); + putOrRemoveDefault(store, + ref, + ChunkCollisionSettingsComponent.getComponentType(), + component, + component.isDefault()); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + + public static void putSolverSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsSolverSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putSolverSettings(store, ref, settings); + } + + public static void putSolverSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsSolverSettings settings) { + requireSpaceUuid(store, ref); + PhysicsThreading.requireWorldThread(store, "update PhysicsStore solver settings"); + SolverSettingsComponent component = + new SolverSettingsComponent(Objects.requireNonNull(settings, "settings")); + putOrRemoveDefault(store, + ref, + SolverSettingsComponent.getComponentType(), + component, + component.isDefault()); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + + public static void putVisualSyncSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsVisualSyncSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putVisualSyncSettings(store, ref, settings); + } + + public static void putVisualSyncSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsVisualSyncSettings settings) { + requireSpaceUuid(store, ref); + PhysicsThreading.requireWorldThread(store, "update PhysicsStore visual sync settings"); + VisualSyncSettingsComponent component = + new VisualSyncSettingsComponent(Objects.requireNonNull(settings, "settings")); + putOrRemoveDefault(store, + ref, + VisualSyncSettingsComponent.getComponentType(), + component, + component.isDefault()); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + + public static void putVisualMaterializationSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsVisualMaterializationSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putVisualMaterializationSettings(store, ref, settings); + } + + public static void putVisualMaterializationSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsVisualMaterializationSettings settings) { + requireSpaceUuid(store, ref); + PhysicsThreading.requireWorldThread(store, + "update PhysicsStore visual materialization settings"); + VisualMaterializationSettingsComponent component = + new VisualMaterializationSettingsComponent(Objects.requireNonNull(settings, + "settings")); + putOrRemoveDefault(store, + ref, + VisualMaterializationSettingsComponent.getComponentType(), + component, + component.isDefault()); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + + public static void putCollisionLodSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsCollisionLodSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putCollisionLodSettings(store, ref, settings); + } + + public static void putCollisionLodSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsCollisionLodSettings settings) { + requireSpaceUuid(store, ref); + PhysicsThreading.requireWorldThread(store, "update PhysicsStore collision LOD settings"); + CollisionLodSettingsComponent component = + new CollisionLodSettingsComponent(Objects.requireNonNull(settings, "settings")); + putOrRemoveDefault(store, + ref, + CollisionLodSettingsComponent.getComponentType(), + component, + component.isDefault()); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + + public static void putExtensionSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsExtensionSettings settings) { + UUID spaceUuid = requireSpaceUuid(store, spaceId); + Ref ref = requireSpaceRef(store, spaceUuid); + putExtensionSettings(store, ref, settings); + } + + public static void putExtensionSettings(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PhysicsExtensionSettings settings) { + requireSpaceUuid(store, ref); + PhysicsThreading.requireWorldThread(store, "update PhysicsStore extension settings"); + ExtensionSettingsComponent component = + new ExtensionSettingsComponent(Objects.requireNonNull(settings, "settings")); + putOrRemoveDefault(store, + ref, + ExtensionSettingsComponent.getComponentType(), + component, + component.isDefault()); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .markSpaceSettingsPending(ref); + } + private static void addSpaceSettingsComponents(@Nonnull Holder holder, @Nonnull PhysicsSpaceSettings settings) { ChunkCollisionSettingsComponent chunkCollision = diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java index 0e062676..e97a02e5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java @@ -168,8 +168,10 @@ public String getDetachedVisualBlockType() { } public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - PhysicsVisualMaterializationSettings target = - settings.getVisualMaterializationSettings(); + copyTo(settings.getVisualMaterializationSettings()); + } + + public void copyTo(@Nonnull PhysicsVisualMaterializationSettings target) { target.setDetachedVisualMaterializationEnabled(detachedVisualMaterializationEnabled); target.setDetachedVisualRadii(detachedVisualMaterializationRadius, detachedVisualDematerializationRadius); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java index e4344896..75a806f0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java @@ -211,7 +211,10 @@ public VisualOcclusionMode getVisualOcclusionMode() { } public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - PhysicsVisualSyncSettings target = settings.getVisualSyncSettings(); + copyTo(settings.getVisualSyncSettings()); + } + + public void copyTo(@Nonnull PhysicsVisualSyncSettings target) { target.setVisualSyncRadii(visualFullSyncRadius, visualMaxSyncRadius); target.setVisualFarSyncCutoffEnabled(visualFarSyncCutoffEnabled); target.setVisualMidSyncIntervalTicks(visualMidSyncIntervalTicks); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java index f215e2d1..0c4aa1e7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java @@ -122,7 +122,10 @@ public boolean isCollisionLodFarSleepEnabled() { } public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - PhysicsCollisionLodSettings target = settings.getCollisionLodSettings(); + copyTo(settings.getCollisionLodSettings()); + } + + public void copyTo(@Nonnull PhysicsCollisionLodSettings target) { target.setCollisionLodEnabled(collisionLodEnabled); target.setCollisionLodRadii(collisionLodNearRadius, collisionLodMidRadius); target.setCollisionLodHysteresis(collisionLodHysteresis); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index 0096d0cf..d15696e2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -3,6 +3,8 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -132,8 +134,7 @@ public static void addSpaceComponents(@Nonnull Holder holder, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { addSpaceComponent(holder, space); - holder.addComponent(ChunkCollisionSettingsComponent.getComponentType(), - Objects.requireNonNull(chunkCollisionSettings, "chunkCollisionSettings").clone()); + addChunkCollisionSettingsComponent(holder, chunkCollisionSettings); addSpaceSettingsComponents(holder, solverSettings, visualSyncSettings, @@ -142,24 +143,53 @@ public static void addSpaceComponents(@Nonnull Holder holder, extensionSettings); } + public static void addChunkCollisionSettingsComponent(@Nonnull Holder holder, + @Nonnull ChunkCollisionSettingsComponent chunkCollisionSettings) { + ChunkCollisionSettingsComponent component = + Objects.requireNonNull(chunkCollisionSettings, "chunkCollisionSettings"); + addIfNonDefault(holder, + ChunkCollisionSettingsComponent.getComponentType(), + component, + component.isDefault()); + } + public static void addSpaceSettingsComponents(@Nonnull Holder holder, @Nonnull SolverSettingsComponent solverSettings, @Nonnull VisualSyncSettingsComponent visualSyncSettings, @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, @Nonnull CollisionLodSettingsComponent collisionLodSettings, @Nonnull ExtensionSettingsComponent extensionSettings) { - Objects.requireNonNull(holder, "holder") - .addComponent(SolverSettingsComponent.getComponentType(), - Objects.requireNonNull(solverSettings, "solverSettings").clone()); - holder.addComponent(VisualSyncSettingsComponent.getComponentType(), - Objects.requireNonNull(visualSyncSettings, "visualSyncSettings").clone()); - holder.addComponent(VisualMaterializationSettingsComponent.getComponentType(), - Objects.requireNonNull(visualMaterializationSettings, - "visualMaterializationSettings").clone()); - holder.addComponent(CollisionLodSettingsComponent.getComponentType(), - Objects.requireNonNull(collisionLodSettings, "collisionLodSettings").clone()); - holder.addComponent(ExtensionSettingsComponent.getComponentType(), - Objects.requireNonNull(extensionSettings, "extensionSettings").clone()); + SolverSettingsComponent solver = Objects.requireNonNull(solverSettings, + "solverSettings"); + addIfNonDefault(holder, + SolverSettingsComponent.getComponentType(), + solver, + solver.isDefault()); + VisualSyncSettingsComponent visualSync = Objects.requireNonNull(visualSyncSettings, + "visualSyncSettings"); + addIfNonDefault(holder, + VisualSyncSettingsComponent.getComponentType(), + visualSync, + visualSync.isDefault()); + VisualMaterializationSettingsComponent visualMaterialization = Objects.requireNonNull( + visualMaterializationSettings, + "visualMaterializationSettings"); + addIfNonDefault(holder, + VisualMaterializationSettingsComponent.getComponentType(), + visualMaterialization, + visualMaterialization.isDefault()); + CollisionLodSettingsComponent collisionLod = Objects.requireNonNull(collisionLodSettings, + "collisionLodSettings"); + addIfNonDefault(holder, + CollisionLodSettingsComponent.getComponentType(), + collisionLod, + collisionLod.isDefault()); + ExtensionSettingsComponent extension = Objects.requireNonNull(extensionSettings, + "extensionSettings"); + addIfNonDefault(holder, + ExtensionSettingsComponent.getComponentType(), + extension, + extension.isDefault()); } public static void addBodyComponents(@Nonnull Holder holder, @@ -203,9 +233,7 @@ public static void putSpaceComponents(@Nonnull Store store, checkedStore.putComponent(ref, SpaceComponent.getComponentType(), Objects.requireNonNull(space, "space").clone()); - checkedStore.putComponent(ref, - ChunkCollisionSettingsComponent.getComponentType(), - Objects.requireNonNull(chunkCollisionSettings, "chunkCollisionSettings").clone()); + putChunkCollisionSettingsComponent(checkedStore, ref, chunkCollisionSettings); putSpaceSettingsComponents(checkedStore, ref, solverSettings, @@ -215,6 +243,21 @@ public static void putSpaceComponents(@Nonnull Store store, extensionSettings); } + public static void putChunkCollisionSettingsComponent(@Nonnull Store store, + @Nonnull Ref ref, + @Nonnull ChunkCollisionSettingsComponent chunkCollisionSettings) { + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsThreading.requireWorldThread(checkedStore, + "put PhysicsStore chunk collision settings component"); + ChunkCollisionSettingsComponent component = + Objects.requireNonNull(chunkCollisionSettings, "chunkCollisionSettings"); + putOrRemoveDefault(checkedStore, + Objects.requireNonNull(ref, "ref"), + ChunkCollisionSettingsComponent.getComponentType(), + component, + component.isDefault()); + } + public static void putSpaceSettingsComponents(@Nonnull Store store, @Nonnull Ref ref, @Nonnull SolverSettingsComponent solverSettings, @@ -226,22 +269,42 @@ public static void putSpaceSettingsComponents(@Nonnull Store store PhysicsThreading.requireWorldThread(checkedStore, "put PhysicsStore space settings components"); Objects.requireNonNull(ref, "ref"); - checkedStore.putComponent(ref, + SolverSettingsComponent solver = Objects.requireNonNull(solverSettings, + "solverSettings"); + putOrRemoveDefault(checkedStore, + ref, SolverSettingsComponent.getComponentType(), - Objects.requireNonNull(solverSettings, "solverSettings").clone()); - checkedStore.putComponent(ref, + solver, + solver.isDefault()); + VisualSyncSettingsComponent visualSync = Objects.requireNonNull(visualSyncSettings, + "visualSyncSettings"); + putOrRemoveDefault(checkedStore, + ref, VisualSyncSettingsComponent.getComponentType(), - Objects.requireNonNull(visualSyncSettings, "visualSyncSettings").clone()); - checkedStore.putComponent(ref, + visualSync, + visualSync.isDefault()); + VisualMaterializationSettingsComponent visualMaterialization = Objects.requireNonNull( + visualMaterializationSettings, + "visualMaterializationSettings"); + putOrRemoveDefault(checkedStore, + ref, VisualMaterializationSettingsComponent.getComponentType(), - Objects.requireNonNull(visualMaterializationSettings, - "visualMaterializationSettings").clone()); - checkedStore.putComponent(ref, + visualMaterialization, + visualMaterialization.isDefault()); + CollisionLodSettingsComponent collisionLod = Objects.requireNonNull(collisionLodSettings, + "collisionLodSettings"); + putOrRemoveDefault(checkedStore, + ref, CollisionLodSettingsComponent.getComponentType(), - Objects.requireNonNull(collisionLodSettings, "collisionLodSettings").clone()); - checkedStore.putComponent(ref, + collisionLod, + collisionLod.isDefault()); + ExtensionSettingsComponent extension = Objects.requireNonNull(extensionSettings, + "extensionSettings"); + putOrRemoveDefault(checkedStore, + ref, ExtensionSettingsComponent.getComponentType(), - Objects.requireNonNull(extensionSettings, "extensionSettings").clone()); + extension, + extension.isDefault()); } public static void putBodyComponents(@Nonnull Store store, @@ -292,4 +355,33 @@ public static void putJointComponent(@Nonnull Store store, Objects.requireNonNull(joint, "joint").clone()); } + private static > void addIfNonDefault( + @Nonnull Holder holder, + @Nonnull ComponentType componentType, + @Nonnull T component, + boolean defaultValue) { + if (!defaultValue) { + Objects.requireNonNull(holder, "holder").addComponent(componentType, copy(component)); + } + } + + private static > void putOrRemoveDefault( + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull ComponentType componentType, + @Nonnull T component, + boolean defaultValue) { + if (defaultValue) { + store.removeComponentIfExists(ref, componentType); + } else { + store.putComponent(ref, componentType, copy(component)); + } + } + + @Nonnull + @SuppressWarnings("unchecked") + private static > T copy(@Nonnull T component) { + return (T) component.clone(); + } + } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index 625c66a7..b71e742c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -19,6 +19,12 @@ import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Collection; import java.util.List; @@ -171,6 +177,167 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, return settings; } + @Nullable + public static PhysicsChunkTerrainSettings chunkTerrainSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + return ref != null ? chunkTerrainSettings(store, ref) : null; + } + + @Nullable + public static PhysicsChunkTerrainSettings chunkTerrainSettings( + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore chunk terrain settings"); + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (!isSpaceRef(checkedStore, checkedRef)) { + return null; + } + PhysicsChunkTerrainSettings settings = new PhysicsChunkTerrainSettings(); + ChunkCollisionSettingsComponent component = checkedStore.getComponent(checkedRef, + ChunkCollisionSettingsComponent.getComponentType()); + if (component != null) { + component.copyTo(settings); + } + return settings; + } + + @Nullable + public static PhysicsSolverSettings solverSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + return ref != null ? solverSettings(store, ref) : null; + } + + @Nullable + public static PhysicsSolverSettings solverSettings(@Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore solver settings"); + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (!isSpaceRef(checkedStore, checkedRef)) { + return null; + } + PhysicsSolverSettings settings = new PhysicsSolverSettings(); + SolverSettingsComponent component = checkedStore.getComponent(checkedRef, + SolverSettingsComponent.getComponentType()); + if (component != null) { + component.copyTo(settings); + } + return settings; + } + + @Nullable + public static PhysicsVisualSyncSettings visualSyncSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + return ref != null ? visualSyncSettings(store, ref) : null; + } + + @Nullable + public static PhysicsVisualSyncSettings visualSyncSettings( + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore visual sync settings"); + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (!isSpaceRef(checkedStore, checkedRef)) { + return null; + } + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + VisualSyncSettingsComponent component = checkedStore.getComponent(checkedRef, + VisualSyncSettingsComponent.getComponentType()); + if (component != null) { + component.copyTo(settings); + } + return settings; + } + + @Nullable + public static PhysicsVisualMaterializationSettings visualMaterializationSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + return ref != null ? visualMaterializationSettings(store, ref) : null; + } + + @Nullable + public static PhysicsVisualMaterializationSettings visualMaterializationSettings( + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore visual materialization settings"); + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (!isSpaceRef(checkedStore, checkedRef)) { + return null; + } + PhysicsVisualMaterializationSettings settings = + new PhysicsVisualMaterializationSettings(); + VisualMaterializationSettingsComponent component = checkedStore.getComponent(checkedRef, + VisualMaterializationSettingsComponent.getComponentType()); + if (component != null) { + component.copyTo(settings); + } + return settings; + } + + @Nullable + public static PhysicsCollisionLodSettings collisionLodSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + return ref != null ? collisionLodSettings(store, ref) : null; + } + + @Nullable + public static PhysicsCollisionLodSettings collisionLodSettings( + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore collision LOD settings"); + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (!isSpaceRef(checkedStore, checkedRef)) { + return null; + } + PhysicsCollisionLodSettings settings = new PhysicsCollisionLodSettings(); + CollisionLodSettingsComponent component = checkedStore.getComponent(checkedRef, + CollisionLodSettingsComponent.getComponentType()); + if (component != null) { + component.copyTo(settings); + } + return settings; + } + + @Nullable + public static PhysicsExtensionSettings extensionSettings( + @Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref ref = resolveRef(store, spaceId); + return ref != null ? extensionSettings(store, ref) : null; + } + + @Nullable + public static PhysicsExtensionSettings extensionSettings( + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsStore extension settings"); + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (!isSpaceRef(checkedStore, checkedRef)) { + return null; + } + PhysicsExtensionSettings settings = new PhysicsExtensionSettings(); + ExtensionSettingsComponent component = checkedStore.getComponent(checkedRef, + ExtensionSettingsComponent.getComponentType()); + if (component != null) { + component.copyTo(settings); + } + return settings; + } + public static void putSettings(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { @@ -191,6 +358,126 @@ public static void putSettings(@Nonnull Store store, Objects.requireNonNull(settings, "settings")); } + public static void putChunkTerrainSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsChunkTerrainSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore chunk terrain settings"); + PhysicsStoreSpaceMutations.putChunkCollisionSettings(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putChunkTerrainSettings(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull PhysicsChunkTerrainSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore chunk terrain settings"); + PhysicsStoreSpaceMutations.putChunkCollisionSettings(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putSolverSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsSolverSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore solver settings"); + PhysicsStoreSpaceMutations.putSolverSettings(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putSolverSettings(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull PhysicsSolverSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore solver settings"); + PhysicsStoreSpaceMutations.putSolverSettings(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putVisualSyncSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsVisualSyncSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore visual sync settings"); + PhysicsStoreSpaceMutations.putVisualSyncSettings(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putVisualSyncSettings(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull PhysicsVisualSyncSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore visual sync settings"); + PhysicsStoreSpaceMutations.putVisualSyncSettings(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putVisualMaterializationSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsVisualMaterializationSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore visual materialization settings"); + PhysicsStoreSpaceMutations.putVisualMaterializationSettings(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putVisualMaterializationSettings(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull PhysicsVisualMaterializationSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore visual materialization settings"); + PhysicsStoreSpaceMutations.putVisualMaterializationSettings(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putCollisionLodSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsCollisionLodSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore collision LOD settings"); + PhysicsStoreSpaceMutations.putCollisionLodSettings(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putCollisionLodSettings(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull PhysicsCollisionLodSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore collision LOD settings"); + PhysicsStoreSpaceMutations.putCollisionLodSettings(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putExtensionSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PhysicsExtensionSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore extension settings"); + PhysicsStoreSpaceMutations.putExtensionSettings(checkedStore, + Objects.requireNonNull(spaceId, "spaceId"), + Objects.requireNonNull(settings, "settings")); + } + + public static void putExtensionSettings(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull PhysicsExtensionSettings settings) { + Store checkedStore = requireWorldThread(store, + "update PhysicsStore extension settings"); + PhysicsStoreSpaceMutations.putExtensionSettings(checkedStore, + Objects.requireNonNull(spaceRef, "spaceRef"), + Objects.requireNonNull(settings, "settings")); + } + @Nullable public static > T getSpaceComponent( @Nonnull Store store, @@ -299,6 +586,13 @@ private static Store requireWorldThread(@Nonnull Store store, + @Nonnull Ref ref) { + return ref.getStore() == store + && ref.isValid() + && store.getComponent(ref, SpaceComponent.getComponentType()) != null; + } + @Nonnull private static Ref requireSpaceRef(@Nonnull Store store, @Nonnull SpaceId spaceId) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 187b29f2..f818bfd7 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -303,6 +303,7 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store Date: Fri, 19 Jun 2026 08:50:13 +0200 Subject: [PATCH 427/534] test(core): cover sparse space settings components Signed-off-by: Blovien --- .../PhysicsSpacesSettingsComponentTest.java | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java new file mode 100644 index 00000000..021807df --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java @@ -0,0 +1,145 @@ +package dev.hytalemodding.impulse.core.plugin.physicsstore; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import com.hypixel.hytale.component.EmptyResourceStorage; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsSpacesSettingsComponentTest { + + @Test + void defaultSpaceSettingsHolderDoesNotMaterializeDefaultComponents() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physics-space-default-holder-test")), + EmptyResourceStorage.get()); + try { + Holder holder = PhysicsEntities.spaceHolder(store, + uuid(1), + new SpaceComponent(new BackendId("test:settings-holder"), + new Vector3f(0.0f, -9.81f, 0.0f)), + new ChunkCollisionSettingsComponent(), + new SolverSettingsComponent(), + new VisualSyncSettingsComponent(), + new VisualMaterializationSettingsComponent(), + new CollisionLodSettingsComponent(), + new ExtensionSettingsComponent()); + Ref ref = store.addEntity(holder, AddReason.SPAWN); + assertNotNull(ref); + + assertNull(store.getComponent(ref, ChunkCollisionSettingsComponent.getComponentType())); + assertNull(store.getComponent(ref, SolverSettingsComponent.getComponentType())); + assertNull(store.getComponent(ref, VisualSyncSettingsComponent.getComponentType())); + assertNull(store.getComponent(ref, + VisualMaterializationSettingsComponent.getComponentType())); + assertNull(store.getComponent(ref, CollisionLodSettingsComponent.getComponentType())); + assertNull(store.getComponent(ref, ExtensionSettingsComponent.getComponentType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void domainSettingWritesAddAndRemoveOnlyTheirOwnComponents() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physics-space-domain-settings-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + Ref spaceRef = PhysicsSpaces.create(store, + uuid(2), + new SpaceId(2001), + new BackendId("test:settings-domain")); + + PhysicsSolverSettings solverSettings = PhysicsSpaces.solverSettings(store, spaceRef); + assertNotNull(solverSettings); + assertEquals(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS, + solverSettings.getSolverIterations()); + assertNull(store.getComponent(spaceRef, SolverSettingsComponent.getComponentType())); + + solverSettings.setSolverIterations(6); + PhysicsSpaces.putSolverSettings(store, spaceRef, solverSettings); + assertNotNull(store.getComponent(spaceRef, + SolverSettingsComponent.getComponentType())); + assertNull(store.getComponent(spaceRef, + CollisionLodSettingsComponent.getComponentType())); + + PhysicsCollisionLodSettings collisionLodSettings = + PhysicsSpaces.collisionLodSettings(store, spaceRef); + assertNotNull(collisionLodSettings); + collisionLodSettings.setCollisionLodEnabled(true); + PhysicsSpaces.putCollisionLodSettings(store, spaceRef, collisionLodSettings); + assertNotNull(store.getComponent(spaceRef, + CollisionLodSettingsComponent.getComponentType())); + assertNotNull(store.getComponent(spaceRef, + SolverSettingsComponent.getComponentType())); + + solverSettings.setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); + PhysicsSpaces.putSolverSettings(store, spaceRef, solverSettings); + assertNull(store.getComponent(spaceRef, SolverSettingsComponent.getComponentType())); + assertNotNull(store.getComponent(spaceRef, + CollisionLodSettingsComponent.getComponentType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static UUID uuid(int lowBits) { + return new UUID(0L, lowBits); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } +} From 74e32bc4e9aa07d4424266755dff259f054b81a8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:01:32 +0200 Subject: [PATCH 428/534] refactor(core): move visual settings components to physicsentity Signed-off-by: Blovien --- .../impulse/core/internal/persistence/PersistentSpaceDto.java | 4 ++-- .../internal/physicsstore/PhysicsStoreSpaceMutations.java | 4 ++-- .../internal/registration/PhysicsComponentTypeRegistry.java | 4 ++-- .../core/internal/resources/PhysicsWorldRuntimeResource.java | 4 ++-- .../core/internal/systems/PersistenceCaptureSystem.java | 4 ++-- .../core/internal/systems/PersistenceHydrationSystem.java | 4 ++-- .../impulse/core/plugin/components/PhysicsComponentTypes.java | 2 ++ .../components/VisualMaterializationSettingsComponent.java | 3 ++- .../components/VisualSyncSettingsComponent.java | 3 ++- .../plugin/modules/physicsentity/components/package-info.java | 2 +- .../impulse/core/plugin/physicsstore/PhysicsEntities.java | 4 ++-- .../impulse/core/plugin/physicsstore/PhysicsSpaces.java | 4 ++-- 12 files changed, 23 insertions(+), 19 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{ => modules/physicsentity}/components/VisualMaterializationSettingsComponent.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{ => modules/physicsentity}/components/VisualSyncSettingsComponent.java (98%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index 752b7f33..3762981f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -13,8 +13,8 @@ import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 42bd210b..4821c4e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -19,8 +19,8 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java index 738d4325..b9120d12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 9fe9d4cd..d896121c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -46,8 +46,8 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java index f184d2bb..59ff5679 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java @@ -34,8 +34,8 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 73d0f360..f08f8282 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -33,8 +33,8 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index c2b1f851..d7c1e013 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -5,6 +5,8 @@ import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import javax.annotation.Nonnull; /** diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualMaterializationSettingsComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualMaterializationSettingsComponent.java index e97a02e5..ac245b64 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualMaterializationSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.components; +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -6,6 +6,7 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java index 75a806f0..7445586a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.components; +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; @@ -7,6 +7,7 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java index aefad673..81674340 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java @@ -1,4 +1,4 @@ /** - * EntityStore components that bind Hytale entities to authoritative PhysicsStore body entities. + * PhysicsEntity components for EntityStore projections and PhysicsStore visual policy settings. */ package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java index d15696e2..00a5b21b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java @@ -20,8 +20,8 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import java.util.Objects; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index b71e742c..ab89a410 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -15,8 +15,8 @@ import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; From 85a36502daa9db3bda192de7509f389021deaa0c Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:01:44 +0200 Subject: [PATCH 429/534] test(core): cover physicsentity visual settings components Signed-off-by: Blovien --- .../PersistentSpaceDtoSettingsTest.java | 4 +- .../PhysicsSpacesSettingsComponentTest.java | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index 50ed73bd..44bb6d86 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -7,8 +7,8 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java index 021807df..9ac46cca 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java @@ -21,11 +21,13 @@ import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -114,11 +116,56 @@ void domainSettingWritesAddAndRemoveOnlyTheirOwnComponents() { assertNotNull(store.getComponent(spaceRef, SolverSettingsComponent.getComponentType())); + PhysicsVisualSyncSettings visualSyncSettings = + PhysicsSpaces.visualSyncSettings(store, spaceRef); + assertNotNull(visualSyncSettings); + assertNull(store.getComponent(spaceRef, + VisualSyncSettingsComponent.getComponentType())); + visualSyncSettings.setVisualMidSyncIntervalTicks(2); + PhysicsSpaces.putVisualSyncSettings(store, spaceRef, visualSyncSettings); + assertNotNull(store.getComponent(spaceRef, + VisualSyncSettingsComponent.getComponentType())); + assertNotNull(store.getComponent(spaceRef, + CollisionLodSettingsComponent.getComponentType())); + + PhysicsVisualMaterializationSettings visualMaterializationSettings = + PhysicsSpaces.visualMaterializationSettings(store, spaceRef); + assertNotNull(visualMaterializationSettings); + assertNull(store.getComponent(spaceRef, + VisualMaterializationSettingsComponent.getComponentType())); + visualMaterializationSettings.setDetachedVisualMaterializationEnabled(true); + PhysicsSpaces.putVisualMaterializationSettings(store, + spaceRef, + visualMaterializationSettings); + assertNotNull(store.getComponent(spaceRef, + VisualMaterializationSettingsComponent.getComponentType())); + assertNotNull(store.getComponent(spaceRef, + VisualSyncSettingsComponent.getComponentType())); + solverSettings.setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); PhysicsSpaces.putSolverSettings(store, spaceRef, solverSettings); assertNull(store.getComponent(spaceRef, SolverSettingsComponent.getComponentType())); assertNotNull(store.getComponent(spaceRef, CollisionLodSettingsComponent.getComponentType())); + + visualSyncSettings.setVisualMidSyncIntervalTicks( + PhysicsVisualSyncSettings.DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS); + PhysicsSpaces.putVisualSyncSettings(store, spaceRef, visualSyncSettings); + assertNull(store.getComponent(spaceRef, + VisualSyncSettingsComponent.getComponentType())); + assertNotNull(store.getComponent(spaceRef, + VisualMaterializationSettingsComponent.getComponentType())); + + visualMaterializationSettings.setDetachedVisualMaterializationEnabled( + PhysicsVisualMaterializationSettings + .DEFAULT_DETACHED_VISUAL_MATERIALIZATION_ENABLED); + PhysicsSpaces.putVisualMaterializationSettings(store, + spaceRef, + visualMaterializationSettings); + assertNull(store.getComponent(spaceRef, + VisualMaterializationSettingsComponent.getComponentType())); + assertNotNull(store.getComponent(spaceRef, + CollisionLodSettingsComponent.getComponentType())); } finally { registry.removeStore(store); registry.shutdown(); From 68271a248b22e637f6019869b3c343a57909cb37 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:10:31 +0200 Subject: [PATCH 430/534] refactor(core): rename physicschunk collision settings Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 4 +- .../core/internal/commands/SpaceCommand.java | 4 +- .../crucible/ImpulseApiCrucibleTests.java | 18 +-- ...tachedStreamingBenchmarkCrucibleTests.java | 6 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 2 +- .../PhysicsChunkBuildOptions.java | 6 +- .../commands/PhysicsChunkSettingsCommand.java | 72 +++++----- .../PhysicsChunkTerrainProducerSystem.java | 5 +- .../PersistentPhysicsStorePreflight.java | 26 ++-- .../persistence/PersistentSpaceDto.java | 50 +++---- .../PhysicsStoreSpaceMutations.java | 10 +- .../resources/PhysicsSpaceRuntime.java | 6 +- .../PhysicsChunkSettingsIndexSystem.java | 2 +- .../ChunkCollisionSettingsComponent.java | 54 ++++---- .../PhysicsChunkCollisionSettings.java | 124 ++++++++++++++++++ .../settings/PhysicsChunkTerrainSettings.java | 124 ------------------ .../plugin/physicsstore/PhysicsSpaces.java | 24 ++-- .../plugin/settings/PhysicsSpaceSettings.java | 28 ++-- .../commands/stress/StressBodiesCommand.java | 32 ++--- 19 files changed, 300 insertions(+), 297 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index cbacfe44..8f23c00c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -45,8 +45,8 @@ * *

        This removes Impulse-owned visual entities, detaches external physics attachments, * clears runtime bodies, joints, and current PhysicsChunk terrain cache bodies. Explicit - * physics spaces are kept, including their PhysicsChunk terrain settings. Spaces with streaming - * PhysicsChunk terrain enabled may build fresh backend terrain bodies again on the next + * physics spaces are kept, including their PhysicsChunk collision settings. Spaces with streaming + * PhysicsChunk collision enabled may build fresh backend terrain bodies again on the next * streaming tick.

        * *

        When a radius is provided, cleanup is intentionally narrower: it selects diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 8a694af0..d5399ae9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -77,7 +77,7 @@ protected void execute(@Nonnull CommandContext context, PhysicsSpaceSettings settings = physicsChunkMode == PhysicsChunkTerrainMode.STREAMING ? PhysicsSpaceSettings.streamingPhysicsChunk() : PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkTerrainSettings().setTerrainMode(physicsChunkMode); + settings.getPhysicsChunkCollisionSettings().setMode(physicsChunkMode); Store physicsStore = PhysicsThreading.store(world); try { @@ -120,7 +120,7 @@ private static void sendSpaces(@Nonnull CommandContext context, PhysicsSpaceSettings settings = PhysicsSpaces.settings(physicsStore, summary.spaceId()); PhysicsChunkTerrainMode physicsChunkMode = settings != null - ? settings.getPhysicsChunkTerrainSettings().getTerrainMode() + ? settings.getPhysicsChunkCollisionSettings().getMode() : PhysicsChunkTerrainMode.NONE; return new SpaceListEntry(summary.spaceId(), summary.backendId().value(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 18bc4941..7dbb9bbf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -206,7 +206,7 @@ private static CompletionStage createdExplicitSpaceLifecycleWorks( PhysicsSpaceSettings spaceSettings = PhysicsSpaces.settings(store, spaceId); boolean registered = PhysicsSpaces.hasSpace(store, spaceId) && spaceSettings != null - && spaceSettings.getPhysicsChunkTerrainSettings().getTerrainMode() + && spaceSettings.getPhysicsChunkCollisionSettings().getMode() == PhysicsChunkTerrainMode.STREAMING; PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); return registered && !PhysicsSpaces.hasSpace(store, spaceId); @@ -334,10 +334,10 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte if (copy == null) { return false; } - return copy.getPhysicsChunkTerrainSettings().getTerrainMode() == PhysicsChunkTerrainMode.STREAMING - && copy.getPhysicsChunkTerrainSettings().getTerrainRadius() == 9 - && copy.getPhysicsChunkTerrainSettings().getBodyTerrainRadius() == 5 - && copy.getPhysicsChunkTerrainSettings().getTerrainTtlTicks() == 77 + return copy.getPhysicsChunkCollisionSettings().getMode() == PhysicsChunkTerrainMode.STREAMING + && copy.getPhysicsChunkCollisionSettings().getRadius() == 9 + && copy.getPhysicsChunkCollisionSettings().getBodyRadius() == 5 + && copy.getPhysicsChunkCollisionSettings().getTtlTicks() == 77 && copy.getVisualSyncSettings().getVisualFullSyncRadius() == 48 && copy.getVisualSyncSettings().getVisualMaxSyncRadius() == 96 && !copy.getVisualSyncSettings().isVisualFarSyncCutoffEnabled() @@ -373,10 +373,10 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte @Nonnull private static PhysicsSpaceSettings populatedSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.STREAMING); - settings.getPhysicsChunkTerrainSettings().setTerrainRadius(9); - settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(5); - settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(77); + settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.STREAMING); + settings.getPhysicsChunkCollisionSettings().setRadius(9); + settings.getPhysicsChunkCollisionSettings().setBodyRadius(5); + settings.getPhysicsChunkCollisionSettings().setTtlTicks(77); settings.getVisualSyncSettings().setVisualMaxSyncRadius(96); settings.getVisualSyncSettings().setVisualFullSyncRadius(48); settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(false); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 09877c19..0477a40c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -245,8 +245,8 @@ private CompletionStage startStageWhenReady(int count, int attempt int retained = retainChunks(chunks); configureMissingSectionDiagnostics(chunks); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.STREAMING); - settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(BODY_STREAMING_RADIUS); + settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.STREAMING); + settings.getPhysicsChunkCollisionSettings().setBodyRadius(BODY_STREAMING_RADIUS); settings.getSolverSettings().setSolverIterations(4); settings.getSolverSettings().setStabilizationIterations(1); settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, @@ -373,7 +373,7 @@ private PrewarmStats prewarmPhysicsChunkTerrain(@Nonnull SpaceId spaceId, int co PhysicsChunkCollisionMutationQueueResource queue = physicsStore.getResource( PhysicsChunkCollisionMutationQueueResource.getResourceType()); PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( - physics.getSpaceSettings(spaceId).getPhysicsChunkTerrainSettings()); + physics.getSpaceSettings(spaceId).getPhysicsChunkCollisionSettings()); PhysicsChunkTerrainPrewarmStats stats = terrainStreaming.ensureAround(world, spaceUuid, queue, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 34a18f75..829b596b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -228,7 +228,7 @@ private CompletionStage startCase(@Nonnull MatrixCase matrixCase) { physics.clearSyntheticVisualInterests(); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.NONE); + settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.NONE); settings.getSolverSettings().setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); settings.getSolverSettings().setStabilizationIterations( PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java index c9f98735..1a721b9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildOptions.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -10,14 +10,14 @@ public record PhysicsChunkBuildOptions(@Nonnull ChunkCollisionMode chunkCollisionMode) { public static final PhysicsChunkBuildOptions DEFAULT = - fromNativeVoxelCollisionEnabled(PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED); + fromNativeVoxelCollisionEnabled(PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED); public PhysicsChunkBuildOptions { Objects.requireNonNull(chunkCollisionMode, "chunkCollisionMode"); } @Nonnull - public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkTerrainSettings settings) { + public static PhysicsChunkBuildOptions fromSettings(@Nonnull PhysicsChunkCollisionSettings settings) { return fromNativeVoxelCollisionEnabled(settings.isNativeVoxelCollisionEnabled()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index 704af228..c4189014 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -31,28 +31,28 @@ public class PhysicsChunkSettingsCommand extends AbstractAsyncPlayerCommand { private final OptionalArg playerRadiusArg = this.withOptionalArg( "playerRadius", "Block radius streamed around players (1-" - + PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS + + PhysicsChunkCollisionSettings.MAX_RADIUS + ")", ArgTypes.INTEGER); private final OptionalArg bodyRadiusArg = this.withOptionalArg( "bodyRadius", "Block radius streamed around awake dynamic bodies (1-" - + PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS + + PhysicsChunkCollisionSettings.MAX_BODY_RADIUS + ")", ArgTypes.INTEGER); private final OptionalArg ttlArg = this.withOptionalArg( "ttl", "Ticks before unused streamed sections are pruned (1-" - + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS + + PhysicsChunkCollisionSettings.MAX_TTL_TICKS + ")", ArgTypes.INTEGER); private final OptionalArg chunkBoundaryArg = this.withOptionalArg( "chunkBoundary", "Entity body chunk-boundary mode: pause or load", ArgTypes.STRING); - private final OptionalArg terrainArg = this.withOptionalArg( - "terrain", - "PhysicsChunk collision bodies: boxes or native_voxels", + private final OptionalArg collisionShapeArg = this.withOptionalArg( + "collisionShape", + "PhysicsChunk collision shape: boxes or native_voxels", ArgTypes.STRING); private final OptionalArg spaceArg = this.withOptionalArg( "space", @@ -60,7 +60,7 @@ public class PhysicsChunkSettingsCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); public PhysicsChunkSettingsCommand() { - super("settings", "Get or set PhysicsChunk streaming settings for a physics space"); + super("settings", "Get or set PhysicsChunk collision settings for a physics space"); } @Nonnull @@ -77,7 +77,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsChunkTerrainSettings settings = PhysicsSpaces.chunkTerrainSettings(physicsStore, + PhysicsChunkCollisionSettings settings = PhysicsSpaces.chunkCollisionSettings(physicsStore, selection.spaceRef()); if (settings == null) { ctx.sender().sendMessage(Message.raw("Physics space id=" + selection.spaceId().value() @@ -89,7 +89,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsChunkTerrainMode mode = settings.getTerrainMode(); + PhysicsChunkTerrainMode mode = settings.getMode(); if (modeArg.provided(ctx)) { mode = parseMode(modeArg.get(ctx)); if (mode == null) { @@ -108,10 +108,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } boolean nativeVoxelCollisionEnabled = settings.isNativeVoxelCollisionEnabled(); - if (terrainArg.provided(ctx)) { - Boolean parsedNativeVoxelCollision = parseTerrain(terrainArg.get(ctx)); + if (collisionShapeArg.provided(ctx)) { + Boolean parsedNativeVoxelCollision = + parseCollisionShape(collisionShapeArg.get(ctx)); if (parsedNativeVoxelCollision == null) { - ctx.sender().sendMessage(Message.raw("terrain must be boxes or native_voxels.")); + ctx.sender().sendMessage(Message.raw( + "collisionShape must be boxes or native_voxels.")); return CompletableFuture.completedFuture(null); } nativeVoxelCollisionEnabled = parsedNativeVoxelCollision; @@ -119,31 +121,31 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int playerRadius = playerRadiusArg.provided(ctx) ? playerRadiusArg.get(ctx) - : settings.getTerrainRadius(); + : settings.getRadius(); int bodyRadius = bodyRadiusArg.provided(ctx) ? bodyRadiusArg.get(ctx) - : settings.getBodyTerrainRadius(); - int ttl = ttlArg.provided(ctx) ? ttlArg.get(ctx) : settings.getTerrainTtlTicks(); - if (outOfRange(playerRadius, PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS) - || outOfRange(bodyRadius, PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS) - || outOfRange(ttl, PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS)) { + : settings.getBodyRadius(); + int ttl = ttlArg.provided(ctx) ? ttlArg.get(ctx) : settings.getTtlTicks(); + if (outOfRange(playerRadius, PhysicsChunkCollisionSettings.MAX_RADIUS) + || outOfRange(bodyRadius, PhysicsChunkCollisionSettings.MAX_BODY_RADIUS) + || outOfRange(ttl, PhysicsChunkCollisionSettings.MAX_TTL_TICKS)) { ctx.sender().sendMessage(Message.raw( - "playerRadius must be 1-" + PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS + "playerRadius must be 1-" + PhysicsChunkCollisionSettings.MAX_RADIUS + ", bodyRadius must be 1-" - + PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS + + PhysicsChunkCollisionSettings.MAX_BODY_RADIUS + ", and ttl must be 1-" - + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS + + PhysicsChunkCollisionSettings.MAX_TTL_TICKS + ".")); return CompletableFuture.completedFuture(null); } - settings.setTerrainMode(mode); + settings.setMode(mode); settings.setEntityChunkBoundaryMode(chunkBoundaryMode); settings.setNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled); - settings.setTerrainRadius(playerRadius); - settings.setBodyTerrainRadius(bodyRadius); - settings.setTerrainTtlTicks(ttl); - PhysicsSpaces.putChunkTerrainSettings(physicsStore, selection.spaceRef(), settings); + settings.setRadius(playerRadius); + settings.setBodyRadius(bodyRadius); + settings.setTtlTicks(ttl); + PhysicsSpaces.putChunkCollisionSettings(physicsStore, selection.spaceRef(), settings); sendSummary(ctx, selection.spaceId(), settings); return CompletableFuture.completedFuture(null); } @@ -154,7 +156,7 @@ private boolean anyArgProvided(@Nonnull CommandContext ctx) { || bodyRadiusArg.provided(ctx) || ttlArg.provided(ctx) || chunkBoundaryArg.provided(ctx) - || terrainArg.provided(ctx); + || collisionShapeArg.provided(ctx); } private static boolean outOfRange(int value, int maxValue) { @@ -163,16 +165,16 @@ private static boolean outOfRange(int value, int maxValue) { private static void sendSummary(@Nonnull CommandContext ctx, @Nonnull SpaceId spaceId, - @Nonnull PhysicsChunkTerrainSettings settings) { + @Nonnull PhysicsChunkCollisionSettings settings) { ctx.sender().sendMessage(Message.raw("Impulse PhysicsChunk settings for space " + spaceId.value() - + ": mode=" + settings.getTerrainMode().name().toLowerCase(Locale.ROOT) - + " playerRadius=" + settings.getTerrainRadius() - + " bodyRadius=" + settings.getBodyTerrainRadius() - + " ttl=" + settings.getTerrainTtlTicks() + + ": mode=" + settings.getMode().name().toLowerCase(Locale.ROOT) + + " playerRadius=" + settings.getRadius() + + " bodyRadius=" + settings.getBodyRadius() + + " ttl=" + settings.getTtlTicks() + " chunkBoundary=" + settings.getEntityChunkBoundaryMode().name().toLowerCase(Locale.ROOT) - + " terrain=" + + " collisionShape=" + (settings.isNativeVoxelCollisionEnabled() ? "native_voxels" : "boxes"))); @@ -198,7 +200,7 @@ private static EntityChunkBoundaryMode parseChunkBoundaryMode(@Nonnull String va } @Nullable - private static Boolean parseTerrain(@Nonnull String value) { + private static Boolean parseCollisionShape(@Nonnull String value) { return switch (value.toLowerCase(Locale.ROOT)) { case "boxes", "box", "merged", "merged_boxes" -> Boolean.FALSE; case "native", "native_voxels", "voxels", "voxel" -> Boolean.TRUE; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java index fd31895a..ac780c9b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java @@ -82,14 +82,15 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { "produce PhysicsStore PhysicsChunk chunk collision mutations"); PhysicsChunkCollisionMutationQueueResource queue = physics.getResource( PhysicsChunkCollisionMutationQueueResource.getResourceType()); - PhysicsChunkSettingsIndexResource terrainSettingsIndex = physics.getResource( + PhysicsChunkSettingsIndexResource chunkCollisionSettingsIndex = physics.getResource( PhysicsChunkSettingsIndexResource.getResourceType()); PhysicsSnapshotResource snapshotResource = physics.getResource( PhysicsSnapshotResource.getResourceType()); PhysicsChunkTerrainStreamingResource streaming = store.getResource( PhysicsChunkTerrainStreamingResource.getResourceType()); - List spaces = terrainSettingsIndex.streamingSpaces(); + List spaces = + chunkCollisionSettingsIndex.streamingSpaces(); if (spaces.isEmpty()) { streaming.retainSpaces(Set.of(), queue); return; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index 9dd7827d..b6670631 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -2,7 +2,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -64,23 +64,23 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, if (!PhysicsStorePersistenceValidation.isFinite(space.getGravity())) { errors.add("PhysicsStore space " + uuid + " has non-finite gravity"); } - if (space.getTerrainRadius() < 1 - || space.getTerrainRadius() - > PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS) { + if (space.getRadius() < 1 + || space.getRadius() + > PhysicsChunkCollisionSettings.MAX_RADIUS) { errors.add("PhysicsStore space " + uuid - + " has invalid PhysicsChunk terrain radius"); + + " has invalid PhysicsChunk collision radius"); } - if (space.getBodyTerrainRadius() < 1 - || space.getBodyTerrainRadius() - > PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS) { + if (space.getBodyRadius() < 1 + || space.getBodyRadius() + > PhysicsChunkCollisionSettings.MAX_BODY_RADIUS) { errors.add("PhysicsStore space " + uuid - + " has invalid PhysicsChunk terrain body radius"); + + " has invalid PhysicsChunk collision body radius"); } - if (space.getTerrainTtlTicks() < 1 - || space.getTerrainTtlTicks() - > PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS) { + if (space.getTtlTicks() < 1 + || space.getTtlTicks() + > PhysicsChunkCollisionSettings.MAX_TTL_TICKS) { errors.add("PhysicsStore space " + uuid - + " has invalid PhysicsChunk terrain TTL"); + + " has invalid PhysicsChunk collision TTL"); } if (!Float.isFinite(space.getChunkCollisionFriction()) || space.getChunkCollisionFriction() < 0.0f) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index 3762981f..f977e6a5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; @@ -52,14 +52,14 @@ public final class PersistentSpaceDto { (dto, value) -> dto.terrainMode = value != null ? value : PhysicsChunkTerrainMode.NONE, - PersistentSpaceDto::getTerrainMode) + PersistentSpaceDto::getMode) .add() .append(new KeyedCodec<>("EntityChunkBoundaryMode", new EnumCodec<>(EntityChunkBoundaryMode.class), false), (dto, value) -> dto.entityChunkBoundaryMode = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + : PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, PersistentSpaceDto::getEntityChunkBoundaryMode) .add() .append(new KeyedCodec<>("NativeVoxelCollision", Codec.BOOLEAN, false), @@ -69,20 +69,20 @@ public final class PersistentSpaceDto { .append(new KeyedCodec<>("ChunkCollisionRadius", Codec.INTEGER, false), (dto, value) -> dto.terrainRadius = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, - PersistentSpaceDto::getTerrainRadius) + : PhysicsChunkCollisionSettings.DEFAULT_RADIUS, + PersistentSpaceDto::getRadius) .add() .append(new KeyedCodec<>("BodyChunkCollisionRadius", Codec.INTEGER, false), (dto, value) -> dto.bodyTerrainRadius = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, - PersistentSpaceDto::getBodyTerrainRadius) + : PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, + PersistentSpaceDto::getBodyRadius) .add() .append(new KeyedCodec<>("ChunkCollisionTtlTicks", Codec.INTEGER, false), (dto, value) -> dto.terrainTtlTicks = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, - PersistentSpaceDto::getTerrainTtlTicks) + : PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, + PersistentSpaceDto::getTtlTicks) .add() .append(new KeyedCodec<>("ChunkCollisionFriction", Codec.FLOAT, false), (dto, value) -> dto.chunkCollisionFriction = value != null @@ -152,15 +152,15 @@ public final class PersistentSpaceDto { private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = - PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelCollisionEnabled = - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; + PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; private int terrainRadius = - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; + PhysicsChunkCollisionSettings.DEFAULT_RADIUS; private int bodyTerrainRadius = - PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; + PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS; private int terrainTtlTicks = - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; + PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS; private float chunkCollisionFriction = PhysicsChunkCollisionDefaults.FRICTION; private float chunkCollisionRestitution = PhysicsChunkCollisionDefaults.RESTITUTION; @@ -189,11 +189,11 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, backendId, gravity, PhysicsChunkTerrainMode.NONE, - PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, - PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, + PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED, + PhysicsChunkCollisionSettings.DEFAULT_RADIUS, + PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, + PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, PhysicsChunkCollisionDefaults.FRICTION, PhysicsChunkCollisionDefaults.RESTITUTION, PhysicsChunkCollisionDefaults.COLLISION_GROUP, @@ -219,7 +219,7 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, backendId, gravity, terrainMode, - PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, nativeVoxelCollisionEnabled, terrainRadius, bodyTerrainRadius, @@ -330,7 +330,7 @@ public Vector3f getGravity() { } @Nonnull - public PhysicsChunkTerrainMode getTerrainMode() { + public PhysicsChunkTerrainMode getMode() { return terrainMode; } @@ -343,15 +343,15 @@ public boolean isNativeVoxelCollisionEnabled() { return nativeVoxelCollisionEnabled; } - public int getTerrainRadius() { + public int getRadius() { return terrainRadius; } - public int getBodyTerrainRadius() { + public int getBodyRadius() { return bodyTerrainRadius; } - public int getTerrainTtlTicks() { + public int getTtlTicks() { return terrainTtlTicks; } @@ -373,7 +373,7 @@ public int getChunkCollisionMask() { @Nonnull public ChunkCollisionSettingsComponent getChunkCollisionSettings() { - return new ChunkCollisionSettingsComponent(getTerrainMode(), + return new ChunkCollisionSettingsComponent(getMode(), entityChunkBoundaryMode, nativeVoxelCollisionEnabled, terrainRadius, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java index 4821c4e6..35fe7c5e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; @@ -153,7 +153,7 @@ public static void putSpaceSettings(@Nonnull Store store, public static void putChunkCollisionSettings(@Nonnull Store store, @Nonnull SpaceId spaceId, - @Nonnull PhysicsChunkTerrainSettings settings) { + @Nonnull PhysicsChunkCollisionSettings settings) { UUID spaceUuid = requireSpaceUuid(store, spaceId); Ref ref = requireSpaceRef(store, spaceUuid); putChunkCollisionSettings(store, ref, settings); @@ -161,7 +161,7 @@ public static void putChunkCollisionSettings(@Nonnull Store store, public static void putChunkCollisionSettings(@Nonnull Store store, @Nonnull Ref ref, - @Nonnull PhysicsChunkTerrainSettings settings) { + @Nonnull PhysicsChunkCollisionSettings settings) { requireSpaceUuid(store, ref); PhysicsThreading.requireWorldThread(store, "update PhysicsStore chunk collision settings"); @@ -301,7 +301,7 @@ public static void putExtensionSettings(@Nonnull Store store, private static void addSpaceSettingsComponents(@Nonnull Holder holder, @Nonnull PhysicsSpaceSettings settings) { ChunkCollisionSettingsComponent chunkCollision = - new ChunkCollisionSettingsComponent(settings.getPhysicsChunkTerrainSettings()); + new ChunkCollisionSettingsComponent(settings.getPhysicsChunkCollisionSettings()); addIfNonDefault(holder, ChunkCollisionSettingsComponent.getComponentType(), chunkCollision, @@ -352,7 +352,7 @@ private static void putSpaceSettingsComponents(@Nonnull Store stor @Nonnull Ref ref, @Nonnull PhysicsSpaceSettings settings) { ChunkCollisionSettingsComponent chunkCollision = - new ChunkCollisionSettingsComponent(settings.getPhysicsChunkTerrainSettings()); + new ChunkCollisionSettingsComponent(settings.getPhysicsChunkCollisionSettings()); putOrRemoveDefault(store, ref, ChunkCollisionSettingsComponent.getComponentType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java index c781b0ce..a7ebbecc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java @@ -31,7 +31,7 @@ public final class PhysicsSpaceRuntime { private final Int2ObjectMap spaces = new Int2ObjectOpenHashMap<>(); /** - * Per-space settings (PhysicsChunk terrain mode, radius, TTL, etc.). Keyed by space id value. + * Per-space settings (PhysicsChunk collision mode, radius, TTL, etc.). Keyed by space id value. */ private final Int2ObjectMap spaceSettings = new Int2ObjectOpenHashMap<>(); @@ -51,7 +51,7 @@ public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId "World %s creating physics space using backend %s collision=%s", worldName, backendId, - settings.getPhysicsChunkTerrainSettings().getTerrainMode()); + settings.getPhysicsChunkCollisionSettings().getMode()); PhysicsBackendRuntime runtime = Impulse.createRuntime(backendId); BackendSpaceHandle backendSpaceHandle = new BackendSpaceHandle(runtime.createSpace(spaceId)); @@ -72,7 +72,7 @@ public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId worldName, spaceId, backendId, - settings.getPhysicsChunkTerrainSettings().getTerrainMode()); + settings.getPhysicsChunkCollisionSettings().getMode()); return binding; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index 7ba4dfa4..f68c518f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -22,7 +22,7 @@ import javax.annotation.Nonnull; /** - * Publishes copied PhysicsChunk terrain settings for PhysicsStore space entities. + * Publishes copied PhysicsChunk collision settings for PhysicsStore space entities. */ public final class PhysicsChunkSettingsIndexSystem extends TickingSystem implements QuerySystem { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java index 5493bae1..6646854b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -39,25 +39,25 @@ public class ChunkCollisionSettingsComponent implements Component false), (component, value) -> component.entityChunkBoundaryMode = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + : PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, ChunkCollisionSettingsComponent::getEntityChunkBoundaryMode) .add() .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), (component, value) -> component.radius = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, + : PhysicsChunkCollisionSettings.DEFAULT_RADIUS, ChunkCollisionSettingsComponent::getRadius) .add() .append(new KeyedCodec<>("BodyRadius", Codec.INTEGER, false), (component, value) -> component.bodyRadius = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, + : PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, ChunkCollisionSettingsComponent::getBodyRadius) .add() .append(new KeyedCodec<>("TtlTicks", Codec.INTEGER, false), (component, value) -> component.ttlTicks = value != null ? value - : PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, + : PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, ChunkCollisionSettingsComponent::getTtlTicks) .add() .build(); @@ -66,23 +66,23 @@ public class ChunkCollisionSettingsComponent implements Component private PhysicsChunkTerrainMode mode = PhysicsChunkTerrainMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = - PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelCollisionEnabled = - PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; - private int radius = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS; - private int bodyRadius = PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS; - private int ttlTicks = PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; + PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; + private int radius = PhysicsChunkCollisionSettings.DEFAULT_RADIUS; + private int bodyRadius = PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS; + private int ttlTicks = PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS; public ChunkCollisionSettingsComponent() { } - public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainSettings settings) { - this(settings.getTerrainMode(), + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkCollisionSettings settings) { + this(settings.getMode(), settings.getEntityChunkBoundaryMode(), settings.isNativeVoxelCollisionEnabled(), - settings.getTerrainRadius(), - settings.getBodyTerrainRadius(), - settings.getTerrainTtlTicks()); + settings.getRadius(), + settings.getBodyRadius(), + settings.getTtlTicks()); } public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, @@ -91,7 +91,7 @@ public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, int bodyRadius, int ttlTicks) { this(mode, - PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, nativeVoxelCollisionEnabled, radius, bodyRadius, @@ -166,27 +166,27 @@ public void setTtlTicks(int ttlTicks) { } public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getPhysicsChunkTerrainSettings()); + copyTo(settings.getPhysicsChunkCollisionSettings()); } - public void copyTo(@Nonnull PhysicsChunkTerrainSettings settings) { - settings.setTerrainMode(mode); + public void copyTo(@Nonnull PhysicsChunkCollisionSettings settings) { + settings.setMode(mode); settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); settings.setNativeVoxelCollisionEnabled(nativeVoxelCollisionEnabled); - settings.setTerrainRadius(radius); - settings.setBodyTerrainRadius(bodyRadius); - settings.setTerrainTtlTicks(ttlTicks); + settings.setRadius(radius); + settings.setBodyRadius(bodyRadius); + settings.setTtlTicks(ttlTicks); } public boolean isDefault() { return mode == PhysicsChunkTerrainMode.NONE && entityChunkBoundaryMode - == PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE + == PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE && nativeVoxelCollisionEnabled - == PhysicsChunkTerrainSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED - && radius == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS - && bodyRadius == PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS - && ttlTicks == PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS; + == PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED + && radius == PhysicsChunkCollisionSettings.DEFAULT_RADIUS + && bodyRadius == PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS + && ttlTicks == PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java new file mode 100644 index 00000000..1d20c612 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java @@ -0,0 +1,124 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; + +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import lombok.Getter; +import lombok.Setter; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * Chunk-collision streaming settings for a PhysicsStore space. + */ +public class PhysicsChunkCollisionSettings { + + /** + * Block radius around each tracked player for streaming chunk collision bodies. + */ + public static final int DEFAULT_RADIUS = 8; + + /** + * Hard block-radius cap for player-centered PhysicsChunk collision streaming. + */ + public static final int MAX_RADIUS = 128; + + /** + * Block radius around each active dynamic physics body for streaming chunk collision bodies. + */ + public static final int DEFAULT_BODY_RADIUS = 4; + + /** + * Hard block-radius cap for dynamic-body PhysicsChunk collision streaming. + */ + public static final int MAX_BODY_RADIUS = 64; + + /** + * Ticks before an unused section's chunk collision bodies are pruned. + */ + public static final int DEFAULT_TTL_TICKS = 100; + + /** + * Hard tick cap for retaining unused streamed chunk-collision sections. + */ + public static final int MAX_TTL_TICKS = 12_000; + + /** + * Default behavior when an entity-backed body reaches an unloaded chunk border. + */ + @Nonnull + public static final EntityChunkBoundaryMode DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE = + EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED; + + /** + * Whether full-cube world sections should use native backend voxel collision when available. + */ + public static final boolean DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED = false; + + @Nonnull + private PhysicsChunkTerrainMode mode = PhysicsChunkTerrainMode.NONE; + @Nonnull + private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; + @Setter + @Getter + private boolean nativeVoxelCollisionEnabled = DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; + @Getter + private int radius = DEFAULT_RADIUS; + @Getter + private int bodyRadius = DEFAULT_BODY_RADIUS; + @Getter + private int ttlTicks = DEFAULT_TTL_TICKS; + + public PhysicsChunkCollisionSettings() { + } + + public PhysicsChunkCollisionSettings(@Nonnull PhysicsChunkCollisionSettings settings) { + mode = settings.mode; + entityChunkBoundaryMode = settings.entityChunkBoundaryMode; + nativeVoxelCollisionEnabled = settings.nativeVoxelCollisionEnabled; + radius = settings.radius; + bodyRadius = settings.bodyRadius; + ttlTicks = settings.ttlTicks; + } + + @Nonnull + public PhysicsChunkTerrainMode getMode() { + return mode; + } + + public void setMode(@Nonnull PhysicsChunkTerrainMode mode) { + this.mode = Objects.requireNonNull(mode, "mode"); + } + + @Nonnull + public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { + return entityChunkBoundaryMode; + } + + public void setEntityChunkBoundaryMode( + @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { + this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, + "entityChunkBoundaryMode"); + } + + public void setRadius(int radius) { + this.radius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "PhysicsChunk collision radius", + radius, + MAX_RADIUS); + } + + public void setBodyRadius(int bodyRadius) { + this.bodyRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "PhysicsChunk collision body radius", + bodyRadius, + MAX_BODY_RADIUS); + } + + public void setTtlTicks(int ttlTicks) { + this.ttlTicks = PhysicsChunkSettingsValidation.requirePositiveAtMost( + "PhysicsChunk collision TTL", + ttlTicks, + MAX_TTL_TICKS); + } + +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java deleted file mode 100644 index a4f935e9..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkTerrainSettings.java +++ /dev/null @@ -1,124 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; - -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; -import lombok.Getter; -import lombok.Setter; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Terrain collider streaming settings for a PhysicsStore space. - */ -public class PhysicsChunkTerrainSettings { - - /** - * Block radius around each tracked player for streaming chunk collision bodies. - */ - public static final int DEFAULT_TERRAIN_RADIUS = 8; - - /** - * Hard block-radius cap for player-centered PhysicsChunk terrain streaming. - */ - public static final int MAX_TERRAIN_RADIUS = 128; - - /** - * Block radius around each active dynamic physics body for streaming chunk collision bodies. - */ - public static final int DEFAULT_BODY_TERRAIN_RADIUS = 4; - - /** - * Hard block-radius cap for dynamic-body PhysicsChunk terrain streaming. - */ - public static final int MAX_BODY_TERRAIN_RADIUS = 64; - - /** - * Ticks before an unused section's chunk collision bodies are pruned. - */ - public static final int DEFAULT_TERRAIN_TTL_TICKS = 100; - - /** - * Hard tick cap for retaining unused streamed terrain sections. - */ - public static final int MAX_TERRAIN_TTL_TICKS = 12_000; - - /** - * Default behavior when an entity-backed body reaches an unloaded chunk border. - */ - @Nonnull - public static final EntityChunkBoundaryMode DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE = - EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED; - - /** - * Whether full-cube world sections should use native backend voxel collision when available. - */ - public static final boolean DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED = false; - - @Nonnull - private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; - @Nonnull - private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - @Setter - @Getter - private boolean nativeVoxelCollisionEnabled = DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; - @Getter - private int terrainRadius = DEFAULT_TERRAIN_RADIUS; - @Getter - private int bodyTerrainRadius = DEFAULT_BODY_TERRAIN_RADIUS; - @Getter - private int terrainTtlTicks = DEFAULT_TERRAIN_TTL_TICKS; - - public PhysicsChunkTerrainSettings() { - } - - public PhysicsChunkTerrainSettings(@Nonnull PhysicsChunkTerrainSettings settings) { - terrainMode = settings.terrainMode; - entityChunkBoundaryMode = settings.entityChunkBoundaryMode; - nativeVoxelCollisionEnabled = settings.nativeVoxelCollisionEnabled; - terrainRadius = settings.terrainRadius; - bodyTerrainRadius = settings.bodyTerrainRadius; - terrainTtlTicks = settings.terrainTtlTicks; - } - - @Nonnull - public PhysicsChunkTerrainMode getTerrainMode() { - return terrainMode; - } - - public void setTerrainMode(@Nonnull PhysicsChunkTerrainMode terrainMode) { - this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); - } - - @Nonnull - public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { - return entityChunkBoundaryMode; - } - - public void setEntityChunkBoundaryMode( - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode) { - this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, - "entityChunkBoundaryMode"); - } - - public void setTerrainRadius(int terrainRadius) { - this.terrainRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain radius", - terrainRadius, - MAX_TERRAIN_RADIUS); - } - - public void setBodyTerrainRadius(int bodyTerrainRadius) { - this.bodyTerrainRadius = PhysicsChunkSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain body radius", - bodyTerrainRadius, - MAX_BODY_TERRAIN_RADIUS); - } - - public void setTerrainTtlTicks(int terrainTtlTicks) { - this.terrainTtlTicks = PhysicsChunkSettingsValidation.requirePositiveAtMost( - "PhysicsChunk terrain TTL", - terrainTtlTicks, - MAX_TERRAIN_TTL_TICKS); - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java index ab89a410..264da3a5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; @@ -178,24 +178,24 @@ public static PhysicsSpaceSettings settings(@Nonnull Store store, } @Nullable - public static PhysicsChunkTerrainSettings chunkTerrainSettings( + public static PhysicsChunkCollisionSettings chunkCollisionSettings( @Nonnull Store store, @Nonnull SpaceId spaceId) { Ref ref = resolveRef(store, spaceId); - return ref != null ? chunkTerrainSettings(store, ref) : null; + return ref != null ? chunkCollisionSettings(store, ref) : null; } @Nullable - public static PhysicsChunkTerrainSettings chunkTerrainSettings( + public static PhysicsChunkCollisionSettings chunkCollisionSettings( @Nonnull Store store, @Nonnull Ref spaceRef) { Store checkedStore = requireWorldThread(store, - "read PhysicsStore chunk terrain settings"); + "read PhysicsStore chunk collision settings"); Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); if (!isSpaceRef(checkedStore, checkedRef)) { return null; } - PhysicsChunkTerrainSettings settings = new PhysicsChunkTerrainSettings(); + PhysicsChunkCollisionSettings settings = new PhysicsChunkCollisionSettings(); ChunkCollisionSettingsComponent component = checkedStore.getComponent(checkedRef, ChunkCollisionSettingsComponent.getComponentType()); if (component != null) { @@ -358,21 +358,21 @@ public static void putSettings(@Nonnull Store store, Objects.requireNonNull(settings, "settings")); } - public static void putChunkTerrainSettings(@Nonnull Store store, + public static void putChunkCollisionSettings(@Nonnull Store store, @Nonnull SpaceId spaceId, - @Nonnull PhysicsChunkTerrainSettings settings) { + @Nonnull PhysicsChunkCollisionSettings settings) { Store checkedStore = requireWorldThread(store, - "update PhysicsStore chunk terrain settings"); + "update PhysicsStore chunk collision settings"); PhysicsStoreSpaceMutations.putChunkCollisionSettings(checkedStore, Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(settings, "settings")); } - public static void putChunkTerrainSettings(@Nonnull Store store, + public static void putChunkCollisionSettings(@Nonnull Store store, @Nonnull Ref spaceRef, - @Nonnull PhysicsChunkTerrainSettings settings) { + @Nonnull PhysicsChunkCollisionSettings settings) { Store checkedStore = requireWorldThread(store, - "update PhysicsStore chunk terrain settings"); + "update PhysicsStore chunk collision settings"); PhysicsStoreSpaceMutations.putChunkCollisionSettings(checkedStore, Objects.requireNonNull(spaceRef, "spaceRef"), Objects.requireNonNull(settings, "settings")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index bc33007b..36d9f3c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.settings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; @@ -9,7 +9,7 @@ import javax.annotation.Nonnull; /** - * Per-space configuration aggregate for terrain collision, solver tuning, + * Per-space configuration aggregate for chunk collision, solver tuning, * collision LOD, visual sync, and detached visual materialization. * *

        Settings are stored on PhysicsStore space entities. New plugin code should create spaces and @@ -20,14 +20,14 @@ * code should read and mutate the domain group directly instead of adding flat * shortcut state here.

        * - *

        Default settings have PhysicsChunk terrain disabled ({@link PhysicsChunkTerrainMode#NONE}), - * which keeps Impulse fully opt-in: no terrain bodies are created unless the integrator + *

        Default settings have PhysicsChunk collision disabled ({@link PhysicsChunkTerrainMode#NONE}), + * which keeps Impulse fully opt-in: no chunk-collision bodies are created unless the integrator * explicitly opts in.

        */ public class PhysicsSpaceSettings { @Nonnull - private final PhysicsChunkTerrainSettings physicsChunkTerrainSettings; + private final PhysicsChunkCollisionSettings physicsChunkCollisionSettings; @Nonnull private final PhysicsVisualSyncSettings visualSyncSettings; @Nonnull @@ -40,7 +40,7 @@ public class PhysicsSpaceSettings { private final PhysicsExtensionSettings extensionSettings; public PhysicsSpaceSettings() { - physicsChunkTerrainSettings = new PhysicsChunkTerrainSettings(); + physicsChunkCollisionSettings = new PhysicsChunkCollisionSettings(); visualSyncSettings = new PhysicsVisualSyncSettings(); solverSettings = new PhysicsSolverSettings(); visualMaterializationSettings = new PhysicsVisualMaterializationSettings(); @@ -49,8 +49,8 @@ public PhysicsSpaceSettings() { } public PhysicsSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { - physicsChunkTerrainSettings = - new PhysicsChunkTerrainSettings(settings.physicsChunkTerrainSettings); + physicsChunkCollisionSettings = + new PhysicsChunkCollisionSettings(settings.physicsChunkCollisionSettings); visualSyncSettings = new PhysicsVisualSyncSettings(settings.visualSyncSettings); solverSettings = @@ -63,11 +63,11 @@ public PhysicsSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { } /** - * Terrain collider streaming and chunk-boundary behavior. + * Chunk-collision streaming and chunk-boundary behavior. */ @Nonnull - public PhysicsChunkTerrainSettings getPhysicsChunkTerrainSettings() { - return physicsChunkTerrainSettings; + public PhysicsChunkCollisionSettings getPhysicsChunkCollisionSettings() { + return physicsChunkCollisionSettings; } /** @@ -116,13 +116,13 @@ public static PhysicsSpaceSettings defaults() { } /** - * Convenience factory for a space with streaming PhysicsChunk terrain enabled. + * Convenience factory for a space with streaming PhysicsChunk collision enabled. */ @Nonnull public static PhysicsSpaceSettings streamingPhysicsChunk() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - settings.getPhysicsChunkTerrainSettings() - .setTerrainMode(PhysicsChunkTerrainMode.STREAMING); + settings.getPhysicsChunkCollisionSettings() + .setMode(PhysicsChunkTerrainMode.STREAMING); return settings; } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index f818bfd7..21f22c5a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -27,7 +27,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -54,7 +54,7 @@ public class StressBodiesCommand extends AbstractAsyncPlayerCommand { DETACHED_VISUAL_DEMATERIALIZATION_RADIUS - DETACHED_VISUAL_MATERIALIZATION_RADIUS; private static final int DETACHED_VISUAL_MAX_MATERIALIZED = 10_000; private static final int DETACHED_VISUAL_MAX_SPAWNS_PER_TICK = 128; - private static final int STRESS_BODY_TERRAIN_RADIUS = 8; + private static final int STRESS_BODY_CHUNK_COLLISION_RADIUS = 8; private static final PhysicsBackendExtensionId RAPIER_SOLVER_EXTENSION_ID = new PhysicsBackendExtensionId("impulse:rapier_solver"); private static final String RAPIER_INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; @@ -229,8 +229,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, batchTiming.entityApplyNanos(), 0L); } - PhysicsChunkTerrainSettings terrainSettings = - settings.getPhysicsChunkTerrainSettings(); + PhysicsChunkCollisionSettings chunkCollisionSettings = + settings.getPhysicsChunkCollisionSettings(); PhysicsVisualMaterializationSettings visualMaterializationSettings = settings.getVisualMaterializationSettings(); PhysicsVisualSyncSettings visualSyncSettings = settings.getVisualSyncSettings(); @@ -248,7 +248,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + ": mode=" + mode.serialized() + " space=" + spaceId.value() + " physicsChunk=streaming" - + " bodyCollisionRadius=" + terrainSettings.getBodyTerrainRadius() + + " bodyChunkCollisionRadius=" + chunkCollisionSettings.getBodyRadius() + " prewarmedSections=" + prewarmedSections + " step=" + worldSettings.getStepMode().getSerializedName() + "/" + worldSettings.getSimulationSteps() @@ -301,13 +301,13 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store Date: Fri, 19 Jun 2026 09:12:18 +0200 Subject: [PATCH 431/534] test(core): update physicschunk collision settings tests Signed-off-by: Blovien --- .../PhysicsChunkNamingSourceGuardTest.java | 4 + .../PersistentSpaceDtoSettingsTest.java | 41 +++---- .../settings/PhysicsSpaceSettingsTest.java | 110 +++++++++--------- 3 files changed, 80 insertions(+), 75 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java index c7832170..f2d0fbfb 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java @@ -61,6 +61,10 @@ void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { file, "dev.hytalemodding.impulse.core.plugin.settings.", "PhysicsChunkTerrainSettings"); + assertRemovedApiAbsent(source, + file, + "dev.hytalemodding.impulse.core.plugin.settings.", + "PhysicsChunkCollisionSettings"); assertRemovedApiAbsent(source, file, "dev.hytalemodding.impulse.core.plugin.settings.", diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index 44bb6d86..ecb9b04d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; @@ -23,21 +23,22 @@ class PersistentSpaceDtoSettingsTest { @Test void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); - original.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(true); + original.getPhysicsChunkCollisionSettings().setNativeVoxelCollisionEnabled(true); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); - PhysicsChunkTerrainSettings terrain = original.getPhysicsChunkTerrainSettings(); + PhysicsChunkCollisionSettings chunkCollision = + original.getPhysicsChunkCollisionSettings(); PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), "test:settings-persistence", new Vector3f(0.0f, -9.81f, 0.0f), - terrain.getTerrainMode(), - terrain.getEntityChunkBoundaryMode(), - terrain.isNativeVoxelCollisionEnabled(), - terrain.getTerrainRadius(), - terrain.getBodyTerrainRadius(), - terrain.getTerrainTtlTicks(), + chunkCollision.getMode(), + chunkCollision.getEntityChunkBoundaryMode(), + chunkCollision.isNativeVoxelCollisionEnabled(), + chunkCollision.getRadius(), + chunkCollision.getBodyRadius(), + chunkCollision.getTtlTicks(), 0.85f, 0.2f, new SolverSettingsComponent(original.getSolverSettings()), @@ -62,7 +63,7 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertEquals(0.2f, decodedState.getChunkCollisionRestitution(), 0.0001f); PhysicsSpaceSettings decoded = decodedState.toSettings(); - assertTrue(decoded.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertTrue(decoded.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); assertDetachedVisualCadence(decoded, 7, 9, 11); PersistentSpaceDto copiedState = state.copy(); @@ -70,23 +71,23 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertEquals(0.2f, copiedState.getChunkCollisionRestitution(), 0.0001f); PhysicsSpaceSettings copied = copiedState.toSettings(); - assertTrue(copied.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertTrue(copied.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); assertDetachedVisualCadence(copied, 7, 9, 11); } @Test void roundTripPreservesChunkCollisionFilter() { - PhysicsChunkTerrainSettings terrain = - PhysicsSpaceSettings.defaults().getPhysicsChunkTerrainSettings(); + PhysicsChunkCollisionSettings chunkCollision = + PhysicsSpaceSettings.defaults().getPhysicsChunkCollisionSettings(); PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), "test:chunk-filter-persistence", new Vector3f(0.0f, -9.81f, 0.0f), - terrain.getTerrainMode(), - terrain.getEntityChunkBoundaryMode(), + chunkCollision.getMode(), + chunkCollision.getEntityChunkBoundaryMode(), false, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, - PhysicsChunkTerrainSettings.DEFAULT_BODY_TERRAIN_RADIUS, - PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_TTL_TICKS, + PhysicsChunkCollisionSettings.DEFAULT_RADIUS, + PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, + PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, PhysicsChunkCollisionDefaults.FRICTION, PhysicsChunkCollisionDefaults.RESTITUTION, 0x40, @@ -107,8 +108,8 @@ void roundTripPreservesChunkCollisionFilter() { assertEquals(0x40, state.copy().getChunkCollisionGroup()); assertEquals(0x03, state.copy().getChunkCollisionMask()); PhysicsSpaceSettings decodedSettings = decoded.toSettings(); - assertEquals(terrain.getEntityChunkBoundaryMode(), - decodedSettings.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode()); + assertEquals(chunkCollision.getEntityChunkBoundaryMode(), + decodedSettings.getPhysicsChunkCollisionSettings().getEntityChunkBoundaryMode()); } private static void assertDetachedVisualCadence(PhysicsSpaceSettings settings, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index 97f47d6a..0866b664 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkTerrainSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; @@ -82,21 +82,21 @@ void acceptsUpdatedVisualSyncRadiiWhenOrderingStaysValid() { } @Test - void rejectsNonPositivePhysicsChunkTerrainValues() { + void rejectsNonPositivePhysicsChunkCollisionValues() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - assertEquals("PhysicsChunk terrain radius must be between 1 and " - + PhysicsChunkTerrainSettings.MAX_TERRAIN_RADIUS, + assertEquals("PhysicsChunk collision radius must be between 1 and " + + PhysicsChunkCollisionSettings.MAX_RADIUS, assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkTerrainSettings().setTerrainRadius(0)).getMessage()); - assertEquals("PhysicsChunk terrain body radius must be between 1 and " - + PhysicsChunkTerrainSettings.MAX_BODY_TERRAIN_RADIUS, + () -> settings.getPhysicsChunkCollisionSettings().setRadius(0)).getMessage()); + assertEquals("PhysicsChunk collision body radius must be between 1 and " + + PhysicsChunkCollisionSettings.MAX_BODY_RADIUS, assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(0)).getMessage()); - assertEquals("PhysicsChunk terrain TTL must be between 1 and " - + PhysicsChunkTerrainSettings.MAX_TERRAIN_TTL_TICKS, + () -> settings.getPhysicsChunkCollisionSettings().setBodyRadius(0)).getMessage()); + assertEquals("PhysicsChunk collision TTL must be between 1 and " + + PhysicsChunkCollisionSettings.MAX_TTL_TICKS, assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(0)).getMessage()); + () -> settings.getPhysicsChunkCollisionSettings().setTtlTicks(0)).getMessage()); assertEquals("Visual full sync radius must be between 1 and " + PhysicsVisualSyncSettings.MAX_VISUAL_FULL_SYNC_RADIUS, assertThrows(IllegalArgumentException.class, @@ -164,23 +164,23 @@ void defaultsFactoryReturnsFreshDefaultSettings() { PhysicsSpaceSettings second = PhysicsSpaceSettings.defaults(); assertNotSame(first, second); - assertEquals(PhysicsChunkTerrainMode.NONE, first.getPhysicsChunkTerrainSettings().getTerrainMode()); - assertSame(PhysicsChunkTerrainSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - first.getPhysicsChunkTerrainSettings().getEntityChunkBoundaryMode()); - assertFalse(first.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertEquals(PhysicsChunkTerrainMode.NONE, first.getPhysicsChunkCollisionSettings().getMode()); + assertSame(PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, + first.getPhysicsChunkCollisionSettings().getEntityChunkBoundaryMode()); + assertFalse(first.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); } @Test void groupedAccessorsExposeIndependentDomainState() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - settings.getPhysicsChunkTerrainSettings().setTerrainRadius(14); + settings.getPhysicsChunkCollisionSettings().setRadius(14); settings.getVisualSyncSettings().setVisualSyncRadii(36, 144); settings.getSolverSettings().setSolverIterations(6); settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(96); settings.getCollisionLodSettings().setCollisionLodRadii(24, 72); - assertEquals(14, settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); + assertEquals(14, settings.getPhysicsChunkCollisionSettings().getRadius()); assertEquals(36, settings.getVisualSyncSettings().getVisualFullSyncRadius()); assertEquals(144, settings.getVisualSyncSettings().getVisualMaxSyncRadius()); assertEquals(6, settings.getSolverSettings().getSolverIterations()); @@ -188,13 +188,13 @@ void groupedAccessorsExposeIndependentDomainState() { assertEquals(24, settings.getCollisionLodSettings().getCollisionLodNearRadius()); assertEquals(72, settings.getCollisionLodSettings().getCollisionLodMidRadius()); - settings.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(5); + settings.getPhysicsChunkCollisionSettings().setBodyRadius(5); settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(3); settings.getSolverSettings().setDynamicSleepLinearThreshold(0.45f); settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(16); settings.getCollisionLodSettings().setCollisionLodHysteresis(4); - assertEquals(5, settings.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); + assertEquals(5, settings.getPhysicsChunkCollisionSettings().getBodyRadius()); assertEquals(3, settings.getVisualSyncSettings().getVisualMidSyncIntervalTicks()); assertEquals(0.45f, settings.getSolverSettings().getDynamicSleepLinearThreshold(), 0.0001f); assertEquals(16, @@ -234,33 +234,33 @@ void collisionLodSettingsCopyConstructorCopiesValues() { } @Test - void terrainSettingsCopyConstructorCopiesValues() { - PhysicsChunkTerrainSettings canonical = new PhysicsChunkTerrainSettings(); + void chunkCollisionSettingsCopyConstructorCopiesValues() { + PhysicsChunkCollisionSettings canonical = new PhysicsChunkCollisionSettings(); - canonical.setTerrainMode(PhysicsChunkTerrainMode.STREAMING); + canonical.setMode(PhysicsChunkTerrainMode.STREAMING); canonical.setEntityChunkBoundaryMode(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK); canonical.setNativeVoxelCollisionEnabled(true); - canonical.setTerrainRadius(18); - canonical.setBodyTerrainRadius(7); - canonical.setTerrainTtlTicks(240); - - PhysicsChunkTerrainSettings canonicalCopy = - new PhysicsChunkTerrainSettings(canonical); - PhysicsChunkTerrainSettings secondCopy = - new PhysicsChunkTerrainSettings(canonicalCopy); - canonical.setTerrainRadius(24); - canonicalCopy.setTerrainRadius(30); - - assertEquals(PhysicsChunkTerrainMode.STREAMING, canonicalCopy.getTerrainMode()); + canonical.setRadius(18); + canonical.setBodyRadius(7); + canonical.setTtlTicks(240); + + PhysicsChunkCollisionSettings canonicalCopy = + new PhysicsChunkCollisionSettings(canonical); + PhysicsChunkCollisionSettings secondCopy = + new PhysicsChunkCollisionSettings(canonicalCopy); + canonical.setRadius(24); + canonicalCopy.setRadius(30); + + assertEquals(PhysicsChunkTerrainMode.STREAMING, canonicalCopy.getMode()); assertEquals(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK, canonicalCopy.getEntityChunkBoundaryMode()); assertTrue(canonicalCopy.isNativeVoxelCollisionEnabled()); - assertEquals(30, canonicalCopy.getTerrainRadius()); - assertEquals(7, canonicalCopy.getBodyTerrainRadius()); - assertEquals(240, canonicalCopy.getTerrainTtlTicks()); - assertEquals(18, secondCopy.getTerrainRadius()); - assertEquals(7, secondCopy.getBodyTerrainRadius()); - assertEquals(240, secondCopy.getTerrainTtlTicks()); + assertEquals(30, canonicalCopy.getRadius()); + assertEquals(7, canonicalCopy.getBodyRadius()); + assertEquals(240, canonicalCopy.getTtlTicks()); + assertEquals(18, secondCopy.getRadius()); + assertEquals(7, secondCopy.getBodyRadius()); + assertEquals(240, secondCopy.getTtlTicks()); } @Test @@ -296,19 +296,19 @@ void defaultsDoNotCarryBackendExtensionValues() { void streamingPhysicsChunkFactoryEnablesStreamingMode() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingPhysicsChunk(); - assertEquals(PhysicsChunkTerrainMode.STREAMING, settings.getPhysicsChunkTerrainSettings().getTerrainMode()); - assertEquals(PhysicsChunkTerrainSettings.DEFAULT_TERRAIN_RADIUS, - settings.getPhysicsChunkTerrainSettings().getTerrainRadius()); + assertEquals(PhysicsChunkTerrainMode.STREAMING, settings.getPhysicsChunkCollisionSettings().getMode()); + assertEquals(PhysicsChunkCollisionSettings.DEFAULT_RADIUS, + settings.getPhysicsChunkCollisionSettings().getRadius()); } @Test void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { PhysicsSpaceSettings original = new PhysicsSpaceSettings(); - original.getPhysicsChunkTerrainSettings().setTerrainMode(PhysicsChunkTerrainMode.STREAMING); - original.getPhysicsChunkTerrainSettings().setTerrainRadius(12); - original.getPhysicsChunkTerrainSettings().setBodyTerrainRadius(6); - original.getPhysicsChunkTerrainSettings().setTerrainTtlTicks(180); - original.getPhysicsChunkTerrainSettings().setNativeVoxelCollisionEnabled(true); + original.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.STREAMING); + original.getPhysicsChunkCollisionSettings().setRadius(12); + original.getPhysicsChunkCollisionSettings().setBodyRadius(6); + original.getPhysicsChunkCollisionSettings().setTtlTicks(180); + original.getPhysicsChunkCollisionSettings().setNativeVoxelCollisionEnabled(true); original.getVisualSyncSettings().setVisualMaxSyncRadius(160); original.getVisualSyncSettings().setVisualFullSyncRadius(80); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(2); @@ -325,22 +325,22 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { original.getCollisionLodSettings().setCollisionLodFarSleepEnabled(false); PhysicsSpaceSettings copy = new PhysicsSpaceSettings(original); - original.getPhysicsChunkTerrainSettings().setTerrainRadius(20); + original.getPhysicsChunkCollisionSettings().setRadius(20); original.getVisualSyncSettings().setVisualSyncRadii(96, 192); original.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(128); original.getCollisionLodSettings().setCollisionLodRadii(48, 112); - assertNotSame(original.getPhysicsChunkTerrainSettings(), copy.getPhysicsChunkTerrainSettings()); + assertNotSame(original.getPhysicsChunkCollisionSettings(), copy.getPhysicsChunkCollisionSettings()); assertNotSame(original.getVisualSyncSettings(), copy.getVisualSyncSettings()); assertNotSame(original.getSolverSettings(), copy.getSolverSettings()); assertNotSame(original.getVisualMaterializationSettings(), copy.getVisualMaterializationSettings()); assertNotSame(original.getCollisionLodSettings(), copy.getCollisionLodSettings()); - assertEquals(PhysicsChunkTerrainMode.STREAMING, copy.getPhysicsChunkTerrainSettings().getTerrainMode()); - assertEquals(12, copy.getPhysicsChunkTerrainSettings().getTerrainRadius()); - assertEquals(6, copy.getPhysicsChunkTerrainSettings().getBodyTerrainRadius()); - assertEquals(180, copy.getPhysicsChunkTerrainSettings().getTerrainTtlTicks()); - assertTrue(copy.getPhysicsChunkTerrainSettings().isNativeVoxelCollisionEnabled()); + assertEquals(PhysicsChunkTerrainMode.STREAMING, copy.getPhysicsChunkCollisionSettings().getMode()); + assertEquals(12, copy.getPhysicsChunkCollisionSettings().getRadius()); + assertEquals(6, copy.getPhysicsChunkCollisionSettings().getBodyRadius()); + assertEquals(180, copy.getPhysicsChunkCollisionSettings().getTtlTicks()); + assertTrue(copy.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); assertEquals(160, copy.getVisualSyncSettings().getVisualMaxSyncRadius()); assertEquals(80, copy.getVisualSyncSettings().getVisualFullSyncRadius()); assertEquals(2, copy.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); From 2856daa0dbe4dede2cb58c47914a1d53f6a32fe1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:18:15 +0200 Subject: [PATCH 432/534] refactor(core): rename physicschunk collision mode Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 22 +++++++++---------- .../crucible/ImpulseApiCrucibleTests.java | 8 +++---- ...tachedStreamingBenchmarkCrucibleTests.java | 4 ++-- ...pulseRapierBodyBenchmarkCrucibleTests.java | 4 ++-- .../commands/PhysicsChunkSettingsCommand.java | 12 +++++----- .../persistence/PersistentSpaceDto.java | 18 +++++++-------- .../PhysicsChunkSettingsIndexResource.java | 6 ++--- ...de.java => PhysicsChunkCollisionMode.java} | 8 +++---- .../physicschunk/PhysicsChunkTerrain.java | 2 +- .../ChunkCollisionSettingsComponent.java | 18 +++++++-------- .../PhysicsChunkCollisionSettings.java | 8 +++---- .../plugin/settings/PhysicsSpaceSettings.java | 6 ++--- .../commands/stress/StressBodiesCommand.java | 6 ++--- 13 files changed, 61 insertions(+), 61 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/{PhysicsChunkTerrainMode.java => PhysicsChunkCollisionMode.java} (50%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index d5399ae9..50d3a046 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.ArrayList; @@ -66,15 +66,15 @@ protected void execute(@Nonnull CommandContext context, return; } - PhysicsChunkTerrainMode physicsChunkMode = physicsChunkArg.provided(context) + PhysicsChunkCollisionMode physicsChunkMode = physicsChunkArg.provided(context) ? parsePhysicsChunkMode(physicsChunkArg.get(context)) - : PhysicsChunkTerrainMode.STREAMING; + : PhysicsChunkCollisionMode.STREAMING; if (physicsChunkMode == null) { context.sendMessage(Message.raw("physicsChunk must be none, manual, or streaming.")); return; } - PhysicsSpaceSettings settings = physicsChunkMode == PhysicsChunkTerrainMode.STREAMING + PhysicsSpaceSettings settings = physicsChunkMode == PhysicsChunkCollisionMode.STREAMING ? PhysicsSpaceSettings.streamingPhysicsChunk() : PhysicsSpaceSettings.defaults(); settings.getPhysicsChunkCollisionSettings().setMode(physicsChunkMode); @@ -119,9 +119,9 @@ private static void sendSpaces(@Nonnull CommandContext context, .map(summary -> { PhysicsSpaceSettings settings = PhysicsSpaces.settings(physicsStore, summary.spaceId()); - PhysicsChunkTerrainMode physicsChunkMode = settings != null + PhysicsChunkCollisionMode physicsChunkMode = settings != null ? settings.getPhysicsChunkCollisionSettings().getMode() - : PhysicsChunkTerrainMode.NONE; + : PhysicsChunkCollisionMode.NONE; return new SpaceListEntry(summary.spaceId(), summary.backendId().value(), summary.bodyCount(), @@ -272,11 +272,11 @@ private static BackendId parseBackendId(@Nonnull CommandContext context, } @Nullable - private static PhysicsChunkTerrainMode parsePhysicsChunkMode(@Nonnull String value) { + private static PhysicsChunkCollisionMode parsePhysicsChunkMode(@Nonnull String value) { return switch (value.toLowerCase(Locale.ROOT)) { - case "none", "off", "disabled" -> PhysicsChunkTerrainMode.NONE; - case "manual" -> PhysicsChunkTerrainMode.MANUAL; - case "streaming", "stream", "on", "enabled" -> PhysicsChunkTerrainMode.STREAMING; + case "none", "off", "disabled" -> PhysicsChunkCollisionMode.NONE; + case "manual" -> PhysicsChunkCollisionMode.MANUAL; + case "streaming", "stream", "on", "enabled" -> PhysicsChunkCollisionMode.STREAMING; default -> null; }; } @@ -298,6 +298,6 @@ private record SpaceListEntry(@Nonnull SpaceId spaceId, @Nonnull String backendId, int bodies, int joints, - @Nonnull PhysicsChunkTerrainMode physicsChunkMode) { + @Nonnull PhysicsChunkCollisionMode physicsChunkMode) { } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 7dbb9bbf..a7ea7599 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -30,7 +30,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import java.util.UUID; import java.util.Collection; import java.util.List; @@ -207,7 +207,7 @@ private static CompletionStage createdExplicitSpaceLifecycleWorks( boolean registered = PhysicsSpaces.hasSpace(store, spaceId) && spaceSettings != null && spaceSettings.getPhysicsChunkCollisionSettings().getMode() - == PhysicsChunkTerrainMode.STREAMING; + == PhysicsChunkCollisionMode.STREAMING; PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); return registered && !PhysicsSpaces.hasSpace(store, spaceId); }); @@ -334,7 +334,7 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte if (copy == null) { return false; } - return copy.getPhysicsChunkCollisionSettings().getMode() == PhysicsChunkTerrainMode.STREAMING + return copy.getPhysicsChunkCollisionSettings().getMode() == PhysicsChunkCollisionMode.STREAMING && copy.getPhysicsChunkCollisionSettings().getRadius() == 9 && copy.getPhysicsChunkCollisionSettings().getBodyRadius() == 5 && copy.getPhysicsChunkCollisionSettings().getTtlTicks() == 77 @@ -373,7 +373,7 @@ private static CompletionStage settingsRoundTrip(@Nonnull CrucibleConte @Nonnull private static PhysicsSpaceSettings populatedSettings() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.STREAMING); + settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); settings.getPhysicsChunkCollisionSettings().setRadius(9); settings.getPhysicsChunkCollisionSettings().setBodyRadius(5); settings.getPhysicsChunkCollisionSettings().setTtlTicks(77); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 0477a40c..5769c0e0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -34,7 +34,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -245,7 +245,7 @@ private CompletionStage startStageWhenReady(int count, int attempt int retained = retainChunks(chunks); configureMissingSectionDiagnostics(chunks); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.STREAMING); + settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); settings.getPhysicsChunkCollisionSettings().setBodyRadius(BODY_STREAMING_RADIUS); settings.getSolverSettings().setSolverIterations(4); settings.getSolverSettings().setStabilizationIterations(1); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 829b596b..756bf5dd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -30,7 +30,7 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.ArrayList; @@ -228,7 +228,7 @@ private CompletionStage startCase(@Nonnull MatrixCase matrixCase) { physics.clearSyntheticVisualInterests(); PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.NONE); + settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.NONE); settings.getSolverSettings().setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); settings.getSolverSettings().setStabilizationIterations( PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index c4189014..2c454c2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import java.util.Locale; @@ -89,7 +89,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - PhysicsChunkTerrainMode mode = settings.getMode(); + PhysicsChunkCollisionMode mode = settings.getMode(); if (modeArg.provided(ctx)) { mode = parseMode(modeArg.get(ctx)); if (mode == null) { @@ -181,11 +181,11 @@ private static void sendSummary(@Nonnull CommandContext ctx, } @Nullable - private static PhysicsChunkTerrainMode parseMode(@Nonnull String value) { + private static PhysicsChunkCollisionMode parseMode(@Nonnull String value) { return switch (value.toLowerCase(Locale.ROOT)) { - case "none", "off", "disabled" -> PhysicsChunkTerrainMode.NONE; - case "manual" -> PhysicsChunkTerrainMode.MANUAL; - case "streaming", "stream", "on", "enabled" -> PhysicsChunkTerrainMode.STREAMING; + case "none", "off", "disabled" -> PhysicsChunkCollisionMode.NONE; + case "manual" -> PhysicsChunkCollisionMode.MANUAL; + case "streaming", "stream", "on", "enabled" -> PhysicsChunkCollisionMode.STREAMING; default -> null; }; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index f977e6a5..ce90132a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; @@ -47,11 +47,11 @@ public final class PersistentSpaceDto { "Persisted PhysicsStore space gravity must be finite")) .add() .append(new KeyedCodec<>("PhysicsChunkTerrainMode", - new EnumCodec<>(PhysicsChunkTerrainMode.class), + new EnumCodec<>(PhysicsChunkCollisionMode.class), false), (dto, value) -> dto.terrainMode = value != null ? value - : PhysicsChunkTerrainMode.NONE, + : PhysicsChunkCollisionMode.NONE, PersistentSpaceDto::getMode) .add() .append(new KeyedCodec<>("EntityChunkBoundaryMode", @@ -149,7 +149,7 @@ public final class PersistentSpaceDto { @Nonnull private final Vector3f gravity = new Vector3f(0.0f, -9.81f, 0.0f); @Nonnull - private PhysicsChunkTerrainMode terrainMode = PhysicsChunkTerrainMode.NONE; + private PhysicsChunkCollisionMode terrainMode = PhysicsChunkCollisionMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; @@ -188,7 +188,7 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this(spaceUuid, backendId, gravity, - PhysicsChunkTerrainMode.NONE, + PhysicsChunkCollisionMode.NONE, PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED, PhysicsChunkCollisionSettings.DEFAULT_RADIUS, @@ -208,7 +208,7 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkTerrainMode terrainMode, + @Nonnull PhysicsChunkCollisionMode terrainMode, boolean nativeVoxelCollisionEnabled, int terrainRadius, int bodyTerrainRadius, @@ -238,7 +238,7 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkTerrainMode terrainMode, + @Nonnull PhysicsChunkCollisionMode terrainMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelCollisionEnabled, int terrainRadius, @@ -274,7 +274,7 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkTerrainMode terrainMode, + @Nonnull PhysicsChunkCollisionMode terrainMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelCollisionEnabled, int terrainRadius, @@ -330,7 +330,7 @@ public Vector3f getGravity() { } @Nonnull - public PhysicsChunkTerrainMode getMode() { + public PhysicsChunkCollisionMode getMode() { return terrainMode; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index d0d1b628..db6d26f5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -5,7 +5,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; @@ -36,7 +36,7 @@ public synchronized void replaceAll(@Nonnull Map streamingSpaces() { return settingsBySpaceUuid.values().stream() - .filter(settings -> settings.mode() == PhysicsChunkTerrainMode.STREAMING) + .filter(settings -> settings.mode() == PhysicsChunkCollisionMode.STREAMING) .toList(); } @@ -68,7 +68,7 @@ public static void setResourceType( } public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, - @Nonnull PhysicsChunkTerrainMode mode, + @Nonnull PhysicsChunkCollisionMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelCollisionEnabled, int radius, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionMode.java similarity index 50% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionMode.java index 220c7c15..5e88d811 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionMode.java @@ -3,19 +3,19 @@ /** * Controls PhysicsChunk collision body generation for a PhysicsStore space. */ -public enum PhysicsChunkTerrainMode { +public enum PhysicsChunkCollisionMode { /** - * Terrain colliders are disabled. + * Chunk-collision bodies are disabled. */ NONE, /** - * Terrain colliders are only built when explicitly requested. + * Chunk-collision bodies are only built when explicitly requested. */ MANUAL, /** - * Terrain colliders stream around players and configured physics bodies. + * Chunk-collision bodies stream around players and configured physics bodies. */ STREAMING } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java index d4442654..38c7f86d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java @@ -161,7 +161,7 @@ private static PhysicsChunkSpaceSettings requireSettings( store.getComponent(spaceRef, ChunkCollisionSettingsComponent.getComponentType()); ChunkCollisionSettingsComponent settings = component != null ? component : new ChunkCollisionSettingsComponent(); - if (settings.getMode() == PhysicsChunkTerrainMode.NONE) { + if (settings.getMode() == PhysicsChunkCollisionMode.NONE) { throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + spaceId); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java index 6646854b..fb1fcb0c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -24,10 +24,10 @@ public class ChunkCollisionSettingsComponent implements Component public static final BuilderCodec CODEC = BuilderCodec.builder( ChunkCollisionSettingsComponent.class, ChunkCollisionSettingsComponent::new) - .append(new KeyedCodec<>("Mode", new EnumCodec<>(PhysicsChunkTerrainMode.class), false), + .append(new KeyedCodec<>("Mode", new EnumCodec<>(PhysicsChunkCollisionMode.class), false), (component, value) -> component.mode = value != null ? value - : PhysicsChunkTerrainMode.NONE, + : PhysicsChunkCollisionMode.NONE, ChunkCollisionSettingsComponent::getMode) .add() .append(new KeyedCodec<>("NativeVoxelCollision", Codec.BOOLEAN, false), @@ -63,7 +63,7 @@ public class ChunkCollisionSettingsComponent implements Component .build(); @Nonnull - private PhysicsChunkTerrainMode mode = PhysicsChunkTerrainMode.NONE; + private PhysicsChunkCollisionMode mode = PhysicsChunkCollisionMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; @@ -85,7 +85,7 @@ public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkCollisionSettings se settings.getTtlTicks()); } - public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkCollisionMode mode, boolean nativeVoxelCollisionEnabled, int radius, int bodyRadius, @@ -98,7 +98,7 @@ public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, ttlTicks); } - public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, + public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkCollisionMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelCollisionEnabled, int radius, @@ -114,11 +114,11 @@ public ChunkCollisionSettingsComponent(@Nonnull PhysicsChunkTerrainMode mode, } @Nonnull - public PhysicsChunkTerrainMode getMode() { + public PhysicsChunkCollisionMode getMode() { return mode; } - public void setMode(@Nonnull PhysicsChunkTerrainMode mode) { + public void setMode(@Nonnull PhysicsChunkCollisionMode mode) { this.mode = Objects.requireNonNull(mode, "mode"); } @@ -179,7 +179,7 @@ public void copyTo(@Nonnull PhysicsChunkCollisionSettings settings) { } public boolean isDefault() { - return mode == PhysicsChunkTerrainMode.NONE + return mode == PhysicsChunkCollisionMode.NONE && entityChunkBoundaryMode == PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE && nativeVoxelCollisionEnabled diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java index 1d20c612..560f0908 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import lombok.Getter; import lombok.Setter; @@ -55,7 +55,7 @@ public class PhysicsChunkCollisionSettings { public static final boolean DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED = false; @Nonnull - private PhysicsChunkTerrainMode mode = PhysicsChunkTerrainMode.NONE; + private PhysicsChunkCollisionMode mode = PhysicsChunkCollisionMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; @Setter @@ -81,11 +81,11 @@ public PhysicsChunkCollisionSettings(@Nonnull PhysicsChunkCollisionSettings sett } @Nonnull - public PhysicsChunkTerrainMode getMode() { + public PhysicsChunkCollisionMode getMode() { return mode; } - public void setMode(@Nonnull PhysicsChunkTerrainMode mode) { + public void setMode(@Nonnull PhysicsChunkCollisionMode mode) { this.mode = Objects.requireNonNull(mode, "mode"); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java index 36d9f3c2..435f9131 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.settings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; @@ -20,7 +20,7 @@ * code should read and mutate the domain group directly instead of adding flat * shortcut state here.

        * - *

        Default settings have PhysicsChunk collision disabled ({@link PhysicsChunkTerrainMode#NONE}), + *

        Default settings have PhysicsChunk collision disabled ({@link PhysicsChunkCollisionMode#NONE}), * which keeps Impulse fully opt-in: no chunk-collision bodies are created unless the integrator * explicitly opts in.

        */ @@ -122,7 +122,7 @@ public static PhysicsSpaceSettings defaults() { public static PhysicsSpaceSettings streamingPhysicsChunk() { PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); settings.getPhysicsChunkCollisionSettings() - .setMode(PhysicsChunkTerrainMode.STREAMING); + .setMode(PhysicsChunkCollisionMode.STREAMING); return settings; } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 21f22c5a..1ccf8431 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; @@ -304,7 +304,7 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store Date: Fri, 19 Jun 2026 09:18:30 +0200 Subject: [PATCH 433/534] test(core): update physicschunk collision mode assertions Signed-off-by: Blovien --- .../plugin/settings/PhysicsSpaceSettingsTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index 0866b664..fd7795c0 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; @@ -164,7 +164,7 @@ void defaultsFactoryReturnsFreshDefaultSettings() { PhysicsSpaceSettings second = PhysicsSpaceSettings.defaults(); assertNotSame(first, second); - assertEquals(PhysicsChunkTerrainMode.NONE, first.getPhysicsChunkCollisionSettings().getMode()); + assertEquals(PhysicsChunkCollisionMode.NONE, first.getPhysicsChunkCollisionSettings().getMode()); assertSame(PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, first.getPhysicsChunkCollisionSettings().getEntityChunkBoundaryMode()); assertFalse(first.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); @@ -237,7 +237,7 @@ void collisionLodSettingsCopyConstructorCopiesValues() { void chunkCollisionSettingsCopyConstructorCopiesValues() { PhysicsChunkCollisionSettings canonical = new PhysicsChunkCollisionSettings(); - canonical.setMode(PhysicsChunkTerrainMode.STREAMING); + canonical.setMode(PhysicsChunkCollisionMode.STREAMING); canonical.setEntityChunkBoundaryMode(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK); canonical.setNativeVoxelCollisionEnabled(true); canonical.setRadius(18); @@ -251,7 +251,7 @@ void chunkCollisionSettingsCopyConstructorCopiesValues() { canonical.setRadius(24); canonicalCopy.setRadius(30); - assertEquals(PhysicsChunkTerrainMode.STREAMING, canonicalCopy.getMode()); + assertEquals(PhysicsChunkCollisionMode.STREAMING, canonicalCopy.getMode()); assertEquals(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK, canonicalCopy.getEntityChunkBoundaryMode()); assertTrue(canonicalCopy.isNativeVoxelCollisionEnabled()); @@ -296,7 +296,7 @@ void defaultsDoNotCarryBackendExtensionValues() { void streamingPhysicsChunkFactoryEnablesStreamingMode() { PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingPhysicsChunk(); - assertEquals(PhysicsChunkTerrainMode.STREAMING, settings.getPhysicsChunkCollisionSettings().getMode()); + assertEquals(PhysicsChunkCollisionMode.STREAMING, settings.getPhysicsChunkCollisionSettings().getMode()); assertEquals(PhysicsChunkCollisionSettings.DEFAULT_RADIUS, settings.getPhysicsChunkCollisionSettings().getRadius()); } @@ -304,7 +304,7 @@ void streamingPhysicsChunkFactoryEnablesStreamingMode() { @Test void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { PhysicsSpaceSettings original = new PhysicsSpaceSettings(); - original.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkTerrainMode.STREAMING); + original.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); original.getPhysicsChunkCollisionSettings().setRadius(12); original.getPhysicsChunkCollisionSettings().setBodyRadius(6); original.getPhysicsChunkCollisionSettings().setTtlTicks(180); @@ -336,7 +336,7 @@ void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { assertNotSame(original.getVisualMaterializationSettings(), copy.getVisualMaterializationSettings()); assertNotSame(original.getCollisionLodSettings(), copy.getCollisionLodSettings()); - assertEquals(PhysicsChunkTerrainMode.STREAMING, copy.getPhysicsChunkCollisionSettings().getMode()); + assertEquals(PhysicsChunkCollisionMode.STREAMING, copy.getPhysicsChunkCollisionSettings().getMode()); assertEquals(12, copy.getPhysicsChunkCollisionSettings().getRadius()); assertEquals(6, copy.getPhysicsChunkCollisionSettings().getBodyRadius()); assertEquals(180, copy.getPhysicsChunkCollisionSettings().getTtlTicks()); From 6f48ddb3eafc8ab6db993c36c56b003b0f658257 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:25:25 +0200 Subject: [PATCH 434/534] refactor(core): rename physicschunk collision facade Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 6 +- .../core/internal/commands/SpaceCommand.java | 8 +-- .../internal/commands/debug/DebugCommand.java | 6 +- ...tachedStreamingBenchmarkCrucibleTests.java | 72 +++++++++---------- ...pulseRapierBodyBenchmarkCrucibleTests.java | 64 ++++++++--------- .../PhysicsStoreBenchmarkQueries.java | 4 +- .../physicschunk/ChunkCollisionMutation.java | 2 +- .../physicschunk/PhysicsChunkBuildStats.java | 2 +- ...ysicsChunkCollisionStreamingResource.java} | 40 +++++------ .../PhysicsChunkStreamingBounds.java | 2 +- .../physicschunk/PhysicsChunkSubPlugin.java | 4 +- .../physicschunk/PhysicsChunkTypes.java | 14 ++-- .../commands/PhysicsChunkCommand.java | 2 +- .../PhysicsChunkPerfReportCommand.java | 26 +++---- .../PhysicsChunkPerfResetCommand.java | 4 +- .../PhysicsChunkPerfToggleCommand.java | 6 +- .../PhysicsChunkProfilingResource.java | 26 +++---- ... PhysicsChunkCollisionProducerSystem.java} | 18 ++--- .../impulse/core/internal/package-info.java | 2 +- .../resources/PhysicsDebugResource.java | 4 +- .../PhysicsWorldRuntimeResource.java | 28 ++++---- .../body/PhysicsBodySpatialIndex.java | 2 +- .../systems/debug/PhysicsDebugRenderer.java | 4 +- .../systems/debug/PhysicsDebugSystem.java | 14 ++-- .../debug/PhysicsStoreDebugQueries.java | 4 +- ...errain.java => PhysicsChunkCollision.java} | 42 +++++------ ...a => PhysicsChunkCollisionBuildStats.java} | 4 +- .../PhysicsChunkCollisionPrewarmStats.java | 10 +++ ...va => PhysicsChunkCollisionProfiling.java} | 26 +++---- ...s.java => PhysicsChunkCollisionStats.java} | 4 +- .../PhysicsChunkTerrainPrewarmStats.java | 10 --- .../modules/physicschunk/package-info.java | 2 +- .../commands/PhysicsChunkExampleCommand.java | 36 +++++----- .../commands/PhysicsStoreExampleCommands.java | 8 +-- .../commands/stress/StressBodiesCommand.java | 6 +- .../explosive/ExplosiveBlockRuntime.java | 6 +- 36 files changed, 259 insertions(+), 259 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/{PhysicsChunkTerrainStreamingResource.java => PhysicsChunkCollisionStreamingResource.java} (85%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/{PhysicsChunkTerrainProducerSystem.java => PhysicsChunkCollisionProducerSystem.java} (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/{PhysicsChunkTerrain.java => PhysicsChunkCollision.java} (85%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/{PhysicsChunkTerrainBuildStats.java => PhysicsChunkCollisionBuildStats.java} (88%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionPrewarmStats.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/{PhysicsChunkTerrainProfiling.java => PhysicsChunkCollisionProfiling.java} (93%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/{PhysicsChunkTerrainStats.java => PhysicsChunkCollisionStats.java} (67%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 8f23c00c..1b9860ac 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -44,14 +44,14 @@ * Clears Impulse-owned runtime state from the target world. * *

        This removes Impulse-owned visual entities, detaches external physics attachments, - * clears runtime bodies, joints, and current PhysicsChunk terrain cache bodies. Explicit + * clears runtime bodies, joints, and current PhysicsChunk collision cache bodies. Explicit * physics spaces are kept, including their PhysicsChunk collision settings. Spaces with streaming * PhysicsChunk collision enabled may build fresh backend terrain bodies again on the next * streaming tick.

        * *

        When a radius is provided, cleanup is intentionally narrower: it selects * registered body snapshots near the player, removes those bodies and their - * attachments/proxies, and leaves spaces plus the PhysicsChunk terrain cache intact.

        + * attachments/proxies, and leaves spaces plus the PhysicsChunk collision cache intact.

        */ public class CleanCommand extends AbstractWorldCommand { @@ -363,7 +363,7 @@ private static void sendCleanRadiusSuccess(@Nonnull CommandContext context, + result.removedBodies() + " runtime bodies, and " + removedEntities.get(REMOVED_SESSIONS) + " control sessions within radius " + radius + " in world " + worldName - + ". Kept explicit physics spaces and PhysicsChunk terrain cache.")); + + ". Kept explicit physics spaces and PhysicsChunk collision cache.")); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 50d3a046..e4e0bedf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -22,7 +22,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; @@ -51,7 +51,7 @@ private static final class CreateCommand extends AbstractWorldCommand { ArgTypes.STRING); private final OptionalArg physicsChunkArg = withOptionalArg( "physicsChunk", - "PhysicsChunk terrain mode: none, manual, or streaming", + "PhysicsChunk collision mode: none, manual, or streaming", ArgTypes.STRING); private CreateCommand() { super("create", "Create an explicit physics space", false); @@ -179,7 +179,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, SpaceId spaceId = selectedSpace.spaceId(); /* - * Backend-only bodies can be generated by systems such as streaming PhysicsChunk terrain. + * Backend-only bodies can be generated by systems such as streaming PhysicsChunk collision. * Those bodies belong to the space/cache lifecycle and are removed when the space is * deleted. Registered bodies are gameplay/runtime resources addressed by durable body * UUID or live PhysicsStore entity ref, so they still require an explicit clean/destroy @@ -215,7 +215,7 @@ private static void deleteIfEmpty(@Nonnull CommandContext context, return; } - PhysicsChunkTerrain.clearSpace(world, physicsStore, spaceId); + PhysicsChunkCollision.clearSpace(world, physicsStore, spaceId); PhysicsSpaces.removeWithContents(physicsStore, spaceId); context.sendMessage(Message.raw("Deleted physics space id=" + rawSpaceId + " with " + backendBodies + " backend bodies and " + joints + " joints.")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java index a63cc1cf..b498ffa8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java @@ -20,8 +20,8 @@ public DebugCommand() { addSubCommand(new DebugFlagCommand("joints", "joint", PhysicsDebugResource::isDebugJointsEnabled, PhysicsDebugResource::setDebugJointsEnabled)); - addSubCommand(new DebugFlagCommand("physicschunk", "PhysicsChunk terrain", - PhysicsDebugResource::isDebugPhysicsChunkTerrainEnabled, - PhysicsDebugResource::setDebugPhysicsChunkTerrainEnabled)); + addSubCommand(new DebugFlagCommand("physicschunk", "PhysicsChunk collision", + PhysicsDebugResource::isDebugPhysicsChunkCollisionEnabled, + PhysicsDebugResource::setDebugPhysicsChunkCollisionEnabled)); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 5769c0e0..58ddad36 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; @@ -28,7 +28,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; @@ -55,7 +55,7 @@ import org.joml.Vector3f; /** - * Benchmark-oriented Crucible scenario for detached bodies using streamed PhysicsChunk terrain. + * Benchmark-oriented Crucible scenario for detached bodies using streamed PhysicsChunk collision. */ @SuppressWarnings("SameParameterValue") final class ImpulseDetachedStreamingBenchmarkCrucibleTests { @@ -143,8 +143,8 @@ private static final class StageRunner { private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final PhysicsChunkProfilingResource terrainProfiling; - private final PhysicsChunkTerrainStreamingResource terrainStreaming; + private final PhysicsChunkProfilingResource collisionProfiling; + private final PhysicsChunkCollisionStreamingResource collisionStreaming; private final PhysicsWorldSettings previousWorldSettings; private final boolean previousPhysicsStoreProfilingEnabled; private final List retainedChunks = new ArrayList<>(); @@ -160,10 +160,10 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) this.physicsStoreProfiling = physicsStore.getResource( PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); - this.terrainProfiling = store.getResource( + this.collisionProfiling = store.getResource( PhysicsChunkProfilingResource.getResourceType()); - this.terrainStreaming = store.getResource( - PhysicsChunkTerrainStreamingResource.getResourceType()); + this.collisionStreaming = store.getResource( + PhysicsChunkCollisionStreamingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); } @@ -193,10 +193,10 @@ private CompletionStage runStage(int stageIndex, .thenCompose(started -> contextWait(plan.warmupTicks()).thenCompose(_ -> { physicsStoreProfiling.reset(); runtimeProfiling.reset(); - terrainProfiling.reset(); + collisionProfiling.reset(); physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); - terrainProfiling.setEnabled(true); + collisionProfiling.setEnabled(true); long startedNanos = System.nanoTime(); return contextWait(plan.sampleTicks()).thenApply( _ -> finishStage(count, started, startedNanos)); @@ -259,14 +259,14 @@ private CompletionStage startStageWhenReady(int count, int attempt SpaceId spaceId = physics.createSpace(CrucibleBackends.requireBackendId(), world.getName(), settings); - PrewarmStats prewarm = prewarmPhysicsChunkTerrain(spaceId, count); + PrewarmStats prewarm = prewarmPhysicsChunkCollision(spaceId, count); spawnDetachedBodies(spaceId, count); physicsStoreProfiling.reset(); runtimeProfiling.reset(); - terrainProfiling.reset(); + collisionProfiling.reset(); physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); - terrainProfiling.setEnabled(true); + collisionProfiling.setEnabled(true); return CompletableFuture.completedFuture( StartedStage.started(spaceId, chunks, retained, prewarm)); } @@ -284,19 +284,19 @@ private StageReport finishStage(int count, StepSnapshot step = runtimeProfiling.getCumulativeStep(); SyncSnapshot sync = runtimeProfiling.getCumulativeSync(); - Snapshot terrainProfilingSnapshot = terrainProfiling.getCumulativeSnapshot(); + Snapshot collisionProfilingSnapshot = collisionProfiling.getCumulativeSnapshot(); double elapsedSeconds = Math.max(0.001, (System.nanoTime() - startedNanos) / 1_000_000_000.0); double observedTickRate = step.getTickSamples() / elapsedSeconds; - SpaceStats stats = SpaceStats.collect(physicsStore, terrainStreaming, spaceId); + SpaceStats stats = SpaceStats.collect(physicsStore, collisionStreaming, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); double avgRegistrationPublicationMs = averageMillis( step.getRegistrationPublicationNanos(), step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); - double avgTerrainMs = averageMillis(terrainProfilingSnapshot.getTickNanos(), - terrainProfilingSnapshot.getTickSamples()); + double avgTerrainMs = averageMillis(collisionProfilingSnapshot.getTickNanos(), + collisionProfilingSnapshot.getTickSamples()); double totalMs = avgStepMs + avgSnapshotMs + avgRegistrationPublicationMs @@ -305,7 +305,7 @@ private StageReport finishStage(int count, StageHealth health = assessHealth(count, observedTickRate, stats, - terrainProfilingSnapshot.getMissingChunks()); + collisionProfilingSnapshot.getMissingChunks()); assert started.chunks() != null; assert started.prewarm() != null; @@ -331,17 +331,17 @@ private StageReport finishStage(int count, stats.terrainBaselineBodies, stats.missingTerrainBaselineBodies, stats.minTerrainBottomClearance(), - terrainProfilingSnapshot.getTickSamples(), - terrainProfilingSnapshot.getEnsureCalls(), - terrainProfilingSnapshot.getSectionRequests(), - terrainProfilingSnapshot.getSectionCacheHits(), - terrainProfilingSnapshot.getSectionsBuilt(), - terrainProfilingSnapshot.getMissingChunks(), - terrainProfilingSnapshot.getMissingBlockChunks(), - terrainProfilingSnapshot.getMissingBlockSections(), - terrainProfilingSnapshot.getUniqueMissingSections(), - terrainProfilingSnapshot.getMissingOutsideRetainedEnvelope(), - terrainProfilingSnapshot.getBodyStreamingTargets(), + collisionProfilingSnapshot.getTickSamples(), + collisionProfilingSnapshot.getEnsureCalls(), + collisionProfilingSnapshot.getSectionRequests(), + collisionProfilingSnapshot.getSectionCacheHits(), + collisionProfilingSnapshot.getSectionsBuilt(), + collisionProfilingSnapshot.getMissingChunks(), + collisionProfilingSnapshot.getMissingBlockChunks(), + collisionProfilingSnapshot.getMissingBlockSections(), + collisionProfilingSnapshot.getUniqueMissingSections(), + collisionProfilingSnapshot.getMissingOutsideRetainedEnvelope(), + collisionProfilingSnapshot.getBodyStreamingTargets(), health); } @@ -358,8 +358,8 @@ private void clearStageState() { PhysicsStoreCrucibleSupport.clearAll(physicsStore); physicsStoreProfiling.reset(); runtimeProfiling.reset(); - terrainProfiling.reset(); - terrainProfiling.clearDiagnosticRetainedSections(); + collisionProfiling.reset(); + collisionProfiling.clearDiagnosticRetainedSections(); } private void restoreStepSettings() { @@ -367,14 +367,14 @@ private void restoreStepSettings() { physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); } - private PrewarmStats prewarmPhysicsChunkTerrain(@Nonnull SpaceId spaceId, int count) { + private PrewarmStats prewarmPhysicsChunkCollision(@Nonnull SpaceId spaceId, int count) { BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(physicsStore, spaceId); PhysicsChunkCollisionMutationQueueResource queue = physicsStore.getResource( PhysicsChunkCollisionMutationQueueResource.getResourceType()); PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( physics.getSpaceSettings(spaceId).getPhysicsChunkCollisionSettings()); - PhysicsChunkTerrainPrewarmStats stats = terrainStreaming.ensureAround(world, + PhysicsChunkCollisionPrewarmStats stats = collisionStreaming.ensureAround(world, spaceUuid, queue, prewarmCenters(layout, count), @@ -439,7 +439,7 @@ private void configureMissingSectionDiagnostics(@Nonnull BenchmarkChunks chunks) section.y(), section.z())); } - terrainProfiling.setDiagnosticRetainedSections(sectionKeys); + collisionProfiling.setDiagnosticRetainedSections(sectionKeys); } private void spawnDetachedBodies(@Nonnull SpaceId spaceId, int count) { @@ -951,11 +951,11 @@ private static final class SpaceStats { private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; private static SpaceStats collect(@Nonnull Store physicsStore, - @Nonnull PhysicsChunkTerrainStreamingResource terrainStreaming, + @Nonnull PhysicsChunkCollisionStreamingResource collisionStreaming, @Nonnull SpaceId spaceId) { BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( physicsStore, - terrainStreaming, + collisionStreaming, new PhysicsStoreBenchmarkQueries.BenchmarkSpaceStatsRequest(spaceId, GROUND_Y, BELOW_PLANE_TOLERANCE, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 756bf5dd..2c51227b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -143,7 +143,7 @@ private static final class MatrixRunner { private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final PhysicsChunkProfilingResource terrainProfiling; + private final PhysicsChunkProfilingResource collisionProfiling; private final PhysicsWorldSettings previousWorldSettings; private final boolean previousPhysicsStoreProfilingEnabled; private final boolean previousRuntimeProfilingEnabled; @@ -160,12 +160,12 @@ private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) this.physicsStoreProfiling = physicsStore.getResource( PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); - this.terrainProfiling = store.getResource( + this.collisionProfiling = store.getResource( PhysicsChunkProfilingResource.getResourceType()); this.previousWorldSettings = physics.getWorldSettings(); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); this.previousRuntimeProfilingEnabled = runtimeProfiling.isEnabled(); - this.previousTerrainProfilingEnabled = terrainProfiling.isEnabled(); + this.previousTerrainProfilingEnabled = collisionProfiling.isEnabled(); } private CompletionStage run() { @@ -197,10 +197,10 @@ private CompletionStage runCase(int index, .thenCompose(_ -> { physicsStoreProfiling.reset(); runtimeProfiling.reset(); - terrainProfiling.reset(); + collisionProfiling.reset(); physicsStoreProfiling.setEnabled(true); runtimeProfiling.setEnabled(true); - terrainProfiling.setEnabled(true); + collisionProfiling.setEnabled(true); long startedNanos = System.nanoTime(); return contextWait(plan.sampleTicks()).thenApply( _ -> finishCase(matrixCase, started, startedNanos)); @@ -304,7 +304,7 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, StepSnapshot step = runtimeProfiling.getCumulativeStep(); SyncSnapshot sync = runtimeProfiling.getCumulativeSync(); - Snapshot terrainProfilingSnapshot = terrainProfiling.getCumulativeSnapshot(); + Snapshot collisionProfilingSnapshot = collisionProfiling.getCumulativeSnapshot(); double elapsedSeconds = Math.max(0.001, (System.nanoTime() - startedNanos) / 1_000_000_000.0); double observedTickRate = step.getTickSamples() / elapsedSeconds; @@ -315,8 +315,8 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, step.getRegistrationPublicationNanos(), step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); - double avgTerrainMs = averageMillis(terrainProfilingSnapshot.getTickNanos(), - terrainProfilingSnapshot.getTickSamples()); + double avgTerrainMs = averageMillis(collisionProfilingSnapshot.getTickNanos(), + collisionProfilingSnapshot.getTickSamples()); double totalMs = avgStepMs + avgSnapshotMs + avgRegistrationPublicationMs @@ -325,7 +325,7 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, MatrixHealth health = assessHealth(matrixCase, observedTickRate, step, - terrainProfilingSnapshot, + collisionProfilingSnapshot, stats); return new MatrixReport(matrixCase, @@ -343,12 +343,12 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, sync.getTickSamples(), sync.getBodiesInspected(), sync.getBodiesSynced(), - terrainProfilingSnapshot.getTickSamples(), - terrainProfilingSnapshot.getStreamingSpaces(), - terrainProfilingSnapshot.getEnsureCalls(), - terrainProfilingSnapshot.getSectionRequests(), - terrainProfilingSnapshot.getSectionsBuilt(), - terrainProfilingSnapshot.getBodyStreamingTargets(), + collisionProfilingSnapshot.getTickSamples(), + collisionProfilingSnapshot.getStreamingSpaces(), + collisionProfilingSnapshot.getEnsureCalls(), + collisionProfilingSnapshot.getSectionRequests(), + collisionProfilingSnapshot.getSectionsBuilt(), + collisionProfilingSnapshot.getBodyStreamingTargets(), stats.bodies, stats.dynamicBodies, stats.detachedBodies, @@ -383,15 +383,15 @@ private void clearCaseState() { PhysicsStoreCrucibleSupport.clearAll(physicsStore); physicsStoreProfiling.reset(); runtimeProfiling.reset(); - terrainProfiling.reset(); - terrainProfiling.clearDiagnosticRetainedSections(); + collisionProfiling.reset(); + collisionProfiling.clearDiagnosticRetainedSections(); } private void restoreSettings() { physics.setWorldSettings(previousWorldSettings); physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); runtimeProfiling.setEnabled(previousRuntimeProfilingEnabled); - terrainProfiling.setEnabled(previousTerrainProfilingEnabled); + collisionProfiling.setEnabled(previousTerrainProfilingEnabled); } private void removeBenchmarkEntities() { @@ -425,14 +425,14 @@ private static CrucibleTestCase.TestOutcome outcome(@Nonnull List private static MatrixHealth assessHealth(@Nonnull MatrixCase matrixCase, double observedTickRate, @Nonnull StepSnapshot step, - @Nonnull Snapshot terrainProfilingSnapshot, + @Nonnull Snapshot collisionProfilingSnapshot, @Nonnull SpaceStats stats) { List stops = new ArrayList<>(); List warnings = new ArrayList<>(); if (step.getTickSamples() <= 0) { stops.add("stepSamples=0"); } - if (terrainProfilingSnapshot.getTickSamples() <= 0) { + if (collisionProfilingSnapshot.getTickSamples() <= 0) { stops.add("terrainSamples=0"); } if (stats.dynamicBodies != matrixCase.count()) { @@ -453,17 +453,17 @@ private static MatrixHealth assessHealth(@Nonnull MatrixCase matrixCase, if (step.getTickSamples() > 0 && step.getBodySnapshots() != expectedSnapshots) { stops.add("bodySnapshots=" + step.getBodySnapshots() + "!=" + expectedSnapshots); } - if (terrainProfilingSnapshot.getStreamingSpaces() > 0) { - stops.add("terrainStreamingSpaces=" + terrainProfilingSnapshot.getStreamingSpaces()); + if (collisionProfilingSnapshot.getStreamingSpaces() > 0) { + stops.add("collisionStreamingSpaces=" + collisionProfilingSnapshot.getStreamingSpaces()); } - if (terrainProfilingSnapshot.getEnsureCalls() > 0) { - stops.add("terrainEnsureCalls=" + terrainProfilingSnapshot.getEnsureCalls()); + if (collisionProfilingSnapshot.getEnsureCalls() > 0) { + stops.add("terrainEnsureCalls=" + collisionProfilingSnapshot.getEnsureCalls()); } - if (terrainProfilingSnapshot.getSectionsBuilt() > 0) { - stops.add("terrainSectionsBuilt=" + terrainProfilingSnapshot.getSectionsBuilt()); + if (collisionProfilingSnapshot.getSectionsBuilt() > 0) { + stops.add("terrainSectionsBuilt=" + collisionProfilingSnapshot.getSectionsBuilt()); } - if (terrainProfilingSnapshot.getBodyStreamingTargets() > 0) { - stops.add("terrainBodyTargets=" + terrainProfilingSnapshot.getBodyStreamingTargets()); + if (collisionProfilingSnapshot.getBodyStreamingTargets() > 0) { + stops.add("terrainBodyTargets=" + collisionProfilingSnapshot.getBodyStreamingTargets()); } if (stats.terrainBodies > 0) { stops.add("terrainBodies=" + stats.terrainBodies); @@ -507,7 +507,7 @@ private static void logComparison(@Nonnull List reports) { MatrixReport second = reports.get(1); LOGGER.at(Level.INFO).log("Crucible Rapier body matrix comparison: %sx=%sms " + "%sx=%sms stepRatio=%s snapshotRatio=%s registrationRatio=%s " - + "totalRatio=%s terrainCounters=%s/%s", + + "totalRatio=%s collisionCounters=%s/%s", first.matrixCase().fixedSubsteps(), format(first.avgStepMs()), second.matrixCase().fixedSubsteps(), @@ -638,7 +638,7 @@ private record MatrixReport(@Nonnull MatrixCase matrixCase, int syncInspected, int syncSynced, int terrainSamples, - int terrainStreamingSpaces, + int collisionStreamingSpaces, int terrainEnsureCalls, int terrainSectionRequests, int terrainSectionsBuilt, @@ -717,7 +717,7 @@ private String summary() { + "/" + syncSynced + " terrain samples/streaming/ensure/req/build/bodyTargets=" + terrainSamples - + "/" + terrainStreamingSpaces + + "/" + collisionStreamingSpaces + "/" + terrainEnsureCalls + "/" + terrainSectionRequests + "/" + terrainSectionsBuilt @@ -738,7 +738,7 @@ private String summary() { private String terrainCounterSummary() { return terrainSamples - + "/" + terrainStreamingSpaces + + "/" + collisionStreamingSpaces + "/" + terrainEnsureCalls + "/" + terrainSectionsBuilt + "/" + terrainBodyTargets; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index 5bc7beab..a293127e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -7,7 +7,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -32,7 +32,7 @@ private PhysicsStoreBenchmarkQueries() { @Nonnull static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store store, - @Nullable PhysicsChunkTerrainStreamingResource streaming, + @Nullable PhysicsChunkCollisionStreamingResource streaming, @Nonnull BenchmarkSpaceStatsRequest query) { PhysicsThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, query.spaceId()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java index 3443b874..068ba86b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java @@ -7,7 +7,7 @@ import javax.annotation.Nullable; /** - * Copied chunk collision mutation emitted from PhysicsChunk terrain code. + * Copied chunk collision mutation emitted from PhysicsChunk collision code. */ public record ChunkCollisionMutation(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java index c9a243d8..e3729cdf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkBuildStats.java @@ -3,7 +3,7 @@ import javax.annotation.Nonnull; /** - * Aggregate statistics from a PhysicsChunk terrain build or rebuild operation. + * Aggregate statistics from a PhysicsChunk collision build or rebuild operation. */ public record PhysicsChunkBuildStats(int scannedBlocks, int solidBlocks, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java index 3de0e9fc..153e1c67 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTerrainStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java @@ -10,9 +10,9 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionStats; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.Objects; @@ -25,20 +25,20 @@ /** * Shared EntityStore-side producer state for copied PhysicsStore chunk collision mutations. */ -public final class PhysicsChunkTerrainStreamingResource implements Resource { +public final class PhysicsChunkCollisionStreamingResource implements Resource { @Nonnull private final PhysicsChunkMutationCache cache = new PhysicsChunkMutationCache(); private long tick; @Nullable - private static ResourceType resourceType; + private static ResourceType resourceType; - public PhysicsChunkTerrainStreamingResource() { + public PhysicsChunkCollisionStreamingResource() { } public static void setResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { resourceType = Objects.requireNonNull(type, "type"); } @@ -47,9 +47,9 @@ public static void clearResourceType() { } @Nonnull - public static ResourceType getResourceType() { + public static ResourceType getResourceType() { if (resourceType == null) { - throw new IllegalStateException("PhysicsStore PhysicsChunk terrain streaming resource is not registered"); + throw new IllegalStateException("PhysicsStore PhysicsChunk collision streaming resource is not registered"); } return resourceType; } @@ -64,7 +64,7 @@ public synchronized void retainSpaces(@Nonnull Set retainedSpaces, } @Nonnull - public synchronized PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, + public synchronized PhysicsChunkCollisionPrewarmStats ensureAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Iterable centers, @@ -88,11 +88,11 @@ public synchronized PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World accessCache, buildOptions)); } - return new PhysicsChunkTerrainPrewarmStats(visitedSections.size(), terrainStats(total)); + return new PhysicsChunkCollisionPrewarmStats(visitedSections.size(), collisionStats(total)); } @Nonnull - public synchronized PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, + public synchronized PhysicsChunkCollisionBuildStats refreshAround(@Nonnull World world, @Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull Vector3d center, @@ -113,7 +113,7 @@ public synchronized PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World w null, accessCache, buildOptions); - return terrainStats(withRemovedBodies(stats, stats.removedBodies() + removed)); + return collisionStats(withRemovedBodies(stats, stats.removedBodies() + removed)); } @Nonnull @@ -231,8 +231,8 @@ public synchronized int clearSpace(@Nonnull UUID spaceUuid, } @Nonnull - public synchronized PhysicsChunkTerrainStats stats() { - return new PhysicsChunkTerrainStats(cache.spaceCount(), + public synchronized PhysicsChunkCollisionStats stats() { + return new PhysicsChunkCollisionStats(cache.spaceCount(), cache.sectionCount(), cache.bodyCount(), cache.shapeTemplateCount()); @@ -244,16 +244,16 @@ public synchronized int bodyCount(@Nonnull UUID spaceUuid) { @Nonnull @Override - public synchronized PhysicsChunkTerrainStreamingResource clone() { - PhysicsChunkTerrainStreamingResource copy = - new PhysicsChunkTerrainStreamingResource(); + public synchronized PhysicsChunkCollisionStreamingResource clone() { + PhysicsChunkCollisionStreamingResource copy = + new PhysicsChunkCollisionStreamingResource(); copy.tick = tick; return copy; } @Nonnull - private static PhysicsChunkTerrainBuildStats terrainStats(@Nonnull PhysicsChunkBuildStats stats) { - return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), + private static PhysicsChunkCollisionBuildStats collisionStats(@Nonnull PhysicsChunkBuildStats stats) { + return new PhysicsChunkCollisionBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), stats.fullCubeRuns(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBounds.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBounds.java index b2404221..04583aa9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBounds.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStreamingBounds.java @@ -5,7 +5,7 @@ import org.joml.Vector3f; /** - * Chunk/section neighborhood covered by a streamed PhysicsChunk terrain target. + * Chunk/section neighborhood covered by a streamed PhysicsChunk collision target. * *

        Two body targets with the same bounds would trigger the same section * collision requests, so the streaming system can deduplicate them.

        diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java index bb1d3349..2adea839 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java @@ -10,7 +10,7 @@ import javax.annotation.Nonnull; /** - * Bundled subplugin that enables Impulse PhysicsChunk terrain integration. + * Bundled subplugin that enables Impulse PhysicsChunk collision integration. */ public final class PhysicsChunkSubPlugin extends JavaPlugin { @@ -27,7 +27,7 @@ protected void setup() { PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); PhysicsChunkCommandContributions.register(); PhysicsChunkLifecycle.enable(); - LOGGER.at(Level.INFO).log("Impulse PhysicsChunk terrain producer enabled."); + LOGGER.at(Level.INFO).log("Impulse PhysicsChunk collision producer enabled."); } @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java index 0935ed60..c0ec1939 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java @@ -2,9 +2,9 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsChunkTerrainProducerSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsChunkCollisionProducerSystem; import javax.annotation.Nonnull; /** @@ -20,18 +20,18 @@ public static void registerEntityStoreResourceTypes( PhysicsChunkProfilingResource.setResourceType(registry.registerResource( PhysicsChunkProfilingResource.class, PhysicsChunkProfilingResource::new)); - PhysicsChunkTerrainStreamingResource.setResourceType(registry.registerResource( - PhysicsChunkTerrainStreamingResource.class, - PhysicsChunkTerrainStreamingResource::new)); + PhysicsChunkCollisionStreamingResource.setResourceType(registry.registerResource( + PhysicsChunkCollisionStreamingResource.class, + PhysicsChunkCollisionStreamingResource::new)); } public static void registerEntityStoreSystems( @Nonnull ComponentRegistryProxy registry) { - registry.registerSystem(new PhysicsChunkTerrainProducerSystem()); + registry.registerSystem(new PhysicsChunkCollisionProducerSystem()); } public static void clearEntityStoreResourceTypes() { PhysicsChunkProfilingResource.clearResourceType(); - PhysicsChunkTerrainStreamingResource.clearResourceType(); + PhysicsChunkCollisionStreamingResource.clearResourceType(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java index ab167c61..29a25ced 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommand.java @@ -5,7 +5,7 @@ public final class PhysicsChunkCommand extends AbstractCommandCollection { public PhysicsChunkCommand() { - super("physicschunk", "Impulse PhysicsChunk terrain commands"); + super("physicschunk", "Impulse PhysicsChunk collision commands"); addSubCommand(new PhysicsChunkSettingsCommand()); addSubCommand(new PhysicsChunkPerfCommand()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java index e34335d0..91924390 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.StepSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.SyncSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.VisualSnapshotView; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionProfiling; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; @@ -56,8 +56,8 @@ private static void sendReport(@Nonnull CommandContext ctx, VisualSnapshotView cumulativeVisual = runtimeProfiling.cumulativeVisual(); VisualSnapshotView latestVisual = runtimeProfiling.latestVisual(); VisualSnapshotView worstVisual = runtimeProfiling.worstVisual(); - PhysicsChunkTerrainProfiling.Snapshots profiling = - PhysicsChunkTerrainProfiling.snapshots(store); + PhysicsChunkCollisionProfiling.Snapshots profiling = + PhysicsChunkCollisionProfiling.snapshots(store); var cumulative = profiling.cumulative(); var latest = profiling.latest(); var worst = profiling.worst(); @@ -261,8 +261,8 @@ private static void sendReport(@Nonnull CommandContext ctx, + "/" + cumulative.getBodySpatialIndexCandidates() + "/" + cumulative.getBodyStreamingTargets() + " spaces=" + cumulative.getStreamingSpaces() - + " terrainApply queued/skipped=" + cumulative.getTerrainApplyQueued() - + "/" + cumulative.getTerrainApplySkippedPending() + + " collisionApply queued/skipped=" + cumulative.getCollisionApplyQueued() + + "/" + cumulative.getCollisionApplySkippedPending() + " sectionTargets player/body=" + cumulative.getPlayerSectionTargets() + "/" + cumulative.getBodySectionTargets() + " ensureCalls=" + cumulative.getEnsureCalls() @@ -296,8 +296,8 @@ private static void sendReport(@Nonnull CommandContext ctx, + cumulative.getMissingInsideRetainedEnvelope() + "/" + cumulative.getMissingOutsideRetainedEnvelope() + "/" + cumulative.getMissingUnconfiguredRetainedEnvelope())); - List missingSectionSamples = - PhysicsChunkTerrainProfiling.missingSectionSamples(cumulative); + List missingSectionSamples = + PhysicsChunkCollisionProfiling.missingSectionSamples(cumulative); if (!missingSectionSamples.isEmpty()) { ctx.sender().sendMessage(Message.raw("Missing section samples: " + formatMissingSectionSamples(missingSectionSamples))); @@ -327,8 +327,8 @@ private static void sendReport(@Nonnull CommandContext ctx, + "/" + latest.getBodySpatialIndexCandidates() + "/" + latest.getBodyStreamingTargets() + " spaces=" + latest.getStreamingSpaces() - + " terrainApply queued/skipped=" + latest.getTerrainApplyQueued() - + "/" + latest.getTerrainApplySkippedPending() + + " collisionApply queued/skipped=" + latest.getCollisionApplyQueued() + + "/" + latest.getCollisionApplySkippedPending() + " sectionTargets player/body=" + latest.getPlayerSectionTargets() + "/" + latest.getBodySectionTargets() + " ensure=" + latest.getEnsureCalls() @@ -350,8 +350,8 @@ private static void sendReport(@Nonnull CommandContext ctx, + "/" + worst.getBodySpatialIndexCandidates() + "/" + worst.getBodyStreamingTargets() + " spaces=" + worst.getStreamingSpaces() - + " terrainApply queued/skipped=" + worst.getTerrainApplyQueued() - + "/" + worst.getTerrainApplySkippedPending() + + " collisionApply queued/skipped=" + worst.getCollisionApplyQueued() + + "/" + worst.getCollisionApplySkippedPending() + " sectionTargets player/body=" + worst.getPlayerSectionTargets() + "/" + worst.getBodySectionTargets() + " ensure=" + worst.getEnsureCalls() @@ -371,10 +371,10 @@ private static void sendReport(@Nonnull CommandContext ctx, @Nonnull private static String formatMissingSectionSamples( - @Nonnull List samples) { + @Nonnull List samples) { StringBuilder builder = new StringBuilder(); int emitted = 0; - for (PhysicsChunkTerrainProfiling.MissingSectionSampleView sample : samples) { + for (PhysicsChunkCollisionProfiling.MissingSectionSampleView sample : samples) { if (emitted > 0) { builder.append(" | "); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java index f98f9acf..2d69063f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfResetCommand.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionProfiling; import javax.annotation.Nonnull; public class PhysicsChunkPerfResetCommand extends AbstractWorldCommand { @@ -19,7 +19,7 @@ public PhysicsChunkPerfResetCommand() { protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { - PhysicsChunkTerrainProfiling.resetRuntimeProfiling(world, store); + PhysicsChunkCollisionProfiling.resetRuntimeProfiling(world, store); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling counters reset")); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java index 3d5b500c..340fda5c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfToggleCommand.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainProfiling; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionProfiling; import javax.annotation.Nonnull; public class PhysicsChunkPerfToggleCommand extends AbstractWorldCommand { @@ -19,8 +19,8 @@ public PhysicsChunkPerfToggleCommand() { protected void execute(@Nonnull CommandContext ctx, @Nonnull World world, @Nonnull Store store) { - boolean enabled = !PhysicsChunkTerrainProfiling.isRuntimeProfilingEnabled(store); - PhysicsChunkTerrainProfiling.setRuntimeProfilingEnabled(world, store, enabled); + boolean enabled = !PhysicsChunkCollisionProfiling.isRuntimeProfilingEnabled(store); + PhysicsChunkCollisionProfiling.setRuntimeProfilingEnabled(world, store, enabled); ctx.sender().sendMessage(Message.raw("Impulse runtime profiling " + (enabled ? "enabled" : "disabled"))); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java index c9bd9b86..23678388 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResource.java @@ -20,7 +20,7 @@ import org.joml.Vector3f; /** - * Runtime-only profiling state for PhysicsChunk terrain streaming. + * Runtime-only profiling state for PhysicsChunk collision streaming. * *

        This resource collects targeted metrics for the streamed voxel-collision * path so performance work can be driven by section/build/prune behavior rather @@ -155,8 +155,8 @@ public static final class Snapshot { private int playerSectionTargets; private int bodySectionTargets; private int streamingSpaces; - private int terrainApplyQueued; - private int terrainApplySkippedPending; + private int collisionApplyQueued; + private int collisionApplySkippedPending; private int ensureCalls; private int sectionRequests; private int sectionCacheHits; @@ -207,12 +207,12 @@ public void incrementStreamingSpaces() { streamingSpaces++; } - public void incrementTerrainApplyQueued() { - terrainApplyQueued++; + public void incrementCollisionApplyQueued() { + collisionApplyQueued++; } - public void incrementTerrainApplySkippedPending() { - terrainApplySkippedPending++; + public void incrementCollisionApplySkippedPending() { + collisionApplySkippedPending++; } public void addBodyStreamingCandidates(int count) { @@ -400,8 +400,8 @@ public void copyFrom(@Nonnull Snapshot other) { playerSectionTargets = other.playerSectionTargets; bodySectionTargets = other.bodySectionTargets; streamingSpaces = other.streamingSpaces; - terrainApplyQueued = other.terrainApplyQueued; - terrainApplySkippedPending = other.terrainApplySkippedPending; + collisionApplyQueued = other.collisionApplyQueued; + collisionApplySkippedPending = other.collisionApplySkippedPending; ensureCalls = other.ensureCalls; sectionRequests = other.sectionRequests; sectionCacheHits = other.sectionCacheHits; @@ -459,8 +459,8 @@ public void add(@Nonnull Snapshot other) { playerSectionTargets += other.playerSectionTargets; bodySectionTargets += other.bodySectionTargets; streamingSpaces += other.streamingSpaces; - terrainApplyQueued += other.terrainApplyQueued; - terrainApplySkippedPending += other.terrainApplySkippedPending; + collisionApplyQueued += other.collisionApplyQueued; + collisionApplySkippedPending += other.collisionApplySkippedPending; ensureCalls += other.ensureCalls; sectionRequests += other.sectionRequests; sectionCacheHits += other.sectionCacheHits; @@ -516,8 +516,8 @@ public void reset() { playerSectionTargets = 0; bodySectionTargets = 0; streamingSpaces = 0; - terrainApplyQueued = 0; - terrainApplySkippedPending = 0; + collisionApplyQueued = 0; + collisionApplySkippedPending = 0; ensureCalls = 0; sectionRequests = 0; sectionCacheHits = 0; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java index ac780c9b..016637ae 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkTerrainProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkSectionAccessCache; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStreamingBounds; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; @@ -51,7 +51,7 @@ /** * Produces copied PhysicsStore chunk collision mutations from EntityStore and ChunkStore state. */ -public final class PhysicsChunkTerrainProducerSystem extends TickingSystem +public final class PhysicsChunkCollisionProducerSystem extends TickingSystem implements QuerySystem { @Nullable @@ -86,8 +86,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { PhysicsChunkSettingsIndexResource.getResourceType()); PhysicsSnapshotResource snapshotResource = physics.getResource( PhysicsSnapshotResource.getResourceType()); - PhysicsChunkTerrainStreamingResource streaming = store.getResource( - PhysicsChunkTerrainStreamingResource.getResourceType()); + PhysicsChunkCollisionStreamingResource streaming = store.getResource( + PhysicsChunkCollisionStreamingResource.getResourceType()); List spaces = chunkCollisionSettingsIndex.streamingSpaces(); @@ -130,7 +130,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } private static void processSpace(@Nonnull World world, - @Nonnull PhysicsChunkTerrainStreamingResource streaming, + @Nonnull PhysicsChunkCollisionStreamingResource streaming, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull PhysicsChunkSpaceSettings settings, @Nonnull List playerPositions, @@ -196,7 +196,7 @@ private static void processSpace(@Nonnull World world, @Nonnull private static List collectDynamicBodyTargets( - @Nonnull PhysicsChunkTerrainStreamingResource streaming, + @Nonnull PhysicsChunkCollisionStreamingResource streaming, @Nonnull PhysicsChunkSpaceSettings settings, @Nonnull PhysicsSnapshotFrame physicsFrame, long currentTick, @@ -292,7 +292,7 @@ private static Query query() { if (resolved != null) { return resolved; } - synchronized (PhysicsChunkTerrainProducerSystem.class) { + synchronized (PhysicsChunkCollisionProducerSystem.class) { resolved = query; if (resolved == null) { resolved = Query.and(playerType(), transformType()); @@ -308,7 +308,7 @@ private static ComponentType playerType() { if (resolved != null) { return resolved; } - synchronized (PhysicsChunkTerrainProducerSystem.class) { + synchronized (PhysicsChunkCollisionProducerSystem.class) { resolved = playerType; if (resolved == null) { resolved = Player.getComponentType(); @@ -324,7 +324,7 @@ private static ComponentType transformType() { if (resolved != null) { return resolved; } - synchronized (PhysicsChunkTerrainProducerSystem.class) { + synchronized (PhysicsChunkCollisionProducerSystem.class) { resolved = transformType; if (resolved == null) { resolved = TransformComponent.getComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java index 00252c41..71f73d14 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/package-info.java @@ -2,7 +2,7 @@ * Internal Impulse Core implementation packages. * *

        Types under this package tree support the Hytale runtime integration, - * commands, persistence, diagnostics, PhysicsChunk terrain, and system execution. + * commands, persistence, diagnostics, PhysicsChunk collision, and system execution. * They are not the supported third-party plugin API.

        */ package dev.hytalemodding.impulse.core.internal; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index 5ff2af97..dce51190 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -49,7 +49,7 @@ public class PhysicsDebugResource implements Resource { @Setter private boolean debugJointsEnabled = true; @Setter - private boolean debugPhysicsChunkTerrainEnabled; + private boolean debugPhysicsChunkCollisionEnabled; private float overlayRefreshSeconds = DEFAULT_OVERLAY_REFRESH_SECONDS; private float physicsChunkRefreshSeconds = DEFAULT_PHYSICS_CHUNK_REFRESH_SECONDS; @@ -154,7 +154,7 @@ public PhysicsDebugResource clone() { copy.debugMotionEnabled = debugMotionEnabled; copy.debugContactsEnabled = debugContactsEnabled; copy.debugJointsEnabled = debugJointsEnabled; - copy.debugPhysicsChunkTerrainEnabled = debugPhysicsChunkTerrainEnabled; + copy.debugPhysicsChunkCollisionEnabled = debugPhysicsChunkCollisionEnabled; copy.overlayRefreshSeconds = overlayRefreshSeconds; copy.physicsChunkRefreshSeconds = physicsChunkRefreshSeconds; copy.overlayTimeUntilRefresh = overlayTimeUntilRefresh; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index d896121c..e656709d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -26,7 +26,7 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotRefVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; @@ -1041,32 +1041,32 @@ public int getBodySnapshotCellCount() { } @Nonnull - private PhysicsChunkTerrainStreamingResource authoritativePhysicsChunkTerrainStreaming() { + private PhysicsChunkCollisionStreamingResource authoritativePhysicsChunkCollisionStreaming() { Store entityStore = owningStore; if (entityStore == null) { - throw new IllegalStateException("Cannot access PhysicsStore PhysicsChunk terrain streaming " + throw new IllegalStateException("Cannot access PhysicsStore PhysicsChunk collision streaming " + "before this resource is attached to an EntityStore"); } - return entityStore.getResource(PhysicsChunkTerrainStreamingResource.getResourceType()); + return entityStore.getResource(PhysicsChunkCollisionStreamingResource.getResourceType()); } - private void clearAuthoritativePhysicsChunkTerrainStreaming(@Nonnull Store store) { + private void clearAuthoritativePhysicsChunkCollisionStreaming(@Nonnull Store store) { if (!PhysicsChunkLifecycle.isEnabled() || owningStore == null) { return; } PhysicsChunkCollisionMutationQueueResource queue = store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()); - authoritativePhysicsChunkTerrainStreaming().retainSpaces(Set.of(), queue); + authoritativePhysicsChunkCollisionStreaming().retainSpaces(Set.of(), queue); queue.clear(); } - private int clearAuthoritativePhysicsChunkTerrainSpace(@Nonnull Store store, + private int clearAuthoritativePhysicsChunkCollisionSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { int removed = 0; if (PhysicsChunkLifecycle.isEnabled() && owningStore != null) { PhysicsChunkCollisionMutationQueueResource queue = store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()); - removed = authoritativePhysicsChunkTerrainStreaming().clearSpace(spaceUuid, queue); + removed = authoritativePhysicsChunkCollisionStreaming().clearSpace(spaceUuid, queue); } int directlyRemoved = PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); @@ -1189,7 +1189,7 @@ public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("remove physics space"); UUID spaceUuid = requireSpaceUuid(store, spaceId); - clearAuthoritativePhysicsChunkTerrainSpace(store, spaceUuid); + clearAuthoritativePhysicsChunkCollisionSpace(store, spaceUuid); PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); return; } @@ -1205,7 +1205,7 @@ public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, spaceId, store -> { UUID spaceUuid = requireSpaceUuid(store, spaceId); - clearAuthoritativePhysicsChunkTerrainSpace(store, spaceUuid); + clearAuthoritativePhysicsChunkCollisionSpace(store, spaceUuid); PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); }); } @@ -1242,7 +1242,7 @@ private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldNa public void clearAllSpaces(@Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("clear physics spaces"); - clearAuthoritativePhysicsChunkTerrainStreaming(store); + clearAuthoritativePhysicsChunkCollisionStreaming(store); PhysicsStoreRuntimeCleaner.clearAll(store); return; } @@ -1256,7 +1256,7 @@ public PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName return enqueueAuthoritativePhysicsStoreMutation("clear physics spaces", null, store -> { - clearAuthoritativePhysicsChunkTerrainStreaming(store); + clearAuthoritativePhysicsChunkCollisionStreaming(store); PhysicsStoreRuntimeCleaner.clearAll(store); }); } @@ -1297,7 +1297,7 @@ private static RuntimeException collectFailure(@Nullable RuntimeException failur public PhysicsRuntimeResetResult resetRuntimeStateKeepingSpaces(@Nonnull String worldName) { if (isAuthoritativePhysicsStoreActive()) { Store store = authoritativePhysicsStore("reset physics runtime state"); - clearAuthoritativePhysicsChunkTerrainStreaming(store); + clearAuthoritativePhysicsChunkCollisionStreaming(store); return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); } requireLegacyMutationAllowed("reset physics runtime state"); @@ -1314,7 +1314,7 @@ public CompletionStage resetRuntimeStateKeepingSpaces return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, "reset physics runtime state", store -> { - clearAuthoritativePhysicsChunkTerrainStreaming(store); + clearAuthoritativePhysicsChunkCollisionStreaming(store); return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); }); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java index f667354a..ea4e631f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java @@ -26,7 +26,7 @@ * position.

        * *

        Callers use it for area queries that need body identity and pose data, such - * as visual materialization, PhysicsChunk terrain streaming hints, diagnostics, and + * as visual materialization, PhysicsChunk collision streaming hints, diagnostics, and * other nearby-body discovery. Query freshness follows the snapshot publishing * policy for each body.

        */ diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java index a0f7956e..3e8770ee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugRenderer.java @@ -228,7 +228,7 @@ static void renderRay(@Nonnull Collection viewers, renderArrow(viewers, start, direction, color, time); } - static void renderPhysicsChunkTerrainSection(@Nonnull Collection viewers, + static void renderPhysicsChunkCollisionSection(@Nonnull Collection viewers, int chunkX, int sectionY, int chunkZ, @@ -246,7 +246,7 @@ static void renderPhysicsChunkTerrainSection(@Nonnull Collection view TERRAIN_EDGE_PADDING); } - static void renderPhysicsChunkTerrainBox(@Nonnull Collection viewers, + static void renderPhysicsChunkCollisionBox(@Nonnull Collection viewers, @Nonnull BoxCollider box, @Nonnull Vector3f color, float time) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 31f33440..53aa0a95 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -111,9 +111,9 @@ public void tick(float dt, int index, @Nonnull Store store) { boolean debugMotion = debug.isDebugMotionEnabled(); boolean debugContacts = debug.isDebugContactsEnabled(); boolean debugJoints = debug.isDebugJointsEnabled(); - boolean debugTerrain = debug.isDebugPhysicsChunkTerrainEnabled(); + boolean debugCollision = debug.isDebugPhysicsChunkCollisionEnabled(); if (!debugShapes && !debugMotion && !debugContacts && !debugJoints - && !debugTerrain) { + && !debugCollision) { return; } @@ -177,8 +177,8 @@ public void tick(float dt, int index, @Nonnull Store store) { debug.getMaxJoints(), overlayLifetime); } - if (terrainDue && debugTerrain) { - renderPhysicsChunkTerrain(target, + if (terrainDue && debugCollision) { + renderPhysicsChunkCollision(target, physicsStore, spaceId, viewerUuid, @@ -425,7 +425,7 @@ private static void renderJoints(@Nonnull Collection viewers, } } - private static void renderPhysicsChunkTerrain(@Nonnull Collection viewers, + private static void renderPhysicsChunkCollision(@Nonnull Collection viewers, @Nonnull Store physicsStore, @Nonnull SpaceId spaceId, @Nonnull UUID viewerUuid, @@ -456,7 +456,7 @@ private static void renderPhysicsChunkTerrain(@Nonnull Collection vie int sectionLimit = Math.min(maxSections, visibleSections.size()); for (int i = 0; i < sectionLimit; i++) { PhysicsChunkDebugSectionView section = visibleSections.get(i).section(); - PhysicsDebugRenderer.renderPhysicsChunkTerrainSection(viewers, + PhysicsDebugRenderer.renderPhysicsChunkCollisionSection(viewers, section.chunkX(), section.sectionY(), section.chunkZ(), @@ -471,7 +471,7 @@ private static void renderPhysicsChunkTerrain(@Nonnull Collection vie int boxLimit = Math.min(maxBoxes, visibleBoxes.size()); for (int i = 0; i < boxLimit; i++) { VisibleDebugBox visibleBox = visibleBoxes.get(i); - PhysicsDebugRenderer.renderPhysicsChunkTerrainBox(viewers, + PhysicsDebugRenderer.renderPhysicsChunkCollisionBox(viewers, visibleBox.box(), visibleBox.color(), time); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 670991ff..369f6f07 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -102,7 +102,7 @@ static CompletionStage> physicsChunkSectionsA double viewerY = viewerPosition.y; double viewerZ = viewerPosition.z; return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore PhysicsChunk terrain debug read", + "queue PhysicsStore PhysicsChunk collision debug read", physics -> physicsChunkSections(physics, spaceId, viewerX, @@ -212,7 +212,7 @@ private static List physicsChunkSections( double viewerZ, double viewRadius) { PhysicsThreading.requireWorldThread(store, - "read PhysicsStore PhysicsChunk terrain debug sections"); + "read PhysicsStore PhysicsChunk collision debug sections"); SpaceContext spaceContext = space(store, spaceId); if (spaceContext == null) { return List.of(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index 38c7f86d..3266fc37 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkTerrainStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; @@ -23,11 +23,11 @@ import org.joml.Vector3d; /** - * Public PhysicsChunk operations for terrain-backed collision. + * Public PhysicsChunk operations for chunk-backed collision. */ -public final class PhysicsChunkTerrain { +public final class PhysicsChunkCollision { - private PhysicsChunkTerrain() { + private PhysicsChunkCollision() { } public static boolean isSubPluginEnabled() { @@ -35,7 +35,7 @@ public static boolean isSubPluginEnabled() { } @Nonnull - public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, + public static PhysicsChunkCollisionBuildStats rebuildAround(@Nonnull World world, @Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @@ -43,12 +43,12 @@ public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, requireEnabled(); Store checkedStore = requireMatchingWorldThread(world, store, - "rebuild PhysicsChunk terrain"); + "rebuild PhysicsChunk collision"); PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); PhysicsChunkCollisionMutationQueueResource queue = checkedStore.getResource( PhysicsChunkCollisionMutationQueueResource.getResourceType()); int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); - PhysicsChunkTerrainPrewarmStats stats = streaming(world).ensureAround(world, + PhysicsChunkCollisionPrewarmStats stats = streaming(world).ensureAround(world, settings.spaceUuid(), queue, List.of(Objects.requireNonNull(center, "center")), @@ -61,7 +61,7 @@ public static PhysicsChunkTerrainBuildStats rebuildAround(@Nonnull World world, } @Nonnull - public static PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, + public static PhysicsChunkCollisionBuildStats refreshAround(@Nonnull World world, @Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3d center, @@ -69,7 +69,7 @@ public static PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, requireEnabled(); Store checkedStore = requireMatchingWorldThread(world, store, - "refresh PhysicsChunk terrain"); + "refresh PhysicsChunk collision"); PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); return streaming(world).refreshAround(world, settings.spaceUuid(), @@ -82,7 +82,7 @@ public static PhysicsChunkTerrainBuildStats refreshAround(@Nonnull World world, } @Nonnull - public static PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, + public static PhysicsChunkCollisionPrewarmStats ensureAround(@Nonnull World world, @Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Iterable centers, @@ -91,7 +91,7 @@ public static PhysicsChunkTerrainPrewarmStats ensureAround(@Nonnull World world, requireEnabled(); Store checkedStore = requireMatchingWorldThread(world, store, - "ensure PhysicsChunk terrain"); + "ensure PhysicsChunk collision"); PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); return streaming(world).ensureAround(world, settings.spaceUuid(), @@ -108,22 +108,22 @@ public static int clearSpace(@Nonnull World world, @Nonnull SpaceId spaceId) { Store checkedStore = requireMatchingWorldThread(world, store, - "clear PhysicsChunk terrain"); + "clear PhysicsChunk collision"); UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, Objects.requireNonNull(spaceId, "spaceId")); return clearSpaceRows(world, checkedStore, spaceUuid); } @Nonnull - public static PhysicsChunkTerrainStats stats(@Nonnull World world) { + public static PhysicsChunkCollisionStats stats(@Nonnull World world) { Objects.requireNonNull(world, "world"); if (!world.isInThread()) { - throw new IllegalStateException("Cannot read PhysicsChunk terrain stats " + throw new IllegalStateException("Cannot read PhysicsChunk collision stats " + "outside the owning world thread"); } return isSubPluginEnabled() ? streaming(world).stats() - : new PhysicsChunkTerrainStats(0, 0, 0, 0); + : new PhysicsChunkCollisionStats(0, 0, 0, 0); } private static void requireEnabled() { @@ -162,7 +162,7 @@ private static PhysicsChunkSpaceSettings requireSettings( ChunkCollisionSettingsComponent settings = component != null ? component : new ChunkCollisionSettingsComponent(); if (settings.getMode() == PhysicsChunkCollisionMode.NONE) { - throw new IllegalStateException("PhysicsChunk terrain is disabled for space " + throw new IllegalStateException("PhysicsChunk collision is disabled for space " + spaceId); } return new PhysicsChunkSpaceSettings(spaceUuid, @@ -175,11 +175,11 @@ private static PhysicsChunkSpaceSettings requireSettings( } @Nonnull - private static PhysicsChunkTerrainStreamingResource streaming(@Nonnull World world) { + private static PhysicsChunkCollisionStreamingResource streaming(@Nonnull World world) { Store entityStore = Objects.requireNonNull(world, "world") .getEntityStore() .getStore(); - return entityStore.getResource(PhysicsChunkTerrainStreamingResource.getResourceType()); + return entityStore.getResource(PhysicsChunkCollisionStreamingResource.getResourceType()); } private static int clearSpaceRows(@Nonnull World world, @@ -196,10 +196,10 @@ private static int clearSpaceRows(@Nonnull World world, } @Nonnull - private static PhysicsChunkTerrainBuildStats withRemovedBodies( - @Nonnull PhysicsChunkTerrainBuildStats stats, + private static PhysicsChunkCollisionBuildStats withRemovedBodies( + @Nonnull PhysicsChunkCollisionBuildStats stats, int removedBodies) { - return new PhysicsChunkTerrainBuildStats(stats.scannedBlocks(), + return new PhysicsChunkCollisionBuildStats(stats.scannedBlocks(), stats.solidBlocks(), stats.culledInteriorBlocks(), stats.fullCubeRuns(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionBuildStats.java similarity index 88% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionBuildStats.java index f98d2e4e..adb34ffa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainBuildStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionBuildStats.java @@ -1,9 +1,9 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Aggregate statistics from building or rebuilding streamed PhysicsChunk terrain geometry. + * Aggregate statistics from building or rebuilding streamed PhysicsChunk collision geometry. */ -public record PhysicsChunkTerrainBuildStats(int scannedBlocks, +public record PhysicsChunkCollisionBuildStats(int scannedBlocks, int solidBlocks, int culledInteriorBlocks, int fullCubeRuns, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionPrewarmStats.java new file mode 100644 index 00000000..fbdfb15d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionPrewarmStats.java @@ -0,0 +1,10 @@ +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; + +import javax.annotation.Nonnull; + +/** + * Statistics from ensuring PhysicsChunk collision around multiple target positions. + */ +public record PhysicsChunkCollisionPrewarmStats(int sectionTargets, + @Nonnull PhysicsChunkCollisionBuildStats buildStats) { +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionProfiling.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionProfiling.java index 676e60c6..fef37db0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionProfiling.java @@ -17,22 +17,22 @@ /** * Public PhysicsChunk profiling helpers for command and diagnostics surfaces. */ -public final class PhysicsChunkTerrainProfiling { +public final class PhysicsChunkCollisionProfiling { - private PhysicsChunkTerrainProfiling() { + private PhysicsChunkCollisionProfiling() { } public static boolean isRuntimeProfilingEnabled(@Nonnull Store store) { PhysicsRuntimeProfilingResource runtimeProfiling = runtimeProfiling(store); - PhysicsChunkProfilingResource terrainProfiling = terrainProfiling(store); - return runtimeProfiling.isEnabled() && terrainProfiling.isEnabled(); + PhysicsChunkProfilingResource collisionProfiling = collisionProfiling(store); + return runtimeProfiling.isEnabled() && collisionProfiling.isEnabled(); } public static void setRuntimeProfilingEnabled(@Nonnull World world, @Nonnull Store store, boolean enabled) { runtimeProfiling(store).setEnabled(enabled); - terrainProfiling(store).setEnabled(enabled); + collisionProfiling(store).setEnabled(enabled); Store physicsStore = physicsStoreOrNull(world); if (physicsStore != null) { physicsStore.getResource(PhysicsProfilingResource.getResourceType()) @@ -43,7 +43,7 @@ public static void setRuntimeProfilingEnabled(@Nonnull World world, public static void resetRuntimeProfiling(@Nonnull World world, @Nonnull Store store) { runtimeProfiling(store).reset(); - terrainProfiling(store).reset(); + collisionProfiling(store).reset(); Store physicsStore = physicsStoreOrNull(world); if (physicsStore != null) { physicsStore.getResource(PhysicsProfilingResource.getResourceType()).reset(); @@ -52,7 +52,7 @@ public static void resetRuntimeProfiling(@Nonnull World world, @Nonnull public static Snapshots snapshots(@Nonnull Store store) { - PhysicsChunkProfilingResource profiling = terrainProfiling(store); + PhysicsChunkProfilingResource profiling = collisionProfiling(store); return new Snapshots(profiling.getCumulativeSnapshot(), profiling.getLatestTickSnapshot(), profiling.getWorstTickSnapshot(), @@ -64,7 +64,7 @@ public static List missingSectionSamples( @Nonnull SnapshotView snapshot) { return snapshot.snapshot.getMissingSectionSamples() .stream() - .map(PhysicsChunkTerrainProfiling::view) + .map(PhysicsChunkCollisionProfiling::view) .toList(); } @@ -91,7 +91,7 @@ private static PhysicsRuntimeProfilingResource runtimeProfiling( } @Nonnull - private static PhysicsChunkProfilingResource terrainProfiling( + private static PhysicsChunkProfilingResource collisionProfiling( @Nonnull Store store) { return store.getResource(PhysicsChunkProfilingResource.getResourceType()); } @@ -197,12 +197,12 @@ public int getStreamingSpaces() { return snapshot.getStreamingSpaces(); } - public int getTerrainApplyQueued() { - return snapshot.getTerrainApplyQueued(); + public int getCollisionApplyQueued() { + return snapshot.getCollisionApplyQueued(); } - public int getTerrainApplySkippedPending() { - return snapshot.getTerrainApplySkippedPending(); + public int getCollisionApplySkippedPending() { + return snapshot.getCollisionApplySkippedPending(); } public int getEnsureCalls() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionStats.java similarity index 67% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionStats.java index 89104f70..741f395d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainStats.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionStats.java @@ -1,9 +1,9 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; /** - * Current size of the generated PhysicsChunk terrain cache. + * Current size of the generated PhysicsChunk collision cache. */ -public record PhysicsChunkTerrainStats(int spaces, +public record PhysicsChunkCollisionStats(int spaces, int sections, int bodies, int shapeTemplates) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java deleted file mode 100644 index 63791fb2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrainPrewarmStats.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; - -import javax.annotation.Nonnull; - -/** - * Statistics from ensuring PhysicsChunk terrain around multiple target positions. - */ -public record PhysicsChunkTerrainPrewarmStats(int sectionTargets, - @Nonnull PhysicsChunkTerrainBuildStats buildStats) { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java index 911c25f3..abf3e77b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java @@ -1,5 +1,5 @@ /** - * Public API for the bundled PhysicsChunk terrain subplugin. + * Public API for the bundled PhysicsChunk collision subplugin. * *

        Collision LOD settings live under * {@code dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings}.

        diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index d6d1a474..a9e25273 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -13,10 +13,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainBuildStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionBuildStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -26,12 +26,12 @@ import org.joml.Vector3d; /** - * Debug commands for manually building/clearing PhysicsChunk terrain collision. + * Debug commands for manually building/clearing PhysicsChunk collision collision. */ public class PhysicsChunkExampleCommand extends AbstractCommandCollection { public PhysicsChunkExampleCommand() { - super("physicschunk", "Build PhysicsChunk terrain collision from nearby world blocks"); + super("physicschunk", "Build PhysicsChunk collision collision from nearby world blocks"); addSubCommand(new BuildCommand()); addSubCommand(new EnsureCommand()); addSubCommand(new ClearCommand()); @@ -58,7 +58,7 @@ private static final class BuildCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private BuildCommand() { - super("build", "Rebuild nearby PhysicsChunk terrain collision"); + super("build", "Rebuild nearby PhysicsChunk collision collision"); } @Nonnull @@ -76,13 +76,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Store physicsStore = physicsStore(world); - PhysicsChunkTerrainBuildStats stats = PhysicsChunkTerrain.rebuildAround(world, + PhysicsChunkCollisionBuildStats stats = PhysicsChunkCollision.rebuildAround(world, physicsStore, spaceId, playerPos, radius); - ctx.sender().sendMessage(Message.raw("Built PhysicsChunk terrain collision: scanned " + ctx.sender().sendMessage(Message.raw("Built PhysicsChunk collision collision: scanned " + stats.scannedBlocks() + " blocks, solid " + stats.solidBlocks() + ", culled " + stats.culledInteriorBlocks() @@ -113,7 +113,7 @@ private static final class EnsureCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private EnsureCommand() { - super("ensure", "Ensure nearby PhysicsChunk terrain collision is available"); + super("ensure", "Ensure nearby PhysicsChunk collision collision is available"); } @Nonnull @@ -131,14 +131,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } Store physicsStore = physicsStore(world); - PhysicsChunkTerrainPrewarmStats stats = PhysicsChunkTerrain.ensureAround(world, + PhysicsChunkCollisionPrewarmStats stats = PhysicsChunkCollision.ensureAround(world, physicsStore, spaceId, List.of(playerPos), radius, Math.max(0L, world.getTick())); - ctx.sender().sendMessage(Message.raw("Ensured PhysicsChunk terrain collision: targets " + ctx.sender().sendMessage(Message.raw("Ensured PhysicsChunk collision collision: targets " + stats.sectionTargets() + ", bodies " + stats.buildStats().colliderBodies() @@ -157,7 +157,7 @@ private static final class ClearCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private ClearCommand() { - super("clear", "Remove generated PhysicsChunk terrain collision"); + super("clear", "Remove generated PhysicsChunk collision collision"); } @Nonnull @@ -172,9 +172,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Store physicsStore = physicsStore(world); - int removed = PhysicsChunkTerrain.clearSpace(world, physicsStore, spaceId); + int removed = PhysicsChunkCollision.clearSpace(world, physicsStore, spaceId); ctx.sender().sendMessage(Message.raw("Removed " + removed - + " PhysicsChunk terrain bodies.")); + + " PhysicsChunk collision bodies.")); return CompletableFuture.completedFuture(null); } } @@ -182,7 +182,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static final class StatsCommand extends AbstractAsyncPlayerCommand { private StatsCommand() { - super("stats", "Show generated PhysicsChunk terrain collision stats"); + super("stats", "Show generated PhysicsChunk collision collision stats"); } @Nonnull @@ -192,8 +192,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - PhysicsChunkTerrainStats stats = PhysicsChunkTerrain.stats(world); - ctx.sender().sendMessage(Message.raw("PhysicsChunk terrain collision: " + PhysicsChunkCollisionStats stats = PhysicsChunkCollision.stats(world); + ctx.sender().sendMessage(Message.raw("PhysicsChunk collision collision: " + stats.spaces() + " spaces, " + stats.sections() + " sections, " + stats.bodies() + " bodies, " diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 76c76096..e8529b05 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -19,8 +19,8 @@ import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; @@ -282,7 +282,7 @@ static final class ExplosiveCommand extends PhysicsStorePlayerCommand { ArgTypes.STRING); private final OptionalArg radiusArg = withOptionalArg( "radius", - "Block radius fragmented when the explosive block hits PhysicsChunk terrain", + "Block radius fragmented when the explosive block hits PhysicsChunk collision", ArgTypes.INTEGER); private final OptionalArg maxFragmentsArg = withOptionalArg( "maxFragments", @@ -344,7 +344,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " is not bound yet.")); return CompletableFuture.completedFuture(null); } - PhysicsChunkTerrainPrewarmStats stats = PhysicsChunkTerrain.ensureAround(world, + PhysicsChunkCollisionPrewarmStats stats = PhysicsChunkCollision.ensureAround(world, physicsStore, spaceId, List.of(spawn), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 1ccf8431..53aca68d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrainPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; @@ -355,7 +355,7 @@ private static int prewarmStressTerrain(@Nonnull World world, return 0; } - PhysicsChunkTerrainPrewarmStats stats = PhysicsChunkTerrain.ensureAround(world, + PhysicsChunkCollisionPrewarmStats stats = PhysicsChunkCollision.ensureAround(world, PhysicsThreading.store(world), spaceId, layout.positions(count), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 92d9e2b2..9ab633e1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -22,7 +22,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; @@ -135,12 +135,12 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e List groups = groupFragments(fragments, center, settings.getRadius()); Store physicsStore = PhysicsThreading.store(world); - PhysicsChunkTerrain.refreshAround(world, + PhysicsChunkCollision.refreshAround(world, physicsStore, spaceId, center, Math.max(8, settings.getRadius() + 4)); - PhysicsChunkTerrain.ensureAround(world, + PhysicsChunkCollision.ensureAround(world, physicsStore, spaceId, groupCenters(groups), From 55693d57f887197b0b572e70e59bb8f80ff797a1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:26:07 +0200 Subject: [PATCH 435/534] test(core): cover physicschunk collision behavior Signed-off-by: Blovien --- .../PhysicsChunkNamingSourceGuardTest.java | 101 ------------------ .../PhysicsChunkProfilingResourceTest.java | 8 +- .../PersistentSpaceDtoSettingsTest.java | 6 ++ .../PhysicsTypeRegistrationApiTest.java | 4 +- 4 files changed, 12 insertions(+), 107 deletions(-) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java deleted file mode 100644 index f2d0fbfb..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkNamingSourceGuardTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk; - -import static org.junit.jupiter.api.Assertions.assertFalse; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; - -class PhysicsChunkNamingSourceGuardTest { - - @Test - void physicsChunkTerrainFacadeDoesNotDelegateThroughRemovedCompatibilityFacade() - throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkTerrain.java")); - - assertFalse(source.contains(removedPhysicsTerrainFacade() + "."), - "PhysicsChunk terrain facade must own the implementation path"); - } - - @Test - void physicsChunkCommandsUseTerrainNamedProfilingApi() throws IOException { - try (Stream files = Files.walk(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands"))) { - for (Path file : files.filter(path -> path.toString().endsWith(".java")).toList()) { - String source = Files.readString(file); - assertFalse(source.contains(removedPhysicsTerrainFacade() + "Profiling"), - file + " should use PhysicsChunkTerrainProfiling"); - } - } - } - - @Test - void examplesDoNotUseInternalOrDeprecatedTerrainImports() throws IOException { - Path examples = Path.of("../impulse-examples/src/main/java"); - if (!Files.exists(examples)) { - return; - } - try (Stream files = Files.walk(examples)) { - for (Path file : files.filter(path -> path.toString().endsWith(".java")).toList()) { - String source = Files.readString(file); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.internal."), - file + " should use exported plugin APIs"); - assertFalse(source.contains("import dev.hytalemodding.impulse.core.plugin.settings.*;"), - file + " should not wildcard-import flat settings"); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.modules.physicschunk.", - removedPhysicsTerrainFacade()); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.modules.physicschunk.", - removedTerrainPrefix() + "Mode"); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.settings.", - removedPhysicsTerrainFacade() + "Settings"); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.settings.", - "PhysicsChunkTerrainSettings"); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.settings.", - "PhysicsChunkCollisionSettings"); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.settings.", - "PhysicsCollisionLodSettings"); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.settings.", - "PhysicsVisualSyncSettings"); - assertRemovedApiAbsent(source, - file, - "dev.hytalemodding.impulse.core.plugin.settings.", - "PhysicsVisualMaterializationSettings"); - } - } - } - - private static void assertRemovedApiAbsent(String source, - Path file, - String packageName, - String typeName) { - assertFalse(source.contains("import " + packageName + typeName), - file + " should use canonical module APIs"); - assertFalse(source.contains(packageName + typeName), - file + " should use canonical module APIs"); - } - - private static String removedPhysicsTerrainFacade() { - return "Physics" + removedTerrainPrefix(); - } - - private static String removedTerrainPrefix() { - return "World" + "Collision"; - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java index 7339e3be..670847f0 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/profiling/PhysicsChunkProfilingResourceTest.java @@ -60,8 +60,8 @@ void finishTickTracksLatestCumulativeAndWorstSnapshots() { first.incrementBodyTargetSleepingStableSkips(); first.addBodyTargetsPruned(2); first.incrementStreamingSpaces(); - first.incrementTerrainApplyQueued(); - first.incrementTerrainApplySkippedPending(); + first.incrementCollisionApplyQueued(); + first.incrementCollisionApplySkippedPending(); first.incrementEnsureCalls(); first.incrementSectionRequests(); first.incrementSectionCacheHits(); @@ -107,8 +107,8 @@ void finishTickTracksLatestCumulativeAndWorstSnapshots() { assertEquals(1, resource.getCumulative().getBodyTargetSleepingStableSkips()); assertEquals(2, resource.getCumulative().getBodyTargetsPruned()); assertEquals(1, resource.getCumulative().getStreamingSpaces()); - assertEquals(1, resource.getCumulative().getTerrainApplyQueued()); - assertEquals(1, resource.getCumulative().getTerrainApplySkippedPending()); + assertEquals(1, resource.getCumulative().getCollisionApplyQueued()); + assertEquals(1, resource.getCumulative().getCollisionApplySkippedPending()); assertEquals(1, resource.getCumulative().getEnsureCalls()); assertEquals(1, resource.getCumulative().getSectionRequests()); assertEquals(1, resource.getCumulative().getSectionCacheHits()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index ecb9b04d..28226e63 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -23,6 +24,7 @@ class PersistentSpaceDtoSettingsTest { @Test void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); + original.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); original.getPhysicsChunkCollisionSettings().setNativeVoxelCollisionEnabled(true); original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); @@ -63,6 +65,8 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertEquals(0.2f, decodedState.getChunkCollisionRestitution(), 0.0001f); PhysicsSpaceSettings decoded = decodedState.toSettings(); + assertEquals(PhysicsChunkCollisionMode.STREAMING, + decoded.getPhysicsChunkCollisionSettings().getMode()); assertTrue(decoded.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); assertDetachedVisualCadence(decoded, 7, 9, 11); @@ -71,6 +75,8 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertEquals(0.2f, copiedState.getChunkCollisionRestitution(), 0.0001f); PhysicsSpaceSettings copied = copiedState.toSettings(); + assertEquals(PhysicsChunkCollisionMode.STREAMING, + copied.getPhysicsChunkCollisionSettings().getMode()); assertTrue(copied.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); assertDetachedVisualCadence(copied, 7, 9, 11); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java index 9fe70fd4..095e1958 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkTerrain; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -34,7 +34,7 @@ void internalRegistriesOwnRegistrationAndLifecycleWithoutPublicMutators() assertFalse(hasPublicRegistrationMethod(PhysicsComponentTypes.class)); assertFalse(hasPublicRegistrationMethod(PhysicsEntityTypes.class)); assertFalse(hasPublicRegistrationMethod(ImpulseControllableComponent.class)); - assertFalse(hasPublicLifecycleMutator(PhysicsChunkTerrain.class)); + assertFalse(hasPublicLifecycleMutator(PhysicsChunkCollision.class)); } private static boolean hasPublicSetter(Class type) { From 978220aba0300785bd47c11808399de42adc7968 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:36:52 +0200 Subject: [PATCH 436/534] test(core): remove source-shape guard tests Signed-off-by: Blovien --- .../CleanCommandLifecycleGuardTest.java | 17 --- ...icsStoreOwnerLaneSnapshotBoundaryTest.java | 19 --- ...csStoreRuntimeBoundarySourceGuardTest.java | 117 ------------------ 3 files changed, 153 deletions(-) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java index 687521e5..90271658 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -1,8 +1,6 @@ package dev.hytalemodding.impulse.core.internal.commands; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentRegistryProxy; @@ -25,9 +23,6 @@ import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -38,18 +33,6 @@ class CleanCommandLifecycleGuardTest { - @Test - void cleanCommandDoesNotRemoveEveryBodyAttachmentEntity() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java")); - - assertTrue(source.contains("cleanAttachedEntity(")); - assertTrue(source.contains("shouldRemoveEntityWhenBodyMissing()")); - assertFalse(source.contains("removedEntities.incrementAndGet(REMOVED_BODY_ENTITIES);\n" - + " commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), " - + "RemoveReason.REMOVE);")); - } - @Test void radiusCleanSelectsOnlyNormalBodySnapshots() throws Exception { ComponentRegistry registry = new ComponentRegistry<>(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java deleted file mode 100644 index 92b028ce..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreOwnerLaneSnapshotBoundaryTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; - -import static org.junit.jupiter.api.Assertions.assertFalse; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; - -class PhysicsStoreOwnerLaneSnapshotBoundaryTest { - - @Test - void completedStepPublicationDoesNotReadBackendBodiesOnWorldThread() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java")); - - assertFalse(source.contains("backendRuntime.snapshotBodies")); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java deleted file mode 100644 index 719f4301..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeBoundarySourceGuardTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; - -class PhysicsStoreRuntimeBoundarySourceGuardTest { - - @Test - void spaceMutationRuntimeCleanupDoesNotResolveRuntimeBindingsByUuid() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java")); - - assertFalse(source.contains("runtime.getSpaceHandle(spaceUuid)")); - assertFalse(source.contains("runtime.getSpaceBackendId(spaceUuid)")); - } - - @Test - void staleBodyCleanupDoesNotFallbackToRuntimeUuidLookups() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java")); - - assertFalse(source.contains("runtime.getJointHandle(joint.jointUuid())")); - assertFalse(source.contains("runtime.getJointSpaceHandle(joint.jointUuid())")); - assertFalse(source.contains("runtime.getSpaceHandle(joint.spaceUuid())")); - assertFalse(source.contains("runtime.removeBodyHandle(body.bodyUuid())")); - } - - @Test - void backendAccessDoesNotResolveRuntimeSpacesByUuid() throws IOException { - String backendAccess = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java")); - String diagnostics = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java")); - - assertFalse(backendAccess.contains("runtime.getSpaceHandle(spaceUuid)")); - assertFalse(backendAccess.contains("runtime.getSpaceBackendId(spaceUuid)")); - assertFalse(diagnostics.contains("PhysicsBackendAccess.space(runtime, spaceUuid)")); - } - - @Test - void completedStepPublicationIteratesRuntimeSpacesByRef() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java")); - - assertFalse(source.contains("runtime.forEachSpaceBinding")); - } - - @Test - void chunkCollisionVoxelStitchingDoesNotResolveRuntimeBindingsByUuid() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java")); - - assertFalse(source.contains("runtime.getBodyHandle(neighborUuid)")); - assertFalse(source.contains("runtime.getBodySpaceHandle(neighborUuid)")); - } - - @Test - void debugQueriesDoNotResolveRuntimeSpacesByUuid() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java")); - - assertFalse(source.contains("runtime.getSpaceHandle(spaceUuid)")); - assertFalse(source.contains("runtime.getSpaceBackendId(spaceUuid)")); - } - - @Test - void legacyWorldResourceFacadeDoesNotIterateRuntimeSpacesByUuid() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java")); - - assertFalse(source.contains("forEachSpaceBinding")); - } - - @Test - void legacyAuthoritativeAsyncMutationsWaitForBackendIdle() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java")); - - assertTrue(source.contains("PhysicsThreading.callWhenBackendIdleOnWorldThread(world," - + System.lineSeparator() + " operation,")); - assertFalse(source.contains("PhysicsThreading.executeOnWorldThread(world, operation, mutation)")); - } - - @Test - void publicWorldSettingsAsyncWaitsForBackendIdle() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java")); - - assertTrue(source.contains("PhysicsThreading.callWhenBackendIdleOnWorldThread(world," - + System.lineSeparator() + " \"queue PhysicsStore world settings update\"")); - assertFalse(source.contains("PhysicsThreading.executeOnWorldThread(world," - + System.lineSeparator() + " \"queue PhysicsStore world settings update\"")); - } - - @Test - void runtimeResourceDoesNotExposeUuidRuntimeReadApis() throws IOException { - String source = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java")); - - assertFalse(source.contains("getSpaceHandle(@Nonnull UUID")); - assertFalse(source.contains("getSpaceBackendId(@Nonnull UUID")); - assertFalse(source.contains("getBodyHandle(@Nonnull UUID")); - assertFalse(source.contains("getBodySpaceHandle(@Nonnull UUID")); - assertFalse(source.contains("getJointHandle(@Nonnull UUID")); - assertFalse(source.contains("getJointSpaceHandle(@Nonnull UUID")); - assertFalse(source.contains("bodyUuidsForSpaceHandle")); - assertFalse(source.contains("jointUuidsForSpaceHandle")); - assertFalse(source.contains("forEachSpaceBinding")); - assertFalse(source.contains("@Nullable Ref spaceRef")); - assertFalse(source.contains("@Nullable Ref jointRef")); - } -} From 7b7854fd81a51f7cad7c8657e6b57b1ed1fdc1d2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:43:23 +0200 Subject: [PATCH 437/534] refactor(core): add ref-first physicschunk collision api Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 5 +- .../physicschunk/PhysicsChunkCollision.java | 170 +++++++++++++++--- .../commands/PhysicsChunkExampleCommand.java | 22 +-- .../commands/PhysicsStoreExampleCommands.java | 2 +- .../commands/stress/StressBodiesCommand.java | 6 +- .../explosive/ExplosiveBlockRuntime.java | 4 +- 6 files changed, 170 insertions(+), 39 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index e4e0bedf..e3559685 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.commands; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; @@ -191,6 +192,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, summaries -> deleteIfEmpty(context, world, physicsStore, + selectedSpace.spaceRef(), spaceId, spaceId.value(), registeredBodies, @@ -200,6 +202,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, private static void deleteIfEmpty(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store physicsStore, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, int rawSpaceId, int registeredBodies, @@ -215,7 +218,7 @@ private static void deleteIfEmpty(@Nonnull CommandContext context, return; } - PhysicsChunkCollision.clearSpace(world, physicsStore, spaceId); + PhysicsChunkCollision.clearSpace(world, physicsStore, spaceRef); PhysicsSpaces.removeWithContents(physicsStore, spaceId); context.sendMessage(Message.raw("Deleted physics space id=" + rawSpaceId + " with " + backendBodies + " backend bodies and " + joints + " joints.")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index 3266fc37..c15ed124 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -8,16 +8,16 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Vector3d; @@ -44,7 +44,33 @@ public static PhysicsChunkCollisionBuildStats rebuildAround(@Nonnull World world Store checkedStore = requireMatchingWorldThread(world, store, "rebuild PhysicsChunk collision"); - PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); + return rebuildAroundChecked(world, + checkedStore, + requireSpaceRef(checkedStore, spaceId), + center, + radius); + } + + @Nonnull + public static PhysicsChunkCollisionBuildStats rebuildAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Vector3d center, + int radius) { + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "rebuild PhysicsChunk collision"); + return rebuildAroundChecked(world, checkedStore, spaceRef, center, radius); + } + + @Nonnull + private static PhysicsChunkCollisionBuildStats rebuildAroundChecked(@Nonnull World world, + @Nonnull Store checkedStore, + @Nonnull Ref spaceRef, + @Nonnull Vector3d center, + int radius) { + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceRef); PhysicsChunkCollisionMutationQueueResource queue = checkedStore.getResource( PhysicsChunkCollisionMutationQueueResource.getResourceType()); int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); @@ -70,7 +96,33 @@ public static PhysicsChunkCollisionBuildStats refreshAround(@Nonnull World world Store checkedStore = requireMatchingWorldThread(world, store, "refresh PhysicsChunk collision"); - PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); + return refreshAroundChecked(world, + checkedStore, + requireSpaceRef(checkedStore, spaceId), + center, + radius); + } + + @Nonnull + public static PhysicsChunkCollisionBuildStats refreshAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Vector3d center, + int radius) { + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "refresh PhysicsChunk collision"); + return refreshAroundChecked(world, checkedStore, spaceRef, center, radius); + } + + @Nonnull + private static PhysicsChunkCollisionBuildStats refreshAroundChecked(@Nonnull World world, + @Nonnull Store checkedStore, + @Nonnull Ref spaceRef, + @Nonnull Vector3d center, + int radius) { + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceRef); return streaming(world).refreshAround(world, settings.spaceUuid(), checkedStore.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()), @@ -92,7 +144,36 @@ public static PhysicsChunkCollisionPrewarmStats ensureAround(@Nonnull World worl Store checkedStore = requireMatchingWorldThread(world, store, "ensure PhysicsChunk collision"); - PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceId); + return ensureAroundChecked(world, + checkedStore, + requireSpaceRef(checkedStore, spaceId), + centers, + radius, + tick); + } + + @Nonnull + public static PhysicsChunkCollisionPrewarmStats ensureAround(@Nonnull World world, + @Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Iterable centers, + int radius, + long tick) { + requireEnabled(); + Store checkedStore = requireMatchingWorldThread(world, + store, + "ensure PhysicsChunk collision"); + return ensureAroundChecked(world, checkedStore, spaceRef, centers, radius, tick); + } + + @Nonnull + private static PhysicsChunkCollisionPrewarmStats ensureAroundChecked(@Nonnull World world, + @Nonnull Store checkedStore, + @Nonnull Ref spaceRef, + @Nonnull Iterable centers, + int radius, + long tick) { + PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceRef); return streaming(world).ensureAround(world, settings.spaceUuid(), checkedStore.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()), @@ -109,9 +190,18 @@ public static int clearSpace(@Nonnull World world, Store checkedStore = requireMatchingWorldThread(world, store, "clear PhysicsChunk collision"); - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, - Objects.requireNonNull(spaceId, "spaceId")); - return clearSpaceRows(world, checkedStore, spaceUuid); + return clearSpaceRows(world, + checkedStore, + requireSpaceUuid(checkedStore, requireSpaceRef(checkedStore, spaceId))); + } + + public static int clearSpace(@Nonnull World world, + @Nonnull Store store, + @Nonnull Ref spaceRef) { + Store checkedStore = requireMatchingWorldThread(world, + store, + "clear PhysicsChunk collision"); + return clearSpaceRows(world, checkedStore, requireSpaceUuid(checkedStore, spaceRef)); } @Nonnull @@ -146,24 +236,17 @@ private static Store requireMatchingWorldThread(@Nonnull World wor } @Nonnull - private static PhysicsChunkSpaceSettings requireSettings( - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, - Objects.requireNonNull(spaceId, "spaceId")); - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - if (spaceRef == null || !spaceRef.isValid()) { - throw new IllegalStateException("PhysicsStore space id=" + spaceId.value() - + " is not bound yet"); - } + private static PhysicsChunkSpaceSettings requireSettings(@Nonnull Store store, + @Nonnull Ref spaceRef) { + Ref checkedRef = requireValidSpaceRef(store, spaceRef); + UUID spaceUuid = requireSpaceUuid(store, checkedRef); ChunkCollisionSettingsComponent component = - store.getComponent(spaceRef, ChunkCollisionSettingsComponent.getComponentType()); + store.getComponent(checkedRef, ChunkCollisionSettingsComponent.getComponentType()); ChunkCollisionSettingsComponent settings = component != null ? component : new ChunkCollisionSettingsComponent(); if (settings.getMode() == PhysicsChunkCollisionMode.NONE) { throw new IllegalStateException("PhysicsChunk collision is disabled for space " - + spaceId); + + spaceUuid); } return new PhysicsChunkSpaceSettings(spaceUuid, settings.getMode(), @@ -174,6 +257,49 @@ private static PhysicsChunkSpaceSettings requireSettings( settings.getTtlTicks()); } + @Nonnull + private static Ref requireSpaceRef(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + SpaceId checkedSpaceId = Objects.requireNonNull(spaceId, "spaceId"); + Ref ref = PhysicsSpaces.resolveRef(store, checkedSpaceId); + if (ref != null) { + return ref; + } + if (PhysicsSpaces.hasSpace(store, checkedSpaceId)) { + throw new IllegalStateException("PhysicsStore space id=" + checkedSpaceId.value() + + " is not bound yet"); + } + throw new IllegalArgumentException("PhysicsStore space id=" + checkedSpaceId.value() + + " does not exist"); + } + + @Nonnull + private static Ref requireValidSpaceRef(@Nonnull Store store, + @Nonnull Ref spaceRef) { + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + if (checkedRef.getStore() != store || !checkedRef.isValid()) { + throw new IllegalArgumentException("PhysicsStore space entity is not valid: " + + checkedRef); + } + if (store.getComponent(checkedRef, SpaceComponent.getComponentType()) == null) { + throw new IllegalArgumentException("PhysicsStore entity is not a space entity: " + + checkedRef); + } + return checkedRef; + } + + @Nonnull + private static UUID requireSpaceUuid(@Nonnull Store store, + @Nonnull Ref spaceRef) { + Ref checkedRef = requireValidSpaceRef(store, spaceRef); + UuidComponent uuid = store.getComponent(checkedRef, UuidComponent.getComponentType()); + if (uuid == null) { + throw new IllegalStateException("PhysicsStore space entity has no UUID: " + + checkedRef); + } + return uuid.getUuid(); + } + @Nonnull private static PhysicsChunkCollisionStreamingResource streaming(@Nonnull World world) { Store entityStore = Objects.requireNonNull(world, "world") diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index a9e25273..3b6e9b10 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -12,7 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; @@ -71,14 +70,15 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, DEFAULT_RADIUS, 1, MAX_RADIUS); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { + ExamplePhysicsUtils.SpaceSelection space = + ExamplePhysicsUtils.spaceSelection(ctx, world, spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } Store physicsStore = physicsStore(world); PhysicsChunkCollisionBuildStats stats = PhysicsChunkCollision.rebuildAround(world, physicsStore, - spaceId, + space.spaceRef(), playerPos, radius); @@ -125,15 +125,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull World world) { Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int radius = ExamplePhysicsUtils.optionalInt(ctx, radiusArg, DEFAULT_RADIUS, 1, MAX_RADIUS); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { + ExamplePhysicsUtils.SpaceSelection space = + ExamplePhysicsUtils.spaceSelection(ctx, world, spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } Store physicsStore = physicsStore(world); PhysicsChunkCollisionPrewarmStats stats = PhysicsChunkCollision.ensureAround(world, physicsStore, - spaceId, + space.spaceRef(), List.of(playerPos), radius, Math.max(0L, world.getTick())); @@ -167,12 +168,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { + ExamplePhysicsUtils.SpaceSelection space = + ExamplePhysicsUtils.spaceSelection(ctx, world, spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } Store physicsStore = physicsStore(world); - int removed = PhysicsChunkCollision.clearSpace(world, physicsStore, spaceId); + int removed = PhysicsChunkCollision.clearSpace(world, physicsStore, space.spaceRef()); ctx.sender().sendMessage(Message.raw("Removed " + removed + " PhysicsChunk collision bodies.")); return CompletableFuture.completedFuture(null); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index e8529b05..aefe8c7d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -346,7 +346,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } PhysicsChunkCollisionPrewarmStats stats = PhysicsChunkCollision.ensureAround(world, physicsStore, - spaceId, + spaceRef, List.of(spawn), Math.max(8, radius + 6), Math.max(0L, world.getTick())); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 53aca68d..f15e37ab 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -173,7 +173,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, StressLayout layout = StressLayout.forCount(count, playerPos); long prewarmStartNanos = System.nanoTime(); int prewarmedSections = prewarmStressTerrain(world, - spaceId, + spaceRef, settings, mode, layout, @@ -343,7 +343,7 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store spaceRef, @Nonnull PhysicsSpaceSettings settings, @Nonnull StressMode mode, @Nonnull StressLayout layout, @@ -357,7 +357,7 @@ private static int prewarmStressTerrain(@Nonnull World world, PhysicsChunkCollisionPrewarmStats stats = PhysicsChunkCollision.ensureAround(world, PhysicsThreading.store(world), - spaceId, + spaceRef, layout.positions(count), chunkCollisionSettings.getBodyRadius(), 0L); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 9ab633e1..79219643 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -137,12 +137,12 @@ private static ExplosionResult explode(@Nonnull ComponentAccessor e Store physicsStore = PhysicsThreading.store(world); PhysicsChunkCollision.refreshAround(world, physicsStore, - spaceId, + spaceRef, center, Math.max(8, settings.getRadius() + 4)); PhysicsChunkCollision.ensureAround(world, physicsStore, - spaceId, + spaceRef, groupCenters(groups), Math.max(8, maxGroupCollisionRadius(groups) + 4), Math.max(0L, world.getTick())); From ff7e6101d6b914d7bd4efa32028a63e6db1496ed Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:54:29 +0200 Subject: [PATCH 438/534] refactor(examples): show direct physicsstore body authoring Signed-off-by: Blovien --- .../examples/commands/DropCommand.java | 35 +++++++-- .../examples/commands/MaterialsCommand.java | 56 +++++++++++---- .../examples/commands/ShapesCommand.java | 72 +++++++++++++++---- .../examples/utils/ExamplePhysicsUtils.java | 71 ++---------------- 4 files changed, 139 insertions(+), 95 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index 70394744..0f16b125 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.examples.commands; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -11,13 +12,19 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import org.joml.Quaternionf; import org.joml.Vector3d; +import org.joml.Vector3f; /** * Spawn a PhysicsStore body row with an attached visible block entity. @@ -48,6 +55,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, float spawnX = (float) playerPos.x(); float spawnY = (float) playerPos.y() + 5f; float spawnZ = (float) playerPos.z(); + Vector3d position = new Vector3d(spawnX, spawnY, spawnZ); ExamplePhysicsUtils.SpaceSelection space = ExamplePhysicsUtils.spaceSelection(ctx, world, @@ -57,16 +65,31 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } TimeResource time = store.getResource(TimeResource.getResourceType()); - ExamplePhysicsUtils.spawnBlockBody(store, - time, - space.spaceRef(), - space.spaceId(), - new Vector3d(spawnX, spawnY, spawnZ), - blockType(ctx), + Store physicsStore = PhysicsThreading.store(world); + PhysicsThreading.requireWorldThread(physicsStore, + "spawn an example PhysicsStore body entity"); + UUID bodyUuid = UUID.randomUUID(); + BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(space.spaceRef(), + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.material(0.5f, 0.5f), null); + Ref bodyRef = physicsStore.addEntity( + ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), + AddReason.SPAWN); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( + time, + bodyRef, + bodyUuid, + blockType(ctx), + position, + new Vector3f(), + new Quaternionf(), + Float.NaN, + true), + AddReason.SPAWN); ctx.sender() .sendMessage(Message.raw("Dropped box at " + spawnX + ", " + spawnY + ", " + spawnZ)); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 9f141c2a..be3c9ed3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.examples.commands; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -12,12 +13,15 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; @@ -48,17 +52,33 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + PhysicsThreading.requireWorldThread(physicsStore, + "spawn example PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-3.0, 5.0, 4.0); - spawnSphere(store, time, space.spaceRef(), space.spaceId(), new Vector3d(origin), + spawnSphere(store, + physicsStore, + time, + space.spaceRef(), + new Vector3d(origin), 0.05f, 0.9f, 3.0f); - spawnSphere(store, time, space.spaceRef(), space.spaceId(), + spawnSphere(store, + physicsStore, + time, + space.spaceRef(), new Vector3d(origin).add(2.0, 0.0, 0.0), 0.95f, 0.9f, 3.0f); - spawnSphere(store, time, space.spaceRef(), space.spaceId(), + spawnSphere(store, + physicsStore, + time, + space.spaceRef(), new Vector3d(origin).add(4.0, 0.0, 0.0), 0.5f, 0.0f, 2.0f); - spawnSphere(store, time, space.spaceRef(), space.spaceId(), + spawnSphere(store, + physicsStore, + time, + space.spaceRef(), new Vector3d(origin).add(6.0, 0.0, 0.0), 0.5f, 0.95f, 2.0f); @@ -68,22 +88,34 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawnSphere(@Nonnull Store store, + @Nonnull Store physicsStore, @Nonnull TimeResource time, @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float restitution, float friction, float speed) { - ExamplePhysicsUtils.spawnBlockBody(store, - time, - spaceRef, - spaceId, - position, - ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + UUID bodyUuid = UUID.randomUUID(); + BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(spaceRef, + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.sphere(0.5f), 1.0f, RigidBodySpawnSettings.material(friction, restitution), new Vector3f(speed, 0.0f, 0.0f)); + Ref bodyRef = physicsStore.addEntity( + ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), + AddReason.SPAWN); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( + time, + bodyRef, + bodyUuid, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + position, + new Vector3f(), + new Quaternionf(), + Float.NaN, + true), + AddReason.SPAWN); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index 89af288f..84be66aa 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.examples.commands; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -13,13 +14,17 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import org.joml.Quaternionf; import org.joml.Vector3d; +import org.joml.Vector3f; public class ShapesCommand extends AbstractAsyncPlayerCommand { @@ -48,17 +53,45 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + PhysicsThreading.requireWorldThread(physicsStore, + "spawn example PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-4.0, 3.0, 3.0); - spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.BOX, PhysicsAxis.Y, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.BOX, + PhysicsAxis.Y, origin, 0); - spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.SPHERE, PhysicsAxis.Y, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.SPHERE, + PhysicsAxis.Y, origin, 2); - spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.CAPSULE, PhysicsAxis.Y, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.CAPSULE, + PhysicsAxis.Y, origin, 4); - spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.CYLINDER, PhysicsAxis.Y, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.CYLINDER, + PhysicsAxis.Y, origin, 6); - spawn(store, time, space.spaceRef(), space.spaceId(), ShapeType.CONE, PhysicsAxis.Y, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.CONE, + PhysicsAxis.Y, origin, 8); ctx.sender().sendMessage(Message.raw("Spawned shape demo.")); @@ -66,23 +99,36 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawn(@Nonnull Store store, + @Nonnull Store physicsStore, @Nonnull TimeResource time, @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, @Nonnull ShapeType type, @Nonnull PhysicsAxis axis, @Nonnull Vector3d origin, int xOffset) { - ExamplePhysicsUtils.spawnBlockBody(store, - time, - spaceRef, - spaceId, - new Vector3d(origin).add(xOffset, 0.0, 0.0), - ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + Vector3d position = new Vector3d(origin).add(xOffset, 0.0, 0.0); + UUID bodyUuid = UUID.randomUUID(); + BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(spaceRef, + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), shape(type, axis), 1.0f, RigidBodySpawnSettings.material(0.7f, 0.35f), null); + Ref bodyRef = physicsStore.addEntity( + ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), + AddReason.SPAWN); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( + time, + bodyRef, + bodyUuid, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + position, + new Vector3f(), + new Quaternionf(), + Float.NaN, + true), + AddReason.SPAWN); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 74d24e9b..ac4eded2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -174,6 +174,13 @@ private static Ref addPhysicsStoreBodyUnchecked(@Nonnull Store bodyHolder(@Nonnull Store store, + @Nonnull BodyEntityDescriptor descriptor) { + Objects.requireNonNull(descriptor, "descriptor"); + return bodyHolder(store, descriptor, descriptor.dynamics(), descriptor.target()); + } + @Nonnull private static Holder bodyHolder(@Nonnull Store store, @Nonnull BodyEntityDescriptor descriptor, @@ -231,32 +238,6 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, return firstSpaceId; } - @Nonnull - public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - CreatedBlockBody physicsStoreBody = tryCreatePhysicsStoreBlockBody(store, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - linearVelocity); - if (physicsStoreBody != null) { - return attachBlockBody(store, time, physicsStoreBody); - } - - throw new IllegalStateException("Cannot spawn block body because the target space is not " - + "bound in PhysicsStore: " + spaceId.value()); - } - @Nonnull public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, @Nonnull TimeResource time, @@ -281,44 +262,6 @@ public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, linearVelocity)); } - @Nullable - private static CreatedBlockBody tryCreatePhysicsStoreBlockBody(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - - World world = store.getExternalData().getWorld(); - Ref spaceRef; - try { - spaceRef = resolveSpaceRef(world, spaceId); - } catch (IllegalStateException exception) { - return null; - } - if (spaceRef == null) { - return null; - } - - UUID bodyUuid = UUID.randomUUID(); - try { - return createPhysicsStoreBlockBody(world, - spaceRef, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - linearVelocity, - bodyUuid); - } catch (IllegalStateException exception) { - return null; - } - } - @Nonnull private static CreatedBlockBody createPhysicsStoreBlockBody(@Nonnull World world, @Nonnull Ref spaceRef, From ad4eb62e43a824247ded8ac0a0051ca580b852b5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 09:57:00 +0200 Subject: [PATCH 439/534] refactor(examples): remove hidden block body spawn helper Signed-off-by: Blovien --- .../commands/stress/StressShapesCommand.java | 85 ++++++++++++++----- .../examples/utils/ExamplePhysicsUtils.java | 77 ----------------- 2 files changed, 63 insertions(+), 99 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index f3bcaac2..b2dfb9e2 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.examples.commands.stress; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -13,13 +14,17 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import org.joml.Quaternionf; import org.joml.Vector3d; +import org.joml.Vector3f; public class StressShapesCommand extends AbstractAsyncPlayerCommand { @@ -54,18 +59,16 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d playerPos = new Vector3d(playerRef.getTransform().getPosition()); int sets = ExamplePhysicsUtils.optionalInt(ctx, setsArg, DEFAULT_SETS, 1, MAX_SETS); - SpaceId spaceId = ExamplePhysicsUtils.spaceId(ctx, world, spaceArg); - if (spaceId == null) { - return CompletableFuture.completedFuture(null); - } - Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, - spaceId); - if (spaceRef == null) { - ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() - + " is not bound yet.")); + ExamplePhysicsUtils.SpaceSelection space = ExamplePhysicsUtils.spaceSelection(ctx, + world, + spaceArg); + if (space == null) { return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + PhysicsThreading.requireWorldThread(physicsStore, + "spawn stress shape PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-12.0, 5.0, 5.0); for (int set = 0; set < sets; set++) { @@ -74,15 +77,40 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, int col = set % 4; Vector3d base = new Vector3d(origin).add(col * 7.0, row * 2.2, row * 1.5); - spawn(store, time, spaceRef, spaceId, ShapeType.BOX, axis, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.BOX, + axis, base, 0.0); - spawn(store, time, spaceRef, spaceId, ShapeType.SPHERE, axis, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.SPHERE, + axis, base, 1.2); - spawn(store, time, spaceRef, spaceId, ShapeType.CAPSULE, axis, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.CAPSULE, + axis, base, 2.4); - spawn(store, time, spaceRef, spaceId, ShapeType.CYLINDER, axis, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.CYLINDER, + axis, base, 3.6); - spawn(store, time, spaceRef, spaceId, ShapeType.CONE, axis, + spawn(store, + physicsStore, + time, + space.spaceRef(), + ShapeType.CONE, + axis, base, 4.8); } @@ -92,23 +120,36 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawn(@Nonnull Store store, + @Nonnull Store physicsStore, @Nonnull TimeResource time, @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, @Nonnull ShapeType type, @Nonnull PhysicsAxis axis, @Nonnull Vector3d base, double xOffset) { - ExamplePhysicsUtils.spawnBlockBody(store, - time, - spaceRef, - spaceId, - new Vector3d(base).add(xOffset, 0.0, 0.0), - ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + Vector3d position = new Vector3d(base).add(xOffset, 0.0, 0.0); + UUID bodyUuid = UUID.randomUUID(); + BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(spaceRef, + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), shape(type, axis), 1.0f, RigidBodySpawnSettings.material(0.6f, 0.25f), null); + Ref bodyRef = physicsStore.addEntity( + ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), + AddReason.SPAWN); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( + time, + bodyRef, + bodyUuid, + ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE, + position, + new Vector3f(), + new Quaternionf(), + Float.NaN, + true), + AddReason.SPAWN); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index ac4eded2..574db55f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -238,83 +238,6 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, return firstSpaceId; } - @Nonnull - public static SpawnedBlockBody spawnBlockBody(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - return attachBlockBody(store, - time, - createPhysicsStoreBlockBody(store.getExternalData().getWorld(), - spaceRef, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - linearVelocity)); - } - - @Nonnull - private static CreatedBlockBody createPhysicsStoreBlockBody(@Nonnull World world, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - return createPhysicsStoreBlockBody(world, - spaceRef, - spaceId, - visualPosition, - blockType, - shape, - mass, - settings, - linearVelocity, - UUID.randomUUID()); - } - - @Nonnull - private static CreatedBlockBody createPhysicsStoreBlockBody(@Nonnull World world, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d visualPosition, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull UUID bodyUuid) { - Vector3f bodyCenter = toVector3f(visualPosition); - Ref bodyRef = addPhysicsStoreBody(world, - bodyEntity(spaceRef, - bodyUuid, - bodyCenter, - shape, - mass, - settings, - linearVelocity)); - - return new CreatedBlockBody(bodyUuid, - bodyRef, - spaceId, - blockType, - (float) visualPosition.x, - (float) visualPosition.y, - (float) visualPosition.z, - mass > 0.0f); - } - @Nonnull public static BodyEntityDescriptor bodyEntity(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, From 0d6cd91b423f68a35ff5f8d2a38e17100aa7352c Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:07:59 +0200 Subject: [PATCH 440/534] refactor(core): add ref-first physics runtime indexes Signed-off-by: Blovien --- .../resources/PhysicsRuntimeResource.java | 102 +++++++++++++++--- 1 file changed, 87 insertions(+), 15 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index 18cfd3d5..a99026d2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -8,6 +8,7 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; @@ -53,6 +54,12 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap backendIdsBySpaceRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull + private final Int2ObjectOpenHashMap unambiguousBackendIdsBySpaceHandle = + new Int2ObjectOpenHashMap<>(); + @Nonnull + private final Int2IntOpenHashMap spaceHandleBindingCounts = + new Int2IntOpenHashMap(); + @Nonnull private final Map bodyHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull @@ -83,6 +90,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap jointSpaceHandlesByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull + private final Int2ObjectOpenHashMap> jointRefsByRowIndex = + new Int2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap chunkCollisionPayloadKeysByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -128,13 +138,16 @@ public void putSpaceBinding(@Nonnull UUID spaceUuid, backendIdsBySpaceRowIndex.remove(previousRowIndex); spaceHandlesByRowIndex.remove(previousRowIndex); } + BackendSpaceHandle previousHandle = spaceHandlesByUuid.remove(spaceUuid); + removeSpaceHandleRuntimeIndex(previousHandle); + spaceHandlesByUuid.put(spaceUuid, handle); int rowIndex = checkedSpaceRef.getIndex(); backendIdsBySpaceUuid.put(spaceUuid, backendId); - spaceHandlesByUuid.put(spaceUuid, handle); spaceRefsByUuid.put(spaceUuid, checkedSpaceRef); spaceUuidsByRowIndex.put(rowIndex, spaceUuid); backendIdsBySpaceRowIndex.put(rowIndex, backendId); spaceHandlesByRowIndex.put(rowIndex, handle); + addSpaceHandleRuntimeIndex(handle, backendId); } @Nullable @@ -163,6 +176,7 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { backendIdsBySpaceRowIndex.remove(rowIndex); } if (removed != null) { + removeSpaceHandleRuntimeIndex(removed); LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); if (bodyHandles != null) { bodyHandles.forEach((long bodyHandle) -> { @@ -210,6 +224,7 @@ public BackendSpaceHandle getBodySpaceHandle(@Nonnull Ref bodyRef) } public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { + removePendingBodyOperations(bodyRef); BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); int rowIndex = bodyRef.getIndex(); @@ -313,6 +328,13 @@ public List drainPendingBodyOperations() { return drained; } + public void removePendingBodyOperations(@Nonnull Ref bodyRef) { + Ref checkedBodyRef = Objects.requireNonNull(bodyRef, "bodyRef"); + pendingBodyOperations.removeIf(operation -> + operation.bodyRef().getStore() == checkedBodyRef.getStore() + && operation.bodyRef().getIndex() == checkedBodyRef.getIndex()); + } + private void putJointHandle(@Nonnull UUID jointUuid, @Nonnull Ref jointRef, @Nonnull BackendSpaceHandle spaceHandle, @@ -323,6 +345,7 @@ private void putJointHandle(@Nonnull UUID jointUuid, int previousRowIndex = previousRef.getIndex(); jointHandlesByRowIndex.remove(previousRowIndex); jointSpaceHandlesByRowIndex.remove(previousRowIndex); + jointRefsByRowIndex.remove(previousRowIndex); } int rowIndex = checkedJointRef.getIndex(); jointHandlesByUuid.put(jointUuid, handle); @@ -330,6 +353,7 @@ private void putJointHandle(@Nonnull UUID jointUuid, jointRefsByUuid.put(jointUuid, checkedJointRef); jointHandlesByRowIndex.put(rowIndex, handle); jointSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); + jointRefsByRowIndex.put(rowIndex, checkedJointRef); } public void putJointHandle(@Nonnull Ref jointRef, @@ -357,6 +381,7 @@ public void removeJointHandle(@Nonnull UUID jointUuid) { int rowIndex = jointRef.getIndex(); jointHandlesByRowIndex.remove(rowIndex); jointSpaceHandlesByRowIndex.remove(rowIndex); + jointRefsByRowIndex.remove(rowIndex); } } @@ -366,6 +391,7 @@ public void removeJointHandle(@Nonnull UUID jointUuid, int rowIndex = jointRef.getIndex(); jointHandlesByRowIndex.remove(rowIndex); jointSpaceHandlesByRowIndex.remove(rowIndex); + jointRefsByRowIndex.remove(rowIndex); } @Nonnull @@ -375,7 +401,7 @@ public List> jointRefsForSpaceHandle( int targetSpaceHandle = spaceHandle.value(); jointSpaceHandlesByRowIndex.forEach((rowIndex, handle) -> { if (handle.value() == targetSpaceHandle) { - Ref jointRef = jointRefForRowIndex((int) rowIndex); + Ref jointRef = jointRefsByRowIndex.get((int) rowIndex); if (jointRef != null) { jointRefs.add(jointRef); } @@ -432,6 +458,8 @@ public void clear() { spaceUuidsByRowIndex.clear(); spaceHandlesByRowIndex.clear(); backendIdsBySpaceRowIndex.clear(); + unambiguousBackendIdsBySpaceHandle.clear(); + spaceHandleBindingCounts.clear(); bodyHandlesByUuid.clear(); bodySpaceHandlesByUuid.clear(); bodyRefsByRowIndex.clear(); @@ -442,6 +470,7 @@ public void clear() { jointRefsByUuid.clear(); jointHandlesByRowIndex.clear(); jointSpaceHandlesByRowIndex.clear(); + jointRefsByRowIndex.clear(); chunkCollisionPayloadKeysByRowIndex.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); @@ -530,14 +559,64 @@ public PhysicsBackendRuntime runtimeForSpaceHandle(@Nullable BackendSpaceHandle if (target == null) { return null; } + BackendId backendId = unambiguousBackendIdsBySpaceHandle.get(target.value()); + return backendId != null ? runtimesByBackend.get(backendId) : null; + } + + private void addSpaceHandleRuntimeIndex(@Nonnull BackendSpaceHandle handle, + @Nonnull BackendId backendId) { + int handleValue = handle.value(); + int bindingCount = spaceHandleBindingCounts.get(handleValue) + 1; + spaceHandleBindingCounts.put(handleValue, bindingCount); + if (bindingCount == 1) { + unambiguousBackendIdsBySpaceHandle.put(handleValue, backendId); + } else { + unambiguousBackendIdsBySpaceHandle.remove(handleValue); + } + } + + private void removeSpaceHandleRuntimeIndex(@Nullable BackendSpaceHandle handle) { + if (handle == null) { + return; + } + int handleValue = handle.value(); + int bindingCount = spaceHandleBindingCounts.get(handleValue); + if (bindingCount <= 1) { + spaceHandleBindingCounts.remove(handleValue); + unambiguousBackendIdsBySpaceHandle.remove(handleValue); + return; + } + int remainingBindingCount = bindingCount - 1; + spaceHandleBindingCounts.put(handleValue, remainingBindingCount); + if (remainingBindingCount == 1) { + BackendId backendId = uniqueBackendIdForSpaceHandleValue(handleValue); + if (backendId != null) { + unambiguousBackendIdsBySpaceHandle.put(handleValue, backendId); + } else { + unambiguousBackendIdsBySpaceHandle.remove(handleValue); + } + } else { + unambiguousBackendIdsBySpaceHandle.remove(handleValue); + } + } + + @Nullable + private BackendId uniqueBackendIdForSpaceHandleValue(int handleValue) { + BackendId uniqueBackendId = null; for (Map.Entry entry : spaceHandlesByUuid.entrySet()) { - if (entry.getValue().value() != target.value()) { + if (entry.getValue().value() != handleValue) { continue; } BackendId backendId = backendIdsBySpaceUuid.get(entry.getKey()); - return backendId != null ? runtimesByBackend.get(backendId) : null; + if (backendId == null) { + continue; + } + if (uniqueBackendId != null && !uniqueBackendId.equals(backendId)) { + return null; + } + uniqueBackendId = backendId; } - return null; + return uniqueBackendId; } @Nonnull @@ -561,6 +640,8 @@ public PhysicsRuntimeResource clone() { copy.spaceUuidsByRowIndex.putAll(spaceUuidsByRowIndex); copy.spaceHandlesByRowIndex.putAll(spaceHandlesByRowIndex); copy.backendIdsBySpaceRowIndex.putAll(backendIdsBySpaceRowIndex); + copy.unambiguousBackendIdsBySpaceHandle.putAll(unambiguousBackendIdsBySpaceHandle); + copy.spaceHandleBindingCounts.putAll(spaceHandleBindingCounts); copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); copy.bodyRefsByRowIndex.putAll(bodyRefsByRowIndex); @@ -571,6 +652,7 @@ public PhysicsRuntimeResource clone() { copy.jointRefsByUuid.putAll(jointRefsByUuid); copy.jointHandlesByRowIndex.putAll(jointHandlesByRowIndex); copy.jointSpaceHandlesByRowIndex.putAll(jointSpaceHandlesByRowIndex); + copy.jointRefsByRowIndex.putAll(jointRefsByRowIndex); copy.chunkCollisionPayloadKeysByRowIndex.putAll(chunkCollisionPayloadKeysByRowIndex); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); @@ -720,14 +802,4 @@ private void markRegistrationTopologyChanged() { registrationTopologyGeneration++; } - @Nullable - private Ref jointRefForRowIndex(int rowIndex) { - for (Ref jointRef : jointRefsByUuid.values()) { - if (jointRef.getIndex() == rowIndex) { - return jointRef; - } - } - return null; - } - } From a3ced65b14d268d3ff2f16740bf4649897e1f22a Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:08:36 +0200 Subject: [PATCH 441/534] test(core): cover physics runtime ref indexes Signed-off-by: Blovien --- .../PhysicsStoreResourceIndexTest.java | 102 +++++++++++++----- 1 file changed, 75 insertions(+), 27 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index b96175f2..5a51d464 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; @@ -51,15 +52,33 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { BackendId backendId = new BackendId("test:runtime-index"); PhysicsBackendRuntime backendRuntime = new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + BackendId otherBackendId = new BackendId("test:runtime-index-other"); + PhysicsBackendRuntime otherBackendRuntime = + new FakePhysicsBackendRuntimeProvider(otherBackendId, false, false).createRuntime(); UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000003"); + UUID collidingSpaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000013"); UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000004"); + BackendSpaceHandle oldSpaceHandle = new BackendSpaceHandle(30); BackendSpaceHandle spaceHandle = new BackendSpaceHandle(31); BackendBodyHandle bodyHandle = new BackendBodyHandle(42L); - Ref spaceRef = new TestRef(true); - Ref bodyRef = new TestRef(true); + Ref spaceRef = new TestRef(1); + Ref collidingSpaceRef = new TestRef(13); + Ref bodyRef = new TestRef(2); runtime.putRuntime(backendId, backendRuntime); + runtime.putRuntime(otherBackendId, otherBackendRuntime); + runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, oldSpaceHandle); + assertSame(backendRuntime, runtime.runtimeForSpaceHandle(oldSpaceHandle)); + runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + assertNull(runtime.runtimeForSpaceHandle(oldSpaceHandle)); + assertSame(backendRuntime, runtime.runtimeForSpaceHandle(spaceHandle)); + + runtime.putSpaceBinding(collidingSpaceUuid, collidingSpaceRef, otherBackendId, spaceHandle); + assertNull(runtime.runtimeForSpaceHandle(spaceHandle)); + runtime.removeSpaceHandle(collidingSpaceUuid); + assertSame(backendRuntime, runtime.runtimeForSpaceHandle(spaceHandle)); + runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); runtime.putBodyHitMetadata(bodyHandle, bodyRef, PhysicsBodyType.DYNAMIC, ShapeType.BOX); @@ -84,10 +103,15 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { handles.clear(); runtime.forEachBodyHandle(spaceHandle, handles::add); assertEquals(List.of(), handles); + + runtime.removeSpaceHandle(spaceUuid); + + assertNull(runtime.runtimeForSpaceHandle(spaceHandle)); + assertNull(runtime.getSpaceHandle(spaceRef)); } @Test - void runtimeIndexesExposeRefsForTopologyCleanup() throws ReflectiveOperationException { + void runtimeIndexesExposeRefsForTopologyCleanup() { PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000007"); UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000008"); @@ -96,22 +120,58 @@ void runtimeIndexesExposeRefsForTopologyCleanup() throws ReflectiveOperationExce BackendSpaceHandle spaceHandle = new BackendSpaceHandle(43); BackendBodyHandle bodyHandle = new BackendBodyHandle(44L); BackendJointHandle jointHandle = new BackendJointHandle(45L); - Ref spaceRef = new TestRef(true); - Ref bodyRef = new TestRef(true); - Ref jointRef = new TestRef(true); + BackendJointHandle reboundJointHandle = new BackendJointHandle(46L); + Ref spaceRef = new TestRef(3); + Ref bodyRef = new TestRef(4); + Ref jointRef = new TestRef(5); + Ref reboundJointRef = new TestRef(6); runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); runtime.putJointHandle(jointRef, jointUuid, spaceHandle, jointHandle); - assertEquals(List.of(bodyRef), refsFor(runtime, "bodyRefsForSpaceHandle", spaceHandle)); - assertEquals(List.of(jointRef), refsFor(runtime, "jointRefsForSpaceHandle", spaceHandle)); + assertEquals(List.of(bodyRef), runtime.bodyRefsForSpaceHandle(spaceHandle)); + assertEquals(List.of(jointRef), runtime.jointRefsForSpaceHandle(spaceHandle)); + + runtime.putJointHandle(reboundJointRef, jointUuid, spaceHandle, reboundJointHandle); + + assertNull(runtime.getJointHandle(jointRef)); + assertEquals(reboundJointHandle, runtime.getJointHandle(reboundJointRef)); + assertEquals(List.of(reboundJointRef), runtime.jointRefsForSpaceHandle(spaceHandle)); runtime.removeBodyHandle(bodyUuid, bodyRef); - runtime.removeJointHandle(jointUuid, jointRef); + runtime.removeJointHandle(jointUuid, reboundJointRef); - assertEquals(List.of(), refsFor(runtime, "bodyRefsForSpaceHandle", spaceHandle)); - assertEquals(List.of(), refsFor(runtime, "jointRefsForSpaceHandle", spaceHandle)); + assertEquals(List.of(), runtime.bodyRefsForSpaceHandle(spaceHandle)); + assertEquals(List.of(), runtime.jointRefsForSpaceHandle(spaceHandle)); + } + + @Test + void removeBodyHandleClearsPendingBodyOperationsForThatRef() { + PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); + UUID firstBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000011"); + UUID secondBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000012"); + Ref firstBodyRef = new TestRef(11); + Ref secondBodyRef = new TestRef(12); + + runtime.enqueuePendingBodyOperation(PhysicsRuntimeResource.PendingBodyOperation.wake( + firstBodyUuid, + firstBodyRef, + null, + null)); + runtime.enqueuePendingBodyOperation(PhysicsRuntimeResource.PendingBodyOperation.sleep( + secondBodyUuid, + secondBodyRef, + null, + null)); + + runtime.removeBodyHandle(firstBodyUuid, firstBodyRef); + + List drained = + runtime.drainPendingBodyOperations(); + assertEquals(1, drained.size()); + assertEquals(secondBodyUuid, drained.getFirst().bodyUuid()); + assertEquals(secondBodyRef, drained.getFirst().bodyRef()); } @Test @@ -142,27 +202,15 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { assertNull(resource.getBody(bodyUuid)); } - private static final class TestRef extends Ref { + private static final class TestRef extends Ref { - private final boolean valid; - - private TestRef(boolean valid) { - super(null); - this.valid = valid; + private TestRef(int index) { + super(null, index); } @Override public boolean isValid() { - return valid; + return true; } } - - @SuppressWarnings("unchecked") - private static List refsFor(PhysicsRuntimeResource runtime, - String methodName, - BackendSpaceHandle spaceHandle) throws ReflectiveOperationException { - return (List) PhysicsRuntimeResource.class - .getMethod(methodName, BackendSpaceHandle.class) - .invoke(runtime, spaceHandle); - } } From 91a85f211209ba2bec97ab95fc406ede497efea6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:15:35 +0200 Subject: [PATCH 442/534] refactor(core): resolve physics runtimes by row ref Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutations.java | 4 +- .../resources/PhysicsRuntimeResource.java | 61 ++++++++++++++++++- .../systems/BodyCommandApplicationSystem.java | 7 +-- .../ChunkCollisionComponentSyncSystem.java | 2 +- .../ChunkCollisionMutationDrainSystem.java | 6 +- .../ChunkCollisionVoxelStitchingSystem.java | 2 +- .../internal/systems/JointBindingSystem.java | 4 +- .../systems/StaleBodyRemovalSystem.java | 2 +- .../internal/systems/TargetBindingSystem.java | 9 +-- 9 files changed, 71 insertions(+), 26 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 2c420b99..f40528fe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -135,7 +135,7 @@ private static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtim runtime.removeJointHandle(removal.rowUuid(), removal.ref()); return false; } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(removal.ref()); if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); } @@ -153,7 +153,7 @@ private static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime runtime.removeBodyHandle(removal.rowUuid(), removal.ref()); return false; } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(removal.ref()); if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index a99026d2..2d2adeb7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -75,6 +75,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap bodySpaceHandlesByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull + private final Int2ObjectOpenHashMap backendIdsByBodyRowIndex = + new Int2ObjectOpenHashMap<>(); + @Nonnull private final Map jointHandlesByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull @@ -93,6 +96,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap> jointRefsByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull + private final Int2ObjectOpenHashMap backendIdsByJointRowIndex = + new Int2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap chunkCollisionPayloadKeysByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -187,6 +193,7 @@ public void removeSpaceHandle(@Nonnull UUID spaceUuid) { bodyRefsByRowIndex.remove(rowIndex); bodyHandlesByRowIndex.remove(rowIndex); bodySpaceHandlesByRowIndex.remove(rowIndex); + backendIdsByBodyRowIndex.remove(rowIndex); chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); } }); @@ -203,9 +210,15 @@ public void putBodyHandle(@Nonnull UUID bodyUuid, bodyHandlesByUuid.put(bodyUuid, handle); bodySpaceHandlesByUuid.put(bodyUuid, spaceHandle); int rowIndex = bodyRef.getIndex(); + BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); bodyRefsByRowIndex.put(rowIndex, bodyRef); bodyHandlesByRowIndex.put(rowIndex, handle); bodySpaceHandlesByRowIndex.put(rowIndex, spaceHandle); + if (backendId != null) { + backendIdsByBodyRowIndex.put(rowIndex, backendId); + } else { + backendIdsByBodyRowIndex.remove(rowIndex); + } bodyHandlesBySpaceHandle.computeIfAbsent(spaceHandle.value(), _ -> new LongArrayList()) .add(handle.value()); bodySnapshotMetadataByHandle.put(handle.value(), @@ -231,6 +244,7 @@ public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRefsByRowIndex.remove(rowIndex); BackendBodyHandle removedByRef = bodyHandlesByRowIndex.remove(rowIndex); BackendSpaceHandle spaceHandleByRef = bodySpaceHandlesByRowIndex.remove(rowIndex); + backendIdsByBodyRowIndex.remove(rowIndex); chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); removeBodyHandleIndexes(removed != null ? removed : removedByRef, spaceHandle != null ? spaceHandle : spaceHandleByRef); @@ -337,6 +351,7 @@ public void removePendingBodyOperations(@Nonnull Ref bodyRef) { private void putJointHandle(@Nonnull UUID jointUuid, @Nonnull Ref jointRef, + @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendJointHandle handle) { Ref checkedJointRef = Objects.requireNonNull(jointRef, "jointRef"); @@ -346,6 +361,7 @@ private void putJointHandle(@Nonnull UUID jointUuid, jointHandlesByRowIndex.remove(previousRowIndex); jointSpaceHandlesByRowIndex.remove(previousRowIndex); jointRefsByRowIndex.remove(previousRowIndex); + backendIdsByJointRowIndex.remove(previousRowIndex); } int rowIndex = checkedJointRef.getIndex(); jointHandlesByUuid.put(jointUuid, handle); @@ -354,13 +370,15 @@ private void putJointHandle(@Nonnull UUID jointUuid, jointHandlesByRowIndex.put(rowIndex, handle); jointSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); jointRefsByRowIndex.put(rowIndex, checkedJointRef); + backendIdsByJointRowIndex.put(rowIndex, backendId); } public void putJointHandle(@Nonnull Ref jointRef, @Nonnull UUID jointUuid, + @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendJointHandle handle) { - putJointHandle(jointUuid, jointRef, spaceHandle, handle); + putJointHandle(jointUuid, jointRef, backendId, spaceHandle, handle); } @Nullable @@ -382,6 +400,7 @@ public void removeJointHandle(@Nonnull UUID jointUuid) { jointHandlesByRowIndex.remove(rowIndex); jointSpaceHandlesByRowIndex.remove(rowIndex); jointRefsByRowIndex.remove(rowIndex); + backendIdsByJointRowIndex.remove(rowIndex); } } @@ -392,6 +411,7 @@ public void removeJointHandle(@Nonnull UUID jointUuid, jointHandlesByRowIndex.remove(rowIndex); jointSpaceHandlesByRowIndex.remove(rowIndex); jointRefsByRowIndex.remove(rowIndex); + backendIdsByJointRowIndex.remove(rowIndex); } @Nonnull @@ -465,12 +485,14 @@ public void clear() { bodyRefsByRowIndex.clear(); bodyHandlesByRowIndex.clear(); bodySpaceHandlesByRowIndex.clear(); + backendIdsByBodyRowIndex.clear(); jointHandlesByUuid.clear(); jointSpaceHandlesByUuid.clear(); jointRefsByUuid.clear(); jointHandlesByRowIndex.clear(); jointSpaceHandlesByRowIndex.clear(); jointRefsByRowIndex.clear(); + backendIdsByJointRowIndex.clear(); chunkCollisionPayloadKeysByRowIndex.clear(); bodyHandlesBySpaceHandle.clear(); bodyHitMetadataByHandle.clear(); @@ -504,6 +526,7 @@ private void removeBodyHandleIndexes(@Nullable BackendBodyHandle removed, bodyRefsByRowIndex.remove(rowIndex); bodyHandlesByRowIndex.remove(rowIndex); bodySpaceHandlesByRowIndex.remove(rowIndex); + backendIdsByBodyRowIndex.remove(rowIndex); } } @@ -512,7 +535,10 @@ public void destroyBackendBindings() { for (Map.Entry entry : new ArrayList<>(jointHandlesByUuid.entrySet())) { BackendSpaceHandle spaceHandle = jointSpaceHandlesByUuid.get(entry.getKey()); - PhysicsBackendRuntime runtime = runtimeForSpaceHandle(spaceHandle); + Ref jointRef = jointRefsByUuid.get(entry.getKey()); + PhysicsBackendRuntime runtime = jointRef != null + ? runtimeForJointRef(jointRef) + : runtimeForSpaceHandle(spaceHandle); if (spaceHandle == null || runtime == null) { continue; } @@ -525,7 +551,11 @@ public void destroyBackendBindings() { for (Map.Entry entry : new ArrayList<>(bodyHandlesByUuid.entrySet())) { BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.get(entry.getKey()); - PhysicsBackendRuntime runtime = runtimeForSpaceHandle(spaceHandle); + BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.get(entry.getValue() + .value()); + PhysicsBackendRuntime runtime = metadata != null + ? runtimeForBodyRef(metadata.bodyRef()) + : runtimeForSpaceHandle(spaceHandle); if (spaceHandle == null || runtime == null) { continue; } @@ -563,6 +593,29 @@ public PhysicsBackendRuntime runtimeForSpaceHandle(@Nullable BackendSpaceHandle return backendId != null ? runtimesByBackend.get(backendId) : null; } + @Nullable + public PhysicsBackendRuntime runtimeForSpaceRef(@Nonnull Ref spaceRef) { + return runtimeForBackendId(backendIdsBySpaceRowIndex.get( + Objects.requireNonNull(spaceRef, "spaceRef").getIndex())); + } + + @Nullable + public PhysicsBackendRuntime runtimeForBodyRef(@Nonnull Ref bodyRef) { + return runtimeForBackendId(backendIdsByBodyRowIndex.get( + Objects.requireNonNull(bodyRef, "bodyRef").getIndex())); + } + + @Nullable + public PhysicsBackendRuntime runtimeForJointRef(@Nonnull Ref jointRef) { + return runtimeForBackendId(backendIdsByJointRowIndex.get( + Objects.requireNonNull(jointRef, "jointRef").getIndex())); + } + + @Nullable + private PhysicsBackendRuntime runtimeForBackendId(@Nullable BackendId backendId) { + return backendId != null ? runtimesByBackend.get(backendId) : null; + } + private void addSpaceHandleRuntimeIndex(@Nonnull BackendSpaceHandle handle, @Nonnull BackendId backendId) { int handleValue = handle.value(); @@ -647,12 +700,14 @@ public PhysicsRuntimeResource clone() { copy.bodyRefsByRowIndex.putAll(bodyRefsByRowIndex); copy.bodyHandlesByRowIndex.putAll(bodyHandlesByRowIndex); copy.bodySpaceHandlesByRowIndex.putAll(bodySpaceHandlesByRowIndex); + copy.backendIdsByBodyRowIndex.putAll(backendIdsByBodyRowIndex); copy.jointHandlesByUuid.putAll(jointHandlesByUuid); copy.jointSpaceHandlesByUuid.putAll(jointSpaceHandlesByUuid); copy.jointRefsByUuid.putAll(jointRefsByUuid); copy.jointHandlesByRowIndex.putAll(jointHandlesByRowIndex); copy.jointSpaceHandlesByRowIndex.putAll(jointSpaceHandlesByRowIndex); copy.jointRefsByRowIndex.putAll(jointRefsByRowIndex); + copy.backendIdsByJointRowIndex.putAll(backendIdsByJointRowIndex); copy.chunkCollisionPayloadKeysByRowIndex.putAll(chunkCollisionPayloadKeysByRowIndex); bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index 5dee0fd6..5c431592 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -221,7 +221,7 @@ private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeReso } return null; } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(ref); if (backendRuntime == null) { restore.recordSoftSkip("Body command backend runtime is missing: " + bodyUuid); return null; @@ -229,11 +229,6 @@ private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeReso return new RuntimeBodyBinding(spaceHandle, bodyHandle, backendRuntime); } - private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull BackendSpaceHandle spaceHandle) { - return runtime.runtimeForSpaceHandle(spaceHandle); - } - private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtime, @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBodyType bodyType) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java index de28ea45..3db997cc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java @@ -158,7 +158,7 @@ private static void syncBackend(@Nonnull PhysicsRuntimeResource runtime, if (bodyHandle == null || spaceHandle == null) { return; } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(bodyRef); if (backendRuntime == null) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index ef827177..64ca8d91 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -141,8 +141,8 @@ private static void applyUpsert(@Nonnull Store store, Ref spaceRef = PhysicsStoreSystemSupport.refForUuid(identity, mutation.spaceUuid()); BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; - PhysicsBackendRuntime backendRuntime = spaceHandle != null - ? runtime.runtimeForSpaceHandle(spaceHandle) + PhysicsBackendRuntime backendRuntime = spaceRef != null + ? runtime.runtimeForSpaceRef(spaceRef) : null; if (spaceRef == null || spaceHandle == null || backendRuntime == null) { restore.recordSoftSkip("Chunk collision references unbound space: " @@ -385,7 +385,7 @@ private static void removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, BackendBodyHandle bodyHandle = runtime.getBodyHandle(row.ref()); BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(row.ref()); if (bodyHandle != null && spaceHandle != null) { - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(row.ref()); if (backendRuntime != null) { backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java index feec0b3f..530dbd92 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java @@ -101,7 +101,7 @@ private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, if (bodyHandle == null || spaceHandle == null) { return; } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(bodyRef); if (backendRuntime == null) { restore.recordSoftSkip("Voxel terrain backend runtime is missing: " + source.getSourceKey()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java index 7d08a1fb..c2e62d52 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java @@ -136,7 +136,7 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, joint.getMotorTargetVelocity(), joint.getMotorMaxForce()); BackendJointHandle handle = new BackendJointHandle(jointId); - runtime.putJointHandle(jointRef, jointUuid, spaceHandle, handle); + runtime.putJointHandle(jointRef, jointUuid, backendId, spaceHandle, handle); identity.putJointHandle(handle, jointRef); } catch (RuntimeException exception) { if (jointId != Long.MIN_VALUE) { @@ -210,7 +210,7 @@ private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, return; } BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointRef); - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(jointRef); if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeJoint(spaceHandle.value(), handle.value()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index c6a1977f..d54ef02e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -104,7 +104,7 @@ private static boolean removeDependentJoints(@Nonnull Store store, BackendJointHandle jointHandle = runtime.getJointHandle(joint.ref()); if (jointHandle != null) { BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(joint.ref()); - PhysicsBackendRuntime backendRuntime = runtime.runtimeForSpaceHandle(spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(joint.ref()); if (spaceHandle != null && backendRuntime != null) { try { backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java index 7131fbc8..47e1e0d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java @@ -59,7 +59,7 @@ private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, if (bodyHandle == null || spaceHandle == null) { continue; } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(ref); if (backendRuntime == null) { continue; } @@ -108,7 +108,7 @@ private static void applyPendingBodyOperations(@Nonnull PhysicsRuntimeResource r + operation.bodyUuid()); continue; } - PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceHandle); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(operation.bodyRef()); if (backendRuntime == null) { restore.recordSoftSkip("Pending body operation backend runtime is missing: " + operation.bodyUuid()); @@ -163,11 +163,6 @@ private static void applyForce(@Nonnull PhysicsBackendRuntime backendRuntime, backendRuntime.activateBody(spaceId, bodyId); } - private static PhysicsBackendRuntime runtimeForSpace(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull BackendSpaceHandle spaceHandle) { - return runtime.runtimeForSpaceHandle(spaceHandle); - } - @Nonnull @Override public Query getQuery() { From 7b006595b87eeee538114b18bdc255bf0673881b Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:16:42 +0200 Subject: [PATCH 443/534] test(core): cover ref runtime resolution Signed-off-by: Blovien --- .../PhysicsStoreResourceIndexTest.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index 5a51d464..c564d41a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -73,9 +73,12 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); assertNull(runtime.runtimeForSpaceHandle(oldSpaceHandle)); assertSame(backendRuntime, runtime.runtimeForSpaceHandle(spaceHandle)); + assertSame(backendRuntime, runtime.runtimeForSpaceRef(spaceRef)); runtime.putSpaceBinding(collidingSpaceUuid, collidingSpaceRef, otherBackendId, spaceHandle); assertNull(runtime.runtimeForSpaceHandle(spaceHandle)); + assertSame(backendRuntime, runtime.runtimeForSpaceRef(spaceRef)); + assertSame(otherBackendRuntime, runtime.runtimeForSpaceRef(collidingSpaceRef)); runtime.removeSpaceHandle(collidingSpaceUuid); assertSame(backendRuntime, runtime.runtimeForSpaceHandle(spaceHandle)); @@ -88,6 +91,7 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { assertEquals(backendId, runtime.getSpaceBackendId(spaceRef)); assertEquals(bodyHandle, runtime.getBodyHandle(bodyRef)); assertEquals(spaceHandle, runtime.getBodySpaceHandle(bodyRef)); + assertSame(backendRuntime, runtime.runtimeForBodyRef(bodyRef)); assertEquals(bodyUuid, runtime.getBodySnapshotMetadata(bodyHandle.value()).bodyUuid()); List handles = new ArrayList<>(); @@ -98,6 +102,7 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { assertNull(runtime.getBodyHandle(bodyRef)); assertNull(runtime.getBodySpaceHandle(bodyRef)); + assertNull(runtime.runtimeForBodyRef(bodyRef)); assertNull(runtime.getBodySnapshotMetadata(bodyHandle.value())); assertNull(runtime.getBodyHitMetadata(bodyHandle)); handles.clear(); @@ -117,6 +122,8 @@ void runtimeIndexesExposeRefsForTopologyCleanup() { UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000008"); UUID jointUuid = UUID.fromString("00000000-0000-0000-0000-000000000009"); BackendId backendId = new BackendId("test:runtime-ref-index"); + PhysicsBackendRuntime backendRuntime = + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); BackendSpaceHandle spaceHandle = new BackendSpaceHandle(43); BackendBodyHandle bodyHandle = new BackendBodyHandle(44L); BackendJointHandle jointHandle = new BackendJointHandle(45L); @@ -126,17 +133,28 @@ void runtimeIndexesExposeRefsForTopologyCleanup() { Ref jointRef = new TestRef(5); Ref reboundJointRef = new TestRef(6); + runtime.putRuntime(backendId, backendRuntime); runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); - runtime.putJointHandle(jointRef, jointUuid, spaceHandle, jointHandle); + runtime.putJointHandle(jointRef, jointUuid, backendId, spaceHandle, jointHandle); assertEquals(List.of(bodyRef), runtime.bodyRefsForSpaceHandle(spaceHandle)); assertEquals(List.of(jointRef), runtime.jointRefsForSpaceHandle(spaceHandle)); + assertSame(backendRuntime, runtime.runtimeForSpaceRef(spaceRef)); + assertSame(backendRuntime, runtime.runtimeForBodyRef(bodyRef)); + assertSame(backendRuntime, runtime.runtimeForJointRef(jointRef)); + assertNull(runtime.runtimeForJointRef(new TestRef(99))); - runtime.putJointHandle(reboundJointRef, jointUuid, spaceHandle, reboundJointHandle); + runtime.putJointHandle(reboundJointRef, + jointUuid, + backendId, + spaceHandle, + reboundJointHandle); assertNull(runtime.getJointHandle(jointRef)); + assertNull(runtime.runtimeForJointRef(jointRef)); assertEquals(reboundJointHandle, runtime.getJointHandle(reboundJointRef)); + assertSame(backendRuntime, runtime.runtimeForJointRef(reboundJointRef)); assertEquals(List.of(reboundJointRef), runtime.jointRefsForSpaceHandle(spaceHandle)); runtime.removeBodyHandle(bodyUuid, bodyRef); From 72dee04d457161a6f09d81f2f9e35527b98de9c8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:24:23 +0200 Subject: [PATCH 444/534] refactor(core): centralize physicsstore row cleanup Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreRowCleanup.java | 121 ++++++++++++++++++ .../PhysicsStoreTopologyMutations.java | 65 +++------- .../ChunkCollisionMutationDrainSystem.java | 45 ++----- .../systems/StaleBodyRemovalSystem.java | 57 +++------ 4 files changed, 162 insertions(+), 126 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java new file mode 100644 index 00000000..af49e96a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java @@ -0,0 +1,121 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Shared cleanup for PhysicsStore-owned body and joint rows. + */ +public final class PhysicsStoreRowCleanup { + + private PhysicsStoreRowCleanup() { + } + + public static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID jointUuid, + @Nonnull Ref jointRef) { + BackendJointHandle jointHandle = runtime.getJointHandle(jointRef); + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointRef); + if (jointHandle == null) { + runtime.removeJointHandle(jointUuid, jointRef); + return false; + } + PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(jointRef); + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); + } + identity.removeJointHandle(jointHandle); + runtime.removeJointHandle(jointUuid, jointRef); + return true; + } + + public static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef) { + return removeRuntimeBody(runtime, identity, bodyUuid, bodyRef, null); + } + + public static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nullable PhysicsBackendRuntime fallbackRuntime) { + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyRef); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyRef); + if (bodyHandle == null) { + runtime.removeBodyHandle(bodyUuid, bodyRef); + return false; + } + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(bodyRef); + if (backendRuntime == null) { + backendRuntime = fallbackRuntime; + } + if (spaceHandle != null && backendRuntime != null) { + backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); + } + identity.removeBodyHandle(bodyHandle); + runtime.removeBodyHandle(bodyUuid, bodyRef); + return true; + } + + public static void clearBodyCopiedState(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef) { + PhysicsControlRuntimeStates.clearControlled(bodyRef); + store.getResource(PhysicsSnapshotResource.getResourceType()).removeBody(bodyUuid); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()).removeBody(bodyUuid); + } + + public static void removeBodyEntity(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nullable String payloadResourceKey) { + clearBodyCopiedState(store, bodyUuid, bodyRef); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).removeUuid(bodyUuid, + bodyRef); + removePayload(store, payloadResourceKey); + removeEntityIfValid(store, bodyRef); + } + + public static void removeJointEntity(@Nonnull Store store, + @Nonnull UUID jointUuid, + @Nonnull Ref jointRef) { + store.getResource(PhysicsIdentityIndexResource.getResourceType()).removeUuid(jointUuid, + jointRef); + removeEntityIfValid(store, jointRef); + } + + private static void removePayload(@Nonnull Store store, + @Nullable String payloadResourceKey) { + if (payloadResourceKey == null || payloadResourceKey.isBlank()) { + return; + } + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .remove(payloadResourceKey); + } + + private static void removeEntityIfValid(@Nonnull Store store, + @Nonnull Ref ref) { + if (!ref.isValid()) { + return; + } + store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index f40528fe..02cb52fc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -2,10 +2,8 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; @@ -14,9 +12,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; @@ -129,37 +124,19 @@ private static void removeRuntimeRows(@Nonnull PhysicsRuntimeResource runtime, private static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull RowRemoval removal) { - BackendJointHandle jointHandle = runtime.getJointHandle(removal.ref()); - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(removal.ref()); - if (jointHandle == null) { - runtime.removeJointHandle(removal.rowUuid(), removal.ref()); - return false; - } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(removal.ref()); - if (spaceHandle != null && backendRuntime != null) { - backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); - } - identity.removeJointHandle(jointHandle); - runtime.removeJointHandle(removal.rowUuid(), removal.ref()); - return true; + return PhysicsStoreRowCleanup.removeRuntimeJoint(runtime, + identity, + removal.rowUuid(), + removal.ref()); } private static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull RowRemoval removal) { - BackendBodyHandle bodyHandle = runtime.getBodyHandle(removal.ref()); - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(removal.ref()); - if (bodyHandle == null) { - runtime.removeBodyHandle(removal.rowUuid(), removal.ref()); - return false; - } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(removal.ref()); - if (spaceHandle != null && backendRuntime != null) { - backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); - } - identity.removeBodyHandle(bodyHandle); - runtime.removeBodyHandle(removal.rowUuid(), removal.ref()); - return true; + return PhysicsStoreRowCleanup.removeRuntimeBody(runtime, + identity, + removal.rowUuid(), + removal.ref()); } @Nonnull @@ -295,30 +272,20 @@ private static boolean sameRef(@Nonnull Ref first, private static void removeRows(@Nonnull Store store, @Nonnull List removals) { - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); - PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = - store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()); - PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); for (RowRemoval removal : removals) { if (!removal.ref().isValid()) { continue; } - identity.removeUuid(removal.rowUuid(), removal.ref()); - if (removal.payloadResourceKey() != null - && !removal.payloadResourceKey().isBlank()) { - chunkCollisionPayloads.remove(removal.payloadResourceKey()); - } if (removal.kind() == RowKind.BODY) { - PhysicsControlRuntimeStates.clearControlled(removal.ref()); - snapshots.removeBody(removal.rowUuid()); - registrations.removeBody(removal.rowUuid()); + PhysicsStoreRowCleanup.removeBodyEntity(store, + removal.rowUuid(), + removal.ref(), + removal.payloadResourceKey()); + } else { + PhysicsStoreRowCleanup.removeJointEntity(store, + removal.rowUuid(), + removal.ref()); } - store.removeEntity(removal.ref(), - store.getRegistry().newHolder(), - RemoveReason.REMOVE); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 64ca8d91..f2b8bcf0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -2,7 +2,6 @@ import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -14,20 +13,17 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -107,7 +103,7 @@ private static void applyRemovals(@Nonnull Store store, @Nonnull List mutations) { for (ChunkCollisionMutation mutation : mutations) { if (mutation.remove()) { - removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); + removeGeneratedRows(store, runtime, identity, mutation); removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); } } @@ -154,7 +150,7 @@ private static void applyUpsert(@Nonnull Store store, && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); MaterialComponent material = material(store, spaceRef); CollisionFilterComponent filter = filter(store, spaceRef); - removeGeneratedRows(store, runtime, identity, chunkCollisionPayloads, mutation); + removeGeneratedRows(store, runtime, identity, mutation); removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); if (nativeVoxel) { chunkCollisionPayloads.put(mutation.payloadResourceKey(), voxelPayload(payload)); @@ -328,24 +324,14 @@ private static CollisionFilterComponent filter(@Nonnull Store stor private static void removeGeneratedRows(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull ChunkCollisionMutation mutation) { List rows = collectGeneratedRows(store, mutation); - PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = store.getResource( - PhysicsBodyRegistrationResource.getResourceType()); for (GeneratedRow row : rows) { - removeRuntimeBody(runtime, identity, row); - PhysicsControlRuntimeStates.clearControlled(row.ref()); - snapshots.removeBody(row.uuid()); - registrations.removeBody(row.uuid()); - removePayload(chunkCollisionPayloads, row.payloadResourceKey()); - identity.removeUuid(row.uuid(), row.ref()); - if (row.ref().isValid()) { - store.removeEntity(row.ref(), - store.getRegistry().newHolder(), - RemoveReason.REMOVE); - } + PhysicsStoreRowCleanup.removeRuntimeBody(runtime, identity, row.uuid(), row.ref()); + PhysicsStoreRowCleanup.removeBodyEntity(store, + row.uuid(), + row.ref(), + row.payloadResourceKey()); } } @@ -379,21 +365,6 @@ private static boolean matchesSource(@Nonnull ChunkCollisionMutation mutation, && mutation.sourceKey().equals(source.getSourceKey()); } - private static void removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull GeneratedRow row) { - BackendBodyHandle bodyHandle = runtime.getBodyHandle(row.ref()); - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(row.ref()); - if (bodyHandle != null && spaceHandle != null) { - PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(row.ref()); - if (backendRuntime != null) { - backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); - } - identity.removeBodyHandle(bodyHandle); - } - runtime.removeBodyHandle(row.uuid(), row.ref()); - } - private static void removePayload(@Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nullable String payloadResourceKey) { if (payloadResourceKey != null && !payloadResourceKey.isBlank()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index d54ef02e..174c9257 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core.internal.systems; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -9,15 +8,11 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; @@ -62,7 +57,6 @@ private static void removeStaleBodies(@Nonnull Store store, runtime, restore, staleBodies, - spaceHandle, backendRuntime, bodyId))); if (restore.isFailed()) { @@ -73,22 +67,19 @@ private static void removeStaleBodies(@Nonnull Store store, if (!removeDependentJoints(store, runtime, identity, restore, staleBodyUuids)) { return; } - PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); for (BoundBody body : staleBodies) { try { - body.backendRuntime().removeBody(body.spaceHandle().value(), body.bodyHandle().value()); + PhysicsStoreRowCleanup.removeRuntimeBody(runtime, + identity, + body.bodyUuid(), + body.bodyRef(), + body.backendRuntime()); } catch (RuntimeException exception) { restore.markFailed("PhysicsStore body " + body.bodyUuid() + " failed backend removal: " + exception.getMessage()); return; } - identity.removeBodyHandle(body.bodyHandle()); - identity.removeUuid(body.bodyUuid(), body.bodyRef()); - snapshots.removeBody(body.bodyUuid()); - registrations.removeBody(body.bodyUuid()); - runtime.removeBodyHandle(body.bodyUuid(), body.bodyRef()); + PhysicsStoreRowCleanup.removeBodyEntity(store, body.bodyUuid(), body.bodyRef(), null); } } @@ -101,27 +92,18 @@ private static boolean removeDependentJoints(@Nonnull Store store, return true; } for (BoundJoint joint : collectDependentJoints(store, identity, staleBodyUuids)) { - BackendJointHandle jointHandle = runtime.getJointHandle(joint.ref()); - if (jointHandle != null) { - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(joint.ref()); - PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(joint.ref()); - if (spaceHandle != null && backendRuntime != null) { - try { - backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); - } catch (RuntimeException exception) { - restore.markFailed("PhysicsStore joint " + joint.jointUuid() - + " failed backend removal: " + exception.getMessage()); - return false; - } - } - identity.removeJointHandle(jointHandle); + try { + PhysicsStoreRowCleanup.removeRuntimeJoint(runtime, + identity, + joint.jointUuid(), + joint.ref()); + } catch (RuntimeException exception) { + restore.markFailed("PhysicsStore joint " + joint.jointUuid() + + " failed backend removal: " + exception.getMessage()); + return false; } - runtime.removeJointHandle(joint.jointUuid(), joint.ref()); if (joint.removeRow() && joint.ref().isValid()) { - identity.removeUuid(joint.jointUuid(), joint.ref()); - store.removeEntity(joint.ref(), - store.getRegistry().newHolder(), - RemoveReason.REMOVE); + PhysicsStoreRowCleanup.removeJointEntity(store, joint.jointUuid(), joint.ref()); } } return true; @@ -170,7 +152,6 @@ private static void collectStaleBody(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List staleBodies, - @Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime, long bodyId) { BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); @@ -187,8 +168,6 @@ private static void collectStaleBody(@Nonnull Store store, } staleBodies.add(new BoundBody(metadata.bodyUuid(), metadata.bodyRef(), - spaceHandle, - new BackendBodyHandle(bodyId), backendRuntime)); } @@ -200,8 +179,6 @@ public Set> getDependencies() { private record BoundBody(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRef, - @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } From 16289d759a196046a9a040f0ff23af3480e6b438 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:36:03 +0200 Subject: [PATCH 445/534] fix(core): refresh physicsstore row refs after cleanup Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreRowCleanup.java | 19 ++++ .../PhysicsStoreTopologyMutations.java | 10 +- .../resources/PhysicsRuntimeResource.java | 91 +++++++++++++++++++ .../ChunkCollisionMutationDrainSystem.java | 7 ++ .../systems/StaleBodyRemovalSystem.java | 46 +++++++++- 5 files changed, 168 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java index af49e96a..22c86326 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java @@ -14,6 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -102,6 +103,24 @@ public static void removeJointEntity(@Nonnull Store store, removeEntityIfValid(store, jointRef); } + public static void refreshIdentityAndRuntimeRefs(@Nonnull Store store) { + PhysicsIdentityIndexResource identity = + store.getResource(PhysicsIdentityIndexResource.getResourceType()); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + identity.clearUuidRefs(); + store.getExternalData().clearUuidIndex(); + store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { + UuidComponent uuid = chunk.getComponent(index, UuidComponent.getComponentType()); + if (uuid == null) { + return; + } + Ref ref = chunk.getReferenceTo(index); + identity.putUuid(uuid.getUuid(), ref); + store.getExternalData().putRefForUUID(uuid.getUuid(), ref); + }); + runtime.refreshRowRefs(identity); + } + private static void removePayload(@Nonnull Store store, @Nullable String payloadResourceKey) { if (payloadResourceKey == null || payloadResourceKey.isBlank()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 02cb52fc..2d33c6b6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -272,7 +272,11 @@ private static boolean sameRef(@Nonnull Ref first, private static void removeRows(@Nonnull Store store, @Nonnull List removals) { - for (RowRemoval removal : removals) { + boolean removedAny = false; + for (RowRemoval removal : removals.stream() + .sorted((first, second) -> Integer.compare(second.ref().getIndex(), + first.ref().getIndex())) + .toList()) { if (!removal.ref().isValid()) { continue; } @@ -286,6 +290,10 @@ private static void removeRows(@Nonnull Store store, removal.rowUuid(), removal.ref()); } + removedAny = true; + } + if (removedAny) { + PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index 2d2adeb7..56b60774 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -87,6 +87,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Map> jointRefsByUuid = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map jointBackendIdsByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap jointHandlesByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -367,6 +370,7 @@ private void putJointHandle(@Nonnull UUID jointUuid, jointHandlesByUuid.put(jointUuid, handle); jointSpaceHandlesByUuid.put(jointUuid, spaceHandle); jointRefsByUuid.put(jointUuid, checkedJointRef); + jointBackendIdsByUuid.put(jointUuid, backendId); jointHandlesByRowIndex.put(rowIndex, handle); jointSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); jointRefsByRowIndex.put(rowIndex, checkedJointRef); @@ -394,6 +398,7 @@ public BackendSpaceHandle getJointSpaceHandle(@Nonnull Ref jointRe public void removeJointHandle(@Nonnull UUID jointUuid) { jointHandlesByUuid.remove(jointUuid); jointSpaceHandlesByUuid.remove(jointUuid); + jointBackendIdsByUuid.remove(jointUuid); Ref jointRef = jointRefsByUuid.remove(jointUuid); if (jointRef != null) { int rowIndex = jointRef.getIndex(); @@ -489,6 +494,7 @@ public void clear() { jointHandlesByUuid.clear(); jointSpaceHandlesByUuid.clear(); jointRefsByUuid.clear(); + jointBackendIdsByUuid.clear(); jointHandlesByRowIndex.clear(); jointSpaceHandlesByRowIndex.clear(); jointRefsByRowIndex.clear(); @@ -507,6 +513,90 @@ public void clearTransientBodyOperations() { pendingBodyOperations.clear(); } + public void refreshRowRefs(@Nonnull PhysicsIdentityIndexResource identity) { + PhysicsIdentityIndexResource checkedIdentity = Objects.requireNonNull(identity, "identity"); + refreshSpaceRefs(checkedIdentity); + refreshBodyRefs(checkedIdentity); + refreshJointRefs(checkedIdentity); + } + + private void refreshSpaceRefs(@Nonnull PhysicsIdentityIndexResource identity) { + spaceUuidsByRowIndex.clear(); + spaceHandlesByRowIndex.clear(); + backendIdsBySpaceRowIndex.clear(); + for (UUID spaceUuid : new ArrayList<>(spaceHandlesByUuid.keySet())) { + Ref spaceRef = identity.getByUuid(spaceUuid); + if (spaceRef == null) { + continue; + } + spaceRefsByUuid.put(spaceUuid, spaceRef); + BackendSpaceHandle spaceHandle = spaceHandlesByUuid.get(spaceUuid); + BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); + int rowIndex = spaceRef.getIndex(); + spaceUuidsByRowIndex.put(rowIndex, spaceUuid); + if (spaceHandle != null) { + spaceHandlesByRowIndex.put(rowIndex, spaceHandle); + } + if (backendId != null) { + backendIdsBySpaceRowIndex.put(rowIndex, backendId); + } + } + } + + private void refreshBodyRefs(@Nonnull PhysicsIdentityIndexResource identity) { + bodyRefsByRowIndex.clear(); + bodyHandlesByRowIndex.clear(); + bodySpaceHandlesByRowIndex.clear(); + backendIdsByBodyRowIndex.clear(); + bodySnapshotMetadataByHandle.replaceAll((bodyHandle, metadata) -> { + Ref bodyRef = identity.getByUuid(metadata.bodyUuid()); + if (bodyRef == null) { + return metadata; + } + BackendBodyHandle handle = bodyHandlesByUuid.get(metadata.bodyUuid()); + BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.get(metadata.bodyUuid()); + int rowIndex = bodyRef.getIndex(); + bodyRefsByRowIndex.put(rowIndex, bodyRef); + if (handle != null) { + bodyHandlesByRowIndex.put(rowIndex, handle); + } + if (spaceHandle != null) { + bodySpaceHandlesByRowIndex.put(rowIndex, spaceHandle); + } + BackendId backendId = backendIdsBySpaceUuid.get(metadata.spaceUuid()); + if (backendId != null) { + backendIdsByBodyRowIndex.put(rowIndex, backendId); + } + return new BodySnapshotMetadata(metadata.bodyUuid(), bodyRef, metadata.spaceUuid()); + }); + } + + private void refreshJointRefs(@Nonnull PhysicsIdentityIndexResource identity) { + jointHandlesByRowIndex.clear(); + jointSpaceHandlesByRowIndex.clear(); + jointRefsByRowIndex.clear(); + backendIdsByJointRowIndex.clear(); + for (Map.Entry entry : jointHandlesByUuid.entrySet()) { + UUID jointUuid = entry.getKey(); + Ref jointRef = identity.getByUuid(jointUuid); + if (jointRef == null) { + continue; + } + jointRefsByUuid.put(jointUuid, jointRef); + BackendSpaceHandle spaceHandle = jointSpaceHandlesByUuid.get(jointUuid); + BackendId backendId = jointBackendIdsByUuid.get(jointUuid); + int rowIndex = jointRef.getIndex(); + jointHandlesByRowIndex.put(rowIndex, entry.getValue()); + if (spaceHandle != null) { + jointSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); + } + if (backendId != null) { + backendIdsByJointRowIndex.put(rowIndex, backendId); + } + jointRefsByRowIndex.put(rowIndex, jointRef); + } + } + private void removeBodyHandleIndexes(@Nullable BackendBodyHandle removed, @Nullable BackendSpaceHandle spaceHandle) { if (removed == null || spaceHandle == null) { @@ -704,6 +794,7 @@ public PhysicsRuntimeResource clone() { copy.jointHandlesByUuid.putAll(jointHandlesByUuid); copy.jointSpaceHandlesByUuid.putAll(jointSpaceHandlesByUuid); copy.jointRefsByUuid.putAll(jointRefsByUuid); + copy.jointBackendIdsByUuid.putAll(jointBackendIdsByUuid); copy.jointHandlesByRowIndex.putAll(jointHandlesByRowIndex); copy.jointSpaceHandlesByRowIndex.putAll(jointSpaceHandlesByRowIndex); copy.jointRefsByRowIndex.putAll(jointRefsByRowIndex); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index f2b8bcf0..83208fc8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -326,12 +326,19 @@ private static void removeGeneratedRows(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull ChunkCollisionMutation mutation) { List rows = collectGeneratedRows(store, mutation); + rows.sort((first, second) -> Integer.compare(second.ref().getIndex(), + first.ref().getIndex())); + boolean removedAny = false; for (GeneratedRow row : rows) { PhysicsStoreRowCleanup.removeRuntimeBody(runtime, identity, row.uuid(), row.ref()); PhysicsStoreRowCleanup.removeBodyEntity(store, row.uuid(), row.ref(), row.payloadResourceKey()); + removedAny = true; + } + if (removedAny) { + PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index 174c9257..c8f358d9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -54,6 +54,7 @@ private static void removeStaleBodies(@Nonnull Store store, runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> runtime.forEachBodyHandle(spaceHandle, bodyId -> collectStaleBody(store, + identity, runtime, restore, staleBodies, @@ -67,7 +68,9 @@ private static void removeStaleBodies(@Nonnull Store store, if (!removeDependentJoints(store, runtime, identity, restore, staleBodyUuids)) { return; } - for (BoundBody body : staleBodies) { + List orderedBodies = currentBodyRefs(identity, staleBodies); + boolean removedAny = false; + for (BoundBody body : orderedBodies) { try { PhysicsStoreRowCleanup.removeRuntimeBody(runtime, identity, @@ -80,6 +83,10 @@ private static void removeStaleBodies(@Nonnull Store store, return; } PhysicsStoreRowCleanup.removeBodyEntity(store, body.bodyUuid(), body.bodyRef(), null); + removedAny = true; + } + if (removedAny) { + PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); } } @@ -91,7 +98,11 @@ private static boolean removeDependentJoints(@Nonnull Store store, if (staleBodyUuids.isEmpty()) { return true; } - for (BoundJoint joint : collectDependentJoints(store, identity, staleBodyUuids)) { + List joints = collectDependentJoints(store, identity, staleBodyUuids); + joints.sort((first, second) -> Integer.compare(second.ref().getIndex(), + first.ref().getIndex())); + boolean removedAny = false; + for (BoundJoint joint : joints) { try { PhysicsStoreRowCleanup.removeRuntimeJoint(runtime, identity, @@ -104,11 +115,31 @@ private static boolean removeDependentJoints(@Nonnull Store store, } if (joint.removeRow() && joint.ref().isValid()) { PhysicsStoreRowCleanup.removeJointEntity(store, joint.jointUuid(), joint.ref()); + removedAny = true; } } + if (removedAny) { + PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); + } return true; } + @Nonnull + private static List currentBodyRefs(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull List staleBodies) { + List currentBodies = new ArrayList<>(staleBodies.size()); + for (BoundBody body : staleBodies) { + Ref bodyRef = PhysicsStoreSystemSupport.refForUuid(identity, + body.bodyUuid()); + currentBodies.add(new BoundBody(body.bodyUuid(), + bodyRef != null ? bodyRef : body.bodyRef(), + body.backendRuntime())); + } + currentBodies.sort((first, second) -> Integer.compare(second.bodyRef().getIndex(), + first.bodyRef().getIndex())); + return currentBodies; + } + @Nonnull private static List collectDependentJoints(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @@ -149,6 +180,7 @@ private static boolean endpointRemoved(@Nonnull PhysicsIdentityIndexResource ide } private static void collectStaleBody(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List staleBodies, @@ -160,14 +192,20 @@ private static void collectStaleBody(@Nonnull Store store, + " has no runtime snapshot metadata"); return; } + Ref bodyRef = PhysicsStoreSystemSupport.resolvedRef(identity, + metadata.bodyUuid(), + metadata.bodyRef()); + if (bodyRef == null) { + bodyRef = metadata.bodyRef(); + } BodyComponent body = PhysicsStoreSystemSupport.component(store, - metadata.bodyRef(), + bodyRef, BodyComponent.getComponentType()); if (body != null) { return; } staleBodies.add(new BoundBody(metadata.bodyUuid(), - metadata.bodyRef(), + bodyRef, backendRuntime)); } From f03e978969b0fd369b4d34f64f140d8b09d0680d Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:36:25 +0200 Subject: [PATCH 446/534] test(core): cover physicsstore topology cleanup Signed-off-by: Blovien --- .../PhysicsStoreTopologyMutationsTest.java | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java new file mode 100644 index 00000000..4887a6e7 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java @@ -0,0 +1,366 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendJointType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsStoreTopologyMutationsTest { + + @Test + void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("topology-destroy-body-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(1); + UUID bodyAUuid = uuid(2); + UUID bodyBUuid = uuid(3); + UUID jointUuid = uuid(4); + BackendId backendId = new BackendId("test:topology-cleanup"); + BoundSpace space = addBoundSpace(store, spaceUuid, backendId); + Ref bodyARef = addBody(store, spaceUuid, space.ref(), bodyAUuid); + Ref bodyBRef = addBody(store, spaceUuid, space.ref(), bodyBUuid); + Ref jointRef = addJoint(store, + spaceUuid, + space.ref(), + bodyAUuid, + bodyARef, + bodyBUuid, + bodyBRef, + jointUuid); + BackendBodyHandle bodyAHandle = bindBody(store, + space, + bodyAUuid, + bodyARef, + 0.0f); + bindBody(store, space, bodyBUuid, bodyBRef, 2.0f); + bindJoint(store, space, jointUuid, jointRef, bodyAHandle, bodyBRef); + publishCopiedState(store, spaceUuid, bodyAUuid, bodyARef, bodyBUuid, bodyBRef); + + PhysicsStoreTopologyMutations.destroyBody(store, bodyAUuid); + + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + PhysicsSnapshotResource snapshots = + store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = store.getResource( + PhysicsBodyRegistrationResource.getResourceType()); + assertNull(identity.getByUuid(bodyAUuid)); + assertNull(identity.getByUuid(jointUuid)); + Ref remainingBodyRef = identity.getByUuid(bodyBUuid); + assertNotNull(remainingBodyRef); + assertNull(runtime.getBodyHandle(bodyARef)); + assertNull(runtime.getJointHandle(jointRef)); + assertNotNull(runtime.getBodyHandle(remainingBodyRef)); + assertEquals(1, space.runtime().bodyCount(space.handle().value())); + assertEquals(0, space.runtime().jointCount(space.handle().value())); + assertNull(snapshots.getBody(bodyAUuid)); + assertNotNull(snapshots.getBody(bodyBUuid)); + assertNull(registrations.getBodyRegistrationView(bodyAUuid)); + assertNotNull(registrations.getBodyRegistrationView(bodyBUuid)); + assertFalse(bodyARef.isValid()); + assertFalse(jointRef.isValid()); + assertNotNull(store.getComponent(remainingBodyRef, BodyComponent.getComponentType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static BoundSpace addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + PhysicsBackendRuntime backendRuntime = + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + BackendSpaceHandle spaceHandle = + new BackendSpaceHandle(backendRuntime.createSpace(new SpaceId(42))); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putRuntime(backendId, backendRuntime); + runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + return new BoundSpace(spaceRef, backendRuntime, spaceHandle, backendId); + } + + @Nonnull + private static Ref addBody(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull UUID bodyUuid) { + BodyComponent body = new BodyComponent(spaceUuid, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY); + body.setSpaceRef(spaceRef); + Ref bodyRef = store.addEntity(PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false), + null, + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.5f, 0.1f), + new CollisionFilterComponent(0x01, 0x02)), + AddReason.SPAWN); + assertNotNull(bodyRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(bodyUuid, bodyRef); + store.getExternalData().putRefForUUID(bodyUuid, bodyRef); + return bodyRef; + } + + @Nonnull + private static Ref addJoint(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull UUID bodyAUuid, + @Nonnull Ref bodyARef, + @Nonnull UUID bodyBUuid, + @Nonnull Ref bodyBRef, + @Nonnull UUID jointUuid) { + JointComponent joint = PhysicsJointEntities.joint(spaceRef, + bodyARef, + bodyBRef, + JointType.FIXED, + new Vector3f(), + new Vector3f(), + new Vector3f(0.0f, 1.0f, 0.0f)); + joint.setSpaceUuid(spaceUuid); + joint.setBodyAUuid(bodyAUuid); + joint.setBodyBUuid(bodyBUuid); + Ref jointRef = store.addEntity(PhysicsEntities.jointHolder(store, + jointUuid, + joint), + AddReason.SPAWN); + assertNotNull(jointRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(jointUuid, jointRef); + store.getExternalData().putRefForUUID(jointUuid, jointRef); + return jointRef; + } + + @Nonnull + private static BackendBodyHandle bindBody(@Nonnull Store store, + @Nonnull BoundSpace space, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + float positionX) { + long bodyId = space.runtime().createBody(space.handle().value(), + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.axisCode(PhysicsAxis.Y), + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + positionX, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + BackendBodyHandle handle = new BackendBodyHandle(bodyId); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putBodyHandle(bodyUuid, bodyRef, uuid(1), space.handle(), handle); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putBodyHandle(handle, bodyRef); + return handle; + } + + private static void bindJoint(@Nonnull Store store, + @Nonnull BoundSpace space, + @Nonnull UUID jointUuid, + @Nonnull Ref jointRef, + @Nonnull BackendBodyHandle bodyAHandle, + @Nonnull Ref bodyBRef) { + BackendBodyHandle bodyBHandle = store.getResource(PhysicsRuntimeResource.getResourceType()) + .getBodyHandle(bodyBRef); + assertNotNull(bodyBHandle); + long jointId = space.runtime().createJoint(space.handle().value(), + BackendRuntimeCodes.jointTypeCode(BackendJointType.FIXED), + bodyAHandle.value(), + bodyBHandle.value(), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f); + BackendJointHandle handle = new BackendJointHandle(jointId); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .putJointHandle(jointRef, jointUuid, space.backendId(), space.handle(), handle); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putJointHandle(handle, jointRef); + } + + private static void publishCopiedState(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull UUID bodyAUuid, + @Nonnull Ref bodyARef, + @Nonnull UUID bodyBUuid, + @Nonnull Ref bodyBRef) { + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(bodyARef, bodyAUuid, spaceUuid), + snapshot(bodyBRef, bodyBUuid, spaceUuid)))); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .publish(1L, + List.of(publication(bodyARef, bodyAUuid), + publication(bodyBRef, bodyBUuid))); + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + return PhysicsBodySnapshot.of(bodyRef, + bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + + @Nonnull + private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( + @Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid) { + return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, + new PhysicsBodyRegistrationView(bodyUuid, + new SpaceId(42), + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.RUNTIME_ONLY)); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private record BoundSpace(@Nonnull Ref ref, + @Nonnull PhysicsBackendRuntime runtime, + @Nonnull BackendSpaceHandle handle, + @Nonnull BackendId backendId) { + } +} From d72703adc9eea4d6b42d7a04bf95e0c19b75786e Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:44:27 +0200 Subject: [PATCH 447/534] refactor(core): delegate physicschunk store registration Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkStoreTypes.java | 54 +++++++++++++++++++ .../PhysicsStoreRegistration.java | 31 ++--------- .../resources/PhysicsResourceTypes.java | 12 ----- 3 files changed, 58 insertions(+), 39 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java new file mode 100644 index 00000000..6b4879d3 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java @@ -0,0 +1,54 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionComponentSyncSystem; +import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionMutationDrainSystem; +import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionVoxelStitchingSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; +import javax.annotation.Nonnull; + +/** + * PhysicsStore-side type registration owned by the PhysicsChunk module. + */ +public final class PhysicsChunkStoreTypes { + + private PhysicsChunkStoreTypes() { + } + + public static void registerPhysicsStoreResourceTypes( + @Nonnull ComponentRegistryProxy registry) { + PhysicsChunkCollisionMutationQueueResource.setResourceType(registry.registerResource( + PhysicsChunkCollisionMutationQueueResource.class, + PhysicsChunkCollisionMutationQueueResource::new)); + PhysicsChunkCollisionPayloadResource.setResourceType(registry.registerResource( + PhysicsChunkCollisionPayloadResource.class, + PhysicsChunkCollisionPayloadResource::new)); + PhysicsChunkSettingsIndexResource.setResourceType(registry.registerResource( + PhysicsChunkSettingsIndexResource.class, + PhysicsChunkSettingsIndexResource::new)); + PhysicsChunkComponentSyncResource.setResourceType(registry.registerResource( + PhysicsChunkComponentSyncResource.class, + PhysicsChunkComponentSyncResource::new)); + } + + public static void registerPhysicsStoreSystems( + @Nonnull ComponentRegistryProxy registry) { + registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); + registry.registerSystem(new ChunkCollisionMutationDrainSystem()); + registry.registerSystem(new ChunkCollisionComponentSyncSystem()); + registry.registerSystem(new ChunkCollisionVoxelStitchingSystem()); + } + + public static void clearPhysicsStoreRuntimeResources(@Nonnull Store store) { + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()).clear(); + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); + store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()).clear(); + store.getResource(PhysicsChunkComponentSyncResource.getResourceType()).clear(); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 532b4336..afad5947 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -8,6 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; @@ -20,14 +21,9 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.TickDecision; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionComponentSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.ColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.CompletedStepPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; @@ -40,9 +36,6 @@ import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionMutationDrainSystem; -import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionVoxelStitchingSystem; -import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; @@ -72,19 +65,17 @@ public static void register(@Nonnull ComponentRegistryProxy regist PhysicsStoreHooks.registerTickGate(STEP_TICK_GATE); PhysicsResourceTypes.registerResourceTypes(registry); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(registry); registry.registerSystem(new PersistenceHydrationSystem()); registry.registerSystem(new IdentityIndexSystem()); - registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); + PhysicsChunkStoreTypes.registerPhysicsStoreSystems(registry); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); - registry.registerSystem(new ChunkCollisionMutationDrainSystem()); registry.registerSystem(new BodyBindingSystem()); - registry.registerSystem(new ChunkCollisionComponentSyncSystem()); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); - registry.registerSystem(new ChunkCollisionVoxelStitchingSystem()); registry.registerSystem(new BodyCommandApplicationSystem()); registry.registerSystem(new TargetBindingSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); @@ -110,21 +101,7 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic PhysicsRuntimeResource.getResourceType(), PhysicsRuntimeResource::destroyBackendBindings)); failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsChunkCollisionMutationQueueResource.getResourceType(), - PhysicsChunkCollisionMutationQueueResource::clear)); - failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsChunkCollisionPayloadResource.getResourceType(), - PhysicsChunkCollisionPayloadResource::clear)); - failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsChunkSettingsIndexResource.getResourceType(), - PhysicsChunkSettingsIndexResource::clear)); - failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsChunkComponentSyncResource.getResourceType(), - PhysicsChunkComponentSyncResource::clear)); + () -> PhysicsChunkStoreTypes.clearPhysicsStoreRuntimeResources(store)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsIdentityIndexResource.getResourceType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index f767f610..59a0350a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -87,18 +87,6 @@ public static void registerResourceTypes( debugResourceType = registry.registerResource( dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource.class, dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource::new); - PhysicsChunkCollisionMutationQueueResource.setResourceType(registry.registerResource( - PhysicsChunkCollisionMutationQueueResource.class, - PhysicsChunkCollisionMutationQueueResource::new)); - PhysicsChunkCollisionPayloadResource.setResourceType(registry.registerResource( - PhysicsChunkCollisionPayloadResource.class, - PhysicsChunkCollisionPayloadResource::new)); - PhysicsChunkSettingsIndexResource.setResourceType(registry.registerResource( - PhysicsChunkSettingsIndexResource.class, - PhysicsChunkSettingsIndexResource::new)); - PhysicsChunkComponentSyncResource.setResourceType(registry.registerResource( - PhysicsChunkComponentSyncResource.class, - PhysicsChunkComponentSyncResource::new)); } @Nonnull From 266a69e3f4c57261c718a6352ea5a6b8aa4abf1b Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:50:11 +0200 Subject: [PATCH 448/534] fix(core): skip stale physicschunk collision upserts Signed-off-by: Blovien --- .../physicschunk/ChunkCollisionMutation.java | 29 +++++++++++++++++-- .../PhysicsChunkCollisionProducerSystem.java | 2 ++ ...csChunkCollisionMutationQueueResource.java | 13 ++++++++- .../PhysicsChunkSettingsIndexResource.java | 15 ++++++++++ .../ChunkCollisionMutationDrainSystem.java | 25 ++++++++++++++-- .../PhysicsChunkSettingsIndexSystem.java | 9 ++++-- .../physicschunk/PhysicsChunkCollision.java | 22 ++++++++++---- 7 files changed, 100 insertions(+), 15 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java index 068ba86b..2e79e1f9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ChunkCollisionMutation.java @@ -16,7 +16,11 @@ public record ChunkCollisionMutation(@Nonnull UUID spaceUuid, int chunkZ, @Nonnull String payloadResourceKey, @Nullable ChunkCollisionPayload payload, - boolean remove) { + boolean remove, + long lifecycleGeneration, + long settingsGeneration) { + + private static final long UNSTAMPED_GENERATION = 0L; public ChunkCollisionMutation { Objects.requireNonNull(spaceUuid, "spaceUuid"); @@ -44,7 +48,9 @@ public static ChunkCollisionMutation upsert(@Nonnull UUID spaceUuid, chunkZ, payloadResourceKey, payload, - false); + false, + UNSTAMPED_GENERATION, + UNSTAMPED_GENERATION); } @Nonnull @@ -60,7 +66,24 @@ public static ChunkCollisionMutation remove(@Nonnull UUID spaceUuid, chunkZ, "", null, - true); + true, + UNSTAMPED_GENERATION, + UNSTAMPED_GENERATION); + } + + @Nonnull + public ChunkCollisionMutation stamped(long lifecycleGeneration, + long settingsGeneration) { + return new ChunkCollisionMutation(spaceUuid, + sourceKey, + chunkX, + sectionY, + chunkZ, + payloadResourceKey, + payload, + remove, + lifecycleGeneration, + settingsGeneration); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java index 016637ae..c53d2282 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java @@ -84,6 +84,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { PhysicsChunkCollisionMutationQueueResource.getResourceType()); PhysicsChunkSettingsIndexResource chunkCollisionSettingsIndex = physics.getResource( PhysicsChunkSettingsIndexResource.getResourceType()); + queue.updateStamp(PhysicsChunkLifecycle.generation(), + chunkCollisionSettingsIndex.generation()); PhysicsSnapshotResource snapshotResource = physics.getResource( PhysicsSnapshotResource.getResourceType()); PhysicsChunkCollisionStreamingResource streaming = store.getResource( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java index d4ecb4bf..a77d3a5b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -22,12 +23,20 @@ public final class PhysicsChunkCollisionMutationQueueResource implements Resourc private static ResourceType resourceType; @Nonnull private final Queue mutations = new ArrayDeque<>(); + private long lifecycleGeneration = PhysicsChunkLifecycle.generation(); + private long settingsGeneration = PhysicsChunkSettingsIndexResource.INITIAL_GENERATION; public PhysicsChunkCollisionMutationQueueResource() { } public synchronized void enqueue(@Nonnull ChunkCollisionMutation mutation) { - mutations.add(Objects.requireNonNull(mutation, "mutation")); + mutations.add(Objects.requireNonNull(mutation, "mutation") + .stamped(lifecycleGeneration, settingsGeneration)); + } + + public synchronized void updateStamp(long lifecycleGeneration, long settingsGeneration) { + this.lifecycleGeneration = lifecycleGeneration; + this.settingsGeneration = settingsGeneration; } @Nonnull @@ -60,6 +69,8 @@ public synchronized void clear() { public synchronized PhysicsChunkCollisionMutationQueueResource clone() { PhysicsChunkCollisionMutationQueueResource copy = new PhysicsChunkCollisionMutationQueueResource(); copy.mutations.addAll(mutations); + copy.lifecycleGeneration = lifecycleGeneration; + copy.settingsGeneration = settingsGeneration; return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index db6d26f5..b8f0aac4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -19,18 +19,25 @@ */ public final class PhysicsChunkSettingsIndexResource implements Resource { + public static final long INITIAL_GENERATION = 1L; + @Nullable private static ResourceType resourceType; @Nonnull private final Map settingsBySpaceUuid = new Object2ObjectOpenHashMap<>(); + private long generation = INITIAL_GENERATION; public PhysicsChunkSettingsIndexResource() { } public synchronized void replaceAll(@Nonnull Map settings) { + if (settingsBySpaceUuid.equals(settings)) { + return; + } settingsBySpaceUuid.clear(); settingsBySpaceUuid.putAll(settings); + generation++; } @Nonnull @@ -45,7 +52,14 @@ public synchronized PhysicsChunkSpaceSettings settings(@Nonnull UUID spaceUuid) return settingsBySpaceUuid.get(spaceUuid); } + public synchronized long generation() { + return generation; + } + public synchronized void clear() { + if (!settingsBySpaceUuid.isEmpty()) { + generation++; + } settingsBySpaceUuid.clear(); } @@ -54,6 +68,7 @@ public synchronized void clear() { public synchronized PhysicsChunkSettingsIndexResource clone() { PhysicsChunkSettingsIndexResource copy = new PhysicsChunkSettingsIndexResource(); copy.settingsBySpaceUuid.putAll(settingsBySpaceUuid); + copy.generation = generation; return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 83208fc8..403a59e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -19,9 +19,11 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; @@ -58,7 +60,8 @@ public final class ChunkCollisionMutationDrainSystem extends TickingSystem> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), - new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class) + new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class), + new SystemDependency<>(Order.AFTER, PhysicsChunkSettingsIndexSystem.class) ); @Override @@ -79,9 +82,17 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsIdentityIndexResource.getResourceType()); PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = store.getResource( PhysicsChunkCollisionPayloadResource.getResourceType()); + PhysicsChunkSettingsIndexResource settingsIndex = store.getResource( + PhysicsChunkSettingsIndexResource.getResourceType()); applyRemovals(store, runtime, identity, chunkCollisionPayloads, mutations); - applyUpserts(store, runtime, identity, chunkCollisionPayloads, restore, mutations); + applyUpserts(store, + runtime, + identity, + chunkCollisionPayloads, + settingsIndex, + restore, + mutations); } @Nonnull @@ -113,15 +124,23 @@ private static void applyUpserts(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + @Nonnull PhysicsChunkSettingsIndexResource settingsIndex, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List mutations) { for (ChunkCollisionMutation mutation : mutations) { - if (!mutation.remove()) { + if (!mutation.remove() && isFreshUpsert(settingsIndex, mutation)) { applyUpsert(store, runtime, identity, chunkCollisionPayloads, restore, mutation); } } } + private static boolean isFreshUpsert(@Nonnull PhysicsChunkSettingsIndexResource settingsIndex, + @Nonnull ChunkCollisionMutation mutation) { + return mutation.lifecycleGeneration() == PhysicsChunkLifecycle.generation() + && mutation.settingsGeneration() == settingsIndex.generation() + && settingsIndex.settings(mutation.spaceUuid()) != null; + } + private static void applyUpsert(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index f68c518f..77f36c24 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -10,6 +10,8 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; @@ -38,8 +40,11 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) BiConsumer, CommandBuffer> collector = (chunk, _) -> collectChunk(settingsBySpaceUuid, chunk); store.forEachChunk(systemIndex, collector); - store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()) - .replaceAll(settingsBySpaceUuid); + PhysicsChunkSettingsIndexResource settingsIndex = + store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()); + settingsIndex.replaceAll(settingsBySpaceUuid); + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()) + .updateStamp(PhysicsChunkLifecycle.generation(), settingsIndex.generation()); } private static void collectChunk( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index c15ed124..14ebfa2f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -9,6 +9,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; @@ -71,8 +72,7 @@ private static PhysicsChunkCollisionBuildStats rebuildAroundChecked(@Nonnull Wor @Nonnull Vector3d center, int radius) { PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceRef); - PhysicsChunkCollisionMutationQueueResource queue = checkedStore.getResource( - PhysicsChunkCollisionMutationQueueResource.getResourceType()); + PhysicsChunkCollisionMutationQueueResource queue = stampedQueue(checkedStore); int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); PhysicsChunkCollisionPrewarmStats stats = streaming(world).ensureAround(world, settings.spaceUuid(), @@ -125,7 +125,7 @@ private static PhysicsChunkCollisionBuildStats refreshAroundChecked(@Nonnull Wor PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceRef); return streaming(world).refreshAround(world, settings.spaceUuid(), - checkedStore.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()), + stampedQueue(checkedStore), Objects.requireNonNull(center, "center"), radius, Math.max(0L, world.getTick()), @@ -176,7 +176,7 @@ private static PhysicsChunkCollisionPrewarmStats ensureAroundChecked(@Nonnull Wo PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceRef); return streaming(world).ensureAround(world, settings.spaceUuid(), - checkedStore.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()), + stampedQueue(checkedStore), Objects.requireNonNull(centers, "centers"), radius, tick, @@ -313,14 +313,24 @@ private static int clearSpaceRows(@Nonnull World world, @Nonnull UUID spaceUuid) { int removed = 0; if (isSubPluginEnabled()) { - removed = streaming(world).clearSpace(spaceUuid, - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType())); + removed = streaming(world).clearSpace(spaceUuid, stampedQueue(store)); } int directlyRemoved = PhysicsStoreTopologyMutations.clearTerrainForSpace(store, spaceUuid); return removed != 0 ? removed : directlyRemoved; } + @Nonnull + private static PhysicsChunkCollisionMutationQueueResource stampedQueue( + @Nonnull Store store) { + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + PhysicsChunkSettingsIndexResource settingsIndex = store.getResource( + PhysicsChunkSettingsIndexResource.getResourceType()); + queue.updateStamp(PhysicsChunkLifecycle.generation(), settingsIndex.generation()); + return queue; + } + @Nonnull private static PhysicsChunkCollisionBuildStats withRemovedBodies( @Nonnull PhysicsChunkCollisionBuildStats stats, From d75345a4dc8a284bbee3f0bd4344fcd77d7f66f4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:52:56 +0200 Subject: [PATCH 449/534] test(core): cover stale physicschunk mutations Signed-off-by: Blovien --- ...ChunkCollisionMutationDrainSystemTest.java | 190 ++++++++++++++++-- 1 file changed, 178 insertions(+), 12 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 1b7d4b6b..a328b84c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -26,12 +26,16 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; @@ -49,11 +53,14 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -69,8 +76,7 @@ void upsertCreatesGeneratedRowsWithReusableMaterialAndFilterComponents() { ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsComponentTypeRegistry.registerComponentTypes(proxy); - PhysicsResourceTypes.registerResourceTypes(proxy); + registerTypes(proxy); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-row-test")), EmptyResourceStorage.get()); @@ -135,8 +141,7 @@ void upsertCreatesNativeVoxelRowWhenBackendSupportsVoxelTerrain() { ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsComponentTypeRegistry.registerComponentTypes(proxy); - PhysicsResourceTypes.registerResourceTypes(proxy); + registerTypes(proxy); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-voxel-test")), EmptyResourceStorage.get()); @@ -188,8 +193,7 @@ void destroyingDetailRowDoesNotRemoveNativeVoxelPayloadForSiblingRow() { ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsComponentTypeRegistry.registerComponentTypes(proxy); - PhysicsResourceTypes.registerResourceTypes(proxy); + registerTypes(proxy); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-payload-lifetime-test")), EmptyResourceStorage.get()); @@ -256,8 +260,7 @@ void removeDeletesGeneratedRowsAndPayloadResource() { ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsComponentTypeRegistry.registerComponentTypes(proxy); - PhysicsResourceTypes.registerResourceTypes(proxy); + registerTypes(proxy); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-remove-test")), EmptyResourceStorage.get()); @@ -316,13 +319,131 @@ void removeDeletesGeneratedRowsAndPayloadResource() { } } + @Test + void staleLifecycleUpsertDoesNotCreateGeneratedRows() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + registerTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-stale-lifecycle-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(51); + addBoundSpace(store, spaceUuid, new BackendId("test:chunk-collision-stale-lifecycle")); + String sourceKey = "1:2:3"; + String payloadKey = "chunk-collision/1/2/3"; + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.updateStamp(previousGeneration(PhysicsChunkLifecycle.generation()), + settingsGeneration(store)); + + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 1, + 2, + 3, + payloadKey, + boxPayload(1.0, 2.0, 3.0))); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void staleSettingsUpsertDoesNotCreateGeneratedRows() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + registerTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-stale-settings-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(52); + addBoundSpace(store, spaceUuid, new BackendId("test:chunk-collision-stale-settings")); + String sourceKey = "2:3:4"; + String payloadKey = "chunk-collision/2/3/4"; + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.updateStamp(PhysicsChunkLifecycle.generation(), + previousGeneration(settingsGeneration(store))); + + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 2, + 3, + 4, + payloadKey, + boxPayload(4.0, 5.0, 6.0))); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .get(payloadKey)); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void staleRemoveStillDeletesGeneratedRows() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + registerTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-stale-remove-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(53); + addBoundSpace(store, spaceUuid, new BackendId("test:chunk-collision-stale-remove")); + String sourceKey = "3:4:5"; + String payloadKey = "chunk-collision/3/4/5"; + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 3, + 4, + 5, + payloadKey, + boxPayload(7.0, 8.0, 9.0))); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + assertNotNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + + queue.updateStamp(previousGeneration(PhysicsChunkLifecycle.generation()), + previousGeneration(settingsGeneration(store))); + queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, sourceKey, 3, 4, 5)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Test void sameDrainUpsertThenRemoveKeepsRemoveIntent() { ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsComponentTypeRegistry.registerComponentTypes(proxy); - PhysicsResourceTypes.registerResourceTypes(proxy); + registerTypes(proxy); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-upsert-remove-test")), EmptyResourceStorage.get()); @@ -377,8 +498,7 @@ void sameDrainRemoveThenUpsertKeepsUpsertIntent() { ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsComponentTypeRegistry.registerComponentTypes(proxy); - PhysicsResourceTypes.registerResourceTypes(proxy); + registerTypes(proxy); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-remove-upsert-test")), EmptyResourceStorage.get()); @@ -443,6 +563,13 @@ private static Ref addBoundSpace(@Nonnull Store stor return addBoundSpace(store, spaceUuid, backendId, false); } + private static void registerTypes( + @Nonnull ComponentRegistryProxy proxy) { + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + } + @Nonnull private static Ref addBoundSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -466,9 +593,34 @@ private static Ref addBoundSpace(@Nonnull Store stor spaceRef, backendId, new BackendSpaceHandle(spaceHandle)); + publishSettingsIndex(store, spaceUuid); return spaceRef; } + private static void publishSettingsIndex(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + PhysicsChunkSettingsIndexResource settingsIndex = + store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()); + settingsIndex.replaceAll(Map.of(spaceUuid, new PhysicsChunkSpaceSettings(spaceUuid, + PhysicsChunkCollisionMode.MANUAL, + EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED, + false, + 1, + 1, + 20))); + store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()) + .updateStamp(PhysicsChunkLifecycle.generation(), settingsIndex.generation()); + } + + private static long settingsGeneration(@Nonnull Store store) { + return store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()) + .generation(); + } + + private static long previousGeneration(long generation) { + return Math.max(0L, generation - 1L); + } + @Nonnull private static ChunkCollisionPayload boxPayload(double centerX, double centerY, @@ -621,6 +773,20 @@ private static void assertGeneratedBox(@Nonnull Store store, assertEquals(partIndex, source.getPartIndex()); } + @Nullable + private static Ref generatedBodyRef(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + @Nonnull PartKind partKind, + int partIndex) { + UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + partKind, + partIndex); + return store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(bodyUuid); + } + private static void assertMaterialMatchesSpace(@Nonnull Store store, @Nonnull Ref spaceRef, @Nonnull MaterialComponent material) { From 5d6e12db477a3b0a13c9193e4729953b7e831286 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:54:17 +0200 Subject: [PATCH 450/534] test(core): register physicschunk resources in fixtures Signed-off-by: Blovien --- .../internal/systems/ChunkCollisionComponentSyncSystemTest.java | 2 ++ .../systems/ChunkCollisionVoxelStitchingSystemTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java index 62fcad24..152cb4cd 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; @@ -58,6 +59,7 @@ void spaceSurfaceComponentsSyncGeneratedRowsAndBoundBackends() { new ComponentRegistryProxy<>(new ArrayList<>(), registry); PhysicsComponentTypeRegistry.registerComponentTypes(proxy); PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); proxy.registerSystem(new PersistenceHydrationSystem()); proxy.registerSystem(new IdentityIndexSystem()); proxy.registerSystem(new PhysicsChunkSettingsIndexSystem()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index 675d1475..07e173fb 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -24,6 +24,7 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.Neighbor; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; @@ -68,6 +69,7 @@ void voxelRowsAreStitchedThroughBodyRuntimeHandlesOncePerPayload() { new ComponentRegistryProxy<>(new ArrayList<>(), registry); PhysicsComponentTypeRegistry.registerComponentTypes(proxy); PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-stitch-row-test")), EmptyResourceStorage.get()); From b3cd78d112e6dadf061ef28bee354295b659927f Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 10:58:25 +0200 Subject: [PATCH 451/534] refactor(core): remove stale physics naming shims Signed-off-by: Blovien --- .../physicsstore/PhysicsStoreTopologyMutations.java | 8 ++++---- .../resources/PhysicsWorldRuntimeResource.java | 2 +- .../modules/physicschunk/PhysicsChunkCollision.java | 2 +- .../modules/physicsentity/PhysicsEntityTypes.java | 12 +----------- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index 2d33c6b6..a35bb75b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -86,15 +86,15 @@ public static void removeSpaceWithContents(@Nonnull Store store, PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceUuid); } - public static int clearTerrainForSpace(@Nonnull Store store, + public static int clearChunkCollisionRowsForSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore terrain rows"); + PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore chunk-collision rows"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); int removedBodies = 0; Ref spaceRef = identity.getByUuid(spaceUuid); - List removals = collectTerrainRows(store, spaceUuid, spaceRef); + List removals = collectChunkCollisionRows(store, spaceUuid, spaceRef); for (RowRemoval removal : removals) { if (removeRuntimeBody(runtime, identity, removal)) { removedBodies++; @@ -183,7 +183,7 @@ private static List collectRows(@Nonnull Store store, } @Nonnull - private static List collectTerrainRows(@Nonnull Store store, + private static List collectChunkCollisionRows(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nullable Ref spaceRef) { ComponentType uuidType = UuidComponent.getComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index e656709d..8c5365d7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -1069,7 +1069,7 @@ private int clearAuthoritativePhysicsChunkCollisionSpace(@Nonnull Store bodyAttachmentComponentType; - private PhysicsEntityTypes() { } @@ -29,8 +25,7 @@ public static boolean areEntityStoreTypesRegistered() { } public static boolean isBodyAttachmentComponentTypeRegistered() { - return bodyAttachmentComponentType != null - || PhysicsEntityTypeRegistry.isBodyAttachmentComponentTypeRegistered(); + return PhysicsEntityTypeRegistry.isBodyAttachmentComponentTypeRegistered(); } public static boolean isGeneratedVisualProxyComponentTypeRegistered() { @@ -39,11 +34,6 @@ public static boolean isGeneratedVisualProxyComponentTypeRegistered() { @Nonnull public static ComponentType bodyAttachmentComponentType() { - ComponentType compatibilityType = - bodyAttachmentComponentType; - if (compatibilityType != null) { - return compatibilityType; - } return PhysicsEntityTypeRegistry.bodyAttachmentComponentType(); } From ffd85c621bec62584727f8e09a4164e99aa106cb Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:00:39 +0200 Subject: [PATCH 452/534] refactor(examples): clarify stress spawn timings Signed-off-by: Blovien --- .../stress/StressBenchmarkCommand.java | 39 +++++++++--------- .../commands/stress/StressBodiesCommand.java | 40 ++++++++++++------- .../stress/StressRawBodiesCommand.java | 4 +- .../examples/utils/BlockBodyBatchResult.java | 12 +++--- .../examples/utils/ExamplePhysicsUtils.java | 32 +++++++-------- 5 files changed, 69 insertions(+), 58 deletions(-) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 0a76b6c1..a9ed05e1 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -39,7 +39,7 @@ public class StressBenchmarkCommand extends AbstractAsyncPlayerCommand { private final OptionalArg modeArg = this.withOptionalArg( "mode", - "Benchmark mode: raw/physics-only or entity", + "Benchmark mode: raw PhysicsStore rows or entity visuals", ArgTypes.STRING); private final OptionalArg countArg = this.withOptionalArg( "count", @@ -47,7 +47,7 @@ public class StressBenchmarkCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private final OptionalArg blockTypeArg = this.withOptionalArg( "blockType", - "Hytale block type for entity-backed benchmark visuals", + "Hytale block type for entity visual benchmark rows", ArgTypes.STRING); private final OptionalArg spaceArg = this.withOptionalArg( "space", @@ -55,7 +55,7 @@ public class StressBenchmarkCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); public StressBenchmarkCommand() { - super("benchmark", "Spawn repeatable physics-only or entity-backed body grids"); + super("benchmark", "Spawn repeatable PhysicsStore row or entity visual body grids"); } @Nonnull @@ -119,16 +119,17 @@ private static void spawnBenchmark(@Nonnull CommandContext ctx, ctx.sender().sendMessage(Message.raw("Added " + timing.spawned() + " " + request.mode().label() + " benchmark bodies: setupWallMs=" + millis(timing.setupWallNanos()) - + " entityApplyMs=" + millis(timing.entityApplyNanos()) - + (timing.entityAttachNanos() > 0L - ? " entityAttachMs=" + millis(timing.entityAttachNanos()) + + " physicsStoreApplyMs=" + millis(timing.physicsStoreApplyNanos()) + + (timing.visualAttachNanos() > 0L + ? " visualAttachMs=" + millis(timing.visualAttachNanos()) : "") + " (" + microsPerBody(timing.setupWallNanos(), timing.spawned()) + " us/body). Space bodies before add: " + beforeBodies + (request.mode() == BenchmarkMode.ENTITY ? ". blockType=" + request.blockType() : "") - + ". Body-count updates are visible after PhysicsStore binds the new entities" - + ". This command measures PhysicsStore row setup/entity attachment; use /impulse-examples stress bodies" - + " for detached/detached-view scalability scenarios" + + ". Body-count updates are visible after PhysicsStore binds the new rows" + + ". This command measures raw PhysicsStore row setup or entity visual attachment;" + + " use /impulse-examples stress bodies detached-view" + + " for scalable detached visual diagnostics" + ". For clean comparisons run /impulse clean, /impulse physicschunk perf reset," + " /impulse physicschunk perf toggle before spawning," + " then /impulse physicschunk perf report.")); @@ -185,7 +186,7 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, }); return new BenchmarkSpawnTiming(timing.count(), timing.setupWallNanos(), - timing.entityApplyNanos(), + timing.physicsStoreApplyNanos(), 0L); } @@ -218,9 +219,9 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st } }); return new BenchmarkSpawnTiming(timing.count(), - timing.entityApplyNanos() + timing.entityAttachNanos(), - timing.entityApplyNanos(), - timing.entityAttachNanos()); + timing.physicsStoreApplyNanos() + timing.visualAttachNanos(), + timing.physicsStoreApplyNanos(), + timing.visualAttachNanos()); } @Nonnull @@ -257,8 +258,8 @@ private static Integer tryParseCount(@Nonnull String value) { } private enum BenchmarkMode { - RAW("physics-only PhysicsStore rows"), - ENTITY("entity-backed Hytale"); + RAW("PhysicsStore row"), + ENTITY("entity visual"); private final String label; @@ -286,13 +287,13 @@ private record BenchmarkRequest(BenchmarkMode mode, int count, @Nonnull String b private record BenchmarkSpawnTiming(int spawned, long setupWallNanos, - long entityApplyNanos, - long entityAttachNanos) { + long physicsStoreApplyNanos, + long visualAttachNanos) { private BenchmarkSpawnTiming { setupWallNanos = Math.max(0L, setupWallNanos); - entityApplyNanos = Math.max(0L, entityApplyNanos); - entityAttachNanos = Math.max(0L, entityAttachNanos); + physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); + visualAttachNanos = Math.max(0L, visualAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index f15e37ab..b886d131 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -112,7 +112,7 @@ public class StressBodiesCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); public StressBodiesCommand() { - super("bodies", "Spawn many dynamic box bodies"); + super("bodies", "Spawn dynamic bodies in detached-view, detached, or entity visual modes"); } @Nonnull @@ -202,9 +202,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, layout.positionZ(i)); } }); - timing = new StressSpawnTiming(batchTiming.entityApplyNanos() + batchTiming.entityAttachNanos(), - batchTiming.entityApplyNanos(), - batchTiming.entityAttachNanos()); + timing = new StressSpawnTiming(batchTiming.physicsStoreApplyNanos() + batchTiming.visualAttachNanos(), + batchTiming.physicsStoreApplyNanos(), + batchTiming.visualAttachNanos()); } else { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = detachedSpawnSettings(collisionPolicy); @@ -226,7 +226,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } }); timing = new StressSpawnTiming(batchTiming.setupWallNanos(), - batchTiming.entityApplyNanos(), + batchTiming.physicsStoreApplyNanos(), 0L); } PhysicsChunkCollisionSettings chunkCollisionSettings = @@ -241,9 +241,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " stress bodies: setupWallMs=" + millis(prewarmNanos + timing.setupWallNanos()) + " prewarmMs=" + millis(prewarmNanos) - + " entityApplyMs=" + millis(timing.entityApplyNanos()) - + (timing.entityAttachNanos() > 0L - ? " entityAttachMs=" + millis(timing.entityAttachNanos()) + + " physicsStoreApplyMs=" + millis(timing.physicsStoreApplyNanos()) + + (timing.visualAttachNanos() > 0L + ? " visualAttachMs=" + millis(timing.visualAttachNanos()) : "") + ": mode=" + mode.serialized() + " space=" + spaceId.value() @@ -255,9 +255,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " maxStepDt=" + String.format(Locale.ROOT, "%.3f", worldSettings.getMaxStepDt()) + " visuals=" + mode.visualDescription() + (mode == StressMode.ENTITY ? " blockType=" + visualSettings.blockType() : "") - + (mode.usesDetachedBodies() - ? " body-count and detached-view snapshots update after PhysicsStore binds the new entities" - : "") + + " " + mode.bindingMessage() + (mode == StressMode.DETACHED_VIEW ? " visualProxyCap=" + visualMaterializationSettings.getDetachedVisualMaxMaterialized() @@ -548,6 +546,18 @@ private String visualDescription() { }; } + @Nonnull + private String bindingMessage() { + return switch (this) { + case ENTITY -> "Hytale EntityStore block visuals are attached immediately;" + + " physics motion follows copied PhysicsStore snapshots."; + case DETACHED -> "Body-count diagnostics update after PhysicsStore binds the new rows;" + + " no EntityStore visuals are created."; + case DETACHED_VIEW -> "Body-count diagnostics update after PhysicsStore binds the new rows;" + + " detached visual proxies materialize progressively from copied snapshots."; + }; + } + @Nullable private static StressMode from(@Nonnull String value) { return switch (value) { @@ -619,13 +629,13 @@ private record StressVisualSettings(int materializationRadius, } private record StressSpawnTiming(long setupWallNanos, - long entityApplyNanos, - long entityAttachNanos) { + long physicsStoreApplyNanos, + long visualAttachNanos) { private StressSpawnTiming { setupWallNanos = Math.max(0L, setupWallNanos); - entityApplyNanos = Math.max(0L, entityApplyNanos); - entityAttachNanos = Math.max(0L, entityAttachNanos); + physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); + visualAttachNanos = Math.max(0L, visualAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index 40d46a98..c56e4088 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -110,9 +110,9 @@ private static String successMessage(@Nonnull BodyEntityBatchTiming timing, long totalWallNanos) { return "PhysicsStore added body rows for " + timing.count() + " physics-only bodies: setupWallMs=" + millis(timing.setupWallNanos()) - + " entityApplyMs=" + millis(timing.entityApplyNanos()) + + " physicsStoreApplyMs=" + millis(timing.physicsStoreApplyNanos()) + " totalWallMs=" + millis(totalWallNanos) - + ". Body-count updates are visible after PhysicsStore binds the new entities."; + + ". Body-count updates are visible after PhysicsStore binds the new rows."; } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java index 3e0cf421..7d318d5f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java @@ -5,16 +5,16 @@ record BlockBodyBatchResult(@Nullable ExamplePhysicsUtils.SpawnedBlockBody[] bodies, int count, - long entityApplyNanos, - long entityAttachNanos) { + long physicsStoreApplyNanos, + long visualAttachNanos) { BlockBodyBatchResult { if (bodies != null && bodies.length != count) { throw new IllegalArgumentException("Collected body count does not match batch count"); } count = Math.max(0, count); - entityApplyNanos = Math.max(0L, entityApplyNanos); - entityAttachNanos = Math.max(0L, entityAttachNanos); + physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); + visualAttachNanos = Math.max(0L, visualAttachNanos); } @Nonnull @@ -28,7 +28,7 @@ ExamplePhysicsUtils.SpawnedBlockBody[] collectedBodies() { @Nonnull ExamplePhysicsUtils.BlockBodyBatchTiming timing() { return new ExamplePhysicsUtils.BlockBodyBatchTiming(count, - entityApplyNanos, - entityAttachNanos); + physicsStoreApplyNanos, + visualAttachNanos); } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 574db55f..7da38f3d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -303,10 +303,10 @@ public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World w long applyStartNanos = System.nanoTime(); addPhysicsStoreBodies(world, plan.bodies()); - long entityApplyNanos = System.nanoTime() - applyStartNanos; + long physicsStoreApplyNanos = System.nanoTime() - applyStartNanos; return new BodyEntityBatchTiming(plan.count(), plan.setupWallNanos(), - entityApplyNanos); + physicsStoreApplyNanos); } @Nonnull @@ -335,10 +335,10 @@ public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World w long applyStartNanos = System.nanoTime(); addPhysicsStoreBodies(world, plan.bodies()); - long entityApplyNanos = System.nanoTime() - applyStartNanos; + long physicsStoreApplyNanos = System.nanoTime() - applyStartNanos; return new BodyEntityBatchTiming(plan.count(), plan.setupWallNanos(), - entityApplyNanos); + physicsStoreApplyNanos); } @Nonnull @@ -690,11 +690,11 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store holder, @@ -888,24 +888,24 @@ public record SpaceSelection(@Nonnull SpaceId spaceId, } public record BlockBodyBatchTiming(int count, - long entityApplyNanos, - long entityAttachNanos) { + long physicsStoreApplyNanos, + long visualAttachNanos) { public BlockBodyBatchTiming { count = Math.max(0, count); - entityApplyNanos = Math.max(0L, entityApplyNanos); - entityAttachNanos = Math.max(0L, entityAttachNanos); + physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); + visualAttachNanos = Math.max(0L, visualAttachNanos); } } public record BodyEntityBatchTiming(int count, long setupWallNanos, - long entityApplyNanos) { + long physicsStoreApplyNanos) { public BodyEntityBatchTiming { count = Math.max(0, count); setupWallNanos = Math.max(0L, setupWallNanos); - entityApplyNanos = Math.max(0L, entityApplyNanos); + physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); } } From 105d67fbb6553fa6b981d1f39dcfc8eef310ebcb Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:01:58 +0200 Subject: [PATCH 453/534] refactor(core): align physicschunk naming Signed-off-by: Blovien --- .../persistence/PersistentSpaceDto.java | 86 +++++++++---------- .../physicschunk/PhysicsChunkCollision.java | 9 +- .../commands/PhysicsChunkExampleCommand.java | 18 ++-- 3 files changed, 57 insertions(+), 56 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index ce90132a..37516bfe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -49,7 +49,7 @@ public final class PersistentSpaceDto { .append(new KeyedCodec<>("PhysicsChunkTerrainMode", new EnumCodec<>(PhysicsChunkCollisionMode.class), false), - (dto, value) -> dto.terrainMode = value != null + (dto, value) -> dto.chunkCollisionMode = value != null ? value : PhysicsChunkCollisionMode.NONE, PersistentSpaceDto::getMode) @@ -67,19 +67,19 @@ public final class PersistentSpaceDto { PersistentSpaceDto::isNativeVoxelCollisionEnabled) .add() .append(new KeyedCodec<>("ChunkCollisionRadius", Codec.INTEGER, false), - (dto, value) -> dto.terrainRadius = value != null + (dto, value) -> dto.chunkCollisionRadius = value != null ? value : PhysicsChunkCollisionSettings.DEFAULT_RADIUS, PersistentSpaceDto::getRadius) .add() .append(new KeyedCodec<>("BodyChunkCollisionRadius", Codec.INTEGER, false), - (dto, value) -> dto.bodyTerrainRadius = value != null + (dto, value) -> dto.bodyChunkCollisionRadius = value != null ? value : PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, PersistentSpaceDto::getBodyRadius) .add() .append(new KeyedCodec<>("ChunkCollisionTtlTicks", Codec.INTEGER, false), - (dto, value) -> dto.terrainTtlTicks = value != null + (dto, value) -> dto.chunkCollisionTtlTicks = value != null ? value : PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, PersistentSpaceDto::getTtlTicks) @@ -149,17 +149,17 @@ public final class PersistentSpaceDto { @Nonnull private final Vector3f gravity = new Vector3f(0.0f, -9.81f, 0.0f); @Nonnull - private PhysicsChunkCollisionMode terrainMode = PhysicsChunkCollisionMode.NONE; + private PhysicsChunkCollisionMode chunkCollisionMode = PhysicsChunkCollisionMode.NONE; @Nonnull private EntityChunkBoundaryMode entityChunkBoundaryMode = PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; private boolean nativeVoxelCollisionEnabled = PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; - private int terrainRadius = + private int chunkCollisionRadius = PhysicsChunkCollisionSettings.DEFAULT_RADIUS; - private int bodyTerrainRadius = + private int bodyChunkCollisionRadius = PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS; - private int terrainTtlTicks = + private int chunkCollisionTtlTicks = PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS; private float chunkCollisionFriction = PhysicsChunkCollisionDefaults.FRICTION; private float chunkCollisionRestitution = @@ -208,22 +208,22 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkCollisionMode terrainMode, + @Nonnull PhysicsChunkCollisionMode chunkCollisionMode, boolean nativeVoxelCollisionEnabled, - int terrainRadius, - int bodyTerrainRadius, - int terrainTtlTicks, + int chunkCollisionRadius, + int bodyChunkCollisionRadius, + int chunkCollisionTtlTicks, float chunkCollisionFriction, float chunkCollisionRestitution) { this(spaceUuid, backendId, gravity, - terrainMode, + chunkCollisionMode, PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, nativeVoxelCollisionEnabled, - terrainRadius, - bodyTerrainRadius, - terrainTtlTicks, + chunkCollisionRadius, + bodyChunkCollisionRadius, + chunkCollisionTtlTicks, chunkCollisionFriction, chunkCollisionRestitution, PhysicsChunkCollisionDefaults.COLLISION_GROUP, @@ -238,12 +238,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkCollisionMode terrainMode, + @Nonnull PhysicsChunkCollisionMode chunkCollisionMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelCollisionEnabled, - int terrainRadius, - int bodyTerrainRadius, - int terrainTtlTicks, + int chunkCollisionRadius, + int bodyChunkCollisionRadius, + int chunkCollisionTtlTicks, float chunkCollisionFriction, float chunkCollisionRestitution, @Nonnull SolverSettingsComponent solverSettings, @@ -254,12 +254,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this(spaceUuid, backendId, gravity, - terrainMode, + chunkCollisionMode, entityChunkBoundaryMode, nativeVoxelCollisionEnabled, - terrainRadius, - bodyTerrainRadius, - terrainTtlTicks, + chunkCollisionRadius, + bodyChunkCollisionRadius, + chunkCollisionTtlTicks, chunkCollisionFriction, chunkCollisionRestitution, PhysicsChunkCollisionDefaults.COLLISION_GROUP, @@ -274,12 +274,12 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, public PersistentSpaceDto(@Nonnull UUID spaceUuid, @Nonnull String backendId, @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkCollisionMode terrainMode, + @Nonnull PhysicsChunkCollisionMode chunkCollisionMode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, boolean nativeVoxelCollisionEnabled, - int terrainRadius, - int bodyTerrainRadius, - int terrainTtlTicks, + int chunkCollisionRadius, + int bodyChunkCollisionRadius, + int chunkCollisionTtlTicks, float chunkCollisionFriction, float chunkCollisionRestitution, int chunkCollisionGroup, @@ -292,13 +292,13 @@ public PersistentSpaceDto(@Nonnull UUID spaceUuid, this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); this.backendId = Objects.requireNonNull(backendId, "backendId"); this.gravity.set(Objects.requireNonNull(gravity, "gravity")); - this.terrainMode = Objects.requireNonNull(terrainMode, "terrainMode"); + this.chunkCollisionMode = Objects.requireNonNull(chunkCollisionMode, "chunkCollisionMode"); this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, "entityChunkBoundaryMode"); this.nativeVoxelCollisionEnabled = nativeVoxelCollisionEnabled; - this.terrainRadius = terrainRadius; - this.bodyTerrainRadius = bodyTerrainRadius; - this.terrainTtlTicks = terrainTtlTicks; + this.chunkCollisionRadius = chunkCollisionRadius; + this.bodyChunkCollisionRadius = bodyChunkCollisionRadius; + this.chunkCollisionTtlTicks = chunkCollisionTtlTicks; this.chunkCollisionFriction = chunkCollisionFriction; this.chunkCollisionRestitution = chunkCollisionRestitution; this.chunkCollisionFilter = new CollisionFilterComponent(chunkCollisionGroup, @@ -331,7 +331,7 @@ public Vector3f getGravity() { @Nonnull public PhysicsChunkCollisionMode getMode() { - return terrainMode; + return chunkCollisionMode; } @Nonnull @@ -344,15 +344,15 @@ public boolean isNativeVoxelCollisionEnabled() { } public int getRadius() { - return terrainRadius; + return chunkCollisionRadius; } public int getBodyRadius() { - return bodyTerrainRadius; + return bodyChunkCollisionRadius; } public int getTtlTicks() { - return terrainTtlTicks; + return chunkCollisionTtlTicks; } public float getChunkCollisionFriction() { @@ -376,9 +376,9 @@ public ChunkCollisionSettingsComponent getChunkCollisionSettings() { return new ChunkCollisionSettingsComponent(getMode(), entityChunkBoundaryMode, nativeVoxelCollisionEnabled, - terrainRadius, - bodyTerrainRadius, - terrainTtlTicks); + chunkCollisionRadius, + bodyChunkCollisionRadius, + chunkCollisionTtlTicks); } @Nonnull @@ -445,12 +445,12 @@ public PersistentSpaceDto copy() { return new PersistentSpaceDto(spaceUuid, backendId, gravity, - terrainMode, + chunkCollisionMode, entityChunkBoundaryMode, nativeVoxelCollisionEnabled, - terrainRadius, - bodyTerrainRadius, - terrainTtlTicks, + chunkCollisionRadius, + bodyChunkCollisionRadius, + chunkCollisionTtlTicks, chunkCollisionFriction, chunkCollisionRestitution, getChunkCollisionGroup(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index 80890905..cbe657ef 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -73,7 +73,7 @@ private static PhysicsChunkCollisionBuildStats rebuildAroundChecked(@Nonnull Wor int radius) { PhysicsChunkSpaceSettings settings = requireSettings(checkedStore, spaceRef); PhysicsChunkCollisionMutationQueueResource queue = stampedQueue(checkedStore); - int removed = clearSpaceRows(world, checkedStore, settings.spaceUuid()); + int removed = clearSpaceChunkCollisionRows(world, checkedStore, settings.spaceUuid()); PhysicsChunkCollisionPrewarmStats stats = streaming(world).ensureAround(world, settings.spaceUuid(), queue, @@ -190,7 +190,7 @@ public static int clearSpace(@Nonnull World world, Store checkedStore = requireMatchingWorldThread(world, store, "clear PhysicsChunk collision"); - return clearSpaceRows(world, + return clearSpaceChunkCollisionRows(world, checkedStore, requireSpaceUuid(checkedStore, requireSpaceRef(checkedStore, spaceId))); } @@ -201,7 +201,8 @@ public static int clearSpace(@Nonnull World world, Store checkedStore = requireMatchingWorldThread(world, store, "clear PhysicsChunk collision"); - return clearSpaceRows(world, checkedStore, requireSpaceUuid(checkedStore, spaceRef)); + return clearSpaceChunkCollisionRows(world, checkedStore, requireSpaceUuid(checkedStore, + spaceRef)); } @Nonnull @@ -308,7 +309,7 @@ private static PhysicsChunkCollisionStreamingResource streaming(@Nonnull World w return entityStore.getResource(PhysicsChunkCollisionStreamingResource.getResourceType()); } - private static int clearSpaceRows(@Nonnull World world, + private static int clearSpaceChunkCollisionRows(@Nonnull World world, @Nonnull Store store, @Nonnull UUID spaceUuid) { int removed = 0; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index 3b6e9b10..54041313 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -25,12 +25,12 @@ import org.joml.Vector3d; /** - * Debug commands for manually building/clearing PhysicsChunk collision collision. + * Debug commands for manually building/clearing PhysicsChunk collision. */ public class PhysicsChunkExampleCommand extends AbstractCommandCollection { public PhysicsChunkExampleCommand() { - super("physicschunk", "Build PhysicsChunk collision collision from nearby world blocks"); + super("physicschunk", "Build PhysicsChunk collision from nearby world blocks"); addSubCommand(new BuildCommand()); addSubCommand(new EnsureCommand()); addSubCommand(new ClearCommand()); @@ -57,7 +57,7 @@ private static final class BuildCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private BuildCommand() { - super("build", "Rebuild nearby PhysicsChunk collision collision"); + super("build", "Rebuild nearby PhysicsChunk collision"); } @Nonnull @@ -82,7 +82,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, playerPos, radius); - ctx.sender().sendMessage(Message.raw("Built PhysicsChunk collision collision: scanned " + ctx.sender().sendMessage(Message.raw("Built PhysicsChunk collision: scanned " + stats.scannedBlocks() + " blocks, solid " + stats.solidBlocks() + ", culled " + stats.culledInteriorBlocks() @@ -113,7 +113,7 @@ private static final class EnsureCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private EnsureCommand() { - super("ensure", "Ensure nearby PhysicsChunk collision collision is available"); + super("ensure", "Ensure nearby PhysicsChunk collision is available"); } @Nonnull @@ -139,7 +139,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, radius, Math.max(0L, world.getTick())); - ctx.sender().sendMessage(Message.raw("Ensured PhysicsChunk collision collision: targets " + ctx.sender().sendMessage(Message.raw("Ensured PhysicsChunk collision: targets " + stats.sectionTargets() + ", bodies " + stats.buildStats().colliderBodies() @@ -158,7 +158,7 @@ private static final class ClearCommand extends AbstractAsyncPlayerCommand { ArgTypes.INTEGER); private ClearCommand() { - super("clear", "Remove generated PhysicsChunk collision collision"); + super("clear", "Remove generated PhysicsChunk collision"); } @Nonnull @@ -184,7 +184,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, private static final class StatsCommand extends AbstractAsyncPlayerCommand { private StatsCommand() { - super("stats", "Show generated PhysicsChunk collision collision stats"); + super("stats", "Show generated PhysicsChunk collision stats"); } @Nonnull @@ -195,7 +195,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { PhysicsChunkCollisionStats stats = PhysicsChunkCollision.stats(world); - ctx.sender().sendMessage(Message.raw("PhysicsChunk collision collision: " + ctx.sender().sendMessage(Message.raw("PhysicsChunk collision: " + stats.spaces() + " spaces, " + stats.sections() + " sections, " + stats.bodies() + " bodies, " From 6e3919edb31748f775b76fc908a8a154f666917f Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:04:27 +0200 Subject: [PATCH 454/534] refactor(core): move module settings enums Signed-off-by: Blovien --- .../impulse/core/internal/crucible/ImpulseApiCrucibleTests.java | 2 +- .../physicschunk/commands/PhysicsChunkSettingsCommand.java | 2 +- .../physicsentity/commands/VisualSyncSettingsCommand.java | 2 +- .../impulse/core/internal/persistence/PersistentSpaceDto.java | 2 +- .../internal/resources/PhysicsChunkSettingsIndexResource.java | 2 +- .../impulse/core/internal/systems/sync/PhysicsSyncPolicy.java | 2 +- .../components/ChunkCollisionSettingsComponent.java | 2 +- .../physicschunk}/settings/EntityChunkBoundaryMode.java | 2 +- .../physicschunk/settings/PhysicsChunkCollisionSettings.java | 2 +- .../physicsentity/components/VisualSyncSettingsComponent.java | 2 +- .../physicsentity/settings/PhysicsVisualSyncSettings.java | 2 +- .../physicsentity}/settings/VisualOcclusionMode.java | 2 +- .../impulse/examples/commands/stress/StressBodiesCommand.java | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{ => modules/physicschunk}/settings/EntityChunkBoundaryMode.java (88%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{ => modules/physicsentity}/settings/VisualOcclusionMode.java (67%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index a7ea7599..7a8a95c2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -29,7 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import java.util.UUID; import java.util.Collection; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index 2c454c2e..0c91a488 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java index 22fbd825..ab62343d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index 37516bfe..ac4cc392 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index b8f0aac4..2ace3c6c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -6,7 +6,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.List; import java.util.Map; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java index 43dcd195..b1960b68 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java @@ -4,7 +4,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java index fb1fcb0c..3251db65 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/EntityChunkBoundaryMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/EntityChunkBoundaryMode.java similarity index 88% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/EntityChunkBoundaryMode.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/EntityChunkBoundaryMode.java index 68ff3161..d55d3e85 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/EntityChunkBoundaryMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/EntityChunkBoundaryMode.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.settings; +package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; /** diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java index 560f0908..4e837fa7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/PhysicsChunkCollisionSettings.java @@ -1,7 +1,7 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import lombok.Getter; import lombok.Setter; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java index 7445586a..c9be8527 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import java.util.Objects; import javax.annotation.Nonnull; import lombok.Getter; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java index 7f281d21..ada5b91e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import javax.annotation.Nonnull; import lombok.Getter; import lombok.Setter; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/VisualOcclusionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/VisualOcclusionMode.java similarity index 67% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/VisualOcclusionMode.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/VisualOcclusionMode.java index 34feef2c..8ed13a20 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/VisualOcclusionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/VisualOcclusionMode.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.settings; +package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; public enum VisualOcclusionMode { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index b886d131..1215d2c0 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -29,7 +29,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; From 9331af9a6d185e02f452b0e2089bea0da2f65120 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:05:02 +0200 Subject: [PATCH 455/534] test(core): update module settings enum imports Signed-off-by: Blovien --- .../internal/systems/ChunkCollisionMutationDrainSystemTest.java | 2 +- .../impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index a328b84c..6872ffff 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -54,7 +54,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.EntityChunkBoundaryMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java index fd7795c0..d2f587d4 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; From f55778da9f581229598635859896479b98347d16 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:08:42 +0200 Subject: [PATCH 456/534] test(examples): use physicsentity registry in utils tests Signed-off-by: Blovien --- .../utils/ExamplePhysicsUtilsTest.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java index 5e5052b5..b04d9a6b 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.modules.entity.EntityModule; @@ -14,12 +14,13 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.examples.testsupport.ExampleControlTestSupport; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Quaternionf; @@ -33,30 +34,23 @@ class ExamplePhysicsUtilsTest { private ComponentRegistry registry; private Object previousEntityModule; - private Object previousBodyAttachmentComponentType; @BeforeEach void registerComponentTypes() throws Exception { previousEntityModule = staticField(EntityModule.class, "instance").get(null); - Field bodyAttachmentTypeField = - staticField(PhysicsEntityTypes.class, "bodyAttachmentComponentType"); - previousBodyAttachmentComponentType = bodyAttachmentTypeField.get(null); registry = new ComponentRegistry<>(); registerEntityModuleTypes(); - ComponentType bodyAttachmentType = - registry.registerComponent(BodyAttachmentComponent.class, - "BodyAttachment", - BodyAttachmentComponent.CODEC); - bodyAttachmentTypeField.set(null, bodyAttachmentType); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsEntityTypeRegistry.registerComponentTypes(proxy); ExampleControlTestSupport.enableControl(registry); } @AfterEach void clearComponentTypes() throws Exception { ExampleControlTestSupport.clearControl(); + PhysicsEntityTypeRegistry.clearEntityStoreTypes(); staticField(EntityModule.class, "instance").set(null, previousEntityModule); - staticField(PhysicsEntityTypes.class, "bodyAttachmentComponentType") - .set(null, previousBodyAttachmentComponentType); registry.shutdown(); } From 8b1f8d8cad96d7914ad0c3d49bd505bb4c7fdda3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:18:01 +0200 Subject: [PATCH 457/534] refactor(backends): discover runtime providers only Signed-off-by: Blovien --- README.md | 3 +- .../bullet/BulletBackendRuntimeProvider.java | 39 ++++++++++++++ ...v.hytalemodding.impulse.api.PhysicsBackend | 1 - ....api.runtime.PhysicsBackendRuntimeProvider | 1 + impulse-core/README.md | 4 +- .../impulse/core/BackendDiscovery.java | 51 ++----------------- .../impulse/core/ImpulsePlugin.java | 6 --- .../rapier/RapierBackendRuntimeProvider.java | 33 ++++++++++++ ...v.hytalemodding.impulse.api.PhysicsBackend | 1 - ....api.runtime.PhysicsBackendRuntimeProvider | 1 + 10 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java delete mode 100644 impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend create mode 100644 impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider create mode 100644 impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java delete mode 100644 impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend create mode 100644 impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider diff --git a/README.md b/README.md index 2db2cc22..a4071781 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,8 @@ You can start a debug server with all the example mods and backend jars by runni ### Backend Provider Jars -Backend jars are Java service-provider jars. Impulse discovers `PhysicsBackend` providers from jars anywhere under the configured Hytale `mods` directories. +Backend jars are Java service-provider jars. Impulse discovers `PhysicsBackendRuntimeProvider` +services from jars anywhere under the configured Hytale `mods` directories. When multiple backend jars are installed, create spaces with an explicit backend: diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java new file mode 100644 index 00000000..e15bd2a6 --- /dev/null +++ b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java @@ -0,0 +1,39 @@ +package dev.hytalemodding.impulse.bullet; + +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntime; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +/** + * Runtime-provider service entry point for Bullet. + */ +@SuppressWarnings("removal") +public final class BulletBackendRuntimeProvider implements PhysicsBackendRuntimeProvider { + + private final BulletBackend backend = new BulletBackend(); + + @Nonnull + @Override + public BackendId getId() { + return BulletBackend.ID; + } + + @Override + public void init() { + backend.init(); + } + + @Override + public void setInternalLoggingLevel(@Nonnull Level level) { + backend.setInternalLoggingLevel(level); + } + + @Nonnull + @Override + public PhysicsBackendRuntime createRuntime() { + return new LegacyPhysicsBackendRuntime(backend); + } +} diff --git a/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend b/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend deleted file mode 100644 index 71f3c60f..00000000 --- a/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend +++ /dev/null @@ -1 +0,0 @@ -dev.hytalemodding.impulse.bullet.BulletBackend \ No newline at end of file diff --git a/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider b/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider new file mode 100644 index 00000000..9a1c90a1 --- /dev/null +++ b/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider @@ -0,0 +1 @@ +dev.hytalemodding.impulse.bullet.BulletBackendRuntimeProvider diff --git a/impulse-core/README.md b/impulse-core/README.md index c5c48d46..0e3d9db9 100644 --- a/impulse-core/README.md +++ b/impulse-core/README.md @@ -13,8 +13,8 @@ Impulse core is divided in two categories: - `/impulse backend list` - list discovered backends and active physics spaces. -Backend jars are Java service-provider jars. Impulse discovers `PhysicsBackend` providers from jars -anywhere under the configured Hytale `mods` directories. +Backend jars are Java service-provider jars. Impulse discovers `PhysicsBackendRuntimeProvider` +services from jars anywhere under the configured Hytale `mods` directories. ## Event frames diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/BackendDiscovery.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/BackendDiscovery.java index 4d866729..5d6e6430 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/BackendDiscovery.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/BackendDiscovery.java @@ -2,7 +2,6 @@ import com.hypixel.hytale.logger.HytaleLogger; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBackend; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import java.io.IOException; import java.net.MalformedURLException; @@ -23,41 +22,15 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; -@SuppressWarnings("removal") final class BackendDiscovery { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final String BACKEND_SERVICE_RESOURCE = - "META-INF/services/" + PhysicsBackend.class.getName(); private static final String RUNTIME_PROVIDER_SERVICE_RESOURCE = "META-INF/services/" + PhysicsBackendRuntimeProvider.class.getName(); private BackendDiscovery() { } - @Nonnull - static List discover(@Nonnull Collection backendSearchRoots, - @Nonnull ClassLoader parentClassLoader) { - Map discovered = new LinkedHashMap<>(); - loadFrom(parentClassLoader, "plugin classpath", discovered); - - for (Path backendJar : findBackendProviderJars(backendSearchRoots)) { - try { - URL[] urls = {backendJar.toUri().toURL()}; - URLClassLoader backendLoader = new URLClassLoader( - "ImpulseBackendProvider(" + backendJar.getFileName() + ")", - urls, - parentClassLoader); - loadFrom(backendLoader, backendJar.toString(), discovered); - } catch (MalformedURLException e) { - LOGGER.at(Level.WARNING) - .log("Skipping backend provider jar %s: %s", backendJar, e.getMessage()); - } - } - - return List.copyOf(discovered.values()); - } - @Nonnull static List discoverRuntimeProviders( @Nonnull Collection backendSearchRoots, @@ -95,7 +68,7 @@ private static List findBackendProviderJars( paths.filter(Files::isRegularFile) .filter(BackendDiscovery::isJar) .sorted(Comparator.comparing(Path::toString)) - .filter(BackendDiscovery::containsBackendService) + .filter(BackendDiscovery::containsRuntimeProviderService) .forEach(jars::add); } catch (IOException e) { LOGGER.at(Level.WARNING) @@ -111,10 +84,9 @@ private static boolean isJar(@Nonnull Path path) { return path.getFileName().toString().toLowerCase().endsWith(".jar"); } - private static boolean containsBackendService(@Nonnull Path jarPath) { + private static boolean containsRuntimeProviderService(@Nonnull Path jarPath) { try (JarFile jar = new JarFile(jarPath.toFile())) { - return jar.getEntry(BACKEND_SERVICE_RESOURCE) != null - || jar.getEntry(RUNTIME_PROVIDER_SERVICE_RESOURCE) != null; + return jar.getEntry(RUNTIME_PROVIDER_SERVICE_RESOURCE) != null; } catch (IOException e) { LOGGER.at(Level.WARNING) .log("Skipping unreadable backend provider jar %s: %s", @@ -124,23 +96,6 @@ private static boolean containsBackendService(@Nonnull Path jarPath) { } } - private static void loadFrom(@Nonnull ClassLoader classLoader, - @Nonnull String source, - @Nonnull Map discovered) { - ServiceLoader loader = ServiceLoader.load(PhysicsBackend.class, - classLoader); - try { - for (PhysicsBackend backend : loader) { - discovered.put(backend.getId(), backend); - } - } catch (ServiceConfigurationError e) { - LOGGER.at(Level.WARNING) - .log("Failed to load physics backend provider from %s: %s", - source, - e.getMessage()); - } - } - private static void loadRuntimeProvidersFrom(@Nonnull ClassLoader classLoader, @Nonnull String source, @Nonnull Map discovered) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index ff3d8578..54258302 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -13,7 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBackend; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; @@ -100,17 +99,12 @@ private void registerCrucibleSuites() { } } - @SuppressWarnings("removal") private void discoverBackends() { for (PhysicsBackendRuntimeProvider provider : BackendDiscovery.discoverRuntimeProviders( backendSearchRoots(), getClassLoader())) { Impulse.registerRuntimeProvider(provider); } - for (PhysicsBackend backend : BackendDiscovery.discover(backendSearchRoots(), - getClassLoader())) { - Impulse.registerBackend(backend); - } for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { LOGGER.at(Level.INFO).log("Registered physics backend runtime %s", provider.getId()); diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java new file mode 100644 index 00000000..3fce5814 --- /dev/null +++ b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java @@ -0,0 +1,33 @@ +package dev.hytalemodding.impulse.rapier; + +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntime; +import javax.annotation.Nonnull; + +/** + * Runtime-provider service entry point for Rapier. + */ +@SuppressWarnings("removal") +public final class RapierBackendRuntimeProvider implements PhysicsBackendRuntimeProvider { + + private final RapierBackend backend = new RapierBackend(); + + @Nonnull + @Override + public BackendId getId() { + return RapierBackend.ID; + } + + @Override + public void init() { + backend.init(); + } + + @Nonnull + @Override + public PhysicsBackendRuntime createRuntime() { + return new LegacyPhysicsBackendRuntime(backend); + } +} diff --git a/impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend b/impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend deleted file mode 100644 index 5456be66..00000000 --- a/impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend +++ /dev/null @@ -1 +0,0 @@ -dev.hytalemodding.impulse.rapier.RapierBackend diff --git a/impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider b/impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider new file mode 100644 index 00000000..e0286d8a --- /dev/null +++ b/impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider @@ -0,0 +1 @@ +dev.hytalemodding.impulse.rapier.RapierBackendRuntimeProvider From 03222c5e855c428564472ec57f6a0f7b2c2b87e1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:18:29 +0200 Subject: [PATCH 458/534] test(core): cover runtime provider discovery Signed-off-by: Blovien --- .../ImpulsePluginBackendSelectionTest.java | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java index 69ff4e07..8b75602a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java @@ -5,8 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsSpace; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import java.io.IOException; import java.net.URL; @@ -25,10 +25,10 @@ class ImpulsePluginBackendSelectionTest { - private static final String BACKEND_SERVICE = - "META-INF/services/dev.hytalemodding.impulse.api.PhysicsBackend"; + private static final String RUNTIME_PROVIDER_SERVICE = + "META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider"; private static final String SERVICE_PROVIDER_CLASS = - "dev.hytalemodding.impulse.core.testbackend.JarOnlyServiceLoadedBackend"; + "dev.hytalemodding.impulse.core.testbackend.JarOnlyServiceLoadedRuntimeProvider"; private static final BackendId SERVICE_BACKEND_ID = new BackendId("test:service-loaded"); @TempDir @@ -50,10 +50,11 @@ void discoversClasspathVisibleServiceProviders() throws IOException { try (URLClassLoader loader = new URLClassLoader( new URL[]{providerJar.toUri().toURL()}, Thread.currentThread().getContextClassLoader())) { - List backends = BackendDiscovery.discover(List.of(), loader); + List backends = + BackendDiscovery.discoverRuntimeProviders(List.of(), loader); assertEquals(List.of(SERVICE_BACKEND_ID), backends.stream() - .map(PhysicsBackend::getId) + .map(PhysicsBackendRuntimeProvider::getId) .toList()); } } @@ -64,19 +65,19 @@ void discoversServiceProvidersFromNestedModsJars() throws IOException { Files.createDirectories(backendDirectory); writeServiceJar(backendDirectory.resolve("provider.jar")); - List backends = BackendDiscovery.discover( + List backends = BackendDiscovery.discoverRuntimeProviders( List.of(tempDir.resolve("mods")), Thread.currentThread().getContextClassLoader()); assertEquals(List.of(SERVICE_BACKEND_ID), backends.stream() - .map(PhysicsBackend::getId) + .map(PhysicsBackendRuntimeProvider::getId) .toList()); } private static void writeServiceJar(@Nonnull Path jarPath) throws IOException { Path classFile = compileServiceProviderClass(jarPath.getParent()); try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(jarPath))) { - jar.putNextEntry(new JarEntry(BACKEND_SERVICE)); + jar.putNextEntry(new JarEntry(RUNTIME_PROVIDER_SERVICE)); jar.write((SERVICE_PROVIDER_CLASS + "\n") .getBytes(StandardCharsets.UTF_8)); jar.closeEntry(); @@ -98,29 +99,32 @@ private static Path compileServiceProviderClass(@Nonnull Path outputRoot) throws package dev.hytalemodding.impulse.core.testbackend; import dev.hytalemodding.impulse.api.BackendId; - import dev.hytalemodding.impulse.api.PhysicsBackend; - import dev.hytalemodding.impulse.api.PhysicsSpace; + import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; + import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; + import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import javax.annotation.Nonnull; - public final class JarOnlyServiceLoadedBackend implements PhysicsBackend { + public final class JarOnlyServiceLoadedRuntimeProvider + implements PhysicsBackendRuntimeProvider { - public JarOnlyServiceLoadedBackend() { + private final FakePhysicsBackendRuntimeProvider delegate = + new FakePhysicsBackendRuntimeProvider(new BackendId("test:service-loaded"), + false, + false); + + public JarOnlyServiceLoadedRuntimeProvider() { } @Nonnull @Override public BackendId getId() { - return new BackendId("test:service-loaded"); - } - - @Override - public void init() { + return delegate.getId(); } @Nonnull @Override - public PhysicsSpace createSpace() { - throw new UnsupportedOperationException("not used"); + public PhysicsBackendRuntime createRuntime() { + return delegate.createRuntime(); } } """); From 65facbca0cf2acc881da6ad925eabec304b696ea Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:19:48 +0200 Subject: [PATCH 459/534] refactor(api): remove unused legacy backend registry Signed-off-by: Blovien --- .../hytalemodding/impulse/api/Impulse.java | 104 ------------------ .../impulse/api/PhysicsBackendEventBatch.java | 33 ------ .../api/PhysicsBackendEventBuffer.java | 70 ------------ .../LegacyPhysicsBackendRuntimeProvider.java | 45 -------- 4 files changed, 252 deletions(-) delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java index 898f1fe1..2777ba40 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntimeProvider; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -23,8 +22,6 @@ public final class Impulse { private static final Object REGISTRY_LOCK = new Object(); - private static final Map BACKENDS = new HashMap<>(); - private static final Map RUNTIME_PROVIDERS = new HashMap<>(); @@ -35,22 +32,6 @@ public final class Impulse { private Impulse() { } - /** - * Register or replace a backend implementation. - *

        - * This method is thread-safe. - */ - @Deprecated(forRemoval = true) - public static void registerBackend(@Nonnull PhysicsBackend backend) { - synchronized (REGISTRY_LOCK) { - BACKENDS.put(backend.getId(), backend); - RUNTIME_PROVIDERS.putIfAbsent(backend.getId(), - new LegacyPhysicsBackendRuntimeProvider(backend)); - BACKEND_INIT_LOCKS.computeIfAbsent(backend.getId(), ignored -> new Object()); - INITIALIZED_BACKENDS.remove(backend.getId()); - } - } - /** * Register or replace an id-only backend runtime provider. */ @@ -62,14 +43,6 @@ public static void registerRuntimeProvider(@Nonnull PhysicsBackendRuntimeProvide } } - @Nonnull - @Deprecated(forRemoval = true) - public static Collection getBackends() { - synchronized (REGISTRY_LOCK) { - return Collections.unmodifiableCollection(new ArrayList<>(BACKENDS.values())); - } - } - @Nonnull public static Collection getRuntimeProviders() { synchronized (REGISTRY_LOCK) { @@ -77,18 +50,6 @@ public static Collection getRuntimeProviders() { } } - @Nonnull - @Deprecated(forRemoval = true) - public static PhysicsBackend getBackend(@Nonnull BackendId backendId) { - synchronized (REGISTRY_LOCK) { - PhysicsBackend backend = BACKENDS.get(backendId); - if (backend == null) { - throw new IllegalStateException("No backend registered with id: " + backendId); - } - return backend; - } - } - @Nonnull public static PhysicsBackendRuntimeProvider getRuntimeProvider(@Nonnull BackendId backendId) { synchronized (REGISTRY_LOCK) { @@ -107,71 +68,6 @@ public static PhysicsBackendRuntime createRuntime(@Nonnull BackendId backendId) return provider.createRuntime(); } - /** - * Create a space for the given backend id. - * - *

        This method is safe to call concurrently after the backend is registered. The returned - * space is live backend state and must still be owned by one serialized backend lane.

        - */ - @Nonnull - @Deprecated(forRemoval = true) - public static PhysicsSpace createSpace(@Nonnull BackendId backendId) { - return createSpace(backendId, SpaceId.next()); - } - - /** - * Create a space for the given backend id and logical space id. - * - *

        This method is safe to call concurrently after the backend is registered. The returned - * space is live backend state and must still be owned by one serialized backend lane.

        - */ - @Nonnull - @Deprecated(forRemoval = true) - public static PhysicsSpace createSpace(@Nonnull BackendId backendId, - @Nonnull SpaceId spaceId) { - PhysicsBackend backend = getBackend(backendId); - ensureBackendInitialized(backendId, backend); - PhysicsSpace space = backend.createSpace(spaceId); - if (!spaceId.equals(space.id())) { - throw new IllegalStateException("Backend " + backendId - + " created space id " + space.id() + " but expected " + spaceId); - } - return space; - } - - private static void ensureBackendInitialized(@Nonnull BackendId backendId, - @Nonnull PhysicsBackend backend) { - Object initLock; - synchronized (REGISTRY_LOCK) { - if (INITIALIZED_BACKENDS.contains(backendId)) { - LOGGER.log(Level.FINEST, - "Physics backend " + backendId + " already initialized"); - return; - } - initLock = BACKEND_INIT_LOCKS.computeIfAbsent(backendId, ignored -> new Object()); - } - - synchronized (initLock) { - synchronized (REGISTRY_LOCK) { - if (INITIALIZED_BACKENDS.contains(backendId)) { - LOGGER.log(Level.FINEST, - "Physics backend " + backendId + " already initialized"); - return; - } - } - - LOGGER.log(Level.FINE, - "Initializing physics backend " + backendId + " on thread " - + Thread.currentThread().getName()); - backend.init(); - - synchronized (REGISTRY_LOCK) { - INITIALIZED_BACKENDS.add(backendId); - } - LOGGER.log(Level.INFO, "Initialized physics backend " + backendId); - } - } - private static void ensureRuntimeProviderInitialized(@Nonnull BackendId backendId, @Nonnull PhysicsBackendRuntimeProvider provider) { Object initLock; diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java deleted file mode 100644 index 45cd0fdb..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBatch.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Immutable post-step backend event batch drained by core after a backend step completes. - */ -@Deprecated(forRemoval = true) -public record PhysicsBackendEventBatch(@Nonnull List events, - int droppedEventCount) { - - private static final PhysicsBackendEventBatch EMPTY = new PhysicsBackendEventBatch(List.of(), 0); - - public PhysicsBackendEventBatch { - events = List.copyOf(Objects.requireNonNull(events, "events")); - droppedEventCount = Math.max(0, droppedEventCount); - } - - @Nonnull - public static PhysicsBackendEventBatch empty() { - return EMPTY; - } - - public int size() { - return events.size(); - } - - public boolean isEmpty() { - return events.isEmpty() && droppedEventCount == 0; - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java deleted file mode 100644 index 46cf428d..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBuffer.java +++ /dev/null @@ -1,70 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Bounded in-memory backend event buffer for one or more completed backend steps. - */ -@Deprecated(forRemoval = true) -public final class PhysicsBackendEventBuffer implements PhysicsBackendEventSink { - - public static final int DEFAULT_CAPACITY = 1024; - - private final int capacity; - private final ArrayList events; - private int droppedEventCount; - - public PhysicsBackendEventBuffer() { - this(DEFAULT_CAPACITY); - } - - public PhysicsBackendEventBuffer(int capacity) { - this.capacity = Math.max(0, capacity); - this.events = new ArrayList<>(Math.min(this.capacity, DEFAULT_CAPACITY)); - } - - @Override - public int capacity() { - return capacity; - } - - @Override - public int size() { - return events.size(); - } - - @Override - public int droppedEventCount() { - return droppedEventCount; - } - - @Override - public boolean offer(@Nonnull PhysicsBackendEvent event) { - Objects.requireNonNull(event, "event"); - if (events.size() >= capacity) { - droppedEventCount++; - return false; - } - events.add(event); - return true; - } - - @Nonnull - public PhysicsBackendEventBatch drain() { - if (events.isEmpty() && droppedEventCount == 0) { - return PhysicsBackendEventBatch.empty(); - } - PhysicsBackendEventBatch batch = - new PhysicsBackendEventBatch(List.copyOf(events), droppedEventCount); - clear(); - return batch; - } - - public void clear() { - events.clear(); - droppedEventCount = 0; - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java deleted file mode 100644 index 661267be..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -package dev.hytalemodding.impulse.api.runtime.legacy; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import java.util.Objects; -import java.util.logging.Level; -import javax.annotation.Nonnull; - -/** - * Runtime-provider adapter for legacy object-based backends. - */ -@Deprecated(forRemoval = true) -public final class LegacyPhysicsBackendRuntimeProvider implements PhysicsBackendRuntimeProvider { - - @Nonnull - private final PhysicsBackend backend; - - public LegacyPhysicsBackendRuntimeProvider(@Nonnull PhysicsBackend backend) { - this.backend = Objects.requireNonNull(backend, "backend"); - } - - @Nonnull - @Override - public BackendId getId() { - return backend.getId(); - } - - @Override - public void init() { - backend.init(); - } - - @Override - public void setInternalLoggingLevel(@Nonnull Level level) { - backend.setInternalLoggingLevel(level); - } - - @Nonnull - @Override - public PhysicsBackendRuntime createRuntime() { - return new LegacyPhysicsBackendRuntime(backend); - } -} From 8bb5c2667564aff520ae409f24ec66c2f0076707 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:20:22 +0200 Subject: [PATCH 460/534] test(api): remove legacy event buffer coverage Signed-off-by: Blovien --- .../api/PhysicsBackendEventBufferTest.java | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java deleted file mode 100644 index 277f80e3..00000000 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBackendEventBufferTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsBackendEventBufferTest { - - @Test - void buffersCopiedContactEventsUntilCapacityAndReportsDrops() { - FakePhysicsBackend backend = new FakePhysicsBackend("test:event-buffer"); - PhysicsSpace space = backend.createSpace(); - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - Vector3f pointOnA = new Vector3f(1.0f, 2.0f, 3.0f); - Vector3f pointOnB = new Vector3f(4.0f, 5.0f, 6.0f); - Vector3f normalOnB = new Vector3f(0.0f, 1.0f, 0.0f); - PhysicsBackendEventBuffer buffer = new PhysicsBackendEventBuffer(1); - - assertTrue(buffer.contact(PhysicsContactPhase.OBSERVED, - first, - second, - pointOnA, - pointOnB, - normalOnB, - -0.125f, - 2.5f)); - pointOnA.set(7.0f, 8.0f, 9.0f); - assertFalse(buffer.contact(PhysicsContactPhase.OBSERVED, - first, - second, - pointOnA, - pointOnB, - normalOnB, - -0.25f, - 3.5f)); - - PhysicsBackendEventBatch batch = buffer.drain(); - - assertEquals(1, batch.size()); - assertEquals(1, batch.droppedEventCount()); - PhysicsBackendContactEvent event = (PhysicsBackendContactEvent) batch.events().getFirst(); - assertEquals(PhysicsBackendEventKind.CONTACT_OBSERVED, event.kind()); - assertEquals(PhysicsContactPhase.OBSERVED, event.phase()); - assertEquals(first, event.bodyA()); - assertEquals(second, event.bodyB()); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), event.pointOnA()); - assertEquals(new Vector3f(4.0f, 5.0f, 6.0f), event.pointOnB()); - assertEquals(new Vector3f(0.0f, 1.0f, 0.0f), event.normalOnB()); - assertEquals(-0.125f, event.distance()); - assertEquals(2.5f, event.impulse()); - assertTrue(buffer.drain().isEmpty()); - } -} From 43d6299ff52d71216704e61d9d916ca141fa36f6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:23:17 +0200 Subject: [PATCH 461/534] refactor(core): require refs for physics entity builders Signed-off-by: Blovien --- .../crucible/ImpulseApiCrucibleTests.java | 7 ++- .../crucible/ImpulseLiveCrucibleTests.java | 6 ++- .../crucible/PhysicsStoreCrucibleSupport.java | 15 ++++++- .../physicsstore/PhysicsBodyEntities.java | 43 +++++-------------- .../physicsstore/PhysicsJointEntities.java | 42 +++++++++--------- 5 files changed, 55 insertions(+), 58 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 7a8a95c2..9d243eee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -301,8 +301,11 @@ private static CompletionStage waitApproxTicksOnWorld(@Nonnull CrucibleCon private static Ref addCrucibleBox(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull UUID bodyUuid) { - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId); - BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody(spaceUuid, + Ref spaceRef = PhysicsSpaces.resolveRef(store, spaceId); + if (spaceRef == null) { + throw new IllegalStateException("No PhysicsStore space ref for id=" + spaceId.value()); + } + BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody(spaceRef, bodyUuid, new Vector3f(0.0f, 5.0f, 0.0f), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 27547b66..93b30fc4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -140,8 +140,12 @@ private static void submitLiveBody(Store store, UUID bodyUuid, Vector3d visualPosition) { PhysicsThreading.requireWorldThread(store, "add Crucible live PhysicsStore body entity"); + Ref spaceRef = PhysicsSpaces.resolveRef(store, spaceId); + if (spaceRef == null) { + throw new IllegalStateException("No PhysicsStore space ref for id=" + spaceId.value()); + } BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody( - PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), + spaceRef, bodyUuid, new Vector3f((float) visualPosition.x, (float) visualPosition.y, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index f1aa9d05..dae8a47b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -9,12 +9,12 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -52,8 +52,9 @@ static Ref addBody(@Nonnull Store store, @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { PhysicsThreading.requireWorldThread(store, "add Crucible PhysicsStore body entity"); + Ref spaceRef = requireSpaceRef(store, spaceId); BodyEntityDescriptor descriptor = PhysicsBodyEntities.body( - PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId), + spaceRef, bodyUuid, bodyCenter, shape, @@ -73,4 +74,14 @@ static Ref addBody(@Nonnull Store store, descriptor.material(), descriptor.filter()), AddReason.SPAWN); } + + @Nonnull + private static Ref requireSpaceRef(@Nonnull Store store, + @Nonnull SpaceId spaceId) { + Ref spaceRef = PhysicsSpaces.resolveRef(store, spaceId); + if (spaceRef == null) { + throw new IllegalStateException("No PhysicsStore space ref for id=" + spaceId.value()); + } + return spaceRef; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java index 030d2f49..ba38ee79 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java @@ -31,7 +31,7 @@ private PhysicsBodyEntities() { } @Nonnull - public static BodyEntityDescriptor dynamicBody(@Nonnull UUID spaceUuid, + public static BodyEntityDescriptor dynamicBody(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -39,7 +39,7 @@ public static BodyEntityDescriptor dynamicBody(@Nonnull UUID spaceUuid, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return body(spaceUuid, + return body(spaceRef, bodyUuid, bodyCenter, shape, @@ -52,28 +52,32 @@ public static BodyEntityDescriptor dynamicBody(@Nonnull UUID spaceUuid, } @Nonnull - public static BodyEntityDescriptor dynamicBody(@Nonnull Ref spaceRef, + public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, + @Nonnull PhysicsBodyType bodyType, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity, + @Nonnull PhysicsBodyKind kind, @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return body(spaceRef, + BodyEntityDescriptor descriptor = bodyWithSpaceUuid(PhysicsEntityRefs.entityUuid(spaceRef), bodyUuid, bodyCenter, shape, - PhysicsBodyType.DYNAMIC, + bodyType, mass, settings, linearVelocity, - PhysicsBodyKind.BODY, + kind, persistenceMode); + descriptor.body().setSpaceRef(spaceRef); + return descriptor; } @Nonnull - public static BodyEntityDescriptor body(@Nonnull UUID spaceUuid, + private static BodyEntityDescriptor bodyWithSpaceUuid(@Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -123,31 +127,6 @@ public static BodyEntityDescriptor body(@Nonnull UUID spaceUuid, collisionFilter(settings)); } - @Nonnull - public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, - @Nonnull UUID bodyUuid, - @Nonnull Vector3f bodyCenter, - @Nonnull PhysicsShapeSpec shape, - @Nonnull PhysicsBodyType bodyType, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - BodyEntityDescriptor descriptor = body(PhysicsEntityRefs.entityUuid(spaceRef), - bodyUuid, - bodyCenter, - shape, - bodyType, - mass, - settings, - linearVelocity, - kind, - persistenceMode); - descriptor.body().setSpaceRef(spaceRef); - return descriptor; - } - @Nonnull private static TargetComponent initialTarget(@Nonnull Vector3f bodyCenter, @Nullable Vector3f linearVelocity) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java index 64cada70..fab444a0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java @@ -17,26 +17,6 @@ public final class PhysicsJointEntities { private PhysicsJointEntities() { } - @Nonnull - public static JointComponent joint(@Nonnull UUID spaceUuid, - @Nonnull UUID bodyAUuid, - @Nonnull UUID bodyBUuid, - @Nonnull JointType type, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - JointComponent joint = new JointComponent(); - joint.setSpaceUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); - joint.setBodyAUuid(Objects.requireNonNull(bodyAUuid, "bodyAUuid")); - joint.setBodyBUuid(Objects.requireNonNull(bodyBUuid, "bodyBUuid")); - joint.setType(Objects.requireNonNull(type, "type")); - joint.setAnchorA(anchorA); - joint.setAnchorB(anchorB); - joint.setAxis(axis); - joint.setEnabled(true); - return joint; - } - @Nonnull public static JointComponent joint(@Nonnull Ref spaceRef, @Nonnull Ref bodyARef, @@ -47,7 +27,7 @@ public static JointComponent joint(@Nonnull Ref spaceRef, @Nonnull Vector3f axis) { PhysicsEntityRefs.requireSameStore(spaceRef, bodyARef, "bodyARef"); PhysicsEntityRefs.requireSameStore(spaceRef, bodyBRef, "bodyBRef"); - JointComponent joint = joint(PhysicsEntityRefs.entityUuid(spaceRef), + JointComponent joint = jointWithUuids(PhysicsEntityRefs.entityUuid(spaceRef), PhysicsEntityRefs.entityUuid(bodyARef), PhysicsEntityRefs.entityUuid(bodyBRef), type, @@ -59,4 +39,24 @@ public static JointComponent joint(@Nonnull Ref spaceRef, joint.setBodyBRef(bodyBRef); return joint; } + + @Nonnull + private static JointComponent jointWithUuids(@Nonnull UUID spaceUuid, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid, + @Nonnull JointType type, + @Nonnull Vector3f anchorA, + @Nonnull Vector3f anchorB, + @Nonnull Vector3f axis) { + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); + joint.setBodyAUuid(Objects.requireNonNull(bodyAUuid, "bodyAUuid")); + joint.setBodyBUuid(Objects.requireNonNull(bodyBUuid, "bodyBUuid")); + joint.setType(Objects.requireNonNull(type, "type")); + joint.setAnchorA(anchorA); + joint.setAnchorB(anchorB); + joint.setAxis(axis); + joint.setEnabled(true); + return joint; + } } From b61388399bce4ebe18e2dc13b4d74c3ad1409ea9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 11:24:33 +0200 Subject: [PATCH 462/534] refactor(core): require refs for physicschunk collision Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkCollision.java | 82 ------------------- 1 file changed, 82 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index cbe657ef..bb6db736 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; @@ -15,7 +14,6 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Objects; @@ -35,23 +33,6 @@ public static boolean isSubPluginEnabled() { return PhysicsChunkLifecycle.isEnabled(); } - @Nonnull - public static PhysicsChunkCollisionBuildStats rebuildAround(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - requireEnabled(); - Store checkedStore = requireMatchingWorldThread(world, - store, - "rebuild PhysicsChunk collision"); - return rebuildAroundChecked(world, - checkedStore, - requireSpaceRef(checkedStore, spaceId), - center, - radius); - } - @Nonnull public static PhysicsChunkCollisionBuildStats rebuildAround(@Nonnull World world, @Nonnull Store store, @@ -86,23 +67,6 @@ private static PhysicsChunkCollisionBuildStats rebuildAroundChecked(@Nonnull Wor stats.buildStats().removedBodies() + removed); } - @Nonnull - public static PhysicsChunkCollisionBuildStats refreshAround(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3d center, - int radius) { - requireEnabled(); - Store checkedStore = requireMatchingWorldThread(world, - store, - "refresh PhysicsChunk collision"); - return refreshAroundChecked(world, - checkedStore, - requireSpaceRef(checkedStore, spaceId), - center, - radius); - } - @Nonnull public static PhysicsChunkCollisionBuildStats refreshAround(@Nonnull World world, @Nonnull Store store, @@ -133,25 +97,6 @@ private static PhysicsChunkCollisionBuildStats refreshAroundChecked(@Nonnull Wor settings.buildOptions()); } - @Nonnull - public static PhysicsChunkCollisionPrewarmStats ensureAround(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Iterable centers, - int radius, - long tick) { - requireEnabled(); - Store checkedStore = requireMatchingWorldThread(world, - store, - "ensure PhysicsChunk collision"); - return ensureAroundChecked(world, - checkedStore, - requireSpaceRef(checkedStore, spaceId), - centers, - radius, - tick); - } - @Nonnull public static PhysicsChunkCollisionPrewarmStats ensureAround(@Nonnull World world, @Nonnull Store store, @@ -184,17 +129,6 @@ private static PhysicsChunkCollisionPrewarmStats ensureAroundChecked(@Nonnull Wo settings.buildOptions()); } - public static int clearSpace(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - Store checkedStore = requireMatchingWorldThread(world, - store, - "clear PhysicsChunk collision"); - return clearSpaceChunkCollisionRows(world, - checkedStore, - requireSpaceUuid(checkedStore, requireSpaceRef(checkedStore, spaceId))); - } - public static int clearSpace(@Nonnull World world, @Nonnull Store store, @Nonnull Ref spaceRef) { @@ -258,22 +192,6 @@ private static PhysicsChunkSpaceSettings requireSettings(@Nonnull Store requireSpaceRef(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - SpaceId checkedSpaceId = Objects.requireNonNull(spaceId, "spaceId"); - Ref ref = PhysicsSpaces.resolveRef(store, checkedSpaceId); - if (ref != null) { - return ref; - } - if (PhysicsSpaces.hasSpace(store, checkedSpaceId)) { - throw new IllegalStateException("PhysicsStore space id=" + checkedSpaceId.value() - + " is not bound yet"); - } - throw new IllegalArgumentException("PhysicsStore space id=" + checkedSpaceId.value() - + " does not exist"); - } - @Nonnull private static Ref requireValidSpaceRef(@Nonnull Store store, @Nonnull Ref spaceRef) { From 364b4aa76ed0964245b5d7b03da862f57a0bba53 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 12:04:01 +0200 Subject: [PATCH 463/534] refactor(core): require refs for live physics reads Signed-off-by: Blovien --- .../crucible/ImpulseApiCrucibleTests.java | 18 +- .../physicsstore/PhysicsBackendAccess.java | 38 ---- .../physicsstore/PhysicsDiagnostics.java | 175 --------------- .../plugin/physicsstore/PhysicsRaycasts.java | 209 ------------------ 4 files changed, 9 insertions(+), 431 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 9d243eee..8141d7a7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -229,7 +229,7 @@ private static CompletionStage populatedBodyCleanup( return createPopulatedBodyCleanupState(context) .thenCompose(state -> waitApproxTicksOnWorld(context, 4) .thenCompose(_ -> removeBodyEntityAndWait(context, state.store(), state.bodyRef())) - .thenCompose(_ -> PhysicsDiagnostics.bodyCountAsync(state.store(), state.spaceId())) + .thenCompose(_ -> PhysicsDiagnostics.bodyCountAsync(state.store(), state.spaceRef())) .thenCompose(bodyCount -> PhysicsThreading.callWhenBackendIdleOnWorldThread( state.world(), "check Crucible body cleanup", @@ -252,11 +252,14 @@ private static CompletionStage createPopulatedBodyCle @Nonnull CrucibleContext context) { return callWhenPhysicsStoreIdle(context, "create Crucible body cleanup state", world -> { Store store = physicsStore(world); - SpaceId spaceId = PhysicsSpaces.create(store, + SpaceId spaceId = SpaceId.next(); + Ref spaceRef = PhysicsSpaces.create(store, + UUID.randomUUID(), + spaceId, CrucibleBackends.requireBackendId(), PhysicsSpaceSettings.defaults()); - Ref bodyRef = addCrucibleBox(store, spaceId, UUID.randomUUID()); - return new PopulatedBodyCleanupState(world, store, spaceId, bodyRef); + Ref bodyRef = addCrucibleBox(store, spaceRef, UUID.randomUUID()); + return new PopulatedBodyCleanupState(world, store, spaceId, spaceRef, bodyRef); }); } @@ -299,12 +302,8 @@ private static CompletionStage waitApproxTicksOnWorld(@Nonnull CrucibleCon @Nonnull private static Ref addCrucibleBox(@Nonnull Store store, - @Nonnull SpaceId spaceId, + @Nonnull Ref spaceRef, @Nonnull UUID bodyUuid) { - Ref spaceRef = PhysicsSpaces.resolveRef(store, spaceId); - if (spaceRef == null) { - throw new IllegalStateException("No PhysicsStore space ref for id=" + spaceId.value()); - } BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody(spaceRef, bodyUuid, new Vector3f(0.0f, 5.0f, 0.0f), @@ -414,6 +413,7 @@ private static Store physicsStore(@Nonnull World world) { private record PopulatedBodyCleanupState(@Nonnull World world, @Nonnull Store store, @Nonnull SpaceId spaceId, + @Nonnull Ref spaceRef, @Nonnull Ref bodyRef) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java index 331bbcb5..0bcf1956 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java @@ -8,7 +8,6 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -25,24 +24,6 @@ final class PhysicsBackendAccess { private PhysicsBackendAccess() { } - @Nullable - static SpaceContext space(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - UUID spaceUuid = compatibility.getSpaceUuid(spaceId); - return spaceUuid != null ? space(store, spaceUuid) : null; - } - - @Nullable - static SpaceContext space(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - return spaceRef != null && spaceRef.isValid() ? space(runtime, spaceRef) : null; - } - @Nullable static SpaceContext space(@Nonnull Store store, @Nonnull Ref spaceRef) { @@ -69,25 +50,6 @@ static SpaceContext space(@Nonnull PhysicsRuntimeResource runtime, return new SpaceContext(spaceUuid, backendId, spaceHandle, backendRuntime); } - @Nonnull - static SpaceContext requireSpace(@Nonnull Store store, @Nonnull SpaceId spaceId) { - SpaceContext space = space(store, spaceId); - if (space == null) { - throw new IllegalArgumentException("Physics space id=" + spaceId + " is not registered"); - } - return space; - } - - @Nonnull - static SpaceContext requireSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { - SpaceContext space = space(store, spaceUuid); - if (space == null) { - throw new IllegalArgumentException("Physics space uuid=" + spaceUuid - + " is not registered"); - } - return space; - } - @Nonnull static SpaceContext requireSpace(@Nonnull Store store, @Nonnull Ref spaceRef) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java index de0d665c..de90164b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.UUID; import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; @@ -28,18 +27,6 @@ public final class PhysicsDiagnostics { private PhysicsDiagnostics() { } - public static int bodyCount(@Nonnull Store store, @Nonnull SpaceId spaceId) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); - return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; - } - - public static int bodyCount(@Nonnull Store store, @Nonnull UUID spaceUuid) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); - return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; - } - public static int bodyCount(@Nonnull Store store, @Nonnull Ref spaceRef) { PhysicsBackendAccess.SpaceContext space = @@ -47,42 +34,6 @@ public static int bodyCount(@Nonnull Store store, return space != null ? space.backendRuntime().bodyCount(space.spaceHandle().value()) : 0; } - @Nonnull - public static CompletionStage bodyCountAsync(@Nonnull World world, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore body count read", - physics -> bodyCount(physics, spaceId)); - } - - @Nonnull - public static CompletionStage bodyCountAsync(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore body count read", - physics -> bodyCount(physics, spaceId)); - } - - @Nonnull - public static CompletionStage bodyCountAsync(@Nonnull World world, - @Nonnull UUID spaceUuid) { - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore body count read", - physics -> bodyCount(physics, spaceUuid)); - } - - @Nonnull - public static CompletionStage bodyCountAsync(@Nonnull Store store, - @Nonnull UUID spaceUuid) { - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore body count read", - physics -> bodyCount(physics, spaceUuid)); - } - @Nonnull public static CompletionStage bodyCountAsync(@Nonnull World world, @Nonnull Ref spaceRef) { @@ -150,14 +101,6 @@ public static CompletionStage ccdSupportedAsync(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.requireSpace(store, Objects.requireNonNull(spaceId, "spaceId")); - return solverCapability(spaceId, space); - } - @Nonnull public static SolverCapabilitySummary solverCapability(@Nonnull Store store, @Nonnull Ref spaceRef) { @@ -174,36 +117,6 @@ public static SolverCapabilitySummary solverCapability(@Nonnull Store solverCapabilityAsync( - @Nonnull World world, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore solver capability read", - physics -> solverCapability(physics, spaceId)); - } - - @Nonnull - public static CompletionStage solverCapabilityAsync( - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore solver capability read", - physics -> solverCapability(physics, spaceId)); - } - - @Nonnull - public static CompletionStage solverCapabilityAsync( - @Nonnull World world, - @Nonnull UUID spaceUuid) { - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore solver capability read", - physics -> solverCapability(physics, spaceUuid)); - } - @Nonnull public static CompletionStage solverCapabilityAsync( @Nonnull World world, @@ -224,30 +137,6 @@ public static CompletionStage solverCapabilityAsync( physics -> solverCapability(physics, spaceRef)); } - @Nonnull - public static CompletionStage solverCapabilityAsync( - @Nonnull Store store, - @Nonnull UUID spaceUuid) { - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore solver capability read", - physics -> solverCapability(physics, spaceUuid)); - } - - @Nonnull - public static SolverCapabilitySummary solverCapability(@Nonnull Store store, - @Nonnull UUID spaceUuid) { - PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - SpaceId spaceId = compatibility.getSpaceId(Objects.requireNonNull(spaceUuid, "spaceUuid")); - if (spaceId == null) { - throw new IllegalArgumentException("Physics space uuid=" + spaceUuid - + " has no compatibility SpaceId"); - } - return solverCapability(spaceId, PhysicsBackendAccess.requireSpace(store, spaceUuid)); - } - @Nonnull public static List spaceSummaries(@Nonnull Store store) { PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); @@ -280,19 +169,6 @@ public static CompletionStage> spaceSummariesAsync( PhysicsDiagnostics::spaceSummaries); } - @Nonnull - public static List spaceSummaries(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); - return space != null - ? List.of(PhysicsBackendAccess.summary(compatibility, space)) - : List.of(); - } - @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull Ref spaceRef) { @@ -306,15 +182,6 @@ public static List spaceSummaries(@Nonnull Store sto : List.of(); } - @Nonnull - public static CompletionStage> spaceSummariesAsync(@Nonnull World world, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore space summary read", - physics -> spaceSummaries(physics, spaceId)); - } - @Nonnull public static CompletionStage> spaceSummariesAsync(@Nonnull World world, @Nonnull Ref spaceRef) { @@ -334,48 +201,6 @@ public static CompletionStage> spaceSummariesAsync( physics -> spaceSummaries(physics, spaceRef)); } - @Nonnull - public static CompletionStage> spaceSummariesAsync( - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore space summary read", - physics -> spaceSummaries(physics, spaceId)); - } - - @Nonnull - public static List spaceSummaries(@Nonnull Store store, - @Nonnull UUID spaceUuid) { - PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); - return space != null && compatibility.getSpaceId(space.spaceUuid()) != null - ? List.of(PhysicsBackendAccess.summary(compatibility, space)) - : List.of(); - } - - @Nonnull - public static CompletionStage> spaceSummariesAsync(@Nonnull World world, - @Nonnull UUID spaceUuid) { - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore space summary read", - physics -> spaceSummaries(physics, spaceUuid)); - } - - @Nonnull - public static CompletionStage> spaceSummariesAsync( - @Nonnull Store store, - @Nonnull UUID spaceUuid) { - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore space summary read", - physics -> spaceSummaries(physics, spaceUuid)); - } - @Nonnull public static List unsupportedCcdSpaces(@Nonnull Store store) { PhysicsThreading.requireBackendIdle(store, "read live PhysicsStore backend state"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java index 7009f7d0..96d3a587 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; @@ -14,7 +13,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.UUID; import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; import org.joml.Vector3f; @@ -32,26 +30,6 @@ public final class PhysicsRaycasts { private PhysicsRaycasts() { } - @Nonnull - public static Optional closest(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); - return space != null ? closest(store, space, from, to) : Optional.empty(); - } - - @Nonnull - public static Optional closest(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); - return space != null ? closest(store, space, from, to) : Optional.empty(); - } - @Nonnull public static Optional closest(@Nonnull Store store, @Nonnull Ref spaceRef, @@ -62,26 +40,6 @@ public static Optional closest(@Nonnull Store stor return space != null ? closest(store, space, from, to) : Optional.empty(); } - @Nonnull - public static List all(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); - return space != null ? all(store, space, from, to) : List.of(); - } - - @Nonnull - public static List all(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); - return space != null ? all(store, space, from, to) : List.of(); - } - @Nonnull public static List all(@Nonnull Store store, @Nonnull Ref spaceRef, @@ -92,24 +50,6 @@ public static List all(@Nonnull Store store, return space != null ? all(store, space, from, to) : List.of(); } - @Nonnull - public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull List rays) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceId, "spaceId")); - return closestBatch(store, space, rays); - } - - @Nonnull - public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull List rays) { - PhysicsBackendAccess.SpaceContext space = - PhysicsBackendAccess.space(store, Objects.requireNonNull(spaceUuid, "spaceUuid")); - return closestBatch(store, space, rays); - } - @Nonnull public static RaycastClosestBatchResult closestBatch(@Nonnull Store store, @Nonnull Ref spaceRef, @@ -119,58 +59,6 @@ public static RaycastClosestBatchResult closestBatch(@Nonnull Store> closestAsync(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore closest raycast read", - physics -> closest(physics, spaceId, copiedFrom, copiedTo)); - } - - @Nonnull - public static CompletionStage> closestAsync( - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore closest raycast read", - physics -> closest(physics, spaceId, copiedFrom, copiedTo)); - } - - @Nonnull - public static CompletionStage> closestAsync(@Nonnull World world, - @Nonnull UUID spaceUuid, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore closest raycast read", - physics -> closest(physics, spaceUuid, copiedFrom, copiedTo)); - } - - @Nonnull - public static CompletionStage> closestAsync( - @Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore closest raycast read", - physics -> closest(physics, spaceUuid, copiedFrom, copiedTo)); - } - @Nonnull public static CompletionStage> closestAsync(@Nonnull World world, @Nonnull Ref spaceRef, @@ -198,57 +86,6 @@ public static CompletionStage> closestAsync( physics -> closest(physics, spaceRef, copiedFrom, copiedTo)); } - @Nonnull - public static CompletionStage> allAsync(@Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore all raycast read", - physics -> all(physics, spaceId, copiedFrom, copiedTo)); - } - - @Nonnull - public static CompletionStage> allAsync(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore all raycast read", - physics -> all(physics, spaceId, copiedFrom, copiedTo)); - } - - @Nonnull - public static CompletionStage> allAsync(@Nonnull World world, - @Nonnull UUID spaceUuid, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore all raycast read", - physics -> all(physics, spaceUuid, copiedFrom, copiedTo)); - } - - @Nonnull - public static CompletionStage> allAsync( - @Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull Vector3f from, - @Nonnull Vector3f to) { - Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); - Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore all raycast read", - physics -> all(physics, spaceUuid, copiedFrom, copiedTo)); - } - @Nonnull public static CompletionStage> allAsync(@Nonnull World world, @Nonnull Ref spaceRef, @@ -275,52 +112,6 @@ public static CompletionStage> allAsync(@Nonnull Store all(physics, spaceRef, copiedFrom, copiedTo)); } - @Nonnull - public static CompletionStage closestBatchAsync( - @Nonnull World world, - @Nonnull SpaceId spaceId, - @Nonnull List rays) { - List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore batch raycast read", - physics -> closestBatch(physics, spaceId, copied)); - } - - @Nonnull - public static CompletionStage closestBatchAsync( - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull List rays) { - List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore batch raycast read", - physics -> closestBatch(physics, spaceId, copied)); - } - - @Nonnull - public static CompletionStage closestBatchAsync( - @Nonnull World world, - @Nonnull UUID spaceUuid, - @Nonnull List rays) { - List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(world, - "queue PhysicsStore batch raycast read", - physics -> closestBatch(physics, spaceUuid, copied)); - } - - @Nonnull - public static CompletionStage closestBatchAsync( - @Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull List rays) { - List copied = List.copyOf(Objects.requireNonNull(rays, "rays")); - Objects.requireNonNull(spaceUuid, "spaceUuid"); - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore batch raycast read", - physics -> closestBatch(physics, spaceUuid, copied)); - } - @Nonnull public static CompletionStage closestBatchAsync( @Nonnull World world, From 7e3816d1e8abf5c8a41f3fb74572fc8cea66137d Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 12:15:28 +0200 Subject: [PATCH 464/534] refactor(core): remove dead sync helpers Signed-off-by: Blovien --- .../systems/sync/PhysicsSyncSystem.java | 177 ------------------ 1 file changed, 177 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 3980996f..8bcbc26e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -16,28 +16,21 @@ import com.hypixel.hytale.server.core.modules.entity.system.UpdateLocationSystems; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import java.util.List; import java.util.Objects; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; -import org.joml.Vector3d; import org.joml.Vector3f; /** @@ -65,22 +58,6 @@ public class PhysicsSyncSystem extends EntityTickingSystem { new SystemDependency<>(Order.BEFORE, UpdateLocationSystems.TickingSystem.class) ); - /* - * Low-speed uncontrolled dynamic bodies get a wider visual deadzone and a - * slower keepalive. Controlled bodies bypass this classification so player - * input stays responsive. - */ - private static final float LOW_SPEED_LINEAR_THRESHOLD_SQUARED = 0.2f * 0.2f; - private static final float LOW_SPEED_ANGULAR_THRESHOLD_SQUARED = 0.5f * 0.5f; - private static final float MIN_PREDICTED_ANGULAR_SPEED = 1.0e-4f; - private static final float MIN_SMOOTHING_ALPHA = 0.05f; - private static final float MAX_SMOOTHING_TELEPORT_DISTANCE_SQUARED = 4.0f * 4.0f; - - @Nonnull - private final ThreadLocal> playerInterests = - ThreadLocal.withInitial(List::of); - @Nonnull - private final ThreadLocal syncNanos = ThreadLocal.withInitial(() -> 0L); @Nonnull private final ThreadLocal physicsStoreSnapshots = new ThreadLocal<>(); @@ -111,8 +88,6 @@ public boolean isParallel(int archetypeChunkSize, int taskCount) { @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { - playerInterests.set(VisualInterestCollector.collectSyncInterests(store)); - syncNanos.set(System.nanoTime()); PhysicsRuntimeProfilingResource profiling = store.getResource( PhysicsRuntimeProfilingResource.getResourceType()); PhysicsRuntimeProfilingResource.SyncCollector collector = profiling.isEnabled() @@ -125,8 +100,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { if (collector != null) { profiling.finishSyncSample(collector, System.nanoTime() - startNanos); } - playerInterests.remove(); - syncNanos.remove(); physicsStoreSnapshots.remove(); } } @@ -164,7 +137,6 @@ public void tick(float dt, } return; } - clearMissingPhysicsStoreAttachment(entityRef, attachment, commandBuffer); } @Nullable @@ -237,137 +209,6 @@ private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transf transform.getRotation().set(scratch.euler.x, scratch.euler.y, scratch.euler.z); } - private static void clearMissingPhysicsStoreAttachment(@Nonnull Ref entityRef, - @Nonnull BodyAttachmentComponent attachment, - @Nonnull CommandBuffer commandBuffer) { - // PhysicsStore snapshot publication is intentionally one completed frame behind row - // mutation. Absence from the latest frame is not enough evidence that the body entity is gone. - } - - private static float distance(@Nonnull Vector3d from, @Nonnull Vector3f to) { - double dx = from.x - to.x; - double dy = from.y - to.y; - double dz = from.z - to.z; - double distanceSquared = dx * dx + dy * dy + dz * dz; - if (!Double.isFinite(distanceSquared)) { - return Float.NaN; - } - return (float) Math.sqrt(distanceSquared); - } - - private void applyVisualPose(@Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, - @Nonnull BodyAttachmentComponent attachment, - @Nonnull Scratch scratch) { - PhysicsVisualPoseMath.visualPositionFromBodyPose(scratch.position, - scratch.rotation, - attachment.resolveVisualOriginOffsetY(snapshot.centerOfMassOffsetY()), - attachment.getLocalPositionOffset(), - scratch.visualPosition, - scratch.worldOffset); - scratch.visualRotation.set(scratch.rotation); - - scratch.visualRotation.mul(attachment.getLocalRotationOffset()); - } - - private static boolean shouldSmoothVisual(@Nullable PhysicsSpaceSettings settings, - @Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, - boolean controlled, - @Nonnull PhysicsSyncPolicy.SyncRangeTier rangeTier, - @Nonnull PhysicsBodyRuntimeState.BodySyncState syncState, - @Nonnull PhysicsSyncPolicy.SyncDecision decision) { - return settings != null - && settings.getVisualSyncSettings().isVisualSnapshotSmoothingEnabled() - && !controlled - && rangeTier == PhysicsSyncPolicy.SyncRangeTier.NEAR - && snapshot.isDynamic() - && !snapshot.sleeping() - && syncState.isInitialized() - && decision != PhysicsSyncPolicy.SyncDecision.INITIAL - && decision != PhysicsSyncPolicy.SyncDecision.TRANSITION; - } - - private static void applyVisualSmoothing(@Nonnull PhysicsSpaceSettings settings, - float dt, - @Nonnull PhysicsBodyRuntimeState.BodySyncState syncState, - @Nonnull Scratch scratch) { - if (scratch.visualPosition.distanceSquared(syncState.getLastSyncedPosition()) - > MAX_SMOOTHING_TELEPORT_DISTANCE_SQUARED) { - return; - } - float alpha = smoothingAlpha(settings, dt); - scratch.smoothingTargetPosition.set(scratch.visualPosition); - scratch.visualPosition.set(syncState.getLastSyncedPosition()) - .lerp(scratch.smoothingTargetPosition, alpha); - - scratch.smoothingTargetRotation.set(scratch.visualRotation); - scratch.visualRotation.set(syncState.getLastSyncedRotation()) - .slerp(scratch.smoothingTargetRotation, alpha) - .normalize(); - } - - static float smoothingAlpha(@Nonnull PhysicsSpaceSettings settings, float dt) { - if (!Float.isFinite(dt) || dt <= 0.0f) { - return 1.0f; - } - return Math.clamp(dt * settings.getVisualSyncSettings().getVisualSnapshotSmoothingRate(), - MIN_SMOOTHING_ALPHA, 1.0f); - } - - private static void applySnapshotPrediction(@Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, - float predictionSeconds, - @Nonnull Scratch scratch) { - if (predictionSeconds <= 0.0f || !snapshot.isDynamic() || snapshot.sleeping()) { - return; - } - - snapshot.copyLinearVelocityTo(scratch.linearVelocity); - if (isFinite(scratch.linearVelocity)) { - scratch.position.fma(predictionSeconds, scratch.linearVelocity); - } - - snapshot.copyAngularVelocityTo(scratch.angularVelocity); - if (!isFinite(scratch.angularVelocity)) { - return; - } - float angularSpeed = scratch.angularVelocity.length(); - if (angularSpeed <= MIN_PREDICTED_ANGULAR_SPEED) { - return; - } - float inverseAngularSpeed = 1.0f / angularSpeed; - scratch.predictedRotation.rotationAxis(angularSpeed * predictionSeconds, - scratch.angularVelocity.x * inverseAngularSpeed, - scratch.angularVelocity.y * inverseAngularSpeed, - scratch.angularVelocity.z * inverseAngularSpeed); - scratch.rotation.mul(scratch.predictedRotation).normalize(); - } - - private static boolean isFinite(@Nonnull Vector3f vector) { - return Float.isFinite(vector.x) - && Float.isFinite(vector.y) - && Float.isFinite(vector.z); - } - - @Nullable - private static PhysicsSpaceSettings resolveSpaceSettings(@Nonnull PhysicsWorldRuntimeResource resource, - @Nullable SpaceId spaceId) { - if (spaceId != null) { - return resource.getLiveSpaceSettings(spaceId); - } - return null; - } - - private static boolean shouldCullVisualSync(@Nullable PhysicsSpaceSettings settings, - @Nonnull BodyAttachmentComponent attachment, - boolean controlled) { - if (controlled) { - return false; - } - if (attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { - return true; - } - return settings != null && settings.getVisualSyncSettings().isEntityVisualSyncCullingEnabled(); - } - private static final class Scratch { private final Vector3f position = new Vector3f(); @@ -376,30 +217,12 @@ private static final class Scratch { private final Quaternionf visualRotation = new Quaternionf(); private final Vector3f worldOffset = new Vector3f(); private final Vector3f euler = new Vector3f(); - private final Vector3f linearVelocity = new Vector3f(); - private final Vector3f angularVelocity = new Vector3f(); - private final Quaternionf predictedRotation = new Quaternionf(); - private final Vector3f smoothingTargetPosition = new Vector3f(); - private final Quaternionf smoothingTargetRotation = new Quaternionf(); - @Nullable - private Store cachedResourceStore; @Nullable private Store cachedProfilingStore; @Nullable - private PhysicsWorldRuntimeResource cachedResource; - @Nullable private PhysicsRuntimeProfilingResource cachedProfiling; - @Nonnull - private PhysicsWorldRuntimeResource getResource(@Nonnull Store store) { - if (cachedResourceStore != store || cachedResource == null) { - cachedResourceStore = store; - cachedResource = PhysicsWorldRuntimeResource.require(store); - } - return cachedResource; - } - @Nullable private PhysicsRuntimeProfilingResource.SyncCollector getSyncCollector( @Nonnull Store store) { From 6ba603dd0512cbdca476102c23c654ac91446b06 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 12:33:12 +0200 Subject: [PATCH 465/534] feat(core): persist physics store holders as bson Signed-off-by: Blovien --- .../PhysicsStoreHolderPersistence.java | 225 ++++++++++++++++++ .../PhysicsStoreHolderStorage.java | 134 +++++++++++ .../PhysicsStoreRegistration.java | 9 + .../systems/PersistenceHydrationSystem.java | 12 + .../impulse/early/PhysicsStoreHooks.java | 28 ++- 5 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java new file mode 100644 index 00000000..44f2d20e --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java @@ -0,0 +1,225 @@ +package dev.hytalemodding.impulse.core.internal.persistence; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +/** + * ChunkStore-shaped holder serialization boundary for future PhysicsStore row storage. + */ +public final class PhysicsStoreHolderPersistence { + + private static final UUID NIL_UUID = new UUID(0L, 0L); + + private PhysicsStoreHolderPersistence() { + } + + @Nonnull + public static List> capturePersistentHolders( + @Nonnull Store store) { + Capture capture = new Capture(store, snapshotBodiesByUuid(store)); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> capture.collectChunk(chunk); + store.forEachChunk(UuidComponent.getComponentType(), collector); + return capture.toHolders(); + } + + @Nonnull + public static List capturePersistentHolderBlobs( + @Nonnull Store store) { + return capturePersistentHolders(store).stream() + .map(holder -> encodeHolder(store, holder)) + .toList(); + } + + @Nonnull + public static byte[] encodeHolder(@Nonnull Store store, + @Nonnull Holder holder) { + BsonDocument document = store.getRegistry().serialize( + Objects.requireNonNull(holder, "holder")); + return BsonUtil.writeToBytes(document); + } + + @Nonnull + public static Holder decodeHolder( + @Nonnull ComponentRegistry registry, + @Nonnull byte[] bytes) { + BsonDocument document = BsonUtil.readFromBytes(Objects.requireNonNull(bytes, "bytes")); + Holder holder = registry.deserialize(document); + if (holder == null) { + throw new IllegalArgumentException("PhysicsStore holder payload decoded to null"); + } + return holder; + } + + @Nonnull + private static Map snapshotBodiesByUuid( + @Nonnull Store store) { + PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); + Map bodies = new Object2ObjectOpenHashMap<>(); + for (PhysicsBodySnapshot body : snapshots.getLatestFrame().bodies()) { + bodies.put(body.bodyUuid(), body); + } + return bodies; + } + + private static final class Capture { + + @Nonnull + private final Store store; + @Nonnull + private final Map snapshotsByBodyUuid; + @Nonnull + private final List spaces = new ArrayList<>(); + @Nonnull + private final List bodies = new ArrayList<>(); + @Nonnull + private final List joints = new ArrayList<>(); + @Nonnull + private final ObjectOpenHashSet persistentBodyUuids = new ObjectOpenHashSet<>(); + + private Capture(@Nonnull Store store, + @Nonnull Map snapshotsByBodyUuid) { + this.store = store; + this.snapshotsByBodyUuid = snapshotsByBodyUuid; + } + + private void collectChunk(@Nonnull ArchetypeChunk chunk) { + for (int index = 0; index < chunk.size(); index++) { + UuidComponent uuidComponent = chunk.getComponent(index, + UuidComponent.getComponentType()); + if (uuidComponent == null || NIL_UUID.equals(uuidComponent.getUuid())) { + continue; + } + UUID uuid = uuidComponent.getUuid(); + Ref ref = chunk.getReferenceTo(index); + if (chunk.getComponent(index, SpaceComponent.getComponentType()) != null) { + spaces.add(new Row(uuid, ref)); + } + + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body != null && shouldPersistBody(chunk, index, body)) { + bodies.add(new Row(uuid, ref)); + persistentBodyUuids.add(uuid); + } + + JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); + if (joint != null) { + joints.add(new Row(uuid, ref, joint)); + } + } + } + + @Nonnull + private List> toHolders() { + List rows = new ArrayList<>(spaces.size() + bodies.size() + joints.size()); + rows.addAll(spaces); + rows.addAll(bodies); + for (Row joint : joints) { + if (persistentBodyUuids.contains(joint.requiredJoint().getBodyAUuid()) + && persistentBodyUuids.contains(joint.requiredJoint().getBodyBUuid())) { + rows.add(joint); + } + } + rows.sort(Comparator.comparing(Row::uuid)); + + List> holders = new ArrayList<>(rows.size()); + for (Row row : rows) { + Holder holder = store.copySerializableEntity(row.ref()); + sanitize(holder); + patchBodyTarget(row.uuid(), holder); + holders.add(holder); + } + return List.copyOf(holders); + } + + private void sanitize(@Nonnull Holder holder) { + holder.tryRemoveComponent(BodyCommandComponent.getComponentType()); + holder.tryRemoveComponent(ChunkCollisionSourceComponent.getComponentType()); + } + + private void patchBodyTarget(@Nonnull UUID rowUuid, + @Nonnull Holder holder) { + if (holder.getComponent(BodyComponent.getComponentType()) == null) { + return; + } + + TargetComponent target = holder.getComponent(TargetComponent.getComponentType()); + if (target == null) { + target = new TargetComponent(); + } + PhysicsBodySnapshot snapshot = snapshotsByBodyUuid.get(rowUuid); + if (snapshot != null) { + target.setPosition(snapshot.position()); + target.setRotation(snapshot.rotation()); + target.setLinearVelocity(snapshot.linearVelocity()); + target.setAngularVelocity(snapshot.angularVelocity()); + target.setActivate(!snapshot.sleeping()); + } else { + target.setActivate(target.isActive() || target.isActivate()); + } + target.setActive(false); + target.setTransformEnabled(true); + target.setVelocityEnabled(true); + holder.putComponent(TargetComponent.getComponentType(), target); + } + + private static boolean shouldPersistBody(@Nonnull ArchetypeChunk chunk, + int index, + @Nonnull BodyComponent body) { + return body.getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT + && chunk.getComponent(index, ColliderComponent.getComponentType()) != null + && chunk.getComponent(index, ShapeComponent.getComponentType()) != null + && chunk.getComponent(index, MaterialComponent.getComponentType()) != null + && chunk.getComponent(index, CollisionFilterComponent.getComponentType()) != null; + } + } + + private record Row(@Nonnull UUID uuid, + @Nonnull Ref ref, + @Nullable JointComponent jointComponent) { + + private Row(@Nonnull UUID uuid, @Nonnull Ref ref) { + this(uuid, ref, null); + } + + @Nonnull + private JointComponent requiredJoint() { + if (jointComponent == null) { + throw new IllegalStateException("Row is not a joint"); + } + return jointComponent; + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java new file mode 100644 index 00000000..9ec4478f --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java @@ -0,0 +1,134 @@ +package dev.hytalemodding.impulse.core.internal.persistence; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonBinary; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +/** + * BSON holder file storage for row-native PhysicsStore persistence. + */ +public final class PhysicsStoreHolderStorage { + + private static final int SCHEMA_VERSION = 1; + private static final String DIRECTORY = "physicsstore"; + private static final String FILE_NAME = "holders.bson"; + private static final String SCHEMA_VERSION_FIELD = "SchemaVersion"; + private static final String HOLDERS_FIELD = "Holders"; + + private PhysicsStoreHolderStorage() { + } + + @Nonnull + public static CompletableFuture save(@Nonnull PhysicsStore physicsStore) { + return save(physicsStore.getStore()); + } + + @Nonnull + public static CompletableFuture save(@Nonnull Store store) { + List holderBlobs = PhysicsStoreHolderPersistence.capturePersistentHolderBlobs( + store); + byte[] document = BsonUtil.writeToBytes(document(holderBlobs)); + Path file = file(store.getExternalData()); + return CompletableFuture.runAsync(() -> writeBinaryAtomic(file, document)); + } + + @Nonnull + public static LoadResult load(@Nonnull Store store) { + Path file = file(store.getExternalData()); + if (!Files.exists(file)) { + return LoadResult.missing(); + } + + BsonDocument document; + try { + document = BsonUtil.readFromBytes(Files.readAllBytes(file)); + } catch (IOException exception) { + throw new IllegalStateException("Could not read PhysicsStore holder storage: " + file, + exception); + } + if (document == null) { + throw new IllegalStateException("PhysicsStore holder storage is empty: " + file); + } + int schemaVersion = document.getInt32(SCHEMA_VERSION_FIELD, new BsonInt32(0)).getValue(); + if (schemaVersion != SCHEMA_VERSION) { + throw new IllegalStateException("Unsupported PhysicsStore holder storage schema " + + schemaVersion + "; expected " + SCHEMA_VERSION); + } + + BsonArray holders = document.getArray(HOLDERS_FIELD, new BsonArray()); + int loaded = 0; + for (BsonValue value : holders) { + Holder holder = PhysicsStoreHolderPersistence.decodeHolder( + store.getRegistry(), + value.asBinary().getData()); + store.addEntity(holder, AddReason.LOAD); + loaded++; + } + return new LoadResult(true, loaded); + } + + @Nonnull + static Path file(@Nonnull PhysicsStore physicsStore) { + return physicsStore.getWorld().getSavePath().resolve(DIRECTORY).resolve(FILE_NAME); + } + + @Nonnull + private static BsonDocument document(@Nonnull List holderBlobs) { + BsonArray holders = new BsonArray(); + for (byte[] holderBlob : holderBlobs) { + holders.add(new BsonBinary(holderBlob)); + } + return new BsonDocument() + .append(SCHEMA_VERSION_FIELD, new BsonInt32(SCHEMA_VERSION)) + .append(HOLDERS_FIELD, holders); + } + + private static void writeBinaryAtomic(@Nonnull Path file, @Nonnull byte[] bytes) { + try { + Path parent = file.getParent(); + if (parent != null && !Files.exists(parent)) { + Files.createDirectories(parent); + } + Path temp = file.resolveSibling(file.getFileName() + ".tmp"); + Files.write(temp, + bytes, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE); + try { + Files.move(temp, + file, + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException ignored) { + Files.move(temp, file, StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException exception) { + throw new IllegalStateException("Could not write PhysicsStore holder storage: " + file, + exception); + } + } + + public record LoadResult(boolean present, int loadedCount) { + + @Nonnull + private static LoadResult missing() { + return new LoadResult(false, 0); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index afad5947..e68d92de 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -10,6 +10,7 @@ import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; @@ -40,7 +41,9 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -54,6 +57,9 @@ public final class PhysicsStoreRegistration { private static final Consumer SHUTDOWN_CLEANUP = PhysicsStoreRegistration::clearRuntimeStateBeforeShutdown; @Nonnull + private static final Function> HOLDER_SAVE_HOOK = + PhysicsStoreHolderStorage::save; + @Nonnull private static final PhysicsStoreHooks.TickGate STEP_TICK_GATE = PhysicsStoreRegistration::shouldTickPhysicsStore; @@ -62,6 +68,7 @@ private PhysicsStoreRegistration() { public static void register(@Nonnull ComponentRegistryProxy registry) { PhysicsStoreHooks.registerShutdownHook(SHUTDOWN_CLEANUP); + PhysicsStoreHooks.registerSaveHook(HOLDER_SAVE_HOOK); PhysicsStoreHooks.registerTickGate(STEP_TICK_GATE); PhysicsResourceTypes.registerResourceTypes(registry); @@ -90,6 +97,8 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic return; } RuntimeException failure = null; + failure = runShutdownCleanup(failure, + () -> PhysicsStoreHolderStorage.save(physicsStore).join()); failure = runShutdownCleanup(failure, () -> ensurePersistentResourcePresent(store)); failure = runShutdownCleanup(failure, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index f08f8282..c8d50dce 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; +import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -56,6 +57,17 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (restore.isFailed() || restore.isHydrated()) { return; } + try { + PhysicsStoreHolderStorage.LoadResult holderLoad = PhysicsStoreHolderStorage.load(store); + if (holderLoad.present()) { + restore.markComplete(); + restore.markHydrated(); + return; + } + } catch (RuntimeException exception) { + restore.markFailed(exception.getMessage()); + return; + } PersistentPhysicsStoreResource persistent = store.getResource( PersistentPhysicsStoreResource.getResourceType()); PersistentPhysicsStorePreflight.Result result = persistent.preflight(); diff --git a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java index 085b1b92..4118c182 100644 --- a/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java +++ b/impulse-early-plugin/src/main/java/dev/hytalemodding/impulse/early/PhysicsStoreHooks.java @@ -8,6 +8,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; +import java.util.function.Function; import javax.annotation.Nonnull; public final class PhysicsStoreHooks { @@ -16,6 +17,9 @@ public final class PhysicsStoreHooks { private static final Set> SHUTDOWN_HOOKS = new CopyOnWriteArraySet<>(); @Nonnull + private static final Set>> SAVE_HOOKS = + new CopyOnWriteArraySet<>(); + @Nonnull private static final Set TICK_GATES = new CopyOnWriteArraySet<>(); private PhysicsStoreHooks() { @@ -29,6 +33,16 @@ public static void unregisterShutdownHook(@Nonnull Consumer hook) SHUTDOWN_HOOKS.remove(Objects.requireNonNull(hook, "hook")); } + public static void registerSaveHook( + @Nonnull Function> hook) { + SAVE_HOOKS.add(Objects.requireNonNull(hook, "hook")); + } + + public static void unregisterSaveHook( + @Nonnull Function> hook) { + SAVE_HOOKS.remove(Objects.requireNonNull(hook, "hook")); + } + public static void registerTickGate(@Nonnull TickGate gate) { TICK_GATES.add(Objects.requireNonNull(gate, "gate")); } @@ -61,7 +75,19 @@ public static void tickAfterChunk(@Nonnull PhysicsStore physicsStore, @Nonnull public static CompletableFuture saveResources(@Nonnull PhysicsStore physicsStore) { - return Objects.requireNonNull(physicsStore, "physicsStore").getStore().saveAllResources(); + PhysicsStore checked = Objects.requireNonNull(physicsStore, "physicsStore"); + CompletableFuture[] futures = new CompletableFuture[SAVE_HOOKS.size() + 1]; + int index = 0; + for (Function> hook : SAVE_HOOKS) { + try { + futures[index++] = Objects.requireNonNull(hook.apply(checked), + "save hook future"); + } catch (RuntimeException exception) { + futures[index++] = CompletableFuture.failedFuture(exception); + } + } + futures[index++] = checked.getStore().saveAllResources(); + return CompletableFuture.allOf(futures); } public static void shutdown(@Nonnull PhysicsStore physicsStore) { From 5bbda0c84173bea4e5e40083b644797ced189201 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 12:33:27 +0200 Subject: [PATCH 466/534] test(core): cover physics store holder persistence Signed-off-by: Blovien --- .../PhysicsStoreHolderPersistenceTest.java | 345 ++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java new file mode 100644 index 00000000..f7090e13 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java @@ -0,0 +1,345 @@ +package dev.hytalemodding.impulse.core.internal.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; +import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class PhysicsStoreHolderPersistenceTest { + + private static final UUID SPACE_UUID = uuid(1); + private static final UUID BODY_A_UUID = uuid(2); + private static final UUID BODY_B_UUID = uuid(3); + private static final UUID GENERATED_BODY_UUID = uuid(4); + private static final UUID JOINT_UUID = uuid(5); + private static final UUID LEGACY_SPACE_UUID = uuid(6); + + @TempDir + Path tempDir; + + @Test + void holderBlobsPersistDurableRowsWithSnapshotTargetsOnly() { + StoreFixture fixture = store("holder-capture", tempDir.resolve("capture")); + try { + Ref spaceRef = addSpace(fixture.store(), SPACE_UUID); + Ref bodyARef = addBody(fixture.store(), + BODY_A_UUID, + PhysicsBodyPersistenceMode.PERSISTENT, + spaceRef, + null); + Ref bodyBRef = addBody(fixture.store(), + BODY_B_UUID, + PhysicsBodyPersistenceMode.PERSISTENT, + spaceRef, + null); + fixture.store().putComponent(bodyARef, + BodyCommandComponent.getComponentType(), + BodyCommandComponent.wake()); + addBody(fixture.store(), + GENERATED_BODY_UUID, + PhysicsBodyPersistenceMode.RUNTIME_ONLY, + spaceRef, + new ChunkCollisionSourceComponent("0:0:0", + 0, + 0, + 0, + "chunk-collision/0/0/0", + PartKind.BOX, + 0)); + addJoint(fixture.store(), JOINT_UUID, BODY_A_UUID, BODY_B_UUID); + publishSnapshot(fixture.store(), bodyARef); + + List> decoded = PhysicsStoreHolderPersistence + .capturePersistentHolderBlobs(fixture.store()) + .stream() + .map(blob -> PhysicsStoreHolderPersistence.decodeHolder( + fixture.store().getRegistry(), + blob)) + .toList(); + + assertNotNull(holder(decoded, SPACE_UUID)); + Holder bodyA = holder(decoded, BODY_A_UUID); + assertNotNull(bodyA); + assertNotNull(holder(decoded, BODY_B_UUID)); + assertNotNull(holder(decoded, JOINT_UUID)); + assertNull(holder(decoded, GENERATED_BODY_UUID)); + + assertNull(bodyA.getComponent(BodyCommandComponent.getComponentType())); + BodyComponent body = bodyA.getComponent(BodyComponent.getComponentType()); + assertNotNull(body); + assertNull(body.getSpaceRef()); + TargetComponent target = bodyA.getComponent(TargetComponent.getComponentType()); + assertNotNull(target); + assertFalse(target.isActive()); + assertFalse(target.isActivate()); + assertEquals(new Vector3f(9.0f, 8.0f, 7.0f), target.getPosition()); + assertEquals(new Vector3f(0.1f, 0.2f, 0.3f), target.getLinearVelocity()); + assertEquals(new Vector3f(0.4f, 0.5f, 0.6f), target.getAngularVelocity()); + } finally { + fixture.close(); + } + } + + @Test + void hydrationPrefersHolderStorageOverLegacyDtoResource() { + StoreFixture source = store("holder-save-source", tempDir.resolve("save")); + try { + Ref spaceRef = addSpace(source.store(), SPACE_UUID); + addBody(source.store(), + BODY_A_UUID, + PhysicsBodyPersistenceMode.PERSISTENT, + spaceRef, + null); + PhysicsStoreHolderStorage.save(source.store()).join(); + } finally { + source.close(); + } + + StoreFixture target = store("holder-save-target", tempDir.resolve("save")); + try { + Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider( + "test:legacy-holder-fallback")); + PersistentPhysicsStoreResource legacy = target.store().getResource( + PersistentPhysicsStoreResource.getResourceType()); + legacy.setSpaces(new PersistentSpaceDto[] { + new PersistentSpaceDto(LEGACY_SPACE_UUID, + "test:legacy-holder-fallback", + new Vector3f(0.0f, -9.81f, 0.0f)) + }); + + new PersistenceHydrationSystem().tick(0.0f, 0, target.store()); + + PhysicsRestoreStatusResource restore = target.store().getResource( + PhysicsRestoreStatusResource.getResourceType()); + assertTrue(restore.isHydrated()); + assertFalse(restore.isFailed()); + List rowUuids = rowUuids(target.store()); + assertTrue(rowUuids.contains(SPACE_UUID)); + assertTrue(rowUuids.contains(BODY_A_UUID)); + assertFalse(rowUuids.contains(LEGACY_SPACE_UUID)); + } finally { + target.close(); + } + } + + @Nonnull + private static StoreFixture store(@Nonnull String worldName, @Nonnull Path savePath) { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsStore physicsStore = new PhysicsStore(world(worldName, savePath)); + Store store = registry.addStore(physicsStore, EmptyResourceStorage.get()); + return new StoreFixture(registry, store); + } + + @Nonnull + private static World world(@Nonnull String worldName, @Nonnull Path savePath) { + World world = TestInstanceFactory.world(worldName); + setField(world, World.class, "savePath", savePath); + return world; + } + + @Nonnull + private static Ref addSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(new BackendId("test:holder-persistence"), + new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(ref); + return ref; + } + + @Nonnull + private static Ref addBody(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Ref spaceRef, + ChunkCollisionSourceComponent source) { + Holder holder = PhysicsEntities.bodyHolder(store, + bodyUuid, + body(SPACE_UUID, persistenceMode, spaceRef), + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false), + target(), + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.6f, 0.1f), + new CollisionFilterComponent(PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.ALL)); + if (source != null) { + holder.addComponent(ChunkCollisionSourceComponent.getComponentType(), source); + } + Ref ref = store.addEntity(holder, AddReason.SPAWN); + assertNotNull(ref); + return ref; + } + + @Nonnull + private static BodyComponent body(@Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyPersistenceMode persistenceMode, + @Nonnull Ref spaceRef) { + BodyComponent body = new BodyComponent(spaceUuid, PhysicsBodyKind.BODY, persistenceMode); + body.setSpaceRef(spaceRef); + return body; + } + + @Nonnull + private static TargetComponent target() { + TargetComponent target = new TargetComponent(); + target.setActive(true); + target.setPosition(new Vector3f(1.0f, 2.0f, 3.0f)); + return target; + } + + private static void addJoint(@Nonnull Store store, + @Nonnull UUID jointUuid, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid) { + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(jointUuid)); + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(SPACE_UUID); + joint.setBodyAUuid(bodyAUuid); + joint.setBodyBUuid(bodyBUuid); + holder.addComponent(JointComponent.getComponentType(), joint); + assertNotNull(store.addEntity(holder, AddReason.SPAWN)); + } + + private static void publishSnapshot(@Nonnull Store store, + @Nonnull Ref bodyRef) { + store.getResource(PhysicsSnapshotResource.getResourceType()).publish( + new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(new PhysicsBodySnapshot(bodyRef, + BODY_A_UUID, + SPACE_UUID, + PhysicsBodyType.DYNAMIC, + new Vector3f(9.0f, 8.0f, 7.0f), + new Quaternionf(), + new Vector3f(0.1f, 0.2f, 0.3f), + new Vector3f(0.4f, 0.5f, 0.6f), + 0.0f, + true)))); + } + + private static Holder holder(@Nonnull List> holders, + @Nonnull UUID uuid) { + for (Holder holder : holders) { + UuidComponent component = holder.getComponent(UuidComponent.getComponentType()); + if (component != null && uuid.equals(component.getUuid())) { + return holder; + } + } + return null; + } + + @Nonnull + private static List rowUuids(@Nonnull Store store) { + List uuids = new ArrayList<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> { + for (int index = 0; index < chunk.size(); index++) { + UuidComponent uuid = chunk.getComponent(index, UuidComponent.getComponentType()); + if (uuid != null) { + uuids.add(uuid.getUuid()); + } + } + }; + store.forEachChunk(UuidComponent.getComponentType(), collector); + return uuids; + } + + private static void setField(@Nonnull Object target, + @Nonnull Class owner, + @Nonnull String name, + @Nonnull Object value) { + try { + Field field = owner.getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Failed to set test field " + owner.getName() + "." + name, + exception); + } + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private record StoreFixture(@Nonnull ComponentRegistry registry, + @Nonnull Store store) { + + private void close() { + registry.removeStore(store); + registry.shutdown(); + } + } +} From 21da107a9c78a21db541615ac1d0e460c886b74a Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 12:38:11 +0200 Subject: [PATCH 467/534] fix(core): tolerate missing physics holder save paths Signed-off-by: Blovien --- .../persistence/PhysicsStoreHolderStorage.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java index 9ec4478f..ea2683b1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.bson.BsonArray; import org.bson.BsonBinary; import org.bson.BsonDocument; @@ -43,13 +44,19 @@ public static CompletableFuture save(@Nonnull Store store) { List holderBlobs = PhysicsStoreHolderPersistence.capturePersistentHolderBlobs( store); byte[] document = BsonUtil.writeToBytes(document(holderBlobs)); - Path file = file(store.getExternalData()); + Path file = fileOrNull(store.getExternalData()); + if (file == null) { + return CompletableFuture.completedFuture(null); + } return CompletableFuture.runAsync(() -> writeBinaryAtomic(file, document)); } @Nonnull public static LoadResult load(@Nonnull Store store) { - Path file = file(store.getExternalData()); + Path file = fileOrNull(store.getExternalData()); + if (file == null) { + return LoadResult.missing(); + } if (!Files.exists(file)) { return LoadResult.missing(); } @@ -87,6 +94,12 @@ static Path file(@Nonnull PhysicsStore physicsStore) { return physicsStore.getWorld().getSavePath().resolve(DIRECTORY).resolve(FILE_NAME); } + @Nullable + private static Path fileOrNull(@Nonnull PhysicsStore physicsStore) { + Path savePath = physicsStore.getWorld().getSavePath(); + return savePath != null ? savePath.resolve(DIRECTORY).resolve(FILE_NAME) : null; + } + @Nonnull private static BsonDocument document(@Nonnull List holderBlobs) { BsonArray holders = new BsonArray(); From 45021321046c660833b1463af60b8b6814c077dc Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 12:49:48 +0200 Subject: [PATCH 468/534] refactor(core): demote dto physics persistence to fallback Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkStoreTypes.java | 10 +- .../PersistentPhysicsStoreStorage.java | 69 +++ .../PhysicsStoreHolderStorage.java | 53 +++ .../PhysicsStoreRegistration.java | 13 +- .../resources/PhysicsResourceTypes.java | 3 +- .../systems/PersistenceCaptureSystem.java | 401 ------------------ .../systems/PersistenceHydrationSystem.java | 11 +- .../systems/StepSubmissionSystem.java | 2 +- .../persistence/PhysicsPersistence.java | 60 ++- .../examples/commands/PersistenceCommand.java | 8 +- 10 files changed, 192 insertions(+), 438 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java index 6b4879d3..27de9f01 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java @@ -37,10 +37,18 @@ public static void registerPhysicsStoreResourceTypes( PhysicsChunkComponentSyncResource::new)); } - public static void registerPhysicsStoreSystems( + public static void registerPhysicsStoreSpaceBindingSystems( @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); + } + + public static void registerPhysicsStorePreBodyBindingSystems( + @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new ChunkCollisionMutationDrainSystem()); + } + + public static void registerPhysicsStorePostBodyBindingSystems( + @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new ChunkCollisionComponentSyncSystem()); registry.registerSystem(new ChunkCollisionVoxelStitchingSystem()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java new file mode 100644 index 00000000..18a9c21e --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java @@ -0,0 +1,69 @@ +package dev.hytalemodding.impulse.core.internal.persistence; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.nio.file.Path; +import java.util.concurrent.CompletionException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +/** + * Compatibility reader for legacy DTO resource files. + */ +public final class PersistentPhysicsStoreStorage { + + private static final String RESOURCE_DIRECTORY = "resources"; + private static final String FILE_NAME = "PersistentPhysicsStore.json"; + + private PersistentPhysicsStoreStorage() { + } + + @Nonnull + public static LoadResult load(@Nonnull Store store) { + Path file = fileOrNull(store.getExternalData()); + if (file == null) { + return LoadResult.missing(); + } + BsonDocument document; + try { + document = BsonUtil.readDocument(file).join(); + } catch (CompletionException exception) { + throw new IllegalStateException("Could not read legacy PhysicsStore DTO storage: " + + file, exception.getCause() != null ? exception.getCause() : exception); + } + if (document == null) { + return LoadResult.missing(); + } + PersistentPhysicsStoreResource resource = PersistentPhysicsStoreResource.CODEC.decode( + document, + new ExtraInfo()); + if (resource == null) { + throw new IllegalStateException("Legacy PhysicsStore DTO storage decoded to null: " + + file); + } + return new LoadResult(true, resource); + } + + @Nonnull + static Path file(@Nonnull PhysicsStore physicsStore) { + return physicsStore.getWorld().getSavePath().resolve(RESOURCE_DIRECTORY).resolve(FILE_NAME); + } + + @Nullable + private static Path fileOrNull(@Nonnull PhysicsStore physicsStore) { + Path savePath = physicsStore.getWorld().getSavePath(); + return savePath != null ? savePath.resolve(RESOURCE_DIRECTORY).resolve(FILE_NAME) : null; + } + + public record LoadResult(boolean present, + @Nonnull PersistentPhysicsStoreResource resource) { + + @Nonnull + private static LoadResult missing() { + return new LoadResult(false, new PersistentPhysicsStoreResource()); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java index ea2683b1..23da48c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java @@ -5,6 +5,9 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.BsonUtil; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -89,6 +92,48 @@ public static LoadResult load(@Nonnull Store store) { return new LoadResult(true, loaded); } + @Nonnull + public static Summary summary(@Nonnull Store store) { + Path file = fileOrNull(store.getExternalData()); + if (file == null || !Files.exists(file)) { + return Summary.missing(); + } + BsonDocument document; + try { + document = BsonUtil.readFromBytes(Files.readAllBytes(file)); + } catch (IOException exception) { + throw new IllegalStateException("Could not read PhysicsStore holder storage: " + file, + exception); + } + if (document == null) { + return Summary.missing(); + } + int schemaVersion = document.getInt32(SCHEMA_VERSION_FIELD, new BsonInt32(0)).getValue(); + if (schemaVersion != SCHEMA_VERSION) { + throw new IllegalStateException("Unsupported PhysicsStore holder storage schema " + + schemaVersion + "; expected " + SCHEMA_VERSION); + } + BsonArray holders = document.getArray(HOLDERS_FIELD, new BsonArray()); + int spaces = 0; + int bodies = 0; + int joints = 0; + for (BsonValue value : holders) { + Holder holder = PhysicsStoreHolderPersistence.decodeHolder( + store.getRegistry(), + value.asBinary().getData()); + if (holder.getComponent(SpaceComponent.getComponentType()) != null) { + spaces++; + } + if (holder.getComponent(BodyComponent.getComponentType()) != null) { + bodies++; + } + if (holder.getComponent(JointComponent.getComponentType()) != null) { + joints++; + } + } + return new Summary(true, spaces, bodies, joints); + } + @Nonnull static Path file(@Nonnull PhysicsStore physicsStore) { return physicsStore.getWorld().getSavePath().resolve(DIRECTORY).resolve(FILE_NAME); @@ -144,4 +189,12 @@ private static LoadResult missing() { return new LoadResult(false, 0); } } + + public record Summary(boolean present, int spaces, int bodies, int joints) { + + @Nonnull + private static Summary missing() { + return new Summary(false, 0, 0, 0); + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index e68d92de..046d8056 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -9,7 +9,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; @@ -29,7 +28,6 @@ import dev.hytalemodding.impulse.core.internal.systems.CompletedStepPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.JointBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.PersistenceCaptureSystem; import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreQueuedReadSystem; import dev.hytalemodding.impulse.core.internal.systems.SpaceBindingSystem; @@ -76,10 +74,12 @@ public static void register(@Nonnull ComponentRegistryProxy regist registry.registerSystem(new PersistenceHydrationSystem()); registry.registerSystem(new IdentityIndexSystem()); - PhysicsChunkStoreTypes.registerPhysicsStoreSystems(registry); + PhysicsChunkStoreTypes.registerPhysicsStoreSpaceBindingSystems(registry); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); + PhysicsChunkStoreTypes.registerPhysicsStorePreBodyBindingSystems(registry); registry.registerSystem(new BodyBindingSystem()); + PhysicsChunkStoreTypes.registerPhysicsStorePostBodyBindingSystems(registry); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); @@ -87,7 +87,6 @@ public static void register(@Nonnull ComponentRegistryProxy regist registry.registerSystem(new TargetBindingSystem()); registry.registerSystem(new CompletedStepPublicationSystem()); registry.registerSystem(new PhysicsStoreQueuedReadSystem()); - registry.registerSystem(new PersistenceCaptureSystem()); registry.registerSystem(new StepSubmissionSystem()); } @@ -99,8 +98,6 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic RuntimeException failure = null; failure = runShutdownCleanup(failure, () -> PhysicsStoreHolderStorage.save(physicsStore).join()); - failure = runShutdownCleanup(failure, - () -> ensurePersistentResourcePresent(store)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsStepSchedulerResource.getResourceType(), @@ -144,10 +141,6 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic } } - private static void ensurePersistentResourcePresent(@Nonnull Store store) { - store.getResource(PersistentPhysicsStoreResource.getResourceType()); - } - private static > void cleanupResource( @Nonnull Store store, @Nonnull ResourceType type, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 59a0350a..898903bb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -76,8 +76,7 @@ public static void registerResourceTypes( PhysicsStoreReadQueueResource::new); persistentStoreResourceType = registry.registerResource( PersistentPhysicsStoreResource.class, - "PersistentPhysicsStore", - PersistentPhysicsStoreResource.CODEC); + PersistentPhysicsStoreResource::new); restoreStatusResourceType = registry.registerResource( PhysicsRestoreStatusResource.class, PhysicsRestoreStatusResource::new); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java deleted file mode 100644 index 59ff5679..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystem.java +++ /dev/null @@ -1,401 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems; - -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.QuerySystem; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyRuntimeStateDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentJointDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentMaterialDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.BiConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Captures serializable PhysicsStore entities into compact DTO resources. - */ -public final class PersistenceCaptureSystem extends TickingSystem - implements QuerySystem { - - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PhysicsStoreQueuedReadSystem.class) - ); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsRestoreStatusResource restore = store.getResource( - PhysicsRestoreStatusResource.getResourceType()); - if (restore.isPending() || restore.isFailed()) { - return; - } - Capture capture = new Capture(snapshotBodiesByUuid(store)); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> capture.collectChunk(chunk); - store.forEachChunk(systemIndex, collector); - capture.writeTo(store.getResource(PersistentPhysicsStoreResource.getResourceType())); - } - - @Nonnull - private static Map snapshotBodiesByUuid( - @Nonnull Store store) { - PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - Map bodies = new Object2ObjectOpenHashMap<>(); - for (PhysicsBodySnapshot body : snapshots.getLatestFrame().bodies()) { - bodies.put(body.bodyUuid(), body); - } - return bodies; - } - - @Nonnull - @Override - public Query getQuery() { - return PhysicsStoreSystemSupport.uuidQuery(); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - - private static final class Capture { - - @Nonnull - private final Map snapshotsByBodyUuid; - @Nonnull - private final List spaceRows = new ArrayList<>(); - @Nonnull - private final List bodyRows = new ArrayList<>(); - @Nonnull - private final List jointRows = new ArrayList<>(); - - private Capture(@Nonnull Map snapshotsByBodyUuid) { - this.snapshotsByBodyUuid = snapshotsByBodyUuid; - } - - private void collectChunk(@Nonnull ArchetypeChunk chunk) { - for (int index = 0; index < chunk.size(); index++) { - UUID uuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (PhysicsStoreSystemSupport.isNil(uuid)) { - continue; - } - collectRow(uuid, chunk, index); - } - } - - private void collectRow(@Nonnull UUID uuid, - @Nonnull ArchetypeChunk chunk, - int index) { - SpaceComponent space = chunk.getComponent(index, SpaceComponent.getComponentType()); - if (space != null) { - spaceRows.add(new SpaceRow(uuid, - space, - chunk.getComponent(index, ChunkCollisionSettingsComponent.getComponentType()), - chunk.getComponent(index, MaterialComponent.getComponentType()), - chunk.getComponent(index, CollisionFilterComponent.getComponentType()), - chunk.getComponent(index, SolverSettingsComponent.getComponentType()), - chunk.getComponent(index, VisualSyncSettingsComponent.getComponentType()), - chunk.getComponent(index, - VisualMaterializationSettingsComponent.getComponentType()), - chunk.getComponent(index, CollisionLodSettingsComponent.getComponentType()), - chunk.getComponent(index, ExtensionSettingsComponent.getComponentType()))); - } - BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); - if (body != null) { - bodyRows.add(new BodyRow(uuid, - body, - chunk.getComponent(index, DynamicsComponent.getComponentType()), - chunk.getComponent(index, TargetComponent.getComponentType()), - chunk.getComponent(index, ColliderComponent.getComponentType()), - chunk.getComponent(index, ShapeComponent.getComponentType()), - chunk.getComponent(index, MaterialComponent.getComponentType()), - chunk.getComponent(index, CollisionFilterComponent.getComponentType()))); - } - JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); - if (joint != null) { - jointRows.add(new JointRow(uuid, joint)); - } - } - - private void writeTo(@Nonnull PersistentPhysicsStoreResource persistent) { - ObjectOpenHashSet bodyUuids = persistentBodyUuids(); - - persistent.setSpaces(spaceDtos()); - persistent.setBodies(bodyDtos()); - persistent.setColliders(colliderDtos(bodyUuids)); - persistent.setShapes(shapeDtos(bodyUuids)); - persistent.setMaterials(materialDtos(bodyUuids)); - persistent.setJoints(jointDtos(bodyUuids)); - } - - @Nonnull - private ObjectOpenHashSet persistentBodyUuids() { - ObjectOpenHashSet bodyUuids = new ObjectOpenHashSet<>(); - for (BodyRow row : bodyRows) { - if (row.body().getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT - && row.hasAggregateCollider()) { - bodyUuids.add(row.uuid()); - } - } - return bodyUuids; - } - - @Nonnull - private PersistentSpaceDto[] spaceDtos() { - return spaceRows.stream() - .map(this::spaceDto) - .sorted(Comparator.comparing(PersistentSpaceDto::getSpaceUuid)) - .toArray(PersistentSpaceDto[]::new); - } - - @Nonnull - private PersistentSpaceDto spaceDto(@Nonnull SpaceRow row) { - ChunkCollisionSettingsComponent chunkCollision = row.chunkCollisionSettings() != null - ? row.chunkCollisionSettings() - : new ChunkCollisionSettingsComponent(); - MaterialComponent material = row.material() != null - ? row.material() - : new MaterialComponent(PhysicsChunkCollisionDefaults.FRICTION, - PhysicsChunkCollisionDefaults.RESTITUTION); - CollisionFilterComponent filter = row.filter() != null - ? row.filter() - : new CollisionFilterComponent(PhysicsChunkCollisionDefaults.COLLISION_GROUP, - PhysicsChunkCollisionDefaults.COLLISION_MASK); - return new PersistentSpaceDto(row.uuid(), - row.space().getBackendIdValue(), - row.space().getGravity(), - chunkCollision.getMode(), - chunkCollision.getEntityChunkBoundaryMode(), - chunkCollision.isNativeVoxelCollisionEnabled(), - chunkCollision.getRadius(), - chunkCollision.getBodyRadius(), - chunkCollision.getTtlTicks(), - material.getFriction(), - material.getRestitution(), - filter.getCollisionGroup(), - filter.getCollisionMask(), - row.solverSettings() != null - ? row.solverSettings() - : new SolverSettingsComponent(), - row.visualSyncSettings() != null - ? row.visualSyncSettings() - : new VisualSyncSettingsComponent(), - row.visualMaterializationSettings() != null - ? row.visualMaterializationSettings() - : new VisualMaterializationSettingsComponent(), - row.collisionLodSettings() != null - ? row.collisionLodSettings() - : new CollisionLodSettingsComponent(), - row.extensionSettings() != null - ? row.extensionSettings() - : new ExtensionSettingsComponent()); - } - - @Nonnull - private PersistentBodyDto[] bodyDtos() { - return bodyRows.stream() - .filter(row -> row.body().getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT) - .filter(BodyRow::hasAggregateCollider) - .map(this::bodyDto) - .sorted(Comparator.comparing(PersistentBodyDto::getBodyUuid)) - .toArray(PersistentBodyDto[]::new); - } - - @Nonnull - private PersistentBodyDto bodyDto(@Nonnull BodyRow row) { - DynamicsComponent dynamics = row.dynamics() != null - ? row.dynamics() - : new DynamicsComponent(); - return new PersistentBodyDto(row.uuid(), - row.body().getSpaceUuid(), - row.body().getKind(), - row.body().getPersistenceMode(), - dynamics.getBodyType(), - dynamics.getMass(), - dynamics.getLinearDamping(), - dynamics.getAngularDamping(), - dynamics.isContinuousCollisionEnabled(), - new UUID[] {row.uuid()}, - runtimeState(row.uuid(), row.target())); - } - - @Nonnull - private PersistentBodyRuntimeStateDto runtimeState(@Nonnull UUID bodyUuid, - @Nullable TargetComponent target) { - PhysicsBodySnapshot snapshot = snapshotsByBodyUuid.get(bodyUuid); - if (snapshot != null) { - return new PersistentBodyRuntimeStateDto(snapshot.position(), - snapshot.rotation(), - snapshot.linearVelocity(), - snapshot.angularVelocity(), - snapshot.sleeping()); - } - if (target != null) { - return new PersistentBodyRuntimeStateDto(target.getPosition(), - target.getRotation(), - target.getLinearVelocity(), - target.getAngularVelocity(), - !target.isActive() && !target.isActivate()); - } - return new PersistentBodyRuntimeStateDto(new Vector3f(), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - false); - } - - @Nonnull - private PersistentColliderDto[] colliderDtos(@Nonnull Set bodyUuids) { - return bodyRows.stream() - .filter(row -> bodyUuids.contains(row.uuid())) - .filter(BodyRow::hasAggregateCollider) - .map(this::colliderDto) - .sorted(Comparator.comparing(PersistentColliderDto::getColliderUuid)) - .toArray(PersistentColliderDto[]::new); - } - - @Nonnull - private PersistentColliderDto colliderDto(@Nonnull BodyRow row) { - CollisionFilterComponent resolvedFilter = row.filter() != null - ? row.filter() - : new CollisionFilterComponent(); - return new PersistentColliderDto(row.uuid(), - row.uuid(), - row.uuid(), - row.uuid(), - row.collider().getLocalPosition(), - row.collider().getLocalRotation(), - row.collider().isSensor(), - resolvedFilter.getCollisionGroup(), - resolvedFilter.getCollisionMask()); - } - - @Nonnull - private PersistentShapeDto[] shapeDtos(@Nonnull Set bodyUuids) { - return bodyRows.stream() - .filter(row -> bodyUuids.contains(row.uuid())) - .filter(BodyRow::hasAggregateCollider) - .map(row -> new PersistentShapeDto(row.uuid(), - row.shape().getShapeType(), - row.shape().getHalfExtentX(), - row.shape().getHalfExtentY(), - row.shape().getHalfExtentZ(), - row.shape().getRadius(), - row.shape().getHalfHeight(), - row.shape().getAxis(), - row.shape().getGroundY(), - row.shape().getResourceKey())) - .sorted(Comparator.comparing(PersistentShapeDto::getShapeUuid)) - .toArray(PersistentShapeDto[]::new); - } - - @Nonnull - private PersistentMaterialDto[] materialDtos(@Nonnull Set bodyUuids) { - return bodyRows.stream() - .filter(row -> bodyUuids.contains(row.uuid())) - .filter(BodyRow::hasAggregateCollider) - .map(row -> new PersistentMaterialDto(row.uuid(), - row.material().getFriction(), - row.material().getRestitution())) - .sorted(Comparator.comparing(PersistentMaterialDto::getMaterialUuid)) - .toArray(PersistentMaterialDto[]::new); - } - - @Nonnull - private PersistentJointDto[] jointDtos(@Nonnull Set bodyUuids) { - return jointRows.stream() - .filter(row -> bodyUuids.contains(row.joint().getBodyAUuid())) - .filter(row -> bodyUuids.contains(row.joint().getBodyBUuid())) - .map(row -> new PersistentJointDto(row.uuid(), - row.joint().getSpaceUuid(), - row.joint().getBodyAUuid(), - row.joint().getBodyBUuid(), - row.joint().getType(), - row.joint().getAnchorA(), - row.joint().getAnchorB(), - row.joint().getAxis(), - row.joint().getLowerLimit(), - row.joint().getUpperLimit(), - row.joint().isEnabled(), - row.joint().isMotorEnabled(), - row.joint().getMotorTargetVelocity(), - row.joint().getMotorMaxForce(), - row.joint().getSpringRestLength(), - row.joint().getSpringStiffness(), - row.joint().getSpringDamping())) - .sorted(Comparator.comparing(PersistentJointDto::getJointUuid)) - .toArray(PersistentJointDto[]::new); - } - - } - - private record SpaceRow(@Nonnull UUID uuid, - @Nonnull SpaceComponent space, - @Nullable ChunkCollisionSettingsComponent chunkCollisionSettings, - @Nullable MaterialComponent material, - @Nullable CollisionFilterComponent filter, - @Nullable SolverSettingsComponent solverSettings, - @Nullable VisualSyncSettingsComponent visualSyncSettings, - @Nullable VisualMaterializationSettingsComponent visualMaterializationSettings, - @Nullable CollisionLodSettingsComponent collisionLodSettings, - @Nullable ExtensionSettingsComponent extensionSettings) { - } - - private record BodyRow(@Nonnull UUID uuid, - @Nonnull BodyComponent body, - @Nullable DynamicsComponent dynamics, - @Nullable TargetComponent target, - @Nullable ColliderComponent collider, - @Nullable ShapeComponent shape, - @Nullable MaterialComponent material, - @Nullable CollisionFilterComponent filter) { - - private boolean hasAggregateCollider() { - return collider != null && shape != null && material != null && filter != null; - } - } - - private record JointRow(@Nonnull UUID uuid, @Nonnull JointComponent joint) { - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index c8d50dce..8d41de9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentMaterialDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStorePreflight; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreStorage; import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; @@ -68,8 +69,14 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) restore.markFailed(exception.getMessage()); return; } - PersistentPhysicsStoreResource persistent = store.getResource( - PersistentPhysicsStoreResource.getResourceType()); + PersistentPhysicsStoreStorage.LoadResult persistentLoad; + try { + persistentLoad = PersistentPhysicsStoreStorage.load(store); + } catch (RuntimeException exception) { + restore.markFailed(exception.getMessage()); + return; + } + PersistentPhysicsStoreResource persistent = persistentLoad.resource(); PersistentPhysicsStorePreflight.Result result = persistent.preflight(); if (!result.valid()) { restore.markFailed(String.join("; ", result.errors())); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index a3c8e87b..32a5a8d5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -52,7 +52,7 @@ public final class StepSubmissionSystem extends TickingSystem { private static final float MAX_ANGULAR_RADIANS_PER_SUBSTEP = (float) Math.toRadians(30.0); private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, PersistenceCaptureSystem.class) + new SystemDependency<>(Order.AFTER, PhysicsStoreQueuedReadSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 6a9484ea..8e495221 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -4,6 +4,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreStorage; +import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; @@ -21,6 +23,10 @@ public final class PhysicsPersistence { public static final int CURRENT_SCHEMA_VERSION = PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION; + private static final String SAVE_SKIPPED_REASON = + "authoritative-physics-store-holder-save-hook"; + private static final String RESTORE_SKIPPED_REASON = + "authoritative-physics-store-auto-restore"; private PhysicsPersistence() { } @@ -33,7 +39,7 @@ public static SaveResult saveRuntimeSnapshot(@Nonnull Store store) status.storedSpaces(), status.storedBodies(), status.storedJoints(), - "authoritative-physics-store-auto-capture"); + SAVE_SKIPPED_REASON); } @Nonnull @@ -44,20 +50,20 @@ public static CompletionStage saveRuntimeSnapshotAsync( status.storedSpaces(), status.storedBodies(), status.storedJoints(), - "authoritative-physics-store-auto-capture")); + SAVE_SKIPPED_REASON)); } @Nonnull public static RestoreRequestResult requestRuntimeRestore(@Nonnull Store store) { Status status = status(store); - return new RestoreRequestResult(false, "authoritative-physics-store-auto-restore", status); + return new RestoreRequestResult(false, RESTORE_SKIPPED_REASON, status); } @Nonnull public static CompletionStage requestRuntimeRestoreAsync( @Nonnull Store store) { return statusAsync(store).thenApply(status -> - new RestoreRequestResult(false, "authoritative-physics-store-auto-restore", status)); + new RestoreRequestResult(false, RESTORE_SKIPPED_REASON, status)); } @Nonnull @@ -80,8 +86,7 @@ private static Store physicsStore(@Nonnull Store stor @Nonnull private static Status liveStatus(@Nonnull Store physicsStore) { - PersistentPhysicsStoreResource persistent = physicsStore.getResource( - PersistentPhysicsStoreResource.getResourceType()); + SavedStateSummary saved = savedStateSummary(physicsStore); PhysicsRestoreStatusResource restore = physicsStore.getResource( PhysicsRestoreStatusResource.getResourceType()); List summaries = PhysicsDiagnostics.spaceSummaries(physicsStore); @@ -94,10 +99,10 @@ private static Status liveStatus(@Nonnull Store physicsStore) { runtimeBodies, 0, runtimeJoints, - persistent.getSchemaVersion(), - persistent.getSpaces().length, - persistent.getBodies().length, - persistent.getJoints().length, + saved.schemaVersion(), + saved.spaces(), + saved.bodies(), + saved.joints(), restoreState(restore), restoreMessage(restore)); } @@ -106,8 +111,7 @@ private static Status liveStatus(@Nonnull Store physicsStore) { private static Status copiedStatus(@Nonnull Store physicsStore) { PhysicsThreading.requireWorldThread(physicsStore, "read copied PhysicsStore persistence status"); - PersistentPhysicsStoreResource persistent = physicsStore.getResource( - PersistentPhysicsStoreResource.getResourceType()); + SavedStateSummary saved = savedStateSummary(physicsStore); PhysicsRestoreStatusResource restore = physicsStore.getResource( PhysicsRestoreStatusResource.getResourceType()); int runtimeBodies = physicsStore.getResource(PhysicsSnapshotResource.getResourceType()) @@ -119,15 +123,34 @@ private static Status copiedStatus(@Nonnull Store physicsStore) { return new Status(physicsStoreSpaces, runtimeBodies, 0, - persistent.getJoints().length, - persistent.getSchemaVersion(), - persistent.getSpaces().length, - persistent.getBodies().length, - persistent.getJoints().length, + saved.joints(), + saved.schemaVersion(), + saved.spaces(), + saved.bodies(), + saved.joints(), restoreState(restore), restoreMessage(restore)); } + @Nonnull + private static SavedStateSummary savedStateSummary(@Nonnull Store physicsStore) { + PhysicsStoreHolderStorage.Summary holderSummary = PhysicsStoreHolderStorage.summary( + physicsStore); + if (holderSummary.present()) { + return new SavedStateSummary(CURRENT_SCHEMA_VERSION, + holderSummary.spaces(), + holderSummary.bodies(), + holderSummary.joints()); + } + PersistentPhysicsStoreStorage.LoadResult legacy = PersistentPhysicsStoreStorage.load( + physicsStore); + PersistentPhysicsStoreResource resource = legacy.resource(); + return new SavedStateSummary(resource.getSchemaVersion(), + resource.getSpaces().length, + resource.getBodies().length, + resource.getJoints().length); + } + @Nonnull private static RestoreState restoreState(@Nonnull PhysicsRestoreStatusResource restore) { if (restore.isFailed()) { @@ -198,4 +221,7 @@ public boolean hasRestoreMessage() { } } + private record SavedStateSummary(int schemaVersion, int spaces, int bodies, int joints) { + } + } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java index 3e1bd87b..1340f75e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java @@ -48,8 +48,8 @@ private static void sendSaveResult(@Nonnull CommandContext ctx, @Nonnull SaveResult result) { if (!result.synced()) { ctx.sendMessage(Message.raw("Manual Impulse persistence save is disabled in " - + "authoritative PhysicsStore mode; PhysicsStore captures canonical state " - + "automatically. reason=" + result.skippedReason() + + "authoritative PhysicsStore mode; PhysicsStore holders are saved " + + "by the world save hook. reason=" + result.skippedReason() + ", stored schema=" + result.schemaVersion() + ", spaces=" + result.spaces() + ", persistentBodies=" + result.bodies() @@ -96,7 +96,7 @@ private static void sendLoadResult(@Nonnull CommandContext ctx, if (!result.queued()) { ctx.sendMessage(Message.raw("Manual Impulse persistence restore is disabled in " + "authoritative PhysicsStore mode; PhysicsStore restores automatically from " - + "PersistentPhysicsStore during world startup. reason=" + + "holder storage during world startup. reason=" + result.skippedReason() + ", stored schema=" + status.schemaVersion() + ", spaces=" + status.storedSpaces() @@ -140,7 +140,7 @@ private static void sendStatus(@Nonnull CommandContext ctx, + ", runtimeBodies=" + status.runtimePersistentBodies() + ", joints=" + status.runtimeJoints() - + "; PhysicsStore schema=" + status.schemaVersion() + + "; stored PhysicsStore schema=" + status.schemaVersion() + ", spaces=" + status.storedSpaces() + ", bodies=" + status.storedBodies() + ", joints=" + status.storedJoints() From 58ad7011501bff365f13a2af461dfab5a5bb1c8e Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 12:50:01 +0200 Subject: [PATCH 469/534] test(core): cover holder primary physics persistence Signed-off-by: Blovien --- .../PhysicsStoreHolderPersistenceTest.java | 92 ++++++- .../systems/PersistenceCaptureSystemTest.java | 258 ------------------ 2 files changed, 85 insertions(+), 265 deletions(-) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java index f7090e13..e8ff07ee 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hypixel.hytale.codec.ExtraInfo; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentRegistryProxy; @@ -17,6 +18,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.BsonUtil; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsAxis; @@ -27,6 +29,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; @@ -154,13 +157,8 @@ void hydrationPrefersHolderStorageOverLegacyDtoResource() { try { Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider( "test:legacy-holder-fallback")); - PersistentPhysicsStoreResource legacy = target.store().getResource( - PersistentPhysicsStoreResource.getResourceType()); - legacy.setSpaces(new PersistentSpaceDto[] { - new PersistentSpaceDto(LEGACY_SPACE_UUID, - "test:legacy-holder-fallback", - new Vector3f(0.0f, -9.81f, 0.0f)) - }); + writeLegacyDto(target.store(), + legacyResource(LEGACY_SPACE_UUID, "test:legacy-holder-fallback")); new PersistenceHydrationSystem().tick(0.0f, 0, target.store()); @@ -177,6 +175,55 @@ void hydrationPrefersHolderStorageOverLegacyDtoResource() { } } + @Test + void hydrationFallsBackToLegacyDtoWhenHolderStorageIsMissing() { + StoreFixture fixture = store("legacy-fallback", tempDir.resolve("legacy")); + try { + Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider( + "test:legacy-only-fallback")); + writeLegacyDto(fixture.store(), + legacyResource(LEGACY_SPACE_UUID, "test:legacy-only-fallback")); + + new PersistenceHydrationSystem().tick(0.0f, 0, fixture.store()); + + PhysicsRestoreStatusResource restore = fixture.store().getResource( + PhysicsRestoreStatusResource.getResourceType()); + assertTrue(restore.isHydrated()); + assertFalse(restore.isFailed()); + List rowUuids = rowUuids(fixture.store()); + assertTrue(rowUuids.contains(LEGACY_SPACE_UUID)); + assertFalse(rowUuids.contains(SPACE_UUID)); + } finally { + fixture.close(); + } + } + + @Test + void registeredPhysicsStoreTickDoesNotRewriteLegacyDtoResource() { + StoreFixture fixture = registeredStore("registered-no-dto-capture", + tempDir.resolve("registered")); + try { + fixture.store() + .getResource(PersistentPhysicsStoreResource.getResourceType()) + .setSpaces(new PersistentSpaceDto[] { + new PersistentSpaceDto(LEGACY_SPACE_UUID, + "test:legacy-sentinel", + new Vector3f(0.0f, -9.81f, 0.0f)) + }); + addSpace(fixture.store(), SPACE_UUID); + + fixture.store().tick(0.0f); + + PersistentSpaceDto[] spaces = fixture.store() + .getResource(PersistentPhysicsStoreResource.getResourceType()) + .getSpaces(); + assertEquals(1, spaces.length); + assertEquals(LEGACY_SPACE_UUID, spaces[0].getSpaceUuid()); + } finally { + fixture.close(); + } + } + @Nonnull private static StoreFixture store(@Nonnull String worldName, @Nonnull Path savePath) { ComponentRegistry registry = new ComponentRegistry<>(); @@ -189,6 +236,18 @@ private static StoreFixture store(@Nonnull String worldName, @Nonnull Path saveP return new StoreFixture(registry, store); } + @Nonnull + private static StoreFixture registeredStore(@Nonnull String worldName, @Nonnull Path savePath) { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsStoreRegistration.register(proxy); + PhysicsStore physicsStore = new PhysicsStore(world(worldName, savePath)); + Store store = registry.addStore(physicsStore, EmptyResourceStorage.get()); + return new StoreFixture(registry, store); + } + @Nonnull private static World world(@Nonnull String worldName, @Nonnull Path savePath) { World world = TestInstanceFactory.world(worldName); @@ -288,6 +347,25 @@ private static void publishSnapshot(@Nonnull Store store, true)))); } + @Nonnull + private static PersistentPhysicsStoreResource legacyResource(@Nonnull UUID spaceUuid, + @Nonnull String backendId) { + PersistentPhysicsStoreResource legacy = new PersistentPhysicsStoreResource(); + legacy.setSpaces(new PersistentSpaceDto[] { + new PersistentSpaceDto(spaceUuid, + backendId, + new Vector3f(0.0f, -9.81f, 0.0f)) + }); + return legacy; + } + + private static void writeLegacyDto(@Nonnull Store store, + @Nonnull PersistentPhysicsStoreResource legacy) { + BsonUtil.writeDocument(PersistentPhysicsStoreStorage.file(store.getExternalData()), + PersistentPhysicsStoreResource.CODEC.encode(legacy, new ExtraInfo()).asDocument(), + false).join(); + } + private static Holder holder(@Nonnull List> holders, @Nonnull UUID uuid) { for (Holder holder : holders) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java deleted file mode 100644 index d3787571..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceCaptureSystemTest.java +++ /dev/null @@ -1,258 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.component.EmptyResourceStorage; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentMaterialDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; -import java.util.UUID; -import java.util.function.BiConsumer; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PersistenceCaptureSystemTest { - - @Test - void generatedChunkCollisionRowsAreExcludedFromPersistentDtoTables() { - ComponentRegistry registry = new ComponentRegistry<>(); - ComponentRegistryProxy proxy = - new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsComponentTypeRegistry.registerComponentTypes(proxy); - PhysicsResourceTypes.registerResourceTypes(proxy); - Store store = registry.addStore( - new PhysicsStore(TestInstanceFactory.world("physics-capture-runtime-only-terrain-test")), - EmptyResourceStorage.get()); - try { - UUID spaceUuid = uuid(1); - UUID persistentBodyUuid = uuid(2); - UUID generatedBodyUuid = uuid(3); - Ref spaceRef = addSpace(store, spaceUuid); - store.putComponent(spaceRef, - CollisionFilterComponent.getComponentType(), - new CollisionFilterComponent(0x40, 0x03)); - addBody(store, - persistentBodyUuid, - body(spaceUuid, PhysicsBodyKind.BODY, PhysicsBodyPersistenceMode.PERSISTENT, spaceRef), - null); - Ref generatedRef = addBody(store, - generatedBodyUuid, - body(spaceUuid, PhysicsBodyKind.TERRAIN, PhysicsBodyPersistenceMode.RUNTIME_ONLY, spaceRef), - new ChunkCollisionSourceComponent("0:0:0", - 0, - 0, - 0, - "chunk-collision/0/0/0", - PartKind.BOX, - 0)); - - BodyComponent generatedBody = store.getComponent(generatedRef, - BodyComponent.getComponentType()); - assertNotNull(generatedBody); - assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, - generatedBody.getPersistenceMode()); - assertNotNull(store.getComponent(generatedRef, - ChunkCollisionSourceComponent.getComponentType())); - - capturePersistence(store); - - PersistentPhysicsStoreResource persistent = store.getResource( - PersistentPhysicsStoreResource.getResourceType()); - assertEquals(1, persistent.getSpaces().length); - assertEquals(1, persistent.getBodies().length); - assertEquals(1, persistent.getColliders().length); - assertEquals(1, persistent.getShapes().length); - assertEquals(1, persistent.getMaterials().length); - assertEquals(0x40, persistent.getSpaces()[0].getChunkCollisionGroup()); - assertEquals(0x03, persistent.getSpaces()[0].getChunkCollisionMask()); - assertTrue(containsBody(persistent, persistentBodyUuid)); - assertFalse(containsBody(persistent, generatedBodyUuid)); - assertFalse(containsCollider(persistent, generatedBodyUuid)); - assertFalse(containsShape(persistent, generatedBodyUuid)); - assertFalse(containsMaterial(persistent, generatedBodyUuid)); - } finally { - registry.removeStore(store); - registry.shutdown(); - } - } - - @Nonnull - private static Ref addSpace(@Nonnull Store store, - @Nonnull UUID spaceUuid) { - Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, - spaceUuid, - new SpaceComponent(new BackendId("test:persistence-capture"), - new Vector3f(0.0f, -9.81f, 0.0f))), - AddReason.SPAWN); - assertNotNull(ref); - return ref; - } - - private static void capturePersistence(@Nonnull Store store) { - try { - // Full system registration pulls in unrelated backend-binding dependencies. - Class captureType = Arrays.stream(PersistenceCaptureSystem.class.getDeclaredClasses()) - .filter(candidate -> candidate.getSimpleName().equals("Capture")) - .findFirst() - .orElseThrow(); - Constructor constructor = captureType.getDeclaredConstructor(Map.class); - constructor.setAccessible(true); - Object capture = constructor.newInstance(Map.of()); - Method collectChunk = captureType.getDeclaredMethod("collectChunk", - ArchetypeChunk.class); - collectChunk.setAccessible(true); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> invoke(collectChunk, capture, chunk); - store.forEachChunk(new PersistenceCaptureSystem().getQuery(), collector); - Method writeTo = captureType.getDeclaredMethod("writeTo", - PersistentPhysicsStoreResource.class); - writeTo.setAccessible(true); - invoke(writeTo, - capture, - store.getResource(PersistentPhysicsStoreResource.getResourceType())); - } catch (ReflectiveOperationException exception) { - throw new AssertionError("Could not run PersistenceCaptureSystem capture", exception); - } - } - - private static void invoke(@Nonnull Method method, - @Nonnull Object target, - @Nonnull Object argument) { - try { - method.invoke(target, argument); - } catch (IllegalAccessException exception) { - throw new AssertionError(exception); - } catch (InvocationTargetException exception) { - Throwable cause = exception.getCause(); - if (cause instanceof RuntimeException runtimeException) { - throw runtimeException; - } - if (cause instanceof Error error) { - throw error; - } - throw new AssertionError(cause); - } - } - - @Nonnull - private static Ref addBody(@Nonnull Store store, - @Nonnull UUID bodyUuid, - @Nonnull BodyComponent body, - ChunkCollisionSourceComponent source) { - Holder holder = PhysicsEntities.bodyHolder(store, - bodyUuid, - body, - new DynamicsComponent(PhysicsBodyType.STATIC, 0.0f, 0.0f, 0.0f, false), - target(), - new ColliderComponent(new Vector3f(), new Quaternionf(), false), - new ShapeComponent(ShapeType.BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - PhysicsAxis.Y, - 0.0f, - ""), - new MaterialComponent(0.6f, 0.1f), - new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, - PhysicsCollisionFilters.ALL)); - if (source != null) { - holder.addComponent(ChunkCollisionSourceComponent.getComponentType(), source); - } - Ref ref = store.addEntity(holder, AddReason.SPAWN); - assertNotNull(ref); - return ref; - } - - @Nonnull - private static BodyComponent body(@Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull Ref spaceRef) { - BodyComponent body = new BodyComponent(spaceUuid, kind, persistenceMode); - body.setSpaceRef(spaceRef); - return body; - } - - @Nonnull - private static TargetComponent target() { - TargetComponent target = new TargetComponent(); - target.setPosition(new Vector3f(1.0f, 2.0f, 3.0f)); - return target; - } - - private static boolean containsBody(@Nonnull PersistentPhysicsStoreResource persistent, - @Nonnull UUID bodyUuid) { - return Arrays.stream(persistent.getBodies()) - .anyMatch(body -> bodyUuid.equals(body.getBodyUuid())); - } - - private static boolean containsCollider(@Nonnull PersistentPhysicsStoreResource persistent, - @Nonnull UUID colliderUuid) { - return Arrays.stream(persistent.getColliders()) - .map(PersistentColliderDto::getColliderUuid) - .anyMatch(colliderUuid::equals); - } - - private static boolean containsShape(@Nonnull PersistentPhysicsStoreResource persistent, - @Nonnull UUID shapeUuid) { - return Arrays.stream(persistent.getShapes()) - .map(PersistentShapeDto::getShapeUuid) - .anyMatch(shapeUuid::equals); - } - - private static boolean containsMaterial(@Nonnull PersistentPhysicsStoreResource persistent, - @Nonnull UUID materialUuid) { - return Arrays.stream(persistent.getMaterials()) - .map(PersistentMaterialDto::getMaterialUuid) - .anyMatch(materialUuid::equals); - } - - @Nonnull - private static UUID uuid(long leastSignificantBits) { - return new UUID(0L, leastSignificantBits); - } -} From 0782c10a62a7e7b9551a0113c0d2bc9ce7b50e9d Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 13:01:08 +0200 Subject: [PATCH 470/534] fix(core): preflight physics holder imports Signed-off-by: Blovien --- .../PhysicsStoreHolderPreflight.java | 121 ++++++++++++++++++ .../PhysicsStoreHolderStorage.java | 16 ++- 2 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPreflight.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPreflight.java new file mode 100644 index 00000000..c8b15007 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPreflight.java @@ -0,0 +1,121 @@ +package dev.hytalemodding.impulse.core.internal.persistence; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; + +/** + * Validates decoded holder rows before mutating the live PhysicsStore. + */ +final class PhysicsStoreHolderPreflight { + + private static final UUID NIL_UUID = new UUID(0L, 0L); + + private PhysicsStoreHolderPreflight() { + } + + @Nonnull + static Result validate(@Nonnull List> holders) { + List errors = new ArrayList<>(); + ObjectOpenHashSet seen = new ObjectOpenHashSet<>(); + ObjectOpenHashSet spaces = new ObjectOpenHashSet<>(); + ObjectOpenHashSet bodies = new ObjectOpenHashSet<>(); + List bodyRows = new ArrayList<>(); + List jointRows = new ArrayList<>(); + + for (Holder holder : holders) { + UuidComponent uuidComponent = holder.getComponent(UuidComponent.getComponentType()); + if (uuidComponent == null || NIL_UUID.equals(uuidComponent.getUuid())) { + errors.add("PhysicsStore holder row is missing a durable UUID"); + continue; + } + UUID uuid = uuidComponent.getUuid(); + if (!seen.add(uuid)) { + errors.add("Duplicate PhysicsStore holder UUID: " + uuid); + } + + SpaceComponent space = holder.getComponent(SpaceComponent.getComponentType()); + BodyComponent body = holder.getComponent(BodyComponent.getComponentType()); + JointComponent joint = holder.getComponent(JointComponent.getComponentType()); + int primaryComponents = (space != null ? 1 : 0) + + (body != null ? 1 : 0) + + (joint != null ? 1 : 0); + if (primaryComponents == 0) { + errors.add("PhysicsStore holder " + uuid + + " has no space, body, or joint component"); + continue; + } + if (primaryComponents > 1) { + errors.add("PhysicsStore holder " + uuid + + " mixes space, body, and joint components"); + continue; + } + if (space != null) { + spaces.add(uuid); + } else if (body != null) { + bodies.add(uuid); + bodyRows.add(new BodyRow(uuid, body.getSpaceUuid())); + } else { + jointRows.add(new JointRow(uuid, + joint.getSpaceUuid(), + joint.getBodyAUuid(), + joint.getBodyBUuid())); + } + } + + validateBodyRows(bodyRows, spaces, errors); + validateJointRows(jointRows, spaces, bodies, errors); + return new Result(errors.isEmpty(), List.copyOf(errors)); + } + + private static void validateBodyRows(@Nonnull List bodyRows, + @Nonnull ObjectOpenHashSet spaces, + @Nonnull List errors) { + for (BodyRow body : bodyRows) { + if (NIL_UUID.equals(body.spaceUuid()) || !spaces.contains(body.spaceUuid())) { + errors.add("PhysicsStore holder body " + body.uuid() + + " references missing space " + body.spaceUuid()); + } + } + } + + private static void validateJointRows(@Nonnull List jointRows, + @Nonnull ObjectOpenHashSet spaces, + @Nonnull ObjectOpenHashSet bodies, + @Nonnull List errors) { + for (JointRow joint : jointRows) { + if (NIL_UUID.equals(joint.spaceUuid()) || !spaces.contains(joint.spaceUuid())) { + errors.add("PhysicsStore holder joint " + joint.uuid() + + " references missing space " + joint.spaceUuid()); + } + if (NIL_UUID.equals(joint.bodyAUuid()) || !bodies.contains(joint.bodyAUuid())) { + errors.add("PhysicsStore holder joint " + joint.uuid() + + " references missing body A " + joint.bodyAUuid()); + } + if (NIL_UUID.equals(joint.bodyBUuid()) || !bodies.contains(joint.bodyBUuid())) { + errors.add("PhysicsStore holder joint " + joint.uuid() + + " references missing body B " + joint.bodyBUuid()); + } + } + } + + record Result(boolean valid, @Nonnull List errors) { + } + + private record BodyRow(@Nonnull UUID uuid, @Nonnull UUID spaceUuid) { + } + + private record JointRow(@Nonnull UUID uuid, + @Nonnull UUID spaceUuid, + @Nonnull UUID bodyAUuid, + @Nonnull UUID bodyBUuid) { + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java index 23da48c9..fdabbf5e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java @@ -13,6 +13,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -81,11 +82,20 @@ public static LoadResult load(@Nonnull Store store) { } BsonArray holders = document.getArray(HOLDERS_FIELD, new BsonArray()); - int loaded = 0; + List> decodedHolders = new ArrayList<>(holders.size()); for (BsonValue value : holders) { - Holder holder = PhysicsStoreHolderPersistence.decodeHolder( + decodedHolders.add(PhysicsStoreHolderPersistence.decodeHolder( store.getRegistry(), - value.asBinary().getData()); + value.asBinary().getData())); + } + PhysicsStoreHolderPreflight.Result preflight = PhysicsStoreHolderPreflight.validate( + decodedHolders); + if (!preflight.valid()) { + throw new IllegalStateException(String.join("; ", preflight.errors())); + } + + int loaded = 0; + for (Holder holder : decodedHolders) { store.addEntity(holder, AddReason.LOAD); loaded++; } From a57975063831f33d522b32859c91f4c49ab04002 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 13:01:26 +0200 Subject: [PATCH 471/534] test(core): cover invalid physics holder imports Signed-off-by: Blovien --- .../PhysicsStoreHolderPersistenceTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java index e8ff07ee..5776c2b0 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java @@ -52,12 +52,18 @@ import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.Field; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonBinary; +import org.bson.BsonDocument; +import org.bson.BsonInt32; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -224,6 +230,60 @@ void registeredPhysicsStoreTickDoesNotRewriteLegacyDtoResource() { } } + @Test + void holderHydrationRejectsDuplicateUuidWithoutAddingPartialRows() { + StoreFixture fixture = store("holder-duplicate-uuid", + tempDir.resolve("duplicate-uuid")); + try { + Holder first = PhysicsEntities.spaceHolder(fixture.store(), + SPACE_UUID, + new SpaceComponent(new BackendId("test:holder-persistence"), + new Vector3f(0.0f, -9.81f, 0.0f))); + Holder second = fixture.store().getRegistry().newHolder(); + second.addComponent(UuidComponent.getComponentType(), new UuidComponent(SPACE_UUID)); + second.addComponent(BodyComponent.getComponentType(), + new BodyComponent(SPACE_UUID, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.PERSISTENT)); + writeHolderStorage(fixture.store(), List.of(first, second)); + + new PersistenceHydrationSystem().tick(0.0f, 0, fixture.store()); + + PhysicsRestoreStatusResource restore = fixture.store().getResource( + PhysicsRestoreStatusResource.getResourceType()); + assertTrue(restore.isFailed()); + assertFalse(restore.isHydrated()); + assertTrue(rowUuids(fixture.store()).isEmpty()); + } finally { + fixture.close(); + } + } + + @Test + void holderHydrationRejectsBodyWithoutSavedSpaceWithoutAddingPartialRows() { + StoreFixture fixture = store("holder-missing-space", + tempDir.resolve("missing-space")); + try { + Holder body = fixture.store().getRegistry().newHolder(); + body.addComponent(UuidComponent.getComponentType(), new UuidComponent(BODY_A_UUID)); + body.addComponent(BodyComponent.getComponentType(), + new BodyComponent(SPACE_UUID, + PhysicsBodyKind.BODY, + PhysicsBodyPersistenceMode.PERSISTENT)); + writeHolderStorage(fixture.store(), List.of(body)); + + new PersistenceHydrationSystem().tick(0.0f, 0, fixture.store()); + + PhysicsRestoreStatusResource restore = fixture.store().getResource( + PhysicsRestoreStatusResource.getResourceType()); + assertTrue(restore.isFailed()); + assertFalse(restore.isHydrated()); + assertTrue(rowUuids(fixture.store()).isEmpty()); + } finally { + fixture.close(); + } + } + @Nonnull private static StoreFixture store(@Nonnull String worldName, @Nonnull Path savePath) { ComponentRegistry registry = new ComponentRegistry<>(); @@ -366,6 +426,28 @@ private static void writeLegacyDto(@Nonnull Store store, false).join(); } + private static void writeHolderStorage(@Nonnull Store store, + @Nonnull List> holders) { + BsonArray holderBlobs = new BsonArray(); + for (Holder holder : holders) { + holderBlobs.add(new BsonBinary(PhysicsStoreHolderPersistence.encodeHolder(store, + holder))); + } + BsonDocument document = new BsonDocument() + .append("SchemaVersion", new BsonInt32(1)) + .append("Holders", holderBlobs); + Path file = PhysicsStoreHolderStorage.file(store.getExternalData()); + try { + Path parent = file.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + Files.write(file, BsonUtil.writeToBytes(document)); + } catch (IOException exception) { + throw new AssertionError("Failed to write test holder storage", exception); + } + } + private static Holder holder(@Nonnull List> holders, @Nonnull UUID uuid) { for (Holder holder : holders) { From 952ceea265896f66aec99afd9fa01f613de21ae5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 13:11:52 +0200 Subject: [PATCH 472/534] fix(core): tolerate missing physics shutdown resources Signed-off-by: Blovien --- .../core/internal/registration/PhysicsStoreRegistration.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 046d8056..96c3b5ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -146,6 +146,9 @@ private static > void cleanupResource( @Nonnull ResourceType type, @Nonnull Consumer cleanup) { T resource = store.getResource(type); + if (resource == null) { + return; + } cleanup.accept(resource); } From 9523e8b1da8a6ed9054dd1eb3a902e217dc914ef Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 13:13:33 +0200 Subject: [PATCH 473/534] fix(examples): destroy explosive source body Signed-off-by: Blovien --- .../examples/systems/ExplosiveFuseTickSystem.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 23cee10d..2032fb68 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -104,6 +104,7 @@ public void tick(float dt, spaceId, center, explosive); + destroySourceBody(world, attachment); commandBuffer.removeEntity(ref, RemoveReason.REMOVE); } @@ -160,6 +161,16 @@ private static BodyMotionSnapshot bodySnapshot(@Nonnull Store store return snapshot != null ? BodyMotionSnapshot.from(snapshot) : null; } + private static void destroySourceBody(@Nonnull World world, + @Nonnull BodyAttachmentComponent attachment) { + Ref bodyRef = attachment.getBodyRef(); + if (bodyRef != null && bodyRef.isValid()) { + PhysicsBodies.destroyAsync(world, bodyRef); + return; + } + PhysicsBodies.destroyAsync(world, attachment.getBodyUuid()); + } + private record BodyMotionSnapshot(float positionX, float positionY, float positionZ, From 378bc15d2b25f3f0a4779e5fb0cfb00b667bfcf4 Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 16:49:45 +0200 Subject: [PATCH 474/534] refactor(physicschunk): own physics store registrations Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkStoreTypes.java | 6 ++--- .../physicschunk/PhysicsChunkSubPlugin.java | 8 ++++++ .../PhysicsStoreRegistration.java | 4 --- ...PhysicsChunkRegistrationOwnershipTest.java | 27 +++++++++++++++++++ 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java index 27de9f01..376aef8c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java @@ -37,17 +37,17 @@ public static void registerPhysicsStoreResourceTypes( PhysicsChunkComponentSyncResource::new)); } - public static void registerPhysicsStoreSpaceBindingSystems( + public static void registerSpaceBindingSystems( @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); } - public static void registerPhysicsStorePreBodyBindingSystems( + public static void registerPreBodyBindingSystems( @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new ChunkCollisionMutationDrainSystem()); } - public static void registerPhysicsStorePostBodyBindingSystems( + public static void registerPostBodyBindingSystems( @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new ChunkCollisionComponentSyncSystem()); registry.registerSystem(new ChunkCollisionVoxelStitchingSystem()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java index 2adea839..e3f4f493 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java @@ -5,7 +5,9 @@ import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -23,8 +25,14 @@ public PhysicsChunkSubPlugin(@Nonnull JavaPluginInit init) { @Override protected void setup() { ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); + ComponentRegistryProxy physicsRegistry = + PhysicsStoreRegistration.physicsStoreRegistry(this); PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(physicsRegistry); + PhysicsChunkStoreTypes.registerSpaceBindingSystems(physicsRegistry); + PhysicsChunkStoreTypes.registerPreBodyBindingSystems(physicsRegistry); + PhysicsChunkStoreTypes.registerPostBodyBindingSystems(physicsRegistry); PhysicsChunkCommandContributions.register(); PhysicsChunkLifecycle.enable(); LOGGER.at(Level.INFO).log("Impulse PhysicsChunk collision producer enabled."); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 96c3b5ff..cd41f164 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -70,16 +70,12 @@ public static void register(@Nonnull ComponentRegistryProxy regist PhysicsStoreHooks.registerTickGate(STEP_TICK_GATE); PhysicsResourceTypes.registerResourceTypes(registry); - PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(registry); registry.registerSystem(new PersistenceHydrationSystem()); registry.registerSystem(new IdentityIndexSystem()); - PhysicsChunkStoreTypes.registerPhysicsStoreSpaceBindingSystems(registry); registry.registerSystem(new SpaceBindingSystem()); registry.registerSystem(new SpaceSettingsApplicationSystem()); - PhysicsChunkStoreTypes.registerPhysicsStorePreBodyBindingSystems(registry); registry.registerSystem(new BodyBindingSystem()); - PhysicsChunkStoreTypes.registerPhysicsStorePostBodyBindingSystems(registry); registry.registerSystem(new ColliderBindingSystem()); registry.registerSystem(new JointBindingSystem()); registry.registerSystem(new StaleBodyRemovalSystem()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java new file mode 100644 index 00000000..c031e772 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java @@ -0,0 +1,27 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class PhysicsChunkRegistrationOwnershipTest { + + @Test + void physicsChunkSubPluginOwnsPhysicsStoreRegistrations() throws IOException { + String coreRegistration = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java")); + String chunkSubPlugin = Files.readString(Path.of( + "src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java")); + + assertFalse(coreRegistration.contains("PhysicsChunkStoreTypes.register")); + assertTrue(chunkSubPlugin.contains("PhysicsStoreRegistration.physicsStoreRegistry(this)")); + assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(")); + assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerSpaceBindingSystems(")); + assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerPreBodyBindingSystems(")); + assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerPostBodyBindingSystems(")); + } +} From f4b5ed46469b1c2df1ebd2f63acd7a369490498d Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 16:49:54 +0200 Subject: [PATCH 475/534] refactor(api): remove unused simulation helpers Signed-off-by: Blovien --- .../settings/PhysicsSolverSettings.java | 22 ++---------- .../core/plugin/simulation/RigidBodyPose.java | 36 ------------------- 2 files changed, 2 insertions(+), 56 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodyPose.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSolverSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSolverSettings.java index d15f23ed..636e5d49 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSolverSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSolverSettings.java @@ -1,10 +1,12 @@ package dev.hytalemodding.impulse.core.plugin.settings; +import lombok.Getter; import javax.annotation.Nonnull; /** * Portable solver and activation tuning for a physics space. */ +@Getter public class PhysicsSolverSettings { /** @@ -68,10 +70,6 @@ public PhysicsSolverSettings(@Nonnull PhysicsSolverSettings settings) { dynamicSleepTimeUntilSleep = settings.dynamicSleepTimeUntilSleep; } - public int getSolverIterations() { - return solverIterations; - } - public void setSolverIterations(int solverIterations) { if (solverIterations < 1) { throw new IllegalArgumentException("Solver iterations must be positive"); @@ -79,10 +77,6 @@ public void setSolverIterations(int solverIterations) { this.solverIterations = solverIterations; } - public int getStabilizationIterations() { - return stabilizationIterations; - } - public void setStabilizationIterations(int stabilizationIterations) { if (stabilizationIterations < 0) { throw new IllegalArgumentException("Stabilization iterations cannot be negative"); @@ -98,10 +92,6 @@ public void setDynamicSleepTuning(float linearThreshold, setDynamicSleepTimeUntilSleep(timeUntilSleep); } - public float getDynamicSleepLinearThreshold() { - return dynamicSleepLinearThreshold; - } - public void setDynamicSleepLinearThreshold(float dynamicSleepLinearThreshold) { this.dynamicSleepLinearThreshold = PhysicsSettingsValidation.requireFiniteAtLeast( "Dynamic sleep linear threshold", @@ -109,10 +99,6 @@ public void setDynamicSleepLinearThreshold(float dynamicSleepLinearThreshold) { 0.0f); } - public float getDynamicSleepAngularThreshold() { - return dynamicSleepAngularThreshold; - } - public void setDynamicSleepAngularThreshold(float dynamicSleepAngularThreshold) { this.dynamicSleepAngularThreshold = PhysicsSettingsValidation.requireFiniteAtLeast( "Dynamic sleep angular threshold", @@ -120,10 +106,6 @@ public void setDynamicSleepAngularThreshold(float dynamicSleepAngularThreshold) 0.0f); } - public float getDynamicSleepTimeUntilSleep() { - return dynamicSleepTimeUntilSleep; - } - public void setDynamicSleepTimeUntilSleep(float dynamicSleepTimeUntilSleep) { this.dynamicSleepTimeUntilSleep = PhysicsSettingsValidation.requireFiniteAtLeast( "Dynamic sleep time", diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodyPose.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodyPose.java deleted file mode 100644 index 685d92e2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodyPose.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Copied rigid body transform. - */ -public record RigidBodyPose(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - - public RigidBodyPose { - position = new Vector3f(Objects.requireNonNull(position, "position")); - rotation = new Quaternionf(Objects.requireNonNull(rotation, "rotation")); - } - - @Nonnull - public static RigidBodyPose of(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation) { - return new RigidBodyPose(position, rotation); - } - - @Nonnull - @Override - public Vector3f position() { - return new Vector3f(position); - } - - @Nonnull - @Override - public Quaternionf rotation() { - return new Quaternionf(rotation); - } -} From 18e206f249d48e378562f811fa8be9b039b9fdbe Mon Sep 17 00:00:00 2001 From: Blovien Date: Fri, 19 Jun 2026 16:50:16 +0200 Subject: [PATCH 476/534] refactor(core): remove body registration metadata Signed-off-by: Blovien --- .../core/internal/commands/CleanCommand.java | 8 +- .../core/internal/commands/SpaceCommand.java | 9 +- .../crucible/ImpulseApiCrucibleTests.java | 6 +- ...tachedStreamingBenchmarkCrucibleTests.java | 6 +- .../crucible/ImpulseLiveCrucibleTests.java | 4 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 10 +- .../PhysicsStoreBenchmarkQueries.java | 23 +- .../crucible/PhysicsStoreCrucibleSupport.java | 10 +- .../ChunkCollisionSourceComponent.java | 2 +- .../persistence/PersistentBodyDto.java | 36 --- .../PhysicsStoreHolderPersistence.java | 4 +- .../PhysicsStoreTopologyMutations.java | 1 - .../PhysicsBodyRegistrationResource.java | 122 +++++----- .../resources/PhysicsWorldLifecycleState.java | 14 +- .../PhysicsWorldRuntimeResource.java | 41 ++-- .../resources/PhysicsWorldSnapshotState.java | 14 +- .../body/PhysicsBodyRegistration.java | 8 +- .../resources/body/PhysicsBodyRegistry.java | 132 ++--------- .../resources/body/PhysicsBodyRuntime.java | 12 +- .../body/PhysicsBodySnapshotRefVisitor.java | 6 +- .../body/PhysicsBodySnapshotStore.java | 16 +- .../body/PhysicsBodySnapshotVisitor.java | 6 +- .../body/PhysicsBodySpatialIndex.java | 26 +-- .../internal/systems/BodyBindingSystem.java | 3 +- .../ChunkCollisionMutationDrainSystem.java | 11 +- .../ChunkCollisionVoxelStitchingSystem.java | 3 +- .../CompletedStepPublicationSystem.java | 19 +- .../systems/PersistenceHydrationSystem.java | 4 +- .../PhysicsChunkSettingsIndexSystem.java | 3 +- .../internal/systems/SpaceBindingSystem.java | 3 +- .../internal/systems/TargetBindingSystem.java | 1 - .../systems/debug/PhysicsDebugSystem.java | 21 +- .../PhysicsProjectionCleanupSystem.java | 2 +- .../core/plugin/body/PhysicsBodyKind.java | 15 -- .../body/PhysicsBodyPersistenceMode.java | 10 - .../body/PhysicsBodyRegistrationView.java | 22 -- .../core/plugin/components/BodyComponent.java | 45 +--- .../physicschunk/PhysicsChunkCollision.java | 34 ++- .../plugin/physicsstore/PhysicsBodies.java | 56 +++-- .../physicsstore/PhysicsBodyEntities.java | 27 +-- .../snapshot/PhysicsBodySnapshotEntry.java | 10 +- .../PublishedPhysicsBodyFrameStorage.java | 44 ---- .../PublishedPhysicsBodySnapshot.java | 44 ---- .../PublishedPhysicsBodySnapshotCursor.java | 8 - .../PublishedPhysicsSnapshotFrame.java | 6 - .../CleanCommandLifecycleGuardTest.java | 77 ++++--- ...csBodyRegistrationMetadataRemovalTest.java | 44 ++++ .../PhysicsStoreTopologyMutationsTest.java | 17 +- .../PersistentPhysicsStoreResourceTest.java | 4 - .../PhysicsStoreHolderPersistenceTest.java | 26 +-- .../body/PhysicsBodyRegistryTest.java | 34 +-- .../body/PhysicsBodySnapshotStoreTest.java | 20 +- .../PhysicsWorldLifecycleStateTest.java | 8 +- ...ChunkCollisionComponentSyncSystemTest.java | 10 +- ...ChunkCollisionMutationDrainSystemTest.java | 6 - ...hunkCollisionVoxelStitchingSystemTest.java | 6 +- .../PublishedPhysicsSnapshotFrameTest.java | 14 -- .../examples/commands/DropCommand.java | 72 +++++- .../examples/commands/GrabCommand.java | 16 +- .../examples/commands/MaterialsCommand.java | 20 +- .../commands/PhysicsStoreExampleCommands.java | 6 +- .../examples/commands/ShapesCommand.java | 22 +- .../stress/StressBenchmarkCommand.java | 4 - .../commands/stress/StressBodiesCommand.java | 4 - .../stress/StressRawBodiesCommand.java | 4 - .../commands/stress/StressShapesCommand.java | 22 +- .../explosive/ExplosiveBlockRuntime.java | 3 +- .../systems/ExplosiveFuseContactSystem.java | 8 +- .../systems/ExplosiveFuseTickSystem.java | 11 +- .../examples/utils/ExamplePhysicsUtils.java | 215 +----------------- .../examples/commands/DropCommandTest.java | 25 ++ 71 files changed, 531 insertions(+), 1074 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyPersistenceMode.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java create mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 1b9860ac..a5a97efd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -19,10 +19,9 @@ import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; @@ -383,9 +382,8 @@ private static SelectedBodies selectBodiesNear(@Nonnull Store stor Set bodyUuids = new ObjectOpenHashSet<>(); double radiusSquared = (double) radius * radius; for (PhysicsBodySnapshot snapshot : PhysicsBodies.snapshotFrame(store).bodies()) { - PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView(store, - snapshot.bodyUuid()); - if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { + if (!PhysicsBodies.isRegistered(store, snapshot.bodyUuid()) + || PhysicsChunkCollision.isChunkCollisionBody(store, snapshot.bodyUuid())) { continue; } Vector3f position = snapshot.position(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index e3559685..2e2fae16 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -17,7 +17,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; @@ -237,13 +236,7 @@ private static SpaceCounts countSpaceContents(@Nonnull List summar private static int countRegisteredBodies(@Nonnull Store physicsStore, @Nonnull SpaceId spaceId) { - int count = 0; - for (PhysicsBodyRegistrationView registration : PhysicsBodies.registrationViews(physicsStore)) { - if (registration.spaceId().equals(spaceId)) { - count++; - } - } - return count; + return PhysicsBodies.registrationCount(physicsStore, spaceId); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index 8141d7a7..aa52936e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -17,7 +17,6 @@ import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; @@ -236,7 +235,7 @@ private static CompletionStage populatedBodyCleanup( _ -> { boolean spaceEmpty = bodyCount == 0; boolean noRegistrations = - PhysicsBodies.registrationViews(state.store()).isEmpty(); + PhysicsBodies.bodyUuids(state.store()).isEmpty(); boolean removedSpace = true; if (checkSpaceRemoval || spaceEmpty) { PhysicsStoreSpaceMutations.removeEmptySpace( @@ -310,8 +309,7 @@ private static Ref addCrucibleBox(@Nonnull Store sto PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.defaults(), - null, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + null); return store.addEntity(PhysicsEntities.bodyHolder(store, descriptor.bodyUuid(), descriptor.body(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 58ddad36..4c3f8b98 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -26,8 +26,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; @@ -462,9 +460,7 @@ private void spawnDetachedBodies(@Nonnull SpaceId spaceId, int count) { PhysicsBodyType.DYNAMIC, 1.0f, settings, - null, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + null); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 93b30fc4..15b888e5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -25,7 +25,6 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -153,8 +152,7 @@ private static void submitLiveBody(Store store, PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.defaults(), - null, - PhysicsBodyPersistenceMode.PERSISTENT); + null); store.addEntity(PhysicsEntities.bodyHolder(store, descriptor.bodyUuid(), descriptor.body(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index 2c51227b..b8d60c89 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -22,8 +22,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; @@ -268,9 +266,7 @@ private CompletionStage populateBenchmarkSpace(@Nonnull SpaceId spa PhysicsBodyType.STATIC, 0.0f, groundSettings, - null, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + null); PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); for (int i = 0; i < matrixCase.count(); i++) { PhysicsStoreCrucibleSupport.addBody(physicsStore, @@ -283,9 +279,7 @@ private CompletionStage populateBenchmarkSpace(@Nonnull SpaceId spa PhysicsBodyType.DYNAMIC, 1.0f, bodySettings, - null, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + null); } return CompletableFuture.completedFuture(StartedCase.started(spaceId)); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index a293127e..dabf4bf9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -8,9 +8,9 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; @@ -66,15 +66,17 @@ private static void collectBodyRows(@Nonnull ArchetypeChunk chunk, continue; } ShapeComponent shape = chunk.getComponent(index, ShapeComponent.getComponentType()); - classifyBody(stats, body, shape, snapshot, query); + boolean chunkCollisionBody = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()) != null; + classifyBody(stats, shape, snapshot, query, chunkCollisionBody); } } private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, - @Nonnull BodyComponent body, @Nullable ShapeComponent shape, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull BenchmarkSpaceStatsRequest query) { + @Nonnull BenchmarkSpaceStatsRequest query, + boolean chunkCollisionBody) { stats.bodies++; if (snapshot.bodyType() == PhysicsBodyType.DYNAMIC) { stats.dynamicBodies++; @@ -100,18 +102,13 @@ private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, } } - if (body.getKind() == PhysicsBodyKind.BODY) { - stats.detachedBodies++; - return; - } - if (shape != null && shape.getShapeType() == ShapeType.PLANE) { - return; - } - if (body.getKind().isTerrain()) { + if (chunkCollisionBody) { stats.terrainBodies++; return; } - stats.rawBodies++; + if (shape == null || shape.getShapeType() != ShapeType.PLANE) { + stats.detachedBodies++; + } } private static final class BenchmarkSpaceStatsAccumulator { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index dae8a47b..25f199fb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -9,8 +9,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; @@ -48,9 +46,7 @@ static Ref addBody(@Nonnull Store store, @Nonnull PhysicsBodyType bodyType, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nullable Vector3f linearVelocity) { PhysicsThreading.requireWorldThread(store, "add Crucible PhysicsStore body entity"); Ref spaceRef = requireSpaceRef(store, spaceId); BodyEntityDescriptor descriptor = PhysicsBodyEntities.body( @@ -61,9 +57,7 @@ static Ref addBody(@Nonnull Store store, bodyType, mass, settings, - linearVelocity, - kind, - persistenceMode); + linearVelocity); return store.addEntity(PhysicsEntities.bodyHolder(store, descriptor.bodyUuid(), descriptor.body(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java index 291c39dc..f2462431 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java @@ -12,7 +12,7 @@ import javax.annotation.Nonnull; /** - * Internal PhysicsChunk source metadata for generated runtime-only chunk collision body rows. + * Internal PhysicsChunk source metadata for generated chunk collision body rows. */ public final class ChunkCollisionSourceComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java index f77f2356..3d550e07 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java @@ -7,8 +7,6 @@ import com.hypixel.hytale.codec.codecs.array.ArrayCodec; import com.hypixel.hytale.codec.validation.Validators; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Arrays; import java.util.Objects; import java.util.UUID; @@ -31,20 +29,6 @@ public final class PersistentBodyDto { PersistentBodyDto::getSpaceUuid) .addValidator(Validators.nonNull()) .add() - .append(new KeyedCodec<>("Kind", new EnumCodec<>(PhysicsBodyKind.class), false), - (dto, value) -> dto.kind = value != null ? value : PhysicsBodyKind.BODY, - PersistentBodyDto::getKind) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("PersistenceMode", - new EnumCodec<>(PhysicsBodyPersistenceMode.class), - false), - (dto, value) -> dto.persistenceMode = value != null - ? value - : PhysicsBodyPersistenceMode.RUNTIME_ONLY, - PersistentBodyDto::getPersistenceMode) - .addValidator(Validators.nonNull()) - .add() .append(new KeyedCodec<>("BodyType", new EnumCodec<>(PhysicsBodyType.class), false), (dto, value) -> dto.bodyType = value != null ? value : PhysicsBodyType.DYNAMIC, PersistentBodyDto::getBodyType) @@ -94,10 +78,6 @@ public final class PersistentBodyDto { @Nonnull private UUID spaceUuid = new UUID(0L, 0L); @Nonnull - private PhysicsBodyKind kind = PhysicsBodyKind.BODY; - @Nonnull - private PhysicsBodyPersistenceMode persistenceMode = PhysicsBodyPersistenceMode.RUNTIME_ONLY; - @Nonnull private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; private float mass = 1.0f; private float linearDamping; @@ -113,8 +93,6 @@ public PersistentBodyDto() { public PersistentBodyDto(@Nonnull UUID bodyUuid, @Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull PhysicsBodyType bodyType, float mass, float linearDamping, @@ -124,8 +102,6 @@ public PersistentBodyDto(@Nonnull UUID bodyUuid, @Nonnull PersistentBodyRuntimeStateDto runtimeState) { this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.kind = Objects.requireNonNull(kind, "kind"); - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); this.mass = mass; this.linearDamping = linearDamping; @@ -145,16 +121,6 @@ public UUID getSpaceUuid() { return spaceUuid; } - @Nonnull - public PhysicsBodyKind getKind() { - return kind; - } - - @Nonnull - public PhysicsBodyPersistenceMode getPersistenceMode() { - return persistenceMode; - } - @Nonnull public PhysicsBodyType getBodyType() { return bodyType; @@ -190,8 +156,6 @@ public PersistentBodyRuntimeStateDto getRuntimeState() { public PersistentBodyDto copy() { return new PersistentBodyDto(bodyUuid, spaceUuid, - kind, - persistenceMode, bodyType, mass, linearDamping, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java index 44f2d20e..58ee4cad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java @@ -10,7 +10,6 @@ import com.hypixel.hytale.server.core.util.BsonUtil; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -198,8 +197,7 @@ private void patchBodyTarget(@Nonnull UUID rowUuid, private static boolean shouldPersistBody(@Nonnull ArchetypeChunk chunk, int index, @Nonnull BodyComponent body) { - return body.getPersistenceMode() == PhysicsBodyPersistenceMode.PERSISTENT - && chunk.getComponent(index, ColliderComponent.getComponentType()) != null + return chunk.getComponent(index, ColliderComponent.getComponentType()) != null && chunk.getComponent(index, ShapeComponent.getComponentType()) != null && chunk.getComponent(index, MaterialComponent.getComponentType()) != null && chunk.getComponent(index, CollisionFilterComponent.getComponentType()) != null; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java index a35bb75b..d3829a4e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java @@ -198,7 +198,6 @@ private static List collectChunkCollisionRows(@Nonnull Store { @@ -30,56 +28,61 @@ public PhysicsBodyRegistrationResource() { } @Nullable - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull UUID bodyUuid) { - return registrations.viewsByUuid().get(Objects.requireNonNull(bodyUuid, "bodyUuid")); + public SpaceId getBodySpaceId(@Nonnull UUID bodyUuid) { + return registrations.spaceIdsByUuid().get(Objects.requireNonNull(bodyUuid, "bodyUuid")); } @Nullable - public PhysicsBodyRegistrationView getBodyRegistrationView(@Nonnull Ref bodyRef) { - RegistrationByRef registration = registrations.viewsByRowIndex() + public SpaceId getBodySpaceId(@Nonnull Ref bodyRef) { + RegistrationByRef registration = registrations.registrationsByRowIndex() .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); return registration != null && sameRef(registration.bodyRef(), bodyRef) - ? registration.view() + ? registration.spaceId() : null; } + @Nullable + public UUID getBodyUuid(@Nonnull Ref bodyRef) { + RegistrationByRef registration = registrations.registrationsByRowIndex() + .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); + return registration != null && sameRef(registration.bodyRef(), bodyRef) + ? registration.bodyUuid() + : null; + } + + public boolean hasBody(@Nonnull UUID bodyUuid) { + return registrations.spaceIdsByUuid() + .containsKey(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + public boolean hasBody(@Nonnull Ref bodyRef) { + return getBodyUuid(bodyRef) != null; + } + @Nonnull - public Collection getBodyRegistrationViews() { - return registrations.views(); + public Collection getBodyUuids() { + return registrations.bodyUuids(); } public int getBodyRegistrationCount() { - return registrations.views().size(); + return registrations.bodyUuids().size(); } public boolean isCurrent(long registrationTopologyGeneration) { return registrations.registrationTopologyGeneration() == registrationTopologyGeneration; } - public int getBodyRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { - Objects.requireNonNull(persistenceMode, "persistenceMode"); + public int getBodyRegistrationCount(@Nonnull SpaceId spaceId) { + Objects.requireNonNull(spaceId, "spaceId"); int count = 0; - for (PhysicsBodyRegistrationView view : registrations.views()) { - if (view.persistenceMode() == persistenceMode) { + for (SpaceId registeredSpaceId : registrations.spaceIdsByUuid().values()) { + if (registeredSpaceId.equals(spaceId)) { count++; } } return count; } - @Nonnull - public Collection getBodyRegistrationViews( - @Nonnull PhysicsBodyKind kind) { - Objects.requireNonNull(kind, "kind"); - List views = new ArrayList<>(); - for (PhysicsBodyRegistrationView view : registrations.views()) { - if (view.kind() == kind) { - views.add(view); - } - } - return views; - } - public void publish(long registrationTopologyGeneration, @Nonnull Collection publications) { Object2ObjectLinkedOpenHashMap publicationsByUuid = @@ -87,42 +90,43 @@ public void publish(long registrationTopologyGeneration, for (BodyRegistrationPublication publication : publications) { BodyRegistrationPublication checkedPublication = Objects.requireNonNull(publication, "publication"); - publicationsByUuid.put(checkedPublication.view().bodyUuid(), checkedPublication); + publicationsByUuid.put(checkedPublication.bodyUuid(), checkedPublication); } - Object2ObjectLinkedOpenHashMap viewsByUuid = + Object2ObjectLinkedOpenHashMap spaceIdsByUuid = new Object2ObjectLinkedOpenHashMap<>(publicationsByUuid.size()); - Int2ObjectOpenHashMap viewsByRowIndex = + Int2ObjectOpenHashMap registrationsByRowIndex = new Int2ObjectOpenHashMap<>(publicationsByUuid.size()); for (BodyRegistrationPublication publication : publicationsByUuid.values()) { - PhysicsBodyRegistrationView registration = publication.view(); - viewsByUuid.put(registration.bodyUuid(), registration); - viewsByRowIndex.put(publication.bodyRef().getIndex(), - new RegistrationByRef(publication.bodyRef(), registration)); + spaceIdsByUuid.put(publication.bodyUuid(), publication.spaceId()); + registrationsByRowIndex.put(publication.bodyRef().getIndex(), + new RegistrationByRef(publication.bodyRef(), + publication.bodyUuid(), + publication.spaceId())); } registrations = new PublishedRegistrations(registrationTopologyGeneration, - List.copyOf(viewsByUuid.values()), - viewsByUuid, - viewsByRowIndex); + new ArrayList<>(spaceIdsByUuid.keySet()), + spaceIdsByUuid, + registrationsByRowIndex); } public void removeBody(@Nonnull UUID bodyUuid) { Objects.requireNonNull(bodyUuid, "bodyUuid"); PublishedRegistrations current = registrations; - if (!current.viewsByUuid().containsKey(bodyUuid)) { + if (!current.spaceIdsByUuid().containsKey(bodyUuid)) { return; } - Object2ObjectLinkedOpenHashMap viewsByUuid = - new Object2ObjectLinkedOpenHashMap<>(current.viewsByUuid()); - viewsByUuid.remove(bodyUuid); - Int2ObjectOpenHashMap viewsByRowIndex = - new Int2ObjectOpenHashMap<>(current.viewsByRowIndex()); - viewsByRowIndex.int2ObjectEntrySet() - .removeIf(entry -> entry.getValue().view().bodyUuid().equals(bodyUuid)); + Object2ObjectLinkedOpenHashMap spaceIdsByUuid = + new Object2ObjectLinkedOpenHashMap<>(current.spaceIdsByUuid()); + spaceIdsByUuid.remove(bodyUuid); + Int2ObjectOpenHashMap registrationsByRowIndex = + new Int2ObjectOpenHashMap<>(current.registrationsByRowIndex()); + registrationsByRowIndex.int2ObjectEntrySet() + .removeIf(entry -> entry.getValue().bodyUuid().equals(bodyUuid)); registrations = new PublishedRegistrations(current.registrationTopologyGeneration(), - List.copyOf(viewsByUuid.values()), - viewsByUuid, - viewsByRowIndex); + new ArrayList<>(spaceIdsByUuid.keySet()), + spaceIdsByUuid, + registrationsByRowIndex); } public void clear() { @@ -144,28 +148,32 @@ public static ResourceType getRes public record BodyRegistrationPublication( @Nonnull Ref bodyRef, - @Nonnull PhysicsBodyRegistrationView view) { + @Nonnull UUID bodyUuid, + @Nonnull SpaceId spaceId) { public BodyRegistrationPublication { Objects.requireNonNull(bodyRef, "bodyRef"); - Objects.requireNonNull(view, "view"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(spaceId, "spaceId"); } } private record RegistrationByRef(@Nonnull Ref bodyRef, - @Nonnull PhysicsBodyRegistrationView view) { + @Nonnull UUID bodyUuid, + @Nonnull SpaceId spaceId) { private RegistrationByRef { Objects.requireNonNull(bodyRef, "bodyRef"); - Objects.requireNonNull(view, "view"); + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(spaceId, "spaceId"); } } private record PublishedRegistrations( long registrationTopologyGeneration, - @Nonnull List views, - @Nonnull Map viewsByUuid, - @Nonnull Int2ObjectOpenHashMap viewsByRowIndex) { + @Nonnull List bodyUuids, + @Nonnull Map spaceIdsByUuid, + @Nonnull Int2ObjectOpenHashMap registrationsByRowIndex) { private static final PublishedRegistrations EMPTY = new PublishedRegistrations(-1L, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java index 0b85d1cd..db227eba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java @@ -6,8 +6,6 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSnapshotState.ApplyResult; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; @@ -49,10 +47,8 @@ public PhysicsBodySnapshot captureBodySnapshot(@Nonnull PhysicsBodyRegistration public void putBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - snapshotState.putBodySnapshot(bodyUuid, snapshot, spaceId, kind, persistenceMode); + @Nonnull SpaceId spaceId) { + snapshotState.putBodySnapshot(bodyUuid, snapshot, spaceId); } @Nonnull @@ -167,15 +163,11 @@ public void clearBodySnapshots() { snapshotState.clearBodySnapshots(); } - public void publishDetachedRegistrationViews(@Nonnull PhysicsBodyRegistry bodyRegistry) { - bodyRegistry.publishLiveRegistrationViews(); - } - public void markWorldChanged(@Nonnull PhysicsBodyRegistry bodyRegistry, boolean storeTickAttached) { snapshotState.markWorldChanged(); if (!storeTickAttached) { - bodyRegistry.publishLiveRegistrationViews(); + bodyRegistry.publishLiveRegistrations(); } eventState.publishEmpty(snapshotState.worldEpoch(), snapshotState.getLatestPublishedFrame()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java index 8c5365d7..b7f99a10 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java @@ -32,9 +32,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -46,6 +44,7 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; @@ -628,9 +627,9 @@ public boolean hasPublishedBodyRegistration(@Nonnull UUID bodyUuid) { if (hasAttachedAuthoritativePhysicsStore()) { return authoritativePhysicsStore("check copied physics body registration") .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(bodyUuid) != null; + .hasBody(bodyUuid); } - return bodyRegistry.getPublishedRegistrationView(bodyUuid) != null; + return bodyRegistry.hasPublishedRegistration(bodyUuid); } @Nonnull @@ -723,9 +722,7 @@ private static void forEachIndexedAuthoritativeBodySnapshot( if (entry != null) { visitor.accept(entry.bodyUuid(), entry.snapshot(), - entry.spaceId(), - entry.kind(), - entry.persistenceMode()); + entry.spaceId()); } } } @@ -788,9 +785,7 @@ private static int forEachIndexedAuthoritativeBodySnapshotNear( if (withinRadius(entry.snapshot(), center, radiusSquared)) { visitor.accept(entry.bodyUuid(), entry.snapshot(), - entry.spaceId(), - entry.kind(), - entry.persistenceMode()); + entry.spaceId()); } } return candidates; @@ -824,9 +819,7 @@ private static int forEachIndexedAuthoritativeBodySnapshotNearWithRefs( visitor.accept(entry.bodyUuid(), validSnapshotBodyRef(store, body), entry.snapshot(), - entry.spaceId(), - entry.kind(), - entry.persistenceMode()); + entry.spaceId()); } } return candidates; @@ -850,15 +843,13 @@ private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( @Nonnull Store store, @Nonnull PhysicsBodyRegistrationResource registrations, @Nonnull PhysicsBodySnapshot body) { - PhysicsBodyRegistrationView registration = registrations.getBodyRegistrationView(body.bodyUuid()); - if (registration == null) { + SpaceId spaceId = registrations.getBodySpaceId(body.bodyUuid()); + if (spaceId == null) { return null; } return new PhysicsBodySnapshotEntry(body.bodyUuid(), toPublicBodySnapshot(store, body), - registration.spaceId(), - registration.kind(), - registration.persistenceMode()); + spaceId); } @Nullable @@ -1094,7 +1085,15 @@ private void disablePhysicsChunkLifecycleDirect() { private void restoreCollisionLodFiltersDirect() { int fullDynamicMask = PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY; - for (PhysicsBodyRegistration registration : bodyRegistry.getRegistrations(PhysicsBodyKind.BODY)) { + Store physicsStore = hasAttachedAuthoritativePhysicsStore() + ? authoritativePhysicsStore("restore collision LOD filters") + : null; + for (PhysicsBodyRegistration registration : bodyRegistry.getRegistrations()) { + if (physicsStore != null + && PhysicsChunkCollision.isChunkCollisionBody(physicsStore, + registration.bodyUuid())) { + continue; + } PhysicsSpaceBinding space = getSpaceBinding(registration.spaceId()); if (space == null) { continue; @@ -1177,8 +1176,8 @@ public int forEachIndexedBodySnapshotNearWithRefs(@Nonnull SpaceId spaceId, return lifecycleState.forEachIndexedBodySnapshotNear(spaceId, center, radius, - (bodyUuid, snapshot, bodySpaceId, kind, persistenceMode) -> - visitor.accept(bodyUuid, null, snapshot, bodySpaceId, kind, persistenceMode)); + (bodyUuid, snapshot, bodySpaceId) -> + visitor.accept(bodyUuid, null, snapshot, bodySpaceId)); } public void removeSpace(@Nonnull SpaceId spaceId) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java index 0c1d1a78..5bd3660e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java @@ -5,8 +5,6 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotStore; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; @@ -58,11 +56,9 @@ public PhysicsBodySnapshot captureBodySnapshot( public void putBodySnapshot(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - bodySnapshots.put(bodyUuid, snapshot, spaceId, kind, persistenceMode); - captureBodySnapshots.put(bodyUuid, snapshot, spaceId, kind, persistenceMode); + @Nonnull SpaceId spaceId) { + bodySnapshots.put(bodyUuid, snapshot, spaceId); + captureBodySnapshots.put(bodyUuid, snapshot, spaceId); } @Nonnull @@ -105,12 +101,10 @@ public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( int spaceBodyCount = captureBodySnapshots.bodyCount(spaceId); frameBuilder.addSpace(spaceId, frameWorldEpoch, spaceBodyCount); captureBodySnapshots.forEachIndexed(spaceId, - (bodyUuid, snapshot, bodySpaceId, kind, persistenceMode) -> frameBuilder.addBody(bodyUuid, + (bodyUuid, snapshot, bodySpaceId) -> frameBuilder.addBody(bodyUuid, bodySpaceId, frameWorldEpoch, frameWorldEpoch, - kind, - persistenceMode, snapshot)); } PublishedPhysicsSnapshotFrame frame = frameBuilder.build(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java index 77565912..0652ee60 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java @@ -2,8 +2,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -13,15 +11,11 @@ */ public record PhysicsBodyRegistration(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nonnull SpaceId spaceId) { public PhysicsBodyRegistration { Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(backendBodyHandle, "backendBodyHandle"); Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(persistenceMode, "persistenceMode"); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java index 701d6627..e86009d0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java @@ -2,9 +2,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsBodySnapshotCursor; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -32,9 +29,7 @@ public final class PhysicsBodyRegistry { private final Map registrationsByUuid = new Object2ObjectLinkedOpenHashMap<>(); - private final Map registrationViewsByUuid = - new Object2ObjectOpenHashMap<>(); - private final Map publishedRegistrationViewsByUuid = + private final Map publishedRegistrationSpaceIdsByUuid = new Object2ObjectLinkedOpenHashMap<>(); private final Object2LongOpenHashMap publishedLivenessMarks = new Object2LongOpenHashMap<>(); @@ -47,9 +42,7 @@ public final class PhysicsBodyRegistry { @Nonnull public PhysicsBodyRegistration registerBody(@Nonnull UUID bodyUuid, @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nonnull SpaceId spaceId) { validateRegisterable(bodyUuid, backendBodyHandle, spaceId); PhysicsBodyRegistration existingRegistration = registrationsByUuid.get(bodyUuid); if (existingRegistration != null) { @@ -57,10 +50,8 @@ public PhysicsBodyRegistration registerBody(@Nonnull UUID bodyUuid, removeBackendIndex(existingRegistration); } PhysicsBodyRegistration registration = - new PhysicsBodyRegistration(bodyUuid, backendBodyHandle, spaceId, kind, persistenceMode); + new PhysicsBodyRegistration(bodyUuid, backendBodyHandle, spaceId); registrationsByUuid.put(bodyUuid, registration); - registrationViewsByUuid.put(bodyUuid, - new PhysicsBodyRegistrationView(bodyUuid, spaceId, kind, persistenceMode)); bodyUuidsByRawBackendId .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) .put(backendBodyHandle.value(), bodyUuid); @@ -93,7 +84,6 @@ public PhysicsBodyRegistration unregisterBody(@Nonnull UUID bodyUuid) { return null; } - registrationViewsByUuid.remove(bodyUuid); removeBackendIndex(registration); removeFromSpace(registration); return registration; @@ -111,49 +101,17 @@ public PhysicsBodyRegistration getRegistration(@Nonnull UUID bodyUuid) { } @Nullable - public PhysicsBodyRegistrationView getRegistrationView(@Nonnull UUID bodyUuid) { - return registrationViewsByUuid.get(bodyUuid); + public SpaceId getPublishedRegistrationSpaceId(@Nonnull UUID bodyUuid) { + return publishedRegistrationSpaceIdsByUuid.get(bodyUuid); } - @Nullable - public PhysicsBodyRegistrationView getPublishedRegistrationView(@Nonnull UUID bodyUuid) { - return publishedRegistrationViewsByUuid.get(bodyUuid); + public boolean hasPublishedRegistration(@Nonnull UUID bodyUuid) { + return publishedRegistrationSpaceIdsByUuid.containsKey(bodyUuid); } @Nonnull - public Collection getRegistrationViews() { - List views = new ArrayList<>(); - for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { - views.add(registrationViewsByUuid.get(registration.bodyUuid())); - } - return views; - } - - @Nonnull - public Collection getPublishedRegistrationViews() { - return new ArrayList<>(publishedRegistrationViewsByUuid.values()); - } - - @Nonnull - public Collection getPublishedRegistrationViews(@Nonnull PhysicsBodyKind kind) { - List views = new ArrayList<>(); - for (PhysicsBodyRegistrationView view : publishedRegistrationViewsByUuid.values()) { - if (view.kind() == kind) { - views.add(view); - } - } - return views; - } - - @Nonnull - public Collection getRegistrationViews(@Nonnull PhysicsBodyKind kind) { - List views = new ArrayList<>(); - for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { - if (registration.kind() == kind) { - views.add(registrationViewsByUuid.get(registration.bodyUuid())); - } - } - return views; + public Collection getPublishedBodyUuids() { + return new ArrayList<>(publishedRegistrationSpaceIdsByUuid.keySet()); } @Nullable @@ -173,7 +131,7 @@ public int getRegistrationCount() { } public int getPublishedRegistrationCount() { - return publishedRegistrationViewsByUuid.size(); + return publishedRegistrationSpaceIdsByUuid.size(); } public void forEachRegistration(@Nonnull Consumer consumer) { @@ -222,62 +180,28 @@ public int getRegistrationCount(@Nonnull SpaceId spaceId) { return registrations != null ? registrations.size() : 0; } - public int getRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { - int count = 0; - for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { - if (registration.persistenceMode() == persistenceMode) { - count++; - } - } - return count; - } - - public int getPublishedRegistrationCount(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { - int count = 0; - for (PhysicsBodyRegistrationView view : publishedRegistrationViewsByUuid.values()) { - if (view.persistenceMode() == persistenceMode) { - count++; - } - } - return count; - } - - @Nonnull - public Collection getRegistrations(@Nonnull PhysicsBodyKind kind) { - List registrations = new ArrayList<>(); - for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { - if (registration.kind() == kind) { - registrations.add(registration); - } - } - return registrations; - } - public void clear() { registrationsByUuid.clear(); - registrationViewsByUuid.clear(); - publishedRegistrationViewsByUuid.clear(); + publishedRegistrationSpaceIdsByUuid.clear(); publishedLivenessMarks.clear(); bodyUuidsByRawBackendId.clear(); registrationsBySpace.clear(); } - public void publishLiveRegistrationViews() { + public void publishLiveRegistrations() { long generation = nextPublishedLivenessGeneration(); for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { - publishRegistrationView(registration.bodyUuid(), + publishRegistration(registration.bodyUuid(), registration.spaceId(), - registration.kind(), - registration.persistenceMode(), generation); } - retainPublishedRegistrationViews(generation); + retainPublishedRegistrations(generation); } public void applyPublishedRegistrationFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { long generation = nextPublishedLivenessGeneration(); - frame.forEachBodyCursor(body -> publishRegistrationView(body, generation)); - retainPublishedRegistrationViews(generation); + frame.forEachBodyCursor(body -> publishRegistration(body, generation)); + retainPublishedRegistrations(generation); } private void addToSpace(@Nonnull PhysicsBodyRegistration registration) { @@ -310,27 +234,19 @@ private void removeBackendIndex(@Nonnull PhysicsBodyRegistration registration) { } } - private void publishRegistrationView(@Nonnull PublishedPhysicsBodySnapshotCursor body, + private void publishRegistration(@Nonnull PublishedPhysicsBodySnapshotCursor body, long generation) { - publishRegistrationView(body.bodyUuid(), + publishRegistration(body.bodyUuid(), body.spaceId(), - body.kind(), - body.persistenceMode(), generation); } - private void publishRegistrationView(@Nonnull UUID bodyUuid, + private void publishRegistration(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, long generation) { - PhysicsBodyRegistrationView existing = publishedRegistrationViewsByUuid.get(bodyUuid); - if (existing == null - || !existing.spaceId().equals(spaceId) - || existing.kind() != kind - || existing.persistenceMode() != persistenceMode) { - publishedRegistrationViewsByUuid.put(bodyUuid, - new PhysicsBodyRegistrationView(bodyUuid, spaceId, kind, persistenceMode)); + SpaceId existing = publishedRegistrationSpaceIdsByUuid.get(bodyUuid); + if (existing == null || !existing.equals(spaceId)) { + publishedRegistrationSpaceIdsByUuid.put(bodyUuid, spaceId); } publishedLivenessMarks.put(bodyUuid, generation); } @@ -344,8 +260,8 @@ private long nextPublishedLivenessGeneration() { return publishedLivenessGeneration; } - private void retainPublishedRegistrationViews(long generation) { - Iterator iterator = publishedRegistrationViewsByUuid.keySet().iterator(); + private void retainPublishedRegistrations(long generation) { + Iterator iterator = publishedRegistrationSpaceIdsByUuid.keySet().iterator(); while (iterator.hasNext()) { UUID bodyUuid = iterator.next(); if (publishedLivenessMarks.getLong(bodyUuid) != generation) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java index 250cd303..d0644f1e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java @@ -10,8 +10,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldLifecycleState; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.ArrayList; import java.util.UUID; import javax.annotation.Nonnull; @@ -60,9 +58,7 @@ public PhysicsBodyRuntime(@Nonnull PhysicsSpaceRuntime spaceRuntime, @Nonnull public UUID addBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nonnull BackendBodyHandle backendBodyHandle) { PhysicsSpaceBinding binding = spaceRuntime.requireBinding(spaceId); long backendBodyId = backendBodyHandle.value(); if (!binding.runtime().containsBody(binding.backendSpaceHandle().value(), backendBodyId)) { @@ -71,14 +67,12 @@ public UUID addBody(@Nonnull UUID bodyUuid, } bodyRegistry.validateRegisterable(bodyUuid, backendBodyHandle, spaceId); PhysicsBodyRegistration registration = - bodyRegistry.registerBody(bodyUuid, backendBodyHandle, spaceId, kind, persistenceMode); + bodyRegistry.registerBody(bodyUuid, backendBodyHandle, spaceId); PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(binding, backendBodyId); if (snapshot != null) { lifecycleState.putBodySnapshot(registration.bodyUuid(), snapshot, - spaceId, - registration.kind(), - registration.persistenceMode()); + spaceId); } worldChangedMarker.run(); return bodyUuid; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java index 73626420..69c9f14f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java @@ -4,8 +4,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -19,7 +17,5 @@ public interface PhysicsBodySnapshotRefVisitor { void accept(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode); + @Nonnull SpaceId spaceId); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java index 39345121..2b57066d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java @@ -3,8 +3,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsBodySnapshotCursor; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; @@ -58,9 +56,7 @@ public int refresh(@Nonnull Iterable spaces, @Nonnull Physi } spatialIndex.update(bodyUuid, snapshot, - spaceId, - registration.kind(), - registration.persistenceMode()); + spaceId); } } @@ -77,12 +73,10 @@ public ApplyStats applyPublishedFrame(@Nonnull PublishedPhysicsSnapshotFrame fra public void put(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nonnull SpaceId spaceId) { snapshots.put(Objects.requireNonNull(bodyUuid, "bodyUuid"), snapshot); livenessMarks.put(bodyUuid, livenessGeneration); - spatialIndex.update(bodyUuid, snapshot, spaceId, kind, persistenceMode); + spatialIndex.update(bodyUuid, snapshot, spaceId); } @Nullable @@ -199,9 +193,7 @@ public void accept(@Nonnull PublishedPhysicsBodySnapshotCursor bodyFrame) { } spatialIndex.update(bodyUuid, snapshot, - bodyFrame.spaceId(), - bodyFrame.kind(), - bodyFrame.persistenceMode()); + bodyFrame.spaceId()); applied++; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java index c77cd973..21846792 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java @@ -2,8 +2,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.UUID; import javax.annotation.Nonnull; @@ -15,7 +13,5 @@ public interface PhysicsBodySnapshotVisitor { void accept(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode); + @Nonnull SpaceId spaceId); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java index ea4e631f..5467ac4c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java @@ -2,8 +2,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -41,13 +39,11 @@ final class PhysicsBodySpatialIndex { void update(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nonnull SpaceId spaceId) { long cellKey = cellKey(snapshot.positionX(), snapshot.positionY(), snapshot.positionZ()); IndexedBody indexed = entries.get(bodyUuid); if (indexed == null) { - indexed = new IndexedBody(bodyUuid, snapshot, spaceId, kind, persistenceMode, cellKey); + indexed = new IndexedBody(bodyUuid, snapshot, spaceId, cellKey); entries.put(bodyUuid, indexed); addToCell(indexed, cellKey); spaceBodyCounts.addTo(spaceId.value(), 1); @@ -64,8 +60,6 @@ void update(@Nonnull UUID bodyUuid, } indexed.snapshot = snapshot; indexed.spaceId = spaceId; - indexed.kind = kind; - indexed.persistenceMode = persistenceMode; } void remove(@Nonnull UUID bodyUuid) { @@ -238,24 +232,16 @@ private static final class IndexedBody { private PhysicsBodySnapshot snapshot; @Nonnull private SpaceId spaceId; - @Nonnull - private PhysicsBodyKind kind; - @Nonnull - private PhysicsBodyPersistenceMode persistenceMode; private long cellKey; private int cellIndex = -1; private IndexedBody(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, long cellKey) { this.bodyUuid = bodyUuid; this.snapshot = snapshot; this.spaceId = spaceId; - this.kind = kind; - this.persistenceMode = persistenceMode; this.cellKey = cellKey; } @@ -263,17 +249,13 @@ private IndexedBody(@Nonnull UUID bodyUuid, private PhysicsBodySnapshotEntry entry() { return new PhysicsBodySnapshotEntry(bodyUuid, snapshot, - spaceId, - kind, - persistenceMode); + spaceId); } private void visit(@Nonnull PhysicsBodySnapshotVisitor visitor) { visitor.accept(bodyUuid, snapshot, - spaceId, - kind, - persistenceMode); + spaceId); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java index 41e2943c..a396f514 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java @@ -45,8 +45,7 @@ public final class BodyBindingSystem extends TickingSystem private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), - new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class), - new SystemDependency<>(Order.AFTER, ChunkCollisionMutationDrainSystem.class) + new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 403a59e8..2ad59860 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -26,8 +26,6 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRowCleanup; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -54,14 +52,15 @@ import org.joml.Vector3f; /** - * Applies copied PhysicsChunk chunk collision mutations as runtime-only chunk collision body rows. + * Applies copied PhysicsChunk chunk collision mutations as chunk collision body rows. */ public final class ChunkCollisionMutationDrainSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class), - new SystemDependency<>(Order.AFTER, PhysicsChunkSettingsIndexSystem.class) + new SystemDependency<>(Order.AFTER, PhysicsChunkSettingsIndexSystem.class), + new SystemDependency<>(Order.BEFORE, BodyBindingSystem.class) ); @Override @@ -291,9 +290,7 @@ private static void addChunkCollisionBody(@Nonnull Store store, mutation.sourceKey(), partKind, partIndex); - BodyComponent body = new BodyComponent(mutation.spaceUuid(), - PhysicsBodyKind.TERRAIN, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + BodyComponent body = new BodyComponent(mutation.spaceUuid()); body.setSpaceRef(spaceRef); var holder = PhysicsEntities.bodyHolder(store, bodyUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java index 530dbd92..5bbb6217 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java @@ -36,7 +36,8 @@ public final class ChunkCollisionVoxelStitchingSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, BodyBindingSystem.class) + new SystemDependency<>(Order.AFTER, BodyBindingSystem.class), + new SystemDependency<>(Order.BEFORE, TargetBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 9c516ebc..f4b0253e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -22,7 +22,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; @@ -83,7 +82,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) frameDt, bodies); snapshot.publish(frame); - publishRegistrationViews(store, + publishRegistrations(store, systemIndex, runtime, compatibility, @@ -101,7 +100,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) completed.droppedBackendEventCount()); } - private static void publishRegistrationViews(@Nonnull Store store, + private static void publishRegistrations(@Nonnull Store store, int systemIndex, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @@ -115,7 +114,7 @@ private static void publishRegistrationViews(@Nonnull Store store, } long startNanos = profiling.isEnabled() ? System.nanoTime() : 0L; registrations.publish(generation, - collectRegistrationViews(store, + collectRegistrations(store, systemIndex, runtime, compatibility, @@ -127,7 +126,7 @@ private static void publishRegistrationViews(@Nonnull Store store, } @Nonnull - private static List collectRegistrationViews( + private static List collectRegistrations( @Nonnull Store store, int systemIndex, @Nonnull PhysicsRuntimeResource runtime, @@ -136,7 +135,7 @@ private static List collectRegistrationViews( List registrations = new ArrayList<>(snapshot.getLatestFrame().bodies().size()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> collectRegistrationViews(runtime, + (chunk, _) -> collectRegistrations(runtime, compatibility, snapshot, registrations, @@ -145,7 +144,7 @@ private static List collectRegistrationViews( return registrations; } - private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource runtime, + private static void collectRegistrations(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsSnapshotResource snapshot, @Nonnull List registrations, @@ -161,10 +160,8 @@ private static void collectRegistrationViews(@Nonnull PhysicsRuntimeResource run SpaceId spaceId = compatibility.getSpaceId(body.getSpaceUuid()); if (spaceId != null) { registrations.add(new BodyRegistrationPublication(rowRef, - new PhysicsBodyRegistrationView(rowUuid, - spaceId, - body.getKind(), - body.getPersistenceMode()))); + rowUuid, + spaceId)); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 8d41de9d..4113fd25 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -179,9 +179,7 @@ private static void addBody(@Nonnull Store store, @Nonnull Map shapesByUuid, @Nonnull Map materialsByUuid) { Holder holder = row(store, dto.getBodyUuid()); - BodyComponent body = new BodyComponent(dto.getSpaceUuid(), - dto.getKind(), - dto.getPersistenceMode()); + BodyComponent body = new BodyComponent(dto.getSpaceUuid()); DynamicsComponent dynamics = new DynamicsComponent(dto.getBodyType(), dto.getMass(), dto.getLinearDamping(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index 77f36c24..f61855ef 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -30,7 +30,8 @@ public final class PhysicsChunkSettingsIndexSystem extends TickingSystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class) + new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), + new SystemDependency<>(Order.BEFORE, SpaceBindingSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java index 6226543b..1d2477b4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java @@ -39,8 +39,7 @@ public final class SpaceBindingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class), - new SystemDependency<>(Order.AFTER, PhysicsChunkSettingsIndexSystem.class) + new SystemDependency<>(Order.AFTER, IdentityIndexSystem.class) ); @Override diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java index 47e1e0d9..398b531e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java @@ -31,7 +31,6 @@ public final class TargetBindingSystem extends TickingSystem implements QuerySystem { private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, ChunkCollisionVoxelStitchingSystem.class), new SystemDependency<>(Order.AFTER, BodyCommandApplicationSystem.class) ); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 53aa0a95..afc35f40 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -22,8 +22,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; @@ -142,6 +141,7 @@ public void tick(float dt, int index, @Nonnull Store store) { overlayLifetime); renderDetachedBodies(target, store, + physicsStore, resource, viewerPosition, debug.getViewRadius(), @@ -232,10 +232,12 @@ private int renderEntityBodies(@Nonnull Collection viewers, return 0; } double maxDistanceSquared = viewRadius * viewRadius; - for (PhysicsBodyRegistrationView registration : PhysicsBodies.registrationViews(physicsStore, - PhysicsBodyKind.BODY)) { + for (UUID bodyUuid : PhysicsBodies.bodyUuids(physicsStore)) { + if (PhysicsChunkCollision.isChunkCollisionBody(physicsStore, bodyUuid)) { + continue; + } Collection> attachments = PhysicsEntityAttachments.attachments(store, - registration.bodyUuid(), + bodyUuid, null); if (attachments.isEmpty()) { continue; @@ -254,7 +256,7 @@ private int renderEntityBodies(@Nonnull Collection viewers, continue; } - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(registration.bodyUuid(), + PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyUuid, null); if (snapshot == null) { continue; @@ -287,6 +289,7 @@ private int renderEntityBodies(@Nonnull Collection viewers, private static int renderDetachedBodies(@Nonnull Collection viewers, @Nonnull Store store, + @Nonnull Store physicsStore, @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d viewerPosition, double viewRadius, @@ -301,12 +304,12 @@ private static int renderDetachedBodies(@Nonnull Collection viewers, RenderedBodyCount rendered = new RenderedBodyCount(); double maxDistanceSquared = viewRadius * viewRadius; for (SpaceId spaceId : resource.getSpaceIds()) { - resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, _, kind, _) -> { + resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, _) -> { if (rendered.hasReached(maxBodies)) { return; } - if (kind != PhysicsBodyKind.BODY + if (PhysicsChunkCollision.isChunkCollisionBody(physicsStore, bodyUuid) || PhysicsEntityAttachments.hasAttachments(store, bodyUuid, null)) { return; } @@ -358,7 +361,7 @@ private static void renderSpaceOnlyShapes(@Nonnull Collection viewers @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull SpaceId spaceId, float time) { - resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, snapshotSpaceId, kind, persistenceMode) -> { + resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, snapshotSpaceId) -> { if (snapshot.shapeType() != ShapeType.PLANE) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java index e84cb1d7..14b58105 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java @@ -95,7 +95,7 @@ private static boolean hasMissingBody(@Nonnull PhysicsWorldRuntimeResource resou return false; } return store != null - ? PhysicsBodies.registrationView(store, attachment.getBodyUuid()) == null + ? !PhysicsBodies.isRegistered(store, attachment.getBodyUuid()) : !resource.hasPublishedBodyRegistration(attachment.getBodyUuid()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java deleted file mode 100644 index 85970249..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyKind.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.body; - - -/** - * Classifies why a body exists in the runtime registry. - */ -public enum PhysicsBodyKind { - BODY, - TEMPORARY, - TERRAIN; - - public boolean isTerrain() { - return this == TERRAIN; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyPersistenceMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyPersistenceMode.java deleted file mode 100644 index 07c9eea8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyPersistenceMode.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.body; - - -/** - * Controls whether a registered body participates in Impulse persistence. - */ -public enum PhysicsBodyPersistenceMode { - PERSISTENT, - RUNTIME_ONLY -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java deleted file mode 100644 index e02200b2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/body/PhysicsBodyRegistrationView.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.body; - -import dev.hytalemodding.impulse.api.SpaceId; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Immutable body registration metadata safe for public callers outside the store tick lane. - */ -public record PhysicsBodyRegistrationView(@Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - - public PhysicsBodyRegistrationView { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(persistenceMode, "persistenceMode"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java index 16b39223..cf28be94 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/BodyComponent.java @@ -3,20 +3,17 @@ import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * Authored body identity, kind, and persistence policy. + * Authored body space ownership. */ public final class BodyComponent implements Component { @@ -28,36 +25,18 @@ public final class BodyComponent implements Component { (component, value) -> component.spaceUuid = value, BodyComponent::getSpaceUuid) .add() - .append(new KeyedCodec<>("Kind", new EnumCodec<>(PhysicsBodyKind.class), false), - (component, value) -> component.kind = value != null ? value : PhysicsBodyKind.BODY, - BodyComponent::getKind) - .add() - .append(new KeyedCodec<>("PersistenceMode", new EnumCodec<>(PhysicsBodyPersistenceMode.class), false), - (component, value) -> component.persistenceMode = value != null - ? value - : PhysicsBodyPersistenceMode.RUNTIME_ONLY, - BodyComponent::getPersistenceMode) - .add() .build(); @Nonnull private UUID spaceUuid = new UUID(0L, 0L); @Nullable private transient Ref spaceRef; - @Nonnull - private PhysicsBodyKind kind = PhysicsBodyKind.BODY; - @Nonnull - private PhysicsBodyPersistenceMode persistenceMode = PhysicsBodyPersistenceMode.RUNTIME_ONLY; public BodyComponent() { } - public BodyComponent(@Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + public BodyComponent(@Nonnull UUID spaceUuid) { this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.kind = Objects.requireNonNull(kind, "kind"); - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); } @Nonnull @@ -79,24 +58,6 @@ public void setSpaceRef(@Nullable Ref spaceRef) { this.spaceRef = spaceRef; } - @Nonnull - public PhysicsBodyKind getKind() { - return kind; - } - - public void setKind(@Nonnull PhysicsBodyKind kind) { - this.kind = Objects.requireNonNull(kind, "kind"); - } - - @Nonnull - public PhysicsBodyPersistenceMode getPersistenceMode() { - return persistenceMode; - } - - public void setPersistenceMode(@Nonnull PhysicsBodyPersistenceMode persistenceMode) { - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); - } - @Nonnull public static ComponentType getComponentType() { return PhysicsComponentTypes.bodyComponentType(); @@ -105,7 +66,7 @@ public static ComponentType getComponentType() { @Nonnull @Override public BodyComponent clone() { - BodyComponent copy = new BodyComponent(spaceUuid, kind, persistenceMode); + BodyComponent copy = new BodyComponent(spaceUuid); copy.spaceRef = spaceRef; return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index bb6db736..74eb7335 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -7,6 +7,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; @@ -14,11 +15,13 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import java.util.List; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3d; /** @@ -33,6 +36,26 @@ public static boolean isSubPluginEnabled() { return PhysicsChunkLifecycle.isEnabled(); } + public static boolean isChunkCollisionBody(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + Store checkedStore = requireWorldThread(store, + "read PhysicsChunk body ownership"); + Ref bodyRef = PhysicsEntities.resolveRef(checkedStore, + Objects.requireNonNull(bodyUuid, "bodyUuid")); + return bodyRef != null && isChunkCollisionBody(checkedStore, bodyRef); + } + + public static boolean isChunkCollisionBody(@Nonnull Store store, + @Nullable Ref bodyRef) { + Store checkedStore = requireWorldThread(store, + "read PhysicsChunk body ownership"); + if (bodyRef == null || bodyRef.getStore() != checkedStore || !bodyRef.isValid()) { + return false; + } + return checkedStore.getComponent(bodyRef, ChunkCollisionSourceComponent.getComponentType()) + != null; + } + @Nonnull public static PhysicsChunkCollisionBuildStats rebuildAround(@Nonnull World world, @Nonnull Store store, @@ -157,13 +180,20 @@ private static void requireEnabled() { } } + @Nonnull + private static Store requireWorldThread(@Nonnull Store store, + @Nonnull String operation) { + Store checkedStore = Objects.requireNonNull(store, "store"); + PhysicsThreading.requireWorldThread(checkedStore, operation); + return checkedStore; + } + @Nonnull private static Store requireMatchingWorldThread(@Nonnull World world, @Nonnull Store store, @Nonnull String operation) { World checkedWorld = Objects.requireNonNull(world, "world"); - Store checkedStore = Objects.requireNonNull(store, "store"); - PhysicsThreading.requireWorldThread(checkedStore, operation); + Store checkedStore = requireWorldThread(store, operation); if (PhysicsThreading.world(checkedStore) != checkedWorld) { throw new IllegalArgumentException("PhysicsStore does not belong to the supplied world"); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java index 81601d6d..1756e616 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java @@ -4,12 +4,10 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; @@ -29,7 +27,7 @@ private PhysicsBodies() { } @Nullable - public static PhysicsBodyRegistrationView registrationView(@Nonnull Store store, + public static UUID bodyUuid(@Nonnull Store store, @Nonnull Ref bodyRef) { Store checkedStore = requireWorldThread(store, "read copied PhysicsStore body registration"); @@ -38,35 +36,57 @@ public static PhysicsBodyRegistrationView registrationView(@Nonnull Store store, + public static SpaceId spaceId(@Nonnull Store store, @Nonnull UUID bodyUuid) { Store checkedStore = requireWorldThread(store, "read copied PhysicsStore body registration"); return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationView(Objects.requireNonNull(bodyUuid, "bodyUuid")); + .getBodySpaceId(Objects.requireNonNull(bodyUuid, "bodyUuid")); } - @Nonnull - public static Collection registrationViews( - @Nonnull Store store) { + @Nullable + public static SpaceId spaceId(@Nonnull Store store, + @Nonnull Ref bodyRef) { Store checkedStore = requireWorldThread(store, - "read copied PhysicsStore body registrations"); + "read copied PhysicsStore body registration"); + Ref checkedRef = Objects.requireNonNull(bodyRef, "bodyRef"); + if (!sameValidStore(checkedStore, checkedRef)) { + return null; + } + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .getBodySpaceId(checkedRef); + } + + public static boolean isRegistered(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body registration"); + return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .hasBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); + } + + public static boolean isRegistered(@Nonnull Store store, + @Nonnull Ref bodyRef) { + Store checkedStore = requireWorldThread(store, + "read copied PhysicsStore body registration"); + Ref checkedRef = Objects.requireNonNull(bodyRef, "bodyRef"); + if (!sameValidStore(checkedStore, checkedRef)) { + return false; + } return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationViews(); + .hasBody(checkedRef); } @Nonnull - public static Collection registrationViews( - @Nonnull Store store, - @Nonnull PhysicsBodyKind kind) { + public static Collection bodyUuids(@Nonnull Store store) { Store checkedStore = requireWorldThread(store, "read copied PhysicsStore body registrations"); return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationViews(Objects.requireNonNull(kind, "kind")); + .getBodyUuids(); } public static int registrationCount(@Nonnull Store store) { @@ -77,11 +97,11 @@ public static int registrationCount(@Nonnull Store store) { } public static int registrationCount(@Nonnull Store store, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nonnull SpaceId spaceId) { Store checkedStore = requireWorldThread(store, "count copied PhysicsStore body registrations"); return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationCount(Objects.requireNonNull(persistenceMode, "persistenceMode")); + .getBodyRegistrationCount(Objects.requireNonNull(spaceId, "spaceId")); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java index ba38ee79..cfe2130f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java @@ -4,8 +4,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -37,8 +35,7 @@ public static BodyEntityDescriptor dynamicBody(@Nonnull Ref spaceR @Nonnull PhysicsShapeSpec shape, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nullable Vector3f linearVelocity) { return body(spaceRef, bodyUuid, bodyCenter, @@ -46,9 +43,7 @@ public static BodyEntityDescriptor dynamicBody(@Nonnull Ref spaceR PhysicsBodyType.DYNAMIC, mass, settings, - linearVelocity, - PhysicsBodyKind.BODY, - persistenceMode); + linearVelocity); } @Nonnull @@ -59,9 +54,7 @@ public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, @Nonnull PhysicsBodyType bodyType, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nullable Vector3f linearVelocity) { BodyEntityDescriptor descriptor = bodyWithSpaceUuid(PhysicsEntityRefs.entityUuid(spaceRef), bodyUuid, bodyCenter, @@ -69,9 +62,7 @@ public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, bodyType, mass, settings, - linearVelocity, - kind, - persistenceMode); + linearVelocity); descriptor.body().setSpaceRef(spaceRef); return descriptor; } @@ -84,22 +75,16 @@ private static BodyEntityDescriptor bodyWithSpaceUuid(@Nonnull UUID spaceUuid, @Nonnull PhysicsBodyType bodyType, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nullable Vector3f linearVelocity) { Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(bodyCenter, "bodyCenter"); Objects.requireNonNull(shape, "shape"); Objects.requireNonNull(bodyType, "bodyType"); Objects.requireNonNull(settings, "settings"); - Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(persistenceMode, "persistenceMode"); return BodyEntityDescriptor.of(bodyUuid, - new BodyComponent(spaceUuid, - kind, - persistenceMode), + new BodyComponent(spaceUuid), new DynamicsComponent(bodyType, mass, settings.hasLinearDamping() ? settings.linearDamping() : 0.0f, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java index 28f0232f..715f6002 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PhysicsBodySnapshotEntry.java @@ -2,26 +2,20 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; /** - * Snapshot query result carrying a body's durable UUID, latest snapshot, and registration metadata. + * Snapshot query result carrying a body's durable UUID, latest snapshot, and owning space. */ public record PhysicsBodySnapshotEntry(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { + @Nonnull SpaceId spaceId) { public PhysicsBodySnapshotEntry { Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(snapshot, "snapshot"); Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(persistenceMode, "persistenceMode"); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java index 4c9a6c7d..eb54944d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodyFrameStorage.java @@ -5,8 +5,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; @@ -56,8 +54,6 @@ final class PublishedPhysicsBodyFrameStorage { private final SpaceId[] bodySpaceIds; private final long[] bodySpaceEpochs; private final long[] registrationGenerations; - private final PhysicsBodyKind[] kinds; - private final PhysicsBodyPersistenceMode[] persistenceModes; private final PhysicsBodyType[] bodyTypes; private final ShapeType[] shapeTypes; private final PhysicsAxis[] shapeAxes; @@ -77,8 +73,6 @@ private PublishedPhysicsBodyFrameStorage(long frameEpoch, SpaceId[] bodySpaceIds, long[] bodySpaceEpochs, long[] registrationGenerations, - PhysicsBodyKind[] kinds, - PhysicsBodyPersistenceMode[] persistenceModes, PhysicsBodyType[] bodyTypes, ShapeType[] shapeTypes, PhysicsAxis[] shapeAxes, @@ -97,8 +91,6 @@ private PublishedPhysicsBodyFrameStorage(long frameEpoch, this.bodySpaceIds = bodySpaceIds; this.bodySpaceEpochs = bodySpaceEpochs; this.registrationGenerations = registrationGenerations; - this.kinds = kinds; - this.persistenceModes = persistenceModes; this.bodyTypes = bodyTypes; this.shapeTypes = shapeTypes; this.shapeAxes = shapeAxes; @@ -144,8 +136,6 @@ PublishedPhysicsBodySnapshot bodySnapshot(int bodyIndex) { worldEpoch, bodySpaceEpoch(bodyIndex), registrationGeneration(bodyIndex), - kind(bodyIndex), - persistenceMode(bodyIndex), positionX(bodyIndex), positionY(bodyIndex), positionZ(bodyIndex), @@ -215,14 +205,6 @@ private long registrationGeneration(int bodyIndex) { return registrationGenerations[bodyIndex]; } - private PhysicsBodyKind kind(int bodyIndex) { - return kinds[bodyIndex]; - } - - private PhysicsBodyPersistenceMode persistenceMode(int bodyIndex) { - return persistenceModes[bodyIndex]; - } - private PhysicsBodyType bodyType(int bodyIndex) { return bodyTypes[bodyIndex]; } @@ -372,8 +354,6 @@ static final class Builder { private final SpaceId[] bodySpaceIds; private final long[] bodySpaceEpochs; private final long[] registrationGenerations; - private final PhysicsBodyKind[] kinds; - private final PhysicsBodyPersistenceMode[] persistenceModes; private final PhysicsBodyType[] bodyTypes; private final ShapeType[] shapeTypes; private final PhysicsAxis[] shapeAxes; @@ -404,8 +384,6 @@ private Builder(long frameEpoch, long worldEpoch, int expectedSpaces, int expect this.bodySpaceIds = new SpaceId[expectedBodies]; this.bodySpaceEpochs = new long[expectedBodies]; this.registrationGenerations = new long[expectedBodies]; - this.kinds = new PhysicsBodyKind[expectedBodies]; - this.persistenceModes = new PhysicsBodyPersistenceMode[expectedBodies]; this.bodyTypes = new PhysicsBodyType[expectedBodies]; this.shapeTypes = new ShapeType[expectedBodies]; this.shapeAxes = new PhysicsAxis[expectedBodies]; @@ -436,8 +414,6 @@ void addBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull PhysicsBodySnapshot snapshot) { Objects.requireNonNull(bodyUuid, "bodyUuid"); addBody(bodyUuid.getMostSignificantBits(), @@ -445,8 +421,6 @@ void addBody(@Nonnull UUID bodyUuid, spaceId, spaceEpoch, registrationGeneration, - kind, - persistenceMode, snapshot); } @@ -455,8 +429,6 @@ private void addBody(long bodyUuidMostSignificantBits, @Nonnull SpaceId spaceId, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull PhysicsBodySnapshot snapshot) { Objects.requireNonNull(snapshot, "snapshot"); if (currentSpace < 0) { @@ -476,8 +448,6 @@ private void addBody(long bodyUuidMostSignificantBits, bodySpaceIds[nextBody] = Objects.requireNonNull(spaceId, "spaceId"); bodySpaceEpochs[nextBody] = spaceEpoch; registrationGenerations[nextBody] = registrationGeneration; - kinds[nextBody] = Objects.requireNonNull(kind, "kind"); - persistenceModes[nextBody] = Objects.requireNonNull(persistenceMode, "persistenceMode"); bodyTypes[nextBody] = snapshot.bodyType(); shapeTypes[nextBody] = snapshot.shapeType(); shapeAxes[nextBody] = snapshot.shapeAxis(); @@ -544,8 +514,6 @@ PublishedPhysicsBodyFrameStorage build() { bodySpaceIds, bodySpaceEpochs, registrationGenerations, - kinds, - persistenceModes, bodyTypes, shapeTypes, shapeAxes, @@ -602,18 +570,6 @@ public long registrationGeneration() { return PublishedPhysicsBodyFrameStorage.this.registrationGeneration(index); } - @Nonnull - @Override - public PhysicsBodyKind kind() { - return PublishedPhysicsBodyFrameStorage.this.kind(index); - } - - @Nonnull - @Override - public PhysicsBodyPersistenceMode persistenceMode() { - return PublishedPhysicsBodyFrameStorage.this.persistenceMode(index); - } - @Override public float positionX() { return PublishedPhysicsBodyFrameStorage.this.positionX(index); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java index 50e1971b..9a8f4dbf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshot.java @@ -5,8 +5,6 @@ import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -30,10 +28,6 @@ public final class PublishedPhysicsBodySnapshot implements PublishedPhysicsBodyS private final long worldEpoch; private final long spaceEpoch; private final long registrationGeneration; - @Nonnull - private final PhysicsBodyKind kind; - @Nonnull - private final PhysicsBodyPersistenceMode persistenceMode; private final float positionX; private final float positionY; private final float positionZ; @@ -77,8 +71,6 @@ public PublishedPhysicsBodySnapshot(@Nonnull UUID bodyUuid, long worldEpoch, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull Vector3f position, @Nonnull Quaternionf rotation, @Nonnull Vector3f linearVelocity, @@ -99,8 +91,6 @@ public PublishedPhysicsBodySnapshot(@Nonnull UUID bodyUuid, worldEpoch, spaceEpoch, registrationGeneration, - kind, - persistenceMode, position, rotation, linearVelocity, @@ -123,8 +113,6 @@ private PublishedPhysicsBodySnapshot(long bodyUuidMostSignificantBits, long worldEpoch, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull Vector3f position, @Nonnull Quaternionf rotation, @Nonnull Vector3f linearVelocity, @@ -149,8 +137,6 @@ private PublishedPhysicsBodySnapshot(long bodyUuidMostSignificantBits, this.worldEpoch = worldEpoch; this.spaceEpoch = spaceEpoch; this.registrationGeneration = registrationGeneration; - this.kind = Objects.requireNonNull(kind, "kind"); - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); Objects.requireNonNull(position, "position"); this.positionX = position.x; this.positionY = position.y; @@ -204,8 +190,6 @@ public static PublishedPhysicsBodySnapshot from(@Nonnull UUID bodyUuid, long worldEpoch, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull PhysicsBodySnapshot snapshot) { return fromBits(uuidMostSignificantBits(bodyUuid), uuidLeastSignificantBits(bodyUuid), @@ -214,8 +198,6 @@ public static PublishedPhysicsBodySnapshot from(@Nonnull UUID bodyUuid, worldEpoch, spaceEpoch, registrationGeneration, - kind, - persistenceMode, snapshot); } @@ -226,8 +208,6 @@ private static PublishedPhysicsBodySnapshot fromBits(long bodyUuidMostSignifican long worldEpoch, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull PhysicsBodySnapshot snapshot) { Objects.requireNonNull(snapshot, "snapshot"); return new PublishedPhysicsBodySnapshot(bodyUuidMostSignificantBits, @@ -237,8 +217,6 @@ private static PublishedPhysicsBodySnapshot fromBits(long bodyUuidMostSignifican worldEpoch, spaceEpoch, registrationGeneration, - kind, - persistenceMode, snapshot.positionX(), snapshot.positionY(), snapshot.positionZ(), @@ -289,8 +267,6 @@ private static long uuidLeastSignificantBits(@Nonnull UUID bodyUuid) { long worldEpoch, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, float positionX, float positionY, float positionZ, @@ -335,8 +311,6 @@ private static long uuidLeastSignificantBits(@Nonnull UUID bodyUuid) { this.worldEpoch = worldEpoch; this.spaceEpoch = spaceEpoch; this.registrationGeneration = registrationGeneration; - this.kind = Objects.requireNonNull(kind, "kind"); - this.persistenceMode = Objects.requireNonNull(persistenceMode, "persistenceMode"); this.positionX = positionX; this.positionY = positionY; this.positionZ = positionZ; @@ -489,18 +463,6 @@ public long registrationGeneration() { return registrationGeneration; } - @Nonnull - @Override - public PhysicsBodyKind kind() { - return kind; - } - - @Nonnull - @Override - public PhysicsBodyPersistenceMode persistenceMode() { - return persistenceMode; - } - @Nonnull public Vector3f position() { return new Vector3f(positionX, positionY, positionZ); @@ -787,8 +749,6 @@ public boolean equals(@Nullable Object other) { && bodyUuidMostSignificantBits == that.bodyUuidMostSignificantBits && bodyUuidLeastSignificantBits == that.bodyUuidLeastSignificantBits && spaceId.equals(that.spaceId) - && kind == that.kind - && persistenceMode == that.persistenceMode && bodyType == that.bodyType && shapeType == that.shapeType && shapeAxis == that.shapeAxis; @@ -803,8 +763,6 @@ public int hashCode() { result = 31 * result + Long.hashCode(worldEpoch); result = 31 * result + Long.hashCode(spaceEpoch); result = 31 * result + Long.hashCode(registrationGeneration); - result = 31 * result + kind.hashCode(); - result = 31 * result + persistenceMode.hashCode(); result = 31 * result + Float.hashCode(positionX); result = 31 * result + Float.hashCode(positionY); result = 31 * result + Float.hashCode(positionZ); @@ -851,8 +809,6 @@ public String toString() { + ", worldEpoch=" + worldEpoch + ", spaceEpoch=" + spaceEpoch + ", registrationGeneration=" + registrationGeneration - + ", kind=" + kind - + ", persistenceMode=" + persistenceMode + ", position=(" + positionX + ", " + positionY + ", " + positionZ + ')' + ", rotation=(" + rotationX + ", " + rotationY + ", " + rotationZ + ", " + rotationW + ')' + ", linearVelocity=(" + linearVelocityX + ", " + linearVelocityY + ", " + linearVelocityZ + ')' diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java index b2da7380..455f29f0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsBodySnapshotCursor.java @@ -5,8 +5,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.UUID; import javax.annotation.Nonnull; import org.joml.Quaternionf; @@ -35,12 +33,6 @@ public interface PublishedPhysicsBodySnapshotCursor { long registrationGeneration(); - @Nonnull - PhysicsBodyKind kind(); - - @Nonnull - PhysicsBodyPersistenceMode persistenceMode(); - float positionX(); float positionY(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java index d56b8e3e..22d2dacd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrame.java @@ -2,8 +2,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -451,15 +449,11 @@ public Builder addBody(@Nonnull UUID bodyUuid, @Nonnull SpaceId spaceId, long spaceEpoch, long registrationGeneration, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull PhysicsBodySnapshot snapshot) { bodyStorage.addBody(bodyUuid, spaceId, spaceEpoch, registrationGeneration, - kind, - persistenceMode, snapshot); return this; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java index 90271658..d45e5b6c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.EmptyResourceStorage; @@ -9,16 +10,17 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; @@ -34,7 +36,7 @@ class CleanCommandLifecycleGuardTest { @Test - void radiusCleanSelectsOnlyNormalBodySnapshots() throws Exception { + void radiusCleanUsesEcsOwnershipInsteadOfLegacyKind() throws Exception { ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); @@ -46,10 +48,12 @@ void radiusCleanSelectsOnlyNormalBodySnapshots() throws Exception { try { markCurrentThreadAsWorldThread(store); UUID bodyUuid = UUID.randomUUID(); - UUID terrainUuid = UUID.randomUUID(); + UUID generatedUuid = UUID.randomUUID(); UUID spaceUuid = UUID.randomUUID(); - publishSnapshots(store, bodyUuid, terrainUuid, spaceUuid); - publishRegistrations(store, bodyUuid, terrainUuid); + Ref bodyRef = addBodyIdentityRow(store, bodyUuid, false); + Ref generatedRef = addBodyIdentityRow(store, generatedUuid, true); + publishSnapshots(store, bodyUuid, generatedUuid, spaceUuid); + publishRegistrations(store, bodyUuid, bodyRef, generatedUuid, generatedRef); Set selected = selectBodyUuidsNear(store, new Vector3d(), 10.0f); @@ -62,21 +66,46 @@ void radiusCleanSelectsOnlyNormalBodySnapshots() throws Exception { private static void publishSnapshots(@Nonnull Store store, @Nonnull UUID bodyUuid, - @Nonnull UUID terrainUuid, + @Nonnull UUID generatedUuid, @Nonnull UUID spaceUuid) { store.getResource(PhysicsSnapshotResource.getResourceType()) .publish(new PhysicsSnapshotFrame(1L, 0.05f, - List.of(snapshot(bodyUuid, spaceUuid), snapshot(terrainUuid, spaceUuid)))); + List.of(snapshot(bodyUuid, spaceUuid), snapshot(generatedUuid, spaceUuid)))); } private static void publishRegistrations(@Nonnull Store store, @Nonnull UUID bodyUuid, - @Nonnull UUID terrainUuid) { + @Nonnull Ref bodyRef, + @Nonnull UUID generatedUuid, + @Nonnull Ref generatedRef) { store.getResource(PhysicsBodyRegistrationResource.getResourceType()) .publish(1L, - List.of(publication(1, bodyUuid, PhysicsBodyKind.BODY), - publication(2, terrainUuid, PhysicsBodyKind.TERRAIN))); + List.of(publication(bodyRef, bodyUuid), + publication(generatedRef, generatedUuid))); + } + + @Nonnull + private static Ref addBodyIdentityRow(@Nonnull Store store, + @Nonnull UUID bodyUuid, + boolean generatedChunkCollisionBody) { + Ref ref = store.addEntity(PhysicsEntities.entityHolder(store, + bodyUuid), + AddReason.SPAWN); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(bodyUuid, + ref); + if (generatedChunkCollisionBody) { + store.putComponent(ref, + ChunkCollisionSourceComponent.getComponentType(), + new ChunkCollisionSourceComponent("test-source", + 0, + 0, + 0, + "test-payload", + PartKind.BOX, + 0)); + } + return ref; } @Nonnull @@ -104,15 +133,12 @@ private static PhysicsBodySnapshot snapshot(@Nonnull UUID bodyUuid, @Nonnull private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( - int rowIndex, - @Nonnull UUID bodyUuid, - @Nonnull PhysicsBodyKind kind) { + @Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid) { return new PhysicsBodyRegistrationResource.BodyRegistrationPublication( - new TestPhysicsRef(rowIndex), - new PhysicsBodyRegistrationView(bodyUuid, - new SpaceId(1), - kind, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); + bodyRef, + bodyUuid, + new SpaceId(1)); } @Nonnull @@ -143,15 +169,4 @@ private static void markCurrentThreadAsWorldThread(@Nonnull Store } } - private static final class TestPhysicsRef extends Ref { - - private TestPhysicsRef(int index) { - super(null, index); - } - - @Override - public boolean isValid() { - return true; - } - } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java new file mode 100644 index 00000000..e4fef1a4 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java @@ -0,0 +1,44 @@ +package dev.hytalemodding.impulse.core.internal.physicsstore; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class PhysicsBodyRegistrationMetadataRemovalTest { + + @Test + void legacyBodyRegistrationMetadataTypesAreRemoved() throws IOException { + Path sourceRoot = Path.of("src/main/java/dev/hytalemodding/impulse/core"); + assertFalse(Files.exists(sourceRoot.resolve("plugin/body/PhysicsBodyRegistrationView.java"))); + assertFalse(Files.exists(sourceRoot.resolve("plugin/body/PhysicsBodyKind.java"))); + assertFalse(Files.exists(sourceRoot.resolve("plugin/body/PhysicsBodyPersistenceMode.java"))); + + assertNoProductionSourceContains(sourceRoot, + "PhysicsBodyRegistrationView", + "PhysicsBodyKind", + "PhysicsBodyPersistenceMode", + "registrationView", + "registrationViews"); + } + + private static void assertNoProductionSourceContains(@Nonnull Path sourceRoot, + @Nonnull String... forbiddenValues) throws IOException { + try (Stream paths = Files.walk(sourceRoot)) { + for (Path source : paths + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".java")) + .toList()) { + String contents = Files.readString(source); + for (String forbidden : forbiddenValues) { + assertFalse(contents.contains(forbidden), + () -> source + " still contains " + forbidden); + } + } + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java index 4887a6e7..82f89b2c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java @@ -32,9 +32,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -118,8 +115,8 @@ void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { assertEquals(0, space.runtime().jointCount(space.handle().value())); assertNull(snapshots.getBody(bodyAUuid)); assertNotNull(snapshots.getBody(bodyBUuid)); - assertNull(registrations.getBodyRegistrationView(bodyAUuid)); - assertNotNull(registrations.getBodyRegistrationView(bodyBUuid)); + assertFalse(registrations.hasBody(bodyAUuid)); + assertNotNull(registrations.getBodySpaceId(bodyBUuid)); assertFalse(bodyARef.isValid()); assertFalse(jointRef.isValid()); assertNotNull(store.getComponent(remainingBodyRef, BodyComponent.getComponentType())); @@ -157,9 +154,7 @@ private static Ref addBody(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull Ref spaceRef, @Nonnull UUID bodyUuid) { - BodyComponent body = new BodyComponent(spaceUuid, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + BodyComponent body = new BodyComponent(spaceUuid); body.setSpaceRef(spaceRef); Ref bodyRef = store.addEntity(PhysicsEntities.bodyHolder(store, bodyUuid, @@ -334,10 +329,8 @@ private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publi @Nonnull Ref bodyRef, @Nonnull UUID bodyUuid) { return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, - new PhysicsBodyRegistrationView(bodyUuid, - new SpaceId(42), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); + bodyUuid, + new SpaceId(42)); } private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java index 4ccc8b8d..5ad524a7 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java @@ -11,8 +11,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.bson.BsonDocument; @@ -139,8 +137,6 @@ private static PersistentPhysicsStoreResource validResource(String backendId) { private static PersistentBodyDto bodyDto() { return new PersistentBodyDto(BODY_UUID, SPACE_UUID, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT, PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java index 5776c2b0..30bbbaf9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java @@ -35,8 +35,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -82,18 +80,16 @@ class PhysicsStoreHolderPersistenceTest { Path tempDir; @Test - void holderBlobsPersistDurableRowsWithSnapshotTargetsOnly() { + void holderBlobsPersistUuidBodyRowsWithSnapshotTargetsOnly() { StoreFixture fixture = store("holder-capture", tempDir.resolve("capture")); try { Ref spaceRef = addSpace(fixture.store(), SPACE_UUID); Ref bodyARef = addBody(fixture.store(), BODY_A_UUID, - PhysicsBodyPersistenceMode.PERSISTENT, spaceRef, null); Ref bodyBRef = addBody(fixture.store(), BODY_B_UUID, - PhysicsBodyPersistenceMode.PERSISTENT, spaceRef, null); fixture.store().putComponent(bodyARef, @@ -101,7 +97,6 @@ void holderBlobsPersistDurableRowsWithSnapshotTargetsOnly() { BodyCommandComponent.wake()); addBody(fixture.store(), GENERATED_BODY_UUID, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, spaceRef, new ChunkCollisionSourceComponent("0:0:0", 0, @@ -126,7 +121,9 @@ void holderBlobsPersistDurableRowsWithSnapshotTargetsOnly() { assertNotNull(bodyA); assertNotNull(holder(decoded, BODY_B_UUID)); assertNotNull(holder(decoded, JOINT_UUID)); - assertNull(holder(decoded, GENERATED_BODY_UUID)); + Holder generatedBody = holder(decoded, GENERATED_BODY_UUID); + assertNotNull(generatedBody); + assertNull(generatedBody.getComponent(ChunkCollisionSourceComponent.getComponentType())); assertNull(bodyA.getComponent(BodyCommandComponent.getComponentType())); BodyComponent body = bodyA.getComponent(BodyComponent.getComponentType()); @@ -151,7 +148,6 @@ void hydrationPrefersHolderStorageOverLegacyDtoResource() { Ref spaceRef = addSpace(source.store(), SPACE_UUID); addBody(source.store(), BODY_A_UUID, - PhysicsBodyPersistenceMode.PERSISTENT, spaceRef, null); PhysicsStoreHolderStorage.save(source.store()).join(); @@ -242,9 +238,7 @@ void holderHydrationRejectsDuplicateUuidWithoutAddingPartialRows() { Holder second = fixture.store().getRegistry().newHolder(); second.addComponent(UuidComponent.getComponentType(), new UuidComponent(SPACE_UUID)); second.addComponent(BodyComponent.getComponentType(), - new BodyComponent(SPACE_UUID, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT)); + new BodyComponent(SPACE_UUID)); writeHolderStorage(fixture.store(), List.of(first, second)); new PersistenceHydrationSystem().tick(0.0f, 0, fixture.store()); @@ -267,9 +261,7 @@ void holderHydrationRejectsBodyWithoutSavedSpaceWithoutAddingPartialRows() { Holder body = fixture.store().getRegistry().newHolder(); body.addComponent(UuidComponent.getComponentType(), new UuidComponent(BODY_A_UUID)); body.addComponent(BodyComponent.getComponentType(), - new BodyComponent(SPACE_UUID, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT)); + new BodyComponent(SPACE_UUID)); writeHolderStorage(fixture.store(), List.of(body)); new PersistenceHydrationSystem().tick(0.0f, 0, fixture.store()); @@ -330,12 +322,11 @@ private static Ref addSpace(@Nonnull Store store, @Nonnull private static Ref addBody(@Nonnull Store store, @Nonnull UUID bodyUuid, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull Ref spaceRef, ChunkCollisionSourceComponent source) { Holder holder = PhysicsEntities.bodyHolder(store, bodyUuid, - body(SPACE_UUID, persistenceMode, spaceRef), + body(SPACE_UUID, spaceRef), new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false), target(), new ColliderComponent(new Vector3f(), new Quaternionf(), false), @@ -361,9 +352,8 @@ private static Ref addBody(@Nonnull Store store, @Nonnull private static BodyComponent body(@Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull Ref spaceRef) { - BodyComponent body = new BodyComponent(spaceUuid, PhysicsBodyKind.BODY, persistenceMode); + BodyComponent body = new BodyComponent(spaceUuid); body.setSpaceRef(spaceRef); return body; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java index d6934509..e5011186 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java @@ -6,14 +6,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.annotation.Nonnull; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class PhysicsBodyRegistryTest { @@ -28,14 +24,10 @@ void indexesRegistrationsBySpaceWithoutScanningUnrelatedSpaces() { registry.registerBody(firstId, handle(11L), - firstSpace, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + firstSpace); registry.registerBody(secondId, handle(12L), - secondSpace, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + secondSpace); List firstSpaceIds = new ArrayList<>(); registry.forEachRegistration(firstSpace, @@ -59,41 +51,35 @@ void reRegisteringSameBodyWithDifferentSpaceIsRejectedWithoutMovingIndex() { PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); registry.registerBody(bodyId, handle(21L), - firstSpace, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + firstSpace); assertThrows(IllegalArgumentException.class, () -> registry.registerBody(bodyId, handle(21L), - secondSpace, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY)); + secondSpace)); assertEquals(1, registry.getRegistrationCount(firstSpace)); assertEquals(0, registry.getRegistrationCount(secondSpace)); } @Test - void registrationViewsReuseCachedImmutableMetadata() { + void registrationsExposeBodyIdentityAndSpace() { SpaceId space = new SpaceId(1); UUID bodyId = new UUID(0L, 4L); PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); registry.registerBody(bodyId, handle(31L), - space, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + space); - PhysicsBodyRegistrationView first = registry.getRegistrationView(bodyId); - PhysicsBodyRegistrationView second = registry.getRegistrationView(bodyId); - PhysicsBodyRegistrationView fromCollection = registry.getRegistrationViews() + PhysicsBodyRegistration first = registry.getRegistration(bodyId); + PhysicsBodyRegistration second = registry.getRegistration(bodyId); + PhysicsBodyRegistration fromCollection = registry.getRegistrations() .iterator() .next(); assertSame(first, second); assertSame(first, fromCollection); - Assertions.assertNotNull(first); assertEquals(bodyId, first.bodyUuid()); + assertEquals(space, first.spaceId()); } @Nonnull diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java index debea942..7389ec6b 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java @@ -16,8 +16,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSpaceFrame; @@ -63,9 +61,7 @@ void refreshPassesLazySelectedBodiesToBackend() { PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); registry.registerBody(bodyId, new BackendBodyHandle(backendBodyId), - spaceId, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + spaceId); PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); assertEquals(1, store.refresh(List.of(binding), registry)); @@ -133,25 +129,19 @@ void internalNearVisitorExposesSnapshotMetadataWithoutEntryDto() { PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); store.put(nearBodyId, nearSnapshot, - spaceId, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + spaceId); store.put(farBodyId, farSnapshot, - spaceId, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.PERSISTENT); + spaceId); List visited = new ArrayList<>(); int candidates = store.forEachIndexedNear(spaceId, new Vector3f(0.0f, 2.0f, 3.0f), 4.0f, - (bodyId, snapshot, bodySpaceId, kind, persistenceMode) -> { + (bodyId, snapshot, bodySpaceId) -> { visited.add(bodyId); assertSame(nearSnapshot, snapshot); assertEquals(spaceId, bodySpaceId); - assertEquals(PhysicsBodyKind.BODY, kind); - assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, persistenceMode); }); assertEquals(1, candidates); @@ -168,8 +158,6 @@ private static PublishedPhysicsSnapshotFrame frame(SpaceId spaceId, 0L, 0L, 0L, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, position, new Quaternionf(), new Vector3f(), diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java index f19637c5..dd9acfc2 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java @@ -15,8 +15,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldLifecycleState; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; @@ -46,7 +44,7 @@ void stalePublishedFrameIsRejectedAfterWorldEpochChanges() { assertEquals(0, fixture.state.applyPublishedSnapshotFrame(staleFrame, fixture.registry, 21L)); assertEquals(0, fixture.state.bodySnapshotCount()); - assertNull(fixture.registry.getPublishedRegistrationView(bodyUuid)); + assertNull(fixture.registry.getPublishedRegistrationSpaceId(bodyUuid)); assertEquals(0, fixture.state.latestEventFrame().snapshotPublicationCount()); } @@ -138,9 +136,7 @@ private static UUID registerBox(Fixture fixture) { UUID bodyUuid = UUID.randomUUID(); fixture.registry.registerBody(bodyUuid, new BackendBodyHandle(backendBodyId), - fixture.binding.spaceId(), - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + fixture.binding.spaceId()); return bodyUuid; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java index 152cb4cd..3eb16c87 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java @@ -30,8 +30,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -62,11 +60,11 @@ void spaceSurfaceComponentsSyncGeneratedRowsAndBoundBackends() { PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); proxy.registerSystem(new PersistenceHydrationSystem()); proxy.registerSystem(new IdentityIndexSystem()); - proxy.registerSystem(new PhysicsChunkSettingsIndexSystem()); proxy.registerSystem(new SpaceBindingSystem()); proxy.registerSystem(new SpaceSettingsApplicationSystem()); - proxy.registerSystem(new ChunkCollisionMutationDrainSystem()); proxy.registerSystem(new BodyBindingSystem()); + proxy.registerSystem(new PhysicsChunkSettingsIndexSystem()); + proxy.registerSystem(new ChunkCollisionMutationDrainSystem()); proxy.registerSystem(new ChunkCollisionComponentSyncSystem()); Store store = registry.addStore( new PhysicsStore(TestInstanceFactory.world("chunk-collision-component-sync-test")), @@ -169,9 +167,7 @@ private static GeneratedRow addGeneratedBoxRow(@Nonnull Store stor sourceKey, PartKind.BOX, 0); - BodyComponent body = new BodyComponent(spaceUuid, - PhysicsBodyKind.TERRAIN, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + BodyComponent body = new BodyComponent(spaceUuid); body.setSpaceRef(runtime.spaceRef()); Holder holder = PhysicsEntities.bodyHolder(store, bodyUuid, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 6872ffff..3cd859de 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -41,8 +41,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; @@ -668,8 +666,6 @@ private static void assertGeneratedVoxel(@Nonnull Store store, assertEquals(spaceUuid, body.getSpaceUuid()); assertNotNull(body.getSpaceRef()); assertEquals(spaceRef.getIndex(), body.getSpaceRef().getIndex()); - assertEquals(PhysicsBodyKind.TERRAIN, body.getKind()); - assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, body.getPersistenceMode()); ShapeComponent shape = store.getComponent(bodyRef, ShapeComponent.getComponentType()); assertNotNull(shape); @@ -726,8 +722,6 @@ private static void assertGeneratedBox(@Nonnull Store store, assertEquals(spaceUuid, body.getSpaceUuid()); assertNotNull(body.getSpaceRef()); assertEquals(spaceRef.getIndex(), body.getSpaceRef().getIndex()); - assertEquals(PhysicsBodyKind.TERRAIN, body.getKind()); - assertEquals(PhysicsBodyPersistenceMode.RUNTIME_ONLY, body.getPersistenceMode()); DynamicsComponent dynamics = store.getComponent(bodyRef, DynamicsComponent.getComponentType()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index 07e173fb..06a7e506 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -34,8 +34,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -167,9 +165,7 @@ private static Ref addVoxelRow(@Nonnull Store store, sourceKey, PartKind.NATIVE_VOXELS, 0); - BodyComponent body = new BodyComponent(spaceUuid, - PhysicsBodyKind.TERRAIN, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + BodyComponent body = new BodyComponent(spaceUuid); body.setSpaceRef(runtime.spaceRef()); Holder holder = PhysicsEntities.bodyHolder(store, bodyUuid, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java index 4054fcea..7995b8b0 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/snapshot/PublishedPhysicsSnapshotFrameTest.java @@ -8,8 +8,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -39,8 +37,6 @@ void bodySnapshotCopiesPoseAndVelocityOnConstructionAndAccess() { 20L, 30L, 40L, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, position, rotation, linearVelocity, @@ -98,8 +94,6 @@ void factoryCopiesExistingSnapshotData() { 2L, 3L, 4L, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT, ownerLaneSnapshot); ownerLaneSnapshot.position().zero(); ownerLaneSnapshot.rotation().identity(); @@ -111,8 +105,6 @@ void factoryCopiesExistingSnapshotData() { assertEquals(new Vector3f(0.0f, 2.0f, 0.0f), published.linearVelocity()); assertEquals(new Vector3f(0.0f, 0.0f, 3.0f), published.angularVelocity()); assertEquals(4L, published.registrationGeneration()); - assertEquals(PhysicsBodyKind.BODY, published.kind()); - assertEquals(PhysicsBodyPersistenceMode.PERSISTENT, published.persistenceMode()); assertEquals(PhysicsBodyType.KINEMATIC, published.bodyType()); assertEquals(ShapeType.BOX, published.shapeType()); assertEquals(new Vector3f(0.25f, 0.5f, 0.75f), published.boxHalfExtents()); @@ -153,8 +145,6 @@ void factoryDoesNotAliasThreadLocalScratchAcrossPublishedSnapshots() { 2L, 3L, 4L, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT, firstSnapshot); PublishedPhysicsBodySnapshot.from(BODY_ID, SPACE_ID, @@ -162,8 +152,6 @@ void factoryDoesNotAliasThreadLocalScratchAcrossPublishedSnapshots() { 2L, 2L, 2L, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.PERSISTENT, secondSnapshot); assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), first.position()); @@ -326,8 +314,6 @@ private static PublishedPhysicsBodySnapshot bodySnapshot(UUID bodyId, worldEpoch, spaceEpoch, 40L, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, new Vector3f(1.0f, 2.0f, 3.0f), new Quaternionf(), new Vector3f(4.0f, 5.0f, 6.0f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index 0f16b125..d57b74d5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.examples.commands; import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -8,12 +9,21 @@ import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.BlockEntity; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -69,18 +79,27 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsThreading.requireWorldThread(physicsStore, "spawn an example PhysicsStore body entity"); UUID bodyUuid = UUID.randomUUID(); - BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(space.spaceRef(), + BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody(space.spaceRef(), bodyUuid, - ExamplePhysicsUtils.toVector3f(position), + toVector3f(position), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.material(0.5f, 0.5f), null); Ref bodyRef = physicsStore.addEntity( - ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), + PhysicsEntities.bodyHolder(physicsStore, + descriptor.bodyUuid(), + descriptor.body(), + descriptor.dynamics(), + descriptor.target(), + descriptor.collider(), + descriptor.shape(), + descriptor.material(), + descriptor.filter()), AddReason.SPAWN); - store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( - time, + + assert bodyRef != null; + store.addEntity(attachedPhysicsBlockEntityHolder(time, bodyRef, bodyUuid, blockType(ctx), @@ -103,4 +122,47 @@ private String blockType(@Nonnull CommandContext ctx) { ? ExampleBlockEntityVisuals.resolveBlockType(blockTypeArg.get(ctx)) : ExamplePhysicsUtils.DEFAULT_BLOCK_TYPE; } + + @Nonnull + private static Holder attachedPhysicsBlockEntityHolder(@Nonnull TimeResource time, + @Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull String blockType, + @Nonnull Vector3d position, + @Nonnull Vector3f localPositionOffset, + @Nonnull Quaternionf localRotationOffset, + float visualOriginOffsetY, + boolean controllable) { + requirePhysicsEntityVisuals(); + Holder holder = BlockEntity.assembleDefaultBlockEntity(time, + ExampleBlockEntityVisuals.resolveBlockType(blockType), + new Vector3d(position)); + holder.tryRemoveComponent(DespawnComponent.getComponentType()); + holder.tryRemoveComponent(Velocity.getComponentType()); + + BodyAttachmentComponent attachment = BodyAttachmentComponent.impulseOwnedVisual(bodyUuid, + localPositionOffset, + localRotationOffset, + visualOriginOffsetY); + attachment.setBodyRef(bodyRef); + holder.addComponent(BodyAttachmentComponent.getComponentType(), attachment); + if (controllable && PhysicsControlSessions.isAvailable()) { + holder.addComponent(ImpulseControllableComponent.getComponentType(), + new ImpulseControllableComponent()); + } + return holder; + } + + private static void requirePhysicsEntityVisuals() { + if (!PhysicsEntityAttachments.isAvailable()) { + throw new IllegalStateException( + "Impulse PhysicsEntity integration is not available. " + + "Enable HytaleModding:ImpulsePhysicsEntity to spawn entity-backed example visuals."); + } + } + + @Nonnull + private static Vector3f toVector3f(@Nonnull Vector3d vector) { + return new Vector3f((float) vector.x, (float) vector.y, (float) vector.z); + } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index bcc26641..c9064912 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -20,10 +20,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; @@ -231,9 +229,7 @@ private static BodyEntityDescriptor anchorBodyEntity(@Nonnull Ref RigidBodySpawnSettings.material(0.5f, 0.0f) .withSensor(true) .withCollisionFilter(PhysicsCollisionFilters.TERRAIN, 0), - null, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY); + null); } @Nonnull @@ -262,13 +258,13 @@ private static HitSelection selectControllableHit(@Nonnull Store p || !hit.bodyRef().isValid()) { continue; } - PhysicsBodyRegistrationView registration = - PhysicsBodies.registrationView(physicsStore, hit.bodyRef()); - if (registration == null || registration.kind() != PhysicsBodyKind.BODY) { + SpaceId bodySpaceId = PhysicsBodies.spaceId(physicsStore, hit.bodyRef()); + if (bodySpaceId == null + || PhysicsChunkCollision.isChunkCollisionBody(physicsStore, hit.bodyRef())) { continue; } candidates.add(new HitCandidate(hit.bodyRef(), - registration.spaceId(), + bodySpaceId, hit.point(), hit.fraction(), hit.distance())); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index be3c9ed3..894c3bdc 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -14,7 +14,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -52,31 +51,28 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); - Store physicsStore = PhysicsThreading.store(world); - PhysicsThreading.requireWorldThread(physicsStore, - "spawn example PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-3.0, 5.0, 4.0); spawnSphere(store, - physicsStore, + world, time, space.spaceRef(), new Vector3d(origin), 0.05f, 0.9f, 3.0f); spawnSphere(store, - physicsStore, + world, time, space.spaceRef(), new Vector3d(origin).add(2.0, 0.0, 0.0), 0.95f, 0.9f, 3.0f); spawnSphere(store, - physicsStore, + world, time, space.spaceRef(), new Vector3d(origin).add(4.0, 0.0, 0.0), 0.5f, 0.0f, 2.0f); spawnSphere(store, - physicsStore, + world, time, space.spaceRef(), new Vector3d(origin).add(6.0, 0.0, 0.0), @@ -88,7 +84,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawnSphere(@Nonnull Store store, - @Nonnull Store physicsStore, + @Nonnull World world, @Nonnull TimeResource time, @Nonnull Ref spaceRef, @Nonnull Vector3d position, @@ -103,10 +99,8 @@ private static void spawnSphere(@Nonnull Store store, 1.0f, RigidBodySpawnSettings.material(friction, restitution), new Vector3f(speed, 0.0f, 0.0f)); - Ref bodyRef = physicsStore.addEntity( - ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), - AddReason.SPAWN); - store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, descriptor); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, bodyUuid, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index aefe8c7d..ab23f2b6 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -25,7 +25,6 @@ import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; @@ -259,10 +258,9 @@ private static void attachView(@Nonnull CommandContext ctx, @Nullable private static UUID physicsStoreBodyUuid(@Nonnull Ref bodyRef) { - PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView( + return PhysicsBodies.bodyUuid( bodyRef.getStore(), bodyRef); - return registration != null ? registration.bodyUuid() : null; } } @@ -368,7 +366,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, maxFragments, strength, verticalLift); - Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, + Holder holder = ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder(time, bodyRef, bodyUuid, blockType, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index 84be66aa..94e0bd60 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -15,7 +15,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -53,41 +52,38 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); - Store physicsStore = PhysicsThreading.store(world); - PhysicsThreading.requireWorldThread(physicsStore, - "spawn example PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-4.0, 3.0, 3.0); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.BOX, PhysicsAxis.Y, origin, 0); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.SPHERE, PhysicsAxis.Y, origin, 2); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.CAPSULE, PhysicsAxis.Y, origin, 4); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.CYLINDER, PhysicsAxis.Y, origin, 6); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.CONE, @@ -99,7 +95,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawn(@Nonnull Store store, - @Nonnull Store physicsStore, + @Nonnull World world, @Nonnull TimeResource time, @Nonnull Ref spaceRef, @Nonnull ShapeType type, @@ -115,10 +111,8 @@ private static void spawn(@Nonnull Store store, 1.0f, RigidBodySpawnSettings.material(0.7f, 0.35f), null); - Ref bodyRef = physicsStore.addEntity( - ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), - AddReason.SPAWN); - store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, descriptor); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, bodyUuid, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index a9ed05e1..b690c816 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -13,8 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -175,8 +173,6 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, box, 1.0f, spawnSettings, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, bodies -> { for (int i = 0; i < count; i++) { bodies.addBody(layout.positionX(i), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 1215d2c0..41cd1453 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -14,8 +14,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; @@ -216,8 +214,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, box, 1.0f, spawnSettings, - PhysicsBodyKind.BODY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, bodies -> { for (int i = 0; i < count; i++) { bodies.addBody(layout.positionX(i), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index c56e4088..4cf3f8ab 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -12,8 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; @@ -83,8 +81,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, box, 1.0f, spawnSettings, - PhysicsBodyKind.TEMPORARY, - PhysicsBodyPersistenceMode.RUNTIME_ONLY, spawns -> { for (int i = 0; i < count; i++) { int x = i % side; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index b2dfb9e2..7475dbfb 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -15,7 +15,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; @@ -66,9 +65,6 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); - Store physicsStore = PhysicsThreading.store(world); - PhysicsThreading.requireWorldThread(physicsStore, - "spawn stress shape PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-12.0, 5.0, 5.0); for (int set = 0; set < sets; set++) { @@ -78,35 +74,35 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Vector3d base = new Vector3d(origin).add(col * 7.0, row * 2.2, row * 1.5); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.BOX, axis, base, 0.0); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.SPHERE, axis, base, 1.2); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.CAPSULE, axis, base, 2.4); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.CYLINDER, axis, base, 3.6); spawn(store, - physicsStore, + world, time, space.spaceRef(), ShapeType.CONE, @@ -120,7 +116,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawn(@Nonnull Store store, - @Nonnull Store physicsStore, + @Nonnull World world, @Nonnull TimeResource time, @Nonnull Ref spaceRef, @Nonnull ShapeType type, @@ -136,10 +132,8 @@ private static void spawn(@Nonnull Store store, 1.0f, RigidBodySpawnSettings.material(0.6f, 0.25f), null); - Ref bodyRef = physicsStore.addEntity( - ExamplePhysicsUtils.bodyHolder(physicsStore, descriptor), - AddReason.SPAWN); - store.addEntity(ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder( + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, descriptor); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, bodyUuid, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index 79219643..fb6603ef 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -196,7 +196,7 @@ private static void spawnGroupVisuals(@Nonnull TimeResource time, boolean controllableAssigned = false; for (FragmentVisual visual : group.visualBlocks()) { boolean controllable = body.controllable() && !controllableAssigned; - Holder holder = ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(time, + Holder holder = ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder(time, body.bodyRef(), body.bodyUuid(), visual.blockType(), @@ -244,7 +244,6 @@ private static List collectFragments(@Nonnull World world, } @Nullable - @SuppressWarnings({"deprecation", "removal"}) private static FragmentBlock removeFragmentCandidate(@Nonnull World world, int x, int y, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index bd746f66..5325592b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -8,14 +8,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; @@ -99,9 +97,7 @@ private static void armIfExplosiveTouchesWorld(@Nonnull CommandBuffer physicsStore, @Nonnull UUID bodyUuid) { - PhysicsBodyRegistrationView registration = PhysicsBodies.registrationView(physicsStore, - bodyUuid); - return registration != null && registration.kind().isTerrain(); + return PhysicsChunkCollision.isChunkCollisionBody(physicsStore, bodyUuid); } @Nonnull diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 2032fb68..66cdf2fe 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -15,7 +15,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyRegistrationView; import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; @@ -134,13 +133,13 @@ private static SpaceId attachmentSpaceId(@Nonnull Store store, @Nonnull BodyAttachmentComponent attachment) { Store physics = PhysicsThreading.store(store.getExternalData().getWorld()); Ref bodyRef = attachment.getBodyRef(); - PhysicsBodyRegistrationView registration = bodyRef != null && bodyRef.isValid() - ? PhysicsBodies.registrationView(physics, bodyRef) + SpaceId spaceId = bodyRef != null && bodyRef.isValid() + ? PhysicsBodies.spaceId(physics, bodyRef) : null; - if (registration == null) { - registration = PhysicsBodies.registrationView(physics, attachment.getBodyUuid()); + if (spaceId == null) { + spaceId = PhysicsBodies.spaceId(physics, attachment.getBodyUuid()); } - return registration != null ? registration.spaceId() : null; + return spaceId; } @Nullable diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 7da38f3d..eb5ff703 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -13,8 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyKind; -import dev.hytalemodding.impulse.core.plugin.body.PhysicsBodyPersistenceMode; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; @@ -163,24 +161,9 @@ private static Ref addPhysicsStoreBody(@Nonnull Store addPhysicsStoreBodyUnchecked(@Nonnull Store store, - @Nonnull BodyEntityDescriptor descriptor, - @Nonnull DynamicsComponent dynamics, - @Nullable TargetComponent target) { return store.addEntity(bodyHolder(store, descriptor, dynamics, target), AddReason.SPAWN); } - @Nonnull - public static Holder bodyHolder(@Nonnull Store store, - @Nonnull BodyEntityDescriptor descriptor) { - Objects.requireNonNull(descriptor, "descriptor"); - return bodyHolder(store, descriptor, descriptor.dynamics(), descriptor.target()); - } - @Nonnull private static Holder bodyHolder(@Nonnull Store store, @Nonnull BodyEntityDescriptor descriptor, @@ -252,61 +235,7 @@ public static BodyEntityDescriptor bodyEntity(@Nonnull Ref spaceRe shape, mass, settings, - linearVelocity, - PhysicsBodyPersistenceMode.PERSISTENT); - } - - @Nonnull - private static BodyEntityDescriptor bodyEntity(@Nonnull Ref spaceRef, - @Nonnull UUID bodyUuid, - @Nonnull Vector3f bodyCenter, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode) { - return PhysicsBodyEntities.body(spaceRef, - bodyUuid, - bodyCenter, - shape, - PhysicsBodyType.DYNAMIC, - mass, - settings, - linearVelocity, - kind, - persistenceMode); - } - - @Nonnull - public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, - @Nonnull Consumer builder) { - DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(world, - spaceId, - expectedBodies, - shape, - mass, - settings, - kind, - persistenceMode, - builder); - if (plan.isEmpty()) { - return new BodyEntityBatchTiming(0, plan.setupWallNanos(), 0L); - } - - long applyStartNanos = System.nanoTime(); - addPhysicsStoreBodies(world, plan.bodies()); - long physicsStoreApplyNanos = System.nanoTime() - applyStartNanos; - return new BodyEntityBatchTiming(plan.count(), - plan.setupWallNanos(), - physicsStoreApplyNanos); + linearVelocity); } @Nonnull @@ -317,8 +246,6 @@ public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World w @Nonnull PhysicsShapeSpec shape, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull Consumer builder) { DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(spaceRef, spaceId, @@ -326,8 +253,6 @@ public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World w shape, mass, settings, - kind, - persistenceMode, builder); if (plan.isEmpty()) { return new BodyEntityBatchTiming(0, plan.setupWallNanos(), 0L); @@ -348,15 +273,11 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, @Nonnull PhysicsShapeSpec shape, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nonnull PhysicsBodyKind kind, - @Nonnull PhysicsBodyPersistenceMode persistenceMode, @Nonnull Consumer builder) { Objects.requireNonNull(world, "world"); Objects.requireNonNull(spaceId, "spaceId"); Objects.requireNonNull(shape, "shape"); Objects.requireNonNull(settings, "settings"); - Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(persistenceMode, "persistenceMode"); long setupStartNanos = System.nanoTime(); BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); @@ -377,8 +298,6 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, shape, mass, settings, - kind, - persistenceMode, batch, setupStartNanos); } @@ -390,15 +309,11 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref builder) { Objects.requireNonNull(spaceRef, "spaceRef"); Objects.requireNonNull(spaceId, "spaceId"); Objects.requireNonNull(shape, "shape"); Objects.requireNonNull(settings, "settings"); - Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(persistenceMode, "persistenceMode"); PhysicsThreading.requireWorldThread(spaceRef.getStore(), "add dynamic PhysicsStore body entities"); @@ -420,8 +335,6 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); - bodies.add(bodyEntity(spaceRef, + bodies.add(PhysicsBodyEntities.body(spaceRef, bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, + PhysicsBodyType.DYNAMIC, mass, settings, - null, - kind, - persistenceMode)); + null)); } return new DynamicBodyBatchPlan(bodies, System.nanoTime() - setupStartNanos); @@ -475,80 +385,6 @@ public static SpawnedBlockBody attachBlockBody(@Nonnull Store store return new SpawnedBlockBody(created.bodyUuid(), created.spaceId(), entity); } - @Nonnull - public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store store, - @Nonnull TimeResource time, - long serverTick, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder) { - return spawnBlockBodiesInternal(store, - time, - serverTick, - spaceId, - expectedBodies, - blockType, - shape, - mass, - settings, - builder, - true).collectedBodies(); - } - - @Nonnull - public static SpawnedBlockBody[] spawnBlockBodies(@Nonnull Store store, - @Nonnull TimeResource time, - long serverTick, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder) { - return spawnBlockBodiesInternal(store, - time, - serverTick, - spaceRef, - spaceId, - expectedBodies, - blockType, - shape, - mass, - settings, - builder, - true).collectedBodies(); - } - - @Nonnull - public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, - @Nonnull TimeResource time, - long serverTick, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder) { - return spawnBlockBodiesInternal(store, - time, - serverTick, - spaceId, - expectedBodies, - blockType, - shape, - mass, - settings, - builder, - false).timing(); - } - @Nonnull public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, @Nonnull TimeResource time, @@ -727,20 +563,6 @@ static void addControllableMarkerIfAvailable(@Nonnull Holder holder } } - @Nullable - public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store store, - @Nonnull TimeResource time, - @Nonnull UUID bodyUuid, - @Nonnull Vector3d visualPosition, - @Nullable String blockType) { - return spawnExternalBodyViewBlockEntity(store, - time, - null, - bodyUuid, - visualPosition, - blockType); - } - @Nullable public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store store, @Nonnull TimeResource time, @@ -749,7 +571,10 @@ public static Ref spawnExternalBodyViewBlockEntity(@Nonnull Store holder = blockEntityHolder(time, blockType, visualPosition); + + Holder holder = + ExampleBlockEntityVisuals.impulseOwnedBlockVisual(time, blockType, visualPosition); + holder.addComponent(BodyAttachmentComponent.getComponentType(), externalBodyAttachment(bodyUuid, bodyRef)); return store.addEntity(holder, AddReason.SPAWN); @@ -763,7 +588,7 @@ private static Ref spawnAttachedBlockEntity(@Nonnull Store holder = attachedPhysicsStoreBlockEntityHolder(time, + Holder holder = attachedPhysicsBlockEntityHolder(time, bodyRef, physicsBodyUuid, blockType, @@ -776,27 +601,7 @@ private static Ref spawnAttachedBlockEntity(@Nonnull Store attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, - @Nonnull UUID physicsBodyUuid, - @Nullable String blockType, - @Nonnull Vector3d visualPosition, - @Nonnull Vector3f localPositionOffset, - @Nonnull Quaternionf localRotationOffset, - float visualOriginOffsetY, - boolean controllable) { - return attachedPhysicsStoreBlockEntityHolder(time, - null, - physicsBodyUuid, - blockType, - visualPosition, - localPositionOffset, - localRotationOffset, - visualOriginOffsetY, - controllable); - } - - @Nonnull - public static Holder attachedPhysicsStoreBlockEntityHolder(@Nonnull TimeResource time, + public static Holder attachedPhysicsBlockEntityHolder(@Nonnull TimeResource time, @Nullable Ref bodyRef, @Nonnull UUID physicsBodyUuid, @Nullable String blockType, diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java new file mode 100644 index 00000000..63ccd634 --- /dev/null +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java @@ -0,0 +1,25 @@ +package dev.hytalemodding.impulse.examples.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class DropCommandTest { + + @Test + void dropCommandOwnsBodyAndVisualAssembly() throws IOException { + String source = Files.readString(Path.of("src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java")); + + assertTrue(source.contains("PhysicsBodyEntities.dynamicBody(")); + assertTrue(source.contains("PhysicsEntities.bodyHolder(")); + assertTrue(source.contains("BodyAttachmentComponent.impulseOwnedVisual(")); + assertFalse(source.contains("ExamplePhysicsUtils.bodyEntity(")); + assertFalse(source.contains("ExamplePhysicsUtils.toVector3f(")); + assertFalse(source.contains("ExamplePhysicsUtils.addPhysicsStoreBody(")); + assertFalse(source.contains("ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(")); + } +} From 9f94fbc205a5c926cff9c26aec50ff82e5a9db97 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 08:53:13 +0200 Subject: [PATCH 477/534] refactor(core): remove legacy physics world facade Signed-off-by: Blovien --- gradle.properties | 2 +- .../hytalemodding/impulse/api/SpaceId.java | 3 +- impulse-core/README.md | 9 +- .../core/internal/commands/CleanCommand.java | 11 +- .../core/internal/commands/SpaceCommand.java | 30 +- .../internal/commands/SpaceSelection.java | 2 +- .../commands/perf/PerfStatsCommand.java | 4 +- .../EventCollectionSettingCommand.java | 6 +- .../settings/MaxStepDtSettingCommand.java | 4 +- .../SimulationStepsSettingCommand.java | 4 +- .../settings/SolverSettingsCommand.java | 9 +- .../settings/StepModeSettingCommand.java | 8 +- .../StepSchedulingSettingCommand.java | 4 +- .../crucible/ImpulseApiCrucibleTests.java | 225 ++- ...tachedStreamingBenchmarkCrucibleTests.java | 75 +- .../crucible/ImpulseLiveCrucibleTests.java | 31 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 67 +- .../PhysicsStoreBenchmarkQueries.java | 6 +- .../crucible/PhysicsStoreCrucibleSupport.java | 24 +- .../modules/control/ControlLifecycle.java | 20 - .../control/PhysicsControlRuntimeStates.java | 2 +- .../PhysicsKinematicControlSystem.java | 2 +- .../PhysicsStoreControlSessionMutations.java | 4 +- .../physicschunk/PhysicsChunkLifecycle.java | 28 - .../commands/CollisionLodSettingsCommand.java | 4 +- .../PhysicsChunkPerfReportCommand.java | 8 +- .../commands/PhysicsChunkSettingsCommand.java | 4 +- .../commands/PhysicsChunkSpaceSelection.java | 2 +- .../PhysicsChunkCollisionProducerSystem.java | 2 +- .../PhysicsEntityTypeRegistry.java | 31 +- .../VisualMaterializationSettingsCommand.java | 4 +- .../commands/VisualSyncSettingsCommand.java | 4 +- .../PersistentPhysicsStorePreflight.java | 13 +- .../PersistentPhysicsStoreStorage.java | 10 +- .../persistence/PersistentSpaceDto.java | 13 - .../PhysicsAsyncCompletions.java} | 6 +- .../PhysicsSpaceMutations.java} | 153 +- .../PhysicsStoreRowCleanup.java | 48 +- .../PhysicsStoreRuntimeCleaner.java | 4 +- .../PhysicsTopologyMutations.java} | 10 +- .../resources/PhysicsDebugResource.java | 2 +- .../PhysicsStoreRegistration.java | 10 +- .../PhysicsBodyRegistrationResource.java | 24 +- .../PhysicsBodySyncStateResource.java | 65 + .../resources/PhysicsDebugResource.java | 8 +- .../resources/PhysicsResourceTypes.java | 10 +- .../resources/PhysicsSnapshotResource.java | 30 +- .../resources/PhysicsSpaceRuntime.java | 83 +- .../PhysicsStoreReadQueueResource.java | 6 +- .../PhysicsVisualInterestResource.java | 173 ++ .../PhysicsWorldRuntimeResource.java | 1630 ----------------- .../systems/BodyCommandApplicationSystem.java | 1 + .../ChunkCollisionComponentSyncSystem.java | 1 + .../ChunkCollisionMutationDrainSystem.java | 14 +- .../ChunkCollisionVoxelStitchingSystem.java | 2 + .../CompletedStepPublicationSystem.java | 1 + .../systems/PersistenceHydrationSystem.java | 2 +- .../PhysicsChunkSettingsIndexSystem.java | 1 + .../systems/PhysicsStoreSystemSupport.java | 10 +- .../PhysicsWorldResourceAttachmentSystem.java | 39 - .../SpaceSettingsApplicationSystem.java | 3 +- .../systems/StaleBodyRemovalSystem.java | 3 +- .../{ => binding}/BodyBindingSystem.java | 4 +- .../{ => binding}/ColliderBindingSystem.java | 2 +- .../{ => binding}/JointBindingSystem.java | 3 +- .../{ => binding}/SpaceBindingSystem.java | 5 +- .../{ => binding}/TargetBindingSystem.java | 4 +- .../systems/debug/PhysicsDebugSystem.java | 146 +- .../debug/PhysicsStoreDebugQueries.java | 2 +- .../PhysicsStoreEventPublicationSystem.java | 2 +- .../systems/sync/PhysicsSyncPolicy.java | 38 +- .../systems/sync/PhysicsSyncSystem.java | 3 +- .../visual/GeneratedProxyLifecycle.java | 14 +- .../PhysicsProjectionCleanupSystem.java | 15 +- .../visual/VisualInterestCollector.java | 8 +- .../ExtensionSettingsComponent.java | 5 - .../plugin/components/ShapeComponent.java | 62 +- .../components/SolverSettingsComponent.java | 49 +- .../PhysicsEventCollectionMode.java | 2 +- .../control/PhysicsControlSessions.java | 4 +- .../physicschunk/PhysicsChunkCollision.java | 8 +- .../PhysicsChunkCollisionProfiling.java | 2 +- .../ChunkCollisionSettingsComponent.java | 5 - .../CollisionLodSettingsComponent.java | 5 - .../PhysicsEntityAttachments.java | 8 + .../PhysicsEntityDiagnostics.java | 5 +- .../physicsentity/PhysicsEntityTypes.java | 13 +- ...isualMaterializationSettingsComponent.java | 5 - .../VisualSyncSettingsComponent.java | 5 - .../persistence/PhysicsPersistence.java | 4 +- .../PhysicsAsync.java | 2 +- .../PhysicsBackendAccess.java | 6 +- .../PhysicsBodies.java | 6 +- .../PhysicsBodyEntities.java | 49 +- .../PhysicsDiagnostics.java | 2 +- .../PhysicsEntities.java | 2 +- .../PhysicsEntityRefs.java | 2 +- .../PhysicsJointEntities.java | 2 +- .../PhysicsRaycasts.java | 2 +- .../PhysicsSpaces.java | 137 +- .../PhysicsThreading.java | 28 +- .../PhysicsWorlds.java | 2 +- .../physicsstore/BodyEntityDescriptor.java | 72 - .../resources/PhysicsMutationHandle.java | 127 -- .../resources/PhysicsWorldResource.java | 28 - .../plugin/settings/PhysicsSpaceSettings.java | 128 -- .../plugin/settings/PhysicsWorldSettings.java | 3 +- .../simulation/view/RaycastHitView.java | 47 +- impulse-core/src/module-info/module-info.java | 4 +- .../ComponentGranularityStoreProbeTest.java | 300 ++- .../benchmark/IdentityFootprintProbeTest.java | 461 +++++ .../CleanCommandLifecycleGuardTest.java | 2 +- .../modules/control/ControlLifecycleTest.java | 46 +- ...PhysicsChunkRegistrationOwnershipTest.java | 27 - .../PersistentSpaceDtoSettingsTest.java | 62 +- .../PhysicsStoreTopologyMutationsTest.java | 8 +- .../PersistentPhysicsStoreResourceTest.java | 0 .../PhysicsStoreHolderPersistenceTest.java | 2 +- ...csBodyRegistrationMetadataRemovalTest.java | 44 - .../BodyVisualInterestStateTest.java | 4 - .../PhysicsEntityProjectionResourcesTest.java | 67 + .../PhysicsEventCollectionModeTest.java | 2 +- .../PhysicsStoreResourceIndexTest.java | 77 + .../PhysicsWorldRuntimeResourceTest.java | 43 - .../resources/PhysicsWorldSettingsTest.java | 2 +- ...ChunkCollisionComponentSyncSystemTest.java | 8 +- ...ChunkCollisionMutationDrainSystemTest.java | 80 +- ...hunkCollisionVoxelStitchingSystemTest.java | 2 +- .../systems/sync/PhysicsSyncPolicyTest.java | 42 +- .../systems/sync/PhysicsSyncSystemTest.java | 14 +- .../PhysicsProjectionCleanupSystemTest.java | 11 +- .../physics/PhysicsBodyEntitiesTest.java | 109 ++ .../PhysicsSpacesSettingsComponentTest.java | 2 +- .../settings/PhysicsSpaceSettingsTest.java | 362 ---- .../plugin/simulation/RaycastHitViewTest.java | 85 +- .../examples/commands/DropCommand.java | 20 +- .../examples/commands/GrabCommand.java | 20 +- .../examples/commands/JointsCommand.java | 2 +- .../examples/commands/MaterialsCommand.java | 5 +- .../examples/commands/PersistenceCommand.java | 2 +- .../commands/PhysicsChunkExampleCommand.java | 2 +- .../commands/PhysicsStoreExampleCommands.java | 35 +- .../examples/commands/RaycastCommand.java | 4 +- .../examples/commands/ShapesCommand.java | 5 +- .../stress/StressBenchmarkCommand.java | 4 +- .../commands/stress/StressBodiesCommand.java | 71 +- .../commands/stress/StressJointsCommand.java | 2 +- .../commands/stress/StressRaycastCommand.java | 4 +- .../commands/stress/StressShapesCommand.java | 5 +- .../explosive/ExplosiveBlockRuntime.java | 66 +- .../systems/ExplosiveFuseContactSystem.java | 2 +- .../systems/ExplosiveFuseTickSystem.java | 4 +- .../examples/utils/ExamplePhysicsUtils.java | 89 +- .../examples/commands/DropCommandTest.java | 25 - 154 files changed, 2434 insertions(+), 3886 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore/PhysicsStoreAsyncCompletions.java => physics/PhysicsAsyncCompletions.java} (85%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore/PhysicsStoreSpaceMutations.java => physics/PhysicsSpaceMutations.java} (72%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => physics}/PhysicsStoreRowCleanup.java (77%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => physics}/PhysicsStoreRuntimeCleaner.java (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore/PhysicsStoreTopologyMutations.java => physics/PhysicsTopologyMutations.java} (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physicsstore => physics}/resources/PhysicsDebugResource.java (94%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodySyncStateResource.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualInterestResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{ => binding}/BodyBindingSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{ => binding}/ColliderBindingSystem.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{ => binding}/JointBindingSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{ => binding}/SpaceBindingSystem.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{ => binding}/TargetBindingSystem.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{settings => events}/PhysicsEventCollectionMode.java (95%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsAsync.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsBackendAccess.java (92%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsBodies.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsBodyEntities.java (75%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsDiagnostics.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsEntities.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsEntityRefs.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsJointEntities.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsRaycasts.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsSpaces.java (79%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsThreading.java (88%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsWorlds.java (98%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsMutationHandle.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/IdentityFootprintProbeTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{physicsstore => physics}/PhysicsStoreTopologyMutationsTest.java (98%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{physicsstore => physics}/persistence/PersistentPhysicsStoreResourceTest.java (100%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{physicsstore => physics}/persistence/PhysicsStoreHolderPersistenceTest.java (99%) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResourceTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/{physicsstore => physics}/PhysicsSpacesSettingsComponentTest.java (99%) delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java diff --git a/gradle.properties b/gradle.properties index 4ba736f4..29ae4418 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=false org.gradle.caching=false # Common java_version=25 -hytale_version=0.6.0-pre.3 +hytale_version=0.6.0-pre.4 release_type=PRE_RELEASE # Mod options group=dev.hytalemodding diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java index ae2b5bc2..ff40d645 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java @@ -10,8 +10,7 @@ public record SpaceId(int value) { /* - * TODO: If this appears in profiles, replace it with a per-world allocator in - * PhysicsWorldResource to avoid a global static counter. + * PhysicsStore space helpers to avoid a global static counter. */ private static final AtomicInteger COUNTER = new AtomicInteger(0); diff --git a/impulse-core/README.md b/impulse-core/README.md index 0e3d9db9..fdc159d5 100644 --- a/impulse-core/README.md +++ b/impulse-core/README.md @@ -19,11 +19,10 @@ services from jars anywhere under the configured Hytale `mods` directories. ## Event frames `PhysicsWorlds.latestEventFrame(physicsStore)` exposes the latest value-only physics event frame -for diagnostics. The compatibility `PhysicsWorldResource.getLatestEventFrame()` facade returns the -same frame for callers still bound to the EntityStore resource. When collection is enabled, backends -emit bounded post-step `PhysicsBackendEvent` batches; core translates them to stable UUID-primary -`PhysicsFrameEvent` values and copied PhysicsStore refs where available, then publishes one -`PhysicsEventFramePublishedEvent` Hytale world event for the completed frame. +for diagnostics. When collection is enabled, backends emit bounded post-step `PhysicsBackendEvent` +batches; core translates them to stable UUID-primary `PhysicsFrameEvent` values and copied +PhysicsStore refs where available, then publishes one `PhysicsEventFramePublishedEvent` Hytale +world event for the completed frame. Backend event collection is opt-in through `PhysicsWorldSettings.setEventCollectionMode(...)`. Worlds default to `PhysicsEventCollectionMode.DISABLED`; use diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index a5a97efd..e07afa50 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -17,16 +17,16 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; @@ -131,9 +131,10 @@ private static void cleanAll(@Nonnull CommandContext context, }); } - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); CompletionStage reset = - resource.resetRuntimeStateKeepingSpacesAsync(world.getName()); + PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + "clear PhysicsStore body entities", + PhysicsTopologyMutations::clearBodiesKeepingSpaces); reset.whenComplete((result, failure) -> sendCleanAllResult(world, context, removedEntities, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 2e2fae16..c138f8fe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -17,14 +17,14 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.ArrayList; import java.util.Comparator; @@ -74,15 +74,16 @@ protected void execute(@Nonnull CommandContext context, return; } - PhysicsSpaceSettings settings = physicsChunkMode == PhysicsChunkCollisionMode.STREAMING - ? PhysicsSpaceSettings.streamingPhysicsChunk() - : PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkCollisionSettings().setMode(physicsChunkMode); - Store physicsStore = PhysicsThreading.store(world); try { Impulse.getRuntimeProvider(backendId); - SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId, settings); + SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId); + PhysicsChunkCollisionSettings chunkCollisionSettings = + new PhysicsChunkCollisionSettings(); + chunkCollisionSettings.setMode(physicsChunkMode); + PhysicsSpaces.putChunkCollisionSettings(physicsStore, + spaceId, + chunkCollisionSettings); context.sendMessage(Message.raw("Created physics space id=" + spaceId.value() + " backend=" + backendId.value() @@ -117,10 +118,11 @@ private static void sendSpaces(@Nonnull CommandContext context, @Nonnull List summaries) { List spaces = summaries.stream() .map(summary -> { - PhysicsSpaceSettings settings = PhysicsSpaces.settings(physicsStore, + PhysicsChunkCollisionSettings settings = PhysicsSpaces.chunkCollisionSettings( + physicsStore, summary.spaceId()); PhysicsChunkCollisionMode physicsChunkMode = settings != null - ? settings.getPhysicsChunkCollisionSettings().getMode() + ? settings.getMode() : PhysicsChunkCollisionMode.NONE; return new SpaceListEntry(summary.spaceId(), summary.backendId().value(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java index 63a2fd87..648c5a44 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java @@ -10,7 +10,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.Comparator; import java.util.Objects; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java index c4293b9f..fbf92570 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java @@ -4,8 +4,8 @@ import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java index a90cf87f..b95841a0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/EventCollectionSettingCommand.java @@ -11,9 +11,9 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java index 88ef205a..5df2b62c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java @@ -11,8 +11,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java index b83e6e3f..4c914364 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SimulationStepsSettingCommand.java @@ -11,8 +11,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 1bc4f8db..d44f87d7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -8,14 +8,13 @@ import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java index 0fb296c8..b988cebd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java @@ -11,10 +11,10 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java index f4d22f06..dab1390c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java @@ -11,8 +11,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index aa52936e..aaa4ef69 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; @@ -15,21 +14,23 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import java.util.UUID; import java.util.Collection; import java.util.List; @@ -119,7 +120,7 @@ private static CrucibleSuite runtimeStabilitySuite() { "Detached unregister did not remove the backend body"), CrucibleTestCase.async("settings round trip", ImpulseApiCrucibleTests::settingsRoundTrip, - "PhysicsSpaceSettings did not retain runtime settings"))); + "Composable space settings did not retain runtime settings"))); } private static CrucibleSuite transformSyncSuite() { @@ -186,10 +187,8 @@ private static CompletionStage spaceCountRoundTrip(@Nonnull CrucibleCon return callWhenPhysicsStoreIdle(context, "run Crucible space count round trip", world -> { Store store = physicsStore(world); int previousCount = PhysicsSpaces.count(store); - SpaceId spaceId = PhysicsSpaces.create(store, - CrucibleBackends.requireBackendId(), - PhysicsSpaceSettings.defaults()); - PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); + SpaceId spaceId = PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); + PhysicsSpaceMutations.removeEmptySpace(store, spaceId); return PhysicsSpaces.count(store) == previousCount && !PhysicsSpaces.hasSpace(store, spaceId); }); @@ -199,15 +198,17 @@ private static CompletionStage createdExplicitSpaceLifecycleWorks( @Nonnull CrucibleContext context) { return callWhenPhysicsStoreIdle(context, "run Crucible explicit space lifecycle", world -> { Store store = physicsStore(world); - SpaceId spaceId = PhysicsSpaces.create(store, - CrucibleBackends.requireBackendId(), - PhysicsSpaceSettings.streamingPhysicsChunk()); - PhysicsSpaceSettings spaceSettings = PhysicsSpaces.settings(store, spaceId); + SpaceId spaceId = PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); + PhysicsChunkCollisionSettings chunkCollisionSettings = + new PhysicsChunkCollisionSettings(); + chunkCollisionSettings.setMode(PhysicsChunkCollisionMode.STREAMING); + PhysicsSpaces.putChunkCollisionSettings(store, spaceId, chunkCollisionSettings); + PhysicsChunkCollisionSettings spaceSettings = + PhysicsSpaces.chunkCollisionSettings(store, spaceId); boolean registered = PhysicsSpaces.hasSpace(store, spaceId) && spaceSettings != null - && spaceSettings.getPhysicsChunkCollisionSettings().getMode() - == PhysicsChunkCollisionMode.STREAMING; - PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); + && spaceSettings.getMode() == PhysicsChunkCollisionMode.STREAMING; + PhysicsSpaceMutations.removeEmptySpace(store, spaceId); return registered && !PhysicsSpaces.hasSpace(store, spaceId); }); } @@ -238,7 +239,7 @@ private static CompletionStage populatedBodyCleanup( PhysicsBodies.bodyUuids(state.store()).isEmpty(); boolean removedSpace = true; if (checkSpaceRemoval || spaceEmpty) { - PhysicsStoreSpaceMutations.removeEmptySpace( + PhysicsSpaceMutations.removeEmptySpace( state.store(), state.spaceId()); removedSpace = !PhysicsSpaces.hasSpace(state.store(), state.spaceId()); @@ -255,8 +256,7 @@ private static CompletionStage createPopulatedBodyCle Ref spaceRef = PhysicsSpaces.create(store, UUID.randomUUID(), spaceId, - CrucibleBackends.requireBackendId(), - PhysicsSpaceSettings.defaults()); + CrucibleBackends.requireBackendId()); Ref bodyRef = addCrucibleBox(store, spaceRef, UUID.randomUUID()); return new PopulatedBodyCleanupState(world, store, spaceId, spaceRef, bodyRef); }); @@ -303,111 +303,148 @@ private static CompletionStage waitApproxTicksOnWorld(@Nonnull CrucibleCon private static Ref addCrucibleBox(@Nonnull Store store, @Nonnull Ref spaceRef, @Nonnull UUID bodyUuid) { - BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody(spaceRef, + return store.addEntity(PhysicsBodyEntities.dynamicBodyHolder(spaceRef, bodyUuid, new Vector3f(0.0f, 5.0f, 0.0f), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.defaults(), - null); - return store.addEntity(PhysicsEntities.bodyHolder(store, - descriptor.bodyUuid(), - descriptor.body(), - descriptor.dynamics(), - descriptor.target(), - descriptor.collider(), - descriptor.shape(), - descriptor.material(), - descriptor.filter()), AddReason.SPAWN); + null), AddReason.SPAWN); } private static CompletionStage settingsRoundTrip(@Nonnull CrucibleContext context) { - PhysicsSpaceSettings settings = populatedSettings(); + PopulatedSettings settings = populatedSettings(); return callWhenPhysicsStoreIdle(context, "run Crucible settings round trip", world -> { Store store = physicsStore(world); - SpaceId spaceId = PhysicsSpaces.create(store, - CrucibleBackends.requireBackendId(), - settings); + SpaceId spaceId = PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); try { - PhysicsSpaceSettings copy = PhysicsSpaces.settings(store, spaceId); - if (copy == null) { + applyPopulatedSettings(store, spaceId, settings); + PhysicsChunkCollisionSettings chunkCollision = + PhysicsSpaces.chunkCollisionSettings(store, spaceId); + PhysicsVisualSyncSettings visualSync = + PhysicsSpaces.visualSyncSettings(store, spaceId); + PhysicsSolverSettings solver = PhysicsSpaces.solverSettings(store, spaceId); + PhysicsExtensionSettings extension = PhysicsSpaces.extensionSettings(store, + spaceId); + PhysicsVisualMaterializationSettings visualMaterialization = + PhysicsSpaces.visualMaterializationSettings(store, spaceId); + if (chunkCollision == null + || visualSync == null + || solver == null + || extension == null + || visualMaterialization == null) { return false; } - return copy.getPhysicsChunkCollisionSettings().getMode() == PhysicsChunkCollisionMode.STREAMING - && copy.getPhysicsChunkCollisionSettings().getRadius() == 9 - && copy.getPhysicsChunkCollisionSettings().getBodyRadius() == 5 - && copy.getPhysicsChunkCollisionSettings().getTtlTicks() == 77 - && copy.getVisualSyncSettings().getVisualFullSyncRadius() == 48 - && copy.getVisualSyncSettings().getVisualMaxSyncRadius() == 96 - && !copy.getVisualSyncSettings().isVisualFarSyncCutoffEnabled() - && copy.getVisualSyncSettings().getVisualMidSyncIntervalTicks() == 3 - && copy.getVisualSyncSettings().getVisualFarSyncIntervalTicks() == 17 - && copy.getVisualSyncSettings().getVisualOcclusionMode() == VisualOcclusionMode.PRIORITY - && copy.getVisualSyncSettings().getVisualOcclusionRaycastsPerTick() == 31 - && copy.getVisualSyncSettings().getVisualOcclusionCacheTicks() == 7 - && copy.getSolverSettings().getSolverIterations() == 5 - && copy.getSolverSettings().getStabilizationIterations() == 1 - && copy.getExtensionSettings() + return chunkCollision.getMode() == PhysicsChunkCollisionMode.STREAMING + && chunkCollision.getRadius() == 9 + && chunkCollision.getBodyRadius() == 5 + && chunkCollision.getTtlTicks() == 77 + && visualSync.getVisualFullSyncRadius() == 48 + && visualSync.getVisualMaxSyncRadius() == 96 + && !visualSync.isVisualFarSyncCutoffEnabled() + && visualSync.getVisualMidSyncIntervalTicks() == 3 + && visualSync.getVisualFarSyncIntervalTicks() == 17 + && visualSync.getVisualOcclusionMode() == VisualOcclusionMode.PRIORITY + && visualSync.getVisualOcclusionRaycastsPerTick() == 31 + && visualSync.getVisualOcclusionCacheTicks() == 7 + && solver.getSolverIterations() == 5 + && solver.getStabilizationIterations() == 1 + && extension .getInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS) .orElse(-1) == 2 - && copy.getExtensionSettings() + && extension .getInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_MIN_ISLAND_SIZE) .orElse(-1) == 64 - && copy.getVisualSyncSettings().isEntityVisualSyncCullingEnabled() - && copy.getVisualSyncSettings().isVisualVisibilityCullingEnabled() - && copy.getVisualMaterializationSettings().isDetachedVisualMaterializationEnabled() - && copy.getVisualMaterializationSettings().getDetachedVisualMaterializationRadius() == 48 - && copy.getVisualMaterializationSettings().getDetachedVisualDematerializationRadius() == 72 - && copy.getVisualMaterializationSettings().getDetachedVisualMaxSpawnsPerTick() == 33 - && copy.getVisualMaterializationSettings().getDetachedVisualMaxMaterialized() == 444 - && "Rock_Stone".equals(copy.getVisualMaterializationSettings().getDetachedVisualBlockType()); + && visualSync.isEntityVisualSyncCullingEnabled() + && visualSync.isVisualVisibilityCullingEnabled() + && visualMaterialization.isDetachedVisualMaterializationEnabled() + && visualMaterialization.getDetachedVisualMaterializationRadius() == 48 + && visualMaterialization.getDetachedVisualDematerializationRadius() == 72 + && visualMaterialization.getDetachedVisualMaxSpawnsPerTick() == 33 + && visualMaterialization.getDetachedVisualMaxMaterialized() == 444 + && "Rock_Stone".equals(visualMaterialization.getDetachedVisualBlockType()); } finally { - PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceId); + PhysicsSpaceMutations.removeEmptySpace(store, spaceId); } }); } @Nonnull - private static PhysicsSpaceSettings populatedSettings() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); - settings.getPhysicsChunkCollisionSettings().setRadius(9); - settings.getPhysicsChunkCollisionSettings().setBodyRadius(5); - settings.getPhysicsChunkCollisionSettings().setTtlTicks(77); - settings.getVisualSyncSettings().setVisualMaxSyncRadius(96); - settings.getVisualSyncSettings().setVisualFullSyncRadius(48); - settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(false); - settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(3); - settings.getVisualSyncSettings().setVisualFarSyncIntervalTicks(17); - settings.getVisualSyncSettings().setVisualOcclusionMode(VisualOcclusionMode.PRIORITY); - settings.getVisualSyncSettings().setVisualOcclusionRaycastsPerTick(31); - settings.getVisualSyncSettings().setVisualOcclusionCacheTicks(7); - settings.getSolverSettings().setSolverIterations(5); - settings.getSolverSettings().setStabilizationIterations(1); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, + private static PopulatedSettings populatedSettings() { + PhysicsChunkCollisionSettings chunkCollision = new PhysicsChunkCollisionSettings(); + chunkCollision.setMode(PhysicsChunkCollisionMode.STREAMING); + chunkCollision.setRadius(9); + chunkCollision.setBodyRadius(5); + chunkCollision.setTtlTicks(77); + + PhysicsVisualSyncSettings visualSync = new PhysicsVisualSyncSettings(); + visualSync.setVisualMaxSyncRadius(96); + visualSync.setVisualFullSyncRadius(48); + visualSync.setVisualFarSyncCutoffEnabled(false); + visualSync.setVisualMidSyncIntervalTicks(3); + visualSync.setVisualFarSyncIntervalTicks(17); + visualSync.setVisualOcclusionMode(VisualOcclusionMode.PRIORITY); + visualSync.setVisualOcclusionRaycastsPerTick(31); + visualSync.setVisualOcclusionCacheTicks(7); + visualSync.setEntityVisualSyncCullingEnabled(true); + visualSync.setVisualVisibilityCullingEnabled(true); + + PhysicsSolverSettings solver = new PhysicsSolverSettings(); + solver.setSolverIterations(5); + solver.setStabilizationIterations(1); + + PhysicsExtensionSettings extension = new PhysicsExtensionSettings(); + extension.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS, 2); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, + extension.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_MIN_ISLAND_SIZE, 64); - settings.getVisualSyncSettings().setEntityVisualSyncCullingEnabled(true); - settings.getVisualSyncSettings().setVisualVisibilityCullingEnabled(true); - settings.getVisualMaterializationSettings().setDetachedVisualMaterializationEnabled(true); - settings.getVisualMaterializationSettings().setDetachedVisualDematerializationRadius(72); - settings.getVisualMaterializationSettings().setDetachedVisualMaterializationRadius(48); - settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(33); - settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(444); - settings.getVisualMaterializationSettings().setDetachedVisualBlockType("Rock_Stone"); - return settings; + + PhysicsVisualMaterializationSettings visualMaterialization = + new PhysicsVisualMaterializationSettings(); + visualMaterialization.setDetachedVisualMaterializationEnabled(true); + visualMaterialization.setDetachedVisualDematerializationRadius(72); + visualMaterialization.setDetachedVisualMaterializationRadius(48); + visualMaterialization.setDetachedVisualMaxSpawnsPerTick(33); + visualMaterialization.setDetachedVisualMaxMaterialized(444); + visualMaterialization.setDetachedVisualBlockType("Rock_Stone"); + return new PopulatedSettings(chunkCollision, + visualSync, + solver, + extension, + visualMaterialization); + } + + private static void applyPopulatedSettings(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull PopulatedSettings settings) { + PhysicsSpaces.putChunkCollisionSettings(store, + spaceId, + settings.chunkCollisionSettings()); + PhysicsSpaces.putVisualSyncSettings(store, spaceId, settings.visualSyncSettings()); + PhysicsSpaces.putSolverSettings(store, spaceId, settings.solverSettings()); + PhysicsSpaces.putExtensionSettings(store, spaceId, settings.extensionSettings()); + PhysicsSpaces.putVisualMaterializationSettings(store, + spaceId, + settings.visualMaterializationSettings()); } private static Store physicsStore(@Nonnull World world) { return PhysicsStoreCrucibleSupport.physicsStore(world); } + private record PopulatedSettings( + @Nonnull PhysicsChunkCollisionSettings chunkCollisionSettings, + @Nonnull PhysicsVisualSyncSettings visualSyncSettings, + @Nonnull PhysicsSolverSettings solverSettings, + @Nonnull PhysicsExtensionSettings extensionSettings, + @Nonnull PhysicsVisualMaterializationSettings visualMaterializationSettings) { + } + private record PopulatedBodyCleanupState(@Nonnull World world, @Nonnull Store store, @Nonnull SpaceId spaceId, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 4c3f8b98..9a83c2b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -22,17 +22,20 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -137,7 +140,6 @@ private static final class StageRunner { private final CrucibleContext context; private final StagePlan plan; private final World world; - private final PhysicsWorldRuntimeResource physics; private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; @@ -153,7 +155,6 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) this.plan = plan; this.world = context.world(); Store store = world.getEntityStore().getStore(); - this.physics = PhysicsWorldRuntimeResource.require(store); this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); this.physicsStoreProfiling = physicsStore.getResource( PhysicsProfilingResource.getResourceType()); @@ -162,7 +163,7 @@ private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) PhysicsChunkProfilingResource.getResourceType()); this.collisionStreaming = store.getResource( PhysicsChunkCollisionStreamingResource.getResourceType()); - this.previousWorldSettings = physics.getWorldSettings(); + this.previousWorldSettings = PhysicsWorlds.settings(physicsStore); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); } @@ -218,12 +219,12 @@ private CompletionStage startStageWhenReady(int count, int attempt "PhysicsChunk subplugin did not load")); } - PhysicsWorldSettings worldSettings = physics.getWorldSettings(); + PhysicsWorldSettings worldSettings = PhysicsWorlds.settings(physicsStore); worldSettings.setStepMode(PhysicsStepMode.PROGRESSIVE_REFINEMENT); worldSettings.setStepSchedulingMode(PhysicsStepSchedulingMode.DROP_PENDING_DT); worldSettings.setSimulationSteps(1); worldSettings.setMaxStepDt(TARGET_MAX_STEP_DT); - physics.setWorldSettings(worldSettings); + PhysicsWorlds.putSettings(physicsStore, worldSettings); BenchmarkChunks chunks = benchmarkChunks(count); if (!areChunksReady(chunks)) { @@ -242,21 +243,17 @@ private CompletionStage startStageWhenReady(int count, int attempt int retained = retainChunks(chunks); configureMissingSectionDiagnostics(chunks); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); - settings.getPhysicsChunkCollisionSettings().setBodyRadius(BODY_STREAMING_RADIUS); - settings.getSolverSettings().setSolverIterations(4); - settings.getSolverSettings().setStabilizationIterations(1); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_INTERNAL_PGS_ITERATIONS, - 1); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_MIN_ISLAND_SIZE, - 128); - - SpaceId spaceId = physics.createSpace(CrucibleBackends.requireBackendId(), - world.getName(), - settings); + SpaceId spaceId = PhysicsSpaces.create(physicsStore, + CrucibleBackends.requireBackendId()); + PhysicsSpaceMutations.putChunkCollisionSettings(physicsStore, + spaceId, + benchmarkChunkCollisionSettings()); + PhysicsSpaceMutations.putSolverSettings(physicsStore, + spaceId, + benchmarkSolverSettings()); + PhysicsSpaceMutations.putExtensionSettings(physicsStore, + spaceId, + benchmarkExtensionSettings()); PrewarmStats prewarm = prewarmPhysicsChunkCollision(spaceId, count); spawnDetachedBodies(spaceId, count); physicsStoreProfiling.reset(); @@ -276,7 +273,7 @@ private StageReport finishStage(int count, return StageReport.failedPreflight(count, started.failureMessage()); } SpaceId spaceId = started.spaceId(); - if (spaceId == null || !physics.hasSpace(spaceId)) { + if (spaceId == null || !PhysicsSpaces.hasSpace(physicsStore, spaceId)) { return StageReport.failedPreflight(count, "space disappeared during benchmark"); } @@ -361,17 +358,17 @@ private void clearStageState() { } private void restoreStepSettings() { - physics.setWorldSettings(previousWorldSettings); + PhysicsWorlds.putSettings(physicsStore, previousWorldSettings); physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); } private PrewarmStats prewarmPhysicsChunkCollision(@Nonnull SpaceId spaceId, int count) { BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(physicsStore, spaceId); + UUID spaceUuid = PhysicsSpaceMutations.requireSpaceUuid(physicsStore, spaceId); PhysicsChunkCollisionMutationQueueResource queue = physicsStore.getResource( PhysicsChunkCollisionMutationQueueResource.getResourceType()); PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( - physics.getSpaceSettings(spaceId).getPhysicsChunkCollisionSettings()); + benchmarkChunkCollisionSettings()); PhysicsChunkCollisionPrewarmStats stats = collisionStreaming.ensureAround(world, spaceUuid, queue, @@ -385,6 +382,30 @@ private PrewarmStats prewarmPhysicsChunkCollision(@Nonnull SpaceId spaceId, int stats.buildStats().colliderBodies()); } + @Nonnull + private static PhysicsChunkCollisionSettings benchmarkChunkCollisionSettings() { + PhysicsChunkCollisionSettings settings = new PhysicsChunkCollisionSettings(); + settings.setMode(PhysicsChunkCollisionMode.STREAMING); + settings.setBodyRadius(BODY_STREAMING_RADIUS); + return settings; + } + + @Nonnull + private static PhysicsSolverSettings benchmarkSolverSettings() { + PhysicsSolverSettings settings = new PhysicsSolverSettings(); + settings.setSolverIterations(4); + settings.setStabilizationIterations(1); + return settings; + } + + @Nonnull + private static PhysicsExtensionSettings benchmarkExtensionSettings() { + PhysicsExtensionSettings settings = new PhysicsExtensionSettings(); + settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS, 1); + settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_MIN_ISLAND_SIZE, 128); + return settings; + } + @Nonnull private static List prewarmCenters(@Nonnull BenchmarkLayout layout, int count) { List centers = new ArrayList<>(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index 15b888e5..d805fd11 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -12,20 +12,17 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.Comparator; @@ -79,7 +76,7 @@ private static CompletionStage entityBodyFallsThroughEcs(CrucibleContex context.wx(0), context.wy(20), context.wz(0)); - PhysicsStoreSpaceMutations.putSpaceGravity(physicsStore, + PhysicsSpaceMutations.putSpaceGravity(physicsStore, spaceId, new Vector3f(0.0f, -9.81f, 0.0f)); UUID bodyUuid = UUID.randomUUID(); @@ -129,9 +126,7 @@ private static SpaceId liveTestSpaceId(Store store, World world) { if (existingSpaceId != null) { return existingSpaceId; } - return PhysicsSpaces.create(store, - CrucibleBackends.requireBackendId(), - PhysicsSpaceSettings.defaults()); + return PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); } private static void submitLiveBody(Store store, @@ -143,8 +138,7 @@ private static void submitLiveBody(Store store, if (spaceRef == null) { throw new IllegalStateException("No PhysicsStore space ref for id=" + spaceId.value()); } - BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody( - spaceRef, + store.addEntity(PhysicsBodyEntities.dynamicBodyHolder(spaceRef, bodyUuid, new Vector3f((float) visualPosition.x, (float) visualPosition.y, @@ -152,16 +146,7 @@ private static void submitLiveBody(Store store, PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.defaults(), - null); - store.addEntity(PhysicsEntities.bodyHolder(store, - descriptor.bodyUuid(), - descriptor.body(), - descriptor.dynamics(), - descriptor.target(), - descriptor.collider(), - descriptor.shape(), - descriptor.material(), - descriptor.filter()), AddReason.SPAWN); + null), AddReason.SPAWN); } private static Store physicsStore(World world) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index b8d60c89..aff2f197 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -13,6 +13,7 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; @@ -20,15 +21,16 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualInterestResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.ArrayList; @@ -137,7 +139,6 @@ private static final class MatrixRunner { private final MatrixPlan plan; private final World world; private final Store store; - private final PhysicsWorldRuntimeResource physics; private final Store physicsStore; private final PhysicsProfilingResource physicsStoreProfiling; private final PhysicsRuntimeProfilingResource runtimeProfiling; @@ -153,14 +154,13 @@ private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) this.plan = plan; this.world = context.world(); this.store = world.getEntityStore().getStore(); - this.physics = PhysicsWorldRuntimeResource.require(store); this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); this.physicsStoreProfiling = physicsStore.getResource( PhysicsProfilingResource.getResourceType()); this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); this.collisionProfiling = store.getResource( PhysicsChunkProfilingResource.getResourceType()); - this.previousWorldSettings = physics.getWorldSettings(); + this.previousWorldSettings = PhysicsWorlds.settings(physicsStore); this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); this.previousRuntimeProfilingEnabled = runtimeProfiling.isEnabled(); this.previousTerrainProfilingEnabled = collisionProfiling.isEnabled(); @@ -217,29 +217,22 @@ private CompletionStage runCase(int index, private CompletionStage startCase(@Nonnull MatrixCase matrixCase) { clearCaseState(); - PhysicsWorldSettings worldSettings = physics.getWorldSettings(); + PhysicsWorldSettings worldSettings = PhysicsWorlds.settings(physicsStore); worldSettings.setStepMode(PhysicsStepMode.FIXED); worldSettings.setStepSchedulingMode(PhysicsStepSchedulingMode.DROP_PENDING_DT); worldSettings.setSimulationSteps(matrixCase.fixedSubsteps()); worldSettings.setMaxStepDt(TARGET_MAX_STEP_DT); - physics.setWorldSettings(worldSettings); - physics.clearSyntheticVisualInterests(); + PhysicsWorlds.putSettings(physicsStore, worldSettings); + visualInterests().clearSyntheticVisualInterests(); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.NONE); - settings.getSolverSettings().setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); - settings.getSolverSettings().setStabilizationIterations( - PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_INTERNAL_PGS_ITERATIONS, - 1); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_MIN_ISLAND_SIZE, - 128); try { - SpaceId spaceId = physics.createSpace(RAPIER_BACKEND_ID, - world.getName(), - settings); + SpaceId spaceId = PhysicsSpaces.create(physicsStore, RAPIER_BACKEND_ID); + PhysicsSpaceMutations.putSolverSettings(physicsStore, + spaceId, + benchmarkSolverSettings()); + PhysicsSpaceMutations.putExtensionSettings(physicsStore, + spaceId, + benchmarkExtensionSettings()); return populateBenchmarkSpace(spaceId, matrixCase); } catch (RuntimeException exception) { return CompletableFuture.completedFuture( @@ -247,6 +240,23 @@ private CompletionStage startCase(@Nonnull MatrixCase matrixCase) { } } + @Nonnull + private static PhysicsSolverSettings benchmarkSolverSettings() { + PhysicsSolverSettings settings = new PhysicsSolverSettings(); + settings.setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); + settings.setStabilizationIterations( + PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS); + return settings; + } + + @Nonnull + private static PhysicsExtensionSettings benchmarkExtensionSettings() { + PhysicsExtensionSettings settings = new PhysicsExtensionSettings(); + settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS, 1); + settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_MIN_ISLAND_SIZE, 128); + return settings; + } + private CompletionStage populateBenchmarkSpace(@Nonnull SpaceId spaceId, @Nonnull MatrixCase matrixCase) { BenchmarkLayout layout = BenchmarkLayout.flatGrid(matrixCase.count()); @@ -291,7 +301,7 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, return MatrixReport.failedPreflight(matrixCase, started.failureMessage()); } SpaceId spaceId = started.spaceId(); - if (spaceId == null || !physics.hasSpace(spaceId)) { + if (spaceId == null || !PhysicsSpaces.hasSpace(physicsStore, spaceId)) { return MatrixReport.failedPreflight(matrixCase, "space disappeared during benchmark"); } @@ -373,7 +383,7 @@ private CompletionStage waitForPhysicsStoreIdle() { private void clearCaseState() { removeBenchmarkEntities(); - physics.clearSyntheticVisualInterests(); + visualInterests().clearSyntheticVisualInterests(); PhysicsStoreCrucibleSupport.clearAll(physicsStore); physicsStoreProfiling.reset(); runtimeProfiling.reset(); @@ -382,12 +392,17 @@ private void clearCaseState() { } private void restoreSettings() { - physics.setWorldSettings(previousWorldSettings); + PhysicsWorlds.putSettings(physicsStore, previousWorldSettings); physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); runtimeProfiling.setEnabled(previousRuntimeProfilingEnabled); collisionProfiling.setEnabled(previousTerrainProfilingEnabled); } + @Nonnull + private PhysicsVisualInterestResource visualInterests() { + return store.getResource(PhysicsVisualInterestResource.getResourceType()); + } + private void removeBenchmarkEntities() { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java index dabf4bf9..aa765cd9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java @@ -9,9 +9,9 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; @@ -35,7 +35,7 @@ static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store @Nullable PhysicsChunkCollisionStreamingResource streaming, @Nonnull BenchmarkSpaceStatsRequest query) { PhysicsThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); - UUID spaceUuid = PhysicsStoreSpaceMutations.requireSpaceUuid(store, query.spaceId()); + UUID spaceUuid = PhysicsSpaceMutations.requireSpaceUuid(store, query.spaceId()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); BenchmarkSpaceStatsAccumulator stats = new BenchmarkSpaceStatsAccumulator(); BiConsumer, CommandBuffer> collector = diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index 25f199fb..d7545049 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -5,14 +5,12 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRuntimeCleaner; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -49,24 +47,14 @@ static Ref addBody(@Nonnull Store store, @Nullable Vector3f linearVelocity) { PhysicsThreading.requireWorldThread(store, "add Crucible PhysicsStore body entity"); Ref spaceRef = requireSpaceRef(store, spaceId); - BodyEntityDescriptor descriptor = PhysicsBodyEntities.body( - spaceRef, + return store.addEntity(PhysicsBodyEntities.bodyHolder(spaceRef, bodyUuid, bodyCenter, shape, bodyType, mass, settings, - linearVelocity); - return store.addEntity(PhysicsEntities.bodyHolder(store, - descriptor.bodyUuid(), - descriptor.body(), - descriptor.dynamics(), - descriptor.target(), - descriptor.collider(), - descriptor.shape(), - descriptor.material(), - descriptor.filter()), AddReason.SPAWN); + linearVelocity), AddReason.SPAWN); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java index 4b6cecf6..ff89f55e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java @@ -10,7 +10,6 @@ import dev.hytalemodding.impulse.core.internal.modules.SubPluginLifecycleGate; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import java.util.ArrayList; import java.util.Collections; @@ -32,8 +31,6 @@ public final class ControlLifecycle { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); private static final SubPluginLifecycleGate GATE = new SubPluginLifecycleGate( "Impulse control is disabled. Enable HytaleModding:ImpulseControl to start control sessions."); - private static final Set RESOURCES = - Collections.newSetFromMap(new WeakHashMap<>()); private static final Set> STORES = Collections.newSetFromMap(new WeakHashMap<>()); private static final long CLEANUP_TIMEOUT_SECONDS = 5L; @@ -41,7 +38,6 @@ public final class ControlLifecycle { static { GATE.onDisable(ControlLifecycle::cleanupStores); GATE.onDisable(PhysicsControlRuntimeStates::clearAll); - GATE.onDisable(ControlLifecycle::cleanupResources); } private ControlLifecycle() { @@ -67,12 +63,6 @@ public static void requireEnabled() { GATE.requireEnabled(); } - public static void registerResource(@Nonnull PhysicsWorldRuntimeResource resource) { - synchronized (RESOURCES) { - RESOURCES.add(resource); - } - } - public static void registerStore(@Nonnull Store store) { synchronized (STORES) { STORES.add(store); @@ -202,16 +192,6 @@ private static void cleanupStoreOnWorldThread(@Nonnull Store store, } } - private static void cleanupResources() { - ArrayList resources; - synchronized (RESOURCES) { - resources = new ArrayList<>(RESOURCES); - } - for (PhysicsWorldRuntimeResource resource : resources) { - resource.disableControlLifecycle(); - } - } - private record SessionCleanupTarget( @Nonnull Ref ref, @Nonnull PhysicsControlSessionComponent session) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java index fa7979c3..9aa81adc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.Collections; import java.util.Map; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index e407be5a..9ababfd4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 244db719..144b555d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -6,14 +6,14 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycle.java index e4fa3e10..ad3ea5fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkLifecycle.java @@ -1,12 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import dev.hytalemodding.impulse.core.internal.modules.SubPluginLifecycleGate; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Set; -import java.util.WeakHashMap; -import javax.annotation.Nonnull; /** * Server-level lifecycle controlled by the Impulse PhysicsChunk subplugin. @@ -15,12 +9,6 @@ public final class PhysicsChunkLifecycle { private static final SubPluginLifecycleGate GATE = new SubPluginLifecycleGate("Impulse PhysicsChunk subplugin is disabled"); - private static final Set RESOURCES = - Collections.newSetFromMap(new WeakHashMap<>()); - - static { - GATE.onDisable(PhysicsChunkLifecycle::cleanupResources); - } private PhysicsChunkLifecycle() { } @@ -40,20 +28,4 @@ public static boolean isEnabled() { public static long generation() { return GATE.generation(); } - - public static void registerResource(@Nonnull PhysicsWorldRuntimeResource resource) { - synchronized (RESOURCES) { - RESOURCES.add(resource); - } - } - - private static void cleanupResources() { - ArrayList resources; - synchronized (RESOURCES) { - resources = new ArrayList<>(RESOURCES); - } - for (PhysicsWorldRuntimeResource resource : resources) { - resource.disablePhysicsChunkLifecycle(); - } - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java index 5916fc42..7210248e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/CollisionLodSettingsCommand.java @@ -12,8 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java index 91924390..ccdf1167 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java @@ -15,10 +15,10 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.SyncSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsRuntimeProfiling.VisualSnapshotView; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionProfiling; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.Locale; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java index 0c91a488..63c184a8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSettingsCommand.java @@ -12,8 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java index fdcdcd07..a1ad0923 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkSpaceSelection.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import java.util.Comparator; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java index c53d2282..fccdaf87 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java @@ -30,7 +30,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java index b57d9067..eb26e676 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java @@ -2,15 +2,14 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.SystemGroup; import com.hypixel.hytale.component.event.WorldEventType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodySyncStateResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualInterestResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.systems.PhysicsWorldResourceAttachmentSystem; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; @@ -19,7 +18,6 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -35,8 +33,6 @@ public final class PhysicsEntityTypeRegistry { private static ComponentType generatedVisualProxyComponentType; @Nullable - private static ResourceType physicsWorldResourceType; - @Nullable private static WorldEventType physicsEventFramePublishedEventType; @Nullable @@ -57,8 +53,6 @@ public static void registerComponentTypes(@Nonnull ComponentRegistryProxy registry) { - physicsWorldResourceType = registry.registerResource(PhysicsWorldResource.class, - PhysicsWorldRuntimeResource::new); PhysicsDebugResource.setResourceType(registry.registerResource(PhysicsDebugResource.class, PhysicsDebugResource::new)); PhysicsRuntimeProfilingResource.setResourceType(registry.registerResource( @@ -67,6 +61,12 @@ public static void registerResourceTypes(@Nonnull ComponentRegistryProxy registry) { @@ -84,29 +84,30 @@ public static void registerSystems(@Nonnull ComponentRegistryProxy registry.registerSystem(new PhysicsSyncSystem()); registry.registerSystem(new PhysicsDebugSystem()); registry.registerSystem(new PhysicsStoreEventPublicationSystem()); - registry.registerSystem(new PhysicsWorldResourceAttachmentSystem()); } public static void clearEntityStoreTypes() { bodyAttachmentComponentType = null; generatedVisualProxyComponentType = null; - physicsWorldResourceType = null; physicsEventFramePublishedEventType = null; persistenceRestoreGroup = null; PhysicsDebugResource.clearResourceType(); PhysicsRuntimeProfilingResource.clearResourceType(); PhysicsProjectionIndexResource.clearResourceType(); + PhysicsBodySyncStateResource.clearResourceType(); + PhysicsVisualInterestResource.clearResourceType(); } public static boolean areEntityStoreTypesRegistered() { return bodyAttachmentComponentType != null && generatedVisualProxyComponentType != null - && physicsWorldResourceType != null && physicsEventFramePublishedEventType != null && persistenceRestoreGroup != null && PhysicsDebugResource.getResourceType() != null && PhysicsRuntimeProfilingResource.getResourceType() != null - && PhysicsProjectionIndexResource.getResourceType() != null; + && PhysicsProjectionIndexResource.getResourceType() != null + && PhysicsBodySyncStateResource.getResourceType() != null + && PhysicsVisualInterestResource.getResourceType() != null; } public static boolean isBodyAttachmentComponentTypeRegistered() { @@ -130,12 +131,6 @@ public static ComponentType bodyAttachment "Impulse GeneratedVisualProxy component type is not registered"); } - @Nonnull - public static ResourceType physicsWorldResourceType() { - return requireRegistered(physicsWorldResourceType, - "Impulse PhysicsWorld resource type is not registered"); - } - @Nonnull public static WorldEventType physicsEventFramePublishedEventType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java index 38ae90bb..fc18647c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java @@ -13,8 +13,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java index ab62343d..7d53c040 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java @@ -13,8 +13,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import java.util.Locale; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java index b6670631..21290413 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java @@ -93,7 +93,7 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, + " has invalid chunk collision restitution"); } try { - space.toSettings(); + validateSpaceComponents(space); } catch (RuntimeException exception) { errors.add("PhysicsStore space " + uuid + " has invalid space settings: " + exception.getMessage()); @@ -102,6 +102,17 @@ private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, return seen; } + private static void validateSpaceComponents(@Nonnull PersistentSpaceDto space) { + space.getChunkCollisionSettings(); + space.getChunkCollisionMaterial(); + space.getChunkCollisionFilter(); + space.getSolverSettings(); + space.getVisualSyncSettings(); + space.getVisualMaterializationSettings(); + space.getCollisionLodSettings(); + space.getExtensionSettings(); + } + @Nonnull private static Set collectBodies(@Nonnull PersistentBodyDto[] bodies, @Nonnull Set spaces, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java index 18a9c21e..f34008d0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java @@ -7,7 +7,6 @@ import java.nio.file.Path; import java.util.concurrent.CompletionException; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.bson.BsonDocument; /** @@ -23,7 +22,7 @@ private PersistentPhysicsStoreStorage() { @Nonnull public static LoadResult load(@Nonnull Store store) { - Path file = fileOrNull(store.getExternalData()); + Path file = file(store.getExternalData()); if (file == null) { return LoadResult.missing(); } @@ -49,13 +48,8 @@ public static LoadResult load(@Nonnull Store store) { @Nonnull static Path file(@Nonnull PhysicsStore physicsStore) { - return physicsStore.getWorld().getSavePath().resolve(RESOURCE_DIRECTORY).resolve(FILE_NAME); - } - - @Nullable - private static Path fileOrNull(@Nonnull PhysicsStore physicsStore) { Path savePath = physicsStore.getWorld().getSavePath(); - return savePath != null ? savePath.resolve(RESOURCE_DIRECTORY).resolve(FILE_NAME) : null; + return savePath.resolve(RESOURCE_DIRECTORY).resolve(FILE_NAME); } public record LoadResult(boolean present, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java index ac4cc392..609ab17f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java @@ -18,7 +18,6 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -428,18 +427,6 @@ public ExtensionSettingsComponent getExtensionSettings() { return extensionSettings.clone(); } - @Nonnull - public PhysicsSpaceSettings toSettings() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - getChunkCollisionSettings().copyTo(settings); - solverSettings.copyTo(settings); - visualSyncSettings.copyTo(settings); - visualMaterializationSettings.copyTo(settings); - collisionLodSettings.copyTo(settings); - extensionSettings.copyTo(settings); - return settings; - } - @Nonnull public PersistentSpaceDto copy() { return new PersistentSpaceDto(spaceUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreAsyncCompletions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsAsyncCompletions.java similarity index 85% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreAsyncCompletions.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsAsyncCompletions.java index 4ce6401f..cc5cfc7b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreAsyncCompletions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsAsyncCompletions.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; +package dev.hytalemodding.impulse.core.internal.physics; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -8,12 +8,12 @@ /** * Completes public PhysicsStore futures outside the store tick lane. */ -public final class PhysicsStoreAsyncCompletions { +public final class PhysicsAsyncCompletions { private static final ThreadFactory COMPLETION_THREADS = Thread.ofVirtual().name("Impulse PhysicsStore completion ", 1).factory(); - private PhysicsStoreAsyncCompletions() { + private PhysicsAsyncCompletions() { } public static void complete(@Nonnull CompletableFuture completion, T value) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java similarity index 72% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java index 35fe7c5e..c9afc798 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; +package dev.hytalemodding.impulse.core.internal.physics; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Component; @@ -27,23 +27,21 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.joml.Vector3f; /** * Direct PhysicsStore space entity mutations for store-lane callers. */ -public final class PhysicsStoreSpaceMutations { +public final class PhysicsSpaceMutations { - private PhysicsStoreSpaceMutations() { + private PhysicsSpaceMutations() { } @Nonnull @@ -51,29 +49,6 @@ public static Ref addSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull SpaceId compatibilitySpaceId, @Nonnull BackendId backendId) { - return addSpace0(store, spaceUuid, compatibilitySpaceId, backendId, null); - } - - @Nonnull - public static Ref addSpace(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull SpaceId compatibilitySpaceId, - @Nonnull BackendId backendId, - @Nonnull PhysicsSpaceSettings settings) { - return addSpace0(store, - spaceUuid, - compatibilitySpaceId, - backendId, - Objects.requireNonNull(settings, "settings")); - } - - @Nonnull - private static Ref addSpace0(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull SpaceId compatibilitySpaceId, - @Nonnull BackendId backendId, - @Nullable PhysicsSpaceSettings settings) { - PhysicsThreading.requireWorldThread(store, "add a PhysicsStore space entity"); if (backendId.value().isBlank()) { throw new IllegalArgumentException("PhysicsStore space backend id is blank: " @@ -95,9 +70,6 @@ private static Ref addSpace0(@Nonnull Store store, Holder holder = PhysicsEntities.spaceHolder(store, spaceUuid, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))); - if (settings != null) { - addSpaceSettingsComponents(holder, settings); - } Ref ref = store.addEntity(holder, AddReason.SPAWN); assert ref != null; identity.putUuid(spaceUuid, ref); @@ -108,14 +80,6 @@ private static Ref addSpace0(@Nonnull Store store, return ref; } - public static void putSpaceSettings(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { - UUID spaceUuid = requireSpaceUuid(store, spaceId); - Ref ref = requireSpaceRef(store, spaceUuid); - putSpaceSettings(store, ref, settings); - } - public static void putSpaceGravity(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull Vector3f gravity) { @@ -141,16 +105,6 @@ public static void putSpaceGravity(@Nonnull Store store, .markSpaceSettingsPending(ref); } - public static void putSpaceSettings(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull PhysicsSpaceSettings settings) { - requireSpaceUuid(store, ref); - PhysicsThreading.requireWorldThread(store, "update a PhysicsStore space entity"); - putSpaceSettingsComponents(store, ref, Objects.requireNonNull(settings, "settings")); - store.getResource(PhysicsRuntimeResource.getResourceType()) - .markSpaceSettingsPending(ref); - } - public static void putChunkCollisionSettings(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull PhysicsChunkCollisionSettings settings) { @@ -298,103 +252,6 @@ public static void putExtensionSettings(@Nonnull Store store, .markSpaceSettingsPending(ref); } - private static void addSpaceSettingsComponents(@Nonnull Holder holder, - @Nonnull PhysicsSpaceSettings settings) { - ChunkCollisionSettingsComponent chunkCollision = - new ChunkCollisionSettingsComponent(settings.getPhysicsChunkCollisionSettings()); - addIfNonDefault(holder, - ChunkCollisionSettingsComponent.getComponentType(), - chunkCollision, - chunkCollision.isDefault()); - SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); - addIfNonDefault(holder, - SolverSettingsComponent.getComponentType(), - solver, - solver.isDefault()); - VisualSyncSettingsComponent visualSync = - new VisualSyncSettingsComponent(settings.getVisualSyncSettings()); - addIfNonDefault(holder, - VisualSyncSettingsComponent.getComponentType(), - visualSync, - visualSync.isDefault()); - VisualMaterializationSettingsComponent visualMaterialization = - new VisualMaterializationSettingsComponent( - settings.getVisualMaterializationSettings()); - addIfNonDefault(holder, - VisualMaterializationSettingsComponent.getComponentType(), - visualMaterialization, - visualMaterialization.isDefault()); - CollisionLodSettingsComponent collisionLod = - new CollisionLodSettingsComponent(settings.getCollisionLodSettings()); - addIfNonDefault(holder, - CollisionLodSettingsComponent.getComponentType(), - collisionLod, - collisionLod.isDefault()); - ExtensionSettingsComponent extension = - new ExtensionSettingsComponent(settings.getExtensionSettings()); - addIfNonDefault(holder, - ExtensionSettingsComponent.getComponentType(), - extension, - extension.isDefault()); - } - - private static > void addIfNonDefault( - @Nonnull Holder holder, - @Nonnull ComponentType componentType, - @Nonnull T component, - boolean defaultValue) { - if (!defaultValue) { - holder.addComponent(componentType, component); - } - } - - private static void putSpaceSettingsComponents(@Nonnull Store store, - @Nonnull Ref ref, - @Nonnull PhysicsSpaceSettings settings) { - ChunkCollisionSettingsComponent chunkCollision = - new ChunkCollisionSettingsComponent(settings.getPhysicsChunkCollisionSettings()); - putOrRemoveDefault(store, - ref, - ChunkCollisionSettingsComponent.getComponentType(), - chunkCollision, - chunkCollision.isDefault()); - SolverSettingsComponent solver = new SolverSettingsComponent(settings.getSolverSettings()); - putOrRemoveDefault(store, - ref, - SolverSettingsComponent.getComponentType(), - solver, - solver.isDefault()); - VisualSyncSettingsComponent visualSync = - new VisualSyncSettingsComponent(settings.getVisualSyncSettings()); - putOrRemoveDefault(store, - ref, - VisualSyncSettingsComponent.getComponentType(), - visualSync, - visualSync.isDefault()); - VisualMaterializationSettingsComponent visualMaterialization = - new VisualMaterializationSettingsComponent( - settings.getVisualMaterializationSettings()); - putOrRemoveDefault(store, - ref, - VisualMaterializationSettingsComponent.getComponentType(), - visualMaterialization, - visualMaterialization.isDefault()); - CollisionLodSettingsComponent collisionLod = - new CollisionLodSettingsComponent(settings.getCollisionLodSettings()); - putOrRemoveDefault(store, - ref, - CollisionLodSettingsComponent.getComponentType(), - collisionLod, - collisionLod.isDefault()); - ExtensionSettingsComponent extension = - new ExtensionSettingsComponent(settings.getExtensionSettings()); - putOrRemoveDefault(store, - ref, - ExtensionSettingsComponent.getComponentType(), - extension, - extension.isDefault()); - } - private static > void putOrRemoveDefault( @Nonnull Store store, @Nonnull Ref ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java similarity index 77% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index 22c86326..34550b6b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; +package dev.hytalemodding.impulse.core.internal.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; @@ -15,6 +15,10 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -89,6 +93,37 @@ public static void removeBodyEntity(@Nonnull Store store, @Nonnull Ref bodyRef, @Nullable String payloadResourceKey) { clearBodyCopiedState(store, bodyUuid, bodyRef); + removeBodyEntityAfterCopiedStateCleared(store, bodyUuid, bodyRef, payloadResourceKey); + } + + public static void removeBodyEntities(@Nonnull Store store, + @Nonnull Collection removals) { + Objects.requireNonNull(removals, "removals"); + if (removals.isEmpty()) { + return; + } + List bodyUuids = new ArrayList<>(removals.size()); + for (BodyEntityRemoval removal : removals) { + Objects.requireNonNull(removal, "removal"); + bodyUuids.add(removal.bodyUuid()); + PhysicsControlRuntimeStates.clearControlled(removal.bodyRef()); + } + store.getResource(PhysicsSnapshotResource.getResourceType()).removeBodies(bodyUuids); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .removeBodies(bodyUuids); + for (BodyEntityRemoval removal : removals) { + removeBodyEntityAfterCopiedStateCleared(store, + removal.bodyUuid(), + removal.bodyRef(), + removal.payloadResourceKey()); + } + } + + private static void removeBodyEntityAfterCopiedStateCleared( + @Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nullable String payloadResourceKey) { store.getResource(PhysicsIdentityIndexResource.getResourceType()).removeUuid(bodyUuid, bodyRef); removePayload(store, payloadResourceKey); @@ -137,4 +172,15 @@ private static void removeEntityIfValid(@Nonnull Store store, } store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); } + + public record BodyEntityRemoval( + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nullable String payloadResourceKey) { + + public BodyEntityRemoval { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + Objects.requireNonNull(bodyRef, "bodyRef"); + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java index 6a6272ff..4581819e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; +package dev.hytalemodding.impulse.core.internal.physics; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java index d3829a4e..5862b99f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; +package dev.hytalemodding.impulse.core.internal.physics; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; @@ -30,9 +30,9 @@ /** * World-thread topology mutations for public compatibility cleanup paths. */ -public final class PhysicsStoreTopologyMutations { +public final class PhysicsTopologyMutations { - private PhysicsStoreTopologyMutations() { + private PhysicsTopologyMutations() { } public static void destroyBody(@Nonnull Store store, @@ -83,7 +83,7 @@ public static void removeSpaceWithContents(@Nonnull Store store, store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()) .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); removeRows(store, removals); - PhysicsStoreSpaceMutations.removeEmptySpace(store, spaceUuid); + PhysicsSpaceMutations.removeEmptySpace(store, spaceUuid); } public static int clearChunkCollisionRowsForSpace(@Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java index 73d0ff50..62ca06c9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physicsstore/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore.resources; +package dev.hytalemodding.impulse.core.internal.physics.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index cd41f164..2c8e0104 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -22,19 +22,19 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.TickDecision; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; -import dev.hytalemodding.impulse.core.internal.systems.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.ColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.ColliderBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.CompletedStepPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; -import dev.hytalemodding.impulse.core.internal.systems.JointBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.JointBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreQueuedReadSystem; -import dev.hytalemodding.impulse.core.internal.systems.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; -import dev.hytalemodding.impulse.core.internal.systems.TargetBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import java.lang.reflect.InvocationTargetException; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java index 7470863f..80e9f490 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java @@ -7,6 +7,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -112,17 +113,34 @@ public void publish(long registrationTopologyGeneration, public void removeBody(@Nonnull UUID bodyUuid) { Objects.requireNonNull(bodyUuid, "bodyUuid"); + removeBodies(List.of(bodyUuid)); + } + + public void removeBodies(@Nonnull Collection bodyUuids) { + Objects.requireNonNull(bodyUuids, "bodyUuids"); PublishedRegistrations current = registrations; - if (!current.spaceIdsByUuid().containsKey(bodyUuid)) { + if (bodyUuids.isEmpty()) { + return; + } + ObjectOpenHashSet removedBodyUuids = new ObjectOpenHashSet<>(bodyUuids.size()); + for (UUID bodyUuid : bodyUuids) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + if (current.spaceIdsByUuid().containsKey(bodyUuid)) { + removedBodyUuids.add(bodyUuid); + } + } + if (removedBodyUuids.isEmpty()) { return; } Object2ObjectLinkedOpenHashMap spaceIdsByUuid = new Object2ObjectLinkedOpenHashMap<>(current.spaceIdsByUuid()); - spaceIdsByUuid.remove(bodyUuid); + for (UUID bodyUuid : removedBodyUuids) { + spaceIdsByUuid.remove(bodyUuid); + } Int2ObjectOpenHashMap registrationsByRowIndex = new Int2ObjectOpenHashMap<>(current.registrationsByRowIndex()); registrationsByRowIndex.int2ObjectEntrySet() - .removeIf(entry -> entry.getValue().bodyUuid().equals(bodyUuid)); + .removeIf(entry -> removedBodyUuids.contains(entry.getValue().bodyUuid())); registrations = new PublishedRegistrations(current.registrationTopologyGeneration(), new ArrayList<>(spaceIdsByUuid.keySet()), spaceIdsByUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodySyncStateResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodySyncStateResource.java new file mode 100644 index 00000000..7fac0e3a --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodySyncStateResource.java @@ -0,0 +1,65 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime-only EntityStore sync state for body-to-entity transform projection. + */ +public final class PhysicsBodySyncStateResource implements Resource { + + @Nullable + private static ResourceType resourceType; + + private final Map, BodySyncState> bodySyncStates = + new Reference2ObjectOpenHashMap<>(); + + @Nonnull + public synchronized BodySyncState getOrCreate(@Nonnull Ref entityRef) { + return bodySyncStates.computeIfAbsent(entityRef, _ -> new BodySyncState()); + } + + @Nullable + public synchronized BodySyncState get(@Nonnull Ref entityRef) { + return bodySyncStates.get(entityRef); + } + + public synchronized void clear(@Nonnull Ref entityRef) { + bodySyncStates.remove(entityRef); + } + + public synchronized void clear() { + bodySyncStates.clear(); + } + + @Nonnull + @Override + public PhysicsBodySyncStateResource clone() { + PhysicsBodySyncStateResource copy = new PhysicsBodySyncStateResource(); + synchronized (this) { + copy.bodySyncStates.putAll(bodySyncStates); + } + return copy; + } + + @Nullable + public static ResourceType getResourceType() { + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; + } + + public static void clearResourceType() { + resourceType = null; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index dce51190..f0854eff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Set; import java.util.UUID; @@ -15,10 +14,9 @@ /** * Runtime-only debug overlay state for one world EntityStore. * - *

        This resource intentionally keeps transient debug session state out of - * {@link PhysicsWorldResource}. Physics world state is persisted and shared - * by gameplay systems, while debug subscriptions, cadence, and packet budgets - * are temporary operational concerns.

        + *

        This resource intentionally keeps transient debug session state separate from + * PhysicsStore authority. Physics world state is persisted and shared by gameplay systems, + * while debug subscriptions, cadence, and packet budgets are temporary operational concerns.

        */ @Getter public class PhysicsDebugResource implements Resource { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 898903bb..d3443150 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -4,6 +4,8 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; +import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; + import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -40,7 +42,7 @@ public final class PhysicsResourceTypes { private static ResourceType profilingResourceType; @Nullable private static ResourceType debugResourceType; + PhysicsDebugResource> debugResourceType; private PhysicsResourceTypes() { } @@ -84,8 +86,8 @@ public static void registerResourceTypes( PhysicsProfilingResource.class, PhysicsProfilingResource::new); debugResourceType = registry.registerResource( - dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource.class, - dev.hytalemodding.impulse.core.internal.physicsstore.resources.PhysicsDebugResource::new); + PhysicsDebugResource.class, + PhysicsDebugResource::new); } @Nonnull @@ -152,7 +154,7 @@ public static ResourceType profilingReso @Nonnull public static ResourceType debugResourceType() { + PhysicsDebugResource> debugResourceType() { return debugResourceType; } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java index c65897bc..4100567f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java @@ -8,7 +8,9 @@ import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; @@ -66,11 +68,27 @@ public void publish(@Nonnull PhysicsSnapshotFrame frame) { } public void removeBody(@Nonnull UUID bodyUuid) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + removeBodies(List.of(bodyUuid)); + } + + public void removeBodies(@Nonnull Collection bodyUuids) { + Objects.requireNonNull(bodyUuids, "bodyUuids"); PublishedSnapshot current = snapshot; - if (!current.bodiesByUuid().containsKey(bodyUuid)) { + if (bodyUuids.isEmpty()) { + return; + } + ObjectOpenHashSet removedBodyUuids = new ObjectOpenHashSet<>(bodyUuids.size()); + for (UUID bodyUuid : bodyUuids) { + Objects.requireNonNull(bodyUuid, "bodyUuid"); + if (current.bodiesByUuid().containsKey(bodyUuid)) { + removedBodyUuids.add(bodyUuid); + } + } + if (removedBodyUuids.isEmpty()) { return; } - snapshot = withoutBody(current, bodyUuid); + snapshot = withoutBodies(current, removedBodyUuids); } public void clear() { @@ -78,15 +96,15 @@ public void clear() { } @Nonnull - private static PublishedSnapshot withoutBody(@Nonnull PublishedSnapshot current, - @Nonnull UUID bodyUuid) { - int bodyCount = Math.max(0, current.frame().bodies().size() - 1); + private static PublishedSnapshot withoutBodies(@Nonnull PublishedSnapshot current, + @Nonnull ObjectOpenHashSet bodyUuids) { + int bodyCount = Math.max(0, current.frame().bodies().size() - bodyUuids.size()); List bodies = new ArrayList<>(bodyCount); Map bodiesByUuid = new Object2ObjectOpenHashMap<>(bodyCount); Int2ObjectOpenHashMap bodiesByRowIndex = new Int2ObjectOpenHashMap<>(bodyCount); for (PhysicsBodySnapshot body : current.frame().bodies()) { - if (bodyUuid.equals(body.bodyUuid())) { + if (bodyUuids.contains(body.bodyUuid())) { continue; } bodies.add(body); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java index a7ebbecc..df42c8b8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java @@ -4,12 +4,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -22,7 +17,7 @@ import org.joml.Vector3f; /** - * Id-only space topology and per-space settings for one physics world. + * Id-only space topology for one direct physics world runtime. */ public final class PhysicsSpaceRuntime { @@ -30,17 +25,10 @@ public final class PhysicsSpaceRuntime { private final Int2ObjectMap spaces = new Int2ObjectOpenHashMap<>(); - /** - * Per-space settings (PhysicsChunk collision mode, radius, TTL, etc.). Keyed by space id value. - */ - private final Int2ObjectMap spaceSettings = - new Int2ObjectOpenHashMap<>(); - @Nonnull public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId, @Nonnull SpaceId spaceId, @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings, @Nonnull PhysicsStepMode stepMode) { if (spaces.containsKey(spaceId.value())) { throw new IllegalArgumentException("Physics space id=" + spaceId + " is already registered"); @@ -48,10 +36,9 @@ public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId SpaceId.reserveAtLeast(spaceId.value()); LOGGER.at(Level.FINE).log( - "World %s creating physics space using backend %s collision=%s", + "World %s creating physics space using backend %s", worldName, - backendId, - settings.getPhysicsChunkCollisionSettings().getMode()); + backendId); PhysicsBackendRuntime runtime = Impulse.createRuntime(backendId); BackendSpaceHandle backendSpaceHandle = new BackendSpaceHandle(runtime.createSpace(spaceId)); @@ -59,20 +46,17 @@ public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId new PhysicsSpaceBinding(backendId, spaceId, backendSpaceHandle, runtime); try { validateSpaceCompatibleWithStepMode(binding, stepMode); - applySolverTuning(binding, settings); } catch (RuntimeException exception) { closeBindingSilently(binding, worldName, "discarding failed physics space"); throw exception; } spaces.put(spaceId.value(), binding); - spaceSettings.put(spaceId.value(), new PhysicsSpaceSettings(settings)); LOGGER.at(Level.FINE).log( - "World %s created physics space id=%s backend=%s collision=%s", + "World %s created physics space id=%s backend=%s", worldName, spaceId, - backendId, - settings.getPhysicsChunkCollisionSettings().getMode()); + backendId); return binding; } @@ -110,9 +94,7 @@ public synchronized List getSpaceIds() { @Nullable public synchronized PhysicsSpaceBinding removeSpace(@Nonnull SpaceId spaceId) { - PhysicsSpaceBinding removed = spaces.remove(spaceId.value()); - spaceSettings.remove(spaceId.value()); - return removed; + return spaces.remove(spaceId.value()); } @Nonnull @@ -125,10 +107,6 @@ public synchronized PhysicsRuntimeResetResult resetKeepingSpaces( PhysicsSpaceBinding replacement = null; Vector3f gravity = new Vector3f(); try { - PhysicsSpaceSettings settings = spaceSettings.get(previous.spaceId().value()); - if (settings == null) { - throw new IllegalStateException("Physics space settings are missing for id=" + previous.spaceId()); - } previous.runtime().getGravity(previous.backendSpaceHandle().value(), gravity::set); PhysicsBackendRuntime runtime = Impulse.createRuntime(previous.backendId()); BackendSpaceHandle backendSpaceHandle = @@ -139,7 +117,6 @@ public synchronized PhysicsRuntimeResetResult resetKeepingSpaces( runtime); validateSpaceCompatibleWithStepMode(replacement, stepMode); replacement.runtime().setGravity(backendSpaceHandle.value(), gravity.x, gravity.y, gravity.z); - applySolverTuning(replacement, settings); replacements.add(replacement); } catch (RuntimeException exception) { if (replacement != null) { @@ -174,31 +151,6 @@ private static void closeBindingsSilently(@Nonnull Iterable } } - @Nonnull - public synchronized PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { - return new PhysicsSpaceSettings(getLiveSpaceSettings(spaceId)); - } - - @Nonnull - public synchronized PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { - PhysicsSpaceSettings settings = spaceSettings.get(spaceId.value()); - if (settings == null) { - throw new IllegalStateException("Physics space settings are missing for id=" + spaceId); - } - return settings; - } - - public synchronized void setSpaceSettings(@Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { - PhysicsSpaceBinding binding = spaces.get(spaceId.value()); - if (binding == null) { - throw new IllegalArgumentException("Physics space id=" + spaceId - + " is not registered"); - } - applySolverTuning(binding, settings); - spaceSettings.put(spaceId.value(), new PhysicsSpaceSettings(settings)); - } - public synchronized void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { if (stepMode != PhysicsStepMode.CCD) { return; @@ -221,7 +173,6 @@ public synchronized void clearLiveTopology(@Nonnull String worldName) { closeBindingSilently(binding, worldName, "discarded copied physics space"); } spaces.clear(); - spaceSettings.clear(); } private static void validateSpaceCompatibleWithStepMode(@Nonnull PhysicsSpaceBinding binding, @@ -237,28 +188,6 @@ private static String formatSpace(@Nonnull PhysicsSpaceBinding binding) { return "space " + binding.spaceId().value() + " (" + binding.backendId().value() + ")"; } - private static void applySolverTuning(@Nonnull PhysicsSpaceBinding binding, - @Nonnull PhysicsSpaceSettings settings) { - if (binding.runtime().supportsSolverTuning(binding.backendSpaceHandle().value())) { - binding.runtime().applySolverTuning(binding.backendSpaceHandle().value(), - new PhysicsSolverTuning( - settings.getSolverSettings().getSolverIterations(), - settings.getSolverSettings().getStabilizationIterations())); - } - if (binding.runtime().supportsActivationTuning(binding.backendSpaceHandle().value())) { - binding.runtime().applyActivationTuning(binding.backendSpaceHandle().value(), - new PhysicsActivationTuning( - settings.getSolverSettings().getDynamicSleepLinearThreshold(), - settings.getSolverSettings().getDynamicSleepAngularThreshold(), - settings.getSolverSettings().getDynamicSleepTimeUntilSleep())); - } - for (PhysicsBackendExtensionId extensionId : settings.getExtensionSettings().asMap().keySet()) { - binding.runtime().applyExtensionSettings(binding.backendSpaceHandle().value(), - new PhysicsCapabilityId(extensionId.value()), - consumer -> settings.getExtensionSettings().asStringMap(extensionId).forEach(consumer)); - } - } - private static boolean supportsContinuousCollision(@Nonnull PhysicsSpaceBinding binding) { return binding.runtime().supportsContinuousCollision(binding.backendSpaceHandle().value()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreReadQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreReadQueueResource.java index 209f5966..7a90090d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreReadQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreReadQueueResource.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsAsyncCompletions; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -93,14 +93,14 @@ private QueuedRead(@Nonnull Function, R> read, public void complete(@Nonnull Store store) { try { - PhysicsStoreAsyncCompletions.complete(completion, read.apply(store)); + PhysicsAsyncCompletions.complete(completion, read.apply(store)); } catch (RuntimeException | Error exception) { fail(exception); } } public void fail(@Nonnull Throwable failure) { - PhysicsStoreAsyncCompletions.fail(completion, Objects.requireNonNull(failure, "failure")); + PhysicsAsyncCompletions.fail(completion, Objects.requireNonNull(failure, "failure")); } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualInterestResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualInterestResource.java new file mode 100644 index 00000000..f6a40047 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualInterestResource.java @@ -0,0 +1,173 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Runtime-only EntityStore visual-interest state for generated physics visuals. + */ +public final class PhysicsVisualInterestResource implements Resource { + + @Nullable + private static ResourceType resourceType; + + private final List syntheticVisualInterests = new ArrayList<>(); + private final Map bodyVisualInterestStates = + new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap bodyVisualInterestStatesByRowIndex = + new Int2ObjectOpenHashMap<>(); + private final AtomicLong visualInterestTick = new AtomicLong(); + + public synchronized void setSyntheticVisualInterests( + @Nonnull Collection interests) { + syntheticVisualInterests.clear(); + syntheticVisualInterests.addAll(interests); + } + + @Nonnull + public synchronized List getSyntheticVisualInterests() { + return new ArrayList<>(syntheticVisualInterests); + } + + public synchronized void clearSyntheticVisualInterests() { + syntheticVisualInterests.clear(); + } + + @Nonnull + public synchronized BodyVisualInterestState getOrCreateBodyVisualInterestState( + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + BodyVisualInterestState state; + if (isValidRef(bodyRef)) { + state = getOrCreateBodyVisualInterestState(bodyRef); + } else { + state = bodyVisualInterestStates.computeIfAbsent(bodyUuid, + _ -> new BodyVisualInterestState()); + } + state.advanceVisualInterestTick(visualInterestTick.get()); + return state; + } + + @Nullable + public synchronized BodyVisualInterestState getBodyVisualInterestState( + @Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + BodyVisualInterestState state; + if (isValidRef(bodyRef)) { + int rowIndex = bodyRef.getIndex(); + BodyVisualInterestRefState row = bodyVisualInterestStatesByRowIndex.get(rowIndex); + if (row == null) { + return null; + } + if (!isMatchingLiveRef(row, bodyRef)) { + bodyVisualInterestStatesByRowIndex.remove(rowIndex); + return null; + } + state = row.state(); + } else { + state = bodyVisualInterestStates.get(bodyUuid); + } + if (state != null) { + state.advanceVisualInterestTick(visualInterestTick.get()); + } + return state; + } + + public synchronized void clearBodyVisualInterestState(@Nonnull UUID bodyUuid, + @Nullable Ref bodyRef) { + bodyVisualInterestStates.remove(bodyUuid); + if (!isValidRef(bodyRef)) { + return; + } + int rowIndex = bodyRef.getIndex(); + BodyVisualInterestRefState row = bodyVisualInterestStatesByRowIndex.get(rowIndex); + if (row != null && (!row.bodyRef().isValid() || sameRef(row.bodyRef(), bodyRef))) { + bodyVisualInterestStatesByRowIndex.remove(rowIndex); + } + } + + public long advanceVisualInterestTick() { + return visualInterestTick.incrementAndGet(); + } + + @Nonnull + @Override + public PhysicsVisualInterestResource clone() { + PhysicsVisualInterestResource copy = new PhysicsVisualInterestResource(); + synchronized (this) { + copy.syntheticVisualInterests.addAll(syntheticVisualInterests); + copy.bodyVisualInterestStates.putAll(bodyVisualInterestStates); + for (var entry : bodyVisualInterestStatesByRowIndex.int2ObjectEntrySet()) { + BodyVisualInterestRefState row = entry.getValue(); + copy.bodyVisualInterestStatesByRowIndex.put(entry.getIntKey(), + new BodyVisualInterestRefState(row.bodyRef(), row.state())); + } + copy.visualInterestTick.set(visualInterestTick.get()); + } + return copy; + } + + @Nullable + public static ResourceType getResourceType() { + return resourceType; + } + + public static void setResourceType( + @Nonnull ResourceType type) { + resourceType = type; + } + + public static void clearResourceType() { + resourceType = null; + } + + @Nonnull + private BodyVisualInterestState getOrCreateBodyVisualInterestState( + @Nonnull Ref bodyRef) { + int rowIndex = bodyRef.getIndex(); + BodyVisualInterestRefState row = bodyVisualInterestStatesByRowIndex.get(rowIndex); + if (row == null || !isMatchingLiveRef(row, bodyRef)) { + row = new BodyVisualInterestRefState(bodyRef, new BodyVisualInterestState()); + bodyVisualInterestStatesByRowIndex.put(rowIndex, row); + } + return row.state(); + } + + private static boolean isMatchingLiveRef(@Nonnull BodyVisualInterestRefState row, + @Nonnull Ref bodyRef) { + return row.bodyRef().isValid() && sameRef(row.bodyRef(), bodyRef); + } + + private static boolean isValidRef(@Nullable Ref ref) { + return ref != null && ref.isValid(); + } + + private static boolean sameRef(@Nullable Ref first, + @Nullable Ref second) { + return first == second + || (first != null + && second != null + && first.getStore() != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex()); + } + + private record BodyVisualInterestRefState(@Nonnull Ref bodyRef, + @Nonnull BodyVisualInterestState state) { + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java deleted file mode 100644 index b7f99a10..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResource.java +++ /dev/null @@ -1,1630 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRuntimeCleaner; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntime; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotRefVisitor; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.VisualInterest; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; -import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsMutationHandle; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Internal ECS resource implementation behind {@link PhysicsWorldResource}. - */ -public class PhysicsWorldRuntimeResource extends PhysicsWorldResource { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - - private final PhysicsSpaceRuntime spaceRuntime = new PhysicsSpaceRuntime(); - - private final PhysicsBodyRegistry bodyRegistry = new PhysicsBodyRegistry(); - - @Nonnull - private final PhysicsSimulationRuntime simulationRuntime = new PhysicsSimulationRuntime(); - - private final PhysicsBodyRuntimeState runtimeState = new PhysicsBodyRuntimeState(); - private final PhysicsControlRuntimeState controlRuntime = new PhysicsControlRuntimeState(); - private final PhysicsJointRegistry jointRegistry = new PhysicsJointRegistry(); - private final PhysicsVisualRuntime visualRuntime = new PhysicsVisualRuntime(this::clearBodySyncState); - private final PhysicsWorldLifecycleState lifecycleState = new PhysicsWorldLifecycleState(); - private final PhysicsBodyRuntime bodyRuntime = new PhysicsBodyRuntime(spaceRuntime, - bodyRegistry, - runtimeState, - controlRuntime, - jointRegistry, - visualRuntime, - lifecycleState, - this::markWorldChanged); - - private final AtomicLong visualInterestTick = new AtomicLong(); - @Nullable - private Store owningStore; - - public PhysicsWorldRuntimeResource() { - ControlLifecycle.registerResource(this); - PhysicsChunkLifecycle.registerResource(this); - } - - @Nonnull - public static PhysicsWorldRuntimeResource require(@Nonnull Store store) { - PhysicsWorldRuntimeResource resource = - require(store.getResource(PhysicsWorldResource.getResourceType())); - resource.attachEntityStore(store); - return resource; - } - - @Nonnull - public static PhysicsWorldRuntimeResource require(@Nonnull PhysicsWorldResource resource) { - if (resource instanceof PhysicsWorldRuntimeResource runtime) { - return runtime; - } - throw new IllegalStateException( - "Physics world resource is not the Impulse runtime implementation"); - } - - public void attachEntityStore(@Nonnull Store store) { - owningStore = Objects.requireNonNull(store, "store"); - } - - public void detachEntityStore(@Nonnull Store store) { - if (owningStore == store) { - owningStore = null; - } - } - - @Nonnull - public PhysicsEventFrame getLatestEventFrame() { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read latest physics event frame") - .getResource(PhysicsEventResource.getResourceType()) - .getLatestFrame(); - } - return lifecycleState.latestEventFrame(); - } - - private void assertCanAccessLiveBackendDirectly(@Nonnull String operation) { - Objects.requireNonNull(operation, "operation"); - } - - private void requireLegacyMutationAllowed(@Nonnull String operation) { - if (!isAuthoritativePhysicsStoreActive()) { - return; - } - throw new IllegalStateException("Legacy PhysicsWorldResource mutation is disabled while " - + "authoritative PhysicsStore is active: " + operation - + ". Route this operation through PhysicsStore entities or a PhysicsStore-backed " - + "compatibility bridge."); - } - - private boolean isAuthoritativePhysicsStoreActive() { - return PhysicsStoreEarlyPluginProbe.isAvailable(); - } - - private boolean hasAttachedAuthoritativePhysicsStore() { - return isAuthoritativePhysicsStoreActive() && owningStore != null; - } - - @Nonnull - private World requireAuthoritativeWorld(@Nonnull String operation) { - Store entityStore = owningStore; - if (entityStore == null) { - throw new IllegalStateException("Cannot " + operation - + " through authoritative PhysicsStore before this resource is attached to an " - + "EntityStore"); - } - return entityStore.getExternalData().getWorld(); - } - - @Nonnull - private Store authoritativePhysicsStore(@Nonnull String operation) { - Store store = physicsStore(requireAuthoritativeWorld(operation)); - PhysicsThreading.requireWorldThread(store, operation); - return store; - } - - @Nonnull - private static Store physicsStore(@Nonnull World world) { - return PhysicsThreading.store(world); - } - - private static boolean sameRef(@Nullable Ref first, - @Nullable Ref second) { - return first == second - || (first != null - && second != null - && first.getStore() != null - && first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); - } - - @Nonnull - private PhysicsProjectionIndexResource authoritativeProjectionIndex(@Nonnull String operation) { - Store entityStore = owningStore; - if (entityStore == null) { - throw new IllegalStateException("Cannot " + operation - + " through authoritative PhysicsStore projection before this resource is attached " - + "to an EntityStore"); - } - return entityStore.getResource(PhysicsProjectionIndexResource.getResourceType()); - } - - @Nonnull - private static UUID requireSpaceUuid(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - return PhysicsStoreSpaceMutations.requireSpaceUuid(store, spaceId); - } - - @Nullable - private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( - @Nonnull Store store, - @Nonnull SpaceId spaceId) { - UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); - if (spaceUuid == null) { - return null; - } - Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - if (ref == null || !ref.isValid()) { - return null; - } - return getPhysicsStoreSpaceSettings(store, ref); - } - - @Nullable - private static PhysicsSpaceSettings getPhysicsStoreSpaceSettings( - @Nonnull Store store, - @Nonnull Ref ref) { - Objects.requireNonNull(store, "store"); - Objects.requireNonNull(ref, "ref"); - if (ref.getStore() != store || !ref.isValid()) { - return null; - } - SpaceComponent space = store.getComponent(ref, SpaceComponent.getComponentType()); - if (space == null) { - return null; - } - ChunkCollisionSettingsComponent chunkCollisionSettings = store.getComponent(ref, - ChunkCollisionSettingsComponent.getComponentType()); - SolverSettingsComponent solverSettings = store.getComponent(ref, - SolverSettingsComponent.getComponentType()); - VisualSyncSettingsComponent visualSyncSettings = store.getComponent(ref, - VisualSyncSettingsComponent.getComponentType()); - VisualMaterializationSettingsComponent visualMaterializationSettings = - store.getComponent(ref, VisualMaterializationSettingsComponent.getComponentType()); - CollisionLodSettingsComponent collisionLodSettings = store.getComponent(ref, - CollisionLodSettingsComponent.getComponentType()); - ExtensionSettingsComponent extensionSettings = store.getComponent(ref, - ExtensionSettingsComponent.getComponentType()); - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - if (chunkCollisionSettings != null) { - chunkCollisionSettings.copyTo(settings); - } - if (solverSettings != null) { - solverSettings.copyTo(settings); - } - if (visualSyncSettings != null) { - visualSyncSettings.copyTo(settings); - } - if (visualMaterializationSettings != null) { - visualMaterializationSettings.copyTo(settings); - } - if (collisionLodSettings != null) { - collisionLodSettings.copyTo(settings); - } - if (extensionSettings != null) { - extensionSettings.copyTo(settings); - } - return settings; - } - - private static void validateAuthoritativeStepModeSupported( - @Nonnull Store store, - @Nonnull PhysicsStepMode stepMode) { - if (stepMode != PhysicsStepMode.CCD) { - return; - } - List unsupportedSpaces = new ArrayList<>(); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - runtime.forEachRuntimeSpaceBinding((spaceRef, backendId, spaceHandle, backendRuntime) -> { - if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { - UUID spaceUuid = runtime.getSpaceUuid(spaceRef); - unsupportedSpaces.add((spaceUuid != null ? spaceUuid : spaceRef) - + " backend=" + backendId.value()); - } - }); - if (!unsupportedSpaces.isEmpty()) { - throw new IllegalArgumentException("CCD step mode is not supported by PhysicsStore " - + "spaces: " + unsupportedSpaces); - } - } - - @Nonnull - private PhysicsMutationHandle enqueueAuthoritativePhysicsStoreMutation( - @Nonnull String operation, - @Nullable T value, - @Nonnull Consumer> mutation) { - World world = requireAuthoritativeWorld(operation); - return PhysicsMutationHandle.fromCompletion(operation, - value, - PhysicsThreading.callWhenBackendIdleOnWorldThread(world, - operation, - store -> { - mutation.accept(store); - return null; - })); - } - - private void runDirectRuntimeMutation(@Nonnull String operation, - @Nonnull DirectRuntimeMutation mutation) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(mutation, "mutation"); - try { - mutation.run(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("Physics operation " + operation + " failed", - exception); - } - } - - @Nonnull - private PhysicsMutationHandle enqueueDirectRuntimeMutation(@Nonnull String operation, - @Nonnull DirectRuntimeMutation mutation) { - return enqueueDirectRuntimeMutation(operation, null, mutation); - } - - - @Nonnull - private PhysicsMutationHandle enqueueDirectRuntimeMutation(@Nonnull String operation, - @Nullable T value, - @Nonnull DirectRuntimeMutation mutation) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(mutation, "mutation"); - try { - mutation.run(); - return PhysicsMutationHandle.completed(operation, value); - } catch (Throwable throwable) { - return PhysicsMutationHandle.failed(operation, value, throwable); - } - } - - - @Nonnull - private T callDirectRuntime(@Nonnull String operation, - @Nonnull DirectRuntimeCallable callable) { - Objects.requireNonNull(operation, "operation"); - Objects.requireNonNull(callable, "callable"); - try { - return callable.call(); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("Physics operation " + operation + " failed", - exception); - } - } - - - @Nonnull - public PhysicsWorldSettings getWorldSettings() { - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("read physics world settings") - .getResource(PhysicsWorldSettingsResource.getResourceType()) - .getSettings(); - } - return simulationRuntime.getWorldSettings(); - } - - public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { - PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); - if (hasAttachedAuthoritativePhysicsStore()) { - setAuthoritativeWorldSettings( - authoritativePhysicsStore("set physics world settings"), - requested); - return; - } - if (isAuthoritativePhysicsStoreActive()) { - setWorldSettingsDirect(requested); - return; - } - requireLegacyMutationAllowed("set physics world settings"); - runDirectRuntimeMutation("set physics world settings", () -> setWorldSettingsDirect(requested)); - } - - @Nonnull - public PhysicsMutationHandle setWorldSettingsAsync( - @Nonnull PhysicsWorldSettings settings) { - PhysicsWorldSettings requested = new PhysicsWorldSettings(settings); - if (hasAttachedAuthoritativePhysicsStore()) { - return enqueueAuthoritativePhysicsStoreMutation("set physics world settings", - null, - store -> setAuthoritativeWorldSettings(store, requested)); - } - if (isAuthoritativePhysicsStoreActive()) { - setWorldSettingsDirect(requested); - return PhysicsMutationHandle.completed("set physics world settings", null); - } - requireLegacyMutationAllowed("set physics world settings"); - return enqueueDirectRuntimeMutation("set physics world settings", - () -> setWorldSettingsDirect(requested)); - } - - private void setWorldSettingsDirect(@Nonnull PhysicsWorldSettings settings) { - validateStepModeSupported(settings.getStepMode()); - simulationRuntime.setWorldSettings(settings); - } - - private void setAuthoritativeWorldSettings(@Nonnull Store store, - @Nonnull PhysicsWorldSettings settings) { - PhysicsThreading.requireBackendIdle(store, "set physics world settings"); - validateAuthoritativeStepModeSupported(store, settings.getStepMode()); - store.getResource(PhysicsWorldSettingsResource.getResourceType()).setSettings(settings); - simulationRuntime.setWorldSettings(settings); - } - - @Nonnull - public SpaceId createSpace(@Nonnull BackendId backendId) { - return createSpace(backendId, "", PhysicsSpaceSettings.defaults()); - } - - @Nonnull - public SpaceId createSpace(@Nonnull BackendId backendId, @Nonnull String worldName) { - return createSpace(backendId, worldName, PhysicsSpaceSettings.defaults()); - } - - @Nonnull - public SpaceId createSpace(@Nonnull BackendId backendId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings) { - return createSpace(backendId, SpaceId.next(), worldName, settings); - } - - @Nonnull - public SpaceId createSpace(@Nonnull BackendId backendId, - @Nonnull SpaceId spaceId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings) { - if (isAuthoritativePhysicsStoreActive()) { - Impulse.getRuntimeProvider(backendId); - PhysicsStoreSpaceMutations.addSpace(authoritativePhysicsStore("create physics space"), - UUID.randomUUID(), - spaceId, - backendId, - settings); - return spaceId; - } - requireLegacyMutationAllowed("create physics space"); - callDirectRuntime("create physics space", - () -> createSpaceDirect(backendId, spaceId, worldName, settings)); - return spaceId; - } - - @Nonnull - public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backendId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings) { - SpaceId spaceId = SpaceId.next(); - return createSpaceAsync(backendId, spaceId, worldName, settings); - } - - @Nonnull - public PhysicsMutationHandle createSpaceAsync(@Nonnull BackendId backendId, - @Nonnull SpaceId spaceId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings) { - if (isAuthoritativePhysicsStoreActive()) { - Impulse.getRuntimeProvider(backendId); - PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); - return enqueueAuthoritativePhysicsStoreMutation("create physics space", - spaceId, - store -> PhysicsStoreSpaceMutations.addSpace(store, - UUID.randomUUID(), - spaceId, - backendId, - requested)); - } - requireLegacyMutationAllowed("create physics space"); - return enqueueDirectRuntimeMutation("create physics space", - spaceId, - () -> createSpaceDirect(backendId, spaceId, worldName, settings)); - } - - @Nonnull - private PhysicsSpaceBinding createSpaceDirect(@Nonnull BackendId backendId, - @Nonnull SpaceId spaceId, - @Nonnull String worldName, - @Nonnull PhysicsSpaceSettings settings) { - PhysicsSpaceBinding binding = spaceRuntime.createSpace(backendId, - spaceId, - worldName, - settings, - simulationRuntime.getWorldSettings().getStepMode()); - markWorldChanged(); - return binding; - } - - @Nullable - private PhysicsSpaceBinding getSpaceBinding(@Nonnull SpaceId spaceId) { - return spaceRuntime.getBinding(spaceId); - } - - public boolean hasSpace(@Nonnull SpaceId spaceId) { - if (isAuthoritativePhysicsStoreActive()) { - return authoritativePhysicsStore("check physics space") - .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .hasSpace(spaceId); - } - return spaceRuntime.getBinding(spaceId) != null; - } - - @Nonnull - private PhysicsSpaceBinding requireSpaceBinding(@Nonnull SpaceId spaceId) { - return spaceRuntime.requireBinding(spaceId); - } - - @Nonnull - public Collection getSpaceIds() { - if (isAuthoritativePhysicsStoreActive()) { - return List.copyOf(authoritativePhysicsStore("list physics spaces") - .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .spaceIds()); - } - return spaceRuntime.getSpaceIds(); - } - - public int getSpaceCount() { - if (isAuthoritativePhysicsStoreActive()) { - return authoritativePhysicsStore("count physics spaces") - .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .size(); - } - return spaceRuntime.getSpaceCount(); - } - - public int refreshBodySnapshots() { - if (isAuthoritativePhysicsStoreActive()) { - return authoritativePhysicsStore("refresh copied physics body snapshots") - .getResource(PhysicsSnapshotResource.getResourceType()) - .getLatestFrame() - .bodies() - .size(); - } - return callDirectRuntime("refresh physics body snapshots", () -> { - PublishedPhysicsSnapshotFrame frame = capturePublishedSnapshotFrameDirect(0L, - 0L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - return applyPublishedSnapshotFrame(frame); - }); - } - - @Nonnull - public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - if (isAuthoritativePhysicsStoreActive()) { - Store store = - authoritativePhysicsStore("read copied physics body snapshot"); - dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = getAuthoritativeBodySnapshot(store, bodyUuid); - if (snapshot == null) { - throw new IllegalStateException("No copied PhysicsStore body snapshot is available for " - + bodyUuid); - } - return snapshot; - } - dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); - if (snapshot != null) { - return snapshot; - } - return callDirectRuntime("refresh missing physics body snapshot", - () -> getBodySnapshotDirect(bodyUuid)); - } - - @Nullable - public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid) { - return getBodySnapshotIfRegistered(bodyUuid, null); - } - - @Nullable - public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - if (isAuthoritativePhysicsStoreActive()) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Store store = - authoritativePhysicsStore("read optional copied physics body snapshot"); - PhysicsBodySnapshot snapshot = bodyRef != null && bodyRef.isValid() - ? store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyRef) - : store.getResource(PhysicsSnapshotResource.getResourceType()).getBody(bodyUuid); - return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; - } - dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); - if (snapshot != null) { - return snapshot; - } - return callDirectRuntime("refresh optional physics body snapshot", - () -> getBodySnapshotIfRegisteredDirect(bodyUuid)); - } - - @Nullable - public dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegistered(@Nonnull Ref bodyRef) { - Store store = Objects.requireNonNull(bodyRef, "bodyRef").getStore(); - PhysicsThreading.requireWorldThread(store, "read optional copied physics body snapshot"); - PhysicsBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(bodyRef); - return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; - } - - public boolean hasPublishedBodyRegistration(@Nonnull UUID bodyUuid) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - if (hasAttachedAuthoritativePhysicsStore()) { - return authoritativePhysicsStore("check copied physics body registration") - .getResource(PhysicsBodyRegistrationResource.getResourceType()) - .hasBody(bodyUuid); - } - return bodyRegistry.hasPublishedRegistration(bodyUuid); - } - - @Nonnull - private dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotDirect(@Nonnull UUID bodyUuid) { - dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); - if (snapshot != null) { - return snapshot; - } - PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyUuid); - if (registration == null) { - throw new IllegalArgumentException("Physics body uuid=" + bodyUuid + " is not registered"); - } - return captureLiveBodySnapshot(registration); - } - - @Nullable - private dev.hytalemodding.impulse.api.PhysicsBodySnapshot getBodySnapshotIfRegisteredDirect(@Nonnull UUID bodyUuid) { - dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = lifecycleState.getBodySnapshot(bodyUuid); - if (snapshot != null) { - return snapshot; - } - PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyUuid); - return registration != null ? captureLiveBodySnapshot(registration) : null; - } - - @Nullable - private static dev.hytalemodding.impulse.api.PhysicsBodySnapshot getAuthoritativeBodySnapshot( - @Nonnull Store store, - @Nonnull UUID bodyUuid) { - PhysicsBodySnapshot snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); - return snapshot != null ? toPublicBodySnapshot(store, snapshot) : null; - } - - private static int countAuthoritativeBodySnapshots(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - UUID spaceUuid = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); - if (spaceUuid == null) { - return 0; - } - int count = 0; - for (PhysicsBodySnapshot body : store.getResource(PhysicsSnapshotResource.getResourceType()) - .getLatestFrame() - .bodies()) { - if (spaceUuid.equals(body.spaceUuid())) { - count++; - } - } - return count; - } - - private static void forEachAuthoritativeBodySnapshot(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); - if (spaceUuid == null) { - return; - } - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); - for (PhysicsBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { - if (!spaceUuid.equals(body.spaceUuid())) { - continue; - } - PhysicsBodySnapshotEntry entry = - authoritativeSnapshotEntry(store, registrations, body); - if (entry != null) { - consumer.accept(entry); - } - } - } - - private static void forEachIndexedAuthoritativeBodySnapshot( - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); - if (spaceUuid == null) { - return; - } - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); - for (PhysicsBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { - if (!spaceUuid.equals(body.spaceUuid())) { - continue; - } - PhysicsBodySnapshotEntry entry = - authoritativeSnapshotEntry(store, registrations, body); - if (entry != null) { - visitor.accept(entry.bodyUuid(), - entry.snapshot(), - entry.spaceId()); - } - } - } - - private static int forEachAuthoritativeBodySnapshotNear( - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull Consumer consumer) { - UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); - if (spaceUuid == null || radius < 0.0f || Float.isNaN(radius)) { - return 0; - } - float radiusSquared = radius * radius; - int candidates = 0; - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); - for (PhysicsBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { - if (!spaceUuid.equals(body.spaceUuid())) { - continue; - } - PhysicsBodySnapshotEntry entry = - authoritativeSnapshotEntry(store, registrations, body); - if (entry == null) { - continue; - } - candidates++; - if (withinRadius(entry.snapshot(), center, radiusSquared)) { - consumer.accept(entry); - } - } - return candidates; - } - - private static int forEachIndexedAuthoritativeBodySnapshotNear( - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); - if (spaceUuid == null || radius < 0.0f || Float.isNaN(radius)) { - return 0; - } - float radiusSquared = radius * radius; - int candidates = 0; - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); - for (PhysicsBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { - if (!spaceUuid.equals(body.spaceUuid())) { - continue; - } - PhysicsBodySnapshotEntry entry = - authoritativeSnapshotEntry(store, registrations, body); - if (entry == null) { - continue; - } - candidates++; - if (withinRadius(entry.snapshot(), center, radiusSquared)) { - visitor.accept(entry.bodyUuid(), - entry.snapshot(), - entry.spaceId()); - } - } - return candidates; - } - - private static int forEachIndexedAuthoritativeBodySnapshotNearWithRefs( - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotRefVisitor visitor) { - UUID spaceUuid = authoritativeSpaceUuid(store, spaceId); - if (spaceUuid == null || radius < 0.0f || Float.isNaN(radius)) { - return 0; - } - float radiusSquared = radius * radius; - int candidates = 0; - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); - for (PhysicsBodySnapshot body : authoritativeSnapshotFrame(store).bodies()) { - if (!spaceUuid.equals(body.spaceUuid())) { - continue; - } - PhysicsBodySnapshotEntry entry = - authoritativeSnapshotEntry(store, registrations, body); - if (entry == null) { - continue; - } - candidates++; - if (withinRadius(entry.snapshot(), center, radiusSquared)) { - visitor.accept(entry.bodyUuid(), - validSnapshotBodyRef(store, body), - entry.snapshot(), - entry.spaceId()); - } - } - return candidates; - } - - @Nullable - private static UUID authoritativeSpaceUuid(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) - .getSpaceUuid(Objects.requireNonNull(spaceId, "spaceId")); - } - - @Nonnull - private static PhysicsSnapshotFrame authoritativeSnapshotFrame( - @Nonnull Store store) { - return store.getResource(PhysicsSnapshotResource.getResourceType()).getLatestFrame(); - } - - @Nullable - private static PhysicsBodySnapshotEntry authoritativeSnapshotEntry( - @Nonnull Store store, - @Nonnull PhysicsBodyRegistrationResource registrations, - @Nonnull PhysicsBodySnapshot body) { - SpaceId spaceId = registrations.getBodySpaceId(body.bodyUuid()); - if (spaceId == null) { - return null; - } - return new PhysicsBodySnapshotEntry(body.bodyUuid(), - toPublicBodySnapshot(store, body), - spaceId); - } - - @Nullable - private static Ref validSnapshotBodyRef(@Nonnull Store store, - @Nonnull PhysicsBodySnapshot body) { - Ref bodyRef = body.bodyRef(); - return bodyRef != null && bodyRef.getStore() == store && bodyRef.isValid() - ? bodyRef - : null; - } - - private static boolean withinRadius(@Nonnull dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot, - @Nonnull Vector3f center, - float radiusSquared) { - Objects.requireNonNull(center, "center"); - float dx = snapshot.positionX() - center.x; - float dy = snapshot.positionY() - center.y; - float dz = snapshot.positionZ() - center.z; - return dx * dx + dy * dy + dz * dz <= radiusSquared; - } - - @Nonnull - private static dev.hytalemodding.impulse.api.PhysicsBodySnapshot toPublicBodySnapshot(@Nonnull Store store, - @Nonnull PhysicsBodySnapshot body) { - Ref ref = body.bodyRef(); - if (ref == null || ref.getStore() != store || !ref.isValid()) { - ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(body.bodyUuid()); - } - boolean validRef = ref != null && ref.isValid(); - DynamicsComponent dynamics = validRef - ? store.getComponent(ref, DynamicsComponent.getComponentType()) - : null; - ColliderComponent collider = validRef - ? store.getComponent(ref, ColliderComponent.getComponentType()) - : null; - MaterialComponent material = validRef - ? store.getComponent(ref, MaterialComponent.getComponentType()) - : null; - CollisionFilterComponent filter = validRef - ? store.getComponent(ref, CollisionFilterComponent.getComponentType()) - : null; - ShapeComponent shape = validRef - ? store.getComponent(ref, ShapeComponent.getComponentType()) - : null; - - Vector3f position = body.position(); - Quaternionf rotation = body.rotation(); - Vector3f linearVelocity = body.linearVelocity(); - Vector3f angularVelocity = body.angularVelocity(); - PhysicsBodyType bodyType = body.bodyType(); - ShapeType shapeType = shape != null ? shape.getShapeType() : ShapeType.UNKNOWN; - boolean hasBoxHalfExtents = shapeType == ShapeType.BOX && shape != null; - - return dev.hytalemodding.impulse.api.PhysicsBodySnapshot.of(position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z, - bodyType, - body.sleeping(), - collider != null && collider.isSensor(), - bodyType == PhysicsBodyType.DYNAMIC ? authoredMass(dynamics) : 0.0f, - material != null ? material.getFriction() : 0.5f, - material != null ? material.getRestitution() : 0.0f, - dynamics != null ? dynamics.getLinearDamping() : 0.0f, - dynamics != null ? dynamics.getAngularDamping() : 0.0f, - filter != null ? filter.getCollisionGroup() : PhysicsCollisionFilters.DYNAMIC_BODY, - filter != null ? filter.getCollisionMask() : PhysicsCollisionFilters.ALL, - dynamics != null && dynamics.isContinuousCollisionEnabled(), - body.centerOfMassOffsetY(), - shapeType, - hasBoxHalfExtents, - hasBoxHalfExtents ? shape.getHalfExtentX() : 0.0f, - hasBoxHalfExtents ? shape.getHalfExtentY() : 0.0f, - hasBoxHalfExtents ? shape.getHalfExtentZ() : 0.0f, - shape != null ? shape.getRadius() : 0.0f, - shape != null ? shape.getHalfHeight() : 0.0f, - shape != null ? shape.getAxis() : PhysicsAxis.Y); - } - - private static float authoredMass(@Nullable DynamicsComponent dynamics) { - return dynamics != null ? dynamics.getMass() : 1.0f; - } - - @Nonnull - private dev.hytalemodding.impulse.api.PhysicsBodySnapshot captureLiveBodySnapshot(@Nonnull PhysicsBodyRegistration registration) { - Objects.requireNonNull(registration, "registration"); - assertCanAccessLiveBackendDirectly("capture live physics body snapshot"); - PhysicsSpaceBinding space = requireSpaceBinding(registration.spaceId()); - dev.hytalemodding.impulse.api.PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, - registration.backendBodyHandle().value()); - if (snapshot == null) { - throw new IllegalStateException( - "No live physics body snapshot is available for " + registration.bodyUuid()); - } - return snapshot; - } - - /** - * Captures an immutable snapshot frame on the store tick lane. - * - *

        The generated {@code frameEpoch} and current {@code worldEpoch} govern - * publication ordering and stale-frame rejection. {@code stepSequence} and - * {@code serverTick} are copied through as external correlation metadata.

        - */ - @Nonnull - private PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrameDirect(long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled) { - return capturePublishedSnapshotFrameDirect(stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled, - List.of(), - 0); - } - - @Nonnull - private PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrameDirect(long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled, - @Nonnull List physicsEvents, - int droppedBackendEventCount) { - - assertCanAccessLiveBackendDirectly("capture published physics snapshot frame"); - return lifecycleState.capturePublishedSnapshotFrame(spaceRuntime.getBindings(), - bodyRegistry, - stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled, - physicsEvents, - droppedBackendEventCount); - } - - private int applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { - return lifecycleState.applyPublishedSnapshotFrame(frame, bodyRegistry, 0L); - } - - public int getBodySnapshotCount() { - if (isAuthoritativePhysicsStoreActive()) { - return authoritativePhysicsStore("count copied physics body snapshots") - .getResource(PhysicsSnapshotResource.getResourceType()) - .getLatestFrame() - .bodies() - .size(); - } - return lifecycleState.bodySnapshotCount(); - } - - public int getBodySnapshotCount(@Nonnull SpaceId spaceId) { - if (isAuthoritativePhysicsStoreActive()) { - return countAuthoritativeBodySnapshots( - authoritativePhysicsStore("count copied physics body snapshots"), - spaceId); - } - return lifecycleState.bodySnapshotCount(spaceId); - } - - public int getBodySnapshotCellCount() { - if (isAuthoritativePhysicsStoreActive()) { - return 0; - } - return lifecycleState.bodySnapshotCellCount(); - } - - @Nonnull - private PhysicsChunkCollisionStreamingResource authoritativePhysicsChunkCollisionStreaming() { - Store entityStore = owningStore; - if (entityStore == null) { - throw new IllegalStateException("Cannot access PhysicsStore PhysicsChunk collision streaming " - + "before this resource is attached to an EntityStore"); - } - return entityStore.getResource(PhysicsChunkCollisionStreamingResource.getResourceType()); - } - - private void clearAuthoritativePhysicsChunkCollisionStreaming(@Nonnull Store store) { - if (!PhysicsChunkLifecycle.isEnabled() || owningStore == null) { - return; - } - PhysicsChunkCollisionMutationQueueResource queue = - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()); - authoritativePhysicsChunkCollisionStreaming().retainSpaces(Set.of(), queue); - queue.clear(); - } - - private int clearAuthoritativePhysicsChunkCollisionSpace(@Nonnull Store store, - @Nonnull UUID spaceUuid) { - int removed = 0; - if (PhysicsChunkLifecycle.isEnabled() && owningStore != null) { - PhysicsChunkCollisionMutationQueueResource queue = - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()); - removed = authoritativePhysicsChunkCollisionStreaming().clearSpace(spaceUuid, queue); - } - int directlyRemoved = - PhysicsStoreTopologyMutations.clearChunkCollisionRowsForSpace(store, spaceUuid); - return removed != 0 ? removed : directlyRemoved; - } - - public void disablePhysicsChunkLifecycle() { - if (isAuthoritativePhysicsStoreActive()) { - return; - } - try { - runDirectRuntimeMutation("disable PhysicsChunk lifecycle", this::disablePhysicsChunkLifecycleDirect); - } catch (RejectedExecutionException ignored) { - // The server can unload the subplugin after the store tick lane has already closed. - } catch (RuntimeException exception) { - LOGGER.at(Level.WARNING).log("Failed to disable PhysicsChunk lifecycle: %s", - exception.getMessage()); - } - } - - private void disablePhysicsChunkLifecycleDirect() { - restoreCollisionLodFiltersDirect(); - } - - private void restoreCollisionLodFiltersDirect() { - int fullDynamicMask = PhysicsCollisionFilters.TERRAIN - | PhysicsCollisionFilters.DYNAMIC_BODY; - Store physicsStore = hasAttachedAuthoritativePhysicsStore() - ? authoritativePhysicsStore("restore collision LOD filters") - : null; - for (PhysicsBodyRegistration registration : bodyRegistry.getRegistrations()) { - if (physicsStore != null - && PhysicsChunkCollision.isChunkCollisionBody(physicsStore, - registration.bodyUuid())) { - continue; - } - PhysicsSpaceBinding space = getSpaceBinding(registration.spaceId()); - if (space == null) { - continue; - } - space.runtime().setBodyCollisionFilter(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value(), - PhysicsCollisionFilters.DYNAMIC_BODY, - fullDynamicMask); - space.runtime().activateBody(space.backendSpaceHandle().value(), - registration.backendBodyHandle().value()); - } - } - - public void forEachBodySnapshot(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - if (isAuthoritativePhysicsStoreActive()) { - forEachAuthoritativeBodySnapshot( - authoritativePhysicsStore("iterate copied physics body snapshots"), - spaceId, - consumer); - return; - } - lifecycleState.forEachBodySnapshot(spaceId, consumer); - } - - public void forEachIndexedBodySnapshot(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - if (isAuthoritativePhysicsStoreActive()) { - forEachIndexedAuthoritativeBodySnapshot( - authoritativePhysicsStore("iterate copied physics body snapshots"), - spaceId, - visitor); - return; - } - lifecycleState.forEachIndexedBodySnapshot(spaceId, visitor); - } - - public int forEachBodySnapshotNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull Consumer consumer) { - if (isAuthoritativePhysicsStoreActive()) { - return forEachAuthoritativeBodySnapshotNear( - authoritativePhysicsStore("iterate nearby copied physics body snapshots"), - spaceId, - center, - radius, - consumer); - } - return lifecycleState.forEachBodySnapshotNear(spaceId, center, radius, consumer); - } - - public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - if (isAuthoritativePhysicsStoreActive()) { - return forEachIndexedAuthoritativeBodySnapshotNear( - authoritativePhysicsStore("iterate nearby copied physics body snapshots"), - spaceId, - center, - radius, - visitor); - } - return lifecycleState.forEachIndexedBodySnapshotNear(spaceId, center, radius, visitor); - } - - public int forEachIndexedBodySnapshotNearWithRefs(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotRefVisitor visitor) { - if (isAuthoritativePhysicsStoreActive()) { - return forEachIndexedAuthoritativeBodySnapshotNearWithRefs( - authoritativePhysicsStore("iterate nearby copied physics body snapshots"), - spaceId, - center, - radius, - visitor); - } - return lifecycleState.forEachIndexedBodySnapshotNear(spaceId, - center, - radius, - (bodyUuid, snapshot, bodySpaceId) -> - visitor.accept(bodyUuid, null, snapshot, bodySpaceId)); - } - - public void removeSpace(@Nonnull SpaceId spaceId) { - removeSpace(spaceId, ""); - } - - public void removeSpace(@Nonnull SpaceId spaceId, @Nonnull String worldName) { - if (isAuthoritativePhysicsStoreActive()) { - Store store = authoritativePhysicsStore("remove physics space"); - UUID spaceUuid = requireSpaceUuid(store, spaceId); - clearAuthoritativePhysicsChunkCollisionSpace(store, spaceUuid); - PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); - return; - } - requireLegacyMutationAllowed("remove physics space"); - runDirectRuntimeMutation("remove physics space", () -> removeSpaceDirect(spaceId, worldName)); - } - - @Nonnull - public PhysicsMutationHandle removeSpaceAsync(@Nonnull SpaceId spaceId, - @Nonnull String worldName) { - if (isAuthoritativePhysicsStoreActive()) { - return enqueueAuthoritativePhysicsStoreMutation("remove physics space", - spaceId, - store -> { - UUID spaceUuid = requireSpaceUuid(store, spaceId); - clearAuthoritativePhysicsChunkCollisionSpace(store, spaceUuid); - PhysicsStoreTopologyMutations.removeSpaceWithContents(store, spaceUuid); - }); - } - requireLegacyMutationAllowed("remove physics space"); - return enqueueDirectRuntimeMutation("remove physics space", - spaceId, - () -> removeSpaceDirect(spaceId, worldName)); - } - - private void removeSpaceDirect(@Nonnull SpaceId spaceId, @Nonnull String worldName) { - PhysicsSpaceBinding removed = spaceRuntime.removeSpace(spaceId); - if (removed == null) { - return; - } - - try { - jointRegistry.unregisterSpace(spaceId); - for (PhysicsBodyRegistration registration : new ArrayList<>(bodyRegistry.getRegistrations())) { - if (registration.spaceId().equals(spaceId)) { - destroyBody(registration.bodyUuid(), false); - } - } - LOGGER.at(Level.FINE).log( - "World %s removed physics space id=%s backend=%s", - worldName, - removed.spaceId(), - removed.backendId()); - markWorldChanged(); - } finally { - PhysicsSpaceRuntime.closeBindingSilently(removed, worldName, "removed physics space"); - } - } - - public void clearAllSpaces(@Nonnull String worldName) { - if (isAuthoritativePhysicsStoreActive()) { - Store store = authoritativePhysicsStore("clear physics spaces"); - clearAuthoritativePhysicsChunkCollisionStreaming(store); - PhysicsStoreRuntimeCleaner.clearAll(store); - return; - } - requireLegacyMutationAllowed("clear physics spaces"); - runDirectRuntimeMutation("clear physics spaces", () -> clearAllSpacesDirect(worldName)); - } - - @Nonnull - public PhysicsMutationHandle clearAllSpacesAsync(@Nonnull String worldName) { - if (isAuthoritativePhysicsStoreActive()) { - return enqueueAuthoritativePhysicsStoreMutation("clear physics spaces", - null, - store -> { - clearAuthoritativePhysicsChunkCollisionStreaming(store); - PhysicsStoreRuntimeCleaner.clearAll(store); - }); - } - requireLegacyMutationAllowed("clear physics spaces"); - return enqueueDirectRuntimeMutation("clear physics spaces", - () -> clearAllSpacesDirect(worldName)); - } - - private void clearAllSpacesDirect(@Nonnull String worldName) { - RuntimeException failure = null; - for (SpaceId spaceId : spaceRuntime.getSpaceIds()) { - try { - removeSpaceDirect(spaceId, worldName); - } catch (RuntimeException exception) { - failure = collectFailure(failure, exception); - } - } - if (failure != null) { - throw failure; - } - } - - @Nonnull - private static RuntimeException collectFailure(@Nullable RuntimeException failure, - @Nonnull RuntimeException exception) { - if (failure == null) { - return exception; - } - failure.addSuppressed(exception); - return failure; - } - - /** - * Clears runtime physics state by replacing each native backend space with an empty - * space that keeps the same logical id, backend, settings, and gravity. - */ - @Nonnull - public PhysicsRuntimeResetResult resetRuntimeStateKeepingSpaces(@Nonnull String worldName) { - if (isAuthoritativePhysicsStoreActive()) { - Store store = authoritativePhysicsStore("reset physics runtime state"); - clearAuthoritativePhysicsChunkCollisionStreaming(store); - return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); - } - requireLegacyMutationAllowed("reset physics runtime state"); - return callDirectRuntime("reset physics runtime state", - () -> resetRuntimeStateKeepingSpacesDirect(worldName)); - } - - @Nonnull - public CompletionStage resetRuntimeStateKeepingSpacesAsync( - @Nonnull String worldName) { - Objects.requireNonNull(worldName, "worldName"); - if (isAuthoritativePhysicsStoreActive()) { - World world = requireAuthoritativeWorld("reset physics runtime state"); - return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, - "reset physics runtime state", - store -> { - clearAuthoritativePhysicsChunkCollisionStreaming(store); - return PhysicsStoreTopologyMutations.clearBodiesKeepingSpaces(store); - }); - } - CompletableFuture completion = new CompletableFuture<>(); - try { - requireLegacyMutationAllowed("reset physics runtime state"); - completion.complete(resetRuntimeStateKeepingSpacesDirect(worldName)); - } catch (RuntimeException exception) { - completion.completeExceptionally(exception); - } - return completion.minimalCompletionStage(); - } - - @Nonnull - private PhysicsRuntimeResetResult resetRuntimeStateKeepingSpacesDirect(@Nonnull String worldName) { - PhysicsRuntimeResetResult reset = spaceRuntime.resetKeepingSpaces(worldName, - simulationRuntime.getWorldSettings().getStepMode()); - clearRuntimeTopologyDirect(false); - markWorldChanged(); - return reset; - } - - @Nonnull - public PhysicsSpaceSettings getSpaceSettings(@Nonnull SpaceId spaceId) { - if (isAuthoritativePhysicsStoreActive()) { - PhysicsSpaceSettings settings = getPhysicsStoreSpaceSettings( - authoritativePhysicsStore("read physics space settings"), - spaceId); - if (settings == null) { - throw new IllegalArgumentException("PhysicsStore space id=" + spaceId.value() - + " is not registered"); - } - return settings; - } - return spaceRuntime.getSpaceSettings(spaceId); - } - - @Nonnull - public PhysicsSpaceSettings getSpaceSettings(@Nonnull Ref spaceRef) { - if (!isAuthoritativePhysicsStoreActive()) { - throw new IllegalStateException("Cannot read PhysicsStore space settings by entity ref " - + "when authoritative PhysicsStore mode is unavailable"); - } - PhysicsSpaceSettings settings = getPhysicsStoreSpaceSettings( - authoritativePhysicsStore("read physics space settings"), - spaceRef); - if (settings == null) { - throw new IllegalArgumentException("PhysicsStore space ref=" + spaceRef - + " is not registered"); - } - return settings; - } - - @Nonnull - public PhysicsSpaceSettings getLiveSpaceSettings(@Nonnull SpaceId spaceId) { - return spaceRuntime.getLiveSpaceSettings(spaceId); - } - - public void setSpaceSettings(@Nonnull SpaceId spaceId, @Nonnull PhysicsSpaceSettings settings) { - if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreSpaceMutations.putSpaceSettings( - authoritativePhysicsStore("set physics space settings"), - spaceId, - settings); - return; - } - requireLegacyMutationAllowed("set physics space settings"); - PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); - runDirectRuntimeMutation("set physics space settings", () -> setSpaceSettingsDirect(spaceId, requested)); - } - - public void setSpaceSettings(@Nonnull Ref spaceRef, - @Nonnull PhysicsSpaceSettings settings) { - if (!isAuthoritativePhysicsStoreActive()) { - throw new IllegalStateException("Cannot set PhysicsStore space settings by entity ref " - + "when authoritative PhysicsStore mode is unavailable"); - } - PhysicsStoreSpaceMutations.putSpaceSettings( - authoritativePhysicsStore("set physics space settings"), - spaceRef, - settings); - } - - @Nonnull - public PhysicsMutationHandle setSpaceSettingsAsync(@Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { - if (isAuthoritativePhysicsStoreActive()) { - PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); - return enqueueAuthoritativePhysicsStoreMutation("set physics space settings", - spaceId, - store -> PhysicsStoreSpaceMutations.putSpaceSettings(store, spaceId, requested)); - } - requireLegacyMutationAllowed("set physics space settings"); - PhysicsSpaceSettings requested = new PhysicsSpaceSettings(settings); - return enqueueDirectRuntimeMutation("set physics space settings", - spaceId, - () -> setSpaceSettingsDirect(spaceId, requested)); - } - - private void setSpaceSettingsDirect(@Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { - spaceRuntime.setSpaceSettings(spaceId, settings); - } - - private void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { - spaceRuntime.validateStepModeSupported(stepMode); - } - - public void destroyBody(@Nonnull UUID bodyUuid) { - UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - if (isAuthoritativePhysicsStoreActive()) { - PhysicsStoreTopologyMutations.destroyBody( - authoritativePhysicsStore("destroy physics body"), - checkedBodyUuid); - return; - } - requireLegacyMutationAllowed("destroy physics body"); - destroyBody(checkedBodyUuid, true); - } - - @Nonnull - public PhysicsMutationHandle destroyBodyAsync(@Nonnull UUID bodyUuid) { - UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - if (isAuthoritativePhysicsStoreActive()) { - World world = requireAuthoritativeWorld("destroy physics body"); - return PhysicsMutationHandle.fromCompletion("destroy physics body", - checkedBodyUuid, - PhysicsThreading.callWhenBackendIdleOnWorldThread(world, - "destroy physics body", - store -> { - PhysicsStoreTopologyMutations.destroyBody(store, checkedBodyUuid); - return null; - })); - } - requireLegacyMutationAllowed("destroy physics body"); - return enqueueDirectRuntimeMutation("destroy physics body", - checkedBodyUuid, - () -> destroyBodyDirect(checkedBodyUuid, true)); - } - - private void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { - requireLegacyMutationAllowed("destroy physics body"); - runDirectRuntimeMutation("destroy physics body", () -> destroyBodyDirect(bodyUuid, removeFromSpace)); - } - - private void destroyBodyDirect(@Nonnull UUID bodyUuid, boolean removeFromSpace) { - bodyRuntime.destroyBody(bodyUuid, removeFromSpace); - } - - public void unregisterBodyAttachment(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref attachment) { - if (hasAttachedAuthoritativePhysicsStore()) { - authoritativeProjectionIndex("unregister physics body attachment") - .unregisterAttachment(bodyUuid, bodyRef, attachment); - return; - } - visualRuntime.unregisterAttachment(bodyUuid, bodyRef, attachment); - } - - public void setGeneratedVisualProxy(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref proxy) { - if (hasAttachedAuthoritativePhysicsStore()) { - authoritativeProjectionIndex("set generated visual proxy") - .setGeneratedVisualProxy(bodyUuid, bodyRef, proxy); - return; - } - visualRuntime.setGeneratedVisualProxy(bodyUuid, bodyRef, proxy); - } - - public void clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - if (hasAttachedAuthoritativePhysicsStore()) { - authoritativeProjectionIndex("clear generated visual proxy") - .clearGeneratedVisualProxyForBodyRef(bodyUuid, bodyRef); - return; - } - visualRuntime.clearGeneratedVisualProxy(bodyUuid, bodyRef); - } - - public boolean clearGeneratedVisualProxy(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull Ref expectedProxy) { - if (hasAttachedAuthoritativePhysicsStore()) { - PhysicsProjectionIndexResource projection = - authoritativeProjectionIndex("clear generated visual proxy"); - Ref registered = bodyRef != null - ? projection.getGeneratedVisualProxy(bodyRef) - : projection.getGeneratedVisualProxy(bodyUuid); - if (!sameRef(registered, expectedProxy)) { - return false; - } - projection.clearGeneratedVisualProxy(bodyUuid, bodyRef, expectedProxy); - return true; - } - return visualRuntime.clearGeneratedVisualProxy(bodyUuid, bodyRef, expectedProxy); - } - - public void setSyntheticVisualInterests(@Nonnull Collection interests) { - visualRuntime.setSyntheticVisualInterests(interests); - } - - @Nonnull - public List getSyntheticVisualInterests() { - return visualRuntime.getSyntheticVisualInterests(); - } - - public void clearSyntheticVisualInterests() { - visualRuntime.clearSyntheticVisualInterests(); - } - - private void clearBodyStateDirect() { - clearRuntimeTopologyDirect(false); - markWorldChanged(); - } - - @Nonnull - public BodySyncState getOrCreateBodySyncState(@Nonnull Ref entityRef) { - return runtimeState.getOrCreateBodySyncState(entityRef); - } - - @Nullable - public BodySyncState getBodySyncState(@Nonnull Ref entityRef) { - return runtimeState.getBodySyncState(entityRef); - } - - public void clearBodySyncState(@Nonnull Ref entityRef) { - runtimeState.clearBodySyncState(entityRef); - } - - @Nonnull - public BodyVisualInterestState getOrCreateBodyVisualInterestState(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - BodyVisualInterestState state = - visualRuntime.getOrCreateBodyVisualInterestState(bodyUuid, bodyRef); - state.advanceVisualInterestTick(visualInterestTick.get()); - return state; - } - - @Nullable - public BodyVisualInterestState getBodyVisualInterestState(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef) { - BodyVisualInterestState state = visualRuntime.getBodyVisualInterestState(bodyUuid, - bodyRef); - if (state != null) { - state.advanceVisualInterestTick(visualInterestTick.get()); - } - return state; - } - - public long advanceVisualInterestTick() { - return visualInterestTick.incrementAndGet(); - } - - public void markBodyControlled(@Nonnull Ref bodyRef) { - controlRuntime.markBodyControlled(bodyRef); - } - - public void clearControlledBody(@Nonnull Ref bodyRef) { - controlRuntime.clearControlledBody(bodyRef); - } - - public boolean isBodyControlled(@Nonnull Ref bodyRef) { - return controlRuntime.isBodyControlled(bodyRef); - } - - public void disableControlLifecycle() { - controlRuntime.clear(); - } - - private void copyFrom(@Nonnull PhysicsWorldResource other) { - runDirectRuntimeMutation("copy physics world resource", () -> copyFromDirect(other)); - } - - private void copyFromDirect(@Nonnull PhysicsWorldResource other) { - if (this == other) { - return; - } - PhysicsWorldRuntimeResource otherRuntime = require(other); - spaceRuntime.clearLiveTopology(""); - clearRuntimeTopologyDirect(true); - simulationRuntime.copyFrom(otherRuntime.simulationRuntime); - markWorldChanged(); - } - - private void clearRuntimeTopologyDirect(boolean clearCollision) { - bodyRuntime.clearBodyStateWithoutMarkingWorldChanged(); - } - - private void markWorldChanged() { - lifecycleState.markWorldChanged(bodyRegistry, false); - } - - @FunctionalInterface - private interface DirectRuntimeMutation { - - void run() throws Exception; - } - - @FunctionalInterface - private interface DirectRuntimeCallable { - - T call() throws Exception; - } - - @Nonnull - @Override - public PhysicsWorldResource clone() { - PhysicsWorldRuntimeResource copy = new PhysicsWorldRuntimeResource(); - copy.copyFrom(this); - return copy; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index 5c431592..d325d962 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java index 3db997cc..cb860572 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource.ChunkCollisionSurfaceComponents; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 2ad59860..9eb7bcdd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -25,7 +25,9 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRowCleanup; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -36,7 +38,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -345,15 +347,17 @@ private static void removeGeneratedRows(@Nonnull Store store, rows.sort((first, second) -> Integer.compare(second.ref().getIndex(), first.ref().getIndex())); boolean removedAny = false; + List bodyEntityRemovals = + new ArrayList<>(rows.size()); for (GeneratedRow row : rows) { PhysicsStoreRowCleanup.removeRuntimeBody(runtime, identity, row.uuid(), row.ref()); - PhysicsStoreRowCleanup.removeBodyEntity(store, - row.uuid(), + bodyEntityRemovals.add(new PhysicsStoreRowCleanup.BodyEntityRemoval(row.uuid(), row.ref(), - row.payloadResourceKey()); + row.payloadResourceKey())); removedAny = true; } if (removedAny) { + PhysicsStoreRowCleanup.removeBodyEntities(store, bodyEntityRemovals); PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java index 5bbb6217..d81ecf21 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java @@ -19,6 +19,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index f4b0253e..c7d7a565 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -22,6 +22,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; +import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 4113fd25..b69e48e7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java index f61855ef..60707d13 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java @@ -14,6 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java index 9ead3668..bcaf9208 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java @@ -13,7 +13,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -final class PhysicsStoreSystemSupport { +public final class PhysicsStoreSystemSupport { static final UUID NIL_UUID = new UUID(0L, 0L); private PhysicsStoreSystemSupport() { @@ -25,12 +25,12 @@ static ComponentType uuidType() { } @Nonnull - static Query uuidQuery() { + public static Query uuidQuery() { return uuidType(); } @Nonnull - static UUID rowUuid(@Nonnull ArchetypeChunk chunk, int index) { + public static UUID rowUuid(@Nonnull ArchetypeChunk chunk, int index) { UuidComponent uuid = chunk.getComponent(index, uuidType()); return uuid != null ? uuid.getUuid() : NIL_UUID; } @@ -41,7 +41,7 @@ static UUID rowUuid(@Nonnull Ref ref) { return uuid != null ? uuid.getUuid() : NIL_UUID; } - static boolean isNil(@Nonnull UUID uuid) { + public static boolean isNil(@Nonnull UUID uuid) { return NIL_UUID.equals(uuid); } @@ -63,7 +63,7 @@ static Ref refForUuid(@Nonnull PhysicsIdentityIndexResource identi } @Nullable - static Ref resolvedRef(@Nonnull PhysicsIdentityIndexResource identity, + public static Ref resolvedRef(@Nonnull PhysicsIdentityIndexResource identity, @Nonnull UUID uuid, @Nullable Ref current) { if (isNil(uuid)) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java deleted file mode 100644 index 8edcb762..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsWorldResourceAttachmentSystem.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.dependency.Dependency; -import com.hypixel.hytale.component.dependency.Order; -import com.hypixel.hytale.component.dependency.SystemDependency; -import com.hypixel.hytale.component.system.tick.TickingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; -import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; -import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; -import java.util.Set; -import javax.annotation.Nonnull; - -/** - * Attaches the concrete physics runtime resource to its owning EntityStore. - */ -public final class PhysicsWorldResourceAttachmentSystem extends TickingSystem { - - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.BEFORE, PhysicsStoreEventPublicationSystem.class), - new SystemDependency<>(Order.BEFORE, PhysicsProjectionCleanupSystem.class), - new SystemDependency<>(Order.BEFORE, PhysicsSyncSystem.class), - new SystemDependency<>(Order.BEFORE, PhysicsDebugSystem.class) - ); - - @Override - public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsWorldRuntimeResource.require(store); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java index 60a36b46..99f647db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceSettingsApplicationSystem.java @@ -14,6 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; @@ -94,7 +95,7 @@ static boolean applyIfBound(@Nonnull Store store, return true; } - static void applyBackendSettings(@Nonnull PhysicsBackendRuntime runtime, + public static void applyBackendSettings(@Nonnull PhysicsBackendRuntime runtime, @Nonnull BackendSpaceHandle handle, @Nonnull SolverSettingsComponent solverSettings, @Nullable ExtensionSettingsComponent extensionSettings) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index c8f358d9..3edc8589 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -8,11 +8,12 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreRowCleanup; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.systems.binding.JointBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java index a396f514..e0a7da11 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.systems.binding; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -22,6 +22,8 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; +import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ColliderBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/ColliderBindingSystem.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ColliderBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/ColliderBindingSystem.java index b05a79e2..520eb48a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ColliderBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/ColliderBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.systems.binding; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java index c2e62d52..202dfe3c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.systems.binding; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -20,6 +20,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import java.util.Set; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java index 1d2477b4..5704e5c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.systems.binding; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -21,6 +21,9 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; +import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/TargetBindingSystem.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/TargetBindingSystem.java index 398b531e..da7a09ec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/TargetBindingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.systems.binding; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -17,6 +17,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.PendingBodyOperation; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import java.util.Set; import java.util.function.BiConsumer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index afc35f40..fec65348 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -15,18 +15,28 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -44,6 +54,7 @@ import java.util.concurrent.CompletionStage; import java.util.function.Supplier; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3f; @@ -87,7 +98,6 @@ public Set> getDependencies() { @Override public void tick(float dt, int index, @Nonnull Store store) { World world = store.getExternalData().getWorld(); - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); assert PhysicsDebugResource.getResourceType() != null; PhysicsDebugResource debug = store.getResource(PhysicsDebugResource.getResourceType()); @@ -132,7 +142,6 @@ public void tick(float dt, int index, @Nonnull Store store) { int renderedBodies = renderEntityBodies(target, store, physicsStore, - resource, viewerPosition, debug.getViewRadius(), debugShapes, @@ -142,7 +151,6 @@ public void tick(float dt, int index, @Nonnull Store store) { renderDetachedBodies(target, store, physicsStore, - resource, viewerPosition, debug.getViewRadius(), debugShapes, @@ -151,9 +159,9 @@ public void tick(float dt, int index, @Nonnull Store store) { overlayLifetime); } - for (SpaceId spaceId : resource.getSpaceIds()) { + for (SpaceId spaceId : PhysicsSpaces.spaceIds(physicsStore)) { if (overlayDue && debugShapes) { - renderSpaceOnlyShapes(target, resource, spaceId, overlayLifetime); + renderSpaceOnlyShapes(target, physicsStore, spaceId, overlayLifetime); } if (overlayDue && debugContacts) { renderContacts(target, @@ -220,7 +228,6 @@ private static List resolveSubscribers(@Nonnull World world, private int renderEntityBodies(@Nonnull Collection viewers, @Nonnull Store store, @Nonnull Store physicsStore, - @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d viewerPosition, double viewRadius, boolean debugShapes, @@ -256,8 +263,8 @@ private int renderEntityBodies(@Nonnull Collection viewers, continue; } - PhysicsBodySnapshot snapshot = resource.getBodySnapshotIfRegistered(bodyUuid, - null); + PhysicsBodySnapshot snapshot = apiSnapshot(physicsStore, + PhysicsBodies.snapshot(physicsStore, bodyUuid)); if (snapshot == null) { continue; } @@ -290,7 +297,6 @@ private int renderEntityBodies(@Nonnull Collection viewers, private static int renderDetachedBodies(@Nonnull Collection viewers, @Nonnull Store store, @Nonnull Store physicsStore, - @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull Vector3d viewerPosition, double viewRadius, boolean debugShapes, @@ -303,8 +309,8 @@ private static int renderDetachedBodies(@Nonnull Collection viewers, RenderedBodyCount rendered = new RenderedBodyCount(); double maxDistanceSquared = viewRadius * viewRadius; - for (SpaceId spaceId : resource.getSpaceIds()) { - resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, _) -> { + for (SpaceId spaceId : PhysicsSpaces.spaceIds(physicsStore)) { + forEachBodySnapshot(physicsStore, spaceId, (bodyUuid, snapshot) -> { if (rendered.hasReached(maxBodies)) { return; } @@ -358,10 +364,10 @@ private int value() { } private static void renderSpaceOnlyShapes(@Nonnull Collection viewers, - @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull Store physicsStore, @Nonnull SpaceId spaceId, float time) { - resource.forEachIndexedBodySnapshot(spaceId, (bodyUuid, snapshot, snapshotSpaceId) -> { + forEachBodySnapshot(physicsStore, spaceId, (_, snapshot) -> { if (snapshot.shapeType() != ShapeType.PLANE) { return; } @@ -378,6 +384,114 @@ private static void renderSpaceOnlyShapes(@Nonnull Collection viewers }); } + private static void forEachBodySnapshot(@Nonnull Store physicsStore, + @Nonnull SpaceId spaceId, + @Nonnull BodySnapshotConsumer consumer) { + UUID spaceUuid = physicsStore + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(spaceId); + if (spaceUuid == null) { + return; + } + for (dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot snapshot + : PhysicsBodies.snapshotFrame(physicsStore).bodies()) { + if (!spaceUuid.equals(snapshot.spaceUuid())) { + continue; + } + PhysicsBodySnapshot apiSnapshot = apiSnapshot(physicsStore, snapshot); + if (apiSnapshot != null) { + consumer.accept(snapshot.bodyUuid(), apiSnapshot); + } + } + } + + @Nullable + private static PhysicsBodySnapshot apiSnapshot(@Nonnull Store store, + @Nullable dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot snapshot) { + if (snapshot == null) { + return null; + } + Ref ref = snapshot.bodyRef(); + if (ref == null || ref.getStore() != store || !ref.isValid()) { + ref = PhysicsEntities.resolveRef(store, snapshot.bodyUuid()); + } + boolean validRef = ref != null && ref.isValid(); + DynamicsComponent dynamics = validRef + ? store.getComponent(ref, DynamicsComponent.getComponentType()) + : null; + ColliderComponent collider = validRef + ? store.getComponent(ref, ColliderComponent.getComponentType()) + : null; + MaterialComponent material = validRef + ? store.getComponent(ref, MaterialComponent.getComponentType()) + : null; + CollisionFilterComponent filter = validRef + ? store.getComponent(ref, CollisionFilterComponent.getComponentType()) + : null; + ShapeComponent shape = validRef + ? store.getComponent(ref, ShapeComponent.getComponentType()) + : null; + + Vector3f position = snapshot.position(); + Quaterniond rotationD = new Quaterniond(snapshot.rotationX(), + snapshot.rotationY(), + snapshot.rotationZ(), + snapshot.rotationW()); + org.joml.Quaternionf rotation = new org.joml.Quaternionf((float) rotationD.x, + (float) rotationD.y, + (float) rotationD.z, + (float) rotationD.w); + Vector3f linearVelocity = snapshot.linearVelocity(); + Vector3f angularVelocity = snapshot.angularVelocity(); + PhysicsBodyType bodyType = snapshot.bodyType(); + ShapeType shapeType = shape != null ? shape.getShapeType() : ShapeType.UNKNOWN; + boolean hasBoxHalfExtents = shapeType == ShapeType.BOX && shape != null; + + return PhysicsBodySnapshot.of(position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w, + linearVelocity.x, + linearVelocity.y, + linearVelocity.z, + angularVelocity.x, + angularVelocity.y, + angularVelocity.z, + bodyType, + snapshot.sleeping(), + collider != null && collider.isSensor(), + bodyType == PhysicsBodyType.DYNAMIC ? authoredMass(dynamics) : 0.0f, + material != null ? material.getFriction() : 0.5f, + material != null ? material.getRestitution() : 0.0f, + dynamics != null ? dynamics.getLinearDamping() : 0.0f, + dynamics != null ? dynamics.getAngularDamping() : 0.0f, + filter != null ? filter.getCollisionGroup() : PhysicsCollisionFilters.DYNAMIC_BODY, + filter != null ? filter.getCollisionMask() : PhysicsCollisionFilters.ALL, + dynamics != null && dynamics.isContinuousCollisionEnabled(), + snapshot.centerOfMassOffsetY(), + shapeType, + hasBoxHalfExtents, + hasBoxHalfExtents ? shape.getHalfExtentX() : 0.0f, + hasBoxHalfExtents ? shape.getHalfExtentY() : 0.0f, + hasBoxHalfExtents ? shape.getHalfExtentZ() : 0.0f, + shape != null ? shape.getRadius() : 0.0f, + shape != null ? shape.getHalfHeight() : 0.0f, + shape != null ? shape.getAxis() : PhysicsAxis.Y); + } + + private static float authoredMass(@Nullable DynamicsComponent dynamics) { + return dynamics != null ? dynamics.getMass() : 1.0f; + } + + @FunctionalInterface + private interface BodySnapshotConsumer { + + void accept(@Nonnull UUID bodyUuid, @Nonnull PhysicsBodySnapshot snapshot); + } + private static void renderContacts(@Nonnull Collection viewers, @Nonnull Store physicsStore, @Nonnull SpaceId spaceId, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 369f6f07..857f9ef3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -18,7 +18,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index a69617fa..f91ef6e8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource.StepSample; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java index b1960b68..07873f56 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java @@ -2,7 +2,6 @@ import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import java.util.List; @@ -61,7 +60,7 @@ private PhysicsSyncPolicy() { } @Nonnull - static SyncRangeTier resolveRangeTier(@Nullable PhysicsSpaceSettings settings, + static SyncRangeTier resolveRangeTier(@Nullable PhysicsVisualSyncSettings settings, @Nullable BodyVisualInterestState visualInterestState, boolean rangeLimitedVisual, boolean controlled, @@ -76,18 +75,18 @@ static SyncRangeTier resolveRangeTier(@Nullable PhysicsSpaceSettings settings, if (settings == null) { return SyncRangeTier.NEAR; } - if (settings.getVisualSyncSettings().getVisualOcclusionMode() == VisualOcclusionMode.CULL + if (settings.getVisualOcclusionMode() == VisualOcclusionMode.CULL && visualInterestState != null - && visualInterestState.hasFreshRaycast(settings.getVisualSyncSettings().getVisualOcclusionCacheTicks()) + && visualInterestState.hasFreshRaycast(settings.getVisualOcclusionCacheTicks()) && !visualInterestState.isRaycastVisible()) { // Materialization owns the raycast budget; sync only consumes fresh CULL results. return SyncRangeTier.FAR; } - float fullRadiusSquared = square(settings.getVisualSyncSettings().getVisualFullSyncRadius()); - float maxRadiusSquared = square(settings.getVisualSyncSettings().getVisualMaxSyncRadius()); + float fullRadiusSquared = square(settings.getVisualFullSyncRadius()); + float maxRadiusSquared = square(settings.getVisualMaxSyncRadius()); float nearestDistanceSquared = Float.MAX_VALUE; - boolean visibilityCulling = settings.getVisualSyncSettings().isVisualVisibilityCullingEnabled(); + boolean visibilityCulling = settings.isVisualVisibilityCullingEnabled(); for (PlayerInterest playerInterest : playerInterests) { float distanceSquared = playerInterest.position().distanceSquared(visualPosition); if (distanceSquared <= fullRadiusSquared @@ -103,7 +102,7 @@ static SyncRangeTier resolveRangeTier(@Nullable PhysicsSpaceSettings settings, @Nonnull static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyncState syncState, - @Nullable PhysicsSpaceSettings settings, + @Nullable PhysicsVisualSyncSettings settings, @Nonnull Vector3f position, @Nonnull Quaternionf rotation, boolean sleeping, @@ -116,8 +115,9 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn if (sleeping != syncState.isSleeping()) { return SyncDecision.TRANSITION; } + PhysicsVisualSyncSettings visualSyncSettings = settingsOrDefault(settings); if (rangeTier == SyncRangeTier.FAR - && (settings == null || settings.getVisualSyncSettings().isVisualFarSyncCutoffEnabled())) { + && visualSyncSettings.isVisualFarSyncCutoffEnabled()) { return SyncDecision.SKIP_VISUAL_RANGE; } if (sleeping && rangeTier != SyncRangeTier.NEAR) { @@ -131,15 +131,13 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn if (rangeTier == SyncRangeTier.FAR && !controlled) { positionThresholdSquared = MID_RANGE_POSITION_SYNC_THRESHOLD_SQUARED; rotationDotThreshold = MID_RANGE_ROTATION_SYNC_DOT_THRESHOLD; - keepaliveSeconds = intervalSeconds(settings.getVisualSyncSettings().getVisualFarSyncIntervalTicks()); - minimumIntervalTicks = settings.getVisualSyncSettings().getVisualFarSyncIntervalTicks(); + keepaliveSeconds = intervalSeconds(visualSyncSettings.getVisualFarSyncIntervalTicks()); + minimumIntervalTicks = visualSyncSettings.getVisualFarSyncIntervalTicks(); } else if (rangeTier == SyncRangeTier.MID && !controlled) { positionThresholdSquared = MID_RANGE_POSITION_SYNC_THRESHOLD_SQUARED; rotationDotThreshold = MID_RANGE_ROTATION_SYNC_DOT_THRESHOLD; keepaliveSeconds = MID_RANGE_KEEPALIVE_SECONDS; - minimumIntervalTicks = settings != null - ? settings.getVisualSyncSettings().getVisualMidSyncIntervalTicks() - : PhysicsVisualSyncSettings.DEFAULT_VISUAL_MID_SYNC_INTERVAL_TICKS; + minimumIntervalTicks = visualSyncSettings.getVisualMidSyncIntervalTicks(); } else { positionThresholdSquared = lowSpeed && !controlled ? LOW_SPEED_POSITION_SYNC_THRESHOLD_SQUARED : POSITION_SYNC_THRESHOLD_SQUARED; @@ -205,17 +203,23 @@ private static float intervalSeconds(int ticks) { return ticks * SECONDS_PER_TICK; } - static float visualPredictionSeconds(@Nullable PhysicsSpaceSettings settings, + static float visualPredictionSeconds(@Nullable PhysicsVisualSyncSettings settings, long currentNanos, long snapshotAppliedNanos) { if (settings == null - || !settings.getVisualSyncSettings().isVisualSnapshotPredictionEnabled() + || !settings.isVisualSnapshotPredictionEnabled() || currentNanos <= snapshotAppliedNanos || snapshotAppliedNanos <= 0L) { return 0.0f; } float ageSeconds = (currentNanos - snapshotAppliedNanos) / 1_000_000_000.0f; - return Math.min(ageSeconds, settings.getVisualSyncSettings().getVisualSnapshotPredictionMaxSeconds()); + return Math.min(ageSeconds, settings.getVisualSnapshotPredictionMaxSeconds()); + } + + @Nonnull + private static PhysicsVisualSyncSettings settingsOrDefault( + @Nullable PhysicsVisualSyncSettings settings) { + return settings != null ? settings : new PhysicsVisualSyncSettings(); } public record PlayerInterest(@Nonnull Vector3f position, @Nonnull Vector3f direction) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java index 8bcbc26e..c55736fc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java @@ -22,7 +22,7 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; @@ -88,6 +88,7 @@ public boolean isParallel(int archetypeChunkSize, int taskCount) { @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { + assert PhysicsRuntimeProfilingResource.getResourceType() != null; PhysicsRuntimeProfilingResource profiling = store.getResource( PhysicsRuntimeProfilingResource.getResourceType()); PhysicsRuntimeProfilingResource.SyncCollector collector = profiling.isEnabled() diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java index 65ad10a5..f2a01694 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java @@ -8,8 +8,8 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodySyncStateResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import java.util.UUID; @@ -25,31 +25,29 @@ private GeneratedProxyLifecycle() { } static void removeProxy(@Nonnull ComponentAccessor accessor, - @Nonnull PhysicsWorldRuntimeResource resource, + @Nonnull PhysicsProjectionIndexResource projection, @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nullable Ref proxy) { if (proxy == null) { - resource.clearGeneratedVisualProxy(bodyUuid, bodyRef); + projection.clearGeneratedVisualProxyForBodyRef(bodyUuid, bodyRef); } else { - resource.clearGeneratedVisualProxy(bodyUuid, bodyRef, proxy); + projection.clearGeneratedVisualProxy(bodyUuid, bodyRef, proxy); } removeEntity(accessor, proxy); } public static void clearMissingAttachment(@Nonnull Ref entityRef, @Nonnull BodyAttachmentComponent attachment, - @Nonnull PhysicsWorldRuntimeResource resource, @Nonnull CommandBuffer commandBuffer) { UUID bodyUuid = attachment.getBodyUuid(); PhysicsProjectionIndexResource projection = commandBuffer.getResource( PhysicsProjectionIndexResource.getResourceType()); projection.unregisterAttachment(bodyUuid, attachment.getBodyRef(), entityRef); - resource.unregisterBodyAttachment(bodyUuid, attachment.getBodyRef(), entityRef); - resource.clearBodySyncState(entityRef); + commandBuffer.getResource(PhysicsBodySyncStateResource.getResourceType()) + .clear(entityRef); if (attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY) { projection.clearGeneratedVisualProxy(bodyUuid, attachment.getBodyRef(), entityRef); - resource.clearGeneratedVisualProxy(bodyUuid, attachment.getBodyRef(), entityRef); removeEntity(commandBuffer, entityRef); } else if (attachment.shouldRemoveEntityWhenBodyMissing()) { removeEntity(commandBuffer, entityRef); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java index 14b58105..55edb48c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java @@ -10,13 +10,12 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -62,19 +61,17 @@ private boolean shouldSkipCleanup(@Nonnull Store store) { private static void clearDestroyedBodyAttachments(@Nonnull Store store) { ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); - PhysicsWorldRuntimeResource resource = PhysicsWorldRuntimeResource.require(store); Store physicsStore = PhysicsThreading.storeOrNull( store.getExternalData().getWorld()); store.forEachEntityParallel(attachmentType, (index, archetypeChunk, commandBuffer) -> { BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, attachmentType); - if (attachment == null || !hasMissingBody(resource, physicsStore, attachment)) { + if (attachment == null || !hasMissingBody(physicsStore, attachment)) { return; } GeneratedProxyLifecycle.clearMissingAttachment(archetypeChunk.getReferenceTo(index), attachment, - resource, commandBuffer); }); } @@ -84,8 +81,7 @@ private static boolean hasDestroyedBodyRef(@Nonnull BodyAttachmentComponent atta return bodyRef != null && !bodyRef.isValid(); } - private static boolean hasMissingBody(@Nonnull PhysicsWorldRuntimeResource resource, - @Nullable Store store, + private static boolean hasMissingBody(@Nullable Store store, @Nonnull BodyAttachmentComponent attachment) { if (hasDestroyedBodyRef(attachment)) { return true; @@ -95,8 +91,7 @@ private static boolean hasMissingBody(@Nonnull PhysicsWorldRuntimeResource resou return false; } return store != null - ? !PhysicsBodies.isRegistered(store, attachment.getBodyUuid()) - : !resource.hasPublishedBodyRegistration(attachment.getBodyUuid()); + && !PhysicsBodies.isRegistered(store, attachment.getBodyUuid()); } private static void removeOrphanGeneratedVisualProxyMarkers(@Nonnull Store store) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java index d8c8cf25..b67c7892 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java @@ -9,9 +9,9 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualInterestResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncPolicy; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; @@ -57,8 +57,7 @@ public static List collectSyncInterests( @Nonnull public static List collectMaterializationInterests( - @Nonnull Store store, - @Nonnull PhysicsWorldRuntimeResource resource) { + @Nonnull Store store) { List interests = new ArrayList<>(); for (PlayerRef playerRef : store.getExternalData().getWorld().getPlayerRefs()) { Ref playerEntity = playerRef.getReference(); @@ -76,7 +75,8 @@ public static List collectMaterializationIn new Vector3f((float) position.x, (float) position.y, (float) position.z), playerLookDirection(store, playerEntity, transform))); } - interests.addAll(resource.getSyntheticVisualInterests()); + interests.addAll(store.getResource(PhysicsVisualInterestResource.getResourceType()) + .getSyntheticVisualInterests()); return interests; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java index 331cf20f..9a50e63e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ExtensionSettingsComponent.java @@ -11,7 +11,6 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettingValue; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Arrays; import java.util.Map; import java.util.Objects; @@ -55,10 +54,6 @@ public Entry[] entries() { return copyEntries(entries); } - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getExtensionSettings()); - } - public void copyTo(@Nonnull PhysicsExtensionSettings settings) { for (Entry entry : entries) { entry.copyTo(settings); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java index de856a23..d7e7795d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/ShapeComponent.java @@ -9,6 +9,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.ShapeType; +import lombok.Getter; +import lombok.Setter; import java.util.Objects; import javax.annotation.Nonnull; @@ -61,13 +63,25 @@ public final class ShapeComponent implements Component { @Nonnull private ShapeType shapeType = ShapeType.BOX; + @Setter + @Getter private float halfExtentX = 0.5f; + @Setter + @Getter private float halfExtentY = 0.5f; + @Setter + @Getter private float halfExtentZ = 0.5f; + @Setter + @Getter private float radius = 0.5f; + @Setter + @Getter private float halfHeight = 0.5f; @Nonnull private PhysicsAxis axis = PhysicsAxis.Y; + @Setter + @Getter private float groundY; @Nonnull private String resourceKey = ""; @@ -104,46 +118,6 @@ public void setShapeType(@Nonnull ShapeType shapeType) { this.shapeType = Objects.requireNonNull(shapeType, "shapeType"); } - public float getHalfExtentX() { - return halfExtentX; - } - - public void setHalfExtentX(float halfExtentX) { - this.halfExtentX = halfExtentX; - } - - public float getHalfExtentY() { - return halfExtentY; - } - - public void setHalfExtentY(float halfExtentY) { - this.halfExtentY = halfExtentY; - } - - public float getHalfExtentZ() { - return halfExtentZ; - } - - public void setHalfExtentZ(float halfExtentZ) { - this.halfExtentZ = halfExtentZ; - } - - public float getRadius() { - return radius; - } - - public void setRadius(float radius) { - this.radius = radius; - } - - public float getHalfHeight() { - return halfHeight; - } - - public void setHalfHeight(float halfHeight) { - this.halfHeight = halfHeight; - } - @Nonnull public PhysicsAxis getAxis() { return axis; @@ -153,14 +127,6 @@ public void setAxis(@Nonnull PhysicsAxis axis) { this.axis = Objects.requireNonNull(axis, "axis"); } - public float getGroundY() { - return groundY; - } - - public void setGroundY(float groundY) { - this.groundY = groundY; - } - @Nonnull public String getResourceKey() { return resourceKey; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java index 8e2bae36..2b215778 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/SolverSettingsComponent.java @@ -7,12 +7,15 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import lombok.Getter; +import lombok.Setter; import javax.annotation.Nonnull; /** * Authored backend solver and activation tuning for one PhysicsStore space entity. */ +@Setter +@Getter public final class SolverSettingsComponent implements Component { @Nonnull @@ -83,50 +86,6 @@ public SolverSettingsComponent(int solverIterations, this.dynamicSleepTimeUntilSleep = dynamicSleepTimeUntilSleep; } - public int getSolverIterations() { - return solverIterations; - } - - public void setSolverIterations(int solverIterations) { - this.solverIterations = solverIterations; - } - - public int getStabilizationIterations() { - return stabilizationIterations; - } - - public void setStabilizationIterations(int stabilizationIterations) { - this.stabilizationIterations = stabilizationIterations; - } - - public float getDynamicSleepLinearThreshold() { - return dynamicSleepLinearThreshold; - } - - public void setDynamicSleepLinearThreshold(float dynamicSleepLinearThreshold) { - this.dynamicSleepLinearThreshold = dynamicSleepLinearThreshold; - } - - public float getDynamicSleepAngularThreshold() { - return dynamicSleepAngularThreshold; - } - - public void setDynamicSleepAngularThreshold(float dynamicSleepAngularThreshold) { - this.dynamicSleepAngularThreshold = dynamicSleepAngularThreshold; - } - - public float getDynamicSleepTimeUntilSleep() { - return dynamicSleepTimeUntilSleep; - } - - public void setDynamicSleepTimeUntilSleep(float dynamicSleepTimeUntilSleep) { - this.dynamicSleepTimeUntilSleep = dynamicSleepTimeUntilSleep; - } - - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getSolverSettings()); - } - public void copyTo(@Nonnull PhysicsSolverSettings settings) { settings.setSolverIterations(solverIterations); settings.setStabilizationIterations(stabilizationIterations); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsEventCollectionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventCollectionMode.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsEventCollectionMode.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventCollectionMode.java index 609d7ee0..9d1061a6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsEventCollectionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventCollectionMode.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.settings; +package dev.hytalemodding.impulse.core.plugin.events; import java.util.Locale; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index 61a5d5d2..af3c1b57 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -11,8 +11,8 @@ import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index 74eb7335..c2e7b1a0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -8,15 +8,15 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -265,7 +265,7 @@ private static int clearSpaceChunkCollisionRows(@Nonnull World world, removed = streaming(world).clearSpace(spaceUuid, stampedQueue(store)); } int directlyRemoved = - PhysicsStoreTopologyMutations.clearChunkCollisionRowsForSpace(store, spaceUuid); + PhysicsTopologyMutations.clearChunkCollisionRowsForSpace(store, spaceUuid); return removed != 0 ? removed : directlyRemoved; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionProfiling.java index fef37db0..7fd32f51 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollisionProfiling.java @@ -7,7 +7,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.List; import java.util.Locale; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java index 3251db65..a19da58f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/ChunkCollisionSettingsComponent.java @@ -11,7 +11,6 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -165,10 +164,6 @@ public void setTtlTicks(int ttlTicks) { this.ttlTicks = ttlTicks; } - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getPhysicsChunkCollisionSettings()); - } - public void copyTo(@Nonnull PhysicsChunkCollisionSettings settings) { settings.setMode(mode); settings.setEntityChunkBoundaryMode(entityChunkBoundaryMode); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java index 0c4aa1e7..f650c7ea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/components/CollisionLodSettingsComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import javax.annotation.Nonnull; /** @@ -121,10 +120,6 @@ public boolean isCollisionLodFarSleepEnabled() { return collisionLodFarSleepEnabled; } - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getCollisionLodSettings()); - } - public void copyTo(@Nonnull PhysicsCollisionLodSettings target) { target.setCollisionLodEnabled(collisionLodEnabled); target.setCollisionLodRadii(collisionLodNearRadius, collisionLodMidRadius); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java index 0f7609e1..14252e5d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityAttachments.java @@ -42,6 +42,7 @@ public static void requireAvailable() { public static Collection> attachments(@Nonnull Store store, @Nonnull UUID bodyUuid) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; return requireWorldThread(store, "read PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); @@ -51,6 +52,7 @@ public static Collection> attachments(@Nonnull Store> attachments(@Nonnull Store store, @Nonnull Ref bodyRef) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; return requireWorldThread(store, "read PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getAttachments(Objects.requireNonNull(bodyRef, "bodyRef")); @@ -61,6 +63,7 @@ public static Collection> attachments(@Nonnull Store bodyRef) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; PhysicsProjectionIndexResource projection = requireWorldThread(store, "read PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()); @@ -72,6 +75,7 @@ public static Collection> attachments(@Nonnull Store store, @Nonnull UUID bodyUuid) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; return requireWorldThread(store, "check PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .hasAttachments(Objects.requireNonNull(bodyUuid, "bodyUuid")); @@ -80,6 +84,7 @@ public static boolean hasAttachments(@Nonnull Store store, public static boolean hasAttachments(@Nonnull Store store, @Nonnull Ref bodyRef) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; return requireWorldThread(store, "check PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()) .hasAttachments(Objects.requireNonNull(bodyRef, "bodyRef")); @@ -89,6 +94,7 @@ public static boolean hasAttachments(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nullable Ref bodyRef) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; PhysicsProjectionIndexResource projection = requireWorldThread(store, "check PhysicsStore body attachments") .getResource(PhysicsProjectionIndexResource.getResourceType()); @@ -101,6 +107,7 @@ public static boolean hasAttachments(@Nonnull Store store, public static Ref generatedVisualProxy(@Nonnull Store store, @Nonnull UUID bodyUuid) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; return requireWorldThread(store, "read PhysicsStore generated visual proxy") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getGeneratedVisualProxy(Objects.requireNonNull(bodyUuid, "bodyUuid")); @@ -110,6 +117,7 @@ public static Ref generatedVisualProxy(@Nonnull Store public static Ref generatedVisualProxy(@Nonnull Store store, @Nonnull Ref bodyRef) { requireAvailable(); + assert PhysicsProjectionIndexResource.getResourceType() != null; return requireWorldThread(store, "read PhysicsStore generated visual proxy") .getResource(PhysicsProjectionIndexResource.getResourceType()) .getGeneratedVisualProxy(Objects.requireNonNull(bodyRef, "bodyRef")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java index 32bc3971..41ab28ec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityDiagnostics.java @@ -14,10 +14,7 @@ private PhysicsEntityDiagnostics() { @Nonnull public static Snapshot collect(@Nonnull Store store) { - dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityDiagnostics.Snapshot - snapshot = - dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityDiagnostics.collect( - store); + PhysicsEntityDiagnostics.Snapshot snapshot = PhysicsEntityDiagnostics.collect(store); return new Snapshot(snapshot.physicsBodyEntities(), snapshot.persistentPhysicsBodyEntities(), snapshot.physicsVisualEntities(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index 616b9d21..e85036b1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.SystemGroup; import com.hypixel.hytale.component.event.WorldEventType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; @@ -9,7 +8,6 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; import javax.annotation.Nonnull; /** @@ -38,19 +36,12 @@ public static ComponentType bodyAttachment } @Nonnull - public static ComponentType - generatedVisualProxyComponentType() { + public static ComponentType generatedVisualProxyComponentType() { return PhysicsEntityTypeRegistry.generatedVisualProxyComponentType(); } @Nonnull - public static ResourceType physicsWorldResourceType() { - return PhysicsEntityTypeRegistry.physicsWorldResourceType(); - } - - @Nonnull - public static WorldEventType - physicsEventFramePublishedEventType() { + public static WorldEventType physicsEventFramePublishedEventType() { return PhysicsEntityTypeRegistry.physicsEventFramePublishedEventType(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualMaterializationSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualMaterializationSettingsComponent.java index ac245b64..ce0486ab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualMaterializationSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualMaterializationSettingsComponent.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import java.util.Objects; import javax.annotation.Nonnull; @@ -168,10 +167,6 @@ public String getDetachedVisualBlockType() { return detachedVisualBlockType; } - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getVisualMaterializationSettings()); - } - public void copyTo(@Nonnull PhysicsVisualMaterializationSettings target) { target.setDetachedVisualMaterializationEnabled(detachedVisualMaterializationEnabled); target.setDetachedVisualRadii(detachedVisualMaterializationRadius, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java index c9be8527..dc30bac1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/VisualSyncSettingsComponent.java @@ -8,7 +8,6 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import java.util.Objects; @@ -211,10 +210,6 @@ public VisualOcclusionMode getVisualOcclusionMode() { return visualOcclusionMode; } - public void copyTo(@Nonnull PhysicsSpaceSettings settings) { - copyTo(settings.getVisualSyncSettings()); - } - public void copyTo(@Nonnull PhysicsVisualSyncSettings target) { target.setVisualSyncRadii(visualFullSyncRadius, visualMaxSyncRadius); target.setVisualFarSyncCutoffEnabled(visualFarSyncCutoffEnabled); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 8e495221..cecacf36 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -9,8 +9,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.concurrent.CompletionStage; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsAsync.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsAsync.java index f84df80e..b928600e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsAsync.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsAsync.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.server.core.universe.world.World; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java similarity index 92% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java index 0bcf1956..40d08176 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java @@ -1,11 +1,9 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -88,14 +86,12 @@ static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, float distance) { BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyId); return new RaycastHitView(metadata != null ? metadata.bodyRef() : null, - metadata != null ? metadata.bodyType() : PhysicsBodyType.STATIC, pointX, pointY, pointZ, normalX, normalY, normalZ, - metadata != null ? metadata.shapeType() : ShapeType.UNKNOWN, fraction, distance); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodies.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodies.java index 1756e616..e0db0900 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodies.java @@ -1,11 +1,11 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; @@ -159,7 +159,7 @@ public static void destroy(@Nonnull Store store, @Nonnull UUID bodyUuid) { Store checkedStore = requireWorldThread(store, "destroy a PhysicsStore body entity"); - PhysicsStoreTopologyMutations.destroyBody(checkedStore, + PhysicsTopologyMutations.destroyBody(checkedStore, Objects.requireNonNull(bodyUuid, "bodyUuid")); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntities.java similarity index 75% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntities.java index cfe2130f..db2e30d8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsBodyEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntities.java @@ -1,6 +1,8 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; +import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; @@ -21,7 +23,7 @@ import org.joml.Vector3f; /** - * Factories for PhysicsStore body entity descriptors. + * Factories for PhysicsStore body entity holders. */ public final class PhysicsBodyEntities { @@ -29,14 +31,14 @@ private PhysicsBodyEntities() { } @Nonnull - public static BodyEntityDescriptor dynamicBody(@Nonnull Ref spaceRef, + public static Holder dynamicBodyHolder(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - return body(spaceRef, + return bodyHolder(spaceRef, bodyUuid, bodyCenter, shape, @@ -47,7 +49,7 @@ public static BodyEntityDescriptor dynamicBody(@Nonnull Ref spaceR } @Nonnull - public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, + public static Holder bodyHolder(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @@ -55,27 +57,30 @@ public static BodyEntityDescriptor body(@Nonnull Ref spaceRef, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - BodyEntityDescriptor descriptor = bodyWithSpaceUuid(PhysicsEntityRefs.entityUuid(spaceRef), + Store store = requireSpaceStore(spaceRef); + return bodyHolderWithSpaceUuid(store, + PhysicsEntityRefs.entityUuid(spaceRef), bodyUuid, bodyCenter, shape, bodyType, mass, settings, - linearVelocity); - descriptor.body().setSpaceRef(spaceRef); - return descriptor; + linearVelocity, + spaceRef); } @Nonnull - private static BodyEntityDescriptor bodyWithSpaceUuid(@Nonnull UUID spaceUuid, + private static Holder bodyHolderWithSpaceUuid(@Nonnull Store store, + @Nonnull UUID spaceUuid, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, @Nonnull PhysicsBodyType bodyType, float mass, @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { + @Nullable Vector3f linearVelocity, + @Nonnull Ref spaceRef) { Objects.requireNonNull(spaceUuid, "spaceUuid"); Objects.requireNonNull(bodyUuid, "bodyUuid"); Objects.requireNonNull(bodyCenter, "bodyCenter"); @@ -83,19 +88,20 @@ private static BodyEntityDescriptor bodyWithSpaceUuid(@Nonnull UUID spaceUuid, Objects.requireNonNull(bodyType, "bodyType"); Objects.requireNonNull(settings, "settings"); - return BodyEntityDescriptor.of(bodyUuid, - new BodyComponent(spaceUuid), + BodyComponent body = new BodyComponent(spaceUuid); + body.setSpaceRef(spaceRef); + return PhysicsEntities.bodyHolder(store, + bodyUuid, + body, new DynamicsComponent(bodyType, mass, settings.hasLinearDamping() ? settings.linearDamping() : 0.0f, settings.hasAngularDamping() ? settings.angularDamping() : 0.0f, false), initialTarget(bodyCenter, linearVelocity), - bodyUuid, new ColliderComponent(new Vector3f(), new Quaternionf(), settings.hasSensor() && settings.sensor()), - bodyUuid, new ShapeComponent(shape.type(), shape.halfExtentX(), shape.halfExtentY(), @@ -105,13 +111,22 @@ private static BodyEntityDescriptor bodyWithSpaceUuid(@Nonnull UUID spaceUuid, shape.axis(), shape.groundY(), ""), - bodyUuid, new MaterialComponent(settings.hasFriction() ? settings.friction() : 0.5f, settings.hasRestitution() ? settings.restitution() : 0.0f), - bodyUuid, collisionFilter(settings)); } + @Nonnull + private static Store requireSpaceStore(@Nonnull Ref spaceRef) { + Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); + Store store = checkedRef.getStore(); + PhysicsThreading.requireWorldThread(store, "build a PhysicsStore body entity holder"); + if (!checkedRef.isValid()) { + throw new IllegalStateException("PhysicsStore space ref is not valid: " + checkedRef); + } + return store; + } + @Nonnull private static TargetComponent initialTarget(@Nonnull Vector3f bodyCenter, @Nullable Vector3f linearVelocity) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java index de90164b..93a6cc8b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntities.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntities.java index 00a5b21b..dfde45c8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntities.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntityRefs.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntityRefs.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntityRefs.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntityRefs.java index b4489186..e221c914 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsEntityRefs.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntityRefs.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsJointEntities.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsJointEntities.java index fab444a0..246aed13 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsJointEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsJointEntities.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java index 96d3a587..106ca128 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpaces.java similarity index 79% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpaces.java index 264da3a5..114a43b7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpaces.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; @@ -7,8 +7,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreSpaceMutations; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -25,7 +25,6 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -72,15 +71,6 @@ public static SpaceId create(@Nonnull Store store, return spaceId; } - @Nonnull - public static SpaceId create(@Nonnull Store store, - @Nonnull BackendId backendId, - @Nonnull PhysicsSpaceSettings settings) { - SpaceId spaceId = SpaceId.next(); - create(store, UUID.randomUUID(), spaceId, backendId, settings); - return spaceId; - } - @Nonnull public static Ref create(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -88,27 +78,12 @@ public static Ref create(@Nonnull Store store, @Nonnull BackendId backendId) { Store checkedStore = requireWorldThread(store, "create a PhysicsStore space"); - return PhysicsStoreSpaceMutations.addSpace(checkedStore, + return PhysicsSpaceMutations.addSpace(checkedStore, Objects.requireNonNull(spaceUuid, "spaceUuid"), Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(backendId, "backendId")); } - @Nonnull - public static Ref create(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull SpaceId spaceId, - @Nonnull BackendId backendId, - @Nonnull PhysicsSpaceSettings settings) { - Store checkedStore = requireWorldThread(store, - "create a PhysicsStore space"); - return PhysicsStoreSpaceMutations.addSpace(checkedStore, - Objects.requireNonNull(spaceUuid, "spaceUuid"), - Objects.requireNonNull(spaceId, "spaceId"), - Objects.requireNonNull(backendId, "backendId"), - Objects.requireNonNull(settings, "settings")); - } - @Nonnull public static Collection spaceIds(@Nonnull Store store) { Store checkedStore = requireWorldThread(store, "list PhysicsStore spaces"); @@ -123,60 +98,6 @@ public static int count(@Nonnull Store store) { .size(); } - @Nullable - public static PhysicsSpaceSettings settings(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - Ref ref = resolveRef(store, spaceId); - return ref != null ? settings(store, ref) : null; - } - - @Nullable - public static PhysicsSpaceSettings settings(@Nonnull Store store, - @Nonnull Ref spaceRef) { - Store checkedStore = requireWorldThread(store, - "read PhysicsStore space settings"); - Ref checkedRef = Objects.requireNonNull(spaceRef, "spaceRef"); - if (checkedRef.getStore() != checkedStore || !checkedRef.isValid()) { - return null; - } - if (checkedStore.getComponent(checkedRef, SpaceComponent.getComponentType()) == null) { - return null; - } - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - ChunkCollisionSettingsComponent chunkCollisionSettings = checkedStore.getComponent(checkedRef, - ChunkCollisionSettingsComponent.getComponentType()); - if (chunkCollisionSettings != null) { - chunkCollisionSettings.copyTo(settings); - } - SolverSettingsComponent solverSettings = checkedStore.getComponent(checkedRef, - SolverSettingsComponent.getComponentType()); - if (solverSettings != null) { - solverSettings.copyTo(settings); - } - VisualSyncSettingsComponent visualSyncSettings = checkedStore.getComponent(checkedRef, - VisualSyncSettingsComponent.getComponentType()); - if (visualSyncSettings != null) { - visualSyncSettings.copyTo(settings); - } - VisualMaterializationSettingsComponent visualMaterializationSettings = - checkedStore.getComponent(checkedRef, - VisualMaterializationSettingsComponent.getComponentType()); - if (visualMaterializationSettings != null) { - visualMaterializationSettings.copyTo(settings); - } - CollisionLodSettingsComponent collisionLodSettings = checkedStore.getComponent(checkedRef, - CollisionLodSettingsComponent.getComponentType()); - if (collisionLodSettings != null) { - collisionLodSettings.copyTo(settings); - } - ExtensionSettingsComponent extensionSettings = checkedStore.getComponent(checkedRef, - ExtensionSettingsComponent.getComponentType()); - if (extensionSettings != null) { - extensionSettings.copyTo(settings); - } - return settings; - } - @Nullable public static PhysicsChunkCollisionSettings chunkCollisionSettings( @Nonnull Store store, @@ -338,32 +259,12 @@ public static PhysicsExtensionSettings extensionSettings( return settings; } - public static void putSettings(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsSpaceSettings settings) { - Store checkedStore = requireWorldThread(store, - "update PhysicsStore space settings"); - PhysicsStoreSpaceMutations.putSpaceSettings(checkedStore, - Objects.requireNonNull(spaceId, "spaceId"), - Objects.requireNonNull(settings, "settings")); - } - - public static void putSettings(@Nonnull Store store, - @Nonnull Ref spaceRef, - @Nonnull PhysicsSpaceSettings settings) { - Store checkedStore = requireWorldThread(store, - "update PhysicsStore space settings"); - PhysicsStoreSpaceMutations.putSpaceSettings(checkedStore, - Objects.requireNonNull(spaceRef, "spaceRef"), - Objects.requireNonNull(settings, "settings")); - } - public static void putChunkCollisionSettings(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull PhysicsChunkCollisionSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore chunk collision settings"); - PhysicsStoreSpaceMutations.putChunkCollisionSettings(checkedStore, + PhysicsSpaceMutations.putChunkCollisionSettings(checkedStore, Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(settings, "settings")); } @@ -373,7 +274,7 @@ public static void putChunkCollisionSettings(@Nonnull Store store, @Nonnull PhysicsChunkCollisionSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore chunk collision settings"); - PhysicsStoreSpaceMutations.putChunkCollisionSettings(checkedStore, + PhysicsSpaceMutations.putChunkCollisionSettings(checkedStore, Objects.requireNonNull(spaceRef, "spaceRef"), Objects.requireNonNull(settings, "settings")); } @@ -383,7 +284,7 @@ public static void putSolverSettings(@Nonnull Store store, @Nonnull PhysicsSolverSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore solver settings"); - PhysicsStoreSpaceMutations.putSolverSettings(checkedStore, + PhysicsSpaceMutations.putSolverSettings(checkedStore, Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(settings, "settings")); } @@ -393,7 +294,7 @@ public static void putSolverSettings(@Nonnull Store store, @Nonnull PhysicsSolverSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore solver settings"); - PhysicsStoreSpaceMutations.putSolverSettings(checkedStore, + PhysicsSpaceMutations.putSolverSettings(checkedStore, Objects.requireNonNull(spaceRef, "spaceRef"), Objects.requireNonNull(settings, "settings")); } @@ -403,7 +304,7 @@ public static void putVisualSyncSettings(@Nonnull Store store, @Nonnull PhysicsVisualSyncSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore visual sync settings"); - PhysicsStoreSpaceMutations.putVisualSyncSettings(checkedStore, + PhysicsSpaceMutations.putVisualSyncSettings(checkedStore, Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(settings, "settings")); } @@ -413,7 +314,7 @@ public static void putVisualSyncSettings(@Nonnull Store store, @Nonnull PhysicsVisualSyncSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore visual sync settings"); - PhysicsStoreSpaceMutations.putVisualSyncSettings(checkedStore, + PhysicsSpaceMutations.putVisualSyncSettings(checkedStore, Objects.requireNonNull(spaceRef, "spaceRef"), Objects.requireNonNull(settings, "settings")); } @@ -423,7 +324,7 @@ public static void putVisualMaterializationSettings(@Nonnull Store @Nonnull PhysicsVisualMaterializationSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore visual materialization settings"); - PhysicsStoreSpaceMutations.putVisualMaterializationSettings(checkedStore, + PhysicsSpaceMutations.putVisualMaterializationSettings(checkedStore, Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(settings, "settings")); } @@ -433,7 +334,7 @@ public static void putVisualMaterializationSettings(@Nonnull Store @Nonnull PhysicsVisualMaterializationSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore visual materialization settings"); - PhysicsStoreSpaceMutations.putVisualMaterializationSettings(checkedStore, + PhysicsSpaceMutations.putVisualMaterializationSettings(checkedStore, Objects.requireNonNull(spaceRef, "spaceRef"), Objects.requireNonNull(settings, "settings")); } @@ -443,7 +344,7 @@ public static void putCollisionLodSettings(@Nonnull Store store, @Nonnull PhysicsCollisionLodSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore collision LOD settings"); - PhysicsStoreSpaceMutations.putCollisionLodSettings(checkedStore, + PhysicsSpaceMutations.putCollisionLodSettings(checkedStore, Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(settings, "settings")); } @@ -453,7 +354,7 @@ public static void putCollisionLodSettings(@Nonnull Store store, @Nonnull PhysicsCollisionLodSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore collision LOD settings"); - PhysicsStoreSpaceMutations.putCollisionLodSettings(checkedStore, + PhysicsSpaceMutations.putCollisionLodSettings(checkedStore, Objects.requireNonNull(spaceRef, "spaceRef"), Objects.requireNonNull(settings, "settings")); } @@ -463,7 +364,7 @@ public static void putExtensionSettings(@Nonnull Store store, @Nonnull PhysicsExtensionSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore extension settings"); - PhysicsStoreSpaceMutations.putExtensionSettings(checkedStore, + PhysicsSpaceMutations.putExtensionSettings(checkedStore, Objects.requireNonNull(spaceId, "spaceId"), Objects.requireNonNull(settings, "settings")); } @@ -473,7 +374,7 @@ public static void putExtensionSettings(@Nonnull Store store, @Nonnull PhysicsExtensionSettings settings) { Store checkedStore = requireWorldThread(store, "update PhysicsStore extension settings"); - PhysicsStoreSpaceMutations.putExtensionSettings(checkedStore, + PhysicsSpaceMutations.putExtensionSettings(checkedStore, Objects.requireNonNull(spaceRef, "spaceRef"), Objects.requireNonNull(settings, "settings")); } @@ -565,7 +466,7 @@ public static void removeEmpty(@Nonnull Store store, @Nonnull SpaceId spaceId) { Store checkedStore = requireWorldThread(store, "remove an empty PhysicsStore space"); - PhysicsStoreSpaceMutations.removeEmptySpace(checkedStore, + PhysicsSpaceMutations.removeEmptySpace(checkedStore, Objects.requireNonNull(spaceId, "spaceId")); } @@ -573,8 +474,8 @@ public static void removeWithContents(@Nonnull Store store, @Nonnull SpaceId spaceId) { Store checkedStore = requireWorldThread(store, "remove a PhysicsStore space with contents"); - PhysicsStoreTopologyMutations.removeSpaceWithContents(checkedStore, - PhysicsStoreSpaceMutations.requireSpaceUuid(checkedStore, + PhysicsTopologyMutations.removeSpaceWithContents(checkedStore, + PhysicsSpaceMutations.requireSpaceUuid(checkedStore, Objects.requireNonNull(spaceId, "spaceId"))); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsThreading.java similarity index 88% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsThreading.java index f46b5049..59dc9fe6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsThreading.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsThreading.java @@ -1,10 +1,10 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreWorld; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreAsyncCompletions; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsAsyncCompletions; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; import java.util.Objects; @@ -66,7 +66,7 @@ public static CompletionStage executeOnWorldThread(@Nonnull World world, world.execute(task); } } catch (RuntimeException exception) { - PhysicsStoreAsyncCompletions.fail(completion, exception); + PhysicsAsyncCompletions.fail(completion, exception); } return completion.minimalCompletionStage(); } @@ -95,7 +95,7 @@ public static CompletionStage enqueueReadOnWorldThread(@Nonnull World wor world.execute(task); } } catch (RuntimeException exception) { - PhysicsStoreAsyncCompletions.fail(completion, exception); + PhysicsAsyncCompletions.fail(completion, exception); } return completion.minimalCompletionStage(); } @@ -116,7 +116,7 @@ public static CompletionStage callWhenBackendIdleOnWorldThread(@Nonnull W world.execute(task); } } catch (RuntimeException exception) { - PhysicsStoreAsyncCompletions.fail(completion, exception); + PhysicsAsyncCompletions.fail(completion, exception); } return completion.minimalCompletionStage(); } @@ -129,9 +129,9 @@ private static void execute(@Nonnull World world, Store store = store(world); requireWorldThread(store, operation); mutation.accept(store); - PhysicsStoreAsyncCompletions.complete(completion, null); + PhysicsAsyncCompletions.complete(completion, null); } catch (RuntimeException | Error throwable) { - PhysicsStoreAsyncCompletions.fail(completion, throwable); + PhysicsAsyncCompletions.fail(completion, throwable); } } @@ -153,9 +153,9 @@ private static void callWhenBackendIdle(@Nonnull World world, failure)); return; } - PhysicsStoreAsyncCompletions.complete(completion, action.apply(store)); + PhysicsAsyncCompletions.complete(completion, action.apply(store)); } catch (RuntimeException | Error throwable) { - PhysicsStoreAsyncCompletions.fail(completion, throwable); + PhysicsAsyncCompletions.fail(completion, throwable); } } @@ -165,13 +165,13 @@ private static void rescheduleBackendIdleCall(@Nonnull World world, @Nonnull CompletableFuture completion, Throwable failure) { if (failure != null) { - PhysicsStoreAsyncCompletions.fail(completion, failure); + PhysicsAsyncCompletions.fail(completion, failure); return; } try { world.execute(() -> callWhenBackendIdle(world, operation, action, completion)); } catch (RuntimeException exception) { - PhysicsStoreAsyncCompletions.fail(completion, exception); + PhysicsAsyncCompletions.fail(completion, exception); } } @@ -186,13 +186,13 @@ private static void enqueueRead(@Nonnull World world, .enqueueRead(read) .whenComplete((value, failure) -> { if (failure != null) { - PhysicsStoreAsyncCompletions.fail(completion, failure); + PhysicsAsyncCompletions.fail(completion, failure); } else { - PhysicsStoreAsyncCompletions.complete(completion, value); + PhysicsAsyncCompletions.complete(completion, value); } }); } catch (RuntimeException | Error throwable) { - PhysicsStoreAsyncCompletions.fail(completion, throwable); + PhysicsAsyncCompletions.fail(completion, throwable); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsWorlds.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsWorlds.java index e8043d98..48f41742 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsWorlds.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsWorlds.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java deleted file mode 100644 index 0a99c6ad..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physicsstore/BodyEntityDescriptor.java +++ /dev/null @@ -1,72 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; - -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Copied component graph for one PhysicsStore body entity. - */ -public record BodyEntityDescriptor(@Nonnull UUID bodyUuid, - @Nonnull BodyComponent body, - @Nonnull DynamicsComponent dynamics, - @Nullable TargetComponent target, - @Nonnull UUID colliderUuid, - @Nonnull ColliderComponent collider, - @Nonnull UUID shapeUuid, - @Nonnull ShapeComponent shape, - @Nonnull UUID materialUuid, - @Nonnull MaterialComponent material, - @Nonnull UUID filterUuid, - @Nonnull CollisionFilterComponent filter) { - - public BodyEntityDescriptor { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - body = Objects.requireNonNull(body, "body").clone(); - dynamics = Objects.requireNonNull(dynamics, "dynamics").clone(); - target = target != null ? target.clone() : null; - Objects.requireNonNull(colliderUuid, "colliderUuid"); - collider = Objects.requireNonNull(collider, "collider").clone(); - Objects.requireNonNull(shapeUuid, "shapeUuid"); - shape = Objects.requireNonNull(shape, "shape").clone(); - Objects.requireNonNull(materialUuid, "materialUuid"); - material = Objects.requireNonNull(material, "material").clone(); - Objects.requireNonNull(filterUuid, "filterUuid"); - filter = Objects.requireNonNull(filter, "filter").clone(); - } - - @Nonnull - public static BodyEntityDescriptor of(@Nonnull UUID bodyUuid, - @Nonnull BodyComponent body, - @Nonnull DynamicsComponent dynamics, - @Nullable TargetComponent target, - @Nonnull UUID colliderUuid, - @Nonnull ColliderComponent collider, - @Nonnull UUID shapeUuid, - @Nonnull ShapeComponent shape, - @Nonnull UUID materialUuid, - @Nonnull MaterialComponent material, - @Nonnull UUID filterUuid, - @Nonnull CollisionFilterComponent filter) { - return new BodyEntityDescriptor(bodyUuid, - body, - dynamics, - target, - colliderUuid, - collider, - shapeUuid, - shape, - materialUuid, - material, - filterUuid, - filter); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsMutationHandle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsMutationHandle.java deleted file mode 100644 index 14c2d117..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsMutationHandle.java +++ /dev/null @@ -1,127 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.resources; - -import java.util.Objects; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Public completion handle for an asynchronous physics resource mutation. - * - * @param reserved logical value returned to the caller before the mutation runs - */ -public final class PhysicsMutationHandle { - - @Nonnull - private final String operation; - @Nullable - private final T value; - @Nonnull - private final CompletableFuture completion; - - private PhysicsMutationHandle(@Nonnull String operation, - @Nullable T value, - @Nonnull CompletableFuture completion) { - this.operation = Objects.requireNonNull(operation, "operation"); - this.value = value; - this.completion = Objects.requireNonNull(completion, "completion"); - } - - @Nonnull - public static PhysicsMutationHandle completed(@Nonnull String operation, - @Nullable T value) { - return new PhysicsMutationHandle<>(operation, value, CompletableFuture.completedFuture(value)); - } - - @Nonnull - public static PhysicsMutationHandle failed(@Nonnull String operation, - @Nullable T value, - @Nonnull Throwable failure) { - CompletableFuture completion = new CompletableFuture<>(); - completion.completeExceptionally(Objects.requireNonNull(failure, "failure")); - return new PhysicsMutationHandle<>(operation, value, completion); - } - - @Nonnull - public static PhysicsMutationHandle fromCompletion(@Nonnull String operation, - @Nullable T value, - @Nonnull CompletionStage source) { - CompletableFuture completion = Objects.requireNonNull(source, "source") - .thenApply(_ -> value) - .toCompletableFuture(); - return new PhysicsMutationHandle<>(operation, value, completion); - } - - @Nonnull - public String operation() { - return operation; - } - - @Nullable - public T value() { - return value; - } - - @Nonnull - public CompletionStage completion() { - return completion.minimalCompletionStage(); - } - - public boolean isDone() { - return completion.isDone(); - } - - public boolean completedSuccessfully() { - return completion.isDone() - && !completion.isCompletedExceptionally() - && !completion.isCancelled(); - } - - public boolean failed() { - return failure() != null; - } - - @Nullable - public Throwable failure() { - if (!completion.isDone()) { - return null; - } - try { - completion.join(); - return null; - } catch (CompletionException exception) { - return unwrap(exception); - } catch (CancellationException exception) { - return exception; - } - } - - @Nullable - public T join() { - return completion.join(); - } - - public void throwIfFailed() { - Throwable failure = failure(); - switch (failure) { - case null -> { - return; - } - case RuntimeException runtimeException -> throw runtimeException; - case Error error -> throw error; - default -> { - } - } - throw new IllegalStateException("Async physics mutation " + operation + " failed", - failure); - } - - @Nonnull - private static Throwable unwrap(@Nonnull CompletionException exception) { - Throwable cause = exception.getCause(); - return cause != null ? cause : exception; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java deleted file mode 100644 index 601ad785..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/resources/PhysicsWorldResource.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.resources; - -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; -import javax.annotation.Nonnull; - -/** - * Public resource type for the EntityStore-side physics runtime. - * - *

        The concrete Impulse runtime lives in the internal package. Plugin code should use - * {@code world.getPhysicsStore().getStore()} and {@code core.plugin.physicsstore} helpers for - * PhysicsStore ECS reads and writes.

        - */ -public abstract class PhysicsWorldResource implements Resource { - - protected PhysicsWorldResource() { - } - - public static ResourceType getResourceType() { - return PhysicsEntityTypes.physicsWorldResourceType(); - } - - @Nonnull - @Override - public abstract PhysicsWorldResource clone(); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java deleted file mode 100644 index 435f9131..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettings.java +++ /dev/null @@ -1,128 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.settings; - -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import javax.annotation.Nonnull; - -/** - * Per-space configuration aggregate for chunk collision, solver tuning, - * collision LOD, visual sync, and detached visual materialization. - * - *

        Settings are stored on PhysicsStore space entities. New plugin code should create spaces and - * change per-space settings through {@link PhysicsSpaces}; the world-resource space/settings - * methods remain compatibility facades.

        - * - *

        The grouped accessors expose the domain-owned settings objects. Internal - * code should read and mutate the domain group directly instead of adding flat - * shortcut state here.

        - * - *

        Default settings have PhysicsChunk collision disabled ({@link PhysicsChunkCollisionMode#NONE}), - * which keeps Impulse fully opt-in: no chunk-collision bodies are created unless the integrator - * explicitly opts in.

        - */ -public class PhysicsSpaceSettings { - - @Nonnull - private final PhysicsChunkCollisionSettings physicsChunkCollisionSettings; - @Nonnull - private final PhysicsVisualSyncSettings visualSyncSettings; - @Nonnull - private final PhysicsSolverSettings solverSettings; - @Nonnull - private final PhysicsVisualMaterializationSettings visualMaterializationSettings; - @Nonnull - private final PhysicsCollisionLodSettings collisionLodSettings; - @Nonnull - private final PhysicsExtensionSettings extensionSettings; - - public PhysicsSpaceSettings() { - physicsChunkCollisionSettings = new PhysicsChunkCollisionSettings(); - visualSyncSettings = new PhysicsVisualSyncSettings(); - solverSettings = new PhysicsSolverSettings(); - visualMaterializationSettings = new PhysicsVisualMaterializationSettings(); - collisionLodSettings = new PhysicsCollisionLodSettings(); - extensionSettings = new PhysicsExtensionSettings(); - } - - public PhysicsSpaceSettings(@Nonnull PhysicsSpaceSettings settings) { - physicsChunkCollisionSettings = - new PhysicsChunkCollisionSettings(settings.physicsChunkCollisionSettings); - visualSyncSettings = - new PhysicsVisualSyncSettings(settings.visualSyncSettings); - solverSettings = - new PhysicsSolverSettings(settings.solverSettings); - visualMaterializationSettings = - new PhysicsVisualMaterializationSettings(settings.visualMaterializationSettings); - collisionLodSettings = - new PhysicsCollisionLodSettings(settings.collisionLodSettings); - extensionSettings = new PhysicsExtensionSettings(settings.extensionSettings); - } - - /** - * Chunk-collision streaming and chunk-boundary behavior. - */ - @Nonnull - public PhysicsChunkCollisionSettings getPhysicsChunkCollisionSettings() { - return physicsChunkCollisionSettings; - } - - /** - * Server-to-Hytale transform sync sampling for entity-backed and follower visuals. - */ - @Nonnull - public PhysicsVisualSyncSettings getVisualSyncSettings() { - return visualSyncSettings; - } - - /** - * Backend solver and sleep tuning. - */ - @Nonnull - public PhysicsSolverSettings getSolverSettings() { - return solverSettings; - } - - /** - * Generated visual proxies for detached bodies. - */ - @Nonnull - public PhysicsVisualMaterializationSettings getVisualMaterializationSettings() { - return visualMaterializationSettings; - } - - /** - * Distance-based dynamic-body collision LOD. - */ - @Nonnull - public PhysicsCollisionLodSettings getCollisionLodSettings() { - return collisionLodSettings; - } - - /** - * Capability-keyed backend extension settings. - */ - @Nonnull - public PhysicsExtensionSettings getExtensionSettings() { - return extensionSettings; - } - - @Nonnull - public static PhysicsSpaceSettings defaults() { - return new PhysicsSpaceSettings(); - } - - /** - * Convenience factory for a space with streaming PhysicsChunk collision enabled. - */ - @Nonnull - public static PhysicsSpaceSettings streamingPhysicsChunk() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - settings.getPhysicsChunkCollisionSettings() - .setMode(PhysicsChunkCollisionMode.STREAMING); - return settings; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java index bd8672d7..85901097 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.plugin.settings; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventCollectionMode; import lombok.Getter; import java.util.Objects; import javax.annotation.Nonnull; @@ -9,7 +10,7 @@ * *

        These values control how the world schedules and subdivides physics steps. They are not * per-space solver tuning; {@link PhysicsSolverSettings} remains part of - * {@link PhysicsSpaceSettings} because solver parameters are applied to individual backend spaces.

        + * per-space solver settings because solver parameters are applied to individual backend spaces.

        */ public class PhysicsWorldSettings { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java index 711ae41a..6440e8bd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java @@ -1,52 +1,59 @@ package dev.hytalemodding.impulse.core.plugin.simulation.view; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; /** - * Copied raycast geometry plus the PhysicsStore entity ref hit by the backend. + * Copied raycast geometry plus the PhysicsStore body ref hit by the backend. */ public record RaycastHitView(@Nullable Ref bodyRef, - @Nonnull PhysicsBodyType bodyType, float pointX, float pointY, float pointZ, float normalX, float normalY, float normalZ, - @Nonnull ShapeType shapeType, float fraction, float distance) { public RaycastHitView(@Nullable Ref bodyRef, - @Nonnull PhysicsBodyType bodyType, @Nonnull Vector3f point, @Nonnull Vector3f normal, - @Nonnull ShapeType shapeType, float fraction, float distance) { this(bodyRef, - bodyType, Objects.requireNonNull(point, "point").x, point.y, point.z, Objects.requireNonNull(normal, "normal").x, normal.y, normal.z, - shapeType, fraction, distance); } public RaycastHitView { - Objects.requireNonNull(bodyType, "bodyType"); - Objects.requireNonNull(shapeType, "shapeType"); + } + + @Nonnull + public PhysicsBodyType bodyType() { + DynamicsComponent dynamics = dynamicsComponent(bodyRef); + return dynamics != null ? dynamics.getBodyType() : PhysicsBodyType.STATIC; + } + + @Nonnull + public ShapeType shapeType() { + ShapeComponent shape = shapeComponent(bodyRef); + return shape != null ? shape.getShapeType() : ShapeType.UNKNOWN; } @Nonnull @@ -68,4 +75,26 @@ public Vector3f copyPointTo(@Nonnull Vector3f target) { public Vector3f copyNormalTo(@Nonnull Vector3f target) { return Objects.requireNonNull(target, "target").set(normalX, normalY, normalZ); } + + @Nullable + private static DynamicsComponent dynamicsComponent(@Nullable Ref bodyRef) { + if (bodyRef == null || !bodyRef.isValid()) { + return null; + } + Store store = bodyRef.getStore(); + return store != null + ? store.getComponentConcurrent(bodyRef, DynamicsComponent.getComponentType()) + : null; + } + + @Nullable + private static ShapeComponent shapeComponent(@Nullable Ref bodyRef) { + if (bodyRef == null || !bodyRef.isValid()) { + return null; + } + Store store = bodyRef.getStore(); + return store != null + ? store.getComponentConcurrent(bodyRef, ShapeComponent.getComponentType()) + : null; + } } diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 7909c100..3fe98646 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -4,7 +4,6 @@ requires static jsr305; requires static crucible; - exports dev.hytalemodding.impulse.core.plugin.body; exports dev.hytalemodding.impulse.core.plugin.codec; exports dev.hytalemodding.impulse.core.plugin.components; exports dev.hytalemodding.impulse.core.plugin.events; @@ -16,8 +15,7 @@ exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components; exports dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; exports dev.hytalemodding.impulse.core.plugin.persistence; - exports dev.hytalemodding.impulse.core.plugin.physicsstore; - exports dev.hytalemodding.impulse.core.plugin.resources; + exports dev.hytalemodding.impulse.core.plugin.physics; exports dev.hytalemodding.impulse.core.plugin.settings; exports dev.hytalemodding.impulse.core.plugin.simulation; exports dev.hytalemodding.impulse.core.plugin.simulation.view; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java index 253525f4..fec06153 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/ComponentGranularityStoreProbeTest.java @@ -96,6 +96,10 @@ private static ScenarioResult run(@Nonnull String name, long checksum = iterate(store, required); long iterateNanos = System.nanoTime() - iterateStart; + long targetedIterateStart = System.nanoTime(); + long targetedChecksum = iterate(store, List.of(required.getFirst())); + long targetedIterateNanos = System.nanoTime() - targetedIterateStart; + long mutateStart = System.nanoTime(); replaceComponent(store, required.getFirst(), refs); long mutateNanos = System.nanoTime() - mutateStart; @@ -103,19 +107,32 @@ private static ScenarioResult run(@Nonnull String name, int entityCount = store.getEntityCount(); int archetypeChunkCount = store.getArchetypeChunkCount(); int archetypeDataCount = store.collectArchetypeChunkData().length; + int requiredPayloadInts = required.stream().mapToInt(ComponentSpec::payloadInts).sum(); + long optionalInstances = optional != null + ? ((long) (entities - 1) / OPTIONAL_STRIDE) + 1L + : 0L; + long componentInstances = (long) entities * required.size() + optionalInstances; + long payloadBytes = ((long) entities * requiredPayloadInts + + optionalInstances * (optional != null ? optional.payloadInts() : 0)) + * Integer.BYTES; return new ScenarioResult(name, entities, entityCount, required.size(), optionalFragmentation ? 1 : 0, + requiredPayloadInts, + componentInstances, + payloadBytes, Math.max(0L, heapAfterAdd - heapBefore), addNanos, iterateNanos, + targetedIterateNanos, mutateNanos, archetypeChunkCount, archetypeDataCount, - checksum); + checksum, + targetedChecksum); } finally { registry.removeStore(store); registry.shutdown(); @@ -200,16 +217,17 @@ private static Query query( @Nonnull private static ComponentSpec optional( @Nonnull ComponentRegistry registry) { - return spec(registry, OptionalComponent.class, OptionalComponent::new); + return spec(registry, OptionalComponent.class, OptionalComponent::new, 1); } @Nonnull private static ComponentSpec spec( @Nonnull ComponentRegistry registry, @Nonnull Class typeClass, - @Nonnull IntFunction factory) { + @Nonnull IntFunction factory, + int payloadInts) { return new ComponentSpec<>(registry.registerComponent(typeClass, - () -> factory.apply(0)), factory); + () -> factory.apply(0)), factory, payloadInts); } private static int intProperty(@Nonnull String name, int defaultValue) { @@ -251,7 +269,10 @@ private enum ComponentLayout { @Override List> registerRequired( @Nonnull ComponentRegistry registry) { - return List.of(spec(registry, GroupedComponent.class, GroupedComponent::new)); + return List.of(spec(registry, + GroupedComponent.class, + GroupedComponent::new, + 35)); } }, DOMAIN_SPLIT { @@ -260,13 +281,13 @@ List> registerRequired( List> registerRequired( @Nonnull ComponentRegistry registry) { return List.of( - spec(registry, DomainComponentA.class, DomainComponentA::new), - spec(registry, DomainComponentB.class, DomainComponentB::new), - spec(registry, DomainComponentC.class, DomainComponentC::new), - spec(registry, DomainComponentD.class, DomainComponentD::new), - spec(registry, DomainComponentE.class, DomainComponentE::new), - spec(registry, DomainComponentF.class, DomainComponentF::new), - spec(registry, DomainComponentG.class, DomainComponentG::new)); + spec(registry, DomainComponentA.class, DomainComponentA::new, 5), + spec(registry, DomainComponentB.class, DomainComponentB::new, 5), + spec(registry, DomainComponentC.class, DomainComponentC::new, 5), + spec(registry, DomainComponentD.class, DomainComponentD::new, 5), + spec(registry, DomainComponentE.class, DomainComponentE::new, 5), + spec(registry, DomainComponentF.class, DomainComponentF::new, 5), + spec(registry, DomainComponentG.class, DomainComponentG::new, 5)); } }, TINY { @@ -275,36 +296,41 @@ List> registerRequired( List> registerRequired( @Nonnull ComponentRegistry registry) { return List.of( - spec(registry, TinyComponent01.class, TinyComponent01::new), - spec(registry, TinyComponent02.class, TinyComponent02::new), - spec(registry, TinyComponent03.class, TinyComponent03::new), - spec(registry, TinyComponent04.class, TinyComponent04::new), - spec(registry, TinyComponent05.class, TinyComponent05::new), - spec(registry, TinyComponent06.class, TinyComponent06::new), - spec(registry, TinyComponent07.class, TinyComponent07::new), - spec(registry, TinyComponent08.class, TinyComponent08::new), - spec(registry, TinyComponent09.class, TinyComponent09::new), - spec(registry, TinyComponent10.class, TinyComponent10::new), - spec(registry, TinyComponent11.class, TinyComponent11::new), - spec(registry, TinyComponent12.class, TinyComponent12::new), - spec(registry, TinyComponent13.class, TinyComponent13::new), - spec(registry, TinyComponent14.class, TinyComponent14::new), - spec(registry, TinyComponent15.class, TinyComponent15::new), - spec(registry, TinyComponent16.class, TinyComponent16::new), - spec(registry, TinyComponent17.class, TinyComponent17::new), - spec(registry, TinyComponent18.class, TinyComponent18::new), - spec(registry, TinyComponent19.class, TinyComponent19::new), - spec(registry, TinyComponent20.class, TinyComponent20::new), - spec(registry, TinyComponent21.class, TinyComponent21::new), - spec(registry, TinyComponent22.class, TinyComponent22::new), - spec(registry, TinyComponent23.class, TinyComponent23::new), - spec(registry, TinyComponent24.class, TinyComponent24::new), - spec(registry, TinyComponent25.class, TinyComponent25::new), - spec(registry, TinyComponent26.class, TinyComponent26::new), - spec(registry, TinyComponent27.class, TinyComponent27::new), - spec(registry, TinyComponent28.class, TinyComponent28::new), - spec(registry, TinyComponent29.class, TinyComponent29::new), - spec(registry, TinyComponent30.class, TinyComponent30::new)); + spec(registry, TinyComponent01.class, TinyComponent01::new, 1), + spec(registry, TinyComponent02.class, TinyComponent02::new, 1), + spec(registry, TinyComponent03.class, TinyComponent03::new, 1), + spec(registry, TinyComponent04.class, TinyComponent04::new, 1), + spec(registry, TinyComponent05.class, TinyComponent05::new, 1), + spec(registry, TinyComponent06.class, TinyComponent06::new, 1), + spec(registry, TinyComponent07.class, TinyComponent07::new, 1), + spec(registry, TinyComponent08.class, TinyComponent08::new, 1), + spec(registry, TinyComponent09.class, TinyComponent09::new, 1), + spec(registry, TinyComponent10.class, TinyComponent10::new, 1), + spec(registry, TinyComponent11.class, TinyComponent11::new, 1), + spec(registry, TinyComponent12.class, TinyComponent12::new, 1), + spec(registry, TinyComponent13.class, TinyComponent13::new, 1), + spec(registry, TinyComponent14.class, TinyComponent14::new, 1), + spec(registry, TinyComponent15.class, TinyComponent15::new, 1), + spec(registry, TinyComponent16.class, TinyComponent16::new, 1), + spec(registry, TinyComponent17.class, TinyComponent17::new, 1), + spec(registry, TinyComponent18.class, TinyComponent18::new, 1), + spec(registry, TinyComponent19.class, TinyComponent19::new, 1), + spec(registry, TinyComponent20.class, TinyComponent20::new, 1), + spec(registry, TinyComponent21.class, TinyComponent21::new, 1), + spec(registry, TinyComponent22.class, TinyComponent22::new, 1), + spec(registry, TinyComponent23.class, TinyComponent23::new, 1), + spec(registry, TinyComponent24.class, TinyComponent24::new, 1), + spec(registry, TinyComponent25.class, TinyComponent25::new, 1), + spec(registry, TinyComponent26.class, TinyComponent26::new, 1), + spec(registry, TinyComponent27.class, TinyComponent27::new, 1), + spec(registry, TinyComponent28.class, TinyComponent28::new, 1), + spec(registry, TinyComponent29.class, TinyComponent29::new, 1), + spec(registry, TinyComponent30.class, TinyComponent30::new, 1), + spec(registry, TinyComponent31.class, TinyComponent31::new, 1), + spec(registry, TinyComponent32.class, TinyComponent32::new, 1), + spec(registry, TinyComponent33.class, TinyComponent33::new, 1), + spec(registry, TinyComponent34.class, TinyComponent34::new, 1), + spec(registry, TinyComponent35.class, TinyComponent35::new, 1)); } }; @@ -315,7 +341,8 @@ abstract List> registerRequired( private record ComponentSpec( @Nonnull ComponentType type, - @Nonnull IntFunction factory) { + @Nonnull IntFunction factory, + int payloadInts) { } private record ScenarioResult(@Nonnull String name, @@ -323,13 +350,18 @@ private record ScenarioResult(@Nonnull String name, int storeEntityCount, int requiredComponents, int optionalComponents, + int requiredPayloadInts, + long componentInstances, + long payloadBytes, long heapBytes, long addNanos, long iterateNanos, + long targetedIterateNanos, long mutateNanos, int archetypeChunkCount, int archetypeDataCount, - long iterationChecksum) { + long iterationChecksum, + long targetedIterationChecksum) { @Nonnull static String tsv(@Nonnull List results) { @@ -338,14 +370,23 @@ static String tsv(@Nonnull List results) { "entities", "requiredComponents", "optionalComponents", + "requiredPayloadInts", + "componentInstances", + "payloadBytes", + "payloadBytesPerEntity", "heapBytes", "bytesPerEntity", + "measuredOverheadBytes", + "overheadBytesPerEntity", + "bytesPerComponentInstance", "addNsPerEntity", "iterateNsPerEntity", + "targetedIterateNsPerEntity", "mutateNsPerEntity", "archetypeChunkCount", "archetypeDataCount", - "iterationChecksum"); + "iterationChecksum", + "targetedIterationChecksum"); String rows = results.stream() .map(ScenarioResult::tsvRow) .collect(Collectors.joining(System.lineSeparator())); @@ -359,14 +400,23 @@ private String tsvRow() { Integer.toString(entities), Integer.toString(requiredComponents), Integer.toString(optionalComponents), + Integer.toString(requiredPayloadInts), + Long.toString(componentInstances), + Long.toString(payloadBytes), + Long.toString(payloadBytes / Math.max(1, entities)), Long.toString(heapBytes), Long.toString(heapBytes / Math.max(1, entities)), + Long.toString(Math.max(0L, heapBytes - payloadBytes)), + Long.toString(Math.max(0L, heapBytes - payloadBytes) / Math.max(1, entities)), + Long.toString(heapBytes / Math.max(1L, componentInstances)), Long.toString(addNanos / Math.max(1, entities)), Long.toString(iterateNanos / Math.max(1, entities)), + Long.toString(targetedIterateNanos / Math.max(1, entities)), Long.toString(mutateNanos / Math.max(1, entities)), Integer.toString(archetypeChunkCount), Integer.toString(archetypeDataCount), - Long.toString(iterationChecksum)); + Long.toString(iterationChecksum), + Long.toString(targetedIterationChecksum)); } } @@ -380,15 +430,7 @@ private interface ProbeComponent extends Component { private abstract static class BaseComponent implements ProbeComponent { - private final int seed; - - private BaseComponent(int seed) { - this.seed = seed; - } - - protected final int seed() { - return seed; - } + protected abstract int seed(); @Override public abstract BaseComponent clone(); @@ -396,45 +438,140 @@ protected final int seed() { private abstract static class GroupedBase extends BaseComponent { + private final int value00; + private final int value01; + private final int value02; + private final int value03; + private final int value04; + private final int value05; + private final int value06; + private final int value07; + private final int value08; + private final int value09; + private final int value10; + private final int value11; + private final int value12; + private final int value13; + private final int value14; + private final int value15; + private final int value16; + private final int value17; + private final int value18; + private final int value19; + private final int value20; + private final int value21; + private final int value22; + private final int value23; + private final int value24; + private final int value25; + private final int value26; + private final int value27; + private final int value28; + private final int value29; + private final int value30; + private final int value31; + private final int value32; + private final int value33; + private final int value34; + private GroupedBase(int seed) { - super(seed); + value00 = seed; + value01 = seed + 1; + value02 = seed + 2; + value03 = seed + 3; + value04 = seed + 4; + value05 = seed + 5; + value06 = seed + 6; + value07 = seed + 7; + value08 = seed + 8; + value09 = seed + 9; + value10 = seed + 10; + value11 = seed + 11; + value12 = seed + 12; + value13 = seed + 13; + value14 = seed + 14; + value15 = seed + 15; + value16 = seed + 16; + value17 = seed + 17; + value18 = seed + 18; + value19 = seed + 19; + value20 = seed + 20; + value21 = seed + 21; + value22 = seed + 22; + value23 = seed + 23; + value24 = seed + 24; + value25 = seed + 25; + value26 = seed + 26; + value27 = seed + 27; + value28 = seed + 28; + value29 = seed + 29; + value30 = seed + 30; + value31 = seed + 31; + value32 = seed + 32; + value33 = seed + 33; + value34 = seed + 34; + } + + @Override + protected int seed() { + return value00; } @Override public long checksum() { - long sum = 0L; - for (int i = 0; i < 30; i++) { - sum += seed() + i; - } - return sum; + return (long) value00 + value01 + value02 + value03 + value04 + + value05 + value06 + value07 + value08 + value09 + + value10 + value11 + value12 + value13 + value14 + + value15 + value16 + value17 + value18 + value19 + + value20 + value21 + value22 + value23 + value24 + + value25 + value26 + value27 + value28 + value29 + + value30 + value31 + value32 + value33 + value34; } } private abstract static class DomainBase extends BaseComponent { + private final int value0; + private final int value1; + private final int value2; + private final int value3; + private final int value4; + private DomainBase(int seed) { - super(seed); + value0 = seed; + value1 = seed + 1; + value2 = seed + 2; + value3 = seed + 3; + value4 = seed + 4; + } + + @Override + protected int seed() { + return value0; } @Override public long checksum() { - long sum = 0L; - for (int i = 0; i < 5; i++) { - sum += seed() + i; - } - return sum; + return (long) value0 + value1 + value2 + value3 + value4; } } private abstract static class TinyBase extends BaseComponent { + private final int value; + private TinyBase(int seed) { - super(seed); + value = seed; + } + + @Override + protected int seed() { + return value; } @Override public long checksum() { - return seed(); + return value; } } @@ -632,4 +769,29 @@ private static final class TinyComponent30 extends TinyBase { private TinyComponent30(int seed) { super(seed); } @Override public TinyComponent30 clone() { return new TinyComponent30(seed()); } } + + private static final class TinyComponent31 extends TinyBase { + private TinyComponent31(int seed) { super(seed); } + @Override public TinyComponent31 clone() { return new TinyComponent31(seed()); } + } + + private static final class TinyComponent32 extends TinyBase { + private TinyComponent32(int seed) { super(seed); } + @Override public TinyComponent32 clone() { return new TinyComponent32(seed()); } + } + + private static final class TinyComponent33 extends TinyBase { + private TinyComponent33(int seed) { super(seed); } + @Override public TinyComponent33 clone() { return new TinyComponent33(seed()); } + } + + private static final class TinyComponent34 extends TinyBase { + private TinyComponent34(int seed) { super(seed); } + @Override public TinyComponent34 clone() { return new TinyComponent34(seed()); } + } + + private static final class TinyComponent35 extends TinyBase { + private TinyComponent35(int seed) { super(seed); } + @Override public TinyComponent35 clone() { return new TinyComponent35(seed()); } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/IdentityFootprintProbeTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/IdentityFootprintProbeTest.java new file mode 100644 index 00000000..89eaec2e --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/benchmark/IdentityFootprintProbeTest.java @@ -0,0 +1,461 @@ +package dev.hytalemodding.impulse.core.internal.benchmark; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class IdentityFootprintProbeTest { + + private static final int DEFAULT_ENTITY_COUNT = 10_000; + private static final int DEFAULT_LOOKUP_PASSES = 5; + private static final long GENERATION = 1L; + private static final long SLOT_MASK = 0x0000_FFFF_FFFF_FFFFL; + + @Test + void reportsUuidAndGenerationalIdentityCosts() throws IOException { + int entityCount = intProperty("impulse.identityProbe.entityCount", + DEFAULT_ENTITY_COUNT); + int lookupPasses = intProperty("impulse.identityProbe.lookupPasses", + DEFAULT_LOOKUP_PASSES); + + List results = List.of( + runUuid("uuid-component-only", entityCount, lookupPasses, false), + runGenerationalId("generational-id-component-only", + entityCount, + lookupPasses, + false), + runUuid("uuid-component-object-index", entityCount, lookupPasses, true), + runGenerationalId("generational-id-component-primitive-index", + entityCount, + lookupPasses, + true)); + + Path report = Path.of("build", + "reports", + "impulse", + "identity-footprint-probe.tsv"); + Files.createDirectories(report.getParent()); + Files.writeString(report, ScenarioResult.tsv(results)); + + assertEquals(4, results.size()); + for (ScenarioResult result : results) { + assertEquals(result.entities(), result.storeEntityCount(), result.name()); + assertTrue(result.archetypeChunkCount() > 0, result.name()); + assertNotEquals(0L, result.scanChecksum(), result.name()); + if (!"none".equals(result.indexKind())) { + assertNotEquals(0L, result.lookupChecksum(), result.name()); + } + } + } + + @Nonnull + private static ScenarioResult runUuid(@Nonnull String name, + int entities, + int lookupPasses, + boolean indexed) { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentType type = + registry.registerComponent(UuidIdentityComponent.class, + () -> new UuidIdentityComponent(uuidFor(0))); + Store store = registry.addStore(new BenchmarkWorld(name), + EmptyResourceStorage.get()); + try { + forceGc(); + long heapBefore = usedHeap(); + long buildStart = System.nanoTime(); + Ref[] refs = addUuidRows(store, registry, type, entities); + Object2ObjectOpenHashMap> index = + indexed ? uuidIndex(refs) : null; + long buildNanos = System.nanoTime() - buildStart; + forceGc(); + long heapAfterBuild = usedHeap(); + + ScanResult scan = scanUuid(store, type); + LookupResult lookup = index != null + ? lookupUuid(index, entities, lookupPasses) + : LookupResult.EMPTY; + + return result(name, + entities, + store, + "uuid", + indexed ? "Object2ObjectOpenHashMap" : "none", + heapAfterBuild - heapBefore, + buildNanos, + scan, + lookup); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static ScenarioResult runGenerationalId(@Nonnull String name, + int entities, + int lookupPasses, + boolean indexed) { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentType type = + registry.registerComponent(GenerationalIdComponent.class, + () -> new GenerationalIdComponent(generationalId(0))); + Store store = registry.addStore(new BenchmarkWorld(name), + EmptyResourceStorage.get()); + try { + forceGc(); + long heapBefore = usedHeap(); + long buildStart = System.nanoTime(); + Ref[] refs = addGenerationalRows(store, registry, type, entities); + Long2ObjectOpenHashMap> index = + indexed ? generationalIndex(refs) : null; + long buildNanos = System.nanoTime() - buildStart; + forceGc(); + long heapAfterBuild = usedHeap(); + + ScanResult scan = scanGenerationalId(store, type); + LookupResult lookup = index != null + ? lookupGenerationalId(index, entities, lookupPasses) + : LookupResult.EMPTY; + + return result(name, + entities, + store, + "generational-long", + indexed ? "Long2ObjectOpenHashMap" : "none", + heapAfterBuild - heapBefore, + buildNanos, + scan, + lookup); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static ScenarioResult result(@Nonnull String name, + int entities, + @Nonnull Store store, + @Nonnull String componentKind, + @Nonnull String indexKind, + long heapBytes, + long buildNanos, + @Nonnull ScanResult scan, + @Nonnull LookupResult lookup) { + return new ScenarioResult(name, + entities, + store.getEntityCount(), + componentKind, + indexKind, + Math.max(0L, heapBytes), + buildNanos, + scan.nanos(), + lookup.nanos(), + lookup.count(), + store.getArchetypeChunkCount(), + store.collectArchetypeChunkData().length, + scan.checksum(), + lookup.checksum()); + } + + @Nonnull + private static Ref[] addUuidRows(@Nonnull Store store, + @Nonnull ComponentRegistry registry, + @Nonnull ComponentType type, + int entities) { + @SuppressWarnings("unchecked") + Holder[] holders = new Holder[entities]; + for (int entity = 0; entity < entities; entity++) { + Holder holder = registry.newHolder(); + holder.addComponent(type, new UuidIdentityComponent(uuidFor(entity))); + holders[entity] = holder; + } + return store.addEntities(holders, AddReason.SPAWN); + } + + @Nonnull + private static Ref[] addGenerationalRows( + @Nonnull Store store, + @Nonnull ComponentRegistry registry, + @Nonnull ComponentType type, + int entities) { + @SuppressWarnings("unchecked") + Holder[] holders = new Holder[entities]; + for (int entity = 0; entity < entities; entity++) { + Holder holder = registry.newHolder(); + holder.addComponent(type, new GenerationalIdComponent(generationalId(entity))); + holders[entity] = holder; + } + return store.addEntities(holders, AddReason.SPAWN); + } + + @Nonnull + private static Object2ObjectOpenHashMap> uuidIndex( + @Nonnull Ref[] refs) { + Object2ObjectOpenHashMap> index = + new Object2ObjectOpenHashMap<>(refs.length); + for (int entity = 0; entity < refs.length; entity++) { + index.put(uuidFor(entity), refs[entity]); + } + return index; + } + + @Nonnull + private static Long2ObjectOpenHashMap> generationalIndex( + @Nonnull Ref[] refs) { + Long2ObjectOpenHashMap> index = + new Long2ObjectOpenHashMap<>(refs.length); + for (int entity = 0; entity < refs.length; entity++) { + index.put(generationalId(entity), refs[entity]); + } + return index; + } + + @Nonnull + private static ScanResult scanUuid(@Nonnull Store store, + @Nonnull ComponentType type) { + long start = System.nanoTime(); + long[] checksum = {0L}; + BiConsumer, CommandBuffer> consumer = + (chunk, _) -> { + for (int index = 0; index < chunk.size(); index++) { + checksum[0] += chunk.getComponent(index, type).checksum(); + } + }; + store.forEachChunk(type, consumer); + return new ScanResult(System.nanoTime() - start, checksum[0]); + } + + @Nonnull + private static ScanResult scanGenerationalId(@Nonnull Store store, + @Nonnull ComponentType type) { + long start = System.nanoTime(); + long[] checksum = {0L}; + BiConsumer, CommandBuffer> consumer = + (chunk, _) -> { + for (int index = 0; index < chunk.size(); index++) { + checksum[0] += chunk.getComponent(index, type).checksum(); + } + }; + store.forEachChunk(type, consumer); + return new ScanResult(System.nanoTime() - start, checksum[0]); + } + + @Nonnull + private static LookupResult lookupUuid( + @Nonnull Object2ObjectOpenHashMap> index, + int entities, + int passes) { + UUID[] keys = new UUID[entities]; + for (int entity = 0; entity < entities; entity++) { + keys[entity] = uuidFor(entity); + } + long checksum = 0L; + long start = System.nanoTime(); + for (int pass = 0; pass < passes; pass++) { + for (UUID key : keys) { + Ref ref = index.get(key); + if (ref == null) { + throw new AssertionError("Missing UUID index entry: " + key); + } + checksum += ref.getIndex(); + } + } + return new LookupResult(System.nanoTime() - start, (long) entities * passes, checksum); + } + + @Nonnull + private static LookupResult lookupGenerationalId( + @Nonnull Long2ObjectOpenHashMap> index, + int entities, + int passes) { + long[] keys = new long[entities]; + for (int entity = 0; entity < entities; entity++) { + keys[entity] = generationalId(entity); + } + long checksum = 0L; + long start = System.nanoTime(); + for (int pass = 0; pass < passes; pass++) { + for (long key : keys) { + Ref ref = index.get(key); + if (ref == null) { + throw new AssertionError("Missing generational id index entry: " + key); + } + checksum += ref.getIndex(); + } + } + return new LookupResult(System.nanoTime() - start, (long) entities * passes, checksum); + } + + @Nonnull + private static UUID uuidFor(int index) { + return new UUID(0x496d_7075_6c73_6500L, index + 1L); + } + + private static long generationalId(int index) { + return (GENERATION << 48) | (index & SLOT_MASK); + } + + private static int intProperty(@Nonnull String name, int defaultValue) { + Integer propertyValue = Integer.getInteger(name); + if (propertyValue != null) { + return propertyValue; + } + String environmentName = name.replaceAll("([a-z0-9])([A-Z])", "$1_$2") + .toUpperCase() + .replace('.', '_') + .replace('-', '_'); + String environmentValue = System.getenv(environmentName); + if (environmentValue == null || environmentValue.isBlank()) { + return defaultValue; + } + return Integer.parseInt(environmentValue); + } + + private static long usedHeap() { + Runtime runtime = Runtime.getRuntime(); + return runtime.totalMemory() - runtime.freeMemory(); + } + + private static void forceGc() { + for (int i = 0; i < 3; i++) { + System.gc(); + try { + Thread.sleep(10L); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + return; + } + } + } + + private record ScanResult(long nanos, long checksum) { + } + + private record LookupResult(long nanos, long count, long checksum) { + + private static final LookupResult EMPTY = new LookupResult(0L, 0L, 0L); + } + + private record ScenarioResult(@Nonnull String name, + int entities, + int storeEntityCount, + @Nonnull String componentKind, + @Nonnull String indexKind, + long heapBytes, + long buildNanos, + long scanNanos, + long lookupNanos, + long lookupCount, + int archetypeChunkCount, + int archetypeDataCount, + long scanChecksum, + long lookupChecksum) { + + @Nonnull + static String tsv(@Nonnull List results) { + String header = String.join("\t", + "scenario", + "entities", + "componentKind", + "indexKind", + "heapBytes", + "bytesPerEntity", + "buildNsPerEntity", + "scanNsPerEntity", + "lookupNsPerLookup", + "lookupCount", + "archetypeChunkCount", + "archetypeDataCount", + "scanChecksum", + "lookupChecksum"); + String rows = results.stream() + .map(ScenarioResult::tsvRow) + .collect(Collectors.joining(System.lineSeparator())); + return header + System.lineSeparator() + rows + System.lineSeparator(); + } + + @Nonnull + private String tsvRow() { + return String.join("\t", + name, + Integer.toString(entities), + componentKind, + indexKind, + Long.toString(heapBytes), + Long.toString(heapBytes / Math.max(1, entities)), + Long.toString(buildNanos / Math.max(1, entities)), + Long.toString(scanNanos / Math.max(1, entities)), + Long.toString(lookupNanos / Math.max(1L, lookupCount)), + Long.toString(lookupCount), + Integer.toString(archetypeChunkCount), + Integer.toString(archetypeDataCount), + Long.toString(scanChecksum), + Long.toString(lookupChecksum)); + } + } + + private record BenchmarkWorld(@Nonnull String name) { + } + + private static final class UuidIdentityComponent implements Component { + + @Nonnull + private final UUID uuid; + + private UuidIdentityComponent(@Nonnull UUID uuid) { + this.uuid = uuid; + } + + private long checksum() { + return uuid.getMostSignificantBits() ^ uuid.getLeastSignificantBits(); + } + + @Nonnull + @Override + public UuidIdentityComponent clone() { + return new UuidIdentityComponent(uuid); + } + } + + private static final class GenerationalIdComponent implements Component { + + private final long id; + + private GenerationalIdComponent(long id) { + this.id = id; + } + + private long checksum() { + return id; + } + + @Nonnull + @Override + public GenerationalIdComponent clone() { + return new GenerationalIdComponent(id); + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java index d45e5b6c..b2e323e9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -20,7 +20,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java index 652400df..fab2c4c7 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java @@ -8,13 +8,17 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.EmptyResourceStorage; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import java.lang.reflect.Method; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -56,15 +60,22 @@ void disablingLifecycleWithoutRegisteredSessionComponentDoesNotThrow() { @Test void disablingLifecycleClearsRegisteredControlledBodies() { ControlLifecycle.enable(); - PhysicsWorldRuntimeResource resource = new PhysicsWorldRuntimeResource(); - Ref bodyRef = new TestPhysicsRef(7); - resource.markBodyControlled(bodyRef); + World world = TestInstanceFactory.world("control-physics-world"); + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = registry.addStore( + new PhysicsStore(world), + EmptyResourceStorage.get()); + Ref bodyRef = new TestPhysicsRef(store, 7); - assertTrue(resource.isBodyControlled(bodyRef)); + runOnWorldThread(world, () -> { + PhysicsControlRuntimeStates.markControlled(bodyRef); + assertTrue(PhysicsControlRuntimeStates.isControlled(bodyRef)); + }); ControlLifecycle.disable(); - assertFalse(resource.isBodyControlled(bodyRef)); + runOnWorldThread(world, () -> assertFalse(PhysicsControlRuntimeStates.isControlled(bodyRef))); + registry.shutdown(); } @Test @@ -102,8 +113,8 @@ void disablingLifecycleSkipsStoresWhoseWorldThreadHasStopped() { private static final class TestPhysicsRef extends Ref { - private TestPhysicsRef(int index) { - super(null, index); + private TestPhysicsRef(Store store, int index) { + super(store, index); } @Override @@ -111,4 +122,23 @@ public boolean isValid() { return true; } } + + private static void runOnWorldThread(@Nonnull World world, @Nonnull Runnable task) { + setThread(world, Thread.currentThread()); + try { + task.run(); + } finally { + setThread(world, null); + } + } + + private static void setThread(@Nonnull World world, @Nullable Thread thread) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(world, thread); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Failed to bind test world thread", exception); + } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java deleted file mode 100644 index c031e772..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkRegistrationOwnershipTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.physicschunk; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; - -class PhysicsChunkRegistrationOwnershipTest { - - @Test - void physicsChunkSubPluginOwnsPhysicsStoreRegistrations() throws IOException { - String coreRegistration = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java")); - String chunkSubPlugin = Files.readString(Path.of( - "src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java")); - - assertFalse(coreRegistration.contains("PhysicsChunkStoreTypes.register")); - assertTrue(chunkSubPlugin.contains("PhysicsStoreRegistration.physicsStoreRegistry(this)")); - assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(")); - assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerSpaceBindingSystems(")); - assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerPreBodyBindingSystems(")); - assertTrue(chunkSubPlugin.contains("PhysicsChunkStoreTypes.registerPostBodyBindingSystems(")); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java index 28226e63..8b3c902e 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java @@ -10,9 +10,10 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import java.util.Objects; import java.util.UUID; import org.bson.BsonDocument; @@ -23,15 +24,14 @@ class PersistentSpaceDtoSettingsTest { @Test void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { - PhysicsSpaceSettings original = PhysicsSpaceSettings.defaults(); - original.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); - original.getPhysicsChunkCollisionSettings().setNativeVoxelCollisionEnabled(true); - original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(7); - original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(9); - original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(11); - - PhysicsChunkCollisionSettings chunkCollision = - original.getPhysicsChunkCollisionSettings(); + PhysicsChunkCollisionSettings chunkCollision = new PhysicsChunkCollisionSettings(); + chunkCollision.setMode(PhysicsChunkCollisionMode.STREAMING); + chunkCollision.setNativeVoxelCollisionEnabled(true); + PhysicsVisualMaterializationSettings visualMaterialization = + new PhysicsVisualMaterializationSettings(); + visualMaterialization.setDetachedVisualInterestRefreshIntervalTicks(7); + visualMaterialization.setDetachedVisualCandidateRefreshIntervalTicks(9); + visualMaterialization.setDetachedVisualVisibilityCheckIntervalTicks(11); PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), "test:settings-persistence", new Vector3f(0.0f, -9.81f, 0.0f), @@ -43,11 +43,11 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { chunkCollision.getTtlTicks(), 0.85f, 0.2f, - new SolverSettingsComponent(original.getSolverSettings()), - new VisualSyncSettingsComponent(original.getVisualSyncSettings()), - new VisualMaterializationSettingsComponent(original.getVisualMaterializationSettings()), - new CollisionLodSettingsComponent(original.getCollisionLodSettings()), - new ExtensionSettingsComponent(original.getExtensionSettings())); + new SolverSettingsComponent(), + new VisualSyncSettingsComponent(), + new VisualMaterializationSettingsComponent(visualMaterialization), + new CollisionLodSettingsComponent(), + new ExtensionSettingsComponent()); BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); @@ -64,27 +64,28 @@ void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { assertEquals(0.85f, decodedState.getChunkCollisionFriction(), 0.0001f); assertEquals(0.2f, decodedState.getChunkCollisionRestitution(), 0.0001f); - PhysicsSpaceSettings decoded = decodedState.toSettings(); + ChunkCollisionSettingsComponent decodedChunkCollision = + decodedState.getChunkCollisionSettings(); assertEquals(PhysicsChunkCollisionMode.STREAMING, - decoded.getPhysicsChunkCollisionSettings().getMode()); - assertTrue(decoded.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); - assertDetachedVisualCadence(decoded, 7, 9, 11); + decodedChunkCollision.getMode()); + assertTrue(decodedChunkCollision.isNativeVoxelCollisionEnabled()); + assertDetachedVisualCadence(decodedState.getVisualMaterializationSettings(), 7, 9, 11); PersistentSpaceDto copiedState = state.copy(); assertEquals(0.85f, copiedState.getChunkCollisionFriction(), 0.0001f); assertEquals(0.2f, copiedState.getChunkCollisionRestitution(), 0.0001f); - PhysicsSpaceSettings copied = copiedState.toSettings(); + ChunkCollisionSettingsComponent copiedChunkCollision = + copiedState.getChunkCollisionSettings(); assertEquals(PhysicsChunkCollisionMode.STREAMING, - copied.getPhysicsChunkCollisionSettings().getMode()); - assertTrue(copied.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); - assertDetachedVisualCadence(copied, 7, 9, 11); + copiedChunkCollision.getMode()); + assertTrue(copiedChunkCollision.isNativeVoxelCollisionEnabled()); + assertDetachedVisualCadence(copiedState.getVisualMaterializationSettings(), 7, 9, 11); } @Test void roundTripPreservesChunkCollisionFilter() { - PhysicsChunkCollisionSettings chunkCollision = - PhysicsSpaceSettings.defaults().getPhysicsChunkCollisionSettings(); + PhysicsChunkCollisionSettings chunkCollision = new PhysicsChunkCollisionSettings(); PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), "test:chunk-filter-persistence", new Vector3f(0.0f, -9.81f, 0.0f), @@ -113,20 +114,19 @@ void roundTripPreservesChunkCollisionFilter() { assertEquals(0x03, decoded.getChunkCollisionMask()); assertEquals(0x40, state.copy().getChunkCollisionGroup()); assertEquals(0x03, state.copy().getChunkCollisionMask()); - PhysicsSpaceSettings decodedSettings = decoded.toSettings(); assertEquals(chunkCollision.getEntityChunkBoundaryMode(), - decodedSettings.getPhysicsChunkCollisionSettings().getEntityChunkBoundaryMode()); + decoded.getChunkCollisionSettings().getEntityChunkBoundaryMode()); } - private static void assertDetachedVisualCadence(PhysicsSpaceSettings settings, + private static void assertDetachedVisualCadence(VisualMaterializationSettingsComponent settings, int interestInterval, int candidateInterval, int visibilityInterval) { assertEquals(interestInterval, - settings.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); + settings.getDetachedVisualInterestRefreshIntervalTicks()); assertEquals(candidateInterval, - settings.getVisualMaterializationSettings().getDetachedVisualCandidateRefreshIntervalTicks()); + settings.getDetachedVisualCandidateRefreshIntervalTicks()); assertEquals(visibilityInterval, - settings.getVisualMaterializationSettings().getDetachedVisualVisibilityCheckIntervalTicks()); + settings.getDetachedVisualVisibilityCheckIntervalTicks()); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java similarity index 98% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java index 82f89b2c..bced1493 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; +package dev.hytalemodding.impulse.core.internal.physics; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -40,8 +40,8 @@ import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; @@ -94,7 +94,7 @@ void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { bindJoint(store, space, jointUuid, jointRef, bodyAHandle, bodyBRef); publishCopiedState(store, spaceUuid, bodyAUuid, bodyARef, bodyBUuid, bodyBRef); - PhysicsStoreTopologyMutations.destroyBody(store, bodyAUuid); + PhysicsTopologyMutations.destroyBody(store, bodyAUuid); PhysicsIdentityIndexResource identity = store.getResource( PhysicsIdentityIndexResource.getResourceType()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PersistentPhysicsStoreResourceTest.java similarity index 100% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PersistentPhysicsStoreResourceTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PersistentPhysicsStoreResourceTest.java diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java similarity index 99% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java index 30bbbaf9..45371bc3 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java @@ -46,7 +46,7 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.Field; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java deleted file mode 100644 index e4fef1a4..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physicsstore/PhysicsBodyRegistrationMetadataRemovalTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.physicsstore; - -import static org.junit.jupiter.api.Assertions.assertFalse; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; - -class PhysicsBodyRegistrationMetadataRemovalTest { - - @Test - void legacyBodyRegistrationMetadataTypesAreRemoved() throws IOException { - Path sourceRoot = Path.of("src/main/java/dev/hytalemodding/impulse/core"); - assertFalse(Files.exists(sourceRoot.resolve("plugin/body/PhysicsBodyRegistrationView.java"))); - assertFalse(Files.exists(sourceRoot.resolve("plugin/body/PhysicsBodyKind.java"))); - assertFalse(Files.exists(sourceRoot.resolve("plugin/body/PhysicsBodyPersistenceMode.java"))); - - assertNoProductionSourceContains(sourceRoot, - "PhysicsBodyRegistrationView", - "PhysicsBodyKind", - "PhysicsBodyPersistenceMode", - "registrationView", - "registrationViews"); - } - - private static void assertNoProductionSourceContains(@Nonnull Path sourceRoot, - @Nonnull String... forbiddenValues) throws IOException { - try (Stream paths = Files.walk(sourceRoot)) { - for (Path source : paths - .filter(Files::isRegularFile) - .filter(path -> path.toString().endsWith(".java")) - .toList()) { - String contents = Files.readString(source); - for (String forbidden : forbiddenValues) { - assertFalse(contents.contains(forbidden), - () -> source + " still contains " + forbidden); - } - } - } - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java index b93d5038..f9f2c727 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java @@ -4,8 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.Optional; @@ -59,10 +57,8 @@ void pendingRaycastResultsArePolledWithoutBlocking() { BodyVisualInterestState state = new BodyVisualInterestState(); CompletableFuture> pending = new CompletableFuture<>(); RaycastHitView hit = new RaycastHitView(null, - PhysicsBodyType.DYNAMIC, new Vector3f(), new Vector3f(0.0f, 1.0f, 0.0f), - ShapeType.BOX, 0.5f, 4.0f); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java new file mode 100644 index 00000000..67e48d28 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java @@ -0,0 +1,67 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import java.util.ArrayList; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class PhysicsEntityProjectionResourcesTest { + + @AfterEach + void clearTypes() { + PhysicsEntityTypeRegistry.clearEntityStoreTypes(); + } + + @Test + void registersProjectionResourcesWithoutWorldResourceFacade() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + + PhysicsEntityTypeRegistry.registerComponentTypes(proxy); + PhysicsEntityTypeRegistry.registerResourceTypes(proxy); + PhysicsEntityTypeRegistry.registerEventTypes(proxy); + PhysicsEntityTypeRegistry.registerSystemGroups(proxy); + + assertResourceClass(PhysicsProjectionIndexResource.getResourceType(), + PhysicsProjectionIndexResource.class); + assertResourceClass(PhysicsBodySyncStateResource.getResourceType(), + PhysicsBodySyncStateResource.class); + assertResourceClass(PhysicsVisualInterestResource.getResourceType(), + PhysicsVisualInterestResource.class); + + Store store = registry.addStore(testEntityStore("projection-resources-test"), + EmptyResourceStorage.get()); + try { + assertNotNull(store.getResource(PhysicsProjectionIndexResource.getResourceType())); + assertNotNull(store.getResource(PhysicsBodySyncStateResource.getResourceType())); + assertNotNull(store.getResource(PhysicsVisualInterestResource.getResourceType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + private static > void assertResourceClass( + @Nonnull ResourceType resourceType, + @Nonnull Class expectedType) { + assertSame(expectedType, resourceType.getTypeClass()); + } + + @Nonnull + private static EntityStore testEntityStore(@Nonnull String worldName) { + return new EntityStore(TestInstanceFactory.world(worldName)); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEventCollectionModeTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEventCollectionModeTest.java index cf00c8cc..a33138b3 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEventCollectionModeTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEventCollectionModeTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventCollectionMode; import org.junit.jupiter.api.Test; class PhysicsEventCollectionModeTest { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index c564d41a..c3b6bdd5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.internal.resources; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -15,6 +16,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -220,6 +222,66 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { assertNull(resource.getBody(bodyUuid)); } + @Test + void snapshotResourceRemovesMultipleBodiesInOneBatch() { + PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000015"); + UUID firstBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000016"); + UUID secondBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000017"); + UUID retainedBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000018"); + Ref firstBodyRef = new TestRef(16); + Ref secondBodyRef = new TestRef(17); + Ref retainedBodyRef = new TestRef(18); + PhysicsBodySnapshot first = snapshot(firstBodyRef, firstBodyUuid, spaceUuid); + PhysicsBodySnapshot second = snapshot(secondBodyRef, secondBodyUuid, spaceUuid); + PhysicsBodySnapshot retained = snapshot(retainedBodyRef, retainedBodyUuid, spaceUuid); + resource.publish(new PhysicsSnapshotFrame(12L, 0.05f, List.of(first, second, retained))); + + resource.removeBodies(List.of(firstBodyUuid, secondBodyUuid)); + + assertNull(resource.getBody(firstBodyUuid)); + assertNull(resource.getBody(firstBodyRef)); + assertNull(resource.getBody(secondBodyUuid)); + assertNull(resource.getBody(secondBodyRef)); + assertEquals(retained, resource.getBody(retainedBodyUuid)); + assertEquals(retained, resource.getBody(retainedBodyRef)); + assertEquals(List.of(retained), resource.getLatestFrame().bodies()); + } + + @Test + void bodyRegistrationResourceRemovesMultipleBodiesInOneBatch() { + PhysicsBodyRegistrationResource resource = new PhysicsBodyRegistrationResource(); + UUID firstBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000019"); + UUID secondBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000020"); + UUID retainedBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000021"); + Ref firstBodyRef = new TestRef(19); + Ref secondBodyRef = new TestRef(20); + Ref retainedBodyRef = new TestRef(21); + SpaceId spaceId = new SpaceId(42); + resource.publish(7L, + List.of(new PhysicsBodyRegistrationResource.BodyRegistrationPublication(firstBodyRef, + firstBodyUuid, + spaceId), + new PhysicsBodyRegistrationResource.BodyRegistrationPublication(secondBodyRef, + secondBodyUuid, + spaceId), + new PhysicsBodyRegistrationResource.BodyRegistrationPublication(retainedBodyRef, + retainedBodyUuid, + spaceId))); + + resource.removeBodies(List.of(firstBodyUuid, secondBodyUuid)); + + assertNull(resource.getBodySpaceId(firstBodyUuid)); + assertNull(resource.getBodyUuid(firstBodyRef)); + assertNull(resource.getBodySpaceId(secondBodyUuid)); + assertNull(resource.getBodyUuid(secondBodyRef)); + assertEquals(spaceId, resource.getBodySpaceId(retainedBodyUuid)); + assertEquals(retainedBodyUuid, resource.getBodyUuid(retainedBodyRef)); + assertEquals(List.of(retainedBodyUuid), List.copyOf(resource.getBodyUuids())); + assertEquals(1, resource.getBodyRegistrationCount()); + assertNotNull(resource.getBodySpaceId(retainedBodyRef)); + } + private static final class TestRef extends Ref { private TestRef(int index) { @@ -231,4 +293,19 @@ public boolean isValid() { return true; } } + + private static PhysicsBodySnapshot snapshot(Ref bodyRef, + UUID bodyUuid, + UUID spaceUuid) { + return new PhysicsBodySnapshot(bodyRef, + bodyUuid, + spaceUuid, + PhysicsBodyType.KINEMATIC, + new Vector3f(1.0f, 2.0f, 3.0f), + new Quaternionf(), + new Vector3f(), + new Vector3f(), + 0.0f, + false); + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResourceTest.java deleted file mode 100644 index a3f6336a..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldRuntimeResourceTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertSame; - -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.EmptyResourceStorage; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.resources.PhysicsWorldResource; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; - -class PhysicsWorldRuntimeResourceTest { - - @Test - void registersRuntimeImplementationBehindPublicResourceType() { - ComponentRegistry registry = new ComponentRegistry<>(); - ResourceType resourceType = - registry.registerResource(PhysicsWorldResource.class, - PhysicsWorldRuntimeResource::new); - - assertSame(PhysicsWorldResource.class, resourceType.getTypeClass()); - - Store store = registry.addStore(testEntityStore("runtime-resource-test"), - EmptyResourceStorage.get()); - PhysicsWorldResource resource = store.getResource(resourceType); - - PhysicsWorldRuntimeResource runtime = - assertInstanceOf(PhysicsWorldRuntimeResource.class, resource); - assertSame(runtime, PhysicsWorldRuntimeResource.require(resource)); - - registry.removeStore(store); - registry.shutdown(); - } - - @Nonnull - private static EntityStore testEntityStore(@Nonnull String worldName) { - return new EntityStore(TestInstanceFactory.world(worldName)); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsTest.java index 8476c74c..3b26e6f9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSettingsTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java index 3eb16c87..e293a85c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java @@ -29,6 +29,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -38,7 +40,7 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import java.util.ArrayList; import java.util.UUID; import javax.annotation.Nonnull; @@ -70,6 +72,10 @@ void spaceSurfaceComponentsSyncGeneratedRowsAndBoundBackends() { new PhysicsStore(TestInstanceFactory.world("chunk-collision-component-sync-test")), EmptyResourceStorage.get()); try { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); UUID spaceUuid = uuid(1); RuntimeFixture runtime = addBoundSpace(store, spaceUuid, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 3cd859de..4d1f1f73 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -29,9 +29,10 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; -import dev.hytalemodding.impulse.core.internal.physicsstore.PhysicsStoreTopologyMutations; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; @@ -40,6 +41,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -53,7 +55,9 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -234,7 +238,7 @@ void destroyingDetailRowDoesNotRemoveNativeVoxelPayloadForSiblingRow() { .get(payloadKey); assertVoxelOnlyPayload(retainedPayload); - PhysicsStoreTopologyMutations.destroyBody(store, detailUuid); + PhysicsTopologyMutations.destroyBody(store, detailUuid); assertSame(retainedPayload, store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) @@ -299,8 +303,19 @@ void removeDeletesGeneratedRowsAndPayloadResource() { sourceKey, PartKind.DETAIL_BOX, 0); - assertNotNull(identity.getByUuid(boxUuid)); - assertNotNull(identity.getByUuid(detailUuid)); + Ref boxRef = identity.getByUuid(boxUuid); + Ref detailRef = identity.getByUuid(detailUuid); + assertNotNull(boxRef); + assertNotNull(detailRef); + publishCopiedState(store, spaceUuid, boxUuid, boxRef, detailUuid, detailRef); + PhysicsSnapshotResource snapshots = + store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = + store.getResource(PhysicsBodyRegistrationResource.getResourceType()); + assertNotNull(snapshots.getBody(boxUuid)); + assertNotNull(snapshots.getBody(detailUuid)); + assertTrue(registrations.hasBody(boxUuid)); + assertTrue(registrations.hasBody(detailUuid)); queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, sourceKey, 5, 6, 7)); new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); @@ -308,6 +323,10 @@ void removeDeletesGeneratedRowsAndPayloadResource() { assertEquals(0, queue.size()); assertNull(identity.getByUuid(boxUuid)); assertNull(identity.getByUuid(detailUuid)); + assertNull(snapshots.getBody(boxUuid)); + assertNull(snapshots.getBody(detailUuid)); + assertFalse(registrations.hasBody(boxUuid)); + assertFalse(registrations.hasBody(detailUuid)); assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) .get(payloadKey)); assertSoftSkipsEmpty(store); @@ -615,6 +634,57 @@ private static long settingsGeneration(@Nonnull Store store) { .generation(); } + private static void publishCopiedState(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull UUID firstBodyUuid, + @Nonnull Ref firstBodyRef, + @Nonnull UUID secondBodyUuid, + @Nonnull Ref secondBodyRef) { + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(firstBodyRef, firstBodyUuid, spaceUuid), + snapshot(secondBodyRef, secondBodyUuid, spaceUuid)))); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .publish(1L, + List.of(publication(firstBodyRef, firstBodyUuid), + publication(secondBodyRef, secondBodyUuid))); + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + return PhysicsBodySnapshot.of(bodyRef, + bodyUuid, + spaceUuid, + PhysicsBodyType.STATIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + + @Nonnull + private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( + @Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid) { + return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, + bodyUuid, + new SpaceId(42)); + } + private static long previousGeneration(long generation) { return Math.max(0L, generation - 1L); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index 06a7e506..c571f0e6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -44,7 +44,7 @@ import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicyTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicyTest.java index ceeaf340..53fe3431 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicyTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicyTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import java.util.Arrays; import java.util.List; import org.joml.Quaternionf; @@ -19,7 +19,7 @@ void returnsInitialForUninitializedSyncState() { assertEquals(PhysicsSyncPolicy.SyncDecision.INITIAL, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(), new Quaternionf(), false, @@ -34,7 +34,7 @@ void returnsTransitionWhenSleepingStateChanges() { assertEquals(PhysicsSyncPolicy.SyncDecision.TRANSITION, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(), new Quaternionf(), true, @@ -49,7 +49,7 @@ void farRangeFollowersSkipVisualSync() { assertEquals(PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_RANGE, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(), new Quaternionf(), false, @@ -65,7 +65,7 @@ void lowSpeedNearBodiesUseVisualDeadzoneBeforeKeepalive() { assertEquals(PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_DEADZONE, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(0.05f, 0.0f, 0.0f), new Quaternionf(), false, @@ -81,7 +81,7 @@ void lowSpeedNearBodiesTriggerKeepaliveAtLongerInterval() { assertEquals(PhysicsSyncPolicy.SyncDecision.KEEPALIVE, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(0.05f, 0.0f, 0.0f), new Quaternionf(), false, @@ -97,7 +97,7 @@ void midRangeFollowersUseCoarseThresholdAndKeepalive() { assertEquals(PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_RANGE, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(0.2f, 0.0f, 0.0f), new Quaternionf(), false, @@ -108,7 +108,7 @@ void midRangeFollowersUseCoarseThresholdAndKeepalive() { syncState.recordSkip(0.1f); assertEquals(PhysicsSyncPolicy.SyncDecision.KEEPALIVE, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(0.2f, 0.0f, 0.0f), new Quaternionf(), false, @@ -119,8 +119,8 @@ void midRangeFollowersUseCoarseThresholdAndKeepalive() { @Test void midRangeFollowersRespectConfiguredMinimumInterval() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(4); + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualMidSyncIntervalTicks(4); PhysicsBodyRuntimeState.BodySyncState syncState = initializedState(false); syncState.recordSkip(0.15f); @@ -148,9 +148,9 @@ void midRangeFollowersRespectConfiguredMinimumInterval() { @Test void farRangeLodUsesConfiguredIntervalWhenCutoffIsDisabled() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualFarSyncCutoffEnabled(false); - settings.getVisualSyncSettings().setVisualFarSyncIntervalTicks(40); + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualFarSyncCutoffEnabled(false); + settings.setVisualFarSyncIntervalTicks(40); PhysicsBodyRuntimeState.BodySyncState syncState = initializedState(false); syncState.recordSkip(1.95f); @@ -182,7 +182,7 @@ void controlledBodiesBypassLowSpeedDeadzoneThresholds() { assertEquals(PhysicsSyncPolicy.SyncDecision.THRESHOLD, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(0.05f, 0.0f, 0.0f), new Quaternionf(), false, @@ -198,7 +198,7 @@ void activeBodiesTriggerThresholdOnRotationChange() { assertEquals(PhysicsSyncPolicy.SyncDecision.THRESHOLD, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(), rotated, false, @@ -228,7 +228,7 @@ void sleepingBodiesSkipAfterThresholdAndKeepaliveChecks() { assertEquals(PhysicsSyncPolicy.SyncDecision.SKIP_SLEEPING, PhysicsSyncPolicy.resolveSyncDecision(syncState, - PhysicsSpaceSettings.defaults(), + new PhysicsVisualSyncSettings(), new Vector3f(), new Quaternionf(), true, @@ -239,7 +239,7 @@ void sleepingBodiesSkipAfterThresholdAndKeepaliveChecks() { @Test void rangeTierReturnsNearForNonLimitedOrControlledVisuals() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); List players = interests(new Vector3f(100.0f, 0.0f, 0.0f)); @@ -262,7 +262,7 @@ void rangeTierReturnsNearForNonLimitedOrControlledVisuals() { @Test void rangeTierReturnsFarWhenNoPlayersAreInterested() { assertEquals(PhysicsSyncPolicy.SyncRangeTier.FAR, - PhysicsSyncPolicy.resolveRangeTier(PhysicsSpaceSettings.defaults(), + PhysicsSyncPolicy.resolveRangeTier(new PhysicsVisualSyncSettings(), null, true, false, @@ -283,9 +283,9 @@ void rangeTierFallsBackToNearWhenSettingsAreMissing() { @Test void rangeTierDistinguishesNearMidAndFarBands() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualFullSyncRadius(10); - settings.getVisualSyncSettings().setVisualMaxSyncRadius(20); + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualFullSyncRadius(10); + settings.setVisualMaxSyncRadius(20); List players = interests(new Vector3f(0.0f, 0.0f, 0.0f)); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java index 5dae4cb7..f051e47b 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java @@ -8,7 +8,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import java.util.UUID; import org.joml.Quaterniond; import org.joml.Quaternionf; @@ -20,9 +20,9 @@ class PhysicsSyncSystemTest { @Test void visualPredictionSecondsClampToConfiguredWindow() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualSnapshotPredictionEnabled(true); - settings.getVisualSyncSettings().setVisualSnapshotPredictionMaxSeconds(0.05f); + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualSnapshotPredictionEnabled(true); + settings.setVisualSnapshotPredictionMaxSeconds(0.05f); assertEquals(0.05f, PhysicsSyncPolicy.visualPredictionSeconds(settings, @@ -33,13 +33,13 @@ void visualPredictionSecondsClampToConfiguredWindow() { @Test void visualPredictionSecondsStayZeroWhenDisabledOrMissingFrame() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - settings.getVisualSyncSettings().setVisualSnapshotPredictionEnabled(true); + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualSnapshotPredictionEnabled(true); assertEquals(0.0f, PhysicsSyncPolicy.visualPredictionSeconds(settings, 1_100_000_000L, 0L), 0.0001f); - settings.getVisualSyncSettings().setVisualSnapshotPredictionEnabled(false); + settings.setVisualSnapshotPredictionEnabled(false); assertEquals(0.0f, PhysicsSyncPolicy.visualPredictionSeconds(settings, 1_100_000_000L, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java index 9701a2e8..add406f5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java @@ -16,7 +16,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldRuntimeResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; @@ -92,7 +91,7 @@ void generatedProxyWithDestroyedBodyRefIsRemovedAndUnindexed() { } @Test - void generatedProxyWithMissingBodyRefIsRemovedAndUnindexed() { + void generatedProxyWithMissingBodyRefIsPreservedWithoutPhysicsStoreAuthority() { ComponentRegistry registry = new ComponentRegistry<>(); Store store = store(registry, "projection-cleanup-generated-missing-ref"); try { @@ -108,9 +107,10 @@ void generatedProxyWithMissingBodyRefIsRemovedAndUnindexed() { new PhysicsProjectionCleanupSystem().tick(0.0f, 0, store); - assertFalse(proxyRef.isValid()); - assertFalse(projection.hasAttachments(bodyUuid)); - assertNull(projection.getGeneratedVisualProxy(bodyUuid)); + assertTrue(proxyRef.isValid()); + assertNotNull(store.getComponent(proxyRef, BodyAttachmentComponent.getComponentType())); + assertTrue(projection.hasAttachments(bodyUuid)); + assertNotNull(projection.getGeneratedVisualProxy(bodyUuid)); } finally { registry.removeStore(store); registry.shutdown(); @@ -180,7 +180,6 @@ private static Store store(@Nonnull ComponentRegistry Store store = registry.addStore( new EntityStore(TestInstanceFactory.world(worldName)), EmptyResourceStorage.get()); - PhysicsWorldRuntimeResource.require(store); return store; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java new file mode 100644 index 00000000..200ca649 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java @@ -0,0 +1,109 @@ +package dev.hytalemodding.impulse.core.plugin.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsBodyEntitiesTest { + + @Test + void dynamicBodyHolderInfersEntityIdentityFromBodyUuidAndSpaceRef() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physics-body-entities-holder-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(1); + UUID bodyUuid = uuid(2); + Ref spaceRef = PhysicsSpaces.create(store, + spaceUuid, + new SpaceId(4101), + new BackendId("test:body-entities")); + + Holder holder = PhysicsBodyEntities.dynamicBodyHolder(spaceRef, + bodyUuid, + new Vector3f(1.0f, 2.0f, 3.0f), + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 1.0f, + RigidBodySpawnSettings.defaults(), + null); + Ref bodyRef = store.addEntity(holder, AddReason.SPAWN); + + assertEquals(bodyUuid, + store.getComponent(bodyRef, UuidComponent.getComponentType()).getUuid()); + BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); + assertEquals(spaceUuid, body.getSpaceUuid()); + assertSame(spaceRef, body.getSpaceRef()); + assertEquals(1.0f, + store.getComponent(bodyRef, DynamicsComponent.getComponentType()).getMass()); + assertEquals(0.5f, + store.getComponent(bodyRef, ShapeComponent.getComponentType()).getHalfExtentX()); + assertEquals(0.5f, + store.getComponent(bodyRef, MaterialComponent.getComponentType()).getFriction()); + assertEquals(0.0f, + store.getComponent(bodyRef, ColliderComponent.getComponentType()) + .getLocalPosition() + .x); + assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, + store.getComponent(bodyRef, CollisionFilterComponent.getComponentType()) + .getCollisionGroup()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static UUID uuid(int lowBits) { + return new UUID(0L, lowBits); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java similarity index 99% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java index 9ac46cca..568953d1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physicsstore/PhysicsSpacesSettingsComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.physicsstore; +package dev.hytalemodding.impulse.core.plugin.physics; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java deleted file mode 100644 index d2f587d4..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsSpaceSettingsTest.java +++ /dev/null @@ -1,362 +0,0 @@ -package dev.hytalemodding.impulse.core.plugin.settings; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import org.junit.jupiter.api.Test; - -class PhysicsSpaceSettingsTest { - - @Test - void defaultsExposeHeadlessVisualSyncRadii() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - - assertEquals(PhysicsVisualSyncSettings.DEFAULT_VISUAL_FULL_SYNC_RADIUS, - settings.getVisualSyncSettings().getVisualFullSyncRadius()); - assertEquals(PhysicsVisualSyncSettings.DEFAULT_VISUAL_MAX_SYNC_RADIUS, - settings.getVisualSyncSettings().getVisualMaxSyncRadius()); - assertEquals(PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_INTEREST_REFRESH_INTERVAL_TICKS, - settings.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); - assertEquals(PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_CANDIDATE_REFRESH_INTERVAL_TICKS, - settings.getVisualMaterializationSettings().getDetachedVisualCandidateRefreshIntervalTicks()); - assertEquals(PhysicsVisualMaterializationSettings.DEFAULT_DETACHED_VISUAL_VISIBILITY_CHECK_INTERVAL_TICKS, - settings.getVisualMaterializationSettings().getDetachedVisualVisibilityCheckIntervalTicks()); - assertEquals(PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_NEAR_RADIUS, - settings.getCollisionLodSettings().getCollisionLodNearRadius()); - assertEquals(PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_MID_RADIUS, - settings.getCollisionLodSettings().getCollisionLodMidRadius()); - assertEquals(PhysicsCollisionLodSettings.DEFAULT_COLLISION_LOD_REFRESH_INTERVAL_TICKS, - settings.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks()); - assertEquals(PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_ENABLED, - settings.getVisualSyncSettings().isVisualSnapshotPredictionEnabled()); - assertEquals(PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS, - settings.getVisualSyncSettings().getVisualSnapshotPredictionMaxSeconds(), - 0.0001f); - assertEquals(PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_ENABLED, - settings.getVisualSyncSettings().isVisualSnapshotSmoothingEnabled()); - assertEquals(PhysicsVisualSyncSettings.DEFAULT_VISUAL_SNAPSHOT_SMOOTHING_RATE, - settings.getVisualSyncSettings().getVisualSnapshotSmoothingRate(), - 0.0001f); - } - - @Test - void rejectsVisualFullSyncRadiusAboveMaxRadius() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> settings.getVisualSyncSettings().setVisualFullSyncRadius(settings.getVisualSyncSettings().getVisualMaxSyncRadius() + 1)); - - assertEquals("Visual full sync radius cannot exceed visual max sync radius", - exception.getMessage()); - } - - @Test - void rejectsVisualMaxSyncRadiusBelowFullRadius() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> settings.getVisualSyncSettings().setVisualMaxSyncRadius(settings.getVisualSyncSettings().getVisualFullSyncRadius() - 1)); - - assertEquals("Visual max sync radius cannot be lower than visual full sync radius", - exception.getMessage()); - } - - @Test - void acceptsUpdatedVisualSyncRadiiWhenOrderingStaysValid() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - - settings.getVisualSyncSettings().setVisualMaxSyncRadius(192); - settings.getVisualSyncSettings().setVisualFullSyncRadius(96); - - assertEquals(192, settings.getVisualSyncSettings().getVisualMaxSyncRadius()); - assertEquals(96, settings.getVisualSyncSettings().getVisualFullSyncRadius()); - } - - @Test - void rejectsNonPositivePhysicsChunkCollisionValues() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - - assertEquals("PhysicsChunk collision radius must be between 1 and " - + PhysicsChunkCollisionSettings.MAX_RADIUS, - assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkCollisionSettings().setRadius(0)).getMessage()); - assertEquals("PhysicsChunk collision body radius must be between 1 and " - + PhysicsChunkCollisionSettings.MAX_BODY_RADIUS, - assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkCollisionSettings().setBodyRadius(0)).getMessage()); - assertEquals("PhysicsChunk collision TTL must be between 1 and " - + PhysicsChunkCollisionSettings.MAX_TTL_TICKS, - assertThrows(IllegalArgumentException.class, - () -> settings.getPhysicsChunkCollisionSettings().setTtlTicks(0)).getMessage()); - assertEquals("Visual full sync radius must be between 1 and " - + PhysicsVisualSyncSettings.MAX_VISUAL_FULL_SYNC_RADIUS, - assertThrows(IllegalArgumentException.class, - () -> settings.getVisualSyncSettings().setVisualFullSyncRadius(0)).getMessage()); - assertEquals("Visual max sync radius must be between 1 and " - + PhysicsVisualSyncSettings.MAX_VISUAL_MAX_SYNC_RADIUS, - assertThrows(IllegalArgumentException.class, - () -> settings.getVisualSyncSettings().setVisualMaxSyncRadius(0)).getMessage()); - assertEquals("Detached visual interest refresh interval must be between 1 and " - + PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS, - assertThrows(IllegalArgumentException.class, - () -> settings.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(0)).getMessage()); - assertEquals("Detached visual candidate refresh interval must be between 1 and " - + PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS, - assertThrows(IllegalArgumentException.class, - () -> settings.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(0)).getMessage()); - assertEquals("Detached visual visibility check interval must be between 1 and " - + PhysicsVisualMaterializationSettings.MAX_DETACHED_VISUAL_CACHE_INTERVAL_TICKS, - assertThrows(IllegalArgumentException.class, - () -> settings.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(0)).getMessage()); - assertEquals("Collision LOD near radius must be between 1 and " - + PhysicsCollisionLodSettings.MAX_COLLISION_LOD_RADIUS, - assertThrows(IllegalArgumentException.class, - () -> settings.getCollisionLodSettings().setCollisionLodNearRadius(0)).getMessage()); - assertEquals("Collision LOD mid radius must be between 1 and " - + PhysicsCollisionLodSettings.MAX_COLLISION_LOD_RADIUS, - assertThrows(IllegalArgumentException.class, - () -> settings.getCollisionLodSettings().setCollisionLodMidRadius(0)).getMessage()); - assertEquals("Collision LOD refresh interval must be between 1 and " - + PhysicsCollisionLodSettings.MAX_COLLISION_LOD_REFRESH_INTERVAL_TICKS, - assertThrows(IllegalArgumentException.class, - () -> settings.getCollisionLodSettings().setCollisionLodRefreshIntervalTicks(0)).getMessage()); - assertEquals("Visual snapshot prediction max seconds must be between 0 and " - + PhysicsVisualSyncSettings.MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS, - assertThrows(IllegalArgumentException.class, - () -> settings.getVisualSyncSettings().setVisualSnapshotPredictionMaxSeconds( - PhysicsVisualSyncSettings.MAX_VISUAL_SNAPSHOT_PREDICTION_MAX_SECONDS - + 0.01f)).getMessage()); - assertEquals("Visual snapshot smoothing rate must be > 0 and <= " - + PhysicsVisualSyncSettings.MAX_VISUAL_SNAPSHOT_SMOOTHING_RATE, - assertThrows(IllegalArgumentException.class, - () -> settings.getVisualSyncSettings().setVisualSnapshotSmoothingRate(0.0f)).getMessage()); - } - - @Test - void rejectsInvalidCollisionLodOrderingAndHysteresis() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - - IllegalArgumentException radiusException = assertThrows(IllegalArgumentException.class, - () -> settings.getCollisionLodSettings().setCollisionLodRadii(96, 64)); - IllegalArgumentException hysteresisException = assertThrows(IllegalArgumentException.class, - () -> settings.getCollisionLodSettings().setCollisionLodHysteresis( - PhysicsCollisionLodSettings.MAX_COLLISION_LOD_HYSTERESIS + 1)); - - assertEquals("Collision LOD near radius cannot exceed mid radius", - radiusException.getMessage()); - assertEquals("Collision LOD hysteresis must be between 0 and " - + PhysicsCollisionLodSettings.MAX_COLLISION_LOD_HYSTERESIS, - hysteresisException.getMessage()); - } - - @Test - void defaultsFactoryReturnsFreshDefaultSettings() { - PhysicsSpaceSettings first = PhysicsSpaceSettings.defaults(); - PhysicsSpaceSettings second = PhysicsSpaceSettings.defaults(); - - assertNotSame(first, second); - assertEquals(PhysicsChunkCollisionMode.NONE, first.getPhysicsChunkCollisionSettings().getMode()); - assertSame(PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - first.getPhysicsChunkCollisionSettings().getEntityChunkBoundaryMode()); - assertFalse(first.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); - } - - @Test - void groupedAccessorsExposeIndependentDomainState() { - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(); - - settings.getPhysicsChunkCollisionSettings().setRadius(14); - settings.getVisualSyncSettings().setVisualSyncRadii(36, 144); - settings.getSolverSettings().setSolverIterations(6); - settings.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(96); - settings.getCollisionLodSettings().setCollisionLodRadii(24, 72); - - assertEquals(14, settings.getPhysicsChunkCollisionSettings().getRadius()); - assertEquals(36, settings.getVisualSyncSettings().getVisualFullSyncRadius()); - assertEquals(144, settings.getVisualSyncSettings().getVisualMaxSyncRadius()); - assertEquals(6, settings.getSolverSettings().getSolverIterations()); - assertEquals(96, settings.getVisualMaterializationSettings().getDetachedVisualMaxMaterialized()); - assertEquals(24, settings.getCollisionLodSettings().getCollisionLodNearRadius()); - assertEquals(72, settings.getCollisionLodSettings().getCollisionLodMidRadius()); - - settings.getPhysicsChunkCollisionSettings().setBodyRadius(5); - settings.getVisualSyncSettings().setVisualMidSyncIntervalTicks(3); - settings.getSolverSettings().setDynamicSleepLinearThreshold(0.45f); - settings.getVisualMaterializationSettings().setDetachedVisualMaxSpawnsPerTick(16); - settings.getCollisionLodSettings().setCollisionLodHysteresis(4); - - assertEquals(5, settings.getPhysicsChunkCollisionSettings().getBodyRadius()); - assertEquals(3, settings.getVisualSyncSettings().getVisualMidSyncIntervalTicks()); - assertEquals(0.45f, settings.getSolverSettings().getDynamicSleepLinearThreshold(), 0.0001f); - assertEquals(16, - settings.getVisualMaterializationSettings().getDetachedVisualMaxSpawnsPerTick()); - assertEquals(4, settings.getCollisionLodSettings().getCollisionLodHysteresis()); - } - - @Test - void collisionLodSettingsCopyConstructorCopiesValues() { - PhysicsCollisionLodSettings canonical = new PhysicsCollisionLodSettings(); - - canonical.setCollisionLodEnabled(true); - canonical.setCollisionLodRadii(24, 96); - canonical.setCollisionLodHysteresis(6); - canonical.setCollisionLodRefreshIntervalTicks(8); - canonical.setCollisionLodFarSleepEnabled(false); - - PhysicsCollisionLodSettings canonicalCopy = - new PhysicsCollisionLodSettings(canonical); - PhysicsCollisionLodSettings secondCopy = - new PhysicsCollisionLodSettings(canonicalCopy); - canonical.setCollisionLodRadii(32, 128); - canonicalCopy.setCollisionLodRadii(40, 160); - - assertTrue(canonicalCopy.isCollisionLodEnabled()); - assertEquals(40, canonicalCopy.getCollisionLodNearRadius()); - assertEquals(160, canonicalCopy.getCollisionLodMidRadius()); - assertEquals(6, canonicalCopy.getCollisionLodHysteresis()); - assertEquals(8, canonicalCopy.getCollisionLodRefreshIntervalTicks()); - assertFalse(canonicalCopy.isCollisionLodFarSleepEnabled()); - assertTrue(secondCopy.isCollisionLodEnabled()); - assertEquals(24, secondCopy.getCollisionLodNearRadius()); - assertEquals(96, secondCopy.getCollisionLodMidRadius()); - assertEquals(6, secondCopy.getCollisionLodHysteresis()); - assertEquals(8, secondCopy.getCollisionLodRefreshIntervalTicks()); - assertFalse(secondCopy.isCollisionLodFarSleepEnabled()); - } - - @Test - void chunkCollisionSettingsCopyConstructorCopiesValues() { - PhysicsChunkCollisionSettings canonical = new PhysicsChunkCollisionSettings(); - - canonical.setMode(PhysicsChunkCollisionMode.STREAMING); - canonical.setEntityChunkBoundaryMode(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK); - canonical.setNativeVoxelCollisionEnabled(true); - canonical.setRadius(18); - canonical.setBodyRadius(7); - canonical.setTtlTicks(240); - - PhysicsChunkCollisionSettings canonicalCopy = - new PhysicsChunkCollisionSettings(canonical); - PhysicsChunkCollisionSettings secondCopy = - new PhysicsChunkCollisionSettings(canonicalCopy); - canonical.setRadius(24); - canonicalCopy.setRadius(30); - - assertEquals(PhysicsChunkCollisionMode.STREAMING, canonicalCopy.getMode()); - assertEquals(EntityChunkBoundaryMode.LOAD_TICKING_CHUNK, - canonicalCopy.getEntityChunkBoundaryMode()); - assertTrue(canonicalCopy.isNativeVoxelCollisionEnabled()); - assertEquals(30, canonicalCopy.getRadius()); - assertEquals(7, canonicalCopy.getBodyRadius()); - assertEquals(240, canonicalCopy.getTtlTicks()); - assertEquals(18, secondCopy.getRadius()); - assertEquals(7, secondCopy.getBodyRadius()); - assertEquals(240, secondCopy.getTtlTicks()); - } - - @Test - void extensionSettingsAreTypedAndCopyIsolated() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - PhysicsBackendExtensionId extensionId = new PhysicsBackendExtensionId("test:extension"); - - settings.getExtensionSettings().setInt(extensionId, "iterations", 7); - settings.getExtensionSettings().setFloat(extensionId, "scale", 1.5f); - settings.getExtensionSettings().setBoolean(extensionId, "enabled", true); - settings.getExtensionSettings().setString(extensionId, "mode", "stable"); - - PhysicsSpaceSettings copy = new PhysicsSpaceSettings(settings); - settings.getExtensionSettings().setInt(extensionId, "iterations", 11); - copy.getExtensionSettings().setString(extensionId, "mode", "copy"); - - assertEquals(11, settings.getExtensionSettings().getInt(extensionId, "iterations").orElseThrow()); - assertEquals("stable", settings.getExtensionSettings().getString(extensionId, "mode").orElseThrow()); - assertEquals(7, copy.getExtensionSettings().getInt(extensionId, "iterations").orElseThrow()); - assertEquals(1.5f, copy.getExtensionSettings().getFloat(extensionId, "scale").orElseThrow(), 0.0001f); - assertTrue(copy.getExtensionSettings().getBoolean(extensionId, "enabled").orElseThrow()); - assertEquals("copy", copy.getExtensionSettings().getString(extensionId, "mode").orElseThrow()); - } - - @Test - void defaultsDoNotCarryBackendExtensionValues() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.defaults(); - - assertTrue(settings.getExtensionSettings().isEmpty()); - } - - @Test - void streamingPhysicsChunkFactoryEnablesStreamingMode() { - PhysicsSpaceSettings settings = PhysicsSpaceSettings.streamingPhysicsChunk(); - - assertEquals(PhysicsChunkCollisionMode.STREAMING, settings.getPhysicsChunkCollisionSettings().getMode()); - assertEquals(PhysicsChunkCollisionSettings.DEFAULT_RADIUS, - settings.getPhysicsChunkCollisionSettings().getRadius()); - } - - @Test - void copyConstructorCopiesValuesWithoutSharingOriginalInstance() { - PhysicsSpaceSettings original = new PhysicsSpaceSettings(); - original.getPhysicsChunkCollisionSettings().setMode(PhysicsChunkCollisionMode.STREAMING); - original.getPhysicsChunkCollisionSettings().setRadius(12); - original.getPhysicsChunkCollisionSettings().setBodyRadius(6); - original.getPhysicsChunkCollisionSettings().setTtlTicks(180); - original.getPhysicsChunkCollisionSettings().setNativeVoxelCollisionEnabled(true); - original.getVisualSyncSettings().setVisualMaxSyncRadius(160); - original.getVisualSyncSettings().setVisualFullSyncRadius(80); - original.getVisualMaterializationSettings().setDetachedVisualInterestRefreshIntervalTicks(2); - original.getVisualMaterializationSettings().setDetachedVisualCandidateRefreshIntervalTicks(3); - original.getVisualMaterializationSettings().setDetachedVisualVisibilityCheckIntervalTicks(12); - original.getVisualSyncSettings().setVisualSnapshotPredictionEnabled(true); - original.getVisualSyncSettings().setVisualSnapshotPredictionMaxSeconds(0.08f); - original.getVisualSyncSettings().setVisualSnapshotSmoothingEnabled(true); - original.getVisualSyncSettings().setVisualSnapshotSmoothingRate(18.0f); - original.getCollisionLodSettings().setCollisionLodEnabled(true); - original.getCollisionLodSettings().setCollisionLodRadii(32, 96); - original.getCollisionLodSettings().setCollisionLodHysteresis(8); - original.getCollisionLodSettings().setCollisionLodRefreshIntervalTicks(6); - original.getCollisionLodSettings().setCollisionLodFarSleepEnabled(false); - - PhysicsSpaceSettings copy = new PhysicsSpaceSettings(original); - original.getPhysicsChunkCollisionSettings().setRadius(20); - original.getVisualSyncSettings().setVisualSyncRadii(96, 192); - original.getVisualMaterializationSettings().setDetachedVisualMaxMaterialized(128); - original.getCollisionLodSettings().setCollisionLodRadii(48, 112); - - assertNotSame(original.getPhysicsChunkCollisionSettings(), copy.getPhysicsChunkCollisionSettings()); - assertNotSame(original.getVisualSyncSettings(), copy.getVisualSyncSettings()); - assertNotSame(original.getSolverSettings(), copy.getSolverSettings()); - assertNotSame(original.getVisualMaterializationSettings(), - copy.getVisualMaterializationSettings()); - assertNotSame(original.getCollisionLodSettings(), copy.getCollisionLodSettings()); - assertEquals(PhysicsChunkCollisionMode.STREAMING, copy.getPhysicsChunkCollisionSettings().getMode()); - assertEquals(12, copy.getPhysicsChunkCollisionSettings().getRadius()); - assertEquals(6, copy.getPhysicsChunkCollisionSettings().getBodyRadius()); - assertEquals(180, copy.getPhysicsChunkCollisionSettings().getTtlTicks()); - assertTrue(copy.getPhysicsChunkCollisionSettings().isNativeVoxelCollisionEnabled()); - assertEquals(160, copy.getVisualSyncSettings().getVisualMaxSyncRadius()); - assertEquals(80, copy.getVisualSyncSettings().getVisualFullSyncRadius()); - assertEquals(2, copy.getVisualMaterializationSettings().getDetachedVisualInterestRefreshIntervalTicks()); - assertEquals(3, copy.getVisualMaterializationSettings().getDetachedVisualCandidateRefreshIntervalTicks()); - assertEquals(12, copy.getVisualMaterializationSettings().getDetachedVisualVisibilityCheckIntervalTicks()); - assertTrue(copy.getVisualSyncSettings().isVisualSnapshotPredictionEnabled()); - assertEquals(0.08f, copy.getVisualSyncSettings().getVisualSnapshotPredictionMaxSeconds(), 0.0001f); - assertTrue(copy.getVisualSyncSettings().isVisualSnapshotSmoothingEnabled()); - assertEquals(18.0f, copy.getVisualSyncSettings().getVisualSnapshotSmoothingRate(), 0.0001f); - assertTrue(copy.getCollisionLodSettings().isCollisionLodEnabled()); - assertEquals(32, copy.getCollisionLodSettings().getCollisionLodNearRadius()); - assertEquals(96, copy.getCollisionLodSettings().getCollisionLodMidRadius()); - assertEquals(8, copy.getCollisionLodSettings().getCollisionLodHysteresis()); - assertEquals(6, copy.getCollisionLodSettings().getCollisionLodRefreshIntervalTicks()); - assertFalse(copy.getCollisionLodSettings().isCollisionLodFarSleepEnabled()); - } - -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastHitViewTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastHitViewTest.java index ccce2121..89b72ab9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastHitViewTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastHitViewTest.java @@ -1,49 +1,68 @@ package dev.hytalemodding.impulse.core.plugin.simulation; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertSame; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import java.util.ArrayList; import org.joml.Vector3f; import org.junit.jupiter.api.Test; class RaycastHitViewTest { @Test - void storesGeometryAsScalarsWhileKeepingDefensiveVectorAccessors() { - Vector3f point = new Vector3f(1.0f, 2.0f, 3.0f); - Vector3f normal = new Vector3f(0.0f, 1.0f, 0.0f); - RaycastHitView view = new RaycastHitView(null, - PhysicsBodyType.DYNAMIC, - point, - normal, - ShapeType.BOX, - 0.25f, - 4.5f); - point.set(9.0f, 9.0f, 9.0f); - normal.set(8.0f, 8.0f, 8.0f); + void infersBodyAndShapeTypesFromBodyRefComponents() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("raycast-hit-view-type-inference-test")), + EmptyResourceStorage.get()); + try { + Holder holder = registry.newHolder(); + holder.putComponent(DynamicsComponent.getComponentType(), + new DynamicsComponent(PhysicsBodyType.KINEMATIC, + 0.0f, + 0.0f, + 0.0f, + false)); + holder.putComponent(ShapeComponent.getComponentType(), + new ShapeComponent(ShapeType.CAPSULE, + 0.5f, + 0.5f, + 0.5f, + 0.25f, + 1.0f, + PhysicsAxis.Y, + 0.0f, + "")); + Ref bodyRef = store.addEntity(holder, AddReason.SPAWN); + RaycastHitView view = new RaycastHitView(bodyRef, + new Vector3f(1.0f, 2.0f, 3.0f), + new Vector3f(0.0f, 1.0f, 0.0f), + 0.25f, + 4.5f); - assertEquals(1.0f, view.pointX(), 0.00001f); - assertEquals(2.0f, view.pointY(), 0.00001f); - assertEquals(3.0f, view.pointZ(), 0.00001f); - assertEquals(0.0f, view.normalX(), 0.00001f); - assertEquals(1.0f, view.normalY(), 0.00001f); - assertEquals(0.0f, view.normalZ(), 0.00001f); - - Vector3f pointCopy = view.point(); - pointCopy.set(7.0f, 7.0f, 7.0f); - assertEquals(1.0f, view.pointX(), 0.00001f); - assertEquals(2.0f, view.pointY(), 0.00001f); - assertEquals(3.0f, view.pointZ(), 0.00001f); - assertNotSame(pointCopy, view.point()); - - Vector3f target = new Vector3f(); - assertSame(target, view.copyPointTo(target)); - assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), target); - assertSame(target, view.copyNormalTo(target)); - assertEquals(new Vector3f(0.0f, 1.0f, 0.0f), target); + assertEquals(PhysicsBodyType.KINEMATIC, view.bodyType()); + assertEquals(ShapeType.CAPSULE, view.shapeType()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } } } diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index d57b74d5..048063a9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -21,10 +21,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -79,24 +77,14 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsThreading.requireWorldThread(physicsStore, "spawn an example PhysicsStore body entity"); UUID bodyUuid = UUID.randomUUID(); - BodyEntityDescriptor descriptor = PhysicsBodyEntities.dynamicBody(space.spaceRef(), + Holder bodyHolder = PhysicsBodyEntities.dynamicBodyHolder(space.spaceRef(), bodyUuid, toVector3f(position), PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), 1.0f, RigidBodySpawnSettings.material(0.5f, 0.5f), null); - Ref bodyRef = physicsStore.addEntity( - PhysicsEntities.bodyHolder(physicsStore, - descriptor.bodyUuid(), - descriptor.body(), - descriptor.dynamics(), - descriptor.target(), - descriptor.collider(), - descriptor.shape(), - descriptor.material(), - descriptor.filter()), - AddReason.SPAWN); + Ref bodyRef = physicsStore.addEntity(bodyHolder, AddReason.SPAWN); assert bodyRef != null; store.addEntity(attachedPhysicsBlockEntityHolder(time, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index c9064912..500f55c4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.examples.commands; import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.vector.Transform; @@ -14,7 +15,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.TargetUtil; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; @@ -22,15 +23,14 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; @@ -217,10 +217,10 @@ private static GrabPhysicsState createGrabControl(@Nonnull World world, } @Nonnull - private static BodyEntityDescriptor anchorBodyEntity(@Nonnull Ref spaceRef, + private static Holder anchorBodyEntity(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f hitPoint) { - return PhysicsBodyEntities.body(spaceRef, + return PhysicsBodyEntities.bodyHolder(spaceRef, bodyUuid, hitPoint, PhysicsShapeSpec.sphere(0.08f), diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index a8e163f5..febbbe4b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -13,7 +13,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 894c3bdc..ee043f4e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -13,7 +13,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -92,14 +91,14 @@ private static void spawnSphere(@Nonnull Store store, float friction, float speed) { UUID bodyUuid = UUID.randomUUID(); - BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(spaceRef, + var bodyHolder = ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.sphere(0.5f), 1.0f, RigidBodySpawnSettings.material(friction, restitution), new Vector3f(speed, 0.0f, 0.0f)); - Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, descriptor); + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, bodyHolder); store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java index 1340f75e..62ecec99 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PersistenceCommand.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.RestoreRequestResult; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.SaveResult; import dev.hytalemodding.impulse.core.plugin.persistence.PhysicsPersistence.Status; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java index 54041313..e9d38ac4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsChunkExampleCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionStats; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index ab23f2b6..99a83531 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -21,18 +21,18 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsRaycasts; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsEventCollectionMode; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockPolicy; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; @@ -168,20 +168,23 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Vector3f targetPosition = vector(spawn); - var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyEntity(spaceRef, - bodyUuid, - targetPosition, - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 0.0f, - RigidBodySpawnSettings.material(0.5f, 0.2f), - null), + Holder bodyHolder = ExamplePhysicsUtils.bodyEntity(spaceRef, + bodyUuid, + targetPosition, + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 0.0f, + RigidBodySpawnSettings.material(0.5f, 0.2f), + null); + bodyHolder.tryRemoveComponent(DynamicsComponent.getComponentType()); + bodyHolder.addComponent(DynamicsComponent.getComponentType(), new DynamicsComponent(PhysicsBodyType.KINEMATIC, 0.0f, 0.0f, 0.0f, - false), - target(targetPosition)); + false)); + bodyHolder.tryRemoveComponent(TargetComponent.getComponentType()); + bodyHolder.addComponent(TargetComponent.getComponentType(), target(targetPosition)); + var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, bodyHolder); TimeResource time = store.getResource(TimeResource.getResourceType()); ExamplePhysicsUtils.attachBlockBody(store, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index c991b0a7..01da3aa5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -13,8 +13,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.util.TargetUtil; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index 94e0bd60..ad1197cd 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -14,7 +14,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -104,14 +103,14 @@ private static void spawn(@Nonnull Store store, int xOffset) { Vector3d position = new Vector3d(origin).add(xOffset, 0.0, 0.0); UUID bodyUuid = UUID.randomUUID(); - BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(spaceRef, + var bodyHolder = ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), shape(type, axis), 1.0f, RigidBodySpawnSettings.material(0.7f, 0.35f), null); - Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, descriptor); + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, bodyHolder); store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index b690c816..ad40fc17 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -13,8 +13,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 41cd1453..188a2f72 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -17,12 +17,12 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSpaceSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; @@ -30,7 +30,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.Iterator; @@ -155,13 +155,13 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } Store physicsStore = PhysicsThreading.store(world); - PhysicsSpaceSettings settings = configureStressRuntime(physicsStore, + StressRuntimeSettings runtimeSettings = configureStressRuntime(physicsStore, spaceRef, mode, visibility, visualSettings, collisionLod); - if (settings == null) { + if (runtimeSettings == null) { ctx.sender().sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() + " no longer exists.")); return CompletableFuture.completedFuture(null); @@ -172,7 +172,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, long prewarmStartNanos = System.nanoTime(); int prewarmedSections = prewarmStressTerrain(world, spaceRef, - settings, + runtimeSettings.chunkCollisionSettings(), mode, layout, count); @@ -226,11 +226,11 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, 0L); } PhysicsChunkCollisionSettings chunkCollisionSettings = - settings.getPhysicsChunkCollisionSettings(); + runtimeSettings.chunkCollisionSettings(); PhysicsVisualMaterializationSettings visualMaterializationSettings = - settings.getVisualMaterializationSettings(); - PhysicsVisualSyncSettings visualSyncSettings = settings.getVisualSyncSettings(); - PhysicsCollisionLodSettings collisionLodSettings = settings.getCollisionLodSettings(); + runtimeSettings.visualMaterializationSettings(); + PhysicsVisualSyncSettings visualSyncSettings = runtimeSettings.visualSyncSettings(); + PhysicsCollisionLodSettings collisionLodSettings = runtimeSettings.collisionLodSettings(); PhysicsWorldSettings worldSettings = PhysicsWorlds.settings(physicsStore); ctx.sender().sendMessage(Message.raw("Added " + count @@ -274,38 +274,45 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } @Nullable - private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store physicsStore, + private static StressRuntimeSettings configureStressRuntime(@Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull StressMode mode, @Nonnull StressVisibility visibility, @Nonnull StressVisualSettings visualSettings, @Nullable Boolean collisionLod) { - PhysicsSpaceSettings currentSettings = PhysicsSpaces.settings(physicsStore, spaceRef); - if (currentSettings == null) { + PhysicsSolverSettings solverSettings = PhysicsSpaces.solverSettings(physicsStore, spaceRef); + PhysicsExtensionSettings extensionSettings = PhysicsSpaces.extensionSettings(physicsStore, + spaceRef); + PhysicsChunkCollisionSettings chunkCollisionSettings = + PhysicsSpaces.chunkCollisionSettings(physicsStore, spaceRef); + PhysicsCollisionLodSettings collisionLodSettings = + PhysicsSpaces.collisionLodSettings(physicsStore, spaceRef); + PhysicsVisualMaterializationSettings visualMaterializationSettings = + PhysicsSpaces.visualMaterializationSettings(physicsStore, spaceRef); + PhysicsVisualSyncSettings visualSyncSettings = PhysicsSpaces.visualSyncSettings(physicsStore, + spaceRef); + if (solverSettings == null + || extensionSettings == null + || chunkCollisionSettings == null + || collisionLodSettings == null + || visualMaterializationSettings == null + || visualSyncSettings == null) { return null; } - PhysicsSpaceSettings settings = new PhysicsSpaceSettings(currentSettings); - PhysicsSolverSettings solverSettings = settings.getSolverSettings(); solverSettings.setSolverIterations(1); solverSettings.setStabilizationIterations(1); - settings.getExtensionSettings().setInt(RAPIER_SOLVER_EXTENSION_ID, + extensionSettings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS, 1); solverSettings.setDynamicSleepTuning( PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_LINEAR_THRESHOLD, PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_ANGULAR_THRESHOLD, PhysicsSolverSettings.DEFAULT_DYNAMIC_SLEEP_TIME_UNTIL_SLEEP); - PhysicsChunkCollisionSettings chunkCollisionSettings = - settings.getPhysicsChunkCollisionSettings(); - PhysicsCollisionLodSettings collisionLodSettings = settings.getCollisionLodSettings(); chunkCollisionSettings.setMode(PhysicsChunkCollisionMode.STREAMING); chunkCollisionSettings.setBodyRadius( Math.max(chunkCollisionSettings.getBodyRadius(), STRESS_BODY_CHUNK_COLLISION_RADIUS)); if (mode.usesDetachedBodies()) { - PhysicsVisualMaterializationSettings visualMaterializationSettings = - settings.getVisualMaterializationSettings(); - PhysicsVisualSyncSettings visualSyncSettings = settings.getVisualSyncSettings(); visualMaterializationSettings.setDetachedVisualMaterializationEnabled( mode == StressMode.DETACHED_VIEW); visualSyncSettings.setVisualVisibilityCullingEnabled(mode == StressMode.DETACHED_VIEW @@ -331,19 +338,20 @@ private static PhysicsSpaceSettings configureStressRuntime(@Nonnull Store spaceRef, - @Nonnull PhysicsSpaceSettings settings, + @Nonnull PhysicsChunkCollisionSettings chunkCollisionSettings, @Nonnull StressMode mode, @Nonnull StressLayout layout, int count) { - PhysicsChunkCollisionSettings chunkCollisionSettings = - settings.getPhysicsChunkCollisionSettings(); if (!mode.usesDetachedBodies() || chunkCollisionSettings.getMode() != PhysicsChunkCollisionMode.STREAMING) { return 0; @@ -624,6 +632,13 @@ private record StressVisualSettings(int materializationRadius, boolean smoothingEnabled) { } + private record StressRuntimeSettings( + @Nonnull PhysicsChunkCollisionSettings chunkCollisionSettings, + @Nonnull PhysicsVisualMaterializationSettings visualMaterializationSettings, + @Nonnull PhysicsVisualSyncSettings visualSyncSettings, + @Nonnull PhysicsCollisionLodSettings collisionLodSettings) { + } + private record StressSpawnTiming(long setupWallNanos, long physicsStoreApplyNanos, long visualAttachNanos) { diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 571d7d32..a25d804b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -13,7 +13,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index f4450d93..d01eacc7 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -12,8 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsRaycasts; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsRaycasts; import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.ArrayList; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index 7475dbfb..85af67dc 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -14,7 +14,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; @@ -125,14 +124,14 @@ private static void spawn(@Nonnull Store store, double xOffset) { Vector3d position = new Vector3d(base).add(xOffset, 0.0, 0.0); UUID bodyUuid = UUID.randomUUID(); - BodyEntityDescriptor descriptor = ExamplePhysicsUtils.bodyEntity(spaceRef, + var bodyHolder = ExamplePhysicsUtils.bodyEntity(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), shape(type, axis), 1.0f, RigidBodySpawnSettings.material(0.6f, 0.25f), null); - Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, descriptor); + Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, bodyHolder); store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index fb6603ef..c616b7b5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -17,7 +17,10 @@ import com.hypixel.hytale.server.core.modules.entity.damage.Damage; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; @@ -25,7 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; @@ -252,10 +255,15 @@ private static FragmentBlock removeFragmentCandidate(@Nonnull World world, if (chunk == null) { return null; } + BlockChunk blockChunk = loadedBlockChunk(world, x, z); + if (blockChunk == null) { + return null; + } + BlockSection blockSection = loadedBlockSection(world, x, y, z); int localX = chunkBlockCoordinate(x); int localZ = chunkBlockCoordinate(z); - int blockId = chunk.getBlock(localX, y, localZ); - int rotation = chunk.getRotation(localX, y, localZ).index(); + int blockId = blockChunk.getBlock(localX, y, localZ); + int rotation = blockRotationIndex(blockSection, localX, y, localZ); var blockTypeStore = BlockType.getAssetStore(); if (blockTypeStore == null) { return null; @@ -458,7 +466,56 @@ private static int maxGroupCollisionRadius(@Nonnull List groups) @Nullable private static WorldChunk loadedChunk(@Nonnull World world, int x, int z) { - return world.getChunkIfLoaded(ChunkUtil.indexChunkFromBlock(x, z)); + Ref chunkRef = loadedChunkRef(world, x, z); + if (chunkRef == null) { + return null; + } + Store store = world.getChunkStore().getStore(); + return store.getComponentConcurrent(chunkRef, WorldChunk.getComponentType()); + } + + @Nullable + private static BlockChunk loadedBlockChunk(@Nonnull World world, int x, int z) { + Ref chunkRef = loadedChunkRef(world, x, z); + if (chunkRef == null) { + return null; + } + Store store = world.getChunkStore().getStore(); + return store.getComponentConcurrent(chunkRef, BlockChunk.getComponentType()); + } + + @Nullable + private static BlockSection loadedBlockSection(@Nonnull World world, int x, int y, int z) { + if (y < ChunkUtil.MIN_Y || y > ChunkUtil.HEIGHT_MINUS_1) { + return null; + } + ChunkStore chunkStore = world.getChunkStore(); + Ref sectionRef = chunkStore.getChunkSectionReference( + ChunkUtil.chunkCoordinate(x), + ChunkUtil.indexSection(y), + ChunkUtil.chunkCoordinate(z)); + if (sectionRef == null || !sectionRef.isValid()) { + return null; + } + Store store = chunkStore.getStore(); + return store.getComponentConcurrent(sectionRef, BlockSection.getComponentType()); + } + + @Nullable + private static Ref loadedChunkRef(@Nonnull World world, int x, int z) { + Ref chunkRef = world.getChunkStore() + .getChunkReference(ChunkUtil.indexChunkFromBlock(x, z)); + return chunkRef != null && chunkRef.isValid() ? chunkRef : null; + } + + private static int blockRotationIndex(@Nullable BlockSection section, + int localX, + int y, + int localZ) { + if (section == null) { + return 0; + } + return section.getRotationIndex(localX, y, localZ); } @Nonnull @@ -794,6 +851,7 @@ private EntityDamageExplosionConfig(int radius) { 1.0f, null, null, + false, false) }; soundEventId = EXPLOSION_SOUND_EVENT_ID; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java index 5325592b..4e38af92 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseContactSystem.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockRuntime; import dev.hytalemodding.impulse.examples.explosive.ExplosiveFuseComponent; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java index 66cdf2fe..8a6befe4 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/systems/ExplosiveFuseTickSystem.java @@ -13,9 +13,9 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index eb5ff703..1aae7201 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -14,19 +14,16 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physicsstore.BodyEntityDescriptor; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physicsstore.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -102,41 +99,29 @@ public static SpaceSelection spaceSelection(@Nonnull CommandContext ctx, @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyEntityDescriptor descriptor) { + @Nonnull Holder holder) { Store store = PhysicsThreading.store(world); - return addPhysicsStoreBody(store, descriptor); + return addPhysicsStoreBody(store, holder); } @Nonnull public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyEntityDescriptor descriptor, + @Nonnull Holder holder, @Nonnull BodyCommandComponent command) { Store store = PhysicsThreading.store(world); - Ref bodyRef = addPhysicsStoreBody(store, descriptor); + Ref bodyRef = addPhysicsStoreBody(store, holder); PhysicsBodies.appendCommand(store, bodyRef, command); return bodyRef; } - @Nonnull - public static Ref addPhysicsStoreBody(@Nonnull World world, - @Nonnull BodyEntityDescriptor descriptor, - @Nonnull DynamicsComponent dynamics, - @Nullable TargetComponent target) { - Store store = PhysicsThreading.store(world); - return addPhysicsStoreBody(store, descriptor, dynamics, target); - } - public static void addPhysicsStoreBodies(@Nonnull World world, - @Nonnull Iterable descriptors) { - Objects.requireNonNull(descriptors, "descriptors"); + @Nonnull Iterable> bodyHolders) { + Objects.requireNonNull(bodyHolders, "bodyHolders"); Store store = PhysicsThreading.store(world); PhysicsThreading.requireWorldThread(store, "add PhysicsStore body entities"); List> holders = new ArrayList<>(); - for (BodyEntityDescriptor descriptor : descriptors) { - holders.add(bodyHolder(store, - Objects.requireNonNull(descriptor, "descriptor"), - descriptor.dynamics(), - descriptor.target())); + for (Holder holder : bodyHolders) { + holders.add(Objects.requireNonNull(holder, "holder")); } if (!holders.isEmpty()) { @SuppressWarnings("unchecked") @@ -147,37 +132,9 @@ public static void addPhysicsStoreBodies(@Nonnull World world, @Nonnull private static Ref addPhysicsStoreBody(@Nonnull Store store, - @Nonnull BodyEntityDescriptor descriptor) { - return addPhysicsStoreBody(store, - descriptor, - descriptor.dynamics(), - descriptor.target()); - } - - @Nonnull - private static Ref addPhysicsStoreBody(@Nonnull Store store, - @Nonnull BodyEntityDescriptor descriptor, - @Nonnull DynamicsComponent dynamics, - @Nullable TargetComponent target) { - Objects.requireNonNull(descriptor, "descriptor"); + @Nonnull Holder holder) { PhysicsThreading.requireWorldThread(store, "add a PhysicsStore body entity"); - return store.addEntity(bodyHolder(store, descriptor, dynamics, target), AddReason.SPAWN); - } - - @Nonnull - private static Holder bodyHolder(@Nonnull Store store, - @Nonnull BodyEntityDescriptor descriptor, - @Nonnull DynamicsComponent dynamics, - @Nullable TargetComponent target) { - return PhysicsEntities.bodyHolder(store, - descriptor.bodyUuid(), - descriptor.body(), - Objects.requireNonNull(dynamics, "dynamics"), - target, - descriptor.collider(), - descriptor.shape(), - descriptor.material(), - descriptor.filter()); + return store.addEntity(Objects.requireNonNull(holder, "holder"), AddReason.SPAWN); } @Nonnull @@ -222,14 +179,14 @@ public static SpaceId spaceId(@Nonnull CommandContext ctx, } @Nonnull - public static BodyEntityDescriptor bodyEntity(@Nonnull Ref spaceRef, + public static Holder bodyEntity(@Nonnull Ref spaceRef, @Nonnull UUID bodyUuid, @Nonnull Vector3f bodyCenter, @Nonnull PhysicsShapeSpec shape, float mass, @Nonnull RigidBodySpawnSettings settings, @Nullable Vector3f linearVelocity) { - return PhysicsBodyEntities.dynamicBody(spaceRef, + return PhysicsBodyEntities.dynamicBodyHolder(spaceRef, bodyUuid, bodyCenter, shape, @@ -347,10 +304,10 @@ private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref bodies = new ArrayList<>(batch.size()); + List> bodies = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); - bodies.add(PhysicsBodyEntities.body(spaceRef, + bodies.add(PhysicsBodyEntities.bodyHolder(spaceRef, bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -514,10 +471,10 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store descriptors = new ArrayList<>(batch.size()); + List> bodyHolders = new ArrayList<>(batch.size()); for (int i = 0; i < batch.size(); i++) { UUID bodyUuid = batch.bodyUuid(i); - descriptors.add(bodyEntity(spaceRef, + bodyHolders.add(bodyEntity(spaceRef, bodyUuid, new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), shape, @@ -527,7 +484,7 @@ private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store bodies, + private record DynamicBodyBatchPlan(@Nonnull List> bodies, long setupWallNanos) { DynamicBodyBatchPlan { diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java deleted file mode 100644 index 63ccd634..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DropCommandTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.hytalemodding.impulse.examples.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; - -class DropCommandTest { - - @Test - void dropCommandOwnsBodyAndVisualAssembly() throws IOException { - String source = Files.readString(Path.of("src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java")); - - assertTrue(source.contains("PhysicsBodyEntities.dynamicBody(")); - assertTrue(source.contains("PhysicsEntities.bodyHolder(")); - assertTrue(source.contains("BodyAttachmentComponent.impulseOwnedVisual(")); - assertFalse(source.contains("ExamplePhysicsUtils.bodyEntity(")); - assertFalse(source.contains("ExamplePhysicsUtils.toVector3f(")); - assertFalse(source.contains("ExamplePhysicsUtils.addPhysicsStoreBody(")); - assertFalse(source.contains("ExamplePhysicsUtils.attachedPhysicsStoreBlockEntityHolder(")); - } -} From 94c23f8a17fd3f66fc70ab2cec3197584614e4a9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 08:55:53 +0200 Subject: [PATCH 478/534] refactor(core): move joint type into components Signed-off-by: Blovien --- .../impulse/core/internal/persistence/PersistentJointDto.java | 2 +- .../core/internal/resources/joint/PhysicsJointRegistration.java | 2 +- .../core/internal/resources/joint/PhysicsJointRegistry.java | 2 +- .../impulse/core/plugin/components/JointComponent.java | 1 - .../core/plugin/{simulation => components}/JointType.java | 2 +- .../impulse/core/plugin/physics/PhysicsJointEntities.java | 2 +- .../internal/physics/PhysicsStoreTopologyMutationsTest.java | 2 +- .../hytalemodding/impulse/examples/commands/GrabCommand.java | 2 +- .../hytalemodding/impulse/examples/commands/JointsCommand.java | 2 +- .../impulse/examples/commands/stress/StressJointsCommand.java | 2 +- 10 files changed, 9 insertions(+), 10 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation => components}/JointType.java (75%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java index 0cd320f3..4902aec3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java index 3cf26cc2..19a380a4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java @@ -2,7 +2,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java index 8d53eee1..fe1be3ff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java @@ -2,7 +2,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java index c0e4ef90..881d5e3c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointComponent.java @@ -9,7 +9,6 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointType.java similarity index 75% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointType.java index 5bd35e2c..a7060e9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/JointType.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/JointType.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.components; /** * Public joint kinds supported by PhysicsStore joint entities and snapshot views. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsJointEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsJointEntities.java index 246aed13..f956ad83 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsJointEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsJointEntities.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java index bced1493..f1d06417 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java @@ -42,7 +42,7 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 500f55c4..e80ac737 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -33,7 +33,7 @@ import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index febbbe4b..b32eb97a 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index a25d804b..97f09aab 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -15,7 +15,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.JointType; +import dev.hytalemodding.impulse.core.plugin.components.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; From f7c318e7e3cb1831e6b8e9f462ab4ae038c920b7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 09:09:02 +0200 Subject: [PATCH 479/534] fix(core): batch PhysicsStore body cleanup Signed-off-by: Blovien --- .../physics/PhysicsStoreRowCleanup.java | 36 ++- .../physics/PhysicsTopologyMutations.java | 34 +- .../systems/StaleBodyRemovalSystem.java | 20 +- .../physics/PhysicsStoreRowCleanupTest.java | 169 ++++++++++ .../systems/StaleBodyRemovalSystemTest.java | 294 ++++++++++++++++++ 5 files changed, 528 insertions(+), 25 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index 34550b6b..05587d80 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -83,20 +83,10 @@ public static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, public static void clearBodyCopiedState(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { - PhysicsControlRuntimeStates.clearControlled(bodyRef); - store.getResource(PhysicsSnapshotResource.getResourceType()).removeBody(bodyUuid); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()).removeBody(bodyUuid); + clearBodyCopiedState(store, List.of(new BodyEntityRemoval(bodyUuid, bodyRef, null))); } - public static void removeBodyEntity(@Nonnull Store store, - @Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nullable String payloadResourceKey) { - clearBodyCopiedState(store, bodyUuid, bodyRef); - removeBodyEntityAfterCopiedStateCleared(store, bodyUuid, bodyRef, payloadResourceKey); - } - - public static void removeBodyEntities(@Nonnull Store store, + public static void clearBodyCopiedState(@Nonnull Store store, @Nonnull Collection removals) { Objects.requireNonNull(removals, "removals"); if (removals.isEmpty()) { @@ -111,16 +101,32 @@ public static void removeBodyEntities(@Nonnull Store store, store.getResource(PhysicsSnapshotResource.getResourceType()).removeBodies(bodyUuids); store.getResource(PhysicsBodyRegistrationResource.getResourceType()) .removeBodies(bodyUuids); + } + + public static void removeBodyEntity(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nullable String payloadResourceKey) { + clearBodyCopiedState(store, bodyUuid, bodyRef); + removeBodyEntityRow(store, bodyUuid, bodyRef, payloadResourceKey); + } + + public static void removeBodyEntities(@Nonnull Store store, + @Nonnull Collection removals) { + Objects.requireNonNull(removals, "removals"); + if (removals.isEmpty()) { + return; + } + clearBodyCopiedState(store, removals); for (BodyEntityRemoval removal : removals) { - removeBodyEntityAfterCopiedStateCleared(store, + removeBodyEntityRow(store, removal.bodyUuid(), removal.bodyRef(), removal.payloadResourceKey()); } } - private static void removeBodyEntityAfterCopiedStateCleared( - @Nonnull Store store, + static void removeBodyEntityRow(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef, @Nullable String payloadResourceKey) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java index 5862b99f..34f06945 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java @@ -14,6 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; @@ -59,7 +60,7 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( TopologyCounts removed = countBackendTopology(runtime); List removals = collectRows(store, null, null, null, null); removeRuntimeRows(runtime, identity, removals); - removeRows(store, removals); + removeRows(store, removals, false); clearCopiedBodyState(store); store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()).clear(); store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); @@ -271,16 +272,27 @@ private static boolean sameRef(@Nonnull Ref first, private static void removeRows(@Nonnull Store store, @Nonnull List removals) { + removeRows(store, removals, true); + } + + private static void removeRows(@Nonnull Store store, + @Nonnull List removals, + boolean clearCopiedState) { boolean removedAny = false; - for (RowRemoval removal : removals.stream() + List orderedRemovals = removals.stream() .sorted((first, second) -> Integer.compare(second.ref().getIndex(), first.ref().getIndex())) - .toList()) { + .toList(); + if (clearCopiedState) { + PhysicsStoreRowCleanup.clearBodyCopiedState(store, + bodyEntityRemovals(orderedRemovals)); + } + for (RowRemoval removal : orderedRemovals) { if (!removal.ref().isValid()) { continue; } if (removal.kind() == RowKind.BODY) { - PhysicsStoreRowCleanup.removeBodyEntity(store, + PhysicsStoreRowCleanup.removeBodyEntityRow(store, removal.rowUuid(), removal.ref(), removal.payloadResourceKey()); @@ -296,6 +308,20 @@ private static void removeRows(@Nonnull Store store, } } + @Nonnull + private static List bodyEntityRemovals( + @Nonnull List removals) { + List bodyRemovals = new ArrayList<>(); + for (RowRemoval removal : removals) { + if (removal.kind() == RowKind.BODY && removal.ref().isValid()) { + bodyRemovals.add(new BodyEntityRemoval(removal.rowUuid(), + removal.ref(), + removal.payloadResourceKey())); + } + } + return bodyRemovals; + } + private static void clearCopiedBodyState(@Nonnull Store store) { store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); store.getResource(PhysicsBodyRegistrationResource.getResourceType()).clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index 3edc8589..2bf4e3ed 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -9,6 +9,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -70,7 +71,7 @@ private static void removeStaleBodies(@Nonnull Store store, return; } List orderedBodies = currentBodyRefs(identity, staleBodies); - boolean removedAny = false; + List bodyEntityRemovals = new ArrayList<>(orderedBodies.size()); for (BoundBody body : orderedBodies) { try { PhysicsStoreRowCleanup.removeRuntimeBody(runtime, @@ -79,16 +80,14 @@ private static void removeStaleBodies(@Nonnull Store store, body.bodyRef(), body.backendRuntime()); } catch (RuntimeException exception) { + removeBodyEntities(store, bodyEntityRemovals); restore.markFailed("PhysicsStore body " + body.bodyUuid() + " failed backend removal: " + exception.getMessage()); return; } - PhysicsStoreRowCleanup.removeBodyEntity(store, body.bodyUuid(), body.bodyRef(), null); - removedAny = true; - } - if (removedAny) { - PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); + bodyEntityRemovals.add(new BodyEntityRemoval(body.bodyUuid(), body.bodyRef(), null)); } + removeBodyEntities(store, bodyEntityRemovals); } private static boolean removeDependentJoints(@Nonnull Store store, @@ -125,6 +124,15 @@ private static boolean removeDependentJoints(@Nonnull Store store, return true; } + private static void removeBodyEntities(@Nonnull Store store, + @Nonnull List removals) { + if (removals.isEmpty()) { + return; + } + PhysicsStoreRowCleanup.removeBodyEntities(store, removals); + PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); + } + @Nonnull private static List currentBodyRefs(@Nonnull PhysicsIdentityIndexResource identity, @Nonnull List staleBodies) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java new file mode 100644 index 00000000..74f0e7c0 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java @@ -0,0 +1,169 @@ +package dev.hytalemodding.impulse.core.internal.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class PhysicsStoreRowCleanupTest { + + @Test + void clearBodyCopiedStateRemovesMultipleBodiesWithoutRemovingRows() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("row-cleanup-batch-copied-state")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(1); + UUID firstBodyUuid = uuid(2); + UUID secondBodyUuid = uuid(3); + UUID retainedBodyUuid = uuid(4); + Ref firstBodyRef = addIdentityRow(store, firstBodyUuid); + Ref secondBodyRef = addIdentityRow(store, secondBodyUuid); + Ref retainedBodyRef = addIdentityRow(store, retainedBodyUuid); + publishCopiedState(store, + spaceUuid, + firstBodyUuid, + firstBodyRef, + secondBodyUuid, + secondBodyRef, + retainedBodyUuid, + retainedBodyRef); + + PhysicsStoreRowCleanup.clearBodyCopiedState(store, + List.of(new BodyEntityRemoval(firstBodyUuid, firstBodyRef, null), + new BodyEntityRemoval(secondBodyUuid, secondBodyRef, null))); + + PhysicsSnapshotResource snapshots = + store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = store.getResource( + PhysicsBodyRegistrationResource.getResourceType()); + assertNull(snapshots.getBody(firstBodyUuid)); + assertNull(snapshots.getBody(secondBodyUuid)); + assertNotNull(snapshots.getBody(retainedBodyUuid)); + assertNull(registrations.getBodySpaceId(firstBodyUuid)); + assertNull(registrations.getBodySpaceId(secondBodyUuid)); + assertNotNull(registrations.getBodySpaceId(retainedBodyUuid)); + assertEquals(1, registrations.getBodyRegistrationCount()); + assertNotNull(store.getComponent(firstBodyRef, UuidComponent.getComponentType())); + assertNotNull(store.getComponent(secondBodyRef, UuidComponent.getComponentType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static Ref addIdentityRow(@Nonnull Store store, + @Nonnull UUID bodyUuid) { + Ref ref = store.addEntity(PhysicsEntities.entityHolder(store, bodyUuid), + AddReason.SPAWN); + assertNotNull(ref); + return ref; + } + + private static void publishCopiedState(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull UUID firstBodyUuid, + @Nonnull Ref firstBodyRef, + @Nonnull UUID secondBodyUuid, + @Nonnull Ref secondBodyRef, + @Nonnull UUID retainedBodyUuid, + @Nonnull Ref retainedBodyRef) { + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(firstBodyRef, firstBodyUuid, spaceUuid), + snapshot(secondBodyRef, secondBodyUuid, spaceUuid), + snapshot(retainedBodyRef, retainedBodyUuid, spaceUuid)))); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .publish(1L, + List.of(publication(firstBodyRef, firstBodyUuid), + publication(secondBodyRef, secondBodyUuid), + publication(retainedBodyRef, retainedBodyUuid))); + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + return PhysicsBodySnapshot.of(bodyRef, + bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + + @Nonnull + private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( + @Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid) { + return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, + bodyUuid, + new SpaceId(42)); + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java new file mode 100644 index 00000000..3aedf830 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java @@ -0,0 +1,294 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class StaleBodyRemovalSystemTest { + + @Test + void tickRemovesMultipleStaleBodiesAndTheirCopiedStateTogether() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("stale-body-removal-batch")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(1); + UUID firstStaleUuid = uuid(2); + UUID secondStaleUuid = uuid(3); + UUID retainedUuid = uuid(4); + BoundSpace space = addBoundSpace(store, + spaceUuid, + new BackendId("test:stale-body-removal")); + Ref firstStaleRef = addBody(store, spaceUuid, space.ref(), firstStaleUuid); + Ref secondStaleRef = addBody(store, + spaceUuid, + space.ref(), + secondStaleUuid); + Ref retainedRef = addBody(store, spaceUuid, space.ref(), retainedUuid); + bindBody(store, space, firstStaleUuid, firstStaleRef, 0.0f); + bindBody(store, space, secondStaleUuid, secondStaleRef, 2.0f); + bindBody(store, space, retainedUuid, retainedRef, 4.0f); + publishCopiedState(store, + spaceUuid, + firstStaleUuid, + firstStaleRef, + secondStaleUuid, + secondStaleRef, + retainedUuid, + retainedRef); + store.removeComponent(firstStaleRef, BodyComponent.getComponentType()); + store.removeComponent(secondStaleRef, BodyComponent.getComponentType()); + + new StaleBodyRemovalSystem().tick(0.0f, 0, store); + + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + PhysicsSnapshotResource snapshots = + store.getResource(PhysicsSnapshotResource.getResourceType()); + PhysicsBodyRegistrationResource registrations = store.getResource( + PhysicsBodyRegistrationResource.getResourceType()); + assertFalse(store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .isFailed()); + assertNull(identity.getByUuid(firstStaleUuid)); + assertNull(identity.getByUuid(secondStaleUuid)); + assertNotNull(identity.getByUuid(retainedUuid)); + assertNull(runtime.getBodyHandle(firstStaleRef)); + assertNull(runtime.getBodyHandle(secondStaleRef)); + assertNotNull(runtime.getBodyHandle(retainedRef)); + assertEquals(1, space.runtime().bodyCount(space.handle().value())); + assertNull(snapshots.getBody(firstStaleUuid)); + assertNull(snapshots.getBody(secondStaleUuid)); + assertNotNull(snapshots.getBody(retainedUuid)); + assertNull(registrations.getBodySpaceId(firstStaleUuid)); + assertNull(registrations.getBodySpaceId(secondStaleUuid)); + assertNotNull(registrations.getBodySpaceId(retainedUuid)); + assertFalse(firstStaleRef.isValid()); + assertFalse(secondStaleRef.isValid()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static BoundSpace addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + PhysicsBackendRuntime runtime = + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + BackendSpaceHandle spaceHandle = + new BackendSpaceHandle(runtime.createSpace(new SpaceId(42))); + PhysicsRuntimeResource runtimeResource = store.getResource( + PhysicsRuntimeResource.getResourceType()); + runtimeResource.putRuntime(backendId, runtime); + runtimeResource.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + identity.putSpaceHandle(spaceHandle, spaceRef); + return new BoundSpace(spaceRef, runtime, spaceHandle, backendId); + } + + @Nonnull + private static Ref addBody(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull UUID bodyUuid) { + BodyComponent body = new BodyComponent(spaceUuid); + body.setSpaceRef(spaceRef); + Ref bodyRef = store.addEntity(PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false), + null, + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.5f, 0.1f), + new CollisionFilterComponent(0x01, 0x02)), + AddReason.SPAWN); + assertNotNull(bodyRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(bodyUuid, bodyRef); + store.getExternalData().putRefForUUID(bodyUuid, bodyRef); + return bodyRef; + } + + private static void bindBody(@Nonnull Store store, + @Nonnull BoundSpace space, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + float positionX) { + long bodyId = space.runtime().createBody(space.handle().value(), + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.axisCode(PhysicsAxis.Y), + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + positionX, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + BackendBodyHandle handle = new BackendBodyHandle(bodyId); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putBodyHandle(bodyUuid, bodyRef, uuid(1), space.handle(), handle); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putBodyHandle(handle, bodyRef); + } + + private static void publishCopiedState(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull UUID firstBodyUuid, + @Nonnull Ref firstBodyRef, + @Nonnull UUID secondBodyUuid, + @Nonnull Ref secondBodyRef, + @Nonnull UUID retainedBodyUuid, + @Nonnull Ref retainedBodyRef) { + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(firstBodyRef, firstBodyUuid, spaceUuid), + snapshot(secondBodyRef, secondBodyUuid, spaceUuid), + snapshot(retainedBodyRef, retainedBodyUuid, spaceUuid)))); + store.getResource(PhysicsBodyRegistrationResource.getResourceType()) + .publish(1L, + List.of(publication(firstBodyRef, firstBodyUuid), + publication(secondBodyRef, secondBodyUuid), + publication(retainedBodyRef, retainedBodyUuid))); + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + return PhysicsBodySnapshot.of(bodyRef, + bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + + @Nonnull + private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( + @Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid) { + return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, + bodyUuid, + new SpaceId(42)); + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } + + private record BoundSpace(@Nonnull Ref ref, + @Nonnull PhysicsBackendRuntime runtime, + @Nonnull BackendSpaceHandle handle, + @Nonnull BackendId backendId) { + } +} From 3d3a4bee4a390f9c6f2035497cfc88575d4336a1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 11:34:46 +0200 Subject: [PATCH 480/534] fix(core): buffer body command component writes Signed-off-by: Blovien --- .../systems/BodyCommandApplicationSystem.java | 29 ++++-- .../BodyCommandApplicationSystemTest.java | 88 +++++++++++++++++++ 2 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index d325d962..3b415728 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -38,7 +38,7 @@ public final class BodyCommandApplicationSystem extends TickingSystem> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, BodyBindingSystem.class) ); - private static final Query QUERY = BodyCommandComponent.getComponentType(); + private final Query query = BodyCommandComponent.getComponentType(); @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { @@ -69,7 +69,7 @@ private static void applyCommands(@Nonnull Store store, continue; } for (BodyCommandComponent.Entry command : commands.entries()) { - applyCommand(store, runtime, restore, ref, bodyUuid, command); + applyCommand(store, runtime, restore, commandBuffer, ref, bodyUuid, command); } commandBuffer.removeComponent(ref, BodyCommandComponent.getComponentType()); } @@ -78,6 +78,7 @@ private static void applyCommands(@Nonnull Store store, private static void applyCommand(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, @Nonnull UUID bodyUuid, @Nonnull BodyCommandComponent.Entry command) { @@ -98,15 +99,27 @@ private static void applyCommand(@Nonnull Store store, PendingBodyOperation.Kind.TORQUE_IMPULSE); case FORCE -> enqueueVector(runtime, ref, bodyUuid, command, PendingBodyOperation.Kind.FORCE); case TORQUE -> enqueueVector(runtime, ref, bodyUuid, command, PendingBodyOperation.Kind.TORQUE); - case SET_TYPE -> applyBodyType(store, runtime, restore, ref, bodyUuid, command); + case SET_TYPE -> applyBodyType(store, + runtime, + restore, + commandBuffer, + ref, + bodyUuid, + command); case SET_VELOCITY -> applyVelocity(runtime, restore, ref, bodyUuid, command); - case SET_COLLISION_FILTER -> applyCollisionFilter(runtime, restore, store, ref, bodyUuid, command); + case SET_COLLISION_FILTER -> applyCollisionFilter(runtime, + restore, + commandBuffer, + ref, + bodyUuid, + command); } } private static void applyBodyType(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, @Nonnull UUID bodyUuid, @Nonnull BodyCommandComponent.Entry command) { @@ -115,7 +128,7 @@ private static void applyBodyType(@Nonnull Store store, DynamicsComponent.getComponentType()); DynamicsComponent updated = dynamics != null ? dynamics.clone() : new DynamicsComponent(); updated.setBodyType(command.getBodyType()); - store.putComponent(ref, DynamicsComponent.getComponentType(), updated); + commandBuffer.putComponent(ref, DynamicsComponent.getComponentType(), updated); RuntimeBodyBinding binding = runtimeBodyBinding(runtime, ref, bodyUuid, restore, false); if (binding == null) { @@ -138,11 +151,11 @@ private static void applyBodyType(@Nonnull Store store, private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, - @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, @Nonnull UUID bodyUuid, @Nonnull BodyCommandComponent.Entry command) { - store.putComponent(ref, + commandBuffer.putComponent(ref, CollisionFilterComponent.getComponentType(), new CollisionFilterComponent(command.getCollisionGroup(), command.getCollisionMask())); RuntimeBodyBinding binding = runtimeBodyBinding(runtime, ref, bodyUuid, restore, false); @@ -246,7 +259,7 @@ private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtim @Nonnull @Override public Query getQuery() { - return QUERY; + return query; } @Nonnull diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java new file mode 100644 index 00000000..5cd92c53 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java @@ -0,0 +1,88 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import java.util.ArrayList; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class BodyCommandApplicationSystemTest { + + @Test + void copiedComponentCommandsMutateThroughCommandBufferDuringStoreTick() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + proxy.registerSystem(new BodyBindingSystem()); + proxy.registerSystem(new BodyCommandApplicationSystem()); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("body-command-application-test")), + EmptyResourceStorage.get()); + try { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + Ref bodyRef = addCommandedBody(store); + + store.tick(0.0f); + + DynamicsComponent dynamics = store.getComponent(bodyRef, + DynamicsComponent.getComponentType()); + assertNotNull(dynamics); + assertEquals(PhysicsBodyType.KINEMATIC, dynamics.getBodyType()); + CollisionFilterComponent filter = store.getComponent(bodyRef, + CollisionFilterComponent.getComponentType()); + assertNotNull(filter); + assertEquals(0x40, filter.getCollisionGroup()); + assertEquals(0x07, filter.getCollisionMask()); + assertNull(store.getComponent(bodyRef, BodyCommandComponent.getComponentType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static Ref addCommandedBody(@Nonnull Store store) { + Holder holder = PhysicsEntities.entityHolder(store, UUID.randomUUID()); + holder.addComponent(DynamicsComponent.getComponentType(), + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false)); + holder.addComponent(CollisionFilterComponent.getComponentType(), + new CollisionFilterComponent(0x01, 0x02)); + holder.addComponent(BodyCommandComponent.getComponentType(), + BodyCommandComponent.setType(PhysicsBodyType.KINEMATIC, false) + .append(BodyCommandComponent.setCollisionFilter(0x40, 0x07, false))); + return store.addEntity(holder, AddReason.SPAWN); + } +} From f49804cfd13bb6f400e774a4e8142748253995e3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 12:16:33 +0200 Subject: [PATCH 481/534] fix(core): harden subplugin lifecycle cleanup Signed-off-by: Blovien --- .../modules/control/ControlLifecycle.java | 4 -- .../physicschunk/PhysicsChunkStoreTypes.java | 47 +++++++++++++++-- .../physicschunk/PhysicsChunkSubPlugin.java | 1 + .../physicsentity/PhysicsEntityLifecycle.java | 7 --- ...csChunkCollisionMutationQueueResource.java | 4 ++ .../PhysicsChunkCollisionPayloadResource.java | 4 ++ .../PhysicsChunkComponentSyncResource.java | 4 ++ .../PhysicsChunkSettingsIndexResource.java | 4 ++ .../modules/control/ControlLifecycleTest.java | 10 ---- .../PhysicsChunkStoreTypesTest.java | 50 +++++++++++++++++++ 10 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java index ff89f55e..bf1f6bda 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java @@ -55,10 +55,6 @@ public static boolean isEnabled() { return GATE.isEnabled(); } - public static long generation() { - return GATE.generation(); - } - public static void requireEnabled() { GATE.requireEnabled(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java index 376aef8c..60293d04 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java @@ -1,6 +1,8 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; @@ -11,7 +13,9 @@ import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionVoxelStitchingSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; +import java.util.function.Consumer; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * PhysicsStore-side type registration owned by the PhysicsChunk module. @@ -37,6 +41,13 @@ public static void registerPhysicsStoreResourceTypes( PhysicsChunkComponentSyncResource::new)); } + public static void clearPhysicsStoreResourceTypes() { + PhysicsChunkCollisionMutationQueueResource.clearResourceType(); + PhysicsChunkCollisionPayloadResource.clearResourceType(); + PhysicsChunkSettingsIndexResource.clearResourceType(); + PhysicsChunkComponentSyncResource.clearResourceType(); + } + public static void registerSpaceBindingSystems( @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); @@ -54,9 +65,37 @@ public static void registerPostBodyBindingSystems( } public static void clearPhysicsStoreRuntimeResources(@Nonnull Store store) { - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()).clear(); - store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); - store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()).clear(); - store.getResource(PhysicsChunkComponentSyncResource.getResourceType()).clear(); + clearIfPresent(store, + PhysicsChunkCollisionMutationQueueResource.getResourceType(), + PhysicsChunkCollisionMutationQueueResource::clear); + clearIfPresent(store, + PhysicsChunkCollisionPayloadResource.getResourceType(), + PhysicsChunkCollisionPayloadResource::clear); + clearIfPresent(store, + PhysicsChunkSettingsIndexResource.getResourceType(), + PhysicsChunkSettingsIndexResource::clear); + clearIfPresent(store, + PhysicsChunkComponentSyncResource.getResourceType(), + PhysicsChunkComponentSyncResource::clear); + } + + private static > void clearIfPresent( + @Nonnull Store store, + @Nullable ResourceType type, + @Nonnull Consumer clear) { + if (type == null) { + return; + } + T resource; + try { + type.validate(); + resource = store.getResource(type); + } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException | IllegalStateException _) { + // Optional PhysicsChunk resources can be unregistered before the core shutdown hook runs. + return; + } + if (resource != null) { + clear.accept(resource); + } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java index e3f4f493..010a84e4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java @@ -43,5 +43,6 @@ protected void shutdown() { PhysicsChunkLifecycle.disable(); PhysicsChunkCommandContributions.unregister(); PhysicsChunkTypes.clearEntityStoreResourceTypes(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java index b4f39722..dfd23008 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycle.java @@ -29,11 +29,4 @@ public static boolean isEnabled() { return GATE.isEnabled(); } - public static long generation() { - return GATE.generation(); - } - - public static void requireEnabled() { - GATE.requireEnabled(); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java index a77d3a5b..536b55b1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java @@ -83,4 +83,8 @@ public static void setResourceType( @Nonnull ResourceType type) { resourceType = type; } + + public static void clearResourceType() { + resourceType = null; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java index 2a890b3a..65c41570 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java @@ -57,4 +57,8 @@ public static void setResourceType( @Nonnull ResourceType type) { resourceType = type; } + + public static void clearResourceType() { + resourceType = null; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java index b0cbc12c..1c3a97f7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java @@ -65,6 +65,10 @@ public static void setResourceType( resourceType = type; } + public static void clearResourceType() { + resourceType = null; + } + public record ChunkCollisionSurfaceComponents(float friction, float restitution, int collisionGroup, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java index 2ace3c6c..eb0fdd2c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java @@ -82,6 +82,10 @@ public static void setResourceType( resourceType = type; } + public static void clearResourceType() { + resourceType = null; + } + public record PhysicsChunkSpaceSettings(@Nonnull UUID spaceUuid, @Nonnull PhysicsChunkCollisionMode mode, @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java index fab2c4c7..d0608ece 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java @@ -37,16 +37,6 @@ void lifecycleStartsDisabled() { assertFalse(ControlLifecycle.isEnabled()); } - @Test - void lifecycleGenerationChangesWhenLifecycleIsDisabled() { - ControlLifecycle.enable(); - long enabledGeneration = ControlLifecycle.generation(); - - ControlLifecycle.disable(); - - assertTrue(ControlLifecycle.generation() > enabledGeneration); - } - @Test void disablingLifecycleWithoutRegisteredSessionComponentDoesNotThrow() { ControlTypeRegistry.clearComponentTypes(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java new file mode 100644 index 00000000..b2e9f75d --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java @@ -0,0 +1,50 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import java.util.ArrayList; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class PhysicsChunkStoreTypesTest { + + @Test + void runtimeResourceCleanupIgnoresUnregisteredPhysicsChunkResources() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physicschunk-store-resource-cleanup")), + EmptyResourceStorage.get()); + try { + unregisterPhysicsChunkResources(registry); + + assertDoesNotThrow(() -> PhysicsChunkStoreTypes.clearPhysicsStoreRuntimeResources(store)); + } finally { + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + registry.removeStore(store); + } + } + + private static void unregisterPhysicsChunkResources(@Nonnull ComponentRegistry registry) { + registry.unregisterResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()); + registry.unregisterResource(PhysicsChunkCollisionPayloadResource.getResourceType()); + registry.unregisterResource(PhysicsChunkSettingsIndexResource.getResourceType()); + registry.unregisterResource(PhysicsChunkComponentSyncResource.getResourceType()); + } +} From 61f315662da9777936350e43dc166b9de79d92ae Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 12:31:47 +0200 Subject: [PATCH 482/534] feat(api): add bounded contact reads Signed-off-by: Blovien --- .../impulse/api/PhysicsSpace.java | 12 ++ .../api/runtime/PhysicsBackendRuntime.java | 47 +++++++ .../legacy/LegacyPhysicsBackendRuntime.java | 33 +++++ .../LegacyPhysicsBackendRuntimeTest.java | 120 ++++++++++++++++++ .../impulse/rapier/RapierNative.java | 2 + .../impulse/rapier/RapierSpace.java | 17 ++- .../src/main/rust/src/query_exports.rs | 27 +++- .../rapier/RapierBoundedContactsTest.java | 55 ++++++++ 8 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java index 67de2412..e7f2332c 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java @@ -222,6 +222,18 @@ PhysicsBody createCylinder(float radius, float halfHeight, @Nonnull PhysicsAxis @Nonnull List getContacts(); + @Nonnull + default List getContacts(int maxContacts) { + if (maxContacts <= 0) { + return List.of(); + } + List contacts = getContacts(); + if (contacts.size() <= maxContacts) { + return contacts; + } + return List.copyOf(contacts.subList(0, maxContacts)); + } + /** * Returns the number of active contacts in this space. * diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java index e341f6b7..00941c15 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java @@ -189,6 +189,53 @@ int raycastAll(int spaceId, int contacts(int spaceId, @Nonnull BackendContactSink sink); + default int contacts(int spaceId, int maxContacts, @Nonnull BackendContactSink sink) { + if (maxContacts <= 0) { + return 0; + } + class BoundedContactSink implements BackendContactSink { + + private int count; + + @Override + public void accept(long bodyAId, + long bodyBId, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + if (count >= maxContacts) { + return; + } + sink.accept(bodyAId, + bodyBId, + pointAX, + pointAY, + pointAZ, + pointBX, + pointBY, + pointBZ, + normalBX, + normalBY, + normalBZ, + distance, + impulse); + count++; + } + } + + BoundedContactSink bounded = new BoundedContactSink(); + contacts(spaceId, bounded); + return bounded.count; + } + int contactCount(int spaceId); void runtimeStats(int spaceId, @Nonnull BackendRuntimeStatsSink sink); diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java index 2182c411..38fbcd25 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java @@ -537,6 +537,39 @@ public int contacts(int spaceId, @Nonnull BackendContactSink sink) { return contacts; } + @Override + public int contacts(int spaceId, int maxContacts, @Nonnull BackendContactSink sink) { + if (maxContacts <= 0) { + return 0; + } + SpaceState state = requireSpace(spaceId); + int contacts = 0; + for (PhysicsContact contact : state.space.getContacts(maxContacts)) { + Long bodyAId = state.bodyIdsByBody.get(contact.bodyA()); + Long bodyBId = state.bodyIdsByBody.get(contact.bodyB()); + if (bodyAId != null && bodyBId != null) { + Vector3f pointOnA = contact.pointOnA(); + Vector3f pointOnB = contact.pointOnB(); + Vector3f normalOnB = contact.normalOnB(); + sink.accept(bodyAId, + bodyBId, + pointOnA.x, + pointOnA.y, + pointOnA.z, + pointOnB.x, + pointOnB.y, + pointOnB.z, + normalOnB.x, + normalOnB.y, + normalOnB.z, + contact.distance(), + contact.impulse()); + contacts++; + } + } + return contacts; + } + @Override public int contactCount(int spaceId) { return requireSpace(spaceId).space.contactCount(); diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java index 12a78352..1a3e5378 100644 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java +++ b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java @@ -10,14 +10,17 @@ import dev.hytalemodding.impulse.api.PhysicsBackend; import dev.hytalemodding.impulse.api.PhysicsBody; import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsContact; import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.capability.PhysicsVoxelTerrainCapability; +import dev.hytalemodding.impulse.api.runtime.BackendContactSink; import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; import dev.hytalemodding.impulse.api.runtime.BackendJointType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -26,6 +29,7 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; +import org.joml.Vector3f; import org.junit.jupiter.api.Test; class LegacyPhysicsBackendRuntimeTest { @@ -129,6 +133,46 @@ void wrapsLegacyBackendWithNumericBodyAndJointIds() { assertEquals(bodyB, runtime.jointBodyB(spaceId, joint)); } + @Test + void boundedContactsTruncateLegacyContactList() { + FakePhysicsBackend backend = new FakePhysicsBackend("impulse:test-contacts"); + LegacyPhysicsBackendRuntime runtime = new LegacyPhysicsBackendRuntime(backend); + int spaceId = runtime.createSpace(new SpaceId(9101)); + long bodyAId = createBox(runtime, spaceId, 0.0f); + long bodyBId = createBox(runtime, spaceId, 2.0f); + InMemoryPhysicsSpace space = backend.createdSpaces().getFirst(); + PhysicsBody bodyA = space.getBodies().get(0); + PhysicsBody bodyB = space.getBodies().get(1); + space.addContact(contact(bodyA, bodyB, 0.0f)); + space.addContact(contact(bodyA, bodyB, 1.0f)); + space.addContact(contact(bodyA, bodyB, 2.0f)); + CountingContactSink sink = new CountingContactSink(); + + int emitted = runtime.contacts(spaceId, 2, sink); + + assertEquals(2, emitted); + assertEquals(2, sink.count()); + assertEquals(bodyAId, sink.firstBodyAId()); + assertEquals(bodyBId, sink.firstBodyBId()); + } + + @Test + void boundedContactsRejectNonPositiveLimitWithoutEmittingContacts() { + FakePhysicsBackend backend = new FakePhysicsBackend("impulse:test-zero-contacts"); + LegacyPhysicsBackendRuntime runtime = new LegacyPhysicsBackendRuntime(backend); + int spaceId = runtime.createSpace(new SpaceId(9102)); + createBox(runtime, spaceId, 0.0f); + createBox(runtime, spaceId, 2.0f); + InMemoryPhysicsSpace space = backend.createdSpaces().getFirst(); + space.addContact(contact(space.getBodies().get(0), space.getBodies().get(1), 0.0f)); + CountingContactSink sink = new CountingContactSink(); + + int emitted = runtime.contacts(spaceId, 0, sink); + + assertEquals(0, emitted); + assertEquals(0, sink.count()); + } + @Test void createSpaceRejectsLegacyBackendThatReturnsDifferentExplicitId() { LegacyPhysicsBackendRuntime runtime = @@ -214,6 +258,42 @@ void legacyRuntimePassesThroughVoxelTerrainCapability() { backend.combineCalls()); } + private static long createBox(@Nonnull LegacyPhysicsBackendRuntime runtime, + int spaceId, + float positionX) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + positionX, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + @Nonnull + private static PhysicsContact contact(@Nonnull PhysicsBody bodyA, + @Nonnull PhysicsBody bodyB, + float offset) { + return new PhysicsContact(bodyA, + bodyB, + new Vector3f(offset, 0.0f, 0.0f), + new Vector3f(offset, 1.0f, 0.0f), + new Vector3f(0.0f, 1.0f, 0.0f), + -0.1f, + 1.0f); + } + private record CombineCall(@Nonnull PhysicsBody bodyA, @Nonnull PhysicsBody bodyB, int shiftX, @@ -221,6 +301,46 @@ private record CombineCall(@Nonnull PhysicsBody bodyA, int shiftZ) { } + private static final class CountingContactSink implements BackendContactSink { + + private int count; + private long firstBodyAId = -1L; + private long firstBodyBId = -1L; + + @Override + public void accept(long bodyAId, + long bodyBId, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + if (count == 0) { + firstBodyAId = bodyAId; + firstBodyBId = bodyBId; + } + count++; + } + + private int count() { + return count; + } + + private long firstBodyAId() { + return firstBodyAId; + } + + private long firstBodyBId() { + return firstBodyBId; + } + } + private static final class CapturedBodySnapshot implements BackendBodySnapshotSink { private int shapeTypeCode = BackendRuntimeCodes.SHAPE_UNKNOWN; diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java index cce5de05..e0c0f6c7 100644 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java +++ b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java @@ -243,6 +243,8 @@ static native float[] raycastAllNative(long spaceHandle, static native float[] getContactsNative(long spaceHandle); + static native float[] getContactsLimitedNative(long spaceHandle, int maxContacts); + static native long addJointNative(long spaceHandle, int jointType, long bodyA, diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java index 7002d843..b3a39f23 100644 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java +++ b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java @@ -622,7 +622,22 @@ public List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f @Override public List getContacts() { ensureOpen(); - float[] raw = RapierNative.getContactsNative(nativeSpaceHandle); + return contactsFromRaw(RapierNative.getContactsNative(nativeSpaceHandle)); + } + + @Nonnull + @Override + public List getContacts(int maxContacts) { + ensureOpen(); + if (maxContacts <= 0) { + return List.of(); + } + return contactsFromRaw(RapierNative.getContactsLimitedNative(nativeSpaceHandle, + maxContacts)); + } + + @Nonnull + private List contactsFromRaw(@Nonnull float[] raw) { List contacts = new ArrayList<>(raw.length / CONTACT_FLOATS); for (int i = 0; i + CONTACT_FLOATS <= raw.length; i += CONTACT_FLOATS) { RapierBody bodyA = bodiesByHandle.get(rawBitFloatPairToLong(raw[i], raw[i + 1])); diff --git a/impulse-rapier/src/main/rust/src/query_exports.rs b/impulse-rapier/src/main/rust/src/query_exports.rs index df49c598..9c9ceeda 100644 --- a/impulse-rapier/src/main/rust/src/query_exports.rs +++ b/impulse-rapier/src/main/rust/src/query_exports.rs @@ -6,7 +6,6 @@ const MAX_RAYCAST_FLOATS: usize = RAYCAST_HIT_FLOATS * MAX_RAYCAST_HITS; const CONTACT_FLOATS: usize = 15; const MAX_CONTACT_POINTS: usize = 16_384; -const MAX_CONTACT_FLOATS: usize = CONTACT_FLOATS * MAX_CONTACT_POINTS; fn append_bounded(values: &mut Vec, record: &[jfloat], max_floats: usize) -> bool { if record.len() > max_floats.saturating_sub(values.len()) { @@ -104,12 +103,32 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_getCon _class: JClass, space_handle: jlong, ) -> jfloatArray { - let values = catch_jni_default(Vec::new(), || contact_values(space_handle)); + let values = catch_jni_default(Vec::new(), || { + contact_values(space_handle, MAX_CONTACT_POINTS) + }); + float_array_or_null(&env, &values) +} + +#[no_mangle] +pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_getContactsLimitedNative( + env: JNIEnv, + _class: JClass, + space_handle: jlong, + max_contacts: jint, +) -> jfloatArray { + let values = catch_jni_default(Vec::new(), || { + if max_contacts <= 0 { + Vec::new() + } else { + contact_values(space_handle, (max_contacts as usize).min(MAX_CONTACT_POINTS)) + } + }); float_array_or_null(&env, &values) } -fn contact_values(space_handle: jlong) -> Vec { +fn contact_values(space_handle: jlong, max_contacts: usize) -> Vec { let mut values: Vec = Vec::new(); + let max_floats = CONTACT_FLOATS * max_contacts.min(MAX_CONTACT_POINTS); with_space(space_handle, (), |space| { 'contacts: for pair in space.narrow_phase.contact_pairs() { let Some(body_a_id) = space.collider_to_body_id.get(&pair.collider1) else { @@ -144,7 +163,7 @@ fn contact_values(space_handle: jlong) -> Vec { contact.dist, contact.warmstart_impulse, ], - MAX_CONTACT_FLOATS, + max_floats, ) { break 'contacts; } diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java new file mode 100644 index 00000000..805c2900 --- /dev/null +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java @@ -0,0 +1,55 @@ +package dev.hytalemodding.impulse.rapier; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.PhysicsBody; +import dev.hytalemodding.impulse.api.PhysicsContact; +import dev.hytalemodding.impulse.api.PhysicsSpace; +import java.util.List; +import org.junit.jupiter.api.Test; + +class RapierBoundedContactsTest { + + @Test + void getContactsWithLimitReturnsAtMostRequestedContacts() { + RapierBackend backend = new RapierBackend(); + backend.init(); + PhysicsSpace space = backend.createSpace(); + try { + space.setGravity(0.0f, -9.81f, 0.0f); + addStaticFloor(space, 8, 8); + addRestingBoxes(space, 8, 8); + for (int i = 0; i < 180; i++) { + space.step(1.0f / 30.0f); + } + + List contacts = space.getContacts(5); + + assertFalse(contacts.isEmpty()); + assertTrue(contacts.size() <= 5); + } finally { + space.close(); + } + } + + private static void addStaticFloor(PhysicsSpace space, int width, int depth) { + for (int x = 0; x < width; x++) { + for (int z = 0; z < depth; z++) { + PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 0.0f); + body.setPosition(x, 0.0f, z); + space.addBody(body); + } + } + } + + private static void addRestingBoxes(PhysicsSpace space, int width, int depth) { + for (int x = 0; x < width; x++) { + for (int z = 0; z < depth; z++) { + PhysicsBody body = space.createBox(0.45f, 0.45f, 0.45f, 1.0f); + body.setPosition(x, 1.05f, z); + space.addBody(body); + } + } + } +} From 5e7f4316fc5f9acc9673c6ffef0a8651cc80be70 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 12:32:08 +0200 Subject: [PATCH 483/534] fix(core): bound contact debug queries Signed-off-by: Blovien --- .../resources/PhysicsDebugResource.java | 2 +- .../debug/PhysicsStoreDebugQueries.java | 4 +- .../resources/PhysicsDebugResourceTest.java | 29 +++ .../debug/PhysicsContactDebugCapture.java | 2 +- .../debug/PhysicsStoreDebugQueriesTest.java | 183 ++++++++++++++++++ 5 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResourceTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index f0854eff..74f7823d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -43,7 +43,7 @@ public class PhysicsDebugResource implements Resource { @Setter private boolean debugMotionEnabled = true; @Setter - private boolean debugContactsEnabled = true; + private boolean debugContactsEnabled; @Setter private boolean debugJointsEnabled = true; @Setter diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 857f9ef3..0ed1435f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -112,7 +112,7 @@ static CompletionStage> physicsChunkSectionsA } @Nonnull - private static List contacts(@Nonnull Store store, + static List contacts(@Nonnull Store store, @Nonnull SpaceId spaceId, double viewerX, double viewerY, @@ -132,7 +132,7 @@ private static List contacts(@Nonnull Store visible = new ArrayList<>(Math.min(limit, 64)); - space.backendRuntime().contacts(space.spaceHandle().value(), (bodyAId, + space.backendRuntime().contacts(space.spaceHandle().value(), limit, (bodyAId, bodyBId, pointAX, pointAY, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResourceTest.java new file mode 100644 index 00000000..a7c88bc4 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResourceTest.java @@ -0,0 +1,29 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class PhysicsDebugResourceTest { + + @Test + void contactsDefaultDisabledWhileOtherOverlayFlagsRemainEnabled() { + PhysicsDebugResource resource = new PhysicsDebugResource(); + + assertTrue(resource.isDebugShapesEnabled()); + assertTrue(resource.isDebugMotionEnabled()); + assertFalse(resource.isDebugContactsEnabled()); + assertTrue(resource.isDebugJointsEnabled()); + } + + @Test + void clonePreservesExplicitContactFlag() { + PhysicsDebugResource resource = new PhysicsDebugResource(); + resource.setDebugContactsEnabled(true); + + PhysicsDebugResource copy = resource.clone(); + + assertTrue(copy.isDebugContactsEnabled()); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java index 2586661b..1f577c49 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java @@ -24,7 +24,7 @@ static List collectVisibleContactPri } List primitives = new ArrayList<>(); double radiusSquared = radius * radius; - for (PhysicsContact contact : space.getContacts()) { + for (PhysicsContact contact : space.getContacts(maxContacts)) { Vector3f pointOnB = contact.pointOnB(); Vector3d point = new Vector3d(pointOnB.x, pointOnB.y, pointOnB.z); if (point.distanceSquared(center) <= radiusSquared) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java new file mode 100644 index 00000000..9d040f4d --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java @@ -0,0 +1,183 @@ +package dev.hytalemodding.impulse.core.internal.systems.debug; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendContactSink; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsStoreDebugQueriesTest { + + @Test + void contactDebugQueryUsesBoundedRuntimeContactRead() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("debug-contact-query-bounded-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + RecordingContactRuntime runtime = new RecordingContactRuntime(); + bindSpace(store, runtime); + + List contacts = PhysicsStoreDebugQueries.contacts(store, + new SpaceId(77), + 0.0, + 0.0, + 0.0, + 32.0, + 3); + + assertEquals(3, contacts.size()); + assertEquals(1, runtime.boundedCalls()); + assertEquals(0, runtime.unboundedCalls()); + assertEquals(3, runtime.lastMaxContacts()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + private static void bindSpace(@Nonnull Store store, + @Nonnull RecordingContactRuntime recordingRuntime) { + UUID spaceUuid = new UUID(0L, 77L); + BackendId backendId = new BackendId("test:debug-contact-query"); + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putUuid(spaceUuid, spaceRef); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(new SpaceId(77), spaceUuid); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putRuntime(backendId, recordingRuntime.proxy()); + runtime.putSpaceBinding(spaceUuid, + spaceRef, + backendId, + new BackendSpaceHandle(7700)); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", exception.getCause()); + } + } + + private static final class RecordingContactRuntime implements InvocationHandler { + + private final PhysicsBackendRuntime proxy = + (PhysicsBackendRuntime) Proxy.newProxyInstance( + PhysicsBackendRuntime.class.getClassLoader(), + new Class[] { PhysicsBackendRuntime.class }, + this); + private int boundedCalls; + private int unboundedCalls; + private int lastMaxContacts; + + @Nonnull + private PhysicsBackendRuntime proxy() { + return proxy; + } + + private int boundedCalls() { + return boundedCalls; + } + + private int unboundedCalls() { + return unboundedCalls; + } + + private int lastMaxContacts() { + return lastMaxContacts; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + if ("contacts".equals(method.getName()) && args != null && args.length == 3) { + boundedCalls++; + lastMaxContacts = (Integer) args[1]; + return emitContacts(lastMaxContacts, (BackendContactSink) args[2]); + } + if ("contacts".equals(method.getName()) && args != null && args.length == 2) { + unboundedCalls++; + return emitContacts(8, (BackendContactSink) args[1]); + } + return defaultValue(method.getReturnType()); + } + + private static int emitContacts(int maxContacts, @Nonnull BackendContactSink sink) { + int emitted = 0; + for (int i = 0; i < Math.max(0, maxContacts); i++) { + sink.accept(1L, + 2L, + i, + 0.0f, + 0.0f, + i, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + -0.1f, + 1.0f); + emitted++; + } + return emitted; + } + + private static Object defaultValue(@Nonnull Class returnType) { + if (returnType == Boolean.TYPE) { + return false; + } + if (returnType == Integer.TYPE) { + return 0; + } + if (returnType == Long.TYPE) { + return 0L; + } + if (returnType == Float.TYPE) { + return 0.0f; + } + return null; + } + } +} From 25e95899fe547b57e2128e3e39d864dbe5f4d79c Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 12:32:25 +0200 Subject: [PATCH 484/534] feat(core): report backend runtime pressure Signed-off-by: Blovien --- .../PhysicsChunkPerfReportCommand.java | 105 ++++++++++++------ .../plugin/physics/PhysicsBackendAccess.java | 102 ++++++++++++++++- .../core/plugin/simulation/SpaceSummary.java | 58 +++++++++- .../PhysicsChunkPerfReportCommandTest.java | 33 ++++++ 4 files changed, 262 insertions(+), 36 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java index ccdf1167..9679b9c3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java @@ -71,7 +71,7 @@ private static void sendReport(@Nonnull CommandContext ctx, + runtimeFootprint.summary())); if (runtimeFootprint.hasRuntimeStats()) { ctx.sender().sendMessage(Message.raw("Physics backend runtime stats: " - + runtimeFootprint.runtimeStatsSummary())); + + formatRuntimeStatsSummary(runtimeFootprint))); } ctx.sender().sendMessage(Message.raw("Physics event frame: " + formatEventFrameSummary(PhysicsWorlds.latestEventFrame(physicsStore)))); @@ -238,8 +238,8 @@ private static void sendReport(@Nonnull CommandContext ctx, + "/" + latestVisual.getDematerialized())); } } else { - ctx.sender().sendMessage(Message.raw("No profiled physics step/sync/visual ticks recorded yet." - + (runtimeProfiling.enabled() + ctx.sender().sendMessage(Message.raw("No profiled physics step/sync/visual ticks recorded yet." + + (runtimeProfiling.enabled() ? "" : " Run /impulse physicschunk perf toggle, wait a few seconds, then run /impulse physicschunk perf report."))); } @@ -471,6 +471,46 @@ static boolean hasCompletedStepSamples(@Nonnull StepDrainSnapshotView cumulative return cumulativeStep.getTickSamples() > 0; } + @Nonnull + static String formatRuntimeStatsSummary(@Nonnull RuntimeStatsView stats) { + return "spaces=" + stats.runtimeStatsSpaces() + + " bodies=" + stats.runtimeBodies() + + " colliders=" + stats.runtimeColliders() + + " activeBodies=" + stats.runtimeActiveBodies() + + " activeIslands=" + stats.runtimeActiveIslands() + + " contactPairs=" + stats.runtimeContactPairs() + + " contactManifolds=" + stats.runtimeContactManifolds() + + " contactPoints=" + stats.runtimeContactPoints() + + " dynamicDynamicPairs=" + stats.runtimeDynamicDynamicContactPairs() + + " terrainPairs=" + stats.runtimeTerrainContactPairs() + + " joints=" + stats.runtimeJoints(); + } + + interface RuntimeStatsView { + + int runtimeStatsSpaces(); + + int runtimeBodies(); + + int runtimeColliders(); + + int runtimeActiveBodies(); + + int runtimeContactPairs(); + + int runtimeContactManifolds(); + + int runtimeContactPoints(); + + int runtimeDynamicDynamicContactPairs(); + + int runtimeTerrainContactPairs(); + + int runtimeActiveIslands(); + + int runtimeJoints(); + } + @Nonnull private static String formatMillis(long nanos) { return String.format(Locale.ROOT, "%.3f", nanos / 1_000_000.0); @@ -537,21 +577,21 @@ private static String formatAverageHertz(long totalIntervalNanos, int samples) { } private record RuntimeFootprint(int spaces, - int backendBodies, - int backendJoints, - int detachedBodies, - int detachedVisualProxies, - int runtimeStatsSpaces, - int runtimeBodies, - int runtimeColliders, - int runtimeActiveBodies, - int runtimeContactPairs, - int runtimeContactManifolds, - int runtimeContactPoints, - int runtimeDynamicDynamicContactPairs, - int runtimeTerrainContactPairs, - int runtimeActiveIslands, - int runtimeJoints) { + int backendBodies, + int backendJoints, + int detachedBodies, + int detachedVisualProxies, + int runtimeStatsSpaces, + int runtimeBodies, + int runtimeColliders, + int runtimeActiveBodies, + int runtimeContactPairs, + int runtimeContactManifolds, + int runtimeContactPoints, + int runtimeDynamicDynamicContactPairs, + int runtimeTerrainContactPairs, + int runtimeActiveIslands, + int runtimeJoints) implements RuntimeStatsView { @Nonnull private static RuntimeFootprint collect(@Nonnull List summaries) { @@ -573,6 +613,20 @@ private static RuntimeFootprint collect(@Nonnull List summaries) { spaces++; backendBodies += summary.bodyCount(); backendJoints += summary.jointCount(); + if (summary.runtimeStatsAvailable()) { + runtimeStatsSpaces++; + runtimeBodies += summary.runtimeBodyCount(); + runtimeColliders += summary.runtimeColliderCount(); + runtimeActiveBodies += summary.runtimeActiveBodyCount(); + runtimeContactPairs += summary.runtimeContactPairCount(); + runtimeContactManifolds += summary.runtimeContactManifoldCount(); + runtimeContactPoints += summary.runtimeContactPointCount(); + runtimeDynamicDynamicContactPairs += + summary.runtimeDynamicDynamicContactPairCount(); + runtimeTerrainContactPairs += summary.runtimeTerrainContactPairCount(); + runtimeActiveIslands += summary.runtimeActiveIslandCount(); + runtimeJoints += summary.runtimeJointCount(); + } } return new RuntimeFootprint(spaces, @@ -605,20 +659,5 @@ private String summary() { private boolean hasRuntimeStats() { return runtimeStatsSpaces > 0; } - - @Nonnull - private String runtimeStatsSummary() { - return "spaces=" + runtimeStatsSpaces - + " bodies=" + runtimeBodies - + " colliders=" + runtimeColliders - + " activeBodies=" + runtimeActiveBodies - + " activeIslands=" + runtimeActiveIslands - + " contactPairs=" + runtimeContactPairs - + " contactManifolds=" + runtimeContactManifolds - + " contactPoints=" + runtimeContactPoints - + " dynamicDynamicPairs=" + runtimeDynamicDynamicContactPairs - + " terrainPairs=" + runtimeTerrainContactPairs - + " joints=" + runtimeJoints; - } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java index 40d08176..55066733 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java @@ -5,11 +5,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeStatsSink; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.Objects; @@ -67,10 +68,23 @@ static SpaceSummary summary(@Nonnull PhysicsSpaceCompatibilityIndexResource comp throw new IllegalStateException("PhysicsStore space has no compatibility SpaceId: " + space.spaceUuid()); } + RuntimeStatsCapture runtimeStats = new RuntimeStatsCapture(); + space.backendRuntime().runtimeStats(space.spaceHandle().value(), runtimeStats); return new SpaceSummary(spaceId, space.backendId(), space.backendRuntime().bodyCount(space.spaceHandle().value()), - space.backendRuntime().jointCount(space.spaceHandle().value())); + space.backendRuntime().jointCount(space.spaceHandle().value()), + runtimeStats.available(), + runtimeStats.bodyCount(), + runtimeStats.colliderCount(), + runtimeStats.activeBodyCount(), + runtimeStats.contactPairCount(), + runtimeStats.contactManifoldCount(), + runtimeStats.contactPointCount(), + runtimeStats.dynamicDynamicContactPairCount(), + runtimeStats.terrainContactPairCount(), + runtimeStats.activeIslandCount(), + runtimeStats.jointCount()); } @Nonnull @@ -101,4 +115,88 @@ record SpaceContext(@Nonnull UUID spaceUuid, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } + + private static final class RuntimeStatsCapture implements BackendRuntimeStatsSink { + + private int bodyCount; + private int colliderCount; + private int activeBodyCount; + private int contactPairCount; + private int contactManifoldCount; + private int contactPointCount; + private int dynamicDynamicContactPairCount; + private int terrainContactPairCount; + private int activeIslandCount; + private int jointCount; + private boolean available; + + @Override + public void accept(int bodyCount, + int colliderCount, + int activeBodyCount, + int contactPairCount, + int contactManifoldCount, + int contactPointCount, + int dynamicDynamicContactPairCount, + int terrainContactPairCount, + int activeIslandCount, + int jointCount, + boolean available) { + this.bodyCount = bodyCount; + this.colliderCount = colliderCount; + this.activeBodyCount = activeBodyCount; + this.contactPairCount = contactPairCount; + this.contactManifoldCount = contactManifoldCount; + this.contactPointCount = contactPointCount; + this.dynamicDynamicContactPairCount = dynamicDynamicContactPairCount; + this.terrainContactPairCount = terrainContactPairCount; + this.activeIslandCount = activeIslandCount; + this.jointCount = jointCount; + this.available = available; + } + + private int bodyCount() { + return bodyCount; + } + + private int colliderCount() { + return colliderCount; + } + + private int activeBodyCount() { + return activeBodyCount; + } + + private int contactPairCount() { + return contactPairCount; + } + + private int contactManifoldCount() { + return contactManifoldCount; + } + + private int contactPointCount() { + return contactPointCount; + } + + private int dynamicDynamicContactPairCount() { + return dynamicDynamicContactPairCount; + } + + private int terrainContactPairCount() { + return terrainContactPairCount; + } + + private int activeIslandCount() { + return activeIslandCount; + } + + private int jointCount() { + return jointCount; + } + + private boolean available() { + return available; + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java index df67b75a..eec6b9f9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java @@ -11,12 +11,68 @@ public record SpaceSummary(@Nonnull SpaceId spaceId, @Nonnull BackendId backendId, int bodyCount, - int jointCount) { + int jointCount, + boolean runtimeStatsAvailable, + int runtimeBodyCount, + int runtimeColliderCount, + int runtimeActiveBodyCount, + int runtimeContactPairCount, + int runtimeContactManifoldCount, + int runtimeContactPointCount, + int runtimeDynamicDynamicContactPairCount, + int runtimeTerrainContactPairCount, + int runtimeActiveIslandCount, + int runtimeJointCount) { + + public SpaceSummary(@Nonnull SpaceId spaceId, + @Nonnull BackendId backendId, + int bodyCount, + int jointCount) { + this(spaceId, + backendId, + bodyCount, + jointCount, + false, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0); + } public SpaceSummary { Objects.requireNonNull(spaceId, "spaceId"); Objects.requireNonNull(backendId, "backendId"); bodyCount = Math.max(0, bodyCount); jointCount = Math.max(0, jointCount); + if (!runtimeStatsAvailable) { + runtimeBodyCount = 0; + runtimeColliderCount = 0; + runtimeActiveBodyCount = 0; + runtimeContactPairCount = 0; + runtimeContactManifoldCount = 0; + runtimeContactPointCount = 0; + runtimeDynamicDynamicContactPairCount = 0; + runtimeTerrainContactPairCount = 0; + runtimeActiveIslandCount = 0; + runtimeJointCount = 0; + } else { + runtimeBodyCount = Math.max(0, runtimeBodyCount); + runtimeColliderCount = Math.max(0, runtimeColliderCount); + runtimeActiveBodyCount = Math.max(0, runtimeActiveBodyCount); + runtimeContactPairCount = Math.max(0, runtimeContactPairCount); + runtimeContactManifoldCount = Math.max(0, runtimeContactManifoldCount); + runtimeContactPointCount = Math.max(0, runtimeContactPointCount); + runtimeDynamicDynamicContactPairCount = + Math.max(0, runtimeDynamicDynamicContactPairCount); + runtimeTerrainContactPairCount = Math.max(0, runtimeTerrainContactPairCount); + runtimeActiveIslandCount = Math.max(0, runtimeActiveIslandCount); + runtimeJointCount = Math.max(0, runtimeJointCount); + } } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java index 3a0edae3..6b21e17c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommandTest.java @@ -9,6 +9,26 @@ class PhysicsChunkPerfReportCommandTest { + @Test + void runtimeStatsSummaryIncludesLiveBackendPressureCounters() { + RuntimeStatsSample sample = new RuntimeStatsSample(2, + 180, + 210, + 24, + 96, + 44, + 128, + 4, + 92, + 3, + 12); + + assertEquals("spaces=2 bodies=180 colliders=210 activeBodies=24 activeIslands=3 " + + "contactPairs=96 contactManifolds=44 contactPoints=128 " + + "dynamicDynamicPairs=4 terrainPairs=92 joints=12", + PhysicsChunkPerfReportCommand.formatRuntimeStatsSummary(sample)); + } + @Test void preStepDrainSummaryReportsAverageLatestAndMaxBackpressure() { StepDrainSample cumulative = new StepDrainSample(2, @@ -96,4 +116,17 @@ public int getMaxLateMutationBacklogAtStep() { return maxLateMutationBacklogAtStep; } } + + private record RuntimeStatsSample(int runtimeStatsSpaces, + int runtimeBodies, + int runtimeColliders, + int runtimeActiveBodies, + int runtimeContactPairs, + int runtimeContactManifolds, + int runtimeContactPoints, + int runtimeDynamicDynamicContactPairs, + int runtimeTerrainContactPairs, + int runtimeActiveIslands, + int runtimeJoints) implements PhysicsChunkPerfReportCommand.RuntimeStatsView { + } } From efbc4414bf9bd799a0db18574cb58a35166371e2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 12:32:39 +0200 Subject: [PATCH 485/534] fix(core): rebuild physics uuid indexes serially Signed-off-by: Blovien --- .../physics/PhysicsStoreRowCleanup.java | 27 ++++++++++---- .../physics/PhysicsStoreRowCleanupTest.java | 35 +++++++++++++++++++ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index 05587d80..d4337567 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -148,17 +149,20 @@ public static void refreshIdentityAndRuntimeRefs(@Nonnull Store st PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - identity.clearUuidRefs(); - store.getExternalData().clearUuidIndex(); + ConcurrentLinkedQueue uuidRefs = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { UuidComponent uuid = chunk.getComponent(index, UuidComponent.getComponentType()); - if (uuid == null) { - return; + if (uuid != null) { + uuidRefs.add(new UuidRef(uuid.getUuid(), chunk.getReferenceTo(index))); } - Ref ref = chunk.getReferenceTo(index); - identity.putUuid(uuid.getUuid(), ref); - store.getExternalData().putRefForUUID(uuid.getUuid(), ref); }); + // The identity maps are fastutil/Hytale mutable maps; rebuild them on one thread. + identity.clearUuidRefs(); + store.getExternalData().clearUuidIndex(); + for (UuidRef uuidRef : uuidRefs) { + identity.putUuid(uuidRef.uuid(), uuidRef.ref()); + store.getExternalData().putRefForUUID(uuidRef.uuid(), uuidRef.ref()); + } runtime.refreshRowRefs(identity); } @@ -189,4 +193,13 @@ public record BodyEntityRemoval( Objects.requireNonNull(bodyRef, "bodyRef"); } } + + private record UuidRef(@Nonnull UUID uuid, + @Nonnull Ref ref) { + + private UuidRef { + Objects.requireNonNull(uuid, "uuid"); + Objects.requireNonNull(ref, "ref"); + } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java index 74f0e7c0..6c28bdff 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java @@ -17,6 +17,7 @@ import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; @@ -85,6 +86,40 @@ void clearBodyCopiedStateRemovesMultipleBodiesWithoutRemovingRows() { } } + @Test + void refreshIdentityAndRuntimeRefsRebuildsLargeUuidIndexWithoutParallelMapWrites() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("row-cleanup-refresh-large-index")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + List bodyUuids = new ArrayList<>(); + for (int index = 0; index < 6000; index++) { + UUID bodyUuid = uuid(1000L + index); + bodyUuids.add(bodyUuid); + addIdentityRow(store, bodyUuid); + } + + for (int index = 0; index < 20; index++) { + PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); + } + + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + for (UUID bodyUuid : bodyUuids) { + assertNotNull(identity.getByUuid(bodyUuid)); + } + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Nonnull private static Ref addIdentityRow(@Nonnull Store store, @Nonnull UUID bodyUuid) { From 4461245ec877527e5579e0af2e8601599433db52 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 12:32:52 +0200 Subject: [PATCH 486/534] test(rapier): cover native voxel terrain floors Signed-off-by: Blovien --- .../rapier/RapierVoxelTerrainTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java index 93d386f9..0a0dcb52 100644 --- a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java @@ -7,10 +7,70 @@ import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.capability.PhysicsVoxelTerrainCapability; +import org.joml.Vector3f; import org.junit.jupiter.api.Test; class RapierVoxelTerrainTest { + @Test + void dynamicBoxRestsOnNativeVoxelFloor() { + RapierBackend backend = new RapierBackend(); + backend.init(); + PhysicsSpace space = backend.createSpace(new SpaceId(10)); + try { + space.setGravity(0.0f, -9.81f, 0.0f); + addVoxelFloor(space, 0.0f); + PhysicsBody box = addDynamicBox(space, 8.0f, 3.0f, 8.0f); + + StepResult result = stepAndTrackMinimumY(space, box, 240); + + assertFalse(result.fellThrough()); + } finally { + space.close(); + } + } + + @Test + void dynamicBoxRestsNearNativeVoxelSectionEdge() { + RapierBackend backend = new RapierBackend(); + backend.init(); + PhysicsSpace space = backend.createSpace(new SpaceId(11)); + try { + space.setGravity(0.0f, -9.81f, 0.0f); + addVoxelFloor(space, 0.0f); + PhysicsBody box = addDynamicBox(space, 15.75f, 3.0f, 8.0f); + + StepResult result = stepAndTrackMinimumY(space, box, 240); + + assertFalse(result.fellThrough()); + } finally { + space.close(); + } + } + + @Test + void dynamicBoxRestsAcrossStitchedNativeVoxelSections() { + RapierBackend backend = new RapierBackend(); + backend.init(); + PhysicsSpace space = backend.createSpace(new SpaceId(12)); + try { + space.setGravity(0.0f, -9.81f, 0.0f); + PhysicsVoxelTerrainCapability voxelTerrain = space + .getCapability(PhysicsVoxelTerrainCapability.class) + .orElseThrow(); + PhysicsBody first = addVoxelFloor(space, 0.0f); + PhysicsBody second = addVoxelFloor(space, 16.0f); + voxelTerrain.combineVoxelTerrains(first, second, 16, 0, 0); + PhysicsBody box = addDynamicBox(space, 16.0f, 3.0f, 8.0f); + + StepResult result = stepAndTrackMinimumY(space, box, 240); + + assertFalse(result.fellThrough()); + } finally { + space.close(); + } + } + @Test void combineVoxelTerrainNativeReportsInvalidSpaceHandle() { RapierNative.load(); @@ -63,4 +123,55 @@ void combineVoxelTerrainsRejectsSameBody() { space.close(); } } + + private static PhysicsBody addVoxelFloor(PhysicsSpace space, float originX) { + PhysicsVoxelTerrainCapability voxelTerrain = space + .getCapability(PhysicsVoxelTerrainCapability.class) + .orElseThrow(); + PhysicsBody floor = voxelTerrain.createVoxelTerrain(1.0f, + 1.0f, + 1.0f, + voxelFloorCoordinates(16, 16)); + floor.setPosition(originX, 0.0f, 0.0f); + space.addBody(floor); + return floor; + } + + private static int[] voxelFloorCoordinates(int width, int depth) { + int[] coordinates = new int[width * depth * 3]; + int index = 0; + for (int x = 0; x < width; x++) { + for (int z = 0; z < depth; z++) { + coordinates[index++] = x; + coordinates[index++] = 0; + coordinates[index++] = z; + } + } + return coordinates; + } + + private static PhysicsBody addDynamicBox(PhysicsSpace space, float x, float y, float z) { + PhysicsBody box = space.createBox(0.45f, 0.45f, 0.45f, 1.0f); + box.setPosition(x, y, z); + space.addBody(box); + return box; + } + + private static StepResult stepAndTrackMinimumY(PhysicsSpace space, + PhysicsBody body, + int steps) { + float minY = Float.POSITIVE_INFINITY; + for (int i = 0; i < steps; i++) { + space.step(1.0f / 30.0f); + minY = Math.min(minY, body.getPosition().y); + } + return new StepResult(body.getPosition(), minY); + } + + private record StepResult(Vector3f finalPosition, float minY) { + + private boolean fellThrough() { + return finalPosition.y < 1.0f || minY < 0.75f; + } + } } From dc8f39b85f51a38d521e0bd35d3e44243b33fbce Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 13:32:07 +0200 Subject: [PATCH 487/534] refactor(core): remove body registration resource Signed-off-by: Blovien --- ...tachedStreamingBenchmarkCrucibleTests.java | 10 +- ...pulseRapierBodyBenchmarkCrucibleTests.java | 14 +- .../PhysicsChunkPerfReportCommand.java | 11 - .../physics/PhysicsStoreRowCleanup.java | 3 - .../physics/PhysicsStoreRuntimeCleaner.java | 2 - .../physics/PhysicsTopologyMutations.java | 2 - .../PhysicsStoreRegistration.java | 5 - .../PhysicsBodyRegistrationResource.java | 208 ------------------ .../resources/PhysicsProfilingResource.java | 74 +------ .../resources/PhysicsResourceTypes.java | 12 - .../PhysicsRuntimeProfilingResource.java | 57 ----- .../CompletedStepPublicationSystem.java | 90 -------- .../PhysicsStoreEventPublicationSystem.java | 5 +- .../PhysicsRuntimeProfiling.java | 12 - .../core/plugin/physics/PhysicsBodies.java | 80 ++++--- .../CleanCommandLifecycleGuardTest.java | 30 +-- .../physics/PhysicsStoreRowCleanupTest.java | 29 +-- .../PhysicsStoreTopologyMutationsTest.java | 24 +- .../PhysicsStoreResourceIndexTest.java | 36 --- ...ChunkCollisionMutationDrainSystemTest.java | 28 +-- .../systems/StaleBodyRemovalSystemTest.java | 27 +-- .../plugin/physics/PhysicsBodiesTest.java | 141 ++++++++++++ .../physics/PhysicsBodyEntitiesTest.java | 7 + .../commands/PhysicsStoreExampleCommands.java | 7 +- 24 files changed, 249 insertions(+), 665 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 9a83c2b2..4219ad94 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -286,15 +286,11 @@ private StageReport finishStage(int count, SpaceStats stats = SpaceStats.collect(physicsStore, collisionStreaming, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); - double avgRegistrationPublicationMs = averageMillis( - step.getRegistrationPublicationNanos(), - step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); double avgTerrainMs = averageMillis(collisionProfilingSnapshot.getTickNanos(), collisionProfilingSnapshot.getTickSamples()); double totalMs = avgStepMs + avgSnapshotMs - + avgRegistrationPublicationMs + avgSyncMs + avgTerrainMs; StageHealth health = assessHealth(count, @@ -308,7 +304,6 @@ private StageReport finishStage(int count, observedTickRate, avgStepMs, avgSnapshotMs, - avgRegistrationPublicationMs, avgSyncMs, avgTerrainMs, totalMs, @@ -794,7 +789,6 @@ private record StageReport(int count, double observedTickRate, double avgStepMs, double avgSnapshotMs, - double avgRegistrationPublicationMs, double avgSyncMs, double avgTerrainMs, double totalMs, @@ -834,7 +828,6 @@ private static StageReport failedPreflight(int count, @Nonnull String reason) { 0.0, 0.0, 0.0, - 0.0, 0, 0, 0, @@ -869,9 +862,8 @@ private String summary() { + " reason=" + health.reason() + " tps=" + format(observedTickRate) + " totalMs=" + format(totalMs) - + " step/snapshot/registration/sync/terrainMs=" + format(avgStepMs) + + " step/snapshot/sync/terrainMs=" + format(avgStepMs) + "/" + format(avgSnapshotMs) - + "/" + format(avgRegistrationPublicationMs) + "/" + format(avgSyncMs) + "/" + format(avgTerrainMs) + " bodies dynamic/physicsChunk=" + dynamicBodies diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index aff2f197..c79da2f1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -315,15 +315,11 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, SpaceStats stats = SpaceStats.collect(physicsStore, spaceId); double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); - double avgRegistrationPublicationMs = averageMillis( - step.getRegistrationPublicationNanos(), - step.getTickSamples()); double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); double avgTerrainMs = averageMillis(collisionProfilingSnapshot.getTickNanos(), collisionProfilingSnapshot.getTickSamples()); double totalMs = avgStepMs + avgSnapshotMs - + avgRegistrationPublicationMs + avgSyncMs + avgTerrainMs; MatrixHealth health = assessHealth(matrixCase, @@ -336,7 +332,6 @@ private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, observedTickRate, avgStepMs, avgSnapshotMs, - avgRegistrationPublicationMs, avgSyncMs, avgTerrainMs, totalMs, @@ -515,7 +510,7 @@ private static void logComparison(@Nonnull List reports) { MatrixReport first = reports.get(0); MatrixReport second = reports.get(1); LOGGER.at(Level.INFO).log("Crucible Rapier body matrix comparison: %sx=%sms " - + "%sx=%sms stepRatio=%s snapshotRatio=%s registrationRatio=%s " + + "%sx=%sms stepRatio=%s snapshotRatio=%s " + "totalRatio=%s collisionCounters=%s/%s", first.matrixCase().fixedSubsteps(), format(first.avgStepMs()), @@ -523,8 +518,6 @@ private static void logComparison(@Nonnull List reports) { format(second.avgStepMs()), format(ratio(second.avgStepMs(), first.avgStepMs())), format(ratio(second.avgSnapshotMs(), first.avgSnapshotMs())), - format(ratio(second.avgRegistrationPublicationMs(), - first.avgRegistrationPublicationMs())), format(ratio(second.totalMs(), first.totalMs())), first.terrainCounterSummary(), second.terrainCounterSummary()); @@ -635,7 +628,6 @@ private record MatrixReport(@Nonnull MatrixCase matrixCase, double observedTickRate, double avgStepMs, double avgSnapshotMs, - double avgRegistrationPublicationMs, double avgSyncMs, double avgTerrainMs, double totalMs, @@ -676,7 +668,6 @@ private static MatrixReport failedPreflight(@Nonnull MatrixCase matrixCase, 0.0, 0.0, 0.0, - 0.0, 0, 0, 0, @@ -712,9 +703,8 @@ private String summary() { + " reason=" + health.reason() + " tps=" + format(observedTickRate) + " totalMs=" + format(totalMs) - + " step/snapshot/registration/sync/terrainMs=" + format(avgStepMs) + + " step/snapshot/sync/terrainMs=" + format(avgStepMs) + "/" + format(avgSnapshotMs) - + "/" + format(avgRegistrationPublicationMs) + "/" + format(avgSyncMs) + "/" + format(avgTerrainMs) + " step samples/substeps/bodySnapshots/spatialCells=" + stepSamples diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java index 9679b9c3..3293313f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java @@ -92,11 +92,6 @@ private static void sendReport(@Nonnull CommandContext ctx, + " indexCells=" + formatAverage(cumulativeStep.getSpatialIndexCells(), cumulativeStep.getTickSamples()))); ctx.sender().sendMessage(Message.raw("Physics snapshot avg ms/completedStep=" + formatAverageMillis(cumulativeStep.getSnapshotNanos(), cumulativeStep.getTickSamples()))); - ctx.sender().sendMessage(Message.raw("Physics registration publication avg ms/completedStep=" - + formatAverageMillis(cumulativeStep.getRegistrationPublicationNanos(), - cumulativeStep.getTickSamples()) - + " rebuilds/skips=" + cumulativeStep.getRegistrationPublicationRebuilds() - + "/" + cumulativeStep.getRegistrationPublicationSkips())); ctx.sender().sendMessage(Message.raw("Physics store tick avg queued/run/latency ms=" + formatAverageMillis(cumulativeStep.getStoreTickQueuedNanos(), cumulativeStep.getTickSamples()) + "/" + formatAverageMillis(cumulativeStep.getStoreTickRunNanos(), cumulativeStep.getTickSamples()) @@ -142,12 +137,6 @@ private static void sendReport(@Nonnull CommandContext ctx, + "/" + latestStep.getSpatialIndexCells() + " snapshot latest/worst ms=" + formatMillis(latestStep.getSnapshotNanos()) + "/" + formatMillis(worstStep.getSnapshotNanos()) - + " registration latest/worst ms=" - + formatMillis(latestStep.getRegistrationPublicationNanos()) - + "/" + formatMillis(worstStep.getRegistrationPublicationNanos()) - + " registration latest rebuilds/skips=" - + latestStep.getRegistrationPublicationRebuilds() - + "/" + latestStep.getRegistrationPublicationSkips() + " pendingAge latest/max ms=" + formatMillis(latestStep.getPendingStepAgeNanos()) + "/" + formatMillis(worstStep.getMaxPendingStepAgeNanos()))); if (cumulativeStep.getNativePhaseSamples() > 0) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index d4337567..1d82d6f8 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -9,7 +9,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -100,8 +99,6 @@ public static void clearBodyCopiedState(@Nonnull Store store, PhysicsControlRuntimeStates.clearControlled(removal.bodyRef()); } store.getResource(PhysicsSnapshotResource.getResourceType()).removeBodies(bodyUuids); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .removeBodies(bodyUuids); } public static void removeBodyEntity(@Nonnull Store store, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java index 4581819e..e97d2417 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; @@ -37,7 +36,6 @@ public static void clearAll(@Nonnull Store store) { store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()).clear(); store.getResource(PhysicsEventResource.getResourceType()).clear(); store.getResource(PhysicsProfilingResource.getResourceType()).reset(); store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java index 34f06945..36a7b9a5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -324,7 +323,6 @@ private static List bodyEntityRemovals( private static void clearCopiedBodyState(@Nonnull Store store) { store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()).clear(); store.getResource(PhysicsEventResource.getResourceType()).clear(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 2c8e0104..5a29929a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -10,7 +10,6 @@ import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; @@ -116,10 +115,6 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic () -> cleanupResource(store, PhysicsSnapshotResource.getResourceType(), PhysicsSnapshotResource::clear)); - failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsBodyRegistrationResource.getResourceType(), - PhysicsBodyRegistrationResource::clear)); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsEventResource.getResourceType(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java deleted file mode 100644 index 80e9f490..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodyRegistrationResource.java +++ /dev/null @@ -1,208 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Latest copied body registrations published by the authoritative PhysicsStore. - */ -public final class PhysicsBodyRegistrationResource implements Resource { - - @Nonnull - private volatile PublishedRegistrations registrations = PublishedRegistrations.EMPTY; - - public PhysicsBodyRegistrationResource() { - } - - @Nullable - public SpaceId getBodySpaceId(@Nonnull UUID bodyUuid) { - return registrations.spaceIdsByUuid().get(Objects.requireNonNull(bodyUuid, "bodyUuid")); - } - - @Nullable - public SpaceId getBodySpaceId(@Nonnull Ref bodyRef) { - RegistrationByRef registration = registrations.registrationsByRowIndex() - .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); - return registration != null && sameRef(registration.bodyRef(), bodyRef) - ? registration.spaceId() - : null; - } - - @Nullable - public UUID getBodyUuid(@Nonnull Ref bodyRef) { - RegistrationByRef registration = registrations.registrationsByRowIndex() - .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); - return registration != null && sameRef(registration.bodyRef(), bodyRef) - ? registration.bodyUuid() - : null; - } - - public boolean hasBody(@Nonnull UUID bodyUuid) { - return registrations.spaceIdsByUuid() - .containsKey(Objects.requireNonNull(bodyUuid, "bodyUuid")); - } - - public boolean hasBody(@Nonnull Ref bodyRef) { - return getBodyUuid(bodyRef) != null; - } - - @Nonnull - public Collection getBodyUuids() { - return registrations.bodyUuids(); - } - - public int getBodyRegistrationCount() { - return registrations.bodyUuids().size(); - } - - public boolean isCurrent(long registrationTopologyGeneration) { - return registrations.registrationTopologyGeneration() == registrationTopologyGeneration; - } - - public int getBodyRegistrationCount(@Nonnull SpaceId spaceId) { - Objects.requireNonNull(spaceId, "spaceId"); - int count = 0; - for (SpaceId registeredSpaceId : registrations.spaceIdsByUuid().values()) { - if (registeredSpaceId.equals(spaceId)) { - count++; - } - } - return count; - } - - public void publish(long registrationTopologyGeneration, - @Nonnull Collection publications) { - Object2ObjectLinkedOpenHashMap publicationsByUuid = - new Object2ObjectLinkedOpenHashMap<>(publications.size()); - for (BodyRegistrationPublication publication : publications) { - BodyRegistrationPublication checkedPublication = - Objects.requireNonNull(publication, "publication"); - publicationsByUuid.put(checkedPublication.bodyUuid(), checkedPublication); - } - - Object2ObjectLinkedOpenHashMap spaceIdsByUuid = - new Object2ObjectLinkedOpenHashMap<>(publicationsByUuid.size()); - Int2ObjectOpenHashMap registrationsByRowIndex = - new Int2ObjectOpenHashMap<>(publicationsByUuid.size()); - for (BodyRegistrationPublication publication : publicationsByUuid.values()) { - spaceIdsByUuid.put(publication.bodyUuid(), publication.spaceId()); - registrationsByRowIndex.put(publication.bodyRef().getIndex(), - new RegistrationByRef(publication.bodyRef(), - publication.bodyUuid(), - publication.spaceId())); - } - registrations = new PublishedRegistrations(registrationTopologyGeneration, - new ArrayList<>(spaceIdsByUuid.keySet()), - spaceIdsByUuid, - registrationsByRowIndex); - } - - public void removeBody(@Nonnull UUID bodyUuid) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - removeBodies(List.of(bodyUuid)); - } - - public void removeBodies(@Nonnull Collection bodyUuids) { - Objects.requireNonNull(bodyUuids, "bodyUuids"); - PublishedRegistrations current = registrations; - if (bodyUuids.isEmpty()) { - return; - } - ObjectOpenHashSet removedBodyUuids = new ObjectOpenHashSet<>(bodyUuids.size()); - for (UUID bodyUuid : bodyUuids) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - if (current.spaceIdsByUuid().containsKey(bodyUuid)) { - removedBodyUuids.add(bodyUuid); - } - } - if (removedBodyUuids.isEmpty()) { - return; - } - Object2ObjectLinkedOpenHashMap spaceIdsByUuid = - new Object2ObjectLinkedOpenHashMap<>(current.spaceIdsByUuid()); - for (UUID bodyUuid : removedBodyUuids) { - spaceIdsByUuid.remove(bodyUuid); - } - Int2ObjectOpenHashMap registrationsByRowIndex = - new Int2ObjectOpenHashMap<>(current.registrationsByRowIndex()); - registrationsByRowIndex.int2ObjectEntrySet() - .removeIf(entry -> removedBodyUuids.contains(entry.getValue().bodyUuid())); - registrations = new PublishedRegistrations(current.registrationTopologyGeneration(), - new ArrayList<>(spaceIdsByUuid.keySet()), - spaceIdsByUuid, - registrationsByRowIndex); - } - - public void clear() { - registrations = PublishedRegistrations.EMPTY; - } - - @Nonnull - @Override - public PhysicsBodyRegistrationResource clone() { - PhysicsBodyRegistrationResource copy = new PhysicsBodyRegistrationResource(); - copy.registrations = registrations; - return copy; - } - - @Nonnull - public static ResourceType getResourceType() { - return PhysicsResourceTypes.bodyRegistrationResourceType(); - } - - public record BodyRegistrationPublication( - @Nonnull Ref bodyRef, - @Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId) { - - public BodyRegistrationPublication { - Objects.requireNonNull(bodyRef, "bodyRef"); - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(spaceId, "spaceId"); - } - } - - private record RegistrationByRef(@Nonnull Ref bodyRef, - @Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId) { - - private RegistrationByRef { - Objects.requireNonNull(bodyRef, "bodyRef"); - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(spaceId, "spaceId"); - } - } - - private record PublishedRegistrations( - long registrationTopologyGeneration, - @Nonnull List bodyUuids, - @Nonnull Map spaceIdsByUuid, - @Nonnull Int2ObjectOpenHashMap registrationsByRowIndex) { - - private static final PublishedRegistrations EMPTY = - new PublishedRegistrations(-1L, - List.of(), - Map.of(), - new Int2ObjectOpenHashMap<>()); - } - - private static boolean sameRef(@Nonnull Ref first, - @Nonnull Ref second) { - return first.getIndex() == second.getIndex() - && first.getStore() == second.getStore(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java index 25d9fa52..aea395af 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProfilingResource.java @@ -4,6 +4,8 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; +import lombok.Getter; +import lombok.Setter; import java.util.Objects; import javax.annotation.Nonnull; @@ -12,15 +14,19 @@ */ public final class PhysicsProfilingResource implements Resource { + @Setter + @Getter private boolean enabled; + @Getter private long snapshotNanos; - private long registrationPublicationNanos; + @Getter private long stepSubmitNanos; + @Getter private int spaces; + @Getter private int substeps; + @Getter private int publishedBodies; - private int registrationPublicationRebuilds; - private int registrationPublicationSkips; private int schedulerSamples; private float schedulerInputDtSeconds; private float schedulerSubmittedDtSeconds; @@ -33,14 +39,6 @@ public final class PhysicsProfilingResource implements Resource { public PhysicsProfilingResource() { } - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - public void recordStep(long stepSubmitNanos, int spaces, int substeps, @@ -56,13 +54,6 @@ public void recordSnapshot(long snapshotNanos, int publishedBodies) { this.publishedBodies = Math.max(0, publishedBodies); } - public void recordRegistrationPublication(long registrationPublicationNanos, - boolean rebuilt) { - this.registrationPublicationNanos = Math.max(0L, registrationPublicationNanos); - registrationPublicationRebuilds = rebuilt ? 1 : 0; - registrationPublicationSkips = rebuilt ? 0 : 1; - } - public void recordStepScheduling(float inputDtSeconds, float submittedDtSeconds, float backlogDtSeconds, @@ -78,13 +69,10 @@ public void recordStepScheduling(float inputDtSeconds, public void reset() { snapshotNanos = 0L; - registrationPublicationNanos = 0L; stepSubmitNanos = 0L; spaces = 0; substeps = 0; publishedBodies = 0; - registrationPublicationRebuilds = 0; - registrationPublicationSkips = 0; schedulerSamples = 0; schedulerInputDtSeconds = 0.0f; schedulerSubmittedDtSeconds = 0.0f; @@ -100,10 +88,7 @@ public StepSample latestStepSample() { substeps, stepSubmitNanos, snapshotNanos, - registrationPublicationNanos, publishedBodies, - registrationPublicationRebuilds, - registrationPublicationSkips, schedulerSamples, schedulerInputDtSeconds, schedulerSubmittedDtSeconds, @@ -113,38 +98,6 @@ public StepSample latestStepSample() { nativePhaseStats); } - public long getSnapshotNanos() { - return snapshotNanos; - } - - public long getRegistrationPublicationNanos() { - return registrationPublicationNanos; - } - - public long getStepSubmitNanos() { - return stepSubmitNanos; - } - - public int getSpaces() { - return spaces; - } - - public int getSubsteps() { - return substeps; - } - - public int getPublishedBodies() { - return publishedBodies; - } - - public int getRegistrationPublicationRebuilds() { - return registrationPublicationRebuilds; - } - - public int getRegistrationPublicationSkips() { - return registrationPublicationSkips; - } - @Nonnull public PhysicsStepPhaseStats getNativePhaseStats() { return nativePhaseStats; @@ -156,13 +109,10 @@ public PhysicsProfilingResource clone() { PhysicsProfilingResource copy = new PhysicsProfilingResource(); copy.enabled = enabled; copy.snapshotNanos = snapshotNanos; - copy.registrationPublicationNanos = registrationPublicationNanos; copy.stepSubmitNanos = stepSubmitNanos; copy.spaces = spaces; copy.substeps = substeps; copy.publishedBodies = publishedBodies; - copy.registrationPublicationRebuilds = registrationPublicationRebuilds; - copy.registrationPublicationSkips = registrationPublicationSkips; copy.schedulerSamples = schedulerSamples; copy.schedulerInputDtSeconds = schedulerInputDtSeconds; copy.schedulerSubmittedDtSeconds = schedulerSubmittedDtSeconds; @@ -182,10 +132,7 @@ public record StepSample(int spaces, int substeps, long stepSubmitNanos, long snapshotNanos, - long registrationPublicationNanos, int publishedBodies, - int registrationPublicationRebuilds, - int registrationPublicationSkips, int schedulerSamples, float schedulerInputDtSeconds, float schedulerSubmittedDtSeconds, @@ -199,10 +146,7 @@ public record StepSample(int spaces, substeps = Math.max(0, substeps); stepSubmitNanos = Math.max(0L, stepSubmitNanos); snapshotNanos = Math.max(0L, snapshotNanos); - registrationPublicationNanos = Math.max(0L, registrationPublicationNanos); publishedBodies = Math.max(0, publishedBodies); - registrationPublicationRebuilds = Math.max(0, registrationPublicationRebuilds); - registrationPublicationSkips = Math.max(0, registrationPublicationSkips); schedulerSamples = Math.max(0, schedulerSamples); schedulerInputDtSeconds = safeDt(schedulerInputDtSeconds); schedulerSubmittedDtSeconds = safeDt(schedulerSubmittedDtSeconds); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index d3443150..4ce5db34 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -28,9 +28,6 @@ public final class PhysicsResourceTypes { @Nullable private static ResourceType snapshotResourceType; @Nullable - private static ResourceType - bodyRegistrationResourceType; - @Nullable private static ResourceType eventResourceType; @Nullable private static ResourceType readQueueResourceType; @@ -67,9 +64,6 @@ public static void registerResourceTypes( snapshotResourceType = registry.registerResource( PhysicsSnapshotResource.class, PhysicsSnapshotResource::new); - bodyRegistrationResourceType = registry.registerResource( - PhysicsBodyRegistrationResource.class, - PhysicsBodyRegistrationResource::new); eventResourceType = registry.registerResource( PhysicsEventResource.class, PhysicsEventResource::new); @@ -121,12 +115,6 @@ public static ResourceType snapshotResour return snapshotResourceType; } - @Nonnull - public static ResourceType - bodyRegistrationResourceType() { - return bodyRegistrationResourceType; - } - @Nonnull public static ResourceType eventResourceType() { return eventResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java index 41cd602f..cfef6ed3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/profiling/PhysicsRuntimeProfilingResource.java @@ -203,40 +203,6 @@ public synchronized void recordStep(int spaces, int preStepDrainedMutations, long preStepDrainRunNanos, int lateMutationBacklogAtStep) { - recordStep(spaces, - substeps, - nanos, - bodySnapshots, - spatialIndexCells, - snapshotNanos, - storeTickQueuedNanos, - storeTickRunNanos, - storeTickCompletedNanos, - nativePhaseStats, - preStepDrainedMutations, - preStepDrainRunNanos, - lateMutationBacklogAtStep, - 0L, - 0, - 0); - } - - public synchronized void recordStep(int spaces, - int substeps, - long nanos, - int bodySnapshots, - int spatialIndexCells, - long snapshotNanos, - long storeTickQueuedNanos, - long storeTickRunNanos, - long storeTickCompletedNanos, - @Nonnull PhysicsStepPhaseStats nativePhaseStats, - int preStepDrainedMutations, - long preStepDrainRunNanos, - int lateMutationBacklogAtStep, - long registrationPublicationNanos, - int registrationPublicationRebuilds, - int registrationPublicationSkips) { StepSnapshot snapshot = new StepSnapshot(); snapshot.recordTickSample(); snapshot.setSpaces(spaces); @@ -249,9 +215,6 @@ public synchronized void recordStep(int spaces, snapshot.setStoreTickRunNanos(storeTickRunNanos); snapshot.recordStoreTickStepInterval(recordStoreTickStepInterval(storeTickCompletedNanos)); snapshot.setNativePhaseStats(nativePhaseStats); - snapshot.recordRegistrationPublication(registrationPublicationNanos, - registrationPublicationRebuilds, - registrationPublicationSkips); snapshot.recordPreStepDrain(Math.max(0, preStepDrainedMutations), Math.max(0L, preStepDrainRunNanos), Math.max(0, lateMutationBacklogAtStep)); @@ -455,9 +418,6 @@ public static final class StepSnapshot { private long tickNanos; @Setter private long snapshotNanos; - private long registrationPublicationNanos; - private int registrationPublicationRebuilds; - private int registrationPublicationSkips; @Setter private long storeTickQueuedNanos; @Setter @@ -505,9 +465,6 @@ public void copyFrom(@Nonnull StepSnapshot other) { spatialIndexCells = other.spatialIndexCells; tickNanos = other.tickNanos; snapshotNanos = other.snapshotNanos; - registrationPublicationNanos = other.registrationPublicationNanos; - registrationPublicationRebuilds = other.registrationPublicationRebuilds; - registrationPublicationSkips = other.registrationPublicationSkips; storeTickQueuedNanos = other.storeTickQueuedNanos; storeTickRunNanos = other.storeTickRunNanos; preStepDrainedMutations = other.preStepDrainedMutations; @@ -546,9 +503,6 @@ public void add(@Nonnull StepSnapshot other) { spatialIndexCells += other.spatialIndexCells; tickNanos += other.tickNanos; snapshotNanos += other.snapshotNanos; - registrationPublicationNanos += other.registrationPublicationNanos; - registrationPublicationRebuilds += other.registrationPublicationRebuilds; - registrationPublicationSkips += other.registrationPublicationSkips; storeTickQueuedNanos += other.storeTickQueuedNanos; storeTickRunNanos += other.storeTickRunNanos; preStepDrainedMutations += other.preStepDrainedMutations; @@ -592,9 +546,6 @@ public void reset() { spatialIndexCells = 0; tickNanos = 0L; snapshotNanos = 0L; - registrationPublicationNanos = 0L; - registrationPublicationRebuilds = 0; - registrationPublicationSkips = 0; storeTickQueuedNanos = 0L; storeTickRunNanos = 0L; preStepDrainedMutations = 0; @@ -647,14 +598,6 @@ public void recordPreStepDrain(int drainedMutations, maxLateMutationBacklogAtStep = lateMutationBacklogAtStep; } - public void recordRegistrationPublication(long nanos, - int rebuilds, - int skips) { - registrationPublicationNanos = Math.max(0L, nanos); - registrationPublicationRebuilds = Math.max(0, rebuilds); - registrationPublicationSkips = Math.max(0, skips); - } - private void retainPreStepDrainMaxima(@Nonnull StepSnapshot snapshot) { retainPreStepDrainMaxima(snapshot.maxPreStepDrainedMutations, snapshot.maxLateMutationBacklogAtStep); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index c7d7a565..9026a628 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -1,7 +1,5 @@ package dev.hytalemodding.impulse.core.internal.systems; -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; @@ -10,27 +8,18 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource.BodyRegistrationPublication; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; -import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.UUID; -import java.util.function.BiConsumer; import javax.annotation.Nonnull; /** @@ -57,13 +46,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) .markFailed(message != null ? message : "PhysicsStore owner-lane step failed"); throw new IllegalStateException("PhysicsStore owner-lane step failed", failure); } - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshot = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = store.getResource( - PhysicsBodyRegistrationResource.getResourceType()); PhysicsProfilingResource profiling = store.getResource(PhysicsProfilingResource.getResourceType()); - PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( - PhysicsSpaceCompatibilityIndexResource.getResourceType()); profiling.recordStep(completed.stepSubmitNanos(), completed.spaces(), completed.substeps(), @@ -83,13 +67,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) frameDt, bodies); snapshot.publish(frame); - publishRegistrations(store, - systemIndex, - runtime, - compatibility, - snapshot, - registrations, - profiling); profiling.recordSnapshot(completed.snapshotNanos(), bodies.size()); store.getResource(PhysicsEventResource.getResourceType()) .publishStepFrame(frame.sequence(), @@ -101,73 +78,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) completed.droppedBackendEventCount()); } - private static void publishRegistrations(@Nonnull Store store, - int systemIndex, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull PhysicsSnapshotResource snapshot, - @Nonnull PhysicsBodyRegistrationResource registrations, - @Nonnull PhysicsProfilingResource profiling) { - long generation = runtime.getRegistrationTopologyGeneration(); - if (registrations.isCurrent(generation)) { - profiling.recordRegistrationPublication(0L, false); - return; - } - long startNanos = profiling.isEnabled() ? System.nanoTime() : 0L; - registrations.publish(generation, - collectRegistrations(store, - systemIndex, - runtime, - compatibility, - snapshot)); - long publicationNanos = profiling.isEnabled() - ? System.nanoTime() - startNanos - : 0L; - profiling.recordRegistrationPublication(publicationNanos, true); - } - - @Nonnull - private static List collectRegistrations( - @Nonnull Store store, - int systemIndex, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull PhysicsSnapshotResource snapshot) { - List registrations = - new ArrayList<>(snapshot.getLatestFrame().bodies().size()); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> collectRegistrations(runtime, - compatibility, - snapshot, - registrations, - chunk); - store.forEachChunk(systemIndex, collector); - return registrations; - } - - private static void collectRegistrations(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull PhysicsSnapshotResource snapshot, - @Nonnull List registrations, - @Nonnull ArchetypeChunk chunk) { - for (int index = 0; index < chunk.size(); index++) { - UUID rowUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); - if (PhysicsStoreSystemSupport.isNil(rowUuid)) { - continue; - } - var rowRef = chunk.getReferenceTo(index); - BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); - if (body != null && snapshot.containsBody(rowUuid)) { - SpaceId spaceId = compatibility.getSpaceId(body.getSpaceUuid()); - if (spaceId != null) { - registrations.add(new BodyRegistrationPublication(rowRef, - rowUuid, - spaceId)); - } - } - } - } - @Nonnull @Override public Set> getDependencies() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index f91ef6e8..8f6b0a65 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -86,10 +86,7 @@ private static void recordProfiling(@Nonnull Store store, sample.nativePhaseStats(), 0, 0L, - 0, - sample.registrationPublicationNanos(), - sample.registrationPublicationRebuilds(), - sample.registrationPublicationSkips()); + 0); if (sample.schedulerSamples() > 0) { runtimeProfiling.recordStepScheduling(sample.schedulerInputDtSeconds(), sample.schedulerSubmittedDtSeconds(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java index 62245d2f..37388e32 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsRuntimeProfiling.java @@ -98,18 +98,6 @@ public long getSnapshotNanos() { return snapshot.getSnapshotNanos(); } - public long getRegistrationPublicationNanos() { - return snapshot.getRegistrationPublicationNanos(); - } - - public int getRegistrationPublicationRebuilds() { - return snapshot.getRegistrationPublicationRebuilds(); - } - - public int getRegistrationPublicationSkips() { - return snapshot.getRegistrationPublicationSkips(); - } - public long getStoreTickQueuedNanos() { return snapshot.getStoreTickQueuedNanos(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodies.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodies.java index e0db0900..36b538be 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodies.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodies.java @@ -6,11 +6,12 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.util.ArrayList; import java.util.Collection; import java.util.Objects; import java.util.UUID; @@ -26,47 +27,35 @@ public final class PhysicsBodies { private PhysicsBodies() { } - @Nullable - public static UUID bodyUuid(@Nonnull Store store, - @Nonnull Ref bodyRef) { - Store checkedStore = requireWorldThread(store, - "read copied PhysicsStore body registration"); - Ref checkedRef = Objects.requireNonNull(bodyRef, "bodyRef"); - if (!sameValidStore(checkedStore, checkedRef)) { - return null; - } - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyUuid(checkedRef); - } - @Nullable public static SpaceId spaceId(@Nonnull Store store, @Nonnull UUID bodyUuid) { Store checkedStore = requireWorldThread(store, - "read copied PhysicsStore body registration"); - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodySpaceId(Objects.requireNonNull(bodyUuid, "bodyUuid")); + "read copied PhysicsStore body space"); + PhysicsBodySnapshot snapshot = snapshotResource(checkedStore) + .getBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); + return spaceId(checkedStore, snapshot); } @Nullable public static SpaceId spaceId(@Nonnull Store store, @Nonnull Ref bodyRef) { Store checkedStore = requireWorldThread(store, - "read copied PhysicsStore body registration"); + "read copied PhysicsStore body space"); Ref checkedRef = Objects.requireNonNull(bodyRef, "bodyRef"); if (!sameValidStore(checkedStore, checkedRef)) { return null; } - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodySpaceId(checkedRef); + return spaceId(checkedStore, snapshotResource(checkedStore).getBody(checkedRef)); } public static boolean isRegistered(@Nonnull Store store, @Nonnull UUID bodyUuid) { Store checkedStore = requireWorldThread(store, "read copied PhysicsStore body registration"); - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .hasBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); + PhysicsBodySnapshot snapshot = snapshotResource(checkedStore) + .getBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); + return spaceId(checkedStore, snapshot) != null; } public static boolean isRegistered(@Nonnull Store store, @@ -77,31 +66,47 @@ public static boolean isRegistered(@Nonnull Store store, if (!sameValidStore(checkedStore, checkedRef)) { return false; } - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .hasBody(checkedRef); + return spaceId(checkedStore, snapshotResource(checkedStore).getBody(checkedRef)) != null; } @Nonnull public static Collection bodyUuids(@Nonnull Store store) { Store checkedStore = requireWorldThread(store, "read copied PhysicsStore body registrations"); - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyUuids(); + Collection bodies = snapshotFrame(checkedStore).bodies(); + ArrayList bodyUuids = new ArrayList<>(bodies.size()); + for (PhysicsBodySnapshot body : bodies) { + if (spaceId(checkedStore, body) != null) { + bodyUuids.add(body.bodyUuid()); + } + } + return bodyUuids; } public static int registrationCount(@Nonnull Store store) { Store checkedStore = requireWorldThread(store, "count copied PhysicsStore body registrations"); - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationCount(); + int count = 0; + for (PhysicsBodySnapshot body : snapshotFrame(checkedStore).bodies()) { + if (spaceId(checkedStore, body) != null) { + count++; + } + } + return count; } public static int registrationCount(@Nonnull Store store, @Nonnull SpaceId spaceId) { Store checkedStore = requireWorldThread(store, "count copied PhysicsStore body registrations"); - return checkedStore.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .getBodyRegistrationCount(Objects.requireNonNull(spaceId, "spaceId")); + SpaceId checkedSpaceId = Objects.requireNonNull(spaceId, "spaceId"); + int count = 0; + for (PhysicsBodySnapshot body : snapshotFrame(checkedStore).bodies()) { + if (checkedSpaceId.equals(spaceId(checkedStore, body))) { + count++; + } + } + return count; } @Nullable @@ -225,4 +230,19 @@ private static boolean sameValidStore(@Nonnull Store store, @Nonnull Ref ref) { return ref.getStore() == store && ref.isValid(); } + + @Nonnull + private static PhysicsSnapshotResource snapshotResource(@Nonnull Store store) { + return store.getResource(PhysicsSnapshotResource.getResourceType()); + } + + @Nullable + private static SpaceId spaceId(@Nonnull Store store, + @Nullable PhysicsBodySnapshot snapshot) { + if (snapshot == null) { + return null; + } + return store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceId(snapshot.spaceUuid()); + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java index b2e323e9..31a51832 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -15,10 +15,10 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; @@ -50,10 +50,9 @@ void radiusCleanUsesEcsOwnershipInsteadOfLegacyKind() throws Exception { UUID bodyUuid = UUID.randomUUID(); UUID generatedUuid = UUID.randomUUID(); UUID spaceUuid = UUID.randomUUID(); - Ref bodyRef = addBodyIdentityRow(store, bodyUuid, false); - Ref generatedRef = addBodyIdentityRow(store, generatedUuid, true); + addBodyIdentityRow(store, bodyUuid, false); + addBodyIdentityRow(store, generatedUuid, true); publishSnapshots(store, bodyUuid, generatedUuid, spaceUuid); - publishRegistrations(store, bodyUuid, bodyRef, generatedUuid, generatedRef); Set selected = selectBodyUuidsNear(store, new Vector3d(), 10.0f); @@ -68,23 +67,14 @@ private static void publishSnapshots(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nonnull UUID generatedUuid, @Nonnull UUID spaceUuid) { + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(new SpaceId(1), spaceUuid); store.getResource(PhysicsSnapshotResource.getResourceType()) .publish(new PhysicsSnapshotFrame(1L, 0.05f, List.of(snapshot(bodyUuid, spaceUuid), snapshot(generatedUuid, spaceUuid)))); } - private static void publishRegistrations(@Nonnull Store store, - @Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nonnull UUID generatedUuid, - @Nonnull Ref generatedRef) { - store.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .publish(1L, - List.of(publication(bodyRef, bodyUuid), - publication(generatedRef, generatedUuid))); - } - @Nonnull private static Ref addBodyIdentityRow(@Nonnull Store store, @Nonnull UUID bodyUuid, @@ -131,16 +121,6 @@ private static PhysicsBodySnapshot snapshot(@Nonnull UUID bodyUuid, false); } - @Nonnull - private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( - @Nonnull Ref bodyRef, - @Nonnull UUID bodyUuid) { - return new PhysicsBodyRegistrationResource.BodyRegistrationPublication( - bodyRef, - bodyUuid, - new SpaceId(1)); - } - @Nonnull private static Set selectBodyUuidsNear(@Nonnull Store store, @Nonnull Vector3d center, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java index 6c28bdff..fb3e8450 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java @@ -16,13 +16,14 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; @@ -69,15 +70,13 @@ void clearBodyCopiedStateRemovesMultipleBodiesWithoutRemovingRows() { PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = store.getResource( - PhysicsBodyRegistrationResource.getResourceType()); assertNull(snapshots.getBody(firstBodyUuid)); assertNull(snapshots.getBody(secondBodyUuid)); assertNotNull(snapshots.getBody(retainedBodyUuid)); - assertNull(registrations.getBodySpaceId(firstBodyUuid)); - assertNull(registrations.getBodySpaceId(secondBodyUuid)); - assertNotNull(registrations.getBodySpaceId(retainedBodyUuid)); - assertEquals(1, registrations.getBodyRegistrationCount()); + assertNull(PhysicsBodies.spaceId(store, firstBodyUuid)); + assertNull(PhysicsBodies.spaceId(store, secondBodyUuid)); + assertEquals(new SpaceId(42), PhysicsBodies.spaceId(store, retainedBodyUuid)); + assertEquals(1, PhysicsBodies.registrationCount(store)); assertNotNull(store.getComponent(firstBodyRef, UuidComponent.getComponentType())); assertNotNull(store.getComponent(secondBodyRef, UuidComponent.getComponentType())); } finally { @@ -137,17 +136,14 @@ private static void publishCopiedState(@Nonnull Store store, @Nonnull Ref secondBodyRef, @Nonnull UUID retainedBodyUuid, @Nonnull Ref retainedBodyRef) { + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(new SpaceId(42), spaceUuid); store.getResource(PhysicsSnapshotResource.getResourceType()) .publish(new PhysicsSnapshotFrame(1L, 0.05f, List.of(snapshot(firstBodyRef, firstBodyUuid, spaceUuid), snapshot(secondBodyRef, secondBodyUuid, spaceUuid), snapshot(retainedBodyRef, retainedBodyUuid, spaceUuid)))); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .publish(1L, - List.of(publication(firstBodyRef, firstBodyUuid), - publication(secondBodyRef, secondBodyUuid), - publication(retainedBodyRef, retainedBodyUuid))); } @Nonnull @@ -175,15 +171,6 @@ private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, false); } - @Nonnull - private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( - @Nonnull Ref bodyRef, - @Nonnull UUID bodyUuid) { - return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, - bodyUuid, - new SpaceId(42)); - } - @Nonnull private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java index f1d06417..3d44b385 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java @@ -26,11 +26,11 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -41,6 +41,7 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.components.JointType; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; @@ -102,8 +103,6 @@ void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = store.getResource( - PhysicsBodyRegistrationResource.getResourceType()); assertNull(identity.getByUuid(bodyAUuid)); assertNull(identity.getByUuid(jointUuid)); Ref remainingBodyRef = identity.getByUuid(bodyBUuid); @@ -115,8 +114,8 @@ void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { assertEquals(0, space.runtime().jointCount(space.handle().value())); assertNull(snapshots.getBody(bodyAUuid)); assertNotNull(snapshots.getBody(bodyBUuid)); - assertFalse(registrations.hasBody(bodyAUuid)); - assertNotNull(registrations.getBodySpaceId(bodyBUuid)); + assertFalse(PhysicsBodies.isRegistered(store, bodyAUuid)); + assertEquals(new SpaceId(42), PhysicsBodies.spaceId(store, bodyBUuid)); assertFalse(bodyARef.isValid()); assertFalse(jointRef.isValid()); assertNotNull(store.getComponent(remainingBodyRef, BodyComponent.getComponentType())); @@ -146,6 +145,8 @@ private static BoundSpace addBoundSpace(@Nonnull Store store, PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); runtime.putRuntime(backendId, backendRuntime); runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(new SpaceId(42), spaceUuid); return new BoundSpace(spaceRef, backendRuntime, spaceHandle, backendId); } @@ -293,10 +294,6 @@ private static void publishCopiedState(@Nonnull Store store, 0.05f, List.of(snapshot(bodyARef, bodyAUuid, spaceUuid), snapshot(bodyBRef, bodyBUuid, spaceUuid)))); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .publish(1L, - List.of(publication(bodyARef, bodyAUuid), - publication(bodyBRef, bodyBUuid))); } @Nonnull @@ -324,15 +321,6 @@ private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, false); } - @Nonnull - private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( - @Nonnull Ref bodyRef, - @Nonnull UUID bodyUuid) { - return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, - bodyUuid, - new SpaceId(42)); - } - private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { try { Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index c3b6bdd5..a98fe687 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.core.internal.resources; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -16,7 +15,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -248,40 +246,6 @@ void snapshotResourceRemovesMultipleBodiesInOneBatch() { assertEquals(List.of(retained), resource.getLatestFrame().bodies()); } - @Test - void bodyRegistrationResourceRemovesMultipleBodiesInOneBatch() { - PhysicsBodyRegistrationResource resource = new PhysicsBodyRegistrationResource(); - UUID firstBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000019"); - UUID secondBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000020"); - UUID retainedBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000021"); - Ref firstBodyRef = new TestRef(19); - Ref secondBodyRef = new TestRef(20); - Ref retainedBodyRef = new TestRef(21); - SpaceId spaceId = new SpaceId(42); - resource.publish(7L, - List.of(new PhysicsBodyRegistrationResource.BodyRegistrationPublication(firstBodyRef, - firstBodyUuid, - spaceId), - new PhysicsBodyRegistrationResource.BodyRegistrationPublication(secondBodyRef, - secondBodyUuid, - spaceId), - new PhysicsBodyRegistrationResource.BodyRegistrationPublication(retainedBodyRef, - retainedBodyUuid, - spaceId))); - - resource.removeBodies(List.of(firstBodyUuid, secondBodyUuid)); - - assertNull(resource.getBodySpaceId(firstBodyUuid)); - assertNull(resource.getBodyUuid(firstBodyRef)); - assertNull(resource.getBodySpaceId(secondBodyUuid)); - assertNull(resource.getBodyUuid(secondBodyRef)); - assertEquals(spaceId, resource.getBodySpaceId(retainedBodyUuid)); - assertEquals(retainedBodyUuid, resource.getBodyUuid(retainedBodyRef)); - assertEquals(List.of(retainedBodyUuid), List.copyOf(resource.getBodyUuids())); - assertEquals(1, resource.getBodyRegistrationCount()); - assertNotNull(resource.getBodySpaceId(retainedBodyRef)); - } - private static final class TestRef extends Ref { private TestRef(int index) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index 4d1f1f73..ce1a3c56 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -32,7 +32,6 @@ import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; @@ -42,6 +41,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -56,6 +56,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; @@ -310,12 +311,10 @@ void removeDeletesGeneratedRowsAndPayloadResource() { publishCopiedState(store, spaceUuid, boxUuid, boxRef, detailUuid, detailRef); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = - store.getResource(PhysicsBodyRegistrationResource.getResourceType()); assertNotNull(snapshots.getBody(boxUuid)); assertNotNull(snapshots.getBody(detailUuid)); - assertTrue(registrations.hasBody(boxUuid)); - assertTrue(registrations.hasBody(detailUuid)); + assertTrue(PhysicsBodies.isRegistered(store, boxUuid)); + assertTrue(PhysicsBodies.isRegistered(store, detailUuid)); queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, sourceKey, 5, 6, 7)); new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); @@ -325,8 +324,8 @@ void removeDeletesGeneratedRowsAndPayloadResource() { assertNull(identity.getByUuid(detailUuid)); assertNull(snapshots.getBody(boxUuid)); assertNull(snapshots.getBody(detailUuid)); - assertFalse(registrations.hasBody(boxUuid)); - assertFalse(registrations.hasBody(detailUuid)); + assertFalse(PhysicsBodies.isRegistered(store, boxUuid)); + assertFalse(PhysicsBodies.isRegistered(store, detailUuid)); assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) .get(payloadKey)); assertSoftSkipsEmpty(store); @@ -610,6 +609,8 @@ private static Ref addBoundSpace(@Nonnull Store stor spaceRef, backendId, new BackendSpaceHandle(spaceHandle)); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(new SpaceId(42), spaceUuid); publishSettingsIndex(store, spaceUuid); return spaceRef; } @@ -645,10 +646,6 @@ private static void publishCopiedState(@Nonnull Store store, 0.05f, List.of(snapshot(firstBodyRef, firstBodyUuid, spaceUuid), snapshot(secondBodyRef, secondBodyUuid, spaceUuid)))); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .publish(1L, - List.of(publication(firstBodyRef, firstBodyUuid), - publication(secondBodyRef, secondBodyUuid))); } @Nonnull @@ -676,15 +673,6 @@ private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, false); } - @Nonnull - private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( - @Nonnull Ref bodyRef, - @Nonnull UUID bodyUuid) { - return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, - bodyUuid, - new SpaceId(42)); - } - private static long previousGeneration(long generation) { return Math.max(0L, generation - 1L); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java index 3aedf830..e4710c09 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java @@ -24,12 +24,12 @@ import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodyRegistrationResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -39,6 +39,7 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; @@ -100,8 +101,6 @@ void tickRemovesMultipleStaleBodiesAndTheirCopiedStateTogether() { PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - PhysicsBodyRegistrationResource registrations = store.getResource( - PhysicsBodyRegistrationResource.getResourceType()); assertFalse(store.getResource(PhysicsRestoreStatusResource.getResourceType()) .isFailed()); assertNull(identity.getByUuid(firstStaleUuid)); @@ -114,9 +113,9 @@ void tickRemovesMultipleStaleBodiesAndTheirCopiedStateTogether() { assertNull(snapshots.getBody(firstStaleUuid)); assertNull(snapshots.getBody(secondStaleUuid)); assertNotNull(snapshots.getBody(retainedUuid)); - assertNull(registrations.getBodySpaceId(firstStaleUuid)); - assertNull(registrations.getBodySpaceId(secondStaleUuid)); - assertNotNull(registrations.getBodySpaceId(retainedUuid)); + assertNull(PhysicsBodies.spaceId(store, firstStaleUuid)); + assertNull(PhysicsBodies.spaceId(store, secondStaleUuid)); + assertEquals(new SpaceId(42), PhysicsBodies.spaceId(store, retainedUuid)); assertFalse(firstStaleRef.isValid()); assertFalse(secondStaleRef.isValid()); } finally { @@ -147,6 +146,8 @@ private static BoundSpace addBoundSpace(@Nonnull Store store, runtimeResource.putRuntime(backendId, runtime); runtimeResource.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); identity.putSpaceHandle(spaceHandle, spaceRef); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(new SpaceId(42), spaceUuid); return new BoundSpace(spaceRef, runtime, spaceHandle, backendId); } @@ -227,11 +228,6 @@ private static void publishCopiedState(@Nonnull Store store, List.of(snapshot(firstBodyRef, firstBodyUuid, spaceUuid), snapshot(secondBodyRef, secondBodyUuid, spaceUuid), snapshot(retainedBodyRef, retainedBodyUuid, spaceUuid)))); - store.getResource(PhysicsBodyRegistrationResource.getResourceType()) - .publish(1L, - List.of(publication(firstBodyRef, firstBodyUuid), - publication(secondBodyRef, secondBodyUuid), - publication(retainedBodyRef, retainedBodyUuid))); } @Nonnull @@ -259,15 +255,6 @@ private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, false); } - @Nonnull - private static PhysicsBodyRegistrationResource.BodyRegistrationPublication publication( - @Nonnull Ref bodyRef, - @Nonnull UUID bodyUuid) { - return new PhysicsBodyRegistrationResource.BodyRegistrationPublication(bodyRef, - bodyUuid, - new SpaceId(42)); - } - @Nonnull private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java new file mode 100644 index 00000000..55e3acfa --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java @@ -0,0 +1,141 @@ +package dev.hytalemodding.impulse.core.plugin.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsBodiesTest { + + @Test + void registrationViewsAreDerivedFromSnapshotsWithCompatibleSpaces() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physics-bodies-snapshot-index")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID firstSpaceUuid = uuid(1); + UUID secondSpaceUuid = uuid(2); + UUID unmappedSpaceUuid = uuid(3); + SpaceId firstSpaceId = new SpaceId(41); + SpaceId secondSpaceId = new SpaceId(42); + Ref firstBodyRef = new TestRef(store, 11); + Ref secondBodyRef = new TestRef(store, 12); + Ref unmappedBodyRef = new TestRef(store, 13); + UUID firstBodyUuid = uuid(101); + UUID secondBodyUuid = uuid(102); + UUID unmappedBodyUuid = uuid(103); + + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(firstSpaceId, firstSpaceUuid); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(secondSpaceId, secondSpaceUuid); + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(firstBodyRef, firstBodyUuid, firstSpaceUuid), + snapshot(secondBodyRef, secondBodyUuid, secondSpaceUuid), + snapshot(unmappedBodyRef, unmappedBodyUuid, unmappedSpaceUuid)))); + + assertEquals(firstSpaceId, PhysicsBodies.spaceId(store, firstBodyUuid)); + assertEquals(secondSpaceId, PhysicsBodies.spaceId(store, secondBodyRef)); + assertNull(PhysicsBodies.spaceId(store, unmappedBodyUuid)); + assertTrue(PhysicsBodies.isRegistered(store, firstBodyUuid)); + assertTrue(PhysicsBodies.isRegistered(store, secondBodyRef)); + assertFalse(PhysicsBodies.isRegistered(store, unmappedBodyUuid)); + assertEquals(List.of(firstBodyUuid, secondBodyUuid), + List.copyOf(PhysicsBodies.bodyUuids(store))); + assertEquals(2, PhysicsBodies.registrationCount(store)); + assertEquals(1, PhysicsBodies.registrationCount(store, firstSpaceId)); + assertEquals(1, PhysicsBodies.registrationCount(store, secondSpaceId)); + assertEquals(0, PhysicsBodies.registrationCount(store, new SpaceId(404))); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void bodyRegistrationResourceTypeIsNotExposed() { + assertThrows(NoSuchMethodException.class, + () -> PhysicsResourceTypes.class.getDeclaredMethod("bodyRegistrationResourceType")); + } + + @Nonnull + private static UUID uuid(long lowBits) { + return new UUID(0L, lowBits); + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + return new PhysicsBodySnapshot(bodyRef, + bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + new Vector3f(), + new Quaternionf(), + new Vector3f(), + new Vector3f(), + 0.0f, + false); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } + + private static final class TestRef extends Ref { + + private TestRef(@Nonnull Store store, int index) { + super(store, index); + } + + @Override + public boolean isValid() { + return true; + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java index 200ca649..485af618 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; @@ -37,6 +38,12 @@ class PhysicsBodyEntitiesTest { + @Test + void physicsBodiesDoesNotExposeBodyUuidRefHelper() { + assertThrows(NoSuchMethodException.class, + () -> PhysicsBodies.class.getDeclaredMethod("bodyUuid", Store.class, Ref.class)); + } + @Test void dynamicBodyHolderInfersEntityIdentityFromBodyUuidAndSpaceRef() { ComponentRegistry registry = new ComponentRegistry<>(); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 99a83531..ece1a43e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -28,6 +28,7 @@ import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventCollectionMode; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; @@ -261,9 +262,9 @@ private static void attachView(@Nonnull CommandContext ctx, @Nullable private static UUID physicsStoreBodyUuid(@Nonnull Ref bodyRef) { - return PhysicsBodies.bodyUuid( - bodyRef.getStore(), - bodyRef); + UuidComponent uuid = bodyRef.getStore().getComponent(bodyRef, + UuidComponent.getComponentType()); + return uuid != null ? uuid.getUuid() : null; } } From 4b8e8e2078095894f8a8a52c1cded710d302f5d0 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 17:27:53 +0200 Subject: [PATCH 488/534] refactor(commands): register module command sets Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 6 +- .../core/internal/commands/CleanCommand.java | 168 ++++------------- .../internal/commands/ImpulseCommand.java | 8 +- ...y.java => ImpulseCommandTreeRegistry.java} | 66 +++---- .../commands/settings/SettingsCommand.java | 2 +- .../physicschunk/PhysicsChunkSubPlugin.java | 6 +- ...tions.java => PhysicsChunkCommandSet.java} | 12 +- .../PhysicsEntityProjectionCleanup.java | 178 ++++++++++++++++++ .../physicsentity/PhysicsEntitySubPlugin.java | 6 +- ...ions.java => PhysicsEntityCommandSet.java} | 12 +- ...va => ImpulseCommandTreeRegistryTest.java} | 6 +- ...t.java => PhysicsChunkCommandSetTest.java} | 28 +-- ....java => PhysicsEntityCommandSetTest.java} | 26 +-- .../PhysicsEntityProjectionCleanupTest.java | 139 ++++++++++++++ 14 files changed, 444 insertions(+), 219 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/{ImpulseCommandContributionRegistry.java => ImpulseCommandTreeRegistry.java} (65%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/{PhysicsChunkCommandContributions.java => PhysicsChunkCommandSet.java} (67%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/{PhysicsEntityCommandContributions.java => PhysicsEntityCommandSet.java} (54%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/{ImpulseCommandContributionRegistryTest.java => ImpulseCommandTreeRegistryTest.java} (78%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/{PhysicsChunkCommandContributionRegistryTest.java => PhysicsChunkCommandSetTest.java} (54%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/{PhysicsEntityCommandContributionRegistryTest.java => PhysicsEntityCommandSetTest.java} (52%) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 54258302..928c5a07 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; +import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandTreeRegistry; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; @@ -69,7 +69,7 @@ protected void start() { @Override protected void shutdown() { - ImpulseCommandContributionRegistry.unregister(); + ImpulseCommandTreeRegistry.unregister(); } /** @@ -157,7 +157,7 @@ private String getAvailableBackendIds() { private void registerCommands() { CommandRegistry commandRegistry = getCommandRegistry(); - ImpulseCommandContributionRegistry.register(commandRegistry); + ImpulseCommandTreeRegistry.register(commandRegistry); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index e07afa50..147f7ad3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; @@ -17,14 +16,13 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityProjectionCleanup; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityProjectionCleanup.Result; import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; @@ -33,7 +31,7 @@ import java.util.UUID; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; -import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3d; @@ -54,12 +52,6 @@ */ public class CleanCommand extends AbstractWorldCommand { - private static final int REMOVED_ATTACHMENT_ENTITIES = 0; - private static final int DETACHED_EXTERNAL_ATTACHMENTS = 1; - private static final int REMOVED_ORPHAN_VISUAL_ENTITIES = 2; - private static final int REMOVED_SESSIONS = 3; - private static final int REMOVED_ENTITY_COUNTERS = 4; - private final OptionalArg radiusArg = this.withOptionalArg( "radius", "Only clean Impulse bodies and visual entities within this radius of the player", @@ -84,48 +76,16 @@ protected void execute(@Nonnull CommandContext context, private static void cleanAll(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { - AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); - boolean skippedProjectionCleanup = !PhysicsEntityAttachments.isAvailable(); - if (!skippedProjectionCleanup) { - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - ComponentType generatedProxyType = - GeneratedVisualProxyComponent.getComponentType(); - ComponentType controllableType = - controllableTypeOrNull(); - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, commandBuffer) -> { - BodyAttachmentComponent attachment = - archetypeChunk.getComponent(index, attachmentType); - if (attachment == null) { - return; - } - cleanAttachedEntity(removedEntities, - commandBuffer, - archetypeChunk.getReferenceTo(index), - attachmentType, - controllableType, - attachment); - }); - - store.forEachEntityParallel(generatedProxyType, - (index, archetypeChunk, commandBuffer) -> { - if (archetypeChunk.getComponent(index, attachmentType) != null) { - return; - } - - removedEntities.incrementAndGet(REMOVED_ORPHAN_VISUAL_ENTITIES); - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), - RemoveReason.REMOVE); - }); - } + Result projectionCleanup = + PhysicsEntityProjectionCleanup.cleanAll(store, controllableTypeOrNull()); + AtomicInteger removedSessions = new AtomicInteger(); ComponentType controlSessionType = controlSessionTypeOrNull(); if (controlSessionType != null) { store.forEachEntityParallel(controlSessionType, (index, archetypeChunk, commandBuffer) -> { - removedEntities.incrementAndGet(REMOVED_SESSIONS); + removedSessions.incrementAndGet(); commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), controlSessionType); }); @@ -137,16 +97,16 @@ private static void cleanAll(@Nonnull CommandContext context, PhysicsTopologyMutations::clearBodiesKeepingSpaces); reset.whenComplete((result, failure) -> sendCleanAllResult(world, context, - removedEntities, - skippedProjectionCleanup, + projectionCleanup, + removedSessions.get(), result, failure)); } private static void sendCleanAllResult(@Nonnull World world, @Nonnull CommandContext context, - @Nonnull AtomicIntegerArray removedEntities, - boolean skippedProjectionCleanup, + @Nonnull Result projectionCleanup, + int removedSessions, @Nullable PhysicsRuntimeResetResult reset, @Nullable Throwable failure) { Runnable sender = () -> { @@ -162,8 +122,8 @@ private static void sendCleanAllResult(@Nonnull World world, return; } sendCleanAllSuccess(context, - removedEntities, - skippedProjectionCleanup, + projectionCleanup, + removedSessions, reset, world.getName()); }; @@ -175,21 +135,22 @@ private static void sendCleanAllResult(@Nonnull World world, } private static void sendCleanAllSuccess(@Nonnull CommandContext context, - @Nonnull AtomicIntegerArray removedEntities, - boolean skippedProjectionCleanup, + @Nonnull Result projectionCleanup, + int removedSessions, @Nonnull PhysicsRuntimeResetResult reset, @Nonnull String worldName) { - String prefix = skippedProjectionCleanup + String prefix = projectionCleanup.skipped() ? "Impulse PhysicsEntity integration is not available; skipped EntityStore attachment/proxy cleanup. " : ""; - context.sendMessage(Message.raw(prefix + "Removed " + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + context.sendMessage(Message.raw(prefix + "Removed " + + projectionCleanup.removedAttachmentEntities() + " Impulse-owned attachment entities, " - + removedEntities.get(DETACHED_EXTERNAL_ATTACHMENTS) + + projectionCleanup.detachedExternalAttachments() + " detached external attachments, " - + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) + + projectionCleanup.removedOrphanVisualEntities() + " orphan visual proxy entities, " + reset.removedBodies() + " runtime bodies, " + reset.removedJoints() + " joints, and " - + removedEntities.get(REMOVED_SESSIONS) + " control sessions in world " + worldName + + removedSessions + " control sessions in world " + worldName + ". Kept " + reset.keptSpaces() + " explicit physics spaces.")); } @@ -244,46 +205,13 @@ private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store @Nonnull SelectedBodies selectedBodies, @Nonnull Vector3d center, double radiusSquared) { - AtomicIntegerArray removedEntities = new AtomicIntegerArray(REMOVED_ENTITY_COUNTERS); - boolean skippedProjectionCleanup = !PhysicsEntityAttachments.isAvailable(); - if (!skippedProjectionCleanup) { - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - ComponentType generatedProxyType = - GeneratedVisualProxyComponent.getComponentType(); - ComponentType controllableType = - controllableTypeOrNull(); - - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, commandBuffer) -> { - BodyAttachmentComponent attachment = - archetypeChunk.getComponent(index, attachmentType); - assert attachment != null; - if (!selectedBodies.bodyUuids().contains(attachment.getBodyUuid())) { - return; - } - - cleanAttachedEntity(removedEntities, - commandBuffer, - archetypeChunk.getReferenceTo(index), - attachmentType, - controllableType, - attachment); - }); - - store.forEachEntityParallel(generatedProxyType, - (index, archetypeChunk, commandBuffer) -> { - if (archetypeChunk.getComponent(index, attachmentType) != null - || !entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { - return; - } - - removedEntities.incrementAndGet(REMOVED_ORPHAN_VISUAL_ENTITIES); - commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), - RemoveReason.REMOVE); - }); - } + Result projectionCleanup = PhysicsEntityProjectionCleanup.cleanSelected(store, + selectedBodies.bodyUuids(), + center, + radiusSquared, + controllableTypeOrNull()); + AtomicInteger removedSessions = new AtomicInteger(); ComponentType controlSessionType = controlSessionTypeOrNull(); if (controlSessionType != null) { @@ -302,7 +230,7 @@ private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store return; } - removedEntities.incrementAndGet(REMOVED_SESSIONS); + removedSessions.incrementAndGet(); PhysicsControlSessionCleanup.cleanup(store, session); commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), controlSessionType); @@ -315,8 +243,8 @@ private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store removedBodies++; } - return new RadiusCleanResult(removedEntities, - skippedProjectionCleanup, + return new RadiusCleanResult(projectionCleanup, + removedSessions.get(), removedBodies); } @@ -350,18 +278,18 @@ private static void sendCleanRadiusSuccess(@Nonnull CommandContext context, @Nonnull RadiusCleanResult result, float radius, @Nonnull String worldName) { - AtomicIntegerArray removedEntities = result.removedEntities(); - String prefix = result.skippedProjectionCleanup() + Result projectionCleanup = result.projectionCleanup(); + String prefix = projectionCleanup.skipped() ? "Impulse PhysicsEntity integration is not available; skipped EntityStore attachment/proxy cleanup. " : ""; context.sendMessage(Message.raw(prefix + "Removed " - + removedEntities.get(REMOVED_ATTACHMENT_ENTITIES) + + projectionCleanup.removedAttachmentEntities() + " Impulse-owned attachment entities, " - + removedEntities.get(DETACHED_EXTERNAL_ATTACHMENTS) + + projectionCleanup.detachedExternalAttachments() + " detached external attachments, " - + removedEntities.get(REMOVED_ORPHAN_VISUAL_ENTITIES) + " orphan visual proxy entities, " + + projectionCleanup.removedOrphanVisualEntities() + " orphan visual proxy entities, " + result.removedBodies() + " runtime bodies, and " - + removedEntities.get(REMOVED_SESSIONS) + + result.removedSessions() + " control sessions within radius " + radius + " in world " + worldName + ". Kept explicit physics spaces and PhysicsChunk collision cache.")); } @@ -438,26 +366,6 @@ private static ComponentType controll : null; } - private static void cleanAttachedEntity( - @Nonnull AtomicIntegerArray removedEntities, - @Nonnull CommandBuffer commandBuffer, - @Nonnull Ref entityRef, - @Nonnull ComponentType attachmentType, - @Nullable ComponentType controllableType, - @Nonnull BodyAttachmentComponent attachment) { - if (attachment.shouldRemoveEntityWhenBodyMissing()) { - removedEntities.incrementAndGet(REMOVED_ATTACHMENT_ENTITIES); - commandBuffer.removeEntity(entityRef, RemoveReason.REMOVE); - return; - } - removedEntities.incrementAndGet(DETACHED_EXTERNAL_ATTACHMENTS); - if (controllableType != null - && commandBuffer.getComponent(entityRef, controllableType) != null) { - commandBuffer.removeComponent(entityRef, controllableType); - } - commandBuffer.removeComponent(entityRef, attachmentType); - } - private static boolean containsBody(@Nonnull Set bodyUuids, @Nullable Ref bodyRef) { UUID bodyUuid = rowUuid(bodyRef); @@ -467,8 +375,8 @@ private static boolean containsBody(@Nonnull Set bodyUuids, private record SelectedBodies(@Nonnull Set bodyUuids) { } - private record RadiusCleanResult(@Nonnull AtomicIntegerArray removedEntities, - boolean skippedProjectionCleanup, + private record RadiusCleanResult(@Nonnull Result projectionCleanup, + int removedSessions, int removedBodies) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java index c82dd81f..dd82f355 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java @@ -16,21 +16,21 @@ public ImpulseCommand() { this(List.of()); } - ImpulseCommand(@Nonnull Collection settingsContributions) { + ImpulseCommand(@Nonnull Collection settingsCommands) { super("impulse", "Impulse runtime commands"); addSubCommand(new BackendCommand()); addSubCommand(new CleanCommand()); addSubCommand(new DebugCommand()); addSubCommand(new PerfCommand()); SettingsCommand settingsCommand = new SettingsCommand(); - for (AbstractCommand command : settingsContributions) { - settingsCommand.addContribution(command); + for (AbstractCommand command : settingsCommands) { + settingsCommand.registerSettingsCommand(command); } addSubCommand(settingsCommand); addSubCommand(new SpaceCommand()); } - void addRootContribution(@Nonnull AbstractCommand command) { + void registerRootCommand(@Nonnull AbstractCommand command) { addSubCommand(command); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistry.java similarity index 65% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistry.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistry.java index f57bf23b..752632c7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistry.java @@ -15,14 +15,14 @@ import javax.annotation.Nullable; /** - * Central contribution point for the sealed {@code /impulse} command tree. + * Central registry for the sealed {@code /impulse} command tree. */ -public final class ImpulseCommandContributionRegistry { +public final class ImpulseCommandTreeRegistry { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final Map> ROOT_CONTRIBUTIONS = + private static final Map> ROOT_COMMANDS = new LinkedHashMap<>(); - private static final Map> SETTINGS_CONTRIBUTIONS = + private static final Map> SETTINGS_COMMANDS = new LinkedHashMap<>(); @Nullable @@ -30,7 +30,7 @@ public final class ImpulseCommandContributionRegistry { @Nullable private static CommandRegistration commandRegistration; - private ImpulseCommandContributionRegistry() { + private ImpulseCommandTreeRegistry() { } public static synchronized void register(@Nonnull CommandRegistry registry) { @@ -46,52 +46,52 @@ public static synchronized void unregister() { commandRegistry = null; } - public static synchronized void addRootSubCommand(@Nonnull String id, + public static synchronized void registerRootSubCommand(@Nonnull String id, @Nonnull Supplier supplier) { - if (ROOT_CONTRIBUTIONS.containsKey(id)) { + if (ROOT_COMMANDS.containsKey(id)) { return; } - ROOT_CONTRIBUTIONS.put(id, Objects.requireNonNull(supplier, "supplier")); + ROOT_COMMANDS.put(id, Objects.requireNonNull(supplier, "supplier")); rebuildIfRegistered(); } - public static synchronized void addRootAndSettingsSubCommands( + public static synchronized void registerRootAndSettingsSubCommands( @Nonnull String rootId, @Nonnull Supplier rootSupplier, @Nonnull String settingsId, @Nonnull Supplier settingsSupplier) { - boolean changed = addContribution(ROOT_CONTRIBUTIONS, rootId, rootSupplier); - changed |= addContribution(SETTINGS_CONTRIBUTIONS, settingsId, settingsSupplier); + boolean changed = addCommand(ROOT_COMMANDS, rootId, rootSupplier); + changed |= addCommand(SETTINGS_COMMANDS, settingsId, settingsSupplier); if (changed) { rebuildIfRegistered(); } } - public static synchronized void removeRootSubCommand(@Nonnull String id) { - if (ROOT_CONTRIBUTIONS.remove(id) != null) { + public static synchronized void unregisterRootSubCommand(@Nonnull String id) { + if (ROOT_COMMANDS.remove(id) != null) { rebuildIfRegistered(); } } - public static synchronized void addSettingsSubCommand(@Nonnull String id, + public static synchronized void registerSettingsSubCommand(@Nonnull String id, @Nonnull Supplier supplier) { - if (SETTINGS_CONTRIBUTIONS.containsKey(id)) { + if (SETTINGS_COMMANDS.containsKey(id)) { return; } - SETTINGS_CONTRIBUTIONS.put(id, Objects.requireNonNull(supplier, "supplier")); + SETTINGS_COMMANDS.put(id, Objects.requireNonNull(supplier, "supplier")); rebuildIfRegistered(); } - public static synchronized void removeSettingsSubCommand(@Nonnull String id) { - if (SETTINGS_CONTRIBUTIONS.remove(id) != null) { + public static synchronized void unregisterSettingsSubCommand(@Nonnull String id) { + if (SETTINGS_COMMANDS.remove(id) != null) { rebuildIfRegistered(); } } - public static synchronized void removeRootAndSettingsSubCommands(@Nonnull String rootId, + public static synchronized void unregisterRootAndSettingsSubCommands(@Nonnull String rootId, @Nonnull String settingsId) { - boolean changed = ROOT_CONTRIBUTIONS.remove(rootId) != null; - changed |= SETTINGS_CONTRIBUTIONS.remove(settingsId) != null; + boolean changed = ROOT_COMMANDS.remove(rootId) != null; + changed |= SETTINGS_COMMANDS.remove(settingsId) != null; if (changed) { rebuildIfRegistered(); } @@ -108,8 +108,8 @@ static synchronized void resetForTests() { } commandRegistration = null; commandRegistry = null; - ROOT_CONTRIBUTIONS.clear(); - SETTINGS_CONTRIBUTIONS.clear(); + ROOT_COMMANDS.clear(); + SETTINGS_COMMANDS.clear(); } private static void rebuildIfRegistered() { @@ -118,14 +118,14 @@ private static void rebuildIfRegistered() { } } - private static boolean addContribution( - @Nonnull Map> contributions, + private static boolean addCommand( + @Nonnull Map> commands, @Nonnull String id, @Nonnull Supplier supplier) { - if (contributions.containsKey(id)) { + if (commands.containsKey(id)) { return false; } - contributions.put(id, Objects.requireNonNull(supplier, "supplier")); + commands.put(id, Objects.requireNonNull(supplier, "supplier")); return true; } @@ -148,13 +148,13 @@ private static void rebuildRegisteredRoot() { @Nonnull private static ImpulseCommand createRootCommand() { - List settingsContributions = new ArrayList<>(SETTINGS_CONTRIBUTIONS.size()); - for (Supplier supplier : SETTINGS_CONTRIBUTIONS.values()) { - settingsContributions.add(supplier.get()); + List settingsCommands = new ArrayList<>(SETTINGS_COMMANDS.size()); + for (Supplier supplier : SETTINGS_COMMANDS.values()) { + settingsCommands.add(supplier.get()); } - ImpulseCommand command = new ImpulseCommand(settingsContributions); - for (Supplier supplier : ROOT_CONTRIBUTIONS.values()) { - command.addRootContribution(supplier.get()); + ImpulseCommand command = new ImpulseCommand(settingsCommands); + for (Supplier supplier : ROOT_COMMANDS.values()) { + command.registerRootCommand(supplier.get()); } return command; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java index 86b065c7..4a7e93e1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SettingsCommand.java @@ -12,7 +12,7 @@ public SettingsCommand() { addSubCommand(new SolverSettingsCommand()); } - public void addContribution(@Nonnull AbstractCommand command) { + public void registerSettingsCommand(@Nonnull AbstractCommand command) { addSubCommand(command); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java index 010a84e4..5d9f5256 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandSet; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -33,7 +33,7 @@ protected void setup() { PhysicsChunkStoreTypes.registerSpaceBindingSystems(physicsRegistry); PhysicsChunkStoreTypes.registerPreBodyBindingSystems(physicsRegistry); PhysicsChunkStoreTypes.registerPostBodyBindingSystems(physicsRegistry); - PhysicsChunkCommandContributions.register(); + PhysicsChunkCommandSet.register(); PhysicsChunkLifecycle.enable(); LOGGER.at(Level.INFO).log("Impulse PhysicsChunk collision producer enabled."); } @@ -41,7 +41,7 @@ protected void setup() { @Override protected void shutdown() { PhysicsChunkLifecycle.disable(); - PhysicsChunkCommandContributions.unregister(); + PhysicsChunkCommandSet.unregister(); PhysicsChunkTypes.clearEntityStoreResourceTypes(); PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java similarity index 67% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java index 65fa86d7..05c5b391 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandContributions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java @@ -1,21 +1,21 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; -import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; +import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandTreeRegistry; /** - * Command contributions owned by the PhysicsChunk subplugin. + * Command set owned by the PhysicsChunk subplugin. */ -public final class PhysicsChunkCommandContributions { +public final class PhysicsChunkCommandSet { private static final String PHYSICS_CHUNK_ROOT_COMMAND_ID = "physicschunk.root"; private static final String COLLISION_LOD_SETTINGS_COMMAND_ID = "physicschunk.settings.collision-lod"; - private PhysicsChunkCommandContributions() { + private PhysicsChunkCommandSet() { } public static void register() { - ImpulseCommandContributionRegistry.addRootAndSettingsSubCommands( + ImpulseCommandTreeRegistry.registerRootAndSettingsSubCommands( PHYSICS_CHUNK_ROOT_COMMAND_ID, PhysicsChunkCommand::new, COLLISION_LOD_SETTINGS_COMMAND_ID, @@ -23,7 +23,7 @@ public static void register() { } public static void unregister() { - ImpulseCommandContributionRegistry.removeRootAndSettingsSubCommands( + ImpulseCommandTreeRegistry.unregisterRootAndSettingsSubCommands( PHYSICS_CHUNK_ROOT_COMMAND_ID, COLLISION_LOD_SETTINGS_COMMAND_ID); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java new file mode 100644 index 00000000..14fa3841 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java @@ -0,0 +1,178 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicsentity; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicIntegerArray; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3d; + +/** + * Module-owned cleanup policy for EntityStore physics projections. + */ +public final class PhysicsEntityProjectionCleanup { + + private static final int REMOVED_ATTACHMENT_ENTITIES = 0; + private static final int DETACHED_EXTERNAL_ATTACHMENTS = 1; + private static final int REMOVED_ORPHAN_VISUAL_ENTITIES = 2; + private static final int COUNTERS = 3; + + private PhysicsEntityProjectionCleanup() { + } + + @Nonnull + public static Result cleanAll(@Nonnull Store store, + @Nullable ComponentType> detachableMarkerType) { + if (!PhysicsEntityAttachments.isAvailable()) { + return Result.skippedResult(); + } + AtomicIntegerArray counters = new AtomicIntegerArray(COUNTERS); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); + ComponentType generatedProxyType = + GeneratedVisualProxyComponent.getComponentType(); + + store.forEachEntityParallel(attachmentType, + (index, archetypeChunk, commandBuffer) -> { + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + attachmentType); + if (attachment == null) { + return; + } + cleanAttachedEntity(counters, + commandBuffer, + archetypeChunk.getReferenceTo(index), + attachmentType, + detachableMarkerType, + attachment); + }); + + store.forEachEntityParallel(generatedProxyType, + (index, archetypeChunk, commandBuffer) -> { + if (archetypeChunk.getComponent(index, attachmentType) != null) { + return; + } + removeOrphanProxy(counters, commandBuffer, archetypeChunk.getReferenceTo(index)); + }); + + return Result.from(counters); + } + + @Nonnull + public static Result cleanSelected(@Nonnull Store store, + @Nonnull Set selectedBodyUuids, + @Nonnull Vector3d center, + double radiusSquared, + @Nullable ComponentType> detachableMarkerType) { + if (!PhysicsEntityAttachments.isAvailable()) { + return Result.skippedResult(); + } + Set checkedBodyUuids = Objects.requireNonNull(selectedBodyUuids, + "selectedBodyUuids"); + Vector3d checkedCenter = Objects.requireNonNull(center, "center"); + AtomicIntegerArray counters = new AtomicIntegerArray(COUNTERS); + ComponentType attachmentType = + BodyAttachmentComponent.getComponentType(); + ComponentType generatedProxyType = + GeneratedVisualProxyComponent.getComponentType(); + + store.forEachEntityParallel(attachmentType, + (index, archetypeChunk, commandBuffer) -> { + BodyAttachmentComponent attachment = archetypeChunk.getComponent(index, + attachmentType); + if (attachment == null || !checkedBodyUuids.contains(attachment.getBodyUuid())) { + return; + } + cleanAttachedEntity(counters, + commandBuffer, + archetypeChunk.getReferenceTo(index), + attachmentType, + detachableMarkerType, + attachment); + }); + + store.forEachEntityParallel(generatedProxyType, + (index, archetypeChunk, commandBuffer) -> { + if (archetypeChunk.getComponent(index, attachmentType) != null + || !entityWithinRadius(archetypeChunk, index, checkedCenter, radiusSquared)) { + return; + } + removeOrphanProxy(counters, commandBuffer, archetypeChunk.getReferenceTo(index)); + }); + + return Result.from(counters); + } + + private static void cleanAttachedEntity( + @Nonnull AtomicIntegerArray counters, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref entityRef, + @Nonnull ComponentType attachmentType, + @Nullable ComponentType> detachableMarkerType, + @Nonnull BodyAttachmentComponent attachment) { + if (attachment.shouldRemoveEntityWhenBodyMissing()) { + counters.incrementAndGet(REMOVED_ATTACHMENT_ENTITIES); + commandBuffer.removeEntity(entityRef, RemoveReason.REMOVE); + return; + } + counters.incrementAndGet(DETACHED_EXTERNAL_ATTACHMENTS); + removeMarker(commandBuffer, entityRef, detachableMarkerType); + commandBuffer.removeComponent(entityRef, attachmentType); + } + + private static void removeMarker(@Nonnull CommandBuffer commandBuffer, + @Nonnull Ref entityRef, + @Nullable ComponentType> markerType) { + if (markerType != null && commandBuffer.getComponent(entityRef, markerType) != null) { + commandBuffer.removeComponent(entityRef, markerType); + } + } + + private static void removeOrphanProxy(@Nonnull AtomicIntegerArray counters, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref entityRef) { + counters.incrementAndGet(REMOVED_ORPHAN_VISUAL_ENTITIES); + commandBuffer.removeEntity(entityRef, RemoveReason.REMOVE); + } + + private static boolean entityWithinRadius(@Nonnull ArchetypeChunk archetypeChunk, + int index, + @Nonnull Vector3d center, + double radiusSquared) { + TransformComponent transform = + archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + return transform != null && transform.getPosition().distanceSquared(center) <= radiusSquared; + } + + public record Result(boolean skipped, + int removedAttachmentEntities, + int detachedExternalAttachments, + int removedOrphanVisualEntities) { + + @Nonnull + private static Result skippedResult() { + return new Result(true, 0, 0, 0); + } + + @Nonnull + private static Result from(@Nonnull AtomicIntegerArray counters) { + return new Result(false, + counters.get(REMOVED_ATTACHMENT_ENTITIES), + counters.get(DETACHED_EXTERNAL_ATTACHMENTS), + counters.get(REMOVED_ORPHAN_VISUAL_ENTITIES)); + } + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java index b39dd2e6..49c19152 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands.PhysicsEntityCommandContributions; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands.PhysicsEntityCommandSet; import javax.annotation.Nonnull; /** @@ -24,14 +24,14 @@ protected void setup() { PhysicsEntityTypeRegistry.registerEventTypes(entityRegistry); PhysicsEntityTypeRegistry.registerSystemGroups(entityRegistry); PhysicsEntityTypeRegistry.registerSystems(entityRegistry); - PhysicsEntityCommandContributions.register(); + PhysicsEntityCommandSet.register(); PhysicsEntityLifecycle.enable(); } @Override protected void shutdown() { PhysicsEntityLifecycle.disable(); - PhysicsEntityCommandContributions.unregister(); + PhysicsEntityCommandSet.unregister(); PhysicsEntityTypeRegistry.clearEntityStoreTypes(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandSet.java similarity index 54% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandSet.java index a7e439d5..2cbaa34c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandContributions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/PhysicsEntityCommandSet.java @@ -1,24 +1,24 @@ package dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands; -import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandContributionRegistry; +import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandTreeRegistry; /** - * Command contributions owned by the PhysicsEntity subplugin. + * Command set owned by the PhysicsEntity subplugin. */ -public final class PhysicsEntityCommandContributions { +public final class PhysicsEntityCommandSet { private static final String VISUAL_SETTINGS_COMMAND_ID = "physicsentity.settings.visual"; - private PhysicsEntityCommandContributions() { + private PhysicsEntityCommandSet() { } public static void register() { - ImpulseCommandContributionRegistry.addSettingsSubCommand( + ImpulseCommandTreeRegistry.registerSettingsSubCommand( VISUAL_SETTINGS_COMMAND_ID, VisualSettingsCommand::new); } public static void unregister() { - ImpulseCommandContributionRegistry.removeSettingsSubCommand(VISUAL_SETTINGS_COMMAND_ID); + ImpulseCommandTreeRegistry.unregisterSettingsSubCommand(VISUAL_SETTINGS_COMMAND_ID); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java similarity index 78% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java index 8ee6e62a..66559e37 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandContributionRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java @@ -6,16 +6,16 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -class ImpulseCommandContributionRegistryTest { +class ImpulseCommandTreeRegistryTest { @AfterEach void resetRegistry() { - ImpulseCommandContributionRegistry.resetForTests(); + ImpulseCommandTreeRegistry.resetForTests(); } @Test void coreRootDoesNotOwnPhysicsChunkCommandsByDefault() { - ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); + ImpulseCommand root = ImpulseCommandTreeRegistry.createRootCommandForTests(); assertFalse(root.getSubCommands().containsKey("physicschunk")); assertFalse(settings(root).getSubCommands().containsKey("collision-lod")); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandSetTest.java similarity index 54% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandSetTest.java index b195b8ee..b0a9c637 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandContributionRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandSetTest.java @@ -4,22 +4,22 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.server.core.command.system.AbstractCommand; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandContributions; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandSet; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -class PhysicsChunkCommandContributionRegistryTest { +class PhysicsChunkCommandSetTest { @AfterEach void resetRegistry() { - ImpulseCommandContributionRegistry.resetForTests(); + ImpulseCommandTreeRegistry.resetForTests(); } @Test - void physicsChunkContributesCommandsUnderImpulseRoot() { - PhysicsChunkCommandContributions.register(); + void physicsChunkRegistersCommandSetUnderImpulseRoot() { + PhysicsChunkCommandSet.register(); - ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); + ImpulseCommand root = ImpulseCommandTreeRegistry.createRootCommandForTests(); AbstractCommand physicsChunk = root.getSubCommands().get("physicschunk"); assertTrue(root.getSubCommands().containsKey("physicschunk")); @@ -29,17 +29,17 @@ void physicsChunkContributesCommandsUnderImpulseRoot() { } @Test - void physicsChunkContributionsAreIdempotentAndRemovable() { - PhysicsChunkCommandContributions.register(); - PhysicsChunkCommandContributions.register(); + void physicsChunkCommandSetIsIdempotentAndRemovable() { + PhysicsChunkCommandSet.register(); + PhysicsChunkCommandSet.register(); - ImpulseCommand contributed = ImpulseCommandContributionRegistry.createRootCommandForTests(); - assertTrue(contributed.getSubCommands().containsKey("physicschunk")); - assertTrue(settings(contributed).getSubCommands().containsKey("collision-lod")); + ImpulseCommand registered = ImpulseCommandTreeRegistry.createRootCommandForTests(); + assertTrue(registered.getSubCommands().containsKey("physicschunk")); + assertTrue(settings(registered).getSubCommands().containsKey("collision-lod")); - PhysicsChunkCommandContributions.unregister(); + PhysicsChunkCommandSet.unregister(); - ImpulseCommand removed = ImpulseCommandContributionRegistry.createRootCommandForTests(); + ImpulseCommand removed = ImpulseCommandTreeRegistry.createRootCommandForTests(); assertFalse(removed.getSubCommands().containsKey("physicschunk")); assertFalse(settings(removed).getSubCommands().containsKey("collision-lod")); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandContributionRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandSetTest.java similarity index 52% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandContributionRegistryTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandSetTest.java index 46f06049..3d822abc 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandContributionRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsEntityCommandSetTest.java @@ -4,22 +4,22 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.server.core.command.system.AbstractCommand; -import dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands.PhysicsEntityCommandContributions; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.commands.PhysicsEntityCommandSet; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -class PhysicsEntityCommandContributionRegistryTest { +class PhysicsEntityCommandSetTest { @AfterEach void resetRegistry() { - ImpulseCommandContributionRegistry.resetForTests(); + ImpulseCommandTreeRegistry.resetForTests(); } @Test - void physicsEntityContributesVisualSettingsUnderImpulseSettings() { - PhysicsEntityCommandContributions.register(); + void physicsEntityRegistersVisualSettingsUnderImpulseSettings() { + PhysicsEntityCommandSet.register(); - ImpulseCommand root = ImpulseCommandContributionRegistry.createRootCommandForTests(); + ImpulseCommand root = ImpulseCommandTreeRegistry.createRootCommandForTests(); AbstractCommand visual = settings(root).getSubCommands().get("visual"); assertTrue(settings(root).getSubCommands().containsKey("visual")); @@ -28,16 +28,16 @@ void physicsEntityContributesVisualSettingsUnderImpulseSettings() { } @Test - void physicsEntityContributionsAreIdempotentAndRemovable() { - PhysicsEntityCommandContributions.register(); - PhysicsEntityCommandContributions.register(); + void physicsEntityCommandSetIsIdempotentAndRemovable() { + PhysicsEntityCommandSet.register(); + PhysicsEntityCommandSet.register(); - ImpulseCommand contributed = ImpulseCommandContributionRegistry.createRootCommandForTests(); - assertTrue(settings(contributed).getSubCommands().containsKey("visual")); + ImpulseCommand registered = ImpulseCommandTreeRegistry.createRootCommandForTests(); + assertTrue(settings(registered).getSubCommands().containsKey("visual")); - PhysicsEntityCommandContributions.unregister(); + PhysicsEntityCommandSet.unregister(); - ImpulseCommand removed = ImpulseCommandContributionRegistry.createRootCommandForTests(); + ImpulseCommand removed = ImpulseCommandTreeRegistry.createRootCommandForTests(); assertFalse(settings(removed).getSubCommands().containsKey("visual")); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java new file mode 100644 index 00000000..4ae6307a --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java @@ -0,0 +1,139 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicsentity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import java.util.ArrayList; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class PhysicsEntityProjectionCleanupTest { + + @AfterEach + void clearTypes() { + PhysicsEntityLifecycle.disable(); + PhysicsEntityTypeRegistry.clearEntityStoreTypes(); + } + + @Test + void cleanAllOwnsAttachmentAndProxyCleanupResult() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = store(registry, "physicsentity-clean-all"); + try { + UUID ownedBodyUuid = UUID.randomUUID(); + UUID externalBodyUuid = UUID.randomUUID(); + Ref ownedVisual = addAttachment(store, + ownedBodyUuid, + BodyAttachmentComponent.impulseOwnedVisual(ownedBodyUuid, + new Vector3f(), + new org.joml.Quaternionf(), + Float.NaN)); + Ref external = addAttachment(store, + externalBodyUuid, + BodyAttachmentComponent.externalEntity(externalBodyUuid)); + Ref orphanProxy = addOrphanProxy(store); + + PhysicsEntityProjectionCleanup.Result result = + PhysicsEntityProjectionCleanup.cleanAll(store, null); + + assertFalse(result.skipped()); + assertEquals(1, result.removedAttachmentEntities()); + assertEquals(1, result.detachedExternalAttachments()); + assertEquals(1, result.removedOrphanVisualEntities()); + assertFalse(ownedVisual.isValid()); + assertTrue(external.isValid()); + assertNull(store.getComponent(external, BodyAttachmentComponent.getComponentType())); + assertFalse(orphanProxy.isValid()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void cleanSelectedFiltersAttachmentsByBody() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = store(registry, "physicsentity-clean-selected"); + try { + UUID selectedBodyUuid = UUID.randomUUID(); + UUID ignoredBodyUuid = UUID.randomUUID(); + Ref selected = addAttachment(store, + selectedBodyUuid, + BodyAttachmentComponent.externalEntity(selectedBodyUuid)); + Ref ignored = addAttachment(store, + ignoredBodyUuid, + BodyAttachmentComponent.externalEntity(ignoredBodyUuid)); + PhysicsEntityProjectionCleanup.Result result = + PhysicsEntityProjectionCleanup.cleanSelected(store, + Set.of(selectedBodyUuid), + new org.joml.Vector3d(), + 1.0, + null); + + assertEquals(1, result.detachedExternalAttachments()); + assertEquals(0, result.removedOrphanVisualEntities()); + assertTrue(selected.isValid()); + assertNull(store.getComponent(selected, BodyAttachmentComponent.getComponentType())); + assertNotNull(store.getComponent(ignored, BodyAttachmentComponent.getComponentType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static Store store(@Nonnull ComponentRegistry registry, + @Nonnull String worldName) { + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsEntityTypeRegistry.registerComponentTypes(proxy); + PhysicsEntityTypeRegistry.registerResourceTypes(proxy); + PhysicsEntityTypeRegistry.registerEventTypes(proxy); + PhysicsEntityTypeRegistry.registerSystemGroups(proxy); + PhysicsEntityLifecycle.enable(); + return registry.addStore(new EntityStore(TestInstanceFactory.world(worldName)), + EmptyResourceStorage.get()); + } + + @Nonnull + private static Ref addAttachment(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull BodyAttachmentComponent attachment) { + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(BodyAttachmentComponent.getComponentType(), attachment); + Ref ref = store.addEntity(holder, AddReason.SPAWN); + store.getResource(PhysicsProjectionIndexResource.getResourceType()) + .registerAttachment(bodyUuid, ref); + assertNotNull(ref); + return ref; + } + + @Nonnull + private static Ref addOrphanProxy(@Nonnull Store store) { + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(GeneratedVisualProxyComponent.getComponentType(), + new GeneratedVisualProxyComponent()); + Ref ref = store.addEntity(holder, AddReason.SPAWN); + assertNotNull(ref); + return ref; + } +} From dac751a7ea415afe3aeb83d9a5923ad1b4febbdf Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 17:28:25 +0200 Subject: [PATCH 489/534] fix(core): enforce physics runtime identity boundaries Signed-off-by: Blovien --- .../physics/PhysicsSpaceMutations.java | 3 +- .../physics/PhysicsStoreRowCleanup.java | 87 +- .../resources/PhysicsDebugResource.java | 5 +- .../PhysicsIdentityIndexResource.java | 58 +- .../resources/PhysicsRuntimeResource.java | 824 ++++++++++-------- .../resources/PhysicsSimulationRuntime.java | 26 - .../resources/PhysicsSpaceBinding.java | 23 - .../resources/PhysicsSpaceRuntime.java | 210 ----- .../resources/PhysicsWorldEventState.java | 125 --- .../resources/PhysicsWorldLifecycleState.java | 174 ---- .../resources/PhysicsWorldSnapshotState.java | 216 ----- .../body/PhysicsBodyRegistration.java | 21 - .../resources/body/PhysicsBodyRegistry.java | 273 ------ .../resources/body/PhysicsBodyRuntime.java | 153 ---- .../body/PhysicsBodySnapshotRefVisitor.java | 21 - .../body/PhysicsBodySnapshotStore.java | 225 ----- .../body/PhysicsBodySnapshotVisitor.java | 17 - .../resources/body/PhysicsBodySnapshots.java | 7 - .../body/PhysicsBodySpatialIndex.java | 261 ------ .../joint/PhysicsJointRegistration.java | 45 - .../resources/joint/PhysicsJointRegistry.java | 181 ---- .../systems/BodyCommandApplicationSystem.java | 53 +- .../ChunkCollisionVoxelStitchingSystem.java | 9 +- .../systems/StaleBodyRemovalSystem.java | 15 +- .../systems/StepSubmissionSystem.java | 41 +- .../systems/binding/BodyBindingSystem.java | 19 +- .../systems/binding/JointBindingSystem.java | 22 +- .../systems/binding/SpaceBindingSystem.java | 27 +- .../systems/binding/TargetBindingSystem.java | 8 +- .../plugin/physics/PhysicsBackendAccess.java | 5 +- .../core/plugin/physics/PhysicsRaycasts.java | 11 +- .../physics/PhysicsStoreRowCleanupTest.java | 144 ++- .../PhysicsStoreTopologyMutationsTest.java | 31 +- .../PhysicsStoreResourceIndexTest.java | 201 ++++- .../body/PhysicsBodyRegistryTest.java | 89 -- .../body/PhysicsBodySnapshotStoreTest.java | 201 ----- .../PhysicsWorldLifecycleStateTest.java | 147 ---- .../PhysicsWorldSnapshotStateTest.java | 32 - ...ChunkCollisionComponentSyncSystemTest.java | 36 +- ...ChunkCollisionMutationDrainSystemTest.java | 7 +- ...hunkCollisionVoxelStitchingSystemTest.java | 94 +- .../systems/JointBindingSystemTest.java | 245 ++++++ .../systems/StaleBodyRemovalSystemTest.java | 26 +- .../binding/SpaceBindingSystemTest.java | 146 ++++ .../debug/PhysicsStoreDebugQueriesTest.java | 7 +- 45 files changed, 1508 insertions(+), 3063 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSimulationRuntime.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceBinding.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/snapshot/PhysicsWorldSnapshotStateTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java index c9afc798..e5945ced 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java @@ -298,8 +298,7 @@ public static void removeEmptySpace(@Nonnull Store store, throw new IllegalStateException("PhysicsStore space is not empty: " + spaceUuid); } backendRuntime.destroySpace(handle.value()); - identity.removeSpaceHandle(handle); - runtime.removeSpaceHandle(spaceUuid); + runtime.removeSpaceHandle(ref); } compatibility.removeBySpaceUuid(spaceUuid); if (ref != null && ref.isValid()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index 1d82d6f8..997ad76a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -4,6 +4,7 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; @@ -12,6 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.ArrayList; @@ -35,18 +37,24 @@ public static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime @Nonnull PhysicsIdentityIndexResource identity, @Nonnull UUID jointUuid, @Nonnull Ref jointRef) { - BackendJointHandle jointHandle = runtime.getJointHandle(jointRef); - BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(jointRef); + Ref resolvedJointRef = cleanupRefForUuid(identity, jointUuid, jointRef); + BackendJointHandle jointHandle = runtime.getJointHandle(resolvedJointRef); + BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(resolvedJointRef); if (jointHandle == null) { - runtime.removeJointHandle(jointUuid, jointRef); + if (refMatchesUuid(resolvedJointRef, jointUuid)) { + runtime.removeJointHandle(resolvedJointRef); + } + return false; + } + BackendId backendId = runtime.getJointBackendId(resolvedJointRef); + if (!jointBindingMatchesUuid(runtime, jointUuid, backendId, spaceHandle, jointHandle)) { return false; } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(jointRef); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(resolvedJointRef); if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); } - identity.removeJointHandle(jointHandle); - runtime.removeJointHandle(jointUuid, jointRef); + runtime.removeJointHandle(resolvedJointRef); return true; } @@ -62,24 +70,79 @@ public static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef, @Nullable PhysicsBackendRuntime fallbackRuntime) { - BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyRef); - BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyRef); + Ref resolvedBodyRef = cleanupRefForUuid(identity, bodyUuid, bodyRef); + BackendBodyHandle bodyHandle = runtime.getBodyHandle(resolvedBodyRef); + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(resolvedBodyRef); if (bodyHandle == null) { - runtime.removeBodyHandle(bodyUuid, bodyRef); + if (refMatchesUuid(resolvedBodyRef, bodyUuid)) { + runtime.removeBodyHandle(resolvedBodyRef); + } + return false; + } + BackendId backendId = runtime.getBodyBackendId(resolvedBodyRef); + if (!bodyBindingMatchesUuid(runtime, bodyUuid, backendId, spaceHandle, bodyHandle)) { return false; } - PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(bodyRef); + PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(resolvedBodyRef); if (backendRuntime == null) { backendRuntime = fallbackRuntime; } if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeBody(spaceHandle.value(), bodyHandle.value()); } - identity.removeBodyHandle(bodyHandle); - runtime.removeBodyHandle(bodyUuid, bodyRef); + runtime.removeBodyHandle(resolvedBodyRef); return true; } + @Nonnull + private static Ref cleanupRefForUuid(@Nonnull PhysicsIdentityIndexResource identity, + @Nonnull UUID rowUuid, + @Nonnull Ref suppliedRef) { + Ref indexedRef = identity.getByUuid(rowUuid); + if (refMatchesUuid(indexedRef, rowUuid)) { + return indexedRef; + } + if (refMatchesUuid(suppliedRef, rowUuid)) { + return suppliedRef; + } + return suppliedRef; + } + + private static boolean bodyBindingMatchesUuid(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID bodyUuid, + @Nullable BackendId backendId, + @Nullable BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle) { + if (backendId == null || spaceHandle == null) { + return false; + } + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(backendId, + spaceHandle, + bodyHandle.value()); + return metadata != null && bodyUuid.equals(metadata.bodyUuid()); + } + + private static boolean jointBindingMatchesUuid(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull UUID jointUuid, + @Nullable BackendId backendId, + @Nullable BackendSpaceHandle spaceHandle, + @Nonnull BackendJointHandle jointHandle) { + if (backendId == null || spaceHandle == null) { + return false; + } + UUID boundJointUuid = runtime.getJointUuid(backendId, spaceHandle, jointHandle.value()); + return jointUuid.equals(boundJointUuid); + } + + private static boolean refMatchesUuid(@Nullable Ref ref, + @Nonnull UUID rowUuid) { + if (ref == null || !ref.isValid() || ref.getStore() == null) { + return false; + } + UuidComponent uuid = ref.getStore().getComponent(ref, UuidComponent.getComponentType()); + return uuid != null && rowUuid.equals(uuid.getUuid()); + } + public static void clearBodyCopiedState(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index 74f7823d..99c98f9b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -21,6 +21,7 @@ @Getter public class PhysicsDebugResource implements Resource { + @Getter @Nullable private static ResourceType resourceType; @@ -166,10 +167,6 @@ public PhysicsDebugResource clone() { return copy; } - public static ResourceType getResourceType() { - return resourceType; - } - public static void setResourceType( @Nonnull ResourceType type) { resourceType = type; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java index 135774ec..ce2926b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java @@ -4,8 +4,6 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.UUID; @@ -13,21 +11,12 @@ import javax.annotation.Nullable; /** - * Runtime identity indexes for UUID boundaries and backend handle hot paths. + * Runtime identity index for durable UUID boundaries. */ public final class PhysicsIdentityIndexResource implements Resource { @Nonnull private final Map> refsByUuid = new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectOpenHashMap> spaceRefsByHandle = - new Int2ObjectOpenHashMap<>(); - @Nonnull - private final Long2ObjectOpenHashMap> bodyRefsByHandle = - new Long2ObjectOpenHashMap<>(); - @Nonnull - private final Long2ObjectOpenHashMap> jointRefsByHandle = - new Long2ObjectOpenHashMap<>(); public PhysicsIdentityIndexResource() { } @@ -49,50 +38,8 @@ public void clearUuidRefs() { refsByUuid.clear(); } - public void putSpaceHandle(@Nonnull BackendSpaceHandle handle, @Nonnull Ref ref) { - spaceRefsByHandle.put(handle.value(), ref); - } - - @Nullable - public Ref getBySpaceHandle(@Nonnull BackendSpaceHandle handle) { - return spaceRefsByHandle.get(handle.value()); - } - - public void removeSpaceHandle(@Nonnull BackendSpaceHandle handle) { - spaceRefsByHandle.remove(handle.value()); - } - - public void putBodyHandle(@Nonnull BackendBodyHandle handle, @Nonnull Ref ref) { - bodyRefsByHandle.put(handle.value(), ref); - } - - @Nullable - public Ref getByBodyHandle(@Nonnull BackendBodyHandle handle) { - return bodyRefsByHandle.get(handle.value()); - } - - public void removeBodyHandle(@Nonnull BackendBodyHandle handle) { - bodyRefsByHandle.remove(handle.value()); - } - - public void putJointHandle(@Nonnull BackendJointHandle handle, @Nonnull Ref ref) { - jointRefsByHandle.put(handle.value(), ref); - } - - @Nullable - public Ref getByJointHandle(@Nonnull BackendJointHandle handle) { - return jointRefsByHandle.get(handle.value()); - } - - public void removeJointHandle(@Nonnull BackendJointHandle handle) { - jointRefsByHandle.remove(handle.value()); - } - public void clear() { refsByUuid.clear(); - spaceRefsByHandle.clear(); - bodyRefsByHandle.clear(); - jointRefsByHandle.clear(); } @Nonnull @@ -100,9 +47,6 @@ public void clear() { public PhysicsIdentityIndexResource clone() { PhysicsIdentityIndexResource copy = new PhysicsIdentityIndexResource(); copy.refsByUuid.putAll(refsByUuid); - copy.spaceRefsByHandle.putAll(spaceRefsByHandle); - copy.bodyRefsByHandle.putAll(bodyRefsByHandle); - copy.jointRefsByHandle.putAll(jointRefsByHandle); return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index 56b60774..28653b23 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -8,15 +8,11 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import lombok.Getter; -import lombok.Setter; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -26,6 +22,8 @@ import java.util.function.LongConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import lombok.Getter; +import lombok.Setter; /** * Runtime-only backend bindings for PhysicsStore spaces, bodies, and joints. @@ -36,16 +34,7 @@ public final class PhysicsRuntimeResource implements Resource { private final Map runtimesByBackend = new Object2ObjectOpenHashMap<>(); @Nonnull - private final Map spaceHandlesByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map backendIdsBySpaceUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map> spaceRefsByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectOpenHashMap spaceUuidsByRowIndex = + private final Int2ObjectOpenHashMap> spaceRefsByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull private final Int2ObjectOpenHashMap spaceHandlesByRowIndex = @@ -54,16 +43,7 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap backendIdsBySpaceRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull - private final Int2ObjectOpenHashMap unambiguousBackendIdsBySpaceHandle = - new Int2ObjectOpenHashMap<>(); - @Nonnull - private final Int2IntOpenHashMap spaceHandleBindingCounts = - new Int2IntOpenHashMap(); - @Nonnull - private final Map bodyHandlesByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map bodySpaceHandlesByUuid = + private final Map spaceMetadataByKey = new Object2ObjectOpenHashMap<>(); @Nonnull private final Int2ObjectOpenHashMap> bodyRefsByRowIndex = @@ -78,18 +58,6 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap backendIdsByBodyRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull - private final Map jointHandlesByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map jointSpaceHandlesByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map> jointRefsByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull - private final Map jointBackendIdsByUuid = - new Object2ObjectOpenHashMap<>(); - @Nonnull private final Int2ObjectOpenHashMap jointHandlesByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -102,17 +70,20 @@ public final class PhysicsRuntimeResource implements Resource { private final Int2ObjectOpenHashMap backendIdsByJointRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull + private final Map jointMetadataByKey = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap chunkCollisionPayloadKeysByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull - private final Int2ObjectOpenHashMap bodyHandlesBySpaceHandle = - new Int2ObjectOpenHashMap<>(); + private final Map bodyHandlesBySpaceKey = + new Object2ObjectOpenHashMap<>(); @Nonnull - private final Long2ObjectOpenHashMap bodyHitMetadataByHandle = - new Long2ObjectOpenHashMap<>(); + private final Map bodyHitMetadataByKey = + new Object2ObjectOpenHashMap<>(); @Nonnull - private final Long2ObjectOpenHashMap bodySnapshotMetadataByHandle = - new Long2ObjectOpenHashMap<>(); + private final Map bodySnapshotMetadataByKey = + new Object2ObjectOpenHashMap<>(); @Nonnull private final List pendingBodyOperations = new ArrayList<>(); @Nonnull @@ -127,7 +98,8 @@ public PhysicsRuntimeResource() { } public void putRuntime(@Nonnull BackendId backendId, @Nonnull PhysicsBackendRuntime runtime) { - runtimesByBackend.put(backendId, runtime); + runtimesByBackend.put(Objects.requireNonNull(backendId, "backendId"), + Objects.requireNonNull(runtime, "runtime")); } @Nullable @@ -135,28 +107,31 @@ public PhysicsBackendRuntime getRuntime(@Nonnull BackendId backendId) { return runtimesByBackend.get(backendId); } - public void putSpaceBinding(@Nonnull UUID spaceUuid, - @Nonnull Ref spaceRef, + public void putSpaceHandle(@Nonnull Ref spaceRef, @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle handle) { Ref checkedSpaceRef = Objects.requireNonNull(spaceRef, "spaceRef"); - Ref previousRef = spaceRefsByUuid.remove(spaceUuid); - if (previousRef != null) { - int previousRowIndex = previousRef.getIndex(); - spaceUuidsByRowIndex.remove(previousRowIndex); - backendIdsBySpaceRowIndex.remove(previousRowIndex); - spaceHandlesByRowIndex.remove(previousRowIndex); - } - BackendSpaceHandle previousHandle = spaceHandlesByUuid.remove(spaceUuid); - removeSpaceHandleRuntimeIndex(previousHandle); - spaceHandlesByUuid.put(spaceUuid, handle); + BackendId checkedBackendId = Objects.requireNonNull(backendId, "backendId"); + BackendSpaceHandle checkedHandle = Objects.requireNonNull(handle, "handle"); int rowIndex = checkedSpaceRef.getIndex(); - backendIdsBySpaceUuid.put(spaceUuid, backendId); - spaceRefsByUuid.put(spaceUuid, checkedSpaceRef); - spaceUuidsByRowIndex.put(rowIndex, spaceUuid); - backendIdsBySpaceRowIndex.put(rowIndex, backendId); - spaceHandlesByRowIndex.put(rowIndex, handle); - addSpaceHandleRuntimeIndex(handle, backendId); + BackendId previousBackendId = backendIdsBySpaceRowIndex.get(rowIndex); + BackendSpaceHandle previousHandle = spaceHandlesByRowIndex.get(rowIndex); + if (previousBackendId != null && previousHandle != null) { + removeSpaceMetadata(new BackendSpaceKey(previousBackendId, previousHandle)); + } + spaceRefsByRowIndex.put(rowIndex, checkedSpaceRef); + backendIdsBySpaceRowIndex.put(rowIndex, checkedBackendId); + spaceHandlesByRowIndex.put(rowIndex, checkedHandle); + markRegistrationTopologyChanged(); + } + + public void putSpaceMetadata(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle handle, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef) { + BackendSpaceKey key = new BackendSpaceKey(backendId, handle); + removeSpaceMetadataForUuid(spaceUuid, key); + spaceMetadataByKey.put(key, new SpaceMetadata(spaceUuid, spaceRef)); } @Nullable @@ -166,7 +141,9 @@ public BackendSpaceHandle getSpaceHandle(@Nonnull Ref spaceRef) { @Nullable public UUID getSpaceUuid(@Nonnull Ref spaceRef) { - return spaceUuidsByRowIndex.get(spaceRef.getIndex()); + BackendSpaceKey key = spaceKey(spaceRef); + SpaceMetadata metadata = key != null ? spaceMetadataByKey.get(key) : null; + return metadata != null ? metadata.spaceUuid() : null; } @Nullable @@ -174,58 +151,69 @@ public BackendId getSpaceBackendId(@Nonnull Ref spaceRef) { return backendIdsBySpaceRowIndex.get(spaceRef.getIndex()); } - public void removeSpaceHandle(@Nonnull UUID spaceUuid) { - BackendSpaceHandle removed = spaceHandlesByUuid.remove(spaceUuid); - backendIdsBySpaceUuid.remove(spaceUuid); - Ref spaceRef = spaceRefsByUuid.remove(spaceUuid); - if (spaceRef != null) { - int rowIndex = spaceRef.getIndex(); - spaceUuidsByRowIndex.remove(rowIndex); - spaceHandlesByRowIndex.remove(rowIndex); - backendIdsBySpaceRowIndex.remove(rowIndex); - } - if (removed != null) { - removeSpaceHandleRuntimeIndex(removed); - LongList bodyHandles = bodyHandlesBySpaceHandle.remove(removed.value()); - if (bodyHandles != null) { - bodyHandles.forEach((long bodyHandle) -> { - bodyHitMetadataByHandle.remove(bodyHandle); - BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(bodyHandle); - if (metadata != null) { - int rowIndex = metadata.bodyRef().getIndex(); - bodyRefsByRowIndex.remove(rowIndex); - bodyHandlesByRowIndex.remove(rowIndex); - bodySpaceHandlesByRowIndex.remove(rowIndex); - backendIdsByBodyRowIndex.remove(rowIndex); - chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); - } - }); - } - markRegistrationTopologyChanged(); + public void removeSpaceHandle(@Nonnull Ref spaceRef) { + int rowIndex = Objects.requireNonNull(spaceRef, "spaceRef").getIndex(); + BackendId backendId = backendIdsBySpaceRowIndex.remove(rowIndex); + BackendSpaceHandle removed = spaceHandlesByRowIndex.remove(rowIndex); + spaceRefsByRowIndex.remove(rowIndex); + if (backendId == null || removed == null) { + return; } + removeSpaceMetadata(new BackendSpaceKey(backendId, removed)); + markRegistrationTopologyChanged(); } - public void putBodyHandle(@Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nonnull UUID spaceUuid, + private void removeSpaceMetadata(@Nonnull BackendSpaceKey key) { + spaceMetadataByKey.remove(key); + LongList bodyHandles = bodyHandlesBySpaceKey.remove(key); + if (bodyHandles != null) { + bodyHandles.forEach((long bodyHandle) -> removeBodyMetadata( + new BackendBodyKey(key.backendId(), key.spaceHandle(), bodyHandle))); + } + List removedJointKeys = new ArrayList<>(); + jointMetadataByKey.keySet().forEach(jointKey -> { + if (jointKey.backendId().equals(key.backendId()) + && jointKey.spaceHandle() == key.spaceHandle()) { + removedJointKeys.add(jointKey); + } + }); + removedJointKeys.forEach(this::removeJointMetadata); + } + + private void removeSpaceMetadataForUuid(@Nonnull UUID spaceUuid, + @Nonnull BackendSpaceKey replacementKey) { + List removedKeys = new ArrayList<>(); + spaceMetadataByKey.forEach((key, metadata) -> { + if (!key.equals(replacementKey) && metadata.spaceUuid().equals(spaceUuid)) { + removedKeys.add(key); + } + }); + removedKeys.forEach(this::removeSpaceMetadata); + } + + public void putBodyHandle(@Nonnull Ref bodyRef, + @Nonnull Ref spaceRef, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle handle) { - bodyHandlesByUuid.put(bodyUuid, handle); - bodySpaceHandlesByUuid.put(bodyUuid, spaceHandle); - int rowIndex = bodyRef.getIndex(); - BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); - bodyRefsByRowIndex.put(rowIndex, bodyRef); - bodyHandlesByRowIndex.put(rowIndex, handle); - bodySpaceHandlesByRowIndex.put(rowIndex, spaceHandle); + Ref checkedBodyRef = Objects.requireNonNull(bodyRef, "bodyRef"); + BackendId backendId = getSpaceBackendId(Objects.requireNonNull(spaceRef, "spaceRef")); + int rowIndex = checkedBodyRef.getIndex(); + BackendId previousBackendId = backendIdsByBodyRowIndex.remove(rowIndex); + BackendBodyHandle previousHandle = bodyHandlesByRowIndex.remove(rowIndex); + BackendSpaceHandle previousSpaceHandle = bodySpaceHandlesByRowIndex.remove(rowIndex); + bodyRefsByRowIndex.remove(rowIndex); + chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); + removeBodyHandleIndexes(previousBackendId, previousHandle, previousSpaceHandle); + bodyRefsByRowIndex.put(rowIndex, checkedBodyRef); + bodyHandlesByRowIndex.put(rowIndex, Objects.requireNonNull(handle, "handle")); + bodySpaceHandlesByRowIndex.put(rowIndex, Objects.requireNonNull(spaceHandle, "spaceHandle")); if (backendId != null) { backendIdsByBodyRowIndex.put(rowIndex, backendId); + bodyHandlesBySpaceKey.computeIfAbsent(new BackendSpaceKey(backendId, spaceHandle), + _ -> new LongArrayList()).add(handle.value()); } else { backendIdsByBodyRowIndex.remove(rowIndex); } - bodyHandlesBySpaceHandle.computeIfAbsent(spaceHandle.value(), _ -> new LongArrayList()) - .add(handle.value()); - bodySnapshotMetadataByHandle.put(handle.value(), - new BodySnapshotMetadata(bodyUuid, bodyRef, spaceUuid)); markRegistrationTopologyChanged(); } @@ -239,28 +227,34 @@ public BackendSpaceHandle getBodySpaceHandle(@Nonnull Ref bodyRef) return bodySpaceHandlesByRowIndex.get(bodyRef.getIndex()); } - public void removeBodyHandle(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { - removePendingBodyOperations(bodyRef); - BackendBodyHandle removed = bodyHandlesByUuid.remove(bodyUuid); - BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.remove(bodyUuid); - int rowIndex = bodyRef.getIndex(); + @Nullable + public BackendId getBodyBackendId(@Nonnull Ref bodyRef) { + return backendIdsByBodyRowIndex.get(bodyRef.getIndex()); + } + + public void removeBodyHandle(@Nonnull Ref bodyRef) { + Ref checkedBodyRef = Objects.requireNonNull(bodyRef, "bodyRef"); + removePendingBodyOperations(checkedBodyRef); + int rowIndex = checkedBodyRef.getIndex(); + BackendId backendId = backendIdsByBodyRowIndex.remove(rowIndex); + BackendBodyHandle removed = bodyHandlesByRowIndex.remove(rowIndex); + BackendSpaceHandle spaceHandle = bodySpaceHandlesByRowIndex.remove(rowIndex); bodyRefsByRowIndex.remove(rowIndex); - BackendBodyHandle removedByRef = bodyHandlesByRowIndex.remove(rowIndex); - BackendSpaceHandle spaceHandleByRef = bodySpaceHandlesByRowIndex.remove(rowIndex); - backendIdsByBodyRowIndex.remove(rowIndex); chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); - removeBodyHandleIndexes(removed != null ? removed : removedByRef, - spaceHandle != null ? spaceHandle : spaceHandleByRef); + removeBodyHandleIndexes(backendId, removed, spaceHandle); markRegistrationTopologyChanged(); } @Nonnull - public List> bodyRefsForSpaceHandle( + public List> bodyRefsForSpaceHandle(@Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle) { List> bodyRefs = new ArrayList<>(); - int targetSpaceHandle = spaceHandle.value(); + BackendSpaceKey target = new BackendSpaceKey(backendId, spaceHandle); bodySpaceHandlesByRowIndex.forEach((rowIndex, handle) -> { - if (handle.value() == targetSpaceHandle) { + BackendId rowBackendId = backendIdsByBodyRowIndex.get((int) rowIndex); + if (rowBackendId != null + && rowBackendId.equals(target.backendId()) + && handle.value() == target.spaceHandle()) { Ref bodyRef = bodyRefsByRowIndex.get((int) rowIndex); if (bodyRef != null) { bodyRefs.add(bodyRef); @@ -270,44 +264,64 @@ public List> bodyRefsForSpaceHandle( return bodyRefs; } - public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, + public void putBodySnapshotMetadata(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nonnull UUID spaceUuid) { + BackendBodyKey key = new BackendBodyKey(backendId, spaceHandle, handle); + removeBodyMetadataForUuid(bodyUuid, key, bodyRef); + bodySnapshotMetadataByKey.put(key, + new BodySnapshotMetadata(bodyUuid, bodyRef, spaceUuid)); + } + + public void putBodyHitMetadata(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle, @Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull ShapeType shapeType) { - BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.get(handle.value()); - putBodyHitMetadata(handle, + BackendBodyKey key = new BackendBodyKey(backendId, spaceHandle, handle); + BodySnapshotMetadata metadata = bodySnapshotMetadataByKey.get(key); + putBodyHitMetadata(backendId, + spaceHandle, + handle, metadata != null ? metadata.bodyUuid() : new UUID(0L, 0L), bodyRef, bodyType, shapeType); } - public void putBodyHitMetadata(@Nonnull BackendBodyHandle handle, + public void putBodyHitMetadata(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle, @Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @Nonnull ShapeType shapeType) { - bodyHitMetadataByHandle.put(handle.value(), + bodyHitMetadataByKey.put(new BackendBodyKey(backendId, spaceHandle, handle), new BodyHitMetadata(bodyUuid, bodyRef, bodyType, shapeType)); } @Nullable - public BodyHitMetadata getBodyHitMetadata(@Nonnull BackendBodyHandle handle) { - return getBodyHitMetadata(handle.value()); + public BodyHitMetadata getBodyHitMetadata(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + long bodyHandle) { + return bodyHitMetadataByKey.get(new BackendBodyKey(backendId, spaceHandle, bodyHandle)); } @Nullable - public BodyHitMetadata getBodyHitMetadata(long bodyHandle) { - return bodyHitMetadataByHandle.get(bodyHandle); - } - - public void removeBodyHitMetadata(@Nonnull BackendBodyHandle handle) { - bodyHitMetadataByHandle.remove(handle.value()); + public BodySnapshotMetadata getBodySnapshotMetadata(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + long bodyHandle) { + return bodySnapshotMetadataByKey.get(new BackendBodyKey(backendId, spaceHandle, bodyHandle)); } - @Nullable - public BodySnapshotMetadata getBodySnapshotMetadata(long bodyHandle) { - return bodySnapshotMetadataByHandle.get(bodyHandle); + public void removeBodyHitMetadata(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle handle) { + bodyHitMetadataByKey.remove(new BackendBodyKey(backendId, spaceHandle, handle)); } public void enqueuePendingBodyOperation(@Nonnull PendingBodyOperation operation) { @@ -352,37 +366,38 @@ public void removePendingBodyOperations(@Nonnull Ref bodyRef) { && operation.bodyRef().getIndex() == checkedBodyRef.getIndex()); } - private void putJointHandle(@Nonnull UUID jointUuid, - @Nonnull Ref jointRef, - @Nonnull BackendId backendId, + public void putJointHandle(@Nonnull Ref jointRef, + @Nonnull Ref spaceRef, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendJointHandle handle) { Ref checkedJointRef = Objects.requireNonNull(jointRef, "jointRef"); - Ref previousRef = jointRefsByUuid.remove(jointUuid); - if (previousRef != null) { - int previousRowIndex = previousRef.getIndex(); - jointHandlesByRowIndex.remove(previousRowIndex); - jointSpaceHandlesByRowIndex.remove(previousRowIndex); - jointRefsByRowIndex.remove(previousRowIndex); - backendIdsByJointRowIndex.remove(previousRowIndex); - } + BackendId backendId = getSpaceBackendId(Objects.requireNonNull(spaceRef, "spaceRef")); int rowIndex = checkedJointRef.getIndex(); - jointHandlesByUuid.put(jointUuid, handle); - jointSpaceHandlesByUuid.put(jointUuid, spaceHandle); - jointRefsByUuid.put(jointUuid, checkedJointRef); - jointBackendIdsByUuid.put(jointUuid, backendId); - jointHandlesByRowIndex.put(rowIndex, handle); - jointSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); + BackendJointHandle previousHandle = jointHandlesByRowIndex.remove(rowIndex); + BackendSpaceHandle previousSpaceHandle = jointSpaceHandlesByRowIndex.remove(rowIndex); + BackendId previousBackendId = backendIdsByJointRowIndex.remove(rowIndex); + if (previousBackendId != null && previousSpaceHandle != null && previousHandle != null) { + removeJointMetadata(new BackendJointKey(previousBackendId, + previousSpaceHandle, + previousHandle)); + } jointRefsByRowIndex.put(rowIndex, checkedJointRef); - backendIdsByJointRowIndex.put(rowIndex, backendId); + jointHandlesByRowIndex.put(rowIndex, Objects.requireNonNull(handle, "handle")); + jointSpaceHandlesByRowIndex.put(rowIndex, Objects.requireNonNull(spaceHandle, "spaceHandle")); + if (backendId != null) { + backendIdsByJointRowIndex.put(rowIndex, backendId); + } } - public void putJointHandle(@Nonnull Ref jointRef, - @Nonnull UUID jointUuid, - @Nonnull BackendId backendId, + public void putJointMetadata(@Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle, - @Nonnull BackendJointHandle handle) { - putJointHandle(jointUuid, jointRef, backendId, spaceHandle, handle); + @Nonnull BackendJointHandle handle, + @Nonnull UUID jointUuid, + @Nonnull Ref jointRef) { + BackendJointKey key = new BackendJointKey(backendId, spaceHandle, handle); + removeJointMetadataForUuid(jointUuid, key, jointRef); + jointMetadataByKey.put(key, + new JointMetadata(jointUuid, jointRef)); } @Nullable @@ -390,42 +405,47 @@ public BackendJointHandle getJointHandle(@Nonnull Ref jointRef) { return jointHandlesByRowIndex.get(jointRef.getIndex()); } + @Nullable + public UUID getJointUuid(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + long jointHandle) { + JointMetadata metadata = jointMetadataByKey.get(new BackendJointKey(backendId, + spaceHandle.value(), + jointHandle)); + return metadata != null ? metadata.jointUuid() : null; + } + @Nullable public BackendSpaceHandle getJointSpaceHandle(@Nonnull Ref jointRef) { return jointSpaceHandlesByRowIndex.get(jointRef.getIndex()); } - public void removeJointHandle(@Nonnull UUID jointUuid) { - jointHandlesByUuid.remove(jointUuid); - jointSpaceHandlesByUuid.remove(jointUuid); - jointBackendIdsByUuid.remove(jointUuid); - Ref jointRef = jointRefsByUuid.remove(jointUuid); - if (jointRef != null) { - int rowIndex = jointRef.getIndex(); - jointHandlesByRowIndex.remove(rowIndex); - jointSpaceHandlesByRowIndex.remove(rowIndex); - jointRefsByRowIndex.remove(rowIndex); - backendIdsByJointRowIndex.remove(rowIndex); - } + @Nullable + public BackendId getJointBackendId(@Nonnull Ref jointRef) { + return backendIdsByJointRowIndex.get(jointRef.getIndex()); } - public void removeJointHandle(@Nonnull UUID jointUuid, - @Nonnull Ref jointRef) { - removeJointHandle(jointUuid); - int rowIndex = jointRef.getIndex(); - jointHandlesByRowIndex.remove(rowIndex); - jointSpaceHandlesByRowIndex.remove(rowIndex); + public void removeJointHandle(@Nonnull Ref jointRef) { + int rowIndex = Objects.requireNonNull(jointRef, "jointRef").getIndex(); + BackendJointHandle removed = jointHandlesByRowIndex.remove(rowIndex); + BackendSpaceHandle spaceHandle = jointSpaceHandlesByRowIndex.remove(rowIndex); + BackendId backendId = backendIdsByJointRowIndex.remove(rowIndex); jointRefsByRowIndex.remove(rowIndex); - backendIdsByJointRowIndex.remove(rowIndex); + if (backendId != null && spaceHandle != null && removed != null) { + removeJointMetadata(new BackendJointKey(backendId, spaceHandle, removed)); + } } @Nonnull - public List> jointRefsForSpaceHandle( + public List> jointRefsForSpaceHandle(@Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle) { List> jointRefs = new ArrayList<>(); - int targetSpaceHandle = spaceHandle.value(); + BackendSpaceKey target = new BackendSpaceKey(backendId, spaceHandle); jointSpaceHandlesByRowIndex.forEach((rowIndex, handle) -> { - if (handle.value() == targetSpaceHandle) { + BackendId rowBackendId = backendIdsByJointRowIndex.get((int) rowIndex); + if (rowBackendId != null + && rowBackendId.equals(target.backendId()) + && handle.value() == target.spaceHandle()) { Ref jointRef = jointRefsByRowIndex.get((int) rowIndex); if (jointRef != null) { jointRefs.add(jointRef); @@ -450,10 +470,10 @@ public void clearChunkCollisionPayloadBound(@Nonnull Ref bodyRef) } public void forEachRuntimeSpaceBinding(@Nonnull RuntimeSpaceBindingConsumer consumer) { - spaceRefsByUuid.values().forEach(spaceRef -> { - int rowIndex = spaceRef.getIndex(); - BackendSpaceHandle spaceHandle = spaceHandlesByRowIndex.get(rowIndex); - BackendId backendId = backendIdsBySpaceRowIndex.get(rowIndex); + Objects.requireNonNull(consumer, "consumer"); + spaceRefsByRowIndex.forEach((rowIndex, spaceRef) -> { + BackendSpaceHandle spaceHandle = spaceHandlesByRowIndex.get((int) rowIndex); + BackendId backendId = backendIdsBySpaceRowIndex.get((int) rowIndex); PhysicsBackendRuntime runtime = backendId != null ? runtimesByBackend.get(backendId) : null; if (spaceHandle != null && backendId != null && runtime != null) { consumer.accept(spaceRef, backendId, spaceHandle, runtime); @@ -461,48 +481,41 @@ public void forEachRuntimeSpaceBinding(@Nonnull RuntimeSpaceBindingConsumer cons }); } - public void forEachBodyHandle(@Nonnull BackendSpaceHandle spaceHandle, + public void forEachBodyHandle(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, @Nonnull LongConsumer consumer) { - LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); + LongList bodyHandles = bodyHandlesBySpaceKey.get(new BackendSpaceKey(backendId, spaceHandle)); if (bodyHandles == null) { return; } bodyHandles.forEach(consumer); } - public int bodyHandleCount(@Nonnull BackendSpaceHandle spaceHandle) { - LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); + public int bodyHandleCount(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle) { + LongList bodyHandles = bodyHandlesBySpaceKey.get(new BackendSpaceKey(backendId, spaceHandle)); return bodyHandles != null ? bodyHandles.size() : 0; } public void clear() { runtimesByBackend.clear(); - spaceHandlesByUuid.clear(); - backendIdsBySpaceUuid.clear(); - spaceRefsByUuid.clear(); - spaceUuidsByRowIndex.clear(); + spaceRefsByRowIndex.clear(); spaceHandlesByRowIndex.clear(); backendIdsBySpaceRowIndex.clear(); - unambiguousBackendIdsBySpaceHandle.clear(); - spaceHandleBindingCounts.clear(); - bodyHandlesByUuid.clear(); - bodySpaceHandlesByUuid.clear(); + spaceMetadataByKey.clear(); bodyRefsByRowIndex.clear(); bodyHandlesByRowIndex.clear(); bodySpaceHandlesByRowIndex.clear(); backendIdsByBodyRowIndex.clear(); - jointHandlesByUuid.clear(); - jointSpaceHandlesByUuid.clear(); - jointRefsByUuid.clear(); - jointBackendIdsByUuid.clear(); jointHandlesByRowIndex.clear(); jointSpaceHandlesByRowIndex.clear(); jointRefsByRowIndex.clear(); backendIdsByJointRowIndex.clear(); + jointMetadataByKey.clear(); chunkCollisionPayloadKeysByRowIndex.clear(); - bodyHandlesBySpaceHandle.clear(); - bodyHitMetadataByHandle.clear(); - bodySnapshotMetadataByHandle.clear(); + bodyHandlesBySpaceKey.clear(); + bodyHitMetadataByKey.clear(); + bodySnapshotMetadataByKey.clear(); pendingBodyOperations.clear(); pendingSpaceSettingsByRowIndex.clear(); started = false; @@ -513,6 +526,14 @@ public void clearTransientBodyOperations() { pendingBodyOperations.clear(); } + public long registrationTopologyGeneration() { + return registrationTopologyGeneration; + } + + private void markRegistrationTopologyChanged() { + registrationTopologyGeneration++; + } + public void refreshRowRefs(@Nonnull PhysicsIdentityIndexResource identity) { PhysicsIdentityIndexResource checkedIdentity = Objects.requireNonNull(identity, "identity"); refreshSpaceRefs(checkedIdentity); @@ -521,26 +542,20 @@ public void refreshRowRefs(@Nonnull PhysicsIdentityIndexResource identity) { } private void refreshSpaceRefs(@Nonnull PhysicsIdentityIndexResource identity) { - spaceUuidsByRowIndex.clear(); + spaceRefsByRowIndex.clear(); spaceHandlesByRowIndex.clear(); backendIdsBySpaceRowIndex.clear(); - for (UUID spaceUuid : new ArrayList<>(spaceHandlesByUuid.keySet())) { - Ref spaceRef = identity.getByUuid(spaceUuid); + spaceMetadataByKey.replaceAll((key, metadata) -> { + Ref spaceRef = identity.getByUuid(metadata.spaceUuid()); if (spaceRef == null) { - continue; + return metadata; } - spaceRefsByUuid.put(spaceUuid, spaceRef); - BackendSpaceHandle spaceHandle = spaceHandlesByUuid.get(spaceUuid); - BackendId backendId = backendIdsBySpaceUuid.get(spaceUuid); int rowIndex = spaceRef.getIndex(); - spaceUuidsByRowIndex.put(rowIndex, spaceUuid); - if (spaceHandle != null) { - spaceHandlesByRowIndex.put(rowIndex, spaceHandle); - } - if (backendId != null) { - backendIdsBySpaceRowIndex.put(rowIndex, backendId); - } - } + spaceRefsByRowIndex.put(rowIndex, spaceRef); + spaceHandlesByRowIndex.put(rowIndex, new BackendSpaceHandle(key.spaceHandle())); + backendIdsBySpaceRowIndex.put(rowIndex, key.backendId()); + return new SpaceMetadata(metadata.spaceUuid(), spaceRef); + }); } private void refreshBodyRefs(@Nonnull PhysicsIdentityIndexResource identity) { @@ -548,27 +563,28 @@ private void refreshBodyRefs(@Nonnull PhysicsIdentityIndexResource identity) { bodyHandlesByRowIndex.clear(); bodySpaceHandlesByRowIndex.clear(); backendIdsByBodyRowIndex.clear(); - bodySnapshotMetadataByHandle.replaceAll((bodyHandle, metadata) -> { + bodySnapshotMetadataByKey.replaceAll((key, metadata) -> { Ref bodyRef = identity.getByUuid(metadata.bodyUuid()); if (bodyRef == null) { return metadata; } - BackendBodyHandle handle = bodyHandlesByUuid.get(metadata.bodyUuid()); - BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.get(metadata.bodyUuid()); int rowIndex = bodyRef.getIndex(); bodyRefsByRowIndex.put(rowIndex, bodyRef); - if (handle != null) { - bodyHandlesByRowIndex.put(rowIndex, handle); - } - if (spaceHandle != null) { - bodySpaceHandlesByRowIndex.put(rowIndex, spaceHandle); - } - BackendId backendId = backendIdsBySpaceUuid.get(metadata.spaceUuid()); - if (backendId != null) { - backendIdsByBodyRowIndex.put(rowIndex, backendId); - } + bodyHandlesByRowIndex.put(rowIndex, new BackendBodyHandle(key.bodyHandle())); + bodySpaceHandlesByRowIndex.put(rowIndex, new BackendSpaceHandle(key.spaceHandle())); + backendIdsByBodyRowIndex.put(rowIndex, key.backendId()); return new BodySnapshotMetadata(metadata.bodyUuid(), bodyRef, metadata.spaceUuid()); }); + bodyHitMetadataByKey.replaceAll((_, metadata) -> { + Ref bodyRef = identity.getByUuid(metadata.bodyUuid()); + if (bodyRef == null) { + return metadata; + } + return new BodyHitMetadata(metadata.bodyUuid(), + bodyRef, + metadata.bodyType(), + metadata.shapeType()); + }); } private void refreshJointRefs(@Nonnull PhysicsIdentityIndexResource identity) { @@ -576,94 +592,151 @@ private void refreshJointRefs(@Nonnull PhysicsIdentityIndexResource identity) { jointSpaceHandlesByRowIndex.clear(); jointRefsByRowIndex.clear(); backendIdsByJointRowIndex.clear(); - for (Map.Entry entry : jointHandlesByUuid.entrySet()) { - UUID jointUuid = entry.getKey(); - Ref jointRef = identity.getByUuid(jointUuid); + jointMetadataByKey.replaceAll((key, metadata) -> { + Ref jointRef = identity.getByUuid(metadata.jointUuid()); if (jointRef == null) { - continue; + return metadata; } - jointRefsByUuid.put(jointUuid, jointRef); - BackendSpaceHandle spaceHandle = jointSpaceHandlesByUuid.get(jointUuid); - BackendId backendId = jointBackendIdsByUuid.get(jointUuid); int rowIndex = jointRef.getIndex(); - jointHandlesByRowIndex.put(rowIndex, entry.getValue()); - if (spaceHandle != null) { - jointSpaceHandlesByRowIndex.put(rowIndex, spaceHandle); - } - if (backendId != null) { - backendIdsByJointRowIndex.put(rowIndex, backendId); - } jointRefsByRowIndex.put(rowIndex, jointRef); - } + jointHandlesByRowIndex.put(rowIndex, new BackendJointHandle(key.jointHandle())); + jointSpaceHandlesByRowIndex.put(rowIndex, new BackendSpaceHandle(key.spaceHandle())); + backendIdsByJointRowIndex.put(rowIndex, key.backendId()); + return new JointMetadata(metadata.jointUuid(), jointRef); + }); } - private void removeBodyHandleIndexes(@Nullable BackendBodyHandle removed, + private void removeBodyHandleIndexes(@Nullable BackendId backendId, + @Nullable BackendBodyHandle removed, @Nullable BackendSpaceHandle spaceHandle) { - if (removed == null || spaceHandle == null) { + if (backendId == null || removed == null || spaceHandle == null) { return; } - LongList bodyHandles = bodyHandlesBySpaceHandle.get(spaceHandle.value()); + BackendBodyKey bodyKey = new BackendBodyKey(backendId, spaceHandle, removed); + removeBodyHandleFromSpaceIndex(bodyKey); + removeBodyMetadata(bodyKey); + } + + private void removeBodyHandleFromSpaceIndex(@Nonnull BackendBodyKey key) { + BackendSpaceKey spaceKey = new BackendSpaceKey(key.backendId(), key.spaceHandle()); + LongList bodyHandles = bodyHandlesBySpaceKey.get(spaceKey); if (bodyHandles != null) { - bodyHandles.rem(removed.value()); + bodyHandles.rem(key.bodyHandle()); if (bodyHandles.isEmpty()) { - bodyHandlesBySpaceHandle.remove(spaceHandle.value()); + bodyHandlesBySpaceKey.remove(spaceKey); } } - bodyHitMetadataByHandle.remove(removed.value()); - BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.remove(removed.value()); - if (metadata != null) { - int rowIndex = metadata.bodyRef().getIndex(); - bodyRefsByRowIndex.remove(rowIndex); - bodyHandlesByRowIndex.remove(rowIndex); - bodySpaceHandlesByRowIndex.remove(rowIndex); - backendIdsByBodyRowIndex.remove(rowIndex); + } + + private void removeBodyMetadata(@Nonnull BackendBodyKey key) { + bodyHitMetadataByKey.remove(key); + BodySnapshotMetadata metadata = bodySnapshotMetadataByKey.remove(key); + if (metadata == null) { + return; + } + int rowIndex = metadata.bodyRef().getIndex(); + bodyRefsByRowIndex.remove(rowIndex); + bodyHandlesByRowIndex.remove(rowIndex); + bodySpaceHandlesByRowIndex.remove(rowIndex); + backendIdsByBodyRowIndex.remove(rowIndex); + chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); + } + + private void removeBodyMetadataForUuid(@Nonnull UUID bodyUuid, + @Nonnull BackendBodyKey replacementKey, + @Nonnull Ref replacementRef) { + List removedKeys = new ArrayList<>(); + bodySnapshotMetadataByKey.forEach((key, metadata) -> { + if (!key.equals(replacementKey) && metadata.bodyUuid().equals(bodyUuid)) { + removedKeys.add(key); + } + }); + for (BackendBodyKey removedKey : removedKeys) { + removeBodyHandleFromSpaceIndex(removedKey); + bodyHitMetadataByKey.remove(removedKey); + BodySnapshotMetadata metadata = bodySnapshotMetadataByKey.remove(removedKey); + if (metadata != null && !sameRow(metadata.bodyRef(), replacementRef)) { + int rowIndex = metadata.bodyRef().getIndex(); + bodyRefsByRowIndex.remove(rowIndex); + bodyHandlesByRowIndex.remove(rowIndex); + bodySpaceHandlesByRowIndex.remove(rowIndex); + backendIdsByBodyRowIndex.remove(rowIndex); + chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); + } } } + private void removeJointMetadata(@Nonnull BackendJointKey key) { + JointMetadata metadata = jointMetadataByKey.remove(key); + if (metadata == null) { + return; + } + int rowIndex = metadata.jointRef().getIndex(); + jointHandlesByRowIndex.remove(rowIndex); + jointSpaceHandlesByRowIndex.remove(rowIndex); + jointRefsByRowIndex.remove(rowIndex); + backendIdsByJointRowIndex.remove(rowIndex); + } + + private void removeJointMetadataForUuid(@Nonnull UUID jointUuid, + @Nonnull BackendJointKey replacementKey, + @Nonnull Ref replacementRef) { + List removedKeys = new ArrayList<>(); + jointMetadataByKey.forEach((key, metadata) -> { + if (!key.equals(replacementKey) && metadata.jointUuid().equals(jointUuid)) { + removedKeys.add(key); + } + }); + for (BackendJointKey removedKey : removedKeys) { + JointMetadata metadata = jointMetadataByKey.remove(removedKey); + if (metadata != null && !sameRow(metadata.jointRef(), replacementRef)) { + int rowIndex = metadata.jointRef().getIndex(); + jointHandlesByRowIndex.remove(rowIndex); + jointSpaceHandlesByRowIndex.remove(rowIndex); + jointRefsByRowIndex.remove(rowIndex); + backendIdsByJointRowIndex.remove(rowIndex); + } + } + } + + private static boolean sameRow(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getStore() == second.getStore() && first.getIndex() == second.getIndex(); + } + public void destroyBackendBindings() { RuntimeException failure = null; - for (Map.Entry entry - : new ArrayList<>(jointHandlesByUuid.entrySet())) { - BackendSpaceHandle spaceHandle = jointSpaceHandlesByUuid.get(entry.getKey()); - Ref jointRef = jointRefsByUuid.get(entry.getKey()); - PhysicsBackendRuntime runtime = jointRef != null - ? runtimeForJointRef(jointRef) - : runtimeForSpaceHandle(spaceHandle); - if (spaceHandle == null || runtime == null) { + for (Map.Entry entry + : new ArrayList<>(jointMetadataByKey.entrySet())) { + PhysicsBackendRuntime runtime = runtimesByBackend.get(entry.getKey().backendId()); + if (runtime == null) { continue; } try { - runtime.removeJoint(spaceHandle.value(), entry.getValue().value()); + runtime.removeJoint(entry.getKey().spaceHandle(), entry.getKey().jointHandle()); } catch (RuntimeException exception) { failure = appendShutdownFailure(failure, exception); } } - for (Map.Entry entry - : new ArrayList<>(bodyHandlesByUuid.entrySet())) { - BackendSpaceHandle spaceHandle = bodySpaceHandlesByUuid.get(entry.getKey()); - BodySnapshotMetadata metadata = bodySnapshotMetadataByHandle.get(entry.getValue() - .value()); - PhysicsBackendRuntime runtime = metadata != null - ? runtimeForBodyRef(metadata.bodyRef()) - : runtimeForSpaceHandle(spaceHandle); - if (spaceHandle == null || runtime == null) { + for (Map.Entry entry + : new ArrayList<>(bodySnapshotMetadataByKey.entrySet())) { + PhysicsBackendRuntime runtime = runtimesByBackend.get(entry.getKey().backendId()); + if (runtime == null) { continue; } try { - runtime.removeBody(spaceHandle.value(), entry.getValue().value()); + runtime.removeBody(entry.getKey().spaceHandle(), entry.getKey().bodyHandle()); } catch (RuntimeException exception) { failure = appendShutdownFailure(failure, exception); } } - for (Map.Entry entry - : new ArrayList<>(spaceHandlesByUuid.entrySet())) { - BackendId backendId = backendIdsBySpaceUuid.get(entry.getKey()); - PhysicsBackendRuntime runtime = backendId != null ? runtimesByBackend.get(backendId) : null; + for (BackendSpaceKey key : new ArrayList<>(spaceMetadataByKey.keySet())) { + PhysicsBackendRuntime runtime = runtimesByBackend.get(key.backendId()); if (runtime == null) { continue; } try { - runtime.destroySpace(entry.getValue().value()); + runtime.destroySpace(key.spaceHandle()); } catch (RuntimeException exception) { failure = appendShutdownFailure(failure, exception); } @@ -674,15 +747,6 @@ public void destroyBackendBindings() { } } - @Nullable - public PhysicsBackendRuntime runtimeForSpaceHandle(@Nullable BackendSpaceHandle target) { - if (target == null) { - return null; - } - BackendId backendId = unambiguousBackendIdsBySpaceHandle.get(target.value()); - return backendId != null ? runtimesByBackend.get(backendId) : null; - } - @Nullable public PhysicsBackendRuntime runtimeForSpaceRef(@Nonnull Ref spaceRef) { return runtimeForBackendId(backendIdsBySpaceRowIndex.get( @@ -706,60 +770,12 @@ private PhysicsBackendRuntime runtimeForBackendId(@Nullable BackendId backendId) return backendId != null ? runtimesByBackend.get(backendId) : null; } - private void addSpaceHandleRuntimeIndex(@Nonnull BackendSpaceHandle handle, - @Nonnull BackendId backendId) { - int handleValue = handle.value(); - int bindingCount = spaceHandleBindingCounts.get(handleValue) + 1; - spaceHandleBindingCounts.put(handleValue, bindingCount); - if (bindingCount == 1) { - unambiguousBackendIdsBySpaceHandle.put(handleValue, backendId); - } else { - unambiguousBackendIdsBySpaceHandle.remove(handleValue); - } - } - - private void removeSpaceHandleRuntimeIndex(@Nullable BackendSpaceHandle handle) { - if (handle == null) { - return; - } - int handleValue = handle.value(); - int bindingCount = spaceHandleBindingCounts.get(handleValue); - if (bindingCount <= 1) { - spaceHandleBindingCounts.remove(handleValue); - unambiguousBackendIdsBySpaceHandle.remove(handleValue); - return; - } - int remainingBindingCount = bindingCount - 1; - spaceHandleBindingCounts.put(handleValue, remainingBindingCount); - if (remainingBindingCount == 1) { - BackendId backendId = uniqueBackendIdForSpaceHandleValue(handleValue); - if (backendId != null) { - unambiguousBackendIdsBySpaceHandle.put(handleValue, backendId); - } else { - unambiguousBackendIdsBySpaceHandle.remove(handleValue); - } - } else { - unambiguousBackendIdsBySpaceHandle.remove(handleValue); - } - } - @Nullable - private BackendId uniqueBackendIdForSpaceHandleValue(int handleValue) { - BackendId uniqueBackendId = null; - for (Map.Entry entry : spaceHandlesByUuid.entrySet()) { - if (entry.getValue().value() != handleValue) { - continue; - } - BackendId backendId = backendIdsBySpaceUuid.get(entry.getKey()); - if (backendId == null) { - continue; - } - if (uniqueBackendId != null && !uniqueBackendId.equals(backendId)) { - return null; - } - uniqueBackendId = backendId; - } - return uniqueBackendId; + private BackendSpaceKey spaceKey(@Nonnull Ref spaceRef) { + int rowIndex = Objects.requireNonNull(spaceRef, "spaceRef").getIndex(); + BackendId backendId = backendIdsBySpaceRowIndex.get(rowIndex); + BackendSpaceHandle handle = spaceHandlesByRowIndex.get(rowIndex); + return backendId != null && handle != null ? new BackendSpaceKey(backendId, handle) : null; } @Nonnull @@ -777,33 +793,24 @@ private static RuntimeException appendShutdownFailure(@Nullable RuntimeException public PhysicsRuntimeResource clone() { PhysicsRuntimeResource copy = new PhysicsRuntimeResource(); copy.runtimesByBackend.putAll(runtimesByBackend); - copy.spaceHandlesByUuid.putAll(spaceHandlesByUuid); - copy.backendIdsBySpaceUuid.putAll(backendIdsBySpaceUuid); - copy.spaceRefsByUuid.putAll(spaceRefsByUuid); - copy.spaceUuidsByRowIndex.putAll(spaceUuidsByRowIndex); + copy.spaceRefsByRowIndex.putAll(spaceRefsByRowIndex); copy.spaceHandlesByRowIndex.putAll(spaceHandlesByRowIndex); copy.backendIdsBySpaceRowIndex.putAll(backendIdsBySpaceRowIndex); - copy.unambiguousBackendIdsBySpaceHandle.putAll(unambiguousBackendIdsBySpaceHandle); - copy.spaceHandleBindingCounts.putAll(spaceHandleBindingCounts); - copy.bodyHandlesByUuid.putAll(bodyHandlesByUuid); - copy.bodySpaceHandlesByUuid.putAll(bodySpaceHandlesByUuid); + copy.spaceMetadataByKey.putAll(spaceMetadataByKey); copy.bodyRefsByRowIndex.putAll(bodyRefsByRowIndex); copy.bodyHandlesByRowIndex.putAll(bodyHandlesByRowIndex); copy.bodySpaceHandlesByRowIndex.putAll(bodySpaceHandlesByRowIndex); copy.backendIdsByBodyRowIndex.putAll(backendIdsByBodyRowIndex); - copy.jointHandlesByUuid.putAll(jointHandlesByUuid); - copy.jointSpaceHandlesByUuid.putAll(jointSpaceHandlesByUuid); - copy.jointRefsByUuid.putAll(jointRefsByUuid); - copy.jointBackendIdsByUuid.putAll(jointBackendIdsByUuid); copy.jointHandlesByRowIndex.putAll(jointHandlesByRowIndex); copy.jointSpaceHandlesByRowIndex.putAll(jointSpaceHandlesByRowIndex); copy.jointRefsByRowIndex.putAll(jointRefsByRowIndex); copy.backendIdsByJointRowIndex.putAll(backendIdsByJointRowIndex); + copy.jointMetadataByKey.putAll(jointMetadataByKey); copy.chunkCollisionPayloadKeysByRowIndex.putAll(chunkCollisionPayloadKeysByRowIndex); - bodyHandlesBySpaceHandle.forEach((spaceHandle, bodyHandles) -> - copy.bodyHandlesBySpaceHandle.put((int) spaceHandle, new LongArrayList(bodyHandles))); - copy.bodyHitMetadataByHandle.putAll(bodyHitMetadataByHandle); - copy.bodySnapshotMetadataByHandle.putAll(bodySnapshotMetadataByHandle); + bodyHandlesBySpaceKey.forEach((key, bodyHandles) -> + copy.bodyHandlesBySpaceKey.put(key, new LongArrayList(bodyHandles))); + copy.bodyHitMetadataByKey.putAll(bodyHitMetadataByKey); + copy.bodySnapshotMetadataByKey.putAll(bodySnapshotMetadataByKey); copy.pendingBodyOperations.addAll(pendingBodyOperations); copy.pendingSpaceSettingsByRowIndex.putAll(pendingSpaceSettingsByRowIndex); copy.registrationTopologyGeneration = registrationTopologyGeneration; @@ -825,6 +832,72 @@ void accept(@Nonnull Ref spaceRef, @Nonnull PhysicsBackendRuntime runtime); } + private record BackendSpaceKey(@Nonnull BackendId backendId, int spaceHandle) { + + private BackendSpaceKey(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle) { + this(backendId, spaceHandle.value()); + } + + private BackendSpaceKey { + Objects.requireNonNull(backendId, "backendId"); + } + } + + private record BackendBodyKey(@Nonnull BackendId backendId, + int spaceHandle, + long bodyHandle) { + + private BackendBodyKey(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendBodyHandle bodyHandle) { + this(backendId, spaceHandle.value(), bodyHandle.value()); + } + + private BackendBodyKey(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + long bodyHandle) { + this(backendId, spaceHandle.value(), bodyHandle); + } + + private BackendBodyKey { + Objects.requireNonNull(backendId, "backendId"); + } + } + + private record BackendJointKey(@Nonnull BackendId backendId, + int spaceHandle, + long jointHandle) { + + private BackendJointKey(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, + @Nonnull BackendJointHandle jointHandle) { + this(backendId, spaceHandle.value(), jointHandle.value()); + } + + private BackendJointKey { + Objects.requireNonNull(backendId, "backendId"); + } + } + + private record SpaceMetadata(@Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef) { + + private SpaceMetadata { + Objects.requireNonNull(spaceUuid, "spaceUuid"); + Objects.requireNonNull(spaceRef, "spaceRef"); + } + } + + private record JointMetadata(@Nonnull UUID jointUuid, + @Nonnull Ref jointRef) { + + private JointMetadata { + Objects.requireNonNull(jointUuid, "jointUuid"); + Objects.requireNonNull(jointRef, "jointRef"); + } + } + public record BodyHitMetadata(@Nonnull UUID bodyUuid, @Nullable Ref bodyRef, @Nonnull PhysicsBodyType bodyType, @@ -851,8 +924,6 @@ public record BodySnapshotMetadata(@Nonnull UUID bodyUuid, public record PendingBodyOperation(@Nonnull Kind kind, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef, - @Nullable BackendSpaceHandle spaceHandle, - @Nullable BackendBodyHandle bodyHandle, float x, float y, float z, @@ -869,26 +940,20 @@ public record PendingBodyOperation(@Nonnull Kind kind, @Nonnull public static PendingBodyOperation wake(@Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nullable BackendSpaceHandle spaceHandle, - @Nullable BackendBodyHandle bodyHandle) { - return empty(Kind.WAKE, bodyUuid, bodyRef, spaceHandle, bodyHandle); + @Nonnull Ref bodyRef) { + return empty(Kind.WAKE, bodyUuid, bodyRef); } @Nonnull public static PendingBodyOperation sleep(@Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nullable BackendSpaceHandle spaceHandle, - @Nullable BackendBodyHandle bodyHandle) { - return empty(Kind.SLEEP, bodyUuid, bodyRef, spaceHandle, bodyHandle); + @Nonnull Ref bodyRef) { + return empty(Kind.SLEEP, bodyUuid, bodyRef); } @Nonnull public static PendingBodyOperation vector(@Nonnull Kind kind, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef, - @Nullable BackendSpaceHandle spaceHandle, - @Nullable BackendBodyHandle bodyHandle, float x, float y, float z, @@ -899,8 +964,6 @@ public static PendingBodyOperation vector(@Nonnull Kind kind, return new PendingBodyOperation(kind, bodyUuid, bodyRef, - spaceHandle, - bodyHandle, x, y, z, @@ -913,14 +976,10 @@ public static PendingBodyOperation vector(@Nonnull Kind kind, @Nonnull private static PendingBodyOperation empty(@Nonnull Kind kind, @Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nullable BackendSpaceHandle spaceHandle, - @Nullable BackendBodyHandle bodyHandle) { + @Nonnull Ref bodyRef) { return new PendingBodyOperation(kind, bodyUuid, bodyRef, - spaceHandle, - bodyHandle, 0.0f, 0.0f, 0.0f, @@ -939,13 +998,4 @@ public enum Kind { TORQUE } } - - public long getRegistrationTopologyGeneration() { - return registrationTopologyGeneration; - } - - private void markRegistrationTopologyChanged() { - registrationTopologyGeneration++; - } - } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSimulationRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSimulationRuntime.java deleted file mode 100644 index 9d2d6f20..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSimulationRuntime.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import javax.annotation.Nonnull; - -/** - * World-level simulation policy for one runtime physics resource. - */ -public final class PhysicsSimulationRuntime { - - @Nonnull - private final PhysicsWorldSettings worldSettings = new PhysicsWorldSettings(); - - @Nonnull - public PhysicsWorldSettings getWorldSettings() { - return new PhysicsWorldSettings(worldSettings); - } - - public void setWorldSettings(@Nonnull PhysicsWorldSettings settings) { - worldSettings.copyFrom(settings); - } - - public void copyFrom(@Nonnull PhysicsSimulationRuntime other) { - worldSettings.copyFrom(other.worldSettings); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceBinding.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceBinding.java deleted file mode 100644 index af8aedbb..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceBinding.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Runtime binding between a stable core space id and backend-local execution handles. - */ -public record PhysicsSpaceBinding(@Nonnull BackendId backendId, - @Nonnull SpaceId spaceId, - @Nonnull BackendSpaceHandle backendSpaceHandle, - @Nonnull PhysicsBackendRuntime runtime) { - - public PhysicsSpaceBinding { - Objects.requireNonNull(backendId, "backendId"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(backendSpaceHandle, "backendSpaceHandle"); - Objects.requireNonNull(runtime, "runtime"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java deleted file mode 100644 index df42c8b8..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSpaceRuntime.java +++ /dev/null @@ -1,210 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import com.hypixel.hytale.logger.HytaleLogger; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Id-only space topology for one direct physics world runtime. - */ -public final class PhysicsSpaceRuntime { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - - private final Int2ObjectMap spaces = new Int2ObjectOpenHashMap<>(); - - @Nonnull - public synchronized PhysicsSpaceBinding createSpace(@Nonnull BackendId backendId, - @Nonnull SpaceId spaceId, - @Nonnull String worldName, - @Nonnull PhysicsStepMode stepMode) { - if (spaces.containsKey(spaceId.value())) { - throw new IllegalArgumentException("Physics space id=" + spaceId + " is already registered"); - } - SpaceId.reserveAtLeast(spaceId.value()); - - LOGGER.at(Level.FINE).log( - "World %s creating physics space using backend %s", - worldName, - backendId); - - PhysicsBackendRuntime runtime = Impulse.createRuntime(backendId); - BackendSpaceHandle backendSpaceHandle = new BackendSpaceHandle(runtime.createSpace(spaceId)); - PhysicsSpaceBinding binding = - new PhysicsSpaceBinding(backendId, spaceId, backendSpaceHandle, runtime); - try { - validateSpaceCompatibleWithStepMode(binding, stepMode); - } catch (RuntimeException exception) { - closeBindingSilently(binding, worldName, "discarding failed physics space"); - throw exception; - } - spaces.put(spaceId.value(), binding); - - LOGGER.at(Level.FINE).log( - "World %s created physics space id=%s backend=%s", - worldName, - spaceId, - backendId); - return binding; - } - - @Nullable - public synchronized PhysicsSpaceBinding getBinding(@Nonnull SpaceId spaceId) { - return spaces.get(spaceId.value()); - } - - @Nonnull - public PhysicsSpaceBinding requireBinding(@Nonnull SpaceId spaceId) { - PhysicsSpaceBinding binding = getBinding(spaceId); - if (binding == null) { - throw new IllegalArgumentException("Physics space id=" + spaceId + " is not registered"); - } - return binding; - } - - public synchronized int getSpaceCount() { - return spaces.size(); - } - - @Nonnull - public synchronized Collection getBindings() { - return new ArrayList<>(spaces.values()); - } - - @Nonnull - public synchronized List getSpaceIds() { - List ids = new ArrayList<>(spaces.size()); - for (PhysicsSpaceBinding binding : spaces.values()) { - ids.add(binding.spaceId()); - } - return ids; - } - - @Nullable - public synchronized PhysicsSpaceBinding removeSpace(@Nonnull SpaceId spaceId) { - return spaces.remove(spaceId.value()); - } - - @Nonnull - public synchronized PhysicsRuntimeResetResult resetKeepingSpaces( - @Nonnull String worldName, - @Nonnull PhysicsStepMode stepMode) { - List previousBindings = new ArrayList<>(spaces.values()); - List replacements = new ArrayList<>(previousBindings.size()); - for (PhysicsSpaceBinding previous : previousBindings) { - PhysicsSpaceBinding replacement = null; - Vector3f gravity = new Vector3f(); - try { - previous.runtime().getGravity(previous.backendSpaceHandle().value(), gravity::set); - PhysicsBackendRuntime runtime = Impulse.createRuntime(previous.backendId()); - BackendSpaceHandle backendSpaceHandle = - new BackendSpaceHandle(runtime.createSpace(previous.spaceId())); - replacement = new PhysicsSpaceBinding(previous.backendId(), - previous.spaceId(), - backendSpaceHandle, - runtime); - validateSpaceCompatibleWithStepMode(replacement, stepMode); - replacement.runtime().setGravity(backendSpaceHandle.value(), gravity.x, gravity.y, gravity.z); - replacements.add(replacement); - } catch (RuntimeException exception) { - if (replacement != null) { - closeBindingSilently(replacement, worldName, "discarding failed clean replacement"); - } - closeBindingsSilently(replacements, worldName, "discarding clean replacements"); - throw exception; - } - } - - int removedBodies = 0; - int removedJoints = 0; - for (PhysicsSpaceBinding previous : previousBindings) { - removedBodies += previous.runtime().bodyCount(previous.backendSpaceHandle().value()); - removedJoints += previous.runtime().jointCount(previous.backendSpaceHandle().value()); - } - spaces.clear(); - for (PhysicsSpaceBinding replacement : replacements) { - spaces.put(replacement.spaceId().value(), replacement); - } - for (PhysicsSpaceBinding previous : previousBindings) { - closeBindingSilently(previous, worldName, "cleaned physics space"); - } - return new PhysicsRuntimeResetResult(removedBodies, removedJoints, replacements.size()); - } - - private static void closeBindingsSilently(@Nonnull Iterable bindings, - @Nonnull String worldName, - @Nonnull String reason) { - for (PhysicsSpaceBinding binding : bindings) { - closeBindingSilently(binding, worldName, reason); - } - } - - public synchronized void validateStepModeSupported(@Nonnull PhysicsStepMode stepMode) { - if (stepMode != PhysicsStepMode.CCD) { - return; - } - - List unsupportedSpaces = new ArrayList<>(); - for (PhysicsSpaceBinding binding : spaces.values()) { - if (!supportsContinuousCollision(binding)) { - unsupportedSpaces.add(formatSpace(binding)); - } - } - if (!unsupportedSpaces.isEmpty()) { - throw new IllegalArgumentException("CCD mode is not available for: " - + String.join(", ", unsupportedSpaces)); - } - } - - public synchronized void clearLiveTopology(@Nonnull String worldName) { - for (PhysicsSpaceBinding binding : new ArrayList<>(spaces.values())) { - closeBindingSilently(binding, worldName, "discarded copied physics space"); - } - spaces.clear(); - } - - private static void validateSpaceCompatibleWithStepMode(@Nonnull PhysicsSpaceBinding binding, - @Nonnull PhysicsStepMode stepMode) { - if (stepMode == PhysicsStepMode.CCD && !supportsContinuousCollision(binding)) { - throw new IllegalArgumentException("CCD mode is not available for " - + formatSpace(binding)); - } - } - - @Nonnull - private static String formatSpace(@Nonnull PhysicsSpaceBinding binding) { - return "space " + binding.spaceId().value() + " (" + binding.backendId().value() + ")"; - } - - private static boolean supportsContinuousCollision(@Nonnull PhysicsSpaceBinding binding) { - return binding.runtime().supportsContinuousCollision(binding.backendSpaceHandle().value()); - } - - static void closeBindingSilently(@Nonnull PhysicsSpaceBinding binding, - @Nonnull String worldName, - @Nonnull String action) { - try { - binding.runtime().destroySpace(binding.backendSpaceHandle().value()); - } catch (RuntimeException exception) { - LOGGER.at(Level.WARNING).log( - "World %s failed to close %s id=%s backend=%s: %s", - worldName, - action, - binding.spaceId(), - binding.backendId(), - exception.getMessage()); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java deleted file mode 100644 index 8f96438f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldEventState.java +++ /dev/null @@ -1,125 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsStepEvent; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nonnull; - -/** - * Latest value-only event frame for store-tick outcomes. - * - *

        This state intentionally replaces the previous frame instead of queueing history. The public - * event frame is a low-overhead diagnostic snapshot of the newest store-tick outcome and latest - * captured snapshot inclusion state.

        - */ -public final class PhysicsWorldEventState { - - private final AtomicLong eventFrameSequence = new AtomicLong(); - @Nonnull - private final AtomicReference latestFrame = - new AtomicReference<>(PhysicsEventFrame.empty(0L)); - - @Nonnull - public PhysicsEventFrame getLatestFrame() { - return latestFrame.get(); - } - - @Nonnull - public PhysicsEventFrame publishStepCaptured(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame snapshotFrame) { - return publishStepCaptured(worldEpoch, snapshotFrame, List.of(), 0); - } - - @Nonnull - public PhysicsEventFrame publishStepCaptured(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame snapshotFrame, - @Nonnull List physicsEvents, - int droppedBackendEventCount) { - Objects.requireNonNull(snapshotFrame, "snapshotFrame"); - PhysicsStepEvent stepEvent = new PhysicsStepEvent(snapshotFrame.stepSequence(), - snapshotFrame.serverTick(), - snapshotFrame.frameEpoch(), - snapshotFrame.status(), - snapshotFrame.bodyCount(), - snapshotFrame.stepNanos(), - snapshotFrame.snapshotNanos()); - return publishFrame(worldEpoch, - snapshotFrame, - List.of(stepEvent), - List.of(), - physicsEvents, - droppedBackendEventCount); - } - - @Nonnull - public PhysicsEventFrame publishSnapshotPublication(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame snapshotFrame, - int appliedBodyCount) { - return publishSnapshotPublication(worldEpoch, snapshotFrame, appliedBodyCount, 0L); - } - - @Nonnull - public PhysicsEventFrame publishSnapshotPublication(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame snapshotFrame, - int appliedBodyCount, - long publicationServerTick) { - Objects.requireNonNull(snapshotFrame, "snapshotFrame"); - PhysicsSnapshotPublicationEvent publicationEvent = - new PhysicsSnapshotPublicationEvent(snapshotFrame.frameEpoch(), - snapshotFrame.worldEpoch(), - snapshotFrame.stepSequence(), - snapshotFrame.serverTick(), - publicationServerTick, - System.nanoTime(), - appliedBodyCount); - return publishFrame(worldEpoch, snapshotFrame, List.of(), List.of(publicationEvent)); - } - - @Nonnull - public PhysicsEventFrame publishEmpty(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame) { - return publishFrame(worldEpoch, - Objects.requireNonNull(latestCapturedSnapshotFrame, "latestCapturedSnapshotFrame"), - List.of(), - List.of()); - } - - @Nonnull - private PhysicsEventFrame publishFrame(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame, - @Nonnull List stepEvents, - @Nonnull List publicationEvents) { - return publishFrame(worldEpoch, - latestCapturedSnapshotFrame, - stepEvents, - publicationEvents, - List.of(), - 0); - } - - @Nonnull - private PhysicsEventFrame publishFrame(long worldEpoch, - @Nonnull PublishedPhysicsSnapshotFrame latestCapturedSnapshotFrame, - @Nonnull List stepEvents, - @Nonnull List publicationEvents, - @Nonnull List physicsEvents, - int droppedBackendEventCount) { - PhysicsEventFrame frame = new PhysicsEventFrame(eventFrameSequence.incrementAndGet(), - worldEpoch, - latestCapturedSnapshotFrame.frameEpoch(), - latestCapturedSnapshotFrame.stepSequence(), - latestCapturedSnapshotFrame.serverTick(), - stepEvents, - publicationEvents, - physicsEvents, - droppedBackendEventCount); - latestFrame.set(frame); - return frame; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java deleted file mode 100644 index db227eba..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldLifecycleState.java +++ /dev/null @@ -1,174 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSnapshotState.ApplyResult; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.Collection; -import java.util.List; -import java.util.UUID; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Coordinates world lifecycle state that becomes visible across physics frame boundaries. - */ -public final class PhysicsWorldLifecycleState { - - private final PhysicsWorldSnapshotState snapshotState = new PhysicsWorldSnapshotState(); - private final PhysicsWorldEventState eventState = new PhysicsWorldEventState(); - - public long worldEpoch() { - return snapshotState.worldEpoch(); - } - - @Nonnull - public PhysicsEventFrame latestEventFrame() { - return eventState.getLatestFrame(); - } - - @Nullable - public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { - return snapshotState.getBodySnapshot(bodyUuid); - } - - @Nonnull - public PhysicsBodySnapshot captureBodySnapshot(@Nonnull PhysicsBodyRegistration registration) { - return snapshotState.captureBodySnapshot(registration); - } - - public void putBodySnapshot(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId) { - snapshotState.putBodySnapshot(bodyUuid, snapshot, spaceId); - } - - @Nonnull - public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( - @Nonnull Collection spaces, - @Nonnull PhysicsBodyRegistry bodyRegistry, - long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled) { - return capturePublishedSnapshotFrame(spaces, - bodyRegistry, - stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled, - List.of(), - 0); - } - - @Nonnull - public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( - @Nonnull Collection spaces, - @Nonnull PhysicsBodyRegistry bodyRegistry, - long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled, - @Nonnull List physicsEvents, - int droppedBackendEventCount) { - PublishedPhysicsSnapshotFrame frame = snapshotState.capturePublishedSnapshotFrame(spaces, - bodyRegistry, - stepSequence, - serverTick, - status, - stepNanos, - profilingEnabled); - eventState.publishStepCaptured(frame.worldEpoch(), - frame, - physicsEvents, - droppedBackendEventCount); - return frame; - } - - public int applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame frame, - @Nonnull PhysicsBodyRegistry bodyRegistry, - long publicationServerTick) { - ApplyResult result = snapshotState.applyPublishedSnapshotFrame(frame); - if (result.currentWorldEpoch()) { - bodyRegistry.applyPublishedRegistrationFrame(frame); - eventState.publishSnapshotPublication(snapshotState.worldEpoch(), - frame, - result.appliedCount(), - publicationServerTick); - } - return result.appliedCount(); - } - - @Nonnull - public PublishedPhysicsSnapshotFrame latestPublishedFrame() { - return snapshotState.getLatestPublishedFrame(); - } - - public long latestSnapshotAppliedNanos() { - return snapshotState.getLatestSnapshotAppliedNanos(); - } - - public int bodySnapshotCount() { - return snapshotState.getBodySnapshotCount(); - } - - public int bodySnapshotCount(@Nonnull SpaceId spaceId) { - return snapshotState.getBodySnapshotCount(spaceId); - } - - public int bodySnapshotCellCount() { - return snapshotState.getBodySnapshotCellCount(); - } - - public void forEachBodySnapshot(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - snapshotState.forEachBodySnapshot(spaceId, consumer); - } - - public void forEachIndexedBodySnapshot(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - snapshotState.forEachIndexedBodySnapshot(spaceId, visitor); - } - - public int forEachBodySnapshotNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull Consumer consumer) { - return snapshotState.forEachBodySnapshotNear(spaceId, center, radius, consumer); - } - - public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - return snapshotState.forEachIndexedBodySnapshotNear(spaceId, center, radius, visitor); - } - - public void removeBodySnapshot(@Nonnull UUID bodyUuid) { - snapshotState.removeBodySnapshot(bodyUuid); - } - - public void clearBodySnapshots() { - snapshotState.clearBodySnapshots(); - } - - public void markWorldChanged(@Nonnull PhysicsBodyRegistry bodyRegistry, - boolean storeTickAttached) { - snapshotState.markWorldChanged(); - if (!storeTickAttached) { - bodyRegistry.publishLiveRegistrations(); - } - eventState.publishEmpty(snapshotState.worldEpoch(), snapshotState.getLatestPublishedFrame()); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java deleted file mode 100644 index 5bd3660e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsWorldSnapshotState.java +++ /dev/null @@ -1,216 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotVisitor; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshotStore; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistration; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.Collection; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import lombok.Getter; -import org.joml.Vector3f; - -/** - * Snapshot and epoch state for a world physics resource. - * - *

        The capture-side store is used while capturing immutable frames on the store tick lane. The - * reader-side store is only updated when a frame is applied for the current world epoch. Keeping - * both stores and the epoch counters together prevents stale capture frames from repopulating - * world-thread snapshots after topology changes.

        - */ -public final class PhysicsWorldSnapshotState { - - private final PhysicsBodySnapshotStore bodySnapshots = new PhysicsBodySnapshotStore(); - private final PhysicsBodySnapshotStore captureBodySnapshots = new PhysicsBodySnapshotStore(); - private final AtomicLong worldEpoch = new AtomicLong(); - private final AtomicLong snapshotFrameEpoch = new AtomicLong(); - @Nonnull - private final AtomicReference latestPublishedFrame = - new AtomicReference<>(PublishedPhysicsSnapshotFrame.empty(0L, 0L)); - @Getter - private volatile long latestSnapshotAppliedNanos; - - @Nullable - public PhysicsBodySnapshot getBodySnapshot(@Nonnull UUID bodyUuid) { - return bodySnapshots.get(bodyUuid); - } - - @Nonnull - public PhysicsBodySnapshot captureBodySnapshot( - @Nonnull PhysicsBodyRegistration registration) { - PhysicsBodySnapshot snapshot = bodySnapshots.get(registration.bodyUuid()); - if (snapshot == null) { - throw new IllegalStateException("No physics body snapshot is available for " + registration.bodyUuid()); - } - return snapshot; - } - - public void putBodySnapshot(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId) { - bodySnapshots.put(bodyUuid, snapshot, spaceId); - captureBodySnapshots.put(bodyUuid, snapshot, spaceId); - } - - @Nonnull - public PublishedPhysicsSnapshotFrame capturePublishedSnapshotFrame( - @Nonnull Collection spaces, - @Nonnull PhysicsBodyRegistry bodyRegistry, - long stepSequence, - long serverTick, - @Nonnull PublishedPhysicsSnapshotFrame.Status status, - long stepNanos, - boolean profilingEnabled) { - Objects.requireNonNull(spaces, "spaces"); - Objects.requireNonNull(bodyRegistry, "bodyRegistry"); - Objects.requireNonNull(status, "status"); - - long frameEpoch = snapshotFrameEpoch.incrementAndGet(); - long frameWorldEpoch = worldEpoch.get(); - long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; - captureBodySnapshots.refresh(spaces, bodyRegistry); - int spatialIndexCellCount = captureBodySnapshots.cellCount(); - - int bodyCount = 0; - for (PhysicsSpaceBinding space : spaces) { - bodyCount += captureBodySnapshots.bodyCount(space.spaceId()); - } - - long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; - PublishedPhysicsSnapshotFrame.Builder frameBuilder = PublishedPhysicsSnapshotFrame.compactBuilder(frameEpoch, - frameWorldEpoch, - stepSequence, - serverTick, - status, - spatialIndexCellCount, - stepNanos, - snapshotNanos, - spaces.size(), - bodyCount); - for (PhysicsSpaceBinding space : spaces) { - SpaceId spaceId = space.spaceId(); - int spaceBodyCount = captureBodySnapshots.bodyCount(spaceId); - frameBuilder.addSpace(spaceId, frameWorldEpoch, spaceBodyCount); - captureBodySnapshots.forEachIndexed(spaceId, - (bodyUuid, snapshot, bodySpaceId) -> frameBuilder.addBody(bodyUuid, - bodySpaceId, - frameWorldEpoch, - frameWorldEpoch, - snapshot)); - } - PublishedPhysicsSnapshotFrame frame = frameBuilder.build(); - publishLatestFrameIfWorldCurrent(frame); - return frame; - } - - @Nonnull - public ApplyResult applyPublishedSnapshotFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { - Objects.requireNonNull(frame, "frame"); - if (frame.worldEpoch() != worldEpoch.get()) { - return new ApplyResult(0, false); - } - - PhysicsBodySnapshotStore.ApplyStats stats = - bodySnapshots.applyPublishedFrame(frame); - latestSnapshotAppliedNanos = System.nanoTime(); - return new ApplyResult(stats.applied(), true); - } - - public record ApplyResult(int appliedCount, boolean currentWorldEpoch) { - } - - @Nonnull - public PublishedPhysicsSnapshotFrame getLatestPublishedFrame() { - return latestPublishedFrame.get(); - } - - public long worldEpoch() { - return worldEpoch.get(); - } - - public int getBodySnapshotCount() { - return bodySnapshots.bodyCount(); - } - - public int getBodySnapshotCount(@Nonnull SpaceId spaceId) { - return bodySnapshots.bodyCount(spaceId); - } - - public int getBodySnapshotCellCount() { - return bodySnapshots.cellCount(); - } - - public void forEachBodySnapshot(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - bodySnapshots.forEach(spaceId, consumer); - } - - public void forEachIndexedBodySnapshot(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - bodySnapshots.forEachIndexed(spaceId, visitor); - } - - public int forEachBodySnapshotNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull Consumer consumer) { - return bodySnapshots.forEachNear(spaceId, center, radius, consumer); - } - - public int forEachIndexedBodySnapshotNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - return bodySnapshots.forEachIndexedNear(spaceId, center, radius, visitor); - } - - public void removeBodySnapshot(@Nonnull UUID bodyUuid) { - bodySnapshots.remove(bodyUuid); - captureBodySnapshots.remove(bodyUuid); - } - - public void clearBodySnapshots() { - bodySnapshots.clear(); - captureBodySnapshots.clear(); - } - - public void markWorldChanged() { - long newWorldEpoch = worldEpoch.incrementAndGet(); - publishLatestFrame(PublishedPhysicsSnapshotFrame.empty(snapshotFrameEpoch.get(), newWorldEpoch)); - } - - private void publishLatestFrameIfWorldCurrent(@Nonnull PublishedPhysicsSnapshotFrame frame) { - publishLatestFrame(frame, true); - } - - private void publishLatestFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { - publishLatestFrame(frame, false); - } - - private void publishLatestFrame(@Nonnull PublishedPhysicsSnapshotFrame frame, - boolean requireCurrentWorldEpoch) { - latestPublishedFrame.updateAndGet(current -> { - if (requireCurrentWorldEpoch && frame.worldEpoch() != worldEpoch.get()) { - return current; - } - return isNewerFrame(frame, current) ? frame : current; - }); - } - - private static boolean isNewerFrame(@Nonnull PublishedPhysicsSnapshotFrame candidate, - @Nonnull PublishedPhysicsSnapshotFrame current) { - if (candidate.worldEpoch() != current.worldEpoch()) { - return candidate.worldEpoch() > current.worldEpoch(); - } - return candidate.frameEpoch() >= current.frameEpoch(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java deleted file mode 100644 index 0652ee60..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistration.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Store tick registration for a stable body UUID and backend-local body handle. - */ -public record PhysicsBodyRegistration(@Nonnull UUID bodyUuid, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull SpaceId spaceId) { - - public PhysicsBodyRegistration { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - Objects.requireNonNull(backendBodyHandle, "backendBodyHandle"); - Objects.requireNonNull(spaceId, "spaceId"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java deleted file mode 100644 index e86009d0..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistry.java +++ /dev/null @@ -1,273 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsBodySnapshotCursor; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.UUID; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Runtime identity index for backend physics bodies. - */ -public final class PhysicsBodyRegistry { - - private final Map registrationsByUuid = - new Object2ObjectLinkedOpenHashMap<>(); - private final Map publishedRegistrationSpaceIdsByUuid = - new Object2ObjectLinkedOpenHashMap<>(); - private final Object2LongOpenHashMap publishedLivenessMarks = - new Object2LongOpenHashMap<>(); - private final Int2ObjectOpenHashMap> bodyUuidsByRawBackendId = - new Int2ObjectOpenHashMap<>(); - private final Int2ObjectOpenHashMap> registrationsBySpace = - new Int2ObjectOpenHashMap<>(); - private long publishedLivenessGeneration; - - @Nonnull - public PhysicsBodyRegistration registerBody(@Nonnull UUID bodyUuid, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull SpaceId spaceId) { - validateRegisterable(bodyUuid, backendBodyHandle, spaceId); - PhysicsBodyRegistration existingRegistration = registrationsByUuid.get(bodyUuid); - if (existingRegistration != null) { - removeFromSpace(existingRegistration); - removeBackendIndex(existingRegistration); - } - PhysicsBodyRegistration registration = - new PhysicsBodyRegistration(bodyUuid, backendBodyHandle, spaceId); - registrationsByUuid.put(bodyUuid, registration); - bodyUuidsByRawBackendId - .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) - .put(backendBodyHandle.value(), bodyUuid); - addToSpace(registration); - return registration; - } - - public void validateRegisterable(@Nonnull UUID bodyUuid, - @Nonnull BackendBodyHandle backendBodyHandle, - @Nonnull SpaceId spaceId) { - Long2ObjectOpenHashMap bodyUuids = - bodyUuidsByRawBackendId.get(spaceId.value()); - UUID existingUuid = bodyUuids != null ? bodyUuids.get(backendBodyHandle.value()) : null; - if (existingUuid != null && !existingUuid.equals(bodyUuid)) { - throw new IllegalArgumentException("Physics body is already registered as " + existingUuid); - } - PhysicsBodyRegistration existingRegistration = registrationsByUuid.get(bodyUuid); - if (existingRegistration != null - && (!existingRegistration.backendBodyHandle().equals(backendBodyHandle) - || !existingRegistration.spaceId().equals(spaceId))) { - throw new IllegalArgumentException("Physics body uuid=" + bodyUuid - + " is already registered to another backend body"); - } - } - - @Nullable - public PhysicsBodyRegistration unregisterBody(@Nonnull UUID bodyUuid) { - PhysicsBodyRegistration registration = registrationsByUuid.remove(bodyUuid); - if (registration == null) { - return null; - } - - removeBackendIndex(registration); - removeFromSpace(registration); - return registration; - } - - @Nullable - public PhysicsBodyRegistration unregisterBody(@Nonnull SpaceId spaceId, long backendBodyId) { - UUID bodyUuid = getBodyUuid(spaceId, backendBodyId); - return bodyUuid != null ? unregisterBody(bodyUuid) : null; - } - - @Nullable - public PhysicsBodyRegistration getRegistration(@Nonnull UUID bodyUuid) { - return registrationsByUuid.get(bodyUuid); - } - - @Nullable - public SpaceId getPublishedRegistrationSpaceId(@Nonnull UUID bodyUuid) { - return publishedRegistrationSpaceIdsByUuid.get(bodyUuid); - } - - public boolean hasPublishedRegistration(@Nonnull UUID bodyUuid) { - return publishedRegistrationSpaceIdsByUuid.containsKey(bodyUuid); - } - - @Nonnull - public Collection getPublishedBodyUuids() { - return new ArrayList<>(publishedRegistrationSpaceIdsByUuid.keySet()); - } - - @Nullable - public UUID getBodyUuid(@Nonnull SpaceId spaceId, long backendBodyId) { - Long2ObjectOpenHashMap bodyUuids = - bodyUuidsByRawBackendId.get(spaceId.value()); - return bodyUuids != null ? bodyUuids.get(backendBodyId) : null; - } - - @Nonnull - public Collection getRegistrations() { - return new ArrayList<>(registrationsByUuid.values()); - } - - public int getRegistrationCount() { - return registrationsByUuid.size(); - } - - public int getPublishedRegistrationCount() { - return publishedRegistrationSpaceIdsByUuid.size(); - } - - public void forEachRegistration(@Nonnull Consumer consumer) { - registrationsByUuid.values().forEach(consumer); - } - - public void forEachRegistration(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - ObjectArrayList registrations = - registrationsBySpace.get(spaceId.value()); - if (registrations == null) { - return; - } - registrations.forEach(consumer); - } - - @Nonnull - Iterator registrationIterator(@Nonnull SpaceId spaceId) { - ObjectArrayList registrations = - registrationsBySpace.get(spaceId.value()); - if (registrations == null) { - return Collections.emptyIterator(); - } - return new Iterator<>() { - - private int index; - - @Override - public boolean hasNext() { - return index < registrations.size(); - } - - @Override - public PhysicsBodyRegistration next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - return registrations.get(index++); - } - }; - } - - public int getRegistrationCount(@Nonnull SpaceId spaceId) { - ObjectArrayList registrations = - registrationsBySpace.get(spaceId.value()); - return registrations != null ? registrations.size() : 0; - } - - public void clear() { - registrationsByUuid.clear(); - publishedRegistrationSpaceIdsByUuid.clear(); - publishedLivenessMarks.clear(); - bodyUuidsByRawBackendId.clear(); - registrationsBySpace.clear(); - } - - public void publishLiveRegistrations() { - long generation = nextPublishedLivenessGeneration(); - for (PhysicsBodyRegistration registration : registrationsByUuid.values()) { - publishRegistration(registration.bodyUuid(), - registration.spaceId(), - generation); - } - retainPublishedRegistrations(generation); - } - - public void applyPublishedRegistrationFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { - long generation = nextPublishedLivenessGeneration(); - frame.forEachBodyCursor(body -> publishRegistration(body, generation)); - retainPublishedRegistrations(generation); - } - - private void addToSpace(@Nonnull PhysicsBodyRegistration registration) { - registrationsBySpace - .computeIfAbsent(registration.spaceId().value(), ignored -> new ObjectArrayList<>()) - .add(registration); - } - - private void removeFromSpace(@Nonnull PhysicsBodyRegistration registration) { - ObjectArrayList registrations = - registrationsBySpace.get(registration.spaceId().value()); - if (registrations == null) { - return; - } - registrations.remove(registration); - if (registrations.isEmpty()) { - registrationsBySpace.remove(registration.spaceId().value()); - } - } - - private void removeBackendIndex(@Nonnull PhysicsBodyRegistration registration) { - Long2ObjectOpenHashMap bodyUuids = - bodyUuidsByRawBackendId.get(registration.spaceId().value()); - if (bodyUuids == null) { - return; - } - bodyUuids.remove(registration.backendBodyHandle().value()); - if (bodyUuids.isEmpty()) { - bodyUuidsByRawBackendId.remove(registration.spaceId().value()); - } - } - - private void publishRegistration(@Nonnull PublishedPhysicsBodySnapshotCursor body, - long generation) { - publishRegistration(body.bodyUuid(), - body.spaceId(), - generation); - } - - private void publishRegistration(@Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId, - long generation) { - SpaceId existing = publishedRegistrationSpaceIdsByUuid.get(bodyUuid); - if (existing == null || !existing.equals(spaceId)) { - publishedRegistrationSpaceIdsByUuid.put(bodyUuid, spaceId); - } - publishedLivenessMarks.put(bodyUuid, generation); - } - - private long nextPublishedLivenessGeneration() { - publishedLivenessGeneration++; - if (publishedLivenessGeneration == 0L) { - publishedLivenessGeneration = 1L; - publishedLivenessMarks.clear(); - } - return publishedLivenessGeneration; - } - - private void retainPublishedRegistrations(long generation) { - Iterator iterator = publishedRegistrationSpaceIdsByUuid.keySet().iterator(); - while (iterator.hasNext()) { - UUID bodyUuid = iterator.next(); - if (publishedLivenessMarks.getLong(bodyUuid) != generation) { - iterator.remove(); - publishedLivenessMarks.removeLong(bodyUuid); - } - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java deleted file mode 100644 index d0644f1e..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntime.java +++ /dev/null @@ -1,153 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeState; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldLifecycleState; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistry; -import dev.hytalemodding.impulse.core.internal.resources.joint.PhysicsJointRegistration; -import java.util.ArrayList; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Body lifecycle orchestration for one physics world. - */ -public final class PhysicsBodyRuntime { - - @Nonnull - private final PhysicsSpaceRuntime spaceRuntime; - @Nonnull - private final PhysicsBodyRegistry bodyRegistry; - @Nonnull - private final PhysicsBodyRuntimeState runtimeState; - @Nonnull - private final PhysicsControlRuntimeState controlRuntime; - @Nonnull - private final PhysicsJointRegistry jointRegistry; - @Nonnull - private final PhysicsVisualRuntime visualRuntime; - @Nonnull - private final PhysicsWorldLifecycleState lifecycleState; - @Nonnull - private final Runnable worldChangedMarker; - - public PhysicsBodyRuntime(@Nonnull PhysicsSpaceRuntime spaceRuntime, - @Nonnull PhysicsBodyRegistry bodyRegistry, - @Nonnull PhysicsBodyRuntimeState runtimeState, - @Nonnull PhysicsControlRuntimeState controlRuntime, - @Nonnull PhysicsJointRegistry jointRegistry, - @Nonnull PhysicsVisualRuntime visualRuntime, - @Nonnull PhysicsWorldLifecycleState lifecycleState, - @Nonnull Runnable worldChangedMarker) { - this.spaceRuntime = spaceRuntime; - this.bodyRegistry = bodyRegistry; - this.runtimeState = runtimeState; - this.controlRuntime = controlRuntime; - this.jointRegistry = jointRegistry; - this.visualRuntime = visualRuntime; - this.lifecycleState = lifecycleState; - this.worldChangedMarker = worldChangedMarker; - } - - @Nonnull - public UUID addBody(@Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId, - @Nonnull BackendBodyHandle backendBodyHandle) { - PhysicsSpaceBinding binding = spaceRuntime.requireBinding(spaceId); - long backendBodyId = backendBodyHandle.value(); - if (!binding.runtime().containsBody(binding.backendSpaceHandle().value(), backendBodyId)) { - throw new IllegalArgumentException("Physics backend body id=" + backendBodyId - + " is not registered in space " + spaceId); - } - bodyRegistry.validateRegisterable(bodyUuid, backendBodyHandle, spaceId); - PhysicsBodyRegistration registration = - bodyRegistry.registerBody(bodyUuid, backendBodyHandle, spaceId); - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(binding, backendBodyId); - if (snapshot != null) { - lifecycleState.putBodySnapshot(registration.bodyUuid(), - snapshot, - spaceId); - } - worldChangedMarker.run(); - return bodyUuid; - } - - public void destroyBody(@Nonnull UUID bodyUuid, boolean removeFromSpace) { - PhysicsBodyRegistration registration = bodyRegistry.getRegistration(bodyUuid); - if (registration != null) { - if (removeFromSpace) { - removeBodyFromSpace(registration); - } - bodyRegistry.unregisterBody(bodyUuid); - clearBodyRuntimeState(bodyUuid); - } else { - clearBodyRuntimeState(bodyUuid); - } - worldChangedMarker.run(); - } - - public void destroyRegisteredBodies() { - RuntimeException failure = null; - boolean bodyFailure = false; - for (PhysicsBodyRegistration registration : new ArrayList<>(bodyRegistry.getRegistrations())) { - try { - destroyBody(registration.bodyUuid(), true); - } catch (RuntimeException exception) { - bodyFailure = true; - failure = collectFailure(failure, exception); - } - } - if (bodyFailure) { - throw failure; - } - clearBodyState(); - } - - public void clearBodyState() { - clearBodyStateWithoutMarkingWorldChanged(); - worldChangedMarker.run(); - } - - public void clearBodyStateWithoutMarkingWorldChanged() { - bodyRegistry.clear(); - runtimeState.clear(); - controlRuntime.clear(); - jointRegistry.clear(); - visualRuntime.clear(); - lifecycleState.clearBodySnapshots(); - } - - public void clearBodyRuntimeState(@Nonnull UUID bodyUuid) { - visualRuntime.clearBodyRuntimeState(bodyUuid, null); - lifecycleState.removeBodySnapshot(bodyUuid); - } - - private void removeBodyFromSpace(@Nonnull PhysicsBodyRegistration registration) { - for (PhysicsJointRegistration joint : jointRegistry.unregisterJointsForBody(registration.bodyUuid())) { - PhysicsSpaceBinding jointSpace = spaceRuntime.getBinding(joint.spaceId()); - if (jointSpace != null) { - jointSpace.runtime().removeJoint(jointSpace.backendSpaceHandle().value(), joint.backendJointHandle().value()); - } - } - PhysicsSpaceBinding binding = spaceRuntime.getBinding(registration.spaceId()); - if (binding != null) { - binding.runtime().removeBody(binding.backendSpaceHandle().value(), registration.backendBodyHandle().value()); - } - } - - @Nonnull - private static RuntimeException collectFailure(@Nullable RuntimeException failure, - @Nonnull RuntimeException exception) { - if (failure == null) { - return exception; - } - failure.addSuppressed(exception); - return failure; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java deleted file mode 100644 index 69c9f14f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotRefVisitor.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Internal snapshot visitor for authoritative paths that can use live PhysicsStore entity refs. - */ -@FunctionalInterface -public interface PhysicsBodySnapshotRefVisitor { - - void accept(@Nonnull UUID bodyUuid, - @Nullable Ref bodyRef, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java deleted file mode 100644 index 2b57066d..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStore.java +++ /dev/null @@ -1,225 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsBodySnapshotCursor; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Cached body snapshots and spatial lookup state for world-level readers. - */ -public final class PhysicsBodySnapshotStore { - - private final Map snapshots = - new Object2ObjectOpenHashMap<>(); - private final Object2LongOpenHashMap livenessMarks = - new Object2LongOpenHashMap<>(); - private final PhysicsBodySpatialIndex spatialIndex = new PhysicsBodySpatialIndex(); - private long livenessGeneration; - - public record ApplyStats(int applied, int inserted, int removed) { - } - - public int refresh(@Nonnull Iterable spaces, @Nonnull PhysicsBodyRegistry bodyRegistry) { - long generation = nextLivenessGeneration(); - MutableInt liveBodies = new MutableInt(); - for (PhysicsSpaceBinding space : spaces) { - SpaceId spaceId = space.spaceId(); - if (bodyRegistry.getRegistrationCount(spaceId) == 0) { - continue; - } - - Iterator registrations = bodyRegistry.registrationIterator(spaceId); - while (registrations.hasNext()) { - PhysicsBodyRegistration registration = registrations.next(); - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(space, registration.backendBodyHandle().value()); - if (snapshot == null) { - continue; - } - UUID bodyUuid = registration.bodyUuid(); - markLive(bodyUuid, generation, liveBodies); - PhysicsBodySnapshot previous = snapshots.get(bodyUuid); - if (snapshot != previous) { - snapshots.put(bodyUuid, snapshot); - } - spatialIndex.update(bodyUuid, - snapshot, - spaceId); - } - } - - retainMarked(generation); - return liveBodies.value(); - } - - @Nonnull - public ApplyStats applyPublishedFrame(@Nonnull PublishedPhysicsSnapshotFrame frame) { - PublishedFrameApplier applier = new PublishedFrameApplier(nextLivenessGeneration()); - frame.forEachBodyCursor(applier); - return new ApplyStats(applier.applied(), applier.inserted(), retainMarked(applier.generation())); - } - - public void put(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId) { - snapshots.put(Objects.requireNonNull(bodyUuid, "bodyUuid"), snapshot); - livenessMarks.put(bodyUuid, livenessGeneration); - spatialIndex.update(bodyUuid, snapshot, spaceId); - } - - @Nullable - public PhysicsBodySnapshot get(@Nonnull UUID bodyUuid) { - return snapshots.get(bodyUuid); - } - - public void remove(@Nonnull UUID bodyUuid) { - snapshots.remove(bodyUuid); - livenessMarks.removeLong(bodyUuid); - spatialIndex.remove(bodyUuid); - } - - public void clear() { - snapshots.clear(); - livenessMarks.clear(); - spatialIndex.clear(); - } - - public int bodyCount() { - return spatialIndex.bodyCount(); - } - - public int bodyCount(@Nonnull SpaceId spaceId) { - return spatialIndex.bodyCount(spaceId); - } - - public int cellCount() { - return spatialIndex.cellCount(); - } - - public void forEach(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - spatialIndex.forEach(spaceId, consumer); - } - - public void forEachIndexed(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - spatialIndex.forEachIndexed(spaceId, visitor); - } - - public int forEachNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull Consumer consumer) { - return spatialIndex.forEachNear(spaceId, center, radius, consumer); - } - - public int forEachIndexedNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - return spatialIndex.forEachIndexedNear(spaceId, center, radius, visitor); - } - - private long nextLivenessGeneration() { - livenessGeneration++; - if (livenessGeneration == 0L) { - livenessGeneration = 1L; - livenessMarks.clear(); - } - return livenessGeneration; - } - - private void markLive(@Nonnull UUID bodyUuid, - long generation, - @Nonnull MutableInt liveBodies) { - if (livenessMarks.put(bodyUuid, generation) != generation) { - liveBodies.increment(); - } - } - - private int retainMarked(long generation) { - int removed = 0; - Iterator iterator = snapshots.keySet().iterator(); - while (iterator.hasNext()) { - UUID bodyUuid = iterator.next(); - if (livenessMarks.getLong(bodyUuid) != generation) { - iterator.remove(); - livenessMarks.removeLong(bodyUuid); - spatialIndex.remove(bodyUuid); - removed++; - } - } - return removed; - } - - private final class PublishedFrameApplier implements Consumer { - - private final long generation; - private final MutableInt liveBodies = new MutableInt(); - private int applied; - private int inserted; - - private PublishedFrameApplier(long generation) { - this.generation = generation; - } - - @Override - public void accept(@Nonnull PublishedPhysicsBodySnapshotCursor bodyFrame) { - UUID bodyUuid = bodyFrame.bodyUuid(); - markLive(bodyUuid, generation, liveBodies); - PhysicsBodySnapshot snapshot = snapshots.get(bodyUuid); - if (snapshot == null) { - inserted++; - snapshot = bodyFrame.toBodySnapshot(); - snapshots.put(bodyUuid, snapshot); - } else if (!bodyFrame.matchesSnapshot(snapshot)) { - snapshot = bodyFrame.toBodySnapshot(); - snapshots.put(bodyUuid, snapshot); - } else { - applied++; - return; - } - spatialIndex.update(bodyUuid, - snapshot, - bodyFrame.spaceId()); - applied++; - } - - private int applied() { - return applied; - } - - private int inserted() { - return inserted; - } - - private long generation() { - return generation; - } - } - - private static final class MutableInt { - - private int value; - - private void increment() { - value++; - } - - private int value() { - return value; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java deleted file mode 100644 index 21846792..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotVisitor.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Internal snapshot visitor that avoids allocating public snapshot-entry DTOs. - */ -@FunctionalInterface -public interface PhysicsBodySnapshotVisitor { - - void accept(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId); -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java index 103a3181..62798319 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java @@ -4,7 +4,6 @@ import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -16,12 +15,6 @@ public final class PhysicsBodySnapshots { private PhysicsBodySnapshots() { } - @Nullable - public static PhysicsBodySnapshot read(@Nonnull PhysicsSpaceBinding space, - long backendBodyId) { - return read(space.runtime(), space.backendSpaceHandle().value(), backendBodyId); - } - @Nullable public static PhysicsBodySnapshot read(@Nonnull PhysicsBackendRuntime runtime, int spaceId, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java deleted file mode 100644 index 5467ac4c..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySpatialIndex.java +++ /dev/null @@ -1,261 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.snapshot.PhysicsBodySnapshotEntry; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Snapshot-side spatial hash for detached physics bodies. - * - *

        Stores the latest published {@link PhysicsBodySnapshot} for each body UUID - * and groups those snapshots into fixed-size world cells. - * Cell membership is updated whenever a body publishes a snapshot in a new - * position.

        - * - *

        Callers use it for area queries that need body identity and pose data, such - * as visual materialization, PhysicsChunk collision streaming hints, diagnostics, and - * other nearby-body discovery. Query freshness follows the snapshot publishing - * policy for each body.

        - */ -final class PhysicsBodySpatialIndex { - - private static final float CELL_SIZE = 16.0f; - private static final int AXIS_MASK = 0x1F_FFFF; - - private final Map entries = new Object2ObjectOpenHashMap<>(); - private final Long2ObjectMap> cells = new Long2ObjectOpenHashMap<>(); - private final Int2IntOpenHashMap spaceBodyCounts = new Int2IntOpenHashMap(); - - void update(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId) { - long cellKey = cellKey(snapshot.positionX(), snapshot.positionY(), snapshot.positionZ()); - IndexedBody indexed = entries.get(bodyUuid); - if (indexed == null) { - indexed = new IndexedBody(bodyUuid, snapshot, spaceId, cellKey); - entries.put(bodyUuid, indexed); - addToCell(indexed, cellKey); - spaceBodyCounts.addTo(spaceId.value(), 1); - return; - } - - if (!indexed.spaceId.equals(spaceId)) { - spaceBodyCounts.addTo(indexed.spaceId.value(), -1); - spaceBodyCounts.addTo(spaceId.value(), 1); - } - if (indexed.cellKey != cellKey) { - removeFromCell(indexed); - addToCell(indexed, cellKey); - } - indexed.snapshot = snapshot; - indexed.spaceId = spaceId; - } - - void remove(@Nonnull UUID bodyUuid) { - IndexedBody indexed = entries.remove(bodyUuid); - if (indexed != null) { - removeFromCell(indexed); - spaceBodyCounts.addTo(indexed.spaceId.value(), -1); - } - } - - void clear() { - entries.clear(); - cells.clear(); - spaceBodyCounts.clear(); - } - - int bodyCount() { - return entries.size(); - } - - int bodyCount(@Nonnull SpaceId spaceId) { - return Math.max(0, spaceBodyCounts.get(spaceId.value())); - } - - int cellCount() { - return cells.size(); - } - - void forEach(@Nonnull SpaceId spaceId, - @Nonnull Consumer consumer) { - for (IndexedBody indexed : entries.values()) { - if (spaceId.equals(indexed.spaceId)) { - consumer.accept(indexed.entry()); - } - } - } - - void forEachIndexed(@Nonnull SpaceId spaceId, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - for (IndexedBody indexed : entries.values()) { - if (spaceId.equals(indexed.spaceId)) { - indexed.visit(visitor); - } - } - } - - int forEachNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull Consumer consumer) { - int minX = cellCoordinate(center.x - radius); - int maxX = cellCoordinate(center.x + radius); - int minY = cellCoordinate(center.y - radius); - int maxY = cellCoordinate(center.y + radius); - int minZ = cellCoordinate(center.z - radius); - int maxZ = cellCoordinate(center.z + radius); - float radiusSquared = radius * radius; - int candidates = 0; - for (int cellX = minX; cellX <= maxX; cellX++) { - for (int cellY = minY; cellY <= maxY; cellY++) { - for (int cellZ = minZ; cellZ <= maxZ; cellZ++) { - List bucket = cells.get(packCell(cellX, cellY, cellZ)); - if (bucket == null) { - continue; - } - for (IndexedBody indexed : bucket) { - if (!spaceId.equals(indexed.spaceId)) { - continue; - } - candidates++; - float dx = indexed.snapshot.positionX() - center.x; - float dy = indexed.snapshot.positionY() - center.y; - float dz = indexed.snapshot.positionZ() - center.z; - if (dx * dx + dy * dy + dz * dz <= radiusSquared) { - consumer.accept(indexed.entry()); - } - } - } - } - } - return candidates; - } - - int forEachIndexedNear(@Nonnull SpaceId spaceId, - @Nonnull Vector3f center, - float radius, - @Nonnull PhysicsBodySnapshotVisitor visitor) { - int minX = cellCoordinate(center.x - radius); - int maxX = cellCoordinate(center.x + radius); - int minY = cellCoordinate(center.y - radius); - int maxY = cellCoordinate(center.y + radius); - int minZ = cellCoordinate(center.z - radius); - int maxZ = cellCoordinate(center.z + radius); - float radiusSquared = radius * radius; - int candidates = 0; - for (int cellX = minX; cellX <= maxX; cellX++) { - for (int cellY = minY; cellY <= maxY; cellY++) { - for (int cellZ = minZ; cellZ <= maxZ; cellZ++) { - List bucket = cells.get(packCell(cellX, cellY, cellZ)); - if (bucket == null) { - continue; - } - for (IndexedBody indexed : bucket) { - if (!spaceId.equals(indexed.spaceId)) { - continue; - } - candidates++; - float dx = indexed.snapshot.positionX() - center.x; - float dy = indexed.snapshot.positionY() - center.y; - float dz = indexed.snapshot.positionZ() - center.z; - if (dx * dx + dy * dy + dz * dz <= radiusSquared) { - indexed.visit(visitor); - } - } - } - } - } - return candidates; - } - - private void removeFromCell(@Nonnull IndexedBody indexed) { - List bucket = cells.get(indexed.cellKey); - if (bucket == null) { - return; - } - int index = indexed.cellIndex; - int lastIndex = bucket.size() - 1; - if (index >= 0 && index <= lastIndex && bucket.get(index) == indexed) { - IndexedBody moved = bucket.get(lastIndex); - bucket.set(index, moved); - moved.cellIndex = index; - bucket.remove(lastIndex); - } else { - bucket.remove(indexed); - } - indexed.cellIndex = -1; - if (bucket.isEmpty()) { - cells.remove(indexed.cellKey); - } - } - - private void addToCell(@Nonnull IndexedBody indexed, long cellKey) { - List bucket = cells.computeIfAbsent(cellKey, ignored -> new ArrayList<>()); - indexed.cellKey = cellKey; - indexed.cellIndex = bucket.size(); - bucket.add(indexed); - } - - private static long cellKey(float positionX, float positionY, float positionZ) { - return packCell(cellCoordinate(positionX), - cellCoordinate(positionY), - cellCoordinate(positionZ)); - } - - private static int cellCoordinate(float value) { - return (int) Math.floor(value / CELL_SIZE); - } - - private static long packCell(int x, int y, int z) { - return ((long) x & AXIS_MASK) << 42 - | ((long) y & AXIS_MASK) << 21 - | ((long) z & AXIS_MASK); - } - - private static final class IndexedBody { - - @Nonnull - private final UUID bodyUuid; - @Nonnull - private PhysicsBodySnapshot snapshot; - @Nonnull - private SpaceId spaceId; - private long cellKey; - private int cellIndex = -1; - - private IndexedBody(@Nonnull UUID bodyUuid, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull SpaceId spaceId, - long cellKey) { - this.bodyUuid = bodyUuid; - this.snapshot = snapshot; - this.spaceId = spaceId; - this.cellKey = cellKey; - } - - @Nonnull - private PhysicsBodySnapshotEntry entry() { - return new PhysicsBodySnapshotEntry(bodyUuid, - snapshot, - spaceId); - } - - private void visit(@Nonnull PhysicsBodySnapshotVisitor visitor) { - visitor.accept(bodyUuid, - snapshot, - spaceId); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java deleted file mode 100644 index 19a380a4..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistration.java +++ /dev/null @@ -1,45 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.joint; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.plugin.components.JointType; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Store tick registration for a stable joint UUID and backend-local joint handle. - */ -public record PhysicsJointRegistration(@Nonnull UUID jointUuid, - @Nonnull BackendJointHandle backendJointHandle, - @Nonnull SpaceId spaceId, - @Nonnull UUID bodyAUuid, - @Nonnull UUID bodyBUuid, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - - public PhysicsJointRegistration { - Objects.requireNonNull(jointUuid, "jointUuid"); - Objects.requireNonNull(backendJointHandle, "backendJointHandle"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(bodyAUuid, "bodyAUuid"); - Objects.requireNonNull(bodyBUuid, "bodyBUuid"); - Objects.requireNonNull(type, "type"); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java deleted file mode 100644 index fe1be3ff..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/joint/PhysicsJointRegistry.java +++ /dev/null @@ -1,181 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.joint; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; -import dev.hytalemodding.impulse.core.plugin.components.JointType; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Runtime identity index for backend physics joints. - */ -public final class PhysicsJointRegistry { - - private final Map registrationsByUuid = - new Object2ObjectLinkedOpenHashMap<>(); - private final Int2ObjectOpenHashMap> jointUuidsByRawBackendId = - new Int2ObjectOpenHashMap<>(); - - @Nonnull - public PhysicsJointRegistration registerJoint(@Nonnull UUID jointUuid, - @Nonnull SpaceId spaceId, - @Nonnull BackendJointHandle backendJointHandle, - @Nonnull UUID bodyAUuid, - @Nonnull UUID bodyBUuid, - @Nonnull JointType type, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - UUID existingUuid = getJointUuid(spaceId, backendJointHandle); - if (existingUuid != null && !existingUuid.equals(jointUuid)) { - throw new IllegalArgumentException("Physics joint is already registered as " + existingUuid); - } - PhysicsJointRegistration existingRegistration = registrationsByUuid.get(jointUuid); - if (existingRegistration != null - && (!existingRegistration.backendJointHandle().equals(backendJointHandle) - || !existingRegistration.spaceId().equals(spaceId))) { - throw new IllegalArgumentException("Physics joint uuid=" + jointUuid - + " is already registered to another backend joint"); - } - if (existingRegistration != null) { - removeBackendIndex(existingRegistration); - } - PhysicsJointRegistration registration = new PhysicsJointRegistration(jointUuid, - backendJointHandle, - spaceId, - bodyAUuid, - bodyBUuid, - type, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce); - registrationsByUuid.put(jointUuid, registration); - jointUuidsByRawBackendId - .computeIfAbsent(spaceId.value(), ignored -> new Long2ObjectOpenHashMap<>()) - .put(backendJointHandle.value(), jointUuid); - return registration; - } - - @Nullable - public PhysicsJointRegistration unregisterJoint(@Nonnull UUID jointUuid) { - PhysicsJointRegistration registration = registrationsByUuid.remove(jointUuid); - if (registration == null) { - return null; - } - - removeBackendIndex(registration); - return registration; - } - - @Nullable - public PhysicsJointRegistration unregisterJoint(@Nonnull SpaceId spaceId, long backendJointId) { - UUID jointUuid = getJointUuid(spaceId, backendJointId); - return jointUuid != null ? unregisterJoint(jointUuid) : null; - } - - @Nonnull - public Collection unregisterJointsForBody(@Nonnull UUID bodyUuid) { - ArrayList removed = new ArrayList<>(); - for (PhysicsJointRegistration registration : registrationsByUuid.values()) { - if (registration.bodyAUuid().equals(bodyUuid) || registration.bodyBUuid().equals(bodyUuid)) { - removed.add(registration.jointUuid()); - } - } - ArrayList registrations = new ArrayList<>(removed.size()); - for (UUID jointUuid : removed) { - PhysicsJointRegistration registration = unregisterJoint(jointUuid); - if (registration != null) { - registrations.add(registration); - } - } - return registrations; - } - - public void unregisterSpace(@Nonnull SpaceId spaceId) { - ArrayList removed = new ArrayList<>(); - for (PhysicsJointRegistration registration : registrationsByUuid.values()) { - if (registration.spaceId().equals(spaceId)) { - removed.add(registration.jointUuid()); - } - } - for (UUID jointUuid : removed) { - unregisterJoint(jointUuid); - } - } - - @Nullable - public PhysicsJointRegistration getRegistration(@Nonnull UUID jointUuid) { - return registrationsByUuid.get(jointUuid); - } - - @Nullable - public UUID getJointUuid(@Nonnull SpaceId spaceId, long backendJointId) { - Long2ObjectOpenHashMap jointUuids = - jointUuidsByRawBackendId.get(spaceId.value()); - return jointUuids != null ? jointUuids.get(backendJointId) : null; - } - - @Nullable - public UUID getJointUuid(@Nonnull SpaceId spaceId, - @Nonnull BackendJointHandle backendJointHandle) { - return getJointUuid(spaceId, backendJointHandle.value()); - } - - @Nonnull - public Collection getRegistrations() { - return new ArrayList<>(registrationsByUuid.values()); - } - - public void clear() { - registrationsByUuid.clear(); - jointUuidsByRawBackendId.clear(); - } - - private void removeBackendIndex(@Nonnull PhysicsJointRegistration registration) { - Long2ObjectOpenHashMap jointUuids = - jointUuidsByRawBackendId.get(registration.spaceId().value()); - if (jointUuids == null) { - return; - } - jointUuids.remove(registration.backendJointHandle().value()); - if (jointUuids.isEmpty()) { - jointUuidsByRawBackendId.remove(registration.spaceId().value()); - } - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index 3b415728..bf86bec1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; @@ -84,13 +85,9 @@ private static void applyCommand(@Nonnull Store store, @Nonnull BodyCommandComponent.Entry command) { switch (command.getKind()) { case WAKE -> runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, - ref, - null, - null)); + ref)); case SLEEP -> runtime.enqueuePendingBodyOperation(PendingBodyOperation.sleep(bodyUuid, - ref, - null, - null)); + ref)); case IMPULSE -> enqueueVector(runtime, ref, bodyUuid, command, PendingBodyOperation.Kind.IMPULSE); case TORQUE_IMPULSE -> enqueueVector(runtime, ref, @@ -133,19 +130,20 @@ private static void applyBodyType(@Nonnull Store store, RuntimeBodyBinding binding = runtimeBodyBinding(runtime, ref, bodyUuid, restore, false); if (binding == null) { if (command.isActivate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref, null, null)); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref)); } return; } binding.backendRuntime().setBodyType(binding.spaceHandle().value(), binding.bodyHandle().value(), BackendRuntimeCodes.bodyTypeCode(command.getBodyType())); - updateBodyHitMetadata(runtime, binding.bodyHandle(), command.getBodyType()); + updateBodyHitMetadata(runtime, + binding.backendId(), + binding.spaceHandle(), + binding.bodyHandle(), + command.getBodyType()); if (command.isActivate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, - ref, - binding.spaceHandle(), - binding.bodyHandle())); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref)); } } @@ -161,7 +159,7 @@ private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime RuntimeBodyBinding binding = runtimeBodyBinding(runtime, ref, bodyUuid, restore, false); if (binding == null) { if (command.isActivate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref, null, null)); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref)); } return; } @@ -170,10 +168,7 @@ private static void applyCollisionFilter(@Nonnull PhysicsRuntimeResource runtime command.getCollisionGroup(), command.getCollisionMask()); if (command.isActivate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, - ref, - binding.spaceHandle(), - binding.bodyHandle())); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref)); } } @@ -195,10 +190,7 @@ private static void applyVelocity(@Nonnull PhysicsRuntimeResource runtime, command.getAngularY(), command.getAngularZ()); if (command.isActivate()) { - runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, - ref, - binding.spaceHandle(), - binding.bodyHandle())); + runtime.enqueuePendingBodyOperation(PendingBodyOperation.wake(bodyUuid, ref)); } } @@ -210,8 +202,6 @@ private static void enqueueVector(@Nonnull PhysicsRuntimeResource runtime, runtime.enqueuePendingBodyOperation(PendingBodyOperation.vector(kind, bodyUuid, ref, - null, - null, command.getX(), command.getY(), command.getZ(), @@ -229,7 +219,8 @@ private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeReso boolean requireBound) { BackendBodyHandle bodyHandle = runtime.getBodyHandle(ref); BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(ref); - if (bodyHandle == null || spaceHandle == null) { + BackendId backendId = runtime.getBodyBackendId(ref); + if (bodyHandle == null || spaceHandle == null || backendId == null) { if (requireBound) { restore.recordSoftSkip("Body command target is unbound: " + bodyUuid); } @@ -240,15 +231,20 @@ private static RuntimeBodyBinding runtimeBodyBinding(@Nonnull PhysicsRuntimeReso restore.recordSoftSkip("Body command backend runtime is missing: " + bodyUuid); return null; } - return new RuntimeBodyBinding(spaceHandle, bodyHandle, backendRuntime); + return new RuntimeBodyBinding(backendId, spaceHandle, bodyHandle, backendRuntime); } private static void updateBodyHitMetadata(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBodyType bodyType) { - PhysicsRuntimeResource.BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyHandle); + PhysicsRuntimeResource.BodyHitMetadata metadata = + runtime.getBodyHitMetadata(backendId, spaceHandle, bodyHandle.value()); if (metadata != null) { - runtime.putBodyHitMetadata(bodyHandle, + runtime.putBodyHitMetadata(backendId, + spaceHandle, + bodyHandle, metadata.bodyUuid(), metadata.bodyRef(), bodyType, @@ -268,7 +264,8 @@ public Set> getDependencies() { return DEPENDENCIES; } - private record RuntimeBodyBinding(@Nonnull BackendSpaceHandle spaceHandle, + private record RuntimeBodyBinding(@Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, @Nonnull BackendBodyHandle bodyHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java index d81ecf21..3c55c4cb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; @@ -101,7 +102,8 @@ private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, @Nonnull ChunkCollisionSourceComponent source) { BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyRef); BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(bodyRef); - if (bodyHandle == null || spaceHandle == null) { + BackendId backendId = runtime.getBodyBackendId(bodyRef); + if (bodyHandle == null || spaceHandle == null || backendId == null) { return; } PhysicsBackendRuntime backendRuntime = runtime.runtimeForBodyRef(bodyRef); @@ -119,6 +121,7 @@ private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, stitchNeighbor(runtime, identity, backendRuntime, + backendId, spaceHandle, body.getSpaceUuid(), bodyHandle, @@ -131,6 +134,7 @@ private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsBackendRuntime backendRuntime, + @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull UUID spaceUuid, @Nonnull BackendBodyHandle bodyHandle, @@ -142,8 +146,11 @@ private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, } BackendBodyHandle neighborBody = runtime.getBodyHandle(neighborRef); BackendSpaceHandle neighborSpace = runtime.getBodySpaceHandle(neighborRef); + BackendId neighborBackendId = runtime.getBodyBackendId(neighborRef); if (neighborBody == null || neighborSpace == null + || neighborBackendId == null + || !neighborBackendId.equals(backendId) || neighborSpace.value() != spaceHandle.value()) { return; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index 2bf4e3ed..e2c47edb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -7,7 +7,9 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; @@ -53,13 +55,16 @@ private static void removeStaleBodies(@Nonnull Store store, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore) { List staleBodies = new ArrayList<>(); - runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> - runtime.forEachBodyHandle(spaceHandle, + runtime.forEachRuntimeSpaceBinding((_, backendId, spaceHandle, backendRuntime) -> + runtime.forEachBodyHandle(backendId, + spaceHandle, bodyId -> collectStaleBody(store, identity, runtime, restore, staleBodies, + backendId, + spaceHandle, backendRuntime, bodyId))); if (restore.isFailed()) { @@ -193,9 +198,13 @@ private static void collectStaleBody(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List staleBodies, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime, long bodyId) { - BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(backendId, + spaceHandle, + bodyId); if (metadata == null) { restore.markFailed("PhysicsStore backend body " + bodyId + " has no runtime snapshot metadata"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index 32a5a8d5..6b4edfb9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -7,6 +7,7 @@ import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsContactPhase; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; @@ -161,8 +162,8 @@ private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtim private static List runtimeStepBindings( @Nonnull PhysicsRuntimeResource runtime) { List bindings = new ArrayList<>(); - runtime.forEachRuntimeSpaceBinding((spaceRef, _, spaceHandle, backendRuntime) -> - bindings.add(new RuntimeStepBinding(spaceRef, spaceHandle, backendRuntime))); + runtime.forEachRuntimeSpaceBinding((spaceRef, backendId, spaceHandle, backendRuntime) -> + bindings.add(new RuntimeStepBinding(spaceRef, backendId, spaceHandle, backendRuntime))); return bindings; } @@ -174,8 +175,9 @@ private static List collectOwnerLaneSnapshots( bindings)); for (RuntimeStepBinding binding : bindings) { binding.backendRuntime().snapshotBodies(binding.spaceHandle().value(), - bodyIds -> runtime.forEachBodyHandle(binding.spaceHandle(), - bodyIds::accept), + bodyIds -> runtime.forEachBodyHandle(binding.backendId(), + binding.spaceHandle(), + bodyIds), (bodyId, _, bodyTypeCode, @@ -211,6 +213,8 @@ private static List collectOwnerLaneSnapshots( _, _) -> collectOwnerLaneSnapshot(runtime, snapshots, + binding.backendId(), + binding.spaceHandle(), bodyId, bodyTypeCode, positionX, @@ -236,13 +240,15 @@ private static int runtimeBodyHandleCount(@Nonnull PhysicsRuntimeResource runtim @Nonnull List bindings) { int bodyCount = 0; for (RuntimeStepBinding binding : bindings) { - bodyCount += runtime.bodyHandleCount(binding.spaceHandle()); + bodyCount += runtime.bodyHandleCount(binding.backendId(), binding.spaceHandle()); } return bodyCount; } private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource runtime, @Nonnull List snapshots, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, long bodyId, int bodyTypeCode, float positionX, @@ -260,7 +266,9 @@ private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource run float angularVelocityZ, float centerOfMassOffsetY, boolean sleeping) { - BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(backendId, + spaceHandle, + bodyId); if (metadata == null) { return; } @@ -322,6 +330,8 @@ private static StepBackendEvents collectOwnerLaneBackendEvents( distance, impulse) -> collectOwnerLaneContactEvent(runtime, backendEvents, + binding.backendId(), + binding.spaceHandle(), spaceId, bodyAId, bodyBId, @@ -342,6 +352,8 @@ private static StepBackendEvents collectOwnerLaneBackendEvents( private static void collectOwnerLaneContactEvent(@Nonnull PhysicsRuntimeResource runtime, @Nonnull StepBackendEvents backendEvents, + @Nonnull BackendId backendId, + @Nonnull BackendSpaceHandle spaceHandle, @Nonnull SpaceId spaceId, long bodyAId, long bodyBId, @@ -356,8 +368,8 @@ private static void collectOwnerLaneContactEvent(@Nonnull PhysicsRuntimeResource float normalBZ, float distance, float impulse) { - BodyHitMetadata bodyA = runtime.getBodyHitMetadata(bodyAId); - BodyHitMetadata bodyB = runtime.getBodyHitMetadata(bodyBId); + BodyHitMetadata bodyA = runtime.getBodyHitMetadata(backendId, spaceHandle, bodyAId); + BodyHitMetadata bodyB = runtime.getBodyHitMetadata(backendId, spaceHandle, bodyBId); if (bodyA == null || bodyA.bodyRef() == null || bodyB == null @@ -386,9 +398,9 @@ private static int resolveAdaptiveStepCount(@Nonnull PhysicsRuntimeResource runt simulationSteps, maxStepDt); StepRisk risk = new StepRisk(dt, minimumSteps); - runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> + runtime.forEachRuntimeSpaceBinding((_, backendId, spaceHandle, backendRuntime) -> backendRuntime.snapshotBodies(spaceHandle.value(), - bodyIds -> runtime.forEachBodyHandle(spaceHandle, bodyIds::accept), + bodyIds -> runtime.forEachBodyHandle(backendId, spaceHandle, bodyIds), risk)); return risk.steps(); } @@ -407,12 +419,14 @@ private static float maxSubmittedDtSeconds(@Nonnull PhysicsWorldSettings setting private static void syncContinuousCollisionMode(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, boolean forceDynamicBodies) { - runtime.forEachRuntimeSpaceBinding((_, _, spaceHandle, backendRuntime) -> { + runtime.forEachRuntimeSpaceBinding((_, backendId, spaceHandle, backendRuntime) -> { if (!backendRuntime.supportsContinuousCollision(spaceHandle.value())) { return; } - runtime.forEachBodyHandle(spaceHandle, bodyId -> { - BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(bodyId); + runtime.forEachBodyHandle(backendId, spaceHandle, bodyId -> { + BodySnapshotMetadata metadata = runtime.getBodySnapshotMetadata(backendId, + spaceHandle, + bodyId); boolean authoredCcd = metadata != null && authoredContinuousCollision(store, metadata); backendRuntime.bodySnapshot(spaceHandle.value(), @@ -650,6 +664,7 @@ private static final class StepCounters { } private record RuntimeStepBinding(@Nonnull Ref spaceRef, + @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull PhysicsBackendRuntime backendRuntime) { } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java index e0a7da11..1bfa3fff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; @@ -120,6 +121,11 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, restore.recordSoftSkip("Body references unbound space: " + bodyUuid); return; } + BackendId backendId = runtime.getSpaceBackendId(spaceRef); + if (backendId == null) { + restore.recordSoftSkip("Body references missing backend id: " + bodyUuid); + return; + } PhysicsBackendRuntime backendRuntime = runtimeForSpace(runtime, spaceRef); if (backendRuntime == null) { restore.recordSoftSkip("Body references missing backend runtime: " + bodyUuid); @@ -193,13 +199,20 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); } applyInitialTargetState(backendRuntime, spaceHandle, bodyHandle, bodyType, target); - runtime.putBodyHandle(bodyUuid, bodyRef, body.getSpaceUuid(), spaceHandle, bodyHandle); - runtime.putBodyHitMetadata(bodyHandle, + runtime.putBodyHandle(bodyRef, spaceRef, spaceHandle, bodyHandle); + runtime.putBodySnapshotMetadata(backendId, + spaceHandle, + bodyHandle, + bodyUuid, + bodyRef, + body.getSpaceUuid()); + runtime.putBodyHitMetadata(backendId, + spaceHandle, + bodyHandle, bodyUuid, bodyRef, bodyType, shape.getShapeType()); - identity.putBodyHandle(bodyHandle, bodyRef); } catch (RuntimeException exception) { if (bodyId != Long.MIN_VALUE) { try { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java index 202dfe3c..90814e90 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.runtime.BackendJointType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; @@ -97,7 +98,9 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, BackendBodyHandle bodyB = bodyBRef != null ? runtime.getBodyHandle(bodyBRef) : null; BackendSpaceHandle bodyASpace = bodyARef != null ? runtime.getBodySpaceHandle(bodyARef) : null; BackendSpaceHandle bodyBSpace = bodyBRef != null ? runtime.getBodySpaceHandle(bodyBRef) : null; - var backendId = spaceRef != null ? runtime.getSpaceBackendId(spaceRef) : null; + BackendId backendId = spaceRef != null ? runtime.getSpaceBackendId(spaceRef) : null; + BackendId bodyABackendId = bodyARef != null ? runtime.getBodyBackendId(bodyARef) : null; + BackendId bodyBBackendId = bodyBRef != null ? runtime.getBodyBackendId(bodyBRef) : null; PhysicsBackendRuntime backendRuntime = backendId != null ? runtime.getRuntime(backendId) : null; if (spaceHandle == null || bodyA == null || bodyB == null || backendRuntime == null) { restore.recordSoftSkip("Joint references unbound endpoint: " + jointUuid); @@ -105,6 +108,10 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, } if (bodyASpace == null || bodyBSpace == null + || bodyABackendId == null + || bodyBBackendId == null + || !bodyABackendId.equals(backendId) + || !bodyBBackendId.equals(backendId) || bodyASpace.value() != spaceHandle.value() || bodyBSpace.value() != spaceHandle.value()) { restore.recordSoftSkip("Joint endpoints are not in the joint space: " + jointUuid); @@ -137,8 +144,8 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, joint.getMotorTargetVelocity(), joint.getMotorMaxForce()); BackendJointHandle handle = new BackendJointHandle(jointId); - runtime.putJointHandle(jointRef, jointUuid, backendId, spaceHandle, handle); - identity.putJointHandle(handle, jointRef); + runtime.putJointHandle(jointRef, spaceRef, spaceHandle, handle); + runtime.putJointMetadata(backendId, spaceHandle, handle, jointUuid, jointRef); } catch (RuntimeException exception) { if (jointId != Long.MIN_VALUE) { try { @@ -161,13 +168,19 @@ private static boolean endpointsBound(@Nonnull PhysicsRuntimeResource runtime, BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; BackendSpaceHandle bodyASpace = bodyARef != null ? runtime.getBodySpaceHandle(bodyARef) : null; BackendSpaceHandle bodyBSpace = bodyBRef != null ? runtime.getBodySpaceHandle(bodyBRef) : null; + BackendId backendId = spaceRef != null ? runtime.getSpaceBackendId(spaceRef) : null; + BackendId bodyABackendId = bodyARef != null ? runtime.getBodyBackendId(bodyARef) : null; + BackendId bodyBBackendId = bodyBRef != null ? runtime.getBodyBackendId(bodyBRef) : null; return spaceHandle != null + && backendId != null && bodyARef != null && bodyBRef != null && runtime.getBodyHandle(bodyARef) != null && runtime.getBodyHandle(bodyBRef) != null && bodyASpace != null && bodyBSpace != null + && backendId.equals(bodyABackendId) + && backendId.equals(bodyBBackendId) && bodyASpace.value() == spaceHandle.value() && bodyBSpace.value() == spaceHandle.value(); } @@ -215,8 +228,7 @@ private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, if (spaceHandle != null && backendRuntime != null) { backendRuntime.removeJoint(spaceHandle.value(), handle.value()); } - identity.removeJointHandle(handle); - runtime.removeJointHandle(jointUuid, jointRef); + runtime.removeJointHandle(jointRef); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java index 5704e5c8..1ee49a2e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java @@ -81,7 +81,9 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, continue; } Ref spaceRef = chunk.getReferenceTo(index); - if (runtime.getSpaceHandle(spaceRef) != null) { + BackendSpaceHandle existingHandle = runtime.getSpaceHandle(spaceRef); + if (existingHandle != null) { + validateBoundSpaceBackend(runtime, restore, spaceRef, spaceUuid, space); continue; } bindSpace(runtime, @@ -97,6 +99,25 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, } } + private static void validateBoundSpaceBackend(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull Ref spaceRef, + @Nonnull UUID spaceUuid, + @Nonnull SpaceComponent space) { + BackendId boundBackendId = runtime.getSpaceBackendId(spaceRef); + if (boundBackendId == null) { + restore.markFailed("PhysicsStore space " + spaceUuid + + " has a backend space handle without a backend id"); + return; + } + BackendId authoredBackendId = space.getBackendId(); + if (!boundBackendId.equals(authoredBackendId)) { + restore.markFailed("PhysicsStore space " + spaceUuid + + " changed backend id after binding: " + boundBackendId.value() + + " -> " + authoredBackendId.value()); + } + } + private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, @Nonnull PhysicsIdentityIndexResource identity, @@ -141,10 +162,10 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, handle, solverSettings != null ? solverSettings : new SolverSettingsComponent(), extensionSettings); - runtime.putSpaceBinding(spaceUuid, ref, backendId, handle); + runtime.putSpaceHandle(ref, backendId, handle); + runtime.putSpaceMetadata(backendId, handle, spaceUuid, ref); runtime.clearPendingSpaceSettings(ref); compatibility.putSpace(compatibilitySpaceId, spaceUuid); - identity.putSpaceHandle(handle, ref); } catch (RuntimeException exception) { if (handle != null) { try { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/TargetBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/TargetBindingSystem.java index da7a09ec..9d0296eb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/TargetBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/TargetBindingSystem.java @@ -98,12 +98,8 @@ private static void applyTargets(@Nonnull PhysicsRuntimeResource runtime, private static void applyPendingBodyOperations(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore) { for (PendingBodyOperation operation : runtime.drainPendingBodyOperations()) { - BackendSpaceHandle spaceHandle = operation.spaceHandle(); - BackendBodyHandle bodyHandle = operation.bodyHandle(); - if (spaceHandle == null || bodyHandle == null) { - spaceHandle = runtime.getBodySpaceHandle(operation.bodyRef()); - bodyHandle = runtime.getBodyHandle(operation.bodyRef()); - } + BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(operation.bodyRef()); + BackendBodyHandle bodyHandle = runtime.getBodyHandle(operation.bodyRef()); if (spaceHandle == null || bodyHandle == null) { restore.recordSoftSkip("Pending body operation body is unbound: " + operation.bodyUuid()); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java index 55066733..52848029 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java @@ -89,6 +89,7 @@ static SpaceSummary summary(@Nonnull PhysicsSpaceCompatibilityIndexResource comp @Nonnull static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull SpaceContext space, long bodyId, float pointX, float pointY, @@ -98,7 +99,9 @@ static RaycastHitView toView(@Nonnull PhysicsRuntimeResource runtime, float normalZ, float fraction, float distance) { - BodyHitMetadata metadata = runtime.getBodyHitMetadata(bodyId); + BodyHitMetadata metadata = runtime.getBodyHitMetadata(space.backendId(), + space.spaceHandle(), + bodyId); return new RaycastHitView(metadata != null ? metadata.bodyRef() : null, pointX, pointY, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java index 106ca128..15dfbd6c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java @@ -142,7 +142,7 @@ private static Optional closest(@Nonnull Store sto @Nonnull Vector3f from, @Nonnull Vector3f to) { PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - RayHitCapture hit = new RayHitCapture(runtime); + RayHitCapture hit = new RayHitCapture(runtime, space); Vector3f copiedFrom = new Vector3f(Objects.requireNonNull(from, "from")); Vector3f copiedTo = new Vector3f(Objects.requireNonNull(to, "to")); boolean hitFound = space.backendRuntime().raycastClosest(space.spaceHandle().value(), @@ -181,6 +181,7 @@ private static List all(@Nonnull Store store, normalZ, fraction, distance) -> hits.add(PhysicsBackendAccess.toView(runtime, + space, bodyId, pointX, pointY, @@ -227,6 +228,7 @@ private static RaycastClosestBatchResult closestBatch(@Nonnull Store hits[rayIndex] = PhysicsBackendAccess.toView(runtime, + space, bodyId, pointX, pointY, @@ -244,11 +246,15 @@ private static final class RayHitCapture implements BackendRayHitSink { @Nonnull private final PhysicsRuntimeResource runtime; + @Nonnull + private final PhysicsBackendAccess.SpaceContext space; private boolean captured; private RaycastHitView view; - private RayHitCapture(@Nonnull PhysicsRuntimeResource runtime) { + private RayHitCapture(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull PhysicsBackendAccess.SpaceContext space) { this.runtime = runtime; + this.space = space; } @Override @@ -262,6 +268,7 @@ public void accept(long bodyId, float fraction, float distance) { view = PhysicsBackendAccess.toView(runtime, + space, bodyId, pointX, pointY, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java index fb3e8450..b2061801 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.internal.physics; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -12,18 +13,26 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.lang.reflect.InvocationTargetException; @@ -32,6 +41,7 @@ import java.util.List; import java.util.UUID; import javax.annotation.Nonnull; +import org.joml.Vector3f; import org.junit.jupiter.api.Test; class PhysicsStoreRowCleanupTest { @@ -119,6 +129,84 @@ void refreshIdentityAndRuntimeRefsRebuildsLargeUuidIndexWithoutParallelMapWrites } } + @Test + void removeRuntimeBodyRejectsStaleRefThatNoLongerMatchesUuid() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("row-cleanup-stale-body-ref")), + EmptyResourceStorage.get()); + try { + RuntimeFixture fixture = addBoundSpace(store, + uuid(30), + new BackendId("test:row-cleanup-body")); + UUID firstBodyUuid = uuid(31); + UUID secondBodyUuid = uuid(32); + Ref firstBodyRef = addIdentityRow(store, firstBodyUuid); + Ref secondBodyRef = addIdentityRow(store, secondBodyUuid); + BackendBodyHandle firstHandle = new BackendBodyHandle(301L); + BackendBodyHandle secondHandle = new BackendBodyHandle(302L); + PhysicsRuntimeResource runtime = + store.getResource(PhysicsRuntimeResource.getResourceType()); + bindBody(runtime, fixture, firstBodyUuid, firstBodyRef, firstHandle); + bindBody(runtime, fixture, secondBodyUuid, secondBodyRef, secondHandle); + + boolean removed = PhysicsStoreRowCleanup.removeRuntimeBody(runtime, + store.getResource(PhysicsIdentityIndexResource.getResourceType()), + firstBodyUuid, + secondBodyRef); + + assertFalse(removed); + assertEquals(firstHandle, runtime.getBodyHandle(firstBodyRef)); + assertEquals(secondHandle, runtime.getBodyHandle(secondBodyRef)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void removeRuntimeJointRejectsStaleRefThatNoLongerMatchesUuid() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("row-cleanup-stale-joint-ref")), + EmptyResourceStorage.get()); + try { + RuntimeFixture fixture = addBoundSpace(store, + uuid(40), + new BackendId("test:row-cleanup-joint")); + UUID firstJointUuid = uuid(41); + UUID secondJointUuid = uuid(42); + Ref firstJointRef = addIdentityRow(store, firstJointUuid); + Ref secondJointRef = addIdentityRow(store, secondJointUuid); + BackendJointHandle firstHandle = new BackendJointHandle(401L); + BackendJointHandle secondHandle = new BackendJointHandle(402L); + PhysicsRuntimeResource runtime = + store.getResource(PhysicsRuntimeResource.getResourceType()); + bindJoint(runtime, fixture, firstJointUuid, firstJointRef, firstHandle); + bindJoint(runtime, fixture, secondJointUuid, secondJointRef, secondHandle); + + boolean removed = PhysicsStoreRowCleanup.removeRuntimeJoint(runtime, + store.getResource(PhysicsIdentityIndexResource.getResourceType()), + firstJointUuid, + secondJointRef); + + assertFalse(removed); + assertEquals(firstHandle, runtime.getJointHandle(firstJointRef)); + assertEquals(secondHandle, runtime.getJointHandle(secondJointRef)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Nonnull private static Ref addIdentityRow(@Nonnull Store store, @Nonnull UUID bodyUuid) { @@ -128,6 +216,54 @@ private static Ref addIdentityRow(@Nonnull Store sto return ref; } + @Nonnull + private static RuntimeFixture addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + FakePhysicsBackendRuntime backendRuntime = (FakePhysicsBackendRuntime) + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + BackendSpaceHandle spaceHandle = + new BackendSpaceHandle(backendRuntime.createSpace(new SpaceId(42))); + PhysicsRuntimeResource runtime = + store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putRuntime(backendId, backendRuntime); + runtime.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); + return new RuntimeFixture(spaceUuid, backendId, spaceRef, spaceHandle); + } + + private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull RuntimeFixture fixture, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + @Nonnull BackendBodyHandle bodyHandle) { + runtime.putBodyHandle(bodyRef, fixture.spaceRef(), fixture.spaceHandle(), bodyHandle); + runtime.putBodySnapshotMetadata(fixture.backendId(), + fixture.spaceHandle(), + bodyHandle, + bodyUuid, + bodyRef, + fixture.spaceUuid()); + } + + private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull RuntimeFixture fixture, + @Nonnull UUID jointUuid, + @Nonnull Ref jointRef, + @Nonnull BackendJointHandle jointHandle) { + runtime.putJointHandle(jointRef, fixture.spaceRef(), fixture.spaceHandle(), jointHandle); + runtime.putJointMetadata(fixture.backendId(), + fixture.spaceHandle(), + jointHandle, + jointUuid, + jointRef); + } + private static void publishCopiedState(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull UUID firstBodyUuid, @@ -188,4 +324,10 @@ private static void markCurrentThreadAsWorldThread(@Nonnull Store exception.getTargetException()); } } + + private record RuntimeFixture(@Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + @Nonnull Ref spaceRef, + @Nonnull BackendSpaceHandle spaceHandle) { + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java index 3d44b385..8e490ab1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java @@ -144,10 +144,11 @@ private static BoundSpace addBoundSpace(@Nonnull Store store, new BackendSpaceHandle(backendRuntime.createSpace(new SpaceId(42))); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); runtime.putRuntime(backendId, backendRuntime); - runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); + runtime.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .putSpace(new SpaceId(42), spaceUuid); - return new BoundSpace(spaceRef, backendRuntime, spaceHandle, backendId); + return new BoundSpace(spaceUuid, spaceRef, backendRuntime, spaceHandle, backendId); } @Nonnull @@ -240,9 +241,20 @@ private static BackendBodyHandle bindBody(@Nonnull Store store, 1.0f); BackendBodyHandle handle = new BackendBodyHandle(bodyId); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - runtime.putBodyHandle(bodyUuid, bodyRef, uuid(1), space.handle(), handle); - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .putBodyHandle(handle, bodyRef); + runtime.putBodyHandle(bodyRef, space.ref(), space.handle(), handle); + runtime.putBodySnapshotMetadata(space.backendId(), + space.handle(), + handle, + bodyUuid, + bodyRef, + space.uuid()); + runtime.putBodyHitMetadata(space.backendId(), + space.handle(), + handle, + bodyUuid, + bodyRef, + PhysicsBodyType.DYNAMIC, + ShapeType.BOX); return handle; } @@ -278,9 +290,9 @@ private static void bindJoint(@Nonnull Store store, 0.0f); BackendJointHandle handle = new BackendJointHandle(jointId); store.getResource(PhysicsRuntimeResource.getResourceType()) - .putJointHandle(jointRef, jointUuid, space.backendId(), space.handle(), handle); - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .putJointHandle(handle, jointRef); + .putJointHandle(jointRef, space.ref(), space.handle(), handle); + store.getResource(PhysicsRuntimeResource.getResourceType()) + .putJointMetadata(space.backendId(), space.handle(), handle, jointUuid, jointRef); } private static void publishCopiedState(@Nonnull Store store, @@ -339,7 +351,8 @@ private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); } - private record BoundSpace(@Nonnull Ref ref, + private record BoundSpace(@Nonnull UUID uuid, + @Nonnull Ref ref, @Nonnull PhysicsBackendRuntime runtime, @Nonnull BackendSpaceHandle handle, @Nonnull BackendId backendId) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index a98fe687..3697c307 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -47,7 +47,7 @@ void compatibilityIndexMaintainsBothDirectionsWhenMappingsMove() { } @Test - void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { + void runtimeIndexesKeepRefHandlesAndScopedBackendMetadataTogether() { PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); BackendId backendId = new BackendId("test:runtime-index"); PhysicsBackendRuntime backendRuntime = @@ -67,23 +67,33 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { runtime.putRuntime(backendId, backendRuntime); runtime.putRuntime(otherBackendId, otherBackendRuntime); - runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, oldSpaceHandle); - assertSame(backendRuntime, runtime.runtimeForSpaceHandle(oldSpaceHandle)); + runtime.putSpaceHandle(spaceRef, backendId, oldSpaceHandle); + runtime.putSpaceMetadata(backendId, oldSpaceHandle, spaceUuid, spaceRef); - runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); - assertNull(runtime.runtimeForSpaceHandle(oldSpaceHandle)); - assertSame(backendRuntime, runtime.runtimeForSpaceHandle(spaceHandle)); + runtime.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); assertSame(backendRuntime, runtime.runtimeForSpaceRef(spaceRef)); - runtime.putSpaceBinding(collidingSpaceUuid, collidingSpaceRef, otherBackendId, spaceHandle); - assertNull(runtime.runtimeForSpaceHandle(spaceHandle)); + runtime.putSpaceHandle(collidingSpaceRef, otherBackendId, spaceHandle); + runtime.putSpaceMetadata(otherBackendId, spaceHandle, collidingSpaceUuid, collidingSpaceRef); assertSame(backendRuntime, runtime.runtimeForSpaceRef(spaceRef)); assertSame(otherBackendRuntime, runtime.runtimeForSpaceRef(collidingSpaceRef)); - runtime.removeSpaceHandle(collidingSpaceUuid); - assertSame(backendRuntime, runtime.runtimeForSpaceHandle(spaceHandle)); + runtime.removeSpaceHandle(collidingSpaceRef); - runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); - runtime.putBodyHitMetadata(bodyHandle, bodyRef, PhysicsBodyType.DYNAMIC, ShapeType.BOX); + runtime.putBodyHandle(bodyRef, spaceRef, spaceHandle, bodyHandle); + runtime.putBodySnapshotMetadata(backendId, + spaceHandle, + bodyHandle, + bodyUuid, + bodyRef, + spaceUuid); + runtime.putBodyHitMetadata(backendId, + spaceHandle, + bodyHandle, + bodyUuid, + bodyRef, + PhysicsBodyType.DYNAMIC, + ShapeType.BOX); assertSame(backendRuntime, runtime.getRuntime(backendId)); assertEquals(spaceUuid, runtime.getSpaceUuid(spaceRef)); @@ -92,26 +102,26 @@ void runtimeIndexesKeepBackendHandlesAndClearHotPathMetadataTogether() { assertEquals(bodyHandle, runtime.getBodyHandle(bodyRef)); assertEquals(spaceHandle, runtime.getBodySpaceHandle(bodyRef)); assertSame(backendRuntime, runtime.runtimeForBodyRef(bodyRef)); - assertEquals(bodyUuid, runtime.getBodySnapshotMetadata(bodyHandle.value()).bodyUuid()); + assertEquals(bodyUuid, + runtime.getBodySnapshotMetadata(backendId, spaceHandle, bodyHandle.value()).bodyUuid()); List handles = new ArrayList<>(); - runtime.forEachBodyHandle(spaceHandle, handles::add); + runtime.forEachBodyHandle(backendId, spaceHandle, handles::add); assertEquals(List.of(bodyHandle.value()), handles); - runtime.removeBodyHandle(bodyUuid, bodyRef); + runtime.removeBodyHandle(bodyRef); assertNull(runtime.getBodyHandle(bodyRef)); assertNull(runtime.getBodySpaceHandle(bodyRef)); assertNull(runtime.runtimeForBodyRef(bodyRef)); - assertNull(runtime.getBodySnapshotMetadata(bodyHandle.value())); - assertNull(runtime.getBodyHitMetadata(bodyHandle)); + assertNull(runtime.getBodySnapshotMetadata(backendId, spaceHandle, bodyHandle.value())); + assertNull(runtime.getBodyHitMetadata(backendId, spaceHandle, bodyHandle.value())); handles.clear(); - runtime.forEachBodyHandle(spaceHandle, handles::add); + runtime.forEachBodyHandle(backendId, spaceHandle, handles::add); assertEquals(List.of(), handles); - runtime.removeSpaceHandle(spaceUuid); + runtime.removeSpaceHandle(spaceRef); - assertNull(runtime.runtimeForSpaceHandle(spaceHandle)); assertNull(runtime.getSpaceHandle(spaceRef)); } @@ -134,34 +144,145 @@ void runtimeIndexesExposeRefsForTopologyCleanup() { Ref reboundJointRef = new TestRef(6); runtime.putRuntime(backendId, backendRuntime); - runtime.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); - runtime.putBodyHandle(bodyUuid, bodyRef, spaceUuid, spaceHandle, bodyHandle); - runtime.putJointHandle(jointRef, jointUuid, backendId, spaceHandle, jointHandle); + runtime.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); + runtime.putBodyHandle(bodyRef, spaceRef, spaceHandle, bodyHandle); + runtime.putBodySnapshotMetadata(backendId, + spaceHandle, + bodyHandle, + bodyUuid, + bodyRef, + spaceUuid); + runtime.putJointHandle(jointRef, spaceRef, spaceHandle, jointHandle); + runtime.putJointMetadata(backendId, spaceHandle, jointHandle, jointUuid, jointRef); - assertEquals(List.of(bodyRef), runtime.bodyRefsForSpaceHandle(spaceHandle)); - assertEquals(List.of(jointRef), runtime.jointRefsForSpaceHandle(spaceHandle)); + assertEquals(List.of(bodyRef), runtime.bodyRefsForSpaceHandle(backendId, spaceHandle)); + assertEquals(List.of(jointRef), runtime.jointRefsForSpaceHandle(backendId, spaceHandle)); assertSame(backendRuntime, runtime.runtimeForSpaceRef(spaceRef)); assertSame(backendRuntime, runtime.runtimeForBodyRef(bodyRef)); assertSame(backendRuntime, runtime.runtimeForJointRef(jointRef)); assertNull(runtime.runtimeForJointRef(new TestRef(99))); - runtime.putJointHandle(reboundJointRef, - jointUuid, - backendId, - spaceHandle, - reboundJointHandle); + runtime.putJointHandle(reboundJointRef, spaceRef, spaceHandle, reboundJointHandle); + runtime.putJointMetadata(backendId, spaceHandle, reboundJointHandle, jointUuid, reboundJointRef); assertNull(runtime.getJointHandle(jointRef)); assertNull(runtime.runtimeForJointRef(jointRef)); assertEquals(reboundJointHandle, runtime.getJointHandle(reboundJointRef)); assertSame(backendRuntime, runtime.runtimeForJointRef(reboundJointRef)); - assertEquals(List.of(reboundJointRef), runtime.jointRefsForSpaceHandle(spaceHandle)); + assertEquals(List.of(reboundJointRef), + runtime.jointRefsForSpaceHandle(backendId, spaceHandle)); + + runtime.removeBodyHandle(bodyRef); + runtime.removeJointHandle(reboundJointRef); + + assertEquals(List.of(), runtime.bodyRefsForSpaceHandle(backendId, spaceHandle)); + assertEquals(List.of(), runtime.jointRefsForSpaceHandle(backendId, spaceHandle)); + } + + @Test + void runtimeBodyHandleReplacementRemovesPreviousSpaceIndexEntry() { + PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000022"); + UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000023"); + BackendId backendId = new BackendId("test:runtime-body-replace"); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(61); + BackendBodyHandle firstHandle = new BackendBodyHandle(62L); + BackendBodyHandle secondHandle = new BackendBodyHandle(63L); + Ref spaceRef = new TestRef(40); + Ref bodyRef = new TestRef(41); + + runtime.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); + runtime.putBodyHandle(bodyRef, spaceRef, spaceHandle, firstHandle); + runtime.putBodySnapshotMetadata(backendId, + spaceHandle, + firstHandle, + bodyUuid, + bodyRef, + spaceUuid); + runtime.putBodyHitMetadata(backendId, + spaceHandle, + firstHandle, + bodyUuid, + bodyRef, + PhysicsBodyType.DYNAMIC, + ShapeType.BOX); + + runtime.putBodyHandle(bodyRef, spaceRef, spaceHandle, secondHandle); + runtime.putBodySnapshotMetadata(backendId, + spaceHandle, + secondHandle, + bodyUuid, + bodyRef, + spaceUuid); + runtime.putBodyHitMetadata(backendId, + spaceHandle, + secondHandle, + bodyUuid, + bodyRef, + PhysicsBodyType.DYNAMIC, + ShapeType.BOX); + + List handles = new ArrayList<>(); + runtime.forEachBodyHandle(backendId, spaceHandle, handles::add); + assertEquals(List.of(secondHandle.value()), handles); + assertEquals(1, runtime.bodyHandleCount(backendId, spaceHandle)); + assertNull(runtime.getBodySnapshotMetadata(backendId, spaceHandle, firstHandle.value())); + assertNull(runtime.getBodyHitMetadata(backendId, spaceHandle, firstHandle.value())); + assertEquals(bodyUuid, + runtime.getBodySnapshotMetadata(backendId, spaceHandle, secondHandle.value()).bodyUuid()); + } - runtime.removeBodyHandle(bodyUuid, bodyRef); - runtime.removeJointHandle(jointUuid, reboundJointRef); + @Test + void runtimeRefreshRebuildsRefIndexesFromScopedBackendMetadata() { + PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); + PhysicsIdentityIndexResource identity = new PhysicsIdentityIndexResource(); + BackendId backendId = new BackendId("test:runtime-refresh"); + PhysicsBackendRuntime backendRuntime = + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000019"); + UUID bodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000020"); + UUID jointUuid = UUID.fromString("00000000-0000-0000-0000-000000000021"); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(55); + BackendBodyHandle bodyHandle = new BackendBodyHandle(56L); + BackendJointHandle jointHandle = new BackendJointHandle(57L); + Ref oldSpaceRef = new TestRef(20); + Ref oldBodyRef = new TestRef(21); + Ref oldJointRef = new TestRef(22); + Ref newSpaceRef = new TestRef(30); + Ref newBodyRef = new TestRef(31); + Ref newJointRef = new TestRef(32); - assertEquals(List.of(), runtime.bodyRefsForSpaceHandle(spaceHandle)); - assertEquals(List.of(), runtime.jointRefsForSpaceHandle(spaceHandle)); + runtime.putRuntime(backendId, backendRuntime); + runtime.putSpaceHandle(oldSpaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, oldSpaceRef); + runtime.putBodyHandle(oldBodyRef, oldSpaceRef, spaceHandle, bodyHandle); + runtime.putBodySnapshotMetadata(backendId, + spaceHandle, + bodyHandle, + bodyUuid, + oldBodyRef, + spaceUuid); + runtime.putJointHandle(oldJointRef, oldSpaceRef, spaceHandle, jointHandle); + runtime.putJointMetadata(backendId, spaceHandle, jointHandle, jointUuid, oldJointRef); + identity.putUuid(spaceUuid, newSpaceRef); + identity.putUuid(bodyUuid, newBodyRef); + identity.putUuid(jointUuid, newJointRef); + + runtime.refreshRowRefs(identity); + + assertNull(runtime.getSpaceHandle(oldSpaceRef)); + assertNull(runtime.getBodyHandle(oldBodyRef)); + assertNull(runtime.getJointHandle(oldJointRef)); + assertEquals(spaceHandle, runtime.getSpaceHandle(newSpaceRef)); + assertEquals(bodyHandle, runtime.getBodyHandle(newBodyRef)); + assertEquals(jointHandle, runtime.getJointHandle(newJointRef)); + assertSame(backendRuntime, runtime.runtimeForSpaceRef(newSpaceRef)); + assertSame(backendRuntime, runtime.runtimeForBodyRef(newBodyRef)); + assertSame(backendRuntime, runtime.runtimeForJointRef(newJointRef)); + assertEquals(newBodyRef, + runtime.getBodySnapshotMetadata(backendId, spaceHandle, bodyHandle.value()).bodyRef()); } @Test @@ -174,16 +295,12 @@ void removeBodyHandleClearsPendingBodyOperationsForThatRef() { runtime.enqueuePendingBodyOperation(PhysicsRuntimeResource.PendingBodyOperation.wake( firstBodyUuid, - firstBodyRef, - null, - null)); + firstBodyRef)); runtime.enqueuePendingBodyOperation(PhysicsRuntimeResource.PendingBodyOperation.sleep( secondBodyUuid, - secondBodyRef, - null, - null)); + secondBodyRef)); - runtime.removeBodyHandle(firstBodyUuid, firstBodyRef); + runtime.removeBodyHandle(firstBodyRef); List drained = runtime.drainPendingBodyOperations(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java deleted file mode 100644 index e5011186..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRegistryTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; - -class PhysicsBodyRegistryTest { - - @Test - void indexesRegistrationsBySpaceWithoutScanningUnrelatedSpaces() { - SpaceId firstSpace = new SpaceId(1); - SpaceId secondSpace = new SpaceId(2); - UUID firstId = new UUID(0L, 1L); - UUID secondId = new UUID(0L, 2L); - PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); - - registry.registerBody(firstId, - handle(11L), - firstSpace); - registry.registerBody(secondId, - handle(12L), - secondSpace); - - List firstSpaceIds = new ArrayList<>(); - registry.forEachRegistration(firstSpace, - registration -> firstSpaceIds.add(registration.bodyUuid())); - - assertEquals(List.of(firstId), firstSpaceIds); - assertEquals(1, registry.getRegistrationCount(firstSpace)); - assertEquals(1, registry.getRegistrationCount(secondSpace)); - - registry.unregisterBody(firstId); - - assertEquals(0, registry.getRegistrationCount(firstSpace)); - assertEquals(1, registry.getRegistrationCount(secondSpace)); - } - - @Test - void reRegisteringSameBodyWithDifferentSpaceIsRejectedWithoutMovingIndex() { - SpaceId firstSpace = new SpaceId(1); - SpaceId secondSpace = new SpaceId(2); - UUID bodyId = new UUID(0L, 3L); - PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); - registry.registerBody(bodyId, - handle(21L), - firstSpace); - - assertThrows(IllegalArgumentException.class, () -> registry.registerBody(bodyId, - handle(21L), - secondSpace)); - - assertEquals(1, registry.getRegistrationCount(firstSpace)); - assertEquals(0, registry.getRegistrationCount(secondSpace)); - } - - @Test - void registrationsExposeBodyIdentityAndSpace() { - SpaceId space = new SpaceId(1); - UUID bodyId = new UUID(0L, 4L); - PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); - registry.registerBody(bodyId, - handle(31L), - space); - - PhysicsBodyRegistration first = registry.getRegistration(bodyId); - PhysicsBodyRegistration second = registry.getRegistration(bodyId); - PhysicsBodyRegistration fromCollection = registry.getRegistrations() - .iterator() - .next(); - - assertSame(first, second); - assertSame(first, fromCollection); - assertEquals(bodyId, first.bodyUuid()); - assertEquals(space, first.spaceId()); - } - - @Nonnull - private static BackendBodyHandle handle(long value) { - return new BackendBodyHandle(value); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java deleted file mode 100644 index 7389ec6b..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshotStoreTest.java +++ /dev/null @@ -1,201 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSpaceFrame; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsBodySnapshotStoreTest { - - @Test - void refreshPassesLazySelectedBodiesToBackend() { - FakePhysicsBackendRuntimeProvider provider = - new FakePhysicsBackendRuntimeProvider("test:snapshot-store-lazy-refresh"); - PhysicsBackendRuntime runtime = provider.createRuntime(); - SpaceId spaceId = new SpaceId(1); - int backendSpaceId = runtime.createSpace(spaceId); - long backendBodyId = runtime.createBody(backendSpaceId, - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 1.0f, - BackendRuntimeCodes.BODY_DYNAMIC, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - UUID bodyId = new UUID(0L, 1L); - PhysicsSpaceBinding binding = new PhysicsSpaceBinding(provider.getId(), - spaceId, - new BackendSpaceHandle(backendSpaceId), - runtime); - PhysicsBodyRegistry registry = new PhysicsBodyRegistry(); - registry.registerBody(bodyId, - new BackendBodyHandle(backendBodyId), - spaceId); - PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); - - assertEquals(1, store.refresh(List.of(binding), registry)); - - assertEquals(1, store.bodyCount()); - } - - @Test - void appliesPublishedFramesIncrementallyWithoutReinsertingUnchangedBodies() { - SpaceId spaceId = new SpaceId(1); - UUID bodyId = new UUID(0L, 1L); - PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); - - PhysicsBodySnapshotStore.ApplyStats firstApply = store.applyPublishedFrame( - frame(spaceId, bodyId, 1L, new Vector3f(1.0f, 2.0f, 3.0f))); - PhysicsBodySnapshotStore.ApplyStats secondApply = store.applyPublishedFrame( - frame(spaceId, bodyId, 2L, new Vector3f(2.0f, 2.0f, 3.0f))); - - assertEquals(1, firstApply.applied()); - assertEquals(1, firstApply.inserted()); - assertEquals(0, firstApply.removed()); - assertEquals(1, secondApply.applied()); - assertEquals(0, secondApply.inserted()); - assertEquals(0, secondApply.removed()); - assertEquals(1, store.bodyCount()); - assertEquals(1, store.bodyCount(spaceId)); - assertEquals(1, store.cellCount()); - } - - @Test - void applyPublishedFrameUsesFrameMetadataWithoutLiveRegistry() { - SpaceId spaceId = new SpaceId(1); - UUID bodyId = new UUID(0L, 12L); - PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); - - PhysicsBodySnapshotStore.ApplyStats apply = store.applyPublishedFrame( - frame(spaceId, bodyId, 1L, new Vector3f(1.0f, 2.0f, 3.0f))); - - assertEquals(1, apply.applied()); - assertEquals(1, apply.inserted()); - assertEquals(1, store.bodyCount()); - assertEquals(1, store.bodyCount(spaceId)); - } - - @Test - void applyPublishedFrameReusesSnapshotWhenBodyStateIsUnchanged() { - SpaceId spaceId = new SpaceId(1); - UUID bodyId = new UUID(0L, 2L); - PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); - - store.applyPublishedFrame(frame(spaceId, bodyId, 1L, new Vector3f(1.0f, 2.0f, 3.0f))); - var firstSnapshot = store.get(bodyId); - store.applyPublishedFrame(frame(spaceId, bodyId, 2L, new Vector3f(1.0f, 2.0f, 3.0f))); - - assertSame(firstSnapshot, store.get(bodyId)); - } - - @Test - void internalNearVisitorExposesSnapshotMetadataWithoutEntryDto() { - SpaceId spaceId = new SpaceId(1); - UUID nearBodyId = new UUID(0L, 10L); - UUID farBodyId = new UUID(0L, 11L); - PhysicsBodySnapshot nearSnapshot = snapshotAt(1.0f, 2.0f, 3.0f); - PhysicsBodySnapshot farSnapshot = snapshotAt(100.0f, 2.0f, 3.0f); - PhysicsBodySnapshotStore store = new PhysicsBodySnapshotStore(); - store.put(nearBodyId, - nearSnapshot, - spaceId); - store.put(farBodyId, - farSnapshot, - spaceId); - - List visited = new ArrayList<>(); - int candidates = store.forEachIndexedNear(spaceId, - new Vector3f(0.0f, 2.0f, 3.0f), - 4.0f, - (bodyId, snapshot, bodySpaceId) -> { - visited.add(bodyId); - assertSame(nearSnapshot, snapshot); - assertEquals(spaceId, bodySpaceId); - }); - - assertEquals(1, candidates); - assertEquals(List.of(nearBodyId), visited); - } - - private static PublishedPhysicsSnapshotFrame frame(SpaceId spaceId, - UUID bodyId, - long frameEpoch, - Vector3f position) { - PublishedPhysicsBodySnapshot body = new PublishedPhysicsBodySnapshot(bodyId, - spaceId, - frameEpoch, - 0L, - 0L, - 0L, - position, - new Quaternionf(), - new Vector3f(), - new Vector3f(), - PhysicsBodyType.DYNAMIC, - false, - false, - 0.0f, - ShapeType.BOX, - new Vector3f(0.5f, 0.5f, 0.5f), - 0.0f, - 0.0f, - PhysicsAxis.Y); - return new PublishedPhysicsSnapshotFrame(frameEpoch, - 0L, - frameEpoch, - frameEpoch, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 1, - 0L, - 0L, - List.of(new PublishedPhysicsSpaceFrame(spaceId, frameEpoch, 0L, 0L, List.of(body)))); - } - - private static PhysicsBodySnapshot snapshotAt(float x, float y, float z) { - return new PhysicsBodySnapshot(new Vector3f(x, y, z), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - PhysicsBodyType.DYNAMIC, - false, - false, - 0.0f, - ShapeType.BOX, - new Vector3f(0.5f, 0.5f, 0.5f), - 0.0f, - 0.0f, - PhysicsAxis.Y); - } - -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java deleted file mode 100644 index dd9acfc2..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/lifecycle/PhysicsWorldLifecycleStateTest.java +++ /dev/null @@ -1,147 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.lifecycle; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; -import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceBinding; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldLifecycleState; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRegistry; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsSnapshotPublicationEvent; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.Test; - -class PhysicsWorldLifecycleStateTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - - @Test - void stalePublishedFrameIsRejectedAfterWorldEpochChanges() { - Fixture fixture = createFixture("stale-frame"); - UUID bodyUuid = registerBox(fixture); - PublishedPhysicsSnapshotFrame staleFrame = fixture.state.capturePublishedSnapshotFrame( - List.of(fixture.binding), - fixture.registry, - 10L, - 20L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - fixture.state.markWorldChanged(fixture.registry, true); - - assertEquals(0, fixture.state.applyPublishedSnapshotFrame(staleFrame, fixture.registry, 21L)); - assertEquals(0, fixture.state.bodySnapshotCount()); - assertNull(fixture.registry.getPublishedRegistrationSpaceId(bodyUuid)); - assertEquals(0, fixture.state.latestEventFrame().snapshotPublicationCount()); - } - - @Test - void currentPublishedFrameAppliesReaderSnapshotState() { - Fixture fixture = createFixture("current-frame"); - UUID bodyUuid = registerBox(fixture); - long appliedBefore = fixture.state.latestSnapshotAppliedNanos(); - - PublishedPhysicsSnapshotFrame frame = fixture.state.capturePublishedSnapshotFrame( - List.of(fixture.binding), - fixture.registry, - 11L, - 21L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - assertEquals(1, fixture.state.applyPublishedSnapshotFrame(frame, fixture.registry, 22L)); - assertEquals(1, fixture.state.bodySnapshotCount()); - assertNotNull(fixture.state.getBodySnapshot(bodyUuid)); - assertTrue(fixture.state.latestSnapshotAppliedNanos() >= appliedBefore); - } - - @Test - void currentFramePublicationCreatesSnapshotPublicationEvent() { - Fixture fixture = createFixture("publication-event"); - registerBox(fixture); - PublishedPhysicsSnapshotFrame frame = fixture.state.capturePublishedSnapshotFrame( - List.of(fixture.binding), - fixture.registry, - 14L, - 42L, - PublishedPhysicsSnapshotFrame.Status.COMPLETE, - 0L, - false); - - int applied = fixture.state.applyPublishedSnapshotFrame(frame, fixture.registry, 43L); - PhysicsEventFrame eventFrame = fixture.state.latestEventFrame(); - PhysicsSnapshotPublicationEvent event = eventFrame.latestSnapshotPublication(); - - assertEquals(1, applied); - assertEquals(1, eventFrame.snapshotPublicationCount()); - assertNotNull(event); - assertEquals(frame.frameEpoch(), event.snapshotFrameEpoch()); - assertEquals(frame.worldEpoch(), event.worldEpoch()); - assertEquals(frame.stepSequence(), event.stepSequence()); - assertEquals(frame.serverTick(), event.serverTick()); - assertEquals(43L, event.publicationServerTick()); - assertTrue(event.publicationNanoTime() > 0L); - assertEquals(applied, event.appliedBodyCount()); - } - - private static Fixture createFixture(String name) { - FakePhysicsBackendRuntimeProvider provider = - new FakePhysicsBackendRuntimeProvider("test:lifecycle-" + name + "-" - + BACKEND_COUNTER.incrementAndGet()); - PhysicsBackendRuntime runtime = provider.createRuntime(); - SpaceId spaceId = new SpaceId(1); - int backendSpaceId = runtime.createSpace(spaceId); - PhysicsSpaceBinding binding = new PhysicsSpaceBinding(provider.getId(), - spaceId, - new BackendSpaceHandle(backendSpaceId), - runtime); - return new Fixture(new PhysicsWorldLifecycleState(), - new PhysicsBodyRegistry(), - binding); - } - - private static UUID registerBox(Fixture fixture) { - long backendBodyId = fixture.binding.runtime().createBody(fixture.binding.backendSpaceHandle().value(), - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 1.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - UUID bodyUuid = UUID.randomUUID(); - fixture.registry.registerBody(bodyUuid, - new BackendBodyHandle(backendBodyId), - fixture.binding.spaceId()); - return bodyUuid; - } - - private record Fixture(PhysicsWorldLifecycleState state, - PhysicsBodyRegistry registry, - PhysicsSpaceBinding binding) { - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/snapshot/PhysicsWorldSnapshotStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/snapshot/PhysicsWorldSnapshotStateTest.java deleted file mode 100644 index c4fcfb5a..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/snapshot/PhysicsWorldSnapshotStateTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.snapshot; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSnapshotState; -import dev.hytalemodding.impulse.core.plugin.snapshot.PublishedPhysicsSnapshotFrame; -import org.junit.jupiter.api.Test; - -class PhysicsWorldSnapshotStateTest { - - @Test - void applyPublishedSnapshotFrameReportsWhetherFrameMatchesCurrentWorldEpoch() { - PhysicsWorldSnapshotState state = new PhysicsWorldSnapshotState(); - PublishedPhysicsSnapshotFrame currentFrame = PublishedPhysicsSnapshotFrame.empty(1L, 0L); - - PhysicsWorldSnapshotState.ApplyResult current = - state.applyPublishedSnapshotFrame(currentFrame); - - assertTrue(current.currentWorldEpoch()); - assertEquals(0, current.appliedCount()); - - state.markWorldChanged(); - - PhysicsWorldSnapshotState.ApplyResult stale = - state.applyPublishedSnapshotFrame(currentFrame); - - assertFalse(stale.currentWorldEpoch()); - assertEquals(0, stale.appliedCount()); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java index e293a85c..6c0c9d54 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java @@ -150,9 +150,9 @@ private static RuntimeFixture addBoundSpace(@Nonnull Store store, PhysicsRuntimeResource runtimeResource = store.getResource( PhysicsRuntimeResource.getResourceType()); runtimeResource.putRuntime(backendId, runtime); - runtimeResource.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); - identity.putSpaceHandle(spaceHandle, spaceRef); - return new RuntimeFixture(spaceRef, spaceHandle, runtime); + runtimeResource.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtimeResource.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); + return new RuntimeFixture(spaceUuid, backendId, spaceRef, spaceHandle, runtime); } private static void putSpaceSurface(@Nonnull Store store, @@ -231,13 +231,25 @@ private static GeneratedRow addGeneratedBoxRow(@Nonnull Store stor 0x01, 0x02); BackendBodyHandle backendBodyHandle = new BackendBodyHandle(bodyHandle); - store.getResource(PhysicsRuntimeResource.getResourceType()) - .putBodyHandle(bodyUuid, - bodyRef, - spaceUuid, - runtime.spaceHandle(), - backendBodyHandle); - identity.putBodyHandle(backendBodyHandle, bodyRef); + PhysicsRuntimeResource runtimeResource = + store.getResource(PhysicsRuntimeResource.getResourceType()); + runtimeResource.putBodyHandle(bodyRef, + runtime.spaceRef(), + runtime.spaceHandle(), + backendBodyHandle); + runtimeResource.putBodySnapshotMetadata(runtime.backendId(), + runtime.spaceHandle(), + backendBodyHandle, + bodyUuid, + bodyRef, + runtime.spaceUuid()); + runtimeResource.putBodyHitMetadata(runtime.backendId(), + runtime.spaceHandle(), + backendBodyHandle, + bodyUuid, + bodyRef, + PhysicsBodyType.STATIC, + ShapeType.BOX); return new GeneratedRow(bodyRef, backendBodyHandle); } @@ -276,7 +288,9 @@ private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); } - private record RuntimeFixture(@Nonnull Ref spaceRef, + private record RuntimeFixture(@Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + @Nonnull Ref spaceRef, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull FakePhysicsBackendRuntime backendRuntime) { } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index ce1a3c56..ccfaa309 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -604,11 +604,10 @@ private static Ref addBoundSpace(@Nonnull Store stor new FakePhysicsBackendRuntimeProvider(backendId, false, voxelTerrain).createRuntime(); int spaceHandle = backendRuntime.createSpace(new SpaceId(42)); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + BackendSpaceHandle backendSpaceHandle = new BackendSpaceHandle(spaceHandle); runtime.putRuntime(backendId, backendRuntime); - runtime.putSpaceBinding(spaceUuid, - spaceRef, - backendId, - new BackendSpaceHandle(spaceHandle)); + runtime.putSpaceHandle(spaceRef, backendId, backendSpaceHandle); + runtime.putSpaceMetadata(backendId, backendSpaceHandle, spaceUuid, spaceRef); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .putSpace(new SpaceId(42), spaceUuid); publishSettingsIndex(store, spaceUuid); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index c571f0e6..ce3ee0f6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -127,6 +127,64 @@ void voxelRowsAreStitchedThroughBodyRuntimeHandlesOncePerPayload() { } } + @Test + void voxelRowsWithSameNumericSpaceHandleAreNotStitchedAcrossBackends() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-cross-backend-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(11); + RuntimeFixture bulletRuntime = addBoundSpace(store, + spaceUuid, + new BackendId("test:chunk-collision-bullet")); + RuntimeFixture rapierRuntime = addBoundSpace(store, + uuid(12), + new BackendId("test:chunk-collision-rapier")); + String firstSourceKey = "0:0:0"; + String secondSourceKey = "1:0:0"; + String firstPayloadKey = "chunk-collision/cross-backend/0"; + String secondPayloadKey = "chunk-collision/cross-backend/1"; + addVoxelRow(store, + bulletRuntime, + spaceUuid, + firstSourceKey, + firstPayloadKey, + 0, + 0, + 0); + addVoxelRow(store, + rapierRuntime, + spaceUuid, + secondSourceKey, + secondPayloadKey, + 1, + 0, + 0); + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .put(firstPayloadKey, + payloadWithNeighbors(List.of(new Neighbor(secondSourceKey, 16, 0, 0)))); + store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) + .put(secondPayloadKey, payloadWithNeighbors(List.of())); + + runStitchingSystem(store); + + assertEquals(List.of(), + bulletRuntime.backendRuntime().combineCalls(bulletRuntime.spaceHandle().value())); + assertEquals(List.of(), + rapierRuntime.backendRuntime().combineCalls(rapierRuntime.spaceHandle().value())); + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Nonnull private static RuntimeFixture addBoundSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -147,9 +205,9 @@ private static RuntimeFixture addBoundSpace(@Nonnull Store store, PhysicsRuntimeResource runtimeResource = store.getResource( PhysicsRuntimeResource.getResourceType()); runtimeResource.putRuntime(backendId, runtime); - runtimeResource.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); - identity.putSpaceHandle(spaceHandle, spaceRef); - return new RuntimeFixture(spaceRef, spaceHandle, runtime); + runtimeResource.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtimeResource.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); + return new RuntimeFixture(spaceUuid, backendId, spaceRef, spaceHandle, runtime); } @Nonnull @@ -214,13 +272,25 @@ private static Ref addVoxelRow(@Nonnull Store store, PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL); BackendBodyHandle backendBodyHandle = new BackendBodyHandle(bodyHandle); - store.getResource(PhysicsRuntimeResource.getResourceType()) - .putBodyHandle(bodyUuid, - bodyRef, - spaceUuid, - runtime.spaceHandle(), - backendBodyHandle); - identity.putBodyHandle(backendBodyHandle, bodyRef); + PhysicsRuntimeResource runtimeResource = + store.getResource(PhysicsRuntimeResource.getResourceType()); + runtimeResource.putBodyHandle(bodyRef, + runtime.spaceRef(), + runtime.spaceHandle(), + backendBodyHandle); + runtimeResource.putBodySnapshotMetadata(runtime.backendId(), + runtime.spaceHandle(), + backendBodyHandle, + bodyUuid, + bodyRef, + runtime.spaceUuid()); + runtimeResource.putBodyHitMetadata(runtime.backendId(), + runtime.spaceHandle(), + backendBodyHandle, + bodyUuid, + bodyRef, + PhysicsBodyType.STATIC, + ShapeType.VOXELS); return bodyRef; } @@ -309,7 +379,9 @@ private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); } - private record RuntimeFixture(@Nonnull Ref spaceRef, + private record RuntimeFixture(@Nonnull UUID spaceUuid, + @Nonnull BackendId backendId, + @Nonnull Ref spaceRef, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull FakePhysicsBackendRuntime backendRuntime) { } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java new file mode 100644 index 00000000..64ca83e8 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java @@ -0,0 +1,245 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.ColliderBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.JointBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointType; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import java.util.ArrayList; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class JointBindingSystemTest { + + @Test + void jointBindingRejectsEndpointBodiesFromDifferentBackendWithSameSpaceHandle() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + proxy.registerSystem(new BodyBindingSystem()); + proxy.registerSystem(new ColliderBindingSystem()); + proxy.registerSystem(new JointBindingSystem()); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("joint-binding-cross-backend-test")), + EmptyResourceStorage.get()); + try { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + BoundSpace jointSpace = addBoundSpace(store, uuid(1), new BackendId("test:joint-a")); + BoundSpace bodySpace = addBoundSpace(store, uuid(2), new BackendId("test:joint-b")); + UUID bodyAUuid = uuid(3); + UUID bodyBUuid = uuid(4); + Ref bodyARef = addBoundBody(store, bodySpace, bodyAUuid, 0.0f); + Ref bodyBRef = addBoundBody(store, bodySpace, bodyBUuid, 1.0f); + UUID jointUuid = uuid(5); + Ref jointRef = addJoint(store, + jointSpace.uuid(), + jointSpace.ref(), + bodyAUuid, + bodyARef, + bodyBUuid, + bodyBRef, + jointUuid); + + store.tick(0.0f); + + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + assertFalse(restore.isFailed()); + assertNull(runtime.getJointHandle(jointRef)); + assertEquals(0, jointSpace.runtime().jointCount(jointSpace.handle().value())); + assertEquals(1, + restore.getSoftSkipsByReason() + .getInt("Joint endpoints are not in the joint space: " + jointUuid)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Nonnull + private static BoundSpace addBoundSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull BackendId backendId) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + FakePhysicsBackendRuntime runtime = (FakePhysicsBackendRuntime) + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(runtime.createSpace(new SpaceId(42))); + PhysicsRuntimeResource runtimeResource = store.getResource( + PhysicsRuntimeResource.getResourceType()); + runtimeResource.putRuntime(backendId, runtime); + runtimeResource.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtimeResource.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); + return new BoundSpace(spaceUuid, backendId, spaceRef, spaceHandle, runtime); + } + + @Nonnull + private static Ref addBoundBody(@Nonnull Store store, + @Nonnull BoundSpace space, + @Nonnull UUID bodyUuid, + float positionX) { + BodyComponent body = new BodyComponent(space.uuid()); + body.setSpaceRef(space.ref()); + Ref bodyRef = store.addEntity(PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false), + null, + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.5f, 0.1f), + new CollisionFilterComponent(0x01, 0x02)), + AddReason.SPAWN); + assertNotNull(bodyRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(bodyUuid, bodyRef); + store.getExternalData().putRefForUUID(bodyUuid, bodyRef); + long bodyId = space.runtime().createBody(space.handle().value(), + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.axisCode(PhysicsAxis.Y), + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + positionX, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); + PhysicsRuntimeResource runtimeResource = store.getResource( + PhysicsRuntimeResource.getResourceType()); + runtimeResource.putBodyHandle(bodyRef, space.ref(), space.handle(), bodyHandle); + runtimeResource.putBodySnapshotMetadata(space.backendId(), + space.handle(), + bodyHandle, + bodyUuid, + bodyRef, + space.uuid()); + runtimeResource.putBodyHitMetadata(space.backendId(), + space.handle(), + bodyHandle, + bodyUuid, + bodyRef, + PhysicsBodyType.DYNAMIC, + ShapeType.BOX); + return bodyRef; + } + + @Nonnull + private static Ref addJoint(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull UUID bodyAUuid, + @Nonnull Ref bodyARef, + @Nonnull UUID bodyBUuid, + @Nonnull Ref bodyBRef, + @Nonnull UUID jointUuid) { + JointComponent joint = new JointComponent(); + joint.setSpaceUuid(spaceUuid); + joint.setSpaceRef(spaceRef); + joint.setBodyAUuid(bodyAUuid); + joint.setBodyARef(bodyARef); + joint.setBodyBUuid(bodyBUuid); + joint.setBodyBRef(bodyBRef); + joint.setType(JointType.FIXED); + joint.setAnchorA(new Vector3f()); + joint.setAnchorB(new Vector3f()); + joint.setAxis(new Vector3f(0.0f, 1.0f, 0.0f)); + joint.setEnabled(true); + Ref jointRef = store.addEntity(PhysicsEntities.jointHolder(store, + jointUuid, + joint), + AddReason.SPAWN); + assertNotNull(jointRef); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + identity.putUuid(jointUuid, jointRef); + store.getExternalData().putRefForUUID(jointUuid, jointRef); + return jointRef; + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private record BoundSpace(@Nonnull UUID uuid, + @Nonnull BackendId backendId, + @Nonnull Ref ref, + @Nonnull BackendSpaceHandle handle, + @Nonnull FakePhysicsBackendRuntime runtime) { + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java index e4710c09..f9012372 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java @@ -144,11 +144,11 @@ private static BoundSpace addBoundSpace(@Nonnull Store store, PhysicsRuntimeResource runtimeResource = store.getResource( PhysicsRuntimeResource.getResourceType()); runtimeResource.putRuntime(backendId, runtime); - runtimeResource.putSpaceBinding(spaceUuid, spaceRef, backendId, spaceHandle); - identity.putSpaceHandle(spaceHandle, spaceRef); + runtimeResource.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtimeResource.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .putSpace(new SpaceId(42), spaceUuid); - return new BoundSpace(spaceRef, runtime, spaceHandle, backendId); + return new BoundSpace(spaceUuid, spaceRef, runtime, spaceHandle, backendId); } @Nonnull @@ -209,9 +209,20 @@ private static void bindBody(@Nonnull Store store, 1.0f); BackendBodyHandle handle = new BackendBodyHandle(bodyId); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - runtime.putBodyHandle(bodyUuid, bodyRef, uuid(1), space.handle(), handle); - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .putBodyHandle(handle, bodyRef); + runtime.putBodyHandle(bodyRef, space.ref(), space.handle(), handle); + runtime.putBodySnapshotMetadata(space.backendId(), + space.handle(), + handle, + bodyUuid, + bodyRef, + space.uuid()); + runtime.putBodyHitMetadata(space.backendId(), + space.handle(), + handle, + bodyUuid, + bodyRef, + PhysicsBodyType.DYNAMIC, + ShapeType.BOX); } private static void publishCopiedState(@Nonnull Store store, @@ -273,7 +284,8 @@ private static void markCurrentThreadAsWorldThread(@Nonnull Store } } - private record BoundSpace(@Nonnull Ref ref, + private record BoundSpace(@Nonnull UUID uuid, + @Nonnull Ref ref, @Nonnull PhysicsBackendRuntime runtime, @Nonnull BackendSpaceHandle handle, @Nonnull BackendId backendId) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java new file mode 100644 index 00000000..b4271465 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java @@ -0,0 +1,146 @@ +package dev.hytalemodding.impulse.core.internal.systems.binding; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class SpaceBindingSystemTest { + + @Test + void boundSpaceBackendIdMutationFailsRestoreInsteadOfSilentlyKeepingOldBinding() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("space-backend-mutation-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000071"); + BackendId originalBackendId = new BackendId("test:space-backend-original"); + BackendId mutatedBackendId = new BackendId("test:space-backend-mutated"); + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(originalBackendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + FakePhysicsBackendRuntime backendRuntime = (FakePhysicsBackendRuntime) + new FakePhysicsBackendRuntimeProvider(originalBackendId, false, false) + .createRuntime(); + BackendSpaceHandle spaceHandle = + new BackendSpaceHandle(backendRuntime.createSpace(new SpaceId(72))); + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + runtime.putRuntime(originalBackendId, backendRuntime); + runtime.putSpaceHandle(spaceRef, originalBackendId, spaceHandle); + runtime.putSpaceMetadata(originalBackendId, spaceHandle, spaceUuid, spaceRef); + + SpaceComponent component = store.getComponent(spaceRef, SpaceComponent.getComponentType()); + assertNotNull(component); + component.setBackendId(mutatedBackendId); + + runSpaceBindingSystem(store); + + PhysicsRestoreStatusResource restore = + store.getResource(PhysicsRestoreStatusResource.getResourceType()); + assertTrue(restore.isFailed()); + assertEquals(originalBackendId, runtime.getSpaceBackendId(spaceRef)); + assertEquals(spaceHandle, runtime.getSpaceHandle(spaceRef)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + private static void runSpaceBindingSystem(@Nonnull Store store) { + try { + SpaceBindingSystem system = new SpaceBindingSystem(); + Method bindChunk = SpaceBindingSystem.class.getDeclaredMethod("bindChunk", + PhysicsRuntimeResource.class, + PhysicsSpaceCompatibilityIndexResource.class, + PhysicsIdentityIndexResource.class, + PhysicsRestoreStatusResource.class, + PhysicsStepMode.class, + ArchetypeChunk.class); + bindChunk.setAccessible(true); + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + PhysicsIdentityIndexResource identity = store.getResource( + PhysicsIdentityIndexResource.getResourceType()); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + PhysicsStepMode stepMode = + store.getResource(PhysicsWorldSettingsResource.getResourceType()) + .getSettings() + .getStepMode(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> invoke(bindChunk, + runtime, + compatibility, + identity, + restore, + stepMode, + chunk); + store.forEachChunk(system.getQuery(), collector); + } catch (NoSuchMethodException exception) { + throw new AssertionError("Could not run SpaceBindingSystem", exception); + } + } + + private static void invoke(@Nonnull Method method, @Nonnull Object... arguments) { + try { + method.invoke(null, arguments); + } catch (IllegalAccessException exception) { + throw new AssertionError(exception); + } catch (InvocationTargetException exception) { + Throwable cause = exception.getCause(); + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + if (cause instanceof Error error) { + throw error; + } + throw new AssertionError(cause); + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java index 9d040f4d..122936e5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java @@ -82,11 +82,10 @@ private static void bindSpace(@Nonnull Store store, store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .putSpace(new SpaceId(77), spaceUuid); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(7700); runtime.putRuntime(backendId, recordingRuntime.proxy()); - runtime.putSpaceBinding(spaceUuid, - spaceRef, - backendId, - new BackendSpaceHandle(7700)); + runtime.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); } private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { From ede2e1b39e6d8e4bc2b4264b987ce2e0d0b3cc9c Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 18:51:52 +0200 Subject: [PATCH 490/534] feat(runtime): reduce physics memory footprint Signed-off-by: Blovien --- .../api/runtime/PhysicsBackendRuntime.java | 6 +- .../legacy/LegacyPhysicsBackendRuntime.java | 19 + ...csChunkCollisionMutationQueueResource.java | 27 +- .../resources/PhysicsSnapshotResource.java | 237 ++- .../PhysicsStoreResourceIndexTest.java | 95 +- .../impulse/rapier/RapierBackendRuntime.java | 1374 +++++++++++++++++ .../rapier/RapierBackendRuntimeProvider.java | 4 +- .../src/main/rust/src/joint_exports.rs | 185 ++- .../src/main/rust/src/query_exports.rs | 5 +- .../src/main/rust/src/space_exports.rs | 24 +- .../RapierBackendRuntimeProviderTest.java | 388 +++++ 11 files changed, 2229 insertions(+), 135 deletions(-) create mode 100644 impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java create mode 100644 impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java index 00941c15..3e2cf236 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java @@ -9,12 +9,16 @@ /** * Store tick backend runtime port using backend-local numeric ids and primitive payloads. */ -public interface PhysicsBackendRuntime { +public interface PhysicsBackendRuntime extends AutoCloseable { int createSpace(@Nonnull SpaceId requestedId); void destroySpace(int spaceId); + @Override + default void close() { + } + void step(int spaceId, float dt); void setGravity(int spaceId, float x, float y, float z); diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java index 38fbcd25..289e4841 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java @@ -85,6 +85,25 @@ public void destroySpace(int spaceId) { } } + @Override + public void close() { + RuntimeException failure = null; + for (Integer spaceId : new ArrayList<>(spaces.keySet())) { + try { + destroySpace(spaceId); + } catch (RuntimeException exception) { + if (failure == null) { + failure = exception; + } else { + failure.addSuppressed(exception); + } + } + } + if (failure != null) { + throw failure; + } + } + @Override public void step(int spaceId, float dt) { requireSpace(spaceId).space.step(dt); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java index 536b55b1..981482a9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java @@ -5,11 +5,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; -import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; -import java.util.Queue; import java.util.function.Predicate; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -22,7 +21,8 @@ public final class PhysicsChunkCollisionMutationQueueResource implements Resourc @Nullable private static ResourceType resourceType; @Nonnull - private final Queue mutations = new ArrayDeque<>(); + private final LinkedHashMap mutations = + new LinkedHashMap<>(); private long lifecycleGeneration = PhysicsChunkLifecycle.generation(); private long settingsGeneration = PhysicsChunkSettingsIndexResource.INITIAL_GENERATION; @@ -30,8 +30,11 @@ public PhysicsChunkCollisionMutationQueueResource() { } public synchronized void enqueue(@Nonnull ChunkCollisionMutation mutation) { - mutations.add(Objects.requireNonNull(mutation, "mutation") - .stamped(lifecycleGeneration, settingsGeneration)); + ChunkCollisionMutation stamped = Objects.requireNonNull(mutation, "mutation") + .stamped(lifecycleGeneration, settingsGeneration); + MutationKey key = new MutationKey(stamped.spaceUuid(), stamped.sourceKey()); + mutations.remove(key); + mutations.put(key, stamped); } public synchronized void updateStamp(long lifecycleGeneration, long settingsGeneration) { @@ -41,11 +44,8 @@ public synchronized void updateStamp(long lifecycleGeneration, long settingsGene @Nonnull public synchronized List drain() { - List drained = new ArrayList<>(mutations.size()); - ChunkCollisionMutation mutation; - while ((mutation = mutations.poll()) != null) { - drained.add(mutation); - } + List drained = new ArrayList<>(mutations.values()); + mutations.clear(); return drained; } @@ -56,7 +56,7 @@ public synchronized int size() { public synchronized int removeIf(@Nonnull Predicate predicate) { Objects.requireNonNull(predicate, "predicate"); int before = mutations.size(); - mutations.removeIf(predicate); + mutations.entrySet().removeIf(entry -> predicate.test(entry.getValue())); return before - mutations.size(); } @@ -68,7 +68,7 @@ public synchronized void clear() { @Override public synchronized PhysicsChunkCollisionMutationQueueResource clone() { PhysicsChunkCollisionMutationQueueResource copy = new PhysicsChunkCollisionMutationQueueResource(); - copy.mutations.addAll(mutations); + copy.mutations.putAll(mutations); copy.lifecycleGeneration = lifecycleGeneration; copy.settingsGeneration = settingsGeneration; return copy; @@ -87,4 +87,7 @@ public static void setResourceType( public static void clearResourceType() { resourceType = null; } + + private record MutationKey(@Nonnull java.util.UUID spaceUuid, @Nonnull String sourceKey) { + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java index 4100567f..c008614b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java @@ -4,15 +4,15 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; @@ -36,35 +36,20 @@ public PhysicsSnapshotFrame getLatestFrame() { @Nullable public PhysicsBodySnapshot getBody(@Nonnull UUID bodyUuid) { - return snapshot.bodiesByUuid().get(bodyUuid); + return snapshot.body(Objects.requireNonNull(bodyUuid, "bodyUuid")); } public boolean containsBody(@Nonnull UUID bodyUuid) { - return snapshot.bodiesByUuid().containsKey(Objects.requireNonNull(bodyUuid, "bodyUuid")); + return snapshot.containsBody(Objects.requireNonNull(bodyUuid, "bodyUuid")); } @Nullable public PhysicsBodySnapshot getBody(@Nonnull Ref bodyRef) { - PhysicsBodySnapshot body = snapshot.bodiesByRowIndex() - .get(Objects.requireNonNull(bodyRef, "bodyRef").getIndex()); - return body != null && sameRef(body.bodyRef(), bodyRef) ? body : null; + return snapshot.body(Objects.requireNonNull(bodyRef, "bodyRef")); } public void publish(@Nonnull PhysicsSnapshotFrame frame) { - int bodyCount = frame.bodies().size(); - Map bodiesByUuid = new Object2ObjectOpenHashMap<>(bodyCount); - Int2ObjectOpenHashMap bodiesByRowIndex = - new Int2ObjectOpenHashMap<>(bodyCount); - for (PhysicsBodySnapshot body : frame.bodies()) { - bodiesByUuid.put(body.bodyUuid(), body); - Ref bodyRef = body.bodyRef(); - if (bodyRef != null) { - bodiesByRowIndex.put(bodyRef.getIndex(), body); - } - } - snapshot = new PublishedSnapshot(frame, - bodiesByUuid, - bodiesByRowIndex); + snapshot = PublishedSnapshot.fromFrame(Objects.requireNonNull(frame, "frame")); } public void removeBody(@Nonnull UUID bodyUuid) { @@ -81,7 +66,7 @@ public void removeBodies(@Nonnull Collection bodyUuids) { ObjectOpenHashSet removedBodyUuids = new ObjectOpenHashSet<>(bodyUuids.size()); for (UUID bodyUuid : bodyUuids) { Objects.requireNonNull(bodyUuid, "bodyUuid"); - if (current.bodiesByUuid().containsKey(bodyUuid)) { + if (current.containsBody(bodyUuid)) { removedBodyUuids.add(bodyUuid); } } @@ -98,28 +83,18 @@ public void clear() { @Nonnull private static PublishedSnapshot withoutBodies(@Nonnull PublishedSnapshot current, @Nonnull ObjectOpenHashSet bodyUuids) { - int bodyCount = Math.max(0, current.frame().bodies().size() - bodyUuids.size()); + int bodyCount = Math.max(0, current.bodyCount() - bodyUuids.size()); List bodies = new ArrayList<>(bodyCount); - Map bodiesByUuid = new Object2ObjectOpenHashMap<>(bodyCount); - Int2ObjectOpenHashMap bodiesByRowIndex = - new Int2ObjectOpenHashMap<>(bodyCount); - for (PhysicsBodySnapshot body : current.frame().bodies()) { + for (int index = 0; index < current.bodyCount(); index++) { + PhysicsBodySnapshot body = current.body(index); if (bodyUuids.contains(body.bodyUuid())) { continue; } bodies.add(body); - bodiesByUuid.put(body.bodyUuid(), body); - Ref bodyRef = body.bodyRef(); - if (bodyRef != null) { - bodiesByRowIndex.put(bodyRef.getIndex(), body); - } } - return new PublishedSnapshot( - new PhysicsSnapshotFrame(current.frame().sequence(), - current.frame().dt(), - bodies), - bodiesByUuid, - bodiesByRowIndex); + return PublishedSnapshot.fromFrame(new PhysicsSnapshotFrame(current.sequence(), + current.dt(), + bodies)); } @Nonnull @@ -135,15 +110,183 @@ public static ResourceType getResourceTyp return PhysicsResourceTypes.snapshotResourceType(); } - private record PublishedSnapshot( - @Nonnull PhysicsSnapshotFrame frame, - @Nonnull Map bodiesByUuid, - @Nonnull Int2ObjectOpenHashMap bodiesByRowIndex) { + private record PublishedSnapshot(long sequence, + float dt, + @Nonnull Ref[] bodyRefs, + @Nonnull UUID[] bodyUuids, + @Nonnull UUID[] spaceUuids, + @Nonnull PhysicsBodyType[] bodyTypes, + @Nonnull float[] values, + @Nonnull boolean[] sleeping, + @Nonnull Object2IntOpenHashMap bodiesByUuid, + @Nonnull Int2IntOpenHashMap bodiesByRowIndex) { + + private static final int POSITION_X = 0; + private static final int POSITION_Y = 1; + private static final int POSITION_Z = 2; + private static final int ROTATION_X = 3; + private static final int ROTATION_Y = 4; + private static final int ROTATION_Z = 5; + private static final int ROTATION_W = 6; + private static final int LINEAR_VELOCITY_X = 7; + private static final int LINEAR_VELOCITY_Y = 8; + private static final int LINEAR_VELOCITY_Z = 9; + private static final int ANGULAR_VELOCITY_X = 10; + private static final int ANGULAR_VELOCITY_Y = 11; + private static final int ANGULAR_VELOCITY_Z = 12; + private static final int CENTER_OF_MASS_OFFSET_Y = 13; + private static final int FLOAT_STRIDE = 14; + + private static final PublishedSnapshot EMPTY = empty(); + + @Nonnull + private static PublishedSnapshot empty() { + return new PublishedSnapshot(PhysicsSnapshotFrame.EMPTY.sequence(), + PhysicsSnapshotFrame.EMPTY.dt(), + emptyRefs(), + new UUID[0], + new UUID[0], + new PhysicsBodyType[0], + new float[0], + new boolean[0], + uuidIndex(0), + rowIndex(0)); + } + + @SuppressWarnings("unchecked") + @Nonnull + private static Ref[] emptyRefs() { + return (Ref[]) new Ref[0]; + } + + @SuppressWarnings("unchecked") + @Nonnull + private static PublishedSnapshot fromFrame(@Nonnull PhysicsSnapshotFrame frame) { + int bodyCount = frame.bodies().size(); + Ref[] bodyRefs = (Ref[]) new Ref[bodyCount]; + UUID[] bodyUuids = new UUID[bodyCount]; + UUID[] spaceUuids = new UUID[bodyCount]; + PhysicsBodyType[] bodyTypes = new PhysicsBodyType[bodyCount]; + float[] values = new float[bodyCount * FLOAT_STRIDE]; + boolean[] sleeping = new boolean[bodyCount]; + Object2IntOpenHashMap bodiesByUuid = uuidIndex(bodyCount); + Int2IntOpenHashMap bodiesByRowIndex = rowIndex(bodyCount); + for (int index = 0; index < bodyCount; index++) { + PhysicsBodySnapshot body = frame.bodies().get(index); + bodyRefs[index] = body.bodyRef(); + bodyUuids[index] = body.bodyUuid(); + spaceUuids[index] = body.spaceUuid(); + bodyTypes[index] = body.bodyType(); + sleeping[index] = body.sleeping(); + values[index * FLOAT_STRIDE + POSITION_X] = body.positionX(); + values[index * FLOAT_STRIDE + POSITION_Y] = body.positionY(); + values[index * FLOAT_STRIDE + POSITION_Z] = body.positionZ(); + values[index * FLOAT_STRIDE + ROTATION_X] = body.rotationX(); + values[index * FLOAT_STRIDE + ROTATION_Y] = body.rotationY(); + values[index * FLOAT_STRIDE + ROTATION_Z] = body.rotationZ(); + values[index * FLOAT_STRIDE + ROTATION_W] = body.rotationW(); + values[index * FLOAT_STRIDE + LINEAR_VELOCITY_X] = body.linearVelocityX(); + values[index * FLOAT_STRIDE + LINEAR_VELOCITY_Y] = body.linearVelocityY(); + values[index * FLOAT_STRIDE + LINEAR_VELOCITY_Z] = body.linearVelocityZ(); + values[index * FLOAT_STRIDE + ANGULAR_VELOCITY_X] = body.angularVelocityX(); + values[index * FLOAT_STRIDE + ANGULAR_VELOCITY_Y] = body.angularVelocityY(); + values[index * FLOAT_STRIDE + ANGULAR_VELOCITY_Z] = body.angularVelocityZ(); + values[index * FLOAT_STRIDE + CENTER_OF_MASS_OFFSET_Y] = + body.centerOfMassOffsetY(); + bodiesByUuid.put(body.bodyUuid(), index); + Ref bodyRef = body.bodyRef(); + if (bodyRef != null) { + bodiesByRowIndex.put(bodyRef.getIndex(), index); + } + } + return new PublishedSnapshot(frame.sequence(), + frame.dt(), + bodyRefs, + bodyUuids, + spaceUuids, + bodyTypes, + values, + sleeping, + bodiesByUuid, + bodiesByRowIndex); + } + + @Nonnull + private static Object2IntOpenHashMap uuidIndex(int expected) { + Object2IntOpenHashMap index = new Object2IntOpenHashMap<>(expected); + index.defaultReturnValue(-1); + return index; + } + + @Nonnull + private static Int2IntOpenHashMap rowIndex(int expected) { + Int2IntOpenHashMap index = new Int2IntOpenHashMap(expected); + index.defaultReturnValue(-1); + return index; + } + + @Nonnull + private PhysicsSnapshotFrame frame() { + if (bodyCount() == 0 && sequence == PhysicsSnapshotFrame.EMPTY.sequence() + && dt == PhysicsSnapshotFrame.EMPTY.dt()) { + return PhysicsSnapshotFrame.EMPTY; + } + List bodies = new ArrayList<>(bodyCount()); + for (int index = 0; index < bodyCount(); index++) { + bodies.add(body(index)); + } + return new PhysicsSnapshotFrame(sequence, dt, bodies); + } + + private int bodyCount() { + return bodyUuids.length; + } + + private boolean containsBody(@Nonnull UUID bodyUuid) { + return bodiesByUuid.getInt(bodyUuid) >= 0; + } + + @Nullable + private PhysicsBodySnapshot body(@Nonnull UUID bodyUuid) { + int index = bodiesByUuid.getInt(bodyUuid); + return index >= 0 ? body(index) : null; + } + + @Nullable + private PhysicsBodySnapshot body(@Nonnull Ref bodyRef) { + int index = bodiesByRowIndex.get(bodyRef.getIndex()); + if (index < 0 || !sameRef(bodyRefs[index], bodyRef)) { + return null; + } + return body(index); + } + + @Nonnull + private PhysicsBodySnapshot body(int index) { + return PhysicsBodySnapshot.of(bodyRefs[index], + bodyUuids[index], + spaceUuids[index], + bodyTypes[index], + value(index, POSITION_X), + value(index, POSITION_Y), + value(index, POSITION_Z), + value(index, ROTATION_X), + value(index, ROTATION_Y), + value(index, ROTATION_Z), + value(index, ROTATION_W), + value(index, LINEAR_VELOCITY_X), + value(index, LINEAR_VELOCITY_Y), + value(index, LINEAR_VELOCITY_Z), + value(index, ANGULAR_VELOCITY_X), + value(index, ANGULAR_VELOCITY_Y), + value(index, ANGULAR_VELOCITY_Z), + value(index, CENTER_OF_MASS_OFFSET_Y), + sleeping[index]); + } - private static final PublishedSnapshot EMPTY = - new PublishedSnapshot(PhysicsSnapshotFrame.EMPTY, - Map.of(), - new Int2ObjectOpenHashMap<>()); + private float value(int bodyIndex, int valueIndex) { + return values[bodyIndex * FLOAT_STRIDE + valueIndex]; + } } private static boolean sameRef(@Nullable Ref first, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index 3697c307..fe68da89 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.internal.resources; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -12,9 +13,12 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -327,8 +331,9 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { resource.publish(frame); - assertEquals(frame, resource.getLatestFrame()); - assertEquals(body, resource.getBody(bodyUuid)); + assertNotSame(frame, resource.getLatestFrame()); + assertSnapshotEquals(body, resource.getBody(bodyUuid)); + assertNotSame(body, resource.getBody(bodyUuid)); assertNull(resource.getBody(UUID.randomUUID())); resource.clear(); @@ -358,9 +363,50 @@ void snapshotResourceRemovesMultipleBodiesInOneBatch() { assertNull(resource.getBody(firstBodyRef)); assertNull(resource.getBody(secondBodyUuid)); assertNull(resource.getBody(secondBodyRef)); - assertEquals(retained, resource.getBody(retainedBodyUuid)); - assertEquals(retained, resource.getBody(retainedBodyRef)); - assertEquals(List.of(retained), resource.getLatestFrame().bodies()); + assertSnapshotEquals(retained, resource.getBody(retainedBodyUuid)); + assertSnapshotEquals(retained, resource.getBody(retainedBodyRef)); + List retainedBodies = resource.getLatestFrame().bodies(); + assertEquals(1, retainedBodies.size()); + assertSnapshotEquals(retained, retainedBodies.getFirst()); + } + + @Test + void chunkCollisionQueueKeepsOnlyLatestMutationPerSourceBeforeDrain() { + PhysicsChunkCollisionMutationQueueResource queue = + new PhysicsChunkCollisionMutationQueueResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000025"); + ChunkCollisionPayload firstPayload = chunkPayload(1.0); + ChunkCollisionPayload secondPayload = chunkPayload(2.0); + + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + "0:1:2", + 0, + 1, + 2, + "chunk-collision/first", + firstPayload)); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + "3:4:5", + 3, + 4, + 5, + "chunk-collision/other", + firstPayload)); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + "0:1:2", + 0, + 1, + 2, + "chunk-collision/second", + secondPayload)); + + assertEquals(2, queue.size()); + List drained = queue.drain(); + + assertEquals(2, drained.size()); + assertEquals("3:4:5", drained.get(0).sourceKey()); + assertEquals("0:1:2", drained.get(1).sourceKey()); + assertSame(secondPayload, drained.get(1).payload()); } private static final class TestRef extends Ref { @@ -389,4 +435,43 @@ private static PhysicsBodySnapshot snapshot(Ref bodyRef, 0.0f, false); } + + private static ChunkCollisionPayload chunkPayload(double centerX) { + return new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(new ChunkCollisionPayload.BoxPayload(centerX, + 0.0, + 0.0, + 0.5, + 0.5, + 0.5)), + List.of(), + false, + List.of()); + } + + private static void assertSnapshotEquals(PhysicsBodySnapshot expected, + PhysicsBodySnapshot actual) { + assertEquals(expected.bodyRef(), actual.bodyRef()); + assertEquals(expected.bodyUuid(), actual.bodyUuid()); + assertEquals(expected.spaceUuid(), actual.spaceUuid()); + assertEquals(expected.bodyType(), actual.bodyType()); + assertEquals(expected.positionX(), actual.positionX()); + assertEquals(expected.positionY(), actual.positionY()); + assertEquals(expected.positionZ(), actual.positionZ()); + assertEquals(expected.rotationX(), actual.rotationX()); + assertEquals(expected.rotationY(), actual.rotationY()); + assertEquals(expected.rotationZ(), actual.rotationZ()); + assertEquals(expected.rotationW(), actual.rotationW()); + assertEquals(expected.linearVelocityX(), actual.linearVelocityX()); + assertEquals(expected.linearVelocityY(), actual.linearVelocityY()); + assertEquals(expected.linearVelocityZ(), actual.linearVelocityZ()); + assertEquals(expected.angularVelocityX(), actual.angularVelocityX()); + assertEquals(expected.angularVelocityY(), actual.angularVelocityY()); + assertEquals(expected.angularVelocityZ(), actual.angularVelocityZ()); + assertEquals(expected.centerOfMassOffsetY(), actual.centerOfMassOffsetY()); + assertEquals(expected.sleeping(), actual.sleeping()); + } } diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java new file mode 100644 index 00000000..1381ed47 --- /dev/null +++ b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java @@ -0,0 +1,1374 @@ +package dev.hytalemodding.impulse.rapier; + +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; +import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; +import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; +import dev.hytalemodding.impulse.api.runtime.BackendBodyIdSource; +import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; +import dev.hytalemodding.impulse.api.runtime.BackendContactSink; +import dev.hytalemodding.impulse.api.runtime.BackendExtensionSettingsSource; +import dev.hytalemodding.impulse.api.runtime.BackendJointType; +import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeStatsSink; +import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; +import dev.hytalemodding.impulse.api.runtime.BackendVec3Sink; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import java.lang.ref.Cleaner; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class RapierBackendRuntime implements PhysicsBackendRuntime { + + private static final Cleaner CLEANER = Cleaner.create(); + private static final float DEFAULT_DYNAMIC_MASS = 1.0f; + private static final int DEFAULT_SOLVER_ITERATIONS = 4; + private static final int DEFAULT_INTERNAL_PGS_ITERATIONS = 1; + private static final int DEFAULT_STABILIZATION_ITERATIONS = 1; + private static final int DEFAULT_MIN_ISLAND_SIZE = 128; + private static final int BODY_SNAPSHOT_FLOATS = 16; + private static final int CONTACT_FLOATS = 15; + private static final int RAY_HIT_FLOATS = 10; + private static final int RUNTIME_STATS_VALUES = 10; + private static final int STEP_PHASE_STATS_VALUES = 6; + private static final PhysicsCapabilityId RAPIER_SOLVER_EXTENSION_ID = + new PhysicsCapabilityId("impulse:rapier_solver"); + private static final String INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; + private static final String MIN_ISLAND_SIZE = "minIslandSize"; + + private final Map spaces = new HashMap<>(); + private long nextBodyId = 1L; + private long nextJointId = 1L; + + @Override + public int createSpace(@Nonnull SpaceId requestedId) { + Objects.requireNonNull(requestedId, "requestedId"); + int spaceId = requestedId.value(); + if (spaces.containsKey(spaceId)) { + throw new IllegalArgumentException("Physics space id=" + requestedId + + " is already registered"); + } + long nativeSpaceHandle = RapierNative.createSpaceNative(); + if (nativeSpaceHandle == 0L) { + throw new IllegalStateException("Rapier returned a null native space handle"); + } + spaces.put(spaceId, new SpaceState(spaceId, nativeSpaceHandle)); + return spaceId; + } + + @Override + public void destroySpace(int spaceId) { + SpaceState state = spaces.remove(spaceId); + if (state != null) { + state.close(); + } + } + + @Override + public void close() { + RuntimeException failure = null; + for (Integer spaceId : new ArrayList<>(spaces.keySet())) { + try { + destroySpace(spaceId); + } catch (RuntimeException exception) { + if (failure == null) { + failure = exception; + } else { + failure.addSuppressed(exception); + } + } + } + if (failure != null) { + throw failure; + } + } + + @Override + public void step(int spaceId, float dt) { + if (dt <= 0.0f) { + return; + } + SpaceState state = requireSpace(spaceId); + if (!RapierNative.stepNative(state.nativeSpaceHandle, dt)) { + throw new IllegalStateException("Rapier native step failed"); + } + } + + @Override + public void setGravity(int spaceId, float x, float y, float z) { + SpaceState state = requireSpace(spaceId); + RapierNative.setGravityNative(state.nativeSpaceHandle, x, y, z); + } + + @Override + public void getGravity(int spaceId, @Nonnull BackendVec3Sink sink) { + Objects.requireNonNull(sink, "sink"); + SpaceState state = requireSpace(spaceId); + float[] out = new float[3]; + RapierNative.getGravityNative(state.nativeSpaceHandle, out); + sink.accept(out[0], out[1], out[2]); + } + + @Override + public long createBody(int spaceId, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + SpaceState state = requireSpace(spaceId); + ShapeType shapeType = BackendRuntimeCodes.shapeType(shapeTypeCode); + if (shapeType == ShapeType.VOXELS || shapeType == ShapeType.UNKNOWN) { + throw new IllegalArgumentException("Unsupported Rapier body shape " + shapeType); + } + PhysicsAxis axis = BackendRuntimeCodes.axis(axisCode); + PhysicsBodyType bodyType = BackendRuntimeCodes.bodyType(bodyTypeCode); + float storedMass = adjustedMass(mass, bodyType); + int storedBodyTypeCode = adjustedBodyTypeCode(bodyType, storedMass); + BodyState body = BodyState.regular(nextBodyId++, + shapeTypeCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode, + centerOfMassOffsetY(shapeType, axis, halfExtentY, radius, halfHeight), + storedMass, + storedBodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW); + long handle = RapierNative.addBodyNative(state.nativeSpaceHandle, + shapeType.ordinal(), + body.halfExtentX, + body.halfExtentY, + body.halfExtentZ, + body.radius, + body.halfHeight, + axis.index(), + BackendRuntimeCodes.bodyType(body.bodyTypeCode).ordinal(), + body.mass, + body.positionX, + body.positionY, + body.positionZ, + body.rotationX, + body.rotationY, + body.rotationZ, + body.rotationW, + body.linearVelocityX, + body.linearVelocityY, + body.linearVelocityZ, + body.angularVelocityX, + body.angularVelocityY, + body.angularVelocityZ, + body.friction, + body.restitution, + body.linearDamping, + body.angularDamping, + body.sensor, + body.collisionGroup, + body.collisionMask, + body.continuousCollisionEnabled); + attachBody(state, body, handle); + return body.bodyId; + } + + @Override + public boolean supportsVoxelTerrain(int spaceId) { + requireSpace(spaceId); + return true; + } + + @Override + public long createVoxelTerrain(int spaceId, + float voxelSizeX, + float voxelSizeY, + float voxelSizeZ, + @Nonnull int[] voxelCoordinates, + float positionX, + float positionY, + float positionZ, + float friction, + float restitution, + int collisionGroup, + int collisionMask) { + Objects.requireNonNull(voxelCoordinates, "voxelCoordinates"); + SpaceState state = requireSpace(spaceId); + BodyState body = BodyState.voxels(nextBodyId++, + voxelSizeX, + voxelSizeY, + voxelSizeZ, + positionX, + positionY, + positionZ, + friction, + restitution, + collisionGroup, + collisionMask); + long handle = RapierNative.addVoxelTerrainNative(state.nativeSpaceHandle, + voxelSizeX, + voxelSizeY, + voxelSizeZ, + voxelCoordinates, + positionX, + positionY, + positionZ, + friction, + restitution, + collisionGroup, + collisionMask); + attachBody(state, body, handle); + return body.bodyId; + } + + @Override + public void combineVoxelTerrains(int spaceId, + long bodyAId, + long bodyBId, + int shiftX, + int shiftY, + int shiftZ) { + SpaceState state = requireSpace(spaceId); + BodyState bodyA = requireBody(state, bodyAId); + BodyState bodyB = requireBody(state, bodyBId); + if (bodyA == bodyB) { + throw new IllegalArgumentException("Cannot combine a voxel terrain body with itself"); + } + requireVoxelTerrain(bodyA); + requireVoxelTerrain(bodyB); + if (!RapierNative.combineVoxelTerrainNative(state.nativeSpaceHandle, + bodyA.nativeBodyHandle, + bodyB.nativeBodyHandle, + shiftX, + shiftY, + shiftZ)) { + throw new IllegalStateException("Rapier native voxel terrain combine failed"); + } + } + + @Override + public void removeBody(int spaceId, long bodyId) { + SpaceState state = requireSpace(spaceId); + BodyState body = state.bodiesById.get(bodyId); + if (body == null) { + return; + } + RapierNative.removeBodyNative(state.nativeSpaceHandle, body.nativeBodyHandle); + state.bodiesById.remove(bodyId); + state.bodyIdsByNativeHandle.remove(body.nativeBodyHandle); + removeAttachedJointState(state, bodyId); + } + + @Override + public int bodyCount(int spaceId) { + return requireSpace(spaceId).bodiesById.size(); + } + + @Override + public boolean containsBody(int spaceId, long bodyId) { + return requireSpace(spaceId).bodiesById.containsKey(bodyId); + } + + @Override + public boolean bodySnapshot(int spaceId, long bodyId, @Nonnull BackendBodySnapshotSink sink) { + Objects.requireNonNull(sink, "sink"); + SpaceState state = requireSpace(spaceId); + BodyState body = state.bodiesById.get(bodyId); + if (body == null) { + return false; + } + state.ensureSnapshotCapacity(1); + state.snapshotBodyHandles[0] = body.nativeBodyHandle; + int written = RapierNative.snapshotBodiesNative(state.nativeSpaceHandle, + state.snapshotBodyHandles, + 1, + state.snapshotBodyData); + if (written > 0) { + body.updateFromNative(state.snapshotBodyData, 0); + } + body.emit(sink); + return true; + } + + @Override + public void snapshotBodies(int spaceId, + @Nonnull BackendBodyIdSource bodyIds, + @Nonnull BackendBodySnapshotSink sink) { + Objects.requireNonNull(bodyIds, "bodyIds"); + Objects.requireNonNull(sink, "sink"); + SpaceState state = requireSpace(spaceId); + SnapshotSelection selection = new SnapshotSelection(state); + bodyIds.forEachBodyId(selection::add); + if (selection.count == 0) { + return; + } + int written = RapierNative.snapshotBodiesNative(state.nativeSpaceHandle, + state.snapshotBodyHandles, + selection.count, + state.snapshotBodyData); + int limit = Math.clamp(written, 0, selection.count); + for (int index = 0; index < limit; index++) { + BodyState body = selection.bodies[index]; + body.updateFromNative(state.snapshotBodyData, index * BODY_SNAPSHOT_FLOATS); + body.emit(sink); + } + for (int index = limit; index < selection.count; index++) { + selection.bodies[index].emit(sink); + } + } + + @Override + public void setBodyTransform(int spaceId, + long bodyId, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + NormalizedRotation rotation = normalizeRotation(rotationX, rotationY, rotationZ, rotationW); + RapierNative.setBodyPositionNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + positionX, + positionY, + positionZ); + body.setPosition(positionX, positionY, positionZ); + RapierNative.setBodyRotationNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + rotation.x, + rotation.y, + rotation.z, + rotation.w); + body.setRotation(rotation.x, rotation.y, rotation.z, rotation.w); + } + + @Override + public void setBodyPosition(int spaceId, long bodyId, float x, float y, float z) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodyPositionNative(state.nativeSpaceHandle, body.nativeBodyHandle, x, y, z); + body.setPosition(x, y, z); + } + + @Override + public void setBodyVelocity(int spaceId, + long bodyId, + float linearX, + float linearY, + float linearZ, + float angularX, + float angularY, + float angularZ) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodyLinearVelocityNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + linearX, + linearY, + linearZ); + body.linearVelocityX = linearX; + body.linearVelocityY = linearY; + body.linearVelocityZ = linearZ; + RapierNative.setBodyAngularVelocityNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + angularX, + angularY, + angularZ); + body.angularVelocityX = angularX; + body.angularVelocityY = angularY; + body.angularVelocityZ = angularZ; + } + + @Override + public void setBodyType(int spaceId, long bodyId, int bodyTypeCode) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + PhysicsBodyType bodyType = BackendRuntimeCodes.bodyType(bodyTypeCode); + float adjustedMass = adjustedMass(body.mass, bodyType); + if (Float.compare(adjustedMass, body.mass) != 0) { + RapierNative.setBodyMassNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + adjustedMass); + body.mass = adjustedMass; + } + int adjustedBodyTypeCode = adjustedBodyTypeCode(bodyType, adjustedMass); + RapierNative.setBodyTypeNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + BackendRuntimeCodes.bodyType(adjustedBodyTypeCode).ordinal()); + body.bodyTypeCode = adjustedBodyTypeCode; + } + + @Override + public void setBodyDamping(int spaceId, long bodyId, float linearDamping, float angularDamping) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodyDampingNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + linearDamping, + angularDamping); + body.linearDamping = linearDamping; + body.angularDamping = angularDamping; + } + + @Override + public void setBodyFriction(int spaceId, long bodyId, float friction) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodyFrictionNative(state.nativeSpaceHandle, body.nativeBodyHandle, friction); + body.friction = friction; + } + + @Override + public void setBodyRestitution(int spaceId, long bodyId, float restitution) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodyRestitutionNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + restitution); + body.restitution = restitution; + } + + @Override + public void setBodyCollisionFilter(int spaceId, long bodyId, int group, int mask) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodyCollisionFilterNative(state.nativeSpaceHandle, + body.nativeBodyHandle, + group, + mask); + body.collisionGroup = group; + body.collisionMask = mask; + } + + @Override + public void setBodySensor(int spaceId, long bodyId, boolean sensor) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodySensorNative(state.nativeSpaceHandle, body.nativeBodyHandle, sensor); + body.sensor = sensor; + } + + @Override + public void setBodyContinuousCollision(int spaceId, long bodyId, boolean enabled) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + RapierNative.setBodyCcdNative(state.nativeSpaceHandle, body.nativeBodyHandle, enabled); + body.continuousCollisionEnabled = enabled; + } + + @Override + public boolean isBodyContinuousCollisionEnabled(int spaceId, long bodyId) { + SpaceState state = requireSpace(spaceId); + BodyState body = requireBody(state, bodyId); + body.continuousCollisionEnabled = + RapierNative.isBodyCcdNative(state.nativeSpaceHandle, body.nativeBodyHandle); + return body.continuousCollisionEnabled; + } + + @Override + public void activateBody(int spaceId, long bodyId) { + SpaceState state = requireSpace(spaceId); + RapierNative.activateBodyNative(state.nativeSpaceHandle, + requireBody(state, bodyId).nativeBodyHandle); + } + + @Override + public void sleepBody(int spaceId, long bodyId) { + SpaceState state = requireSpace(spaceId); + RapierNative.sleepBodyNative(state.nativeSpaceHandle, + requireBody(state, bodyId).nativeBodyHandle); + } + + @Override + public void applyBodyImpulse(int spaceId, + long bodyId, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + SpaceState state = requireSpace(spaceId); + long handle = requireBody(state, bodyId).nativeBodyHandle; + if (torque) { + RapierNative.applyBodyTorqueImpulseNative(state.nativeSpaceHandle, handle, x, y, z); + } else if (hasOffset) { + RapierNative.applyBodyImpulseNative(state.nativeSpaceHandle, + handle, + x, + y, + z, + offsetX, + offsetY, + offsetZ); + } else { + RapierNative.applyBodyCentralImpulseNative(state.nativeSpaceHandle, handle, x, y, z); + } + } + + @Override + public void applyBodyForce(int spaceId, + long bodyId, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + SpaceState state = requireSpace(spaceId); + long handle = requireBody(state, bodyId).nativeBodyHandle; + if (torque) { + RapierNative.applyBodyTorqueNative(state.nativeSpaceHandle, handle, x, y, z); + } else if (hasOffset) { + RapierNative.applyBodyForceNative(state.nativeSpaceHandle, + handle, + x, + y, + z, + offsetX, + offsetY, + offsetZ); + } else { + RapierNative.applyBodyCentralForceNative(state.nativeSpaceHandle, handle, x, y, z); + } + } + + @Override + public long createJoint(int spaceId, + int jointTypeCode, + long bodyAId, + long bodyBId, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisX, + float axisY, + float axisZ, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + boolean motorEnabled, + float motorTargetVelocity, + float motorMaxForce) { + SpaceState state = requireSpace(spaceId); + BodyState bodyA = requireBody(state, bodyAId); + BodyState bodyB = requireBody(state, bodyBId); + NormalizedAxis axis = normalizeAxis(axisX, axisY, axisZ); + long handle = RapierNative.addJointNative(state.nativeSpaceHandle, + BackendRuntimeCodes.jointType(jointTypeCode).ordinal(), + bodyA.nativeBodyHandle, + bodyB.nativeBodyHandle, + anchorAX, + anchorAY, + anchorAZ, + anchorBX, + anchorBY, + anchorBZ, + axis.x, + axis.y, + axis.z, + restLength, + stiffness, + damping); + if (handle == 0L) { + throw new IllegalStateException("Rapier returned a null native joint handle"); + } + BackendJointType type = BackendRuntimeCodes.jointType(jointTypeCode); + if (type == BackendJointType.HINGE || type == BackendJointType.SLIDER) { + try { + RapierNative.setJointLimitsNative(state.nativeSpaceHandle, + handle, + lowerLimit, + upperLimit); + RapierNative.setJointMotorNative(state.nativeSpaceHandle, + handle, + motorEnabled, + motorTargetVelocity, + motorMaxForce); + } catch (RuntimeException exception) { + try { + RapierNative.removeJointNative(state.nativeSpaceHandle, handle); + } catch (RuntimeException cleanupFailure) { + exception.addSuppressed(cleanupFailure); + } + throw exception; + } + } + long jointId = nextJointId++; + JointState joint = new JointState(jointId, + handle, + jointTypeCode, + bodyAId, + bodyBId); + state.jointsById.put(jointId, joint); + return jointId; + } + + @Override + public void removeJoint(int spaceId, long jointId) { + SpaceState state = requireSpace(spaceId); + JointState joint = state.jointsById.get(jointId); + if (joint != null) { + RapierNative.removeJointNative(state.nativeSpaceHandle, joint.nativeJointHandle); + state.jointsById.remove(jointId); + } + } + + @Override + public int jointCount(int spaceId) { + return requireSpace(spaceId).jointsById.size(); + } + + @Override + public int jointType(int spaceId, long jointId) { + return requireJoint(requireSpace(spaceId), jointId).jointTypeCode; + } + + @Override + public long jointBodyA(int spaceId, long jointId) { + return requireJoint(requireSpace(spaceId), jointId).bodyAId; + } + + @Override + public long jointBodyB(int spaceId, long jointId) { + return requireJoint(requireSpace(spaceId), jointId).bodyBId; + } + + @Override + public boolean raycastClosest(int spaceId, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + @Nonnull BackendRayHitSink sink) { + SpaceState state = requireSpace(spaceId); + float[] raw = RapierNative.raycastAllNative(state.nativeSpaceHandle, + fromX, + fromY, + fromZ, + toX, + toY, + toZ); + int closestOffset = -1; + float closestFraction = Float.POSITIVE_INFINITY; + for (int offset = 0; raw != null && offset + RAY_HIT_FLOATS <= raw.length; + offset += RAY_HIT_FLOATS) { + long bodyHandle = rawBitFloatPairToLong(raw[offset], raw[offset + 1]); + if (!state.bodyIdsByNativeHandle.containsKey(bodyHandle)) { + continue; + } + float fraction = raw[offset + 8]; + if (fraction < closestFraction) { + closestFraction = fraction; + closestOffset = offset; + } + } + if (closestOffset < 0) { + return false; + } + emitRayHit(state, raw, closestOffset, sink); + return true; + } + + @Override + public int raycastAll(int spaceId, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + @Nonnull BackendRayHitSink sink) { + SpaceState state = requireSpace(spaceId); + float[] raw = RapierNative.raycastAllNative(state.nativeSpaceHandle, + fromX, + fromY, + fromZ, + toX, + toY, + toZ); + int hits = 0; + for (int offset = 0; raw != null && offset + RAY_HIT_FLOATS <= raw.length; + offset += RAY_HIT_FLOATS) { + if (emitRayHit(state, raw, offset, sink)) { + hits++; + } + } + return hits; + } + + @Override + public int contacts(int spaceId, @Nonnull BackendContactSink sink) { + SpaceState state = requireSpace(spaceId); + return emitContacts(state, RapierNative.getContactsNative(state.nativeSpaceHandle), sink); + } + + @Override + public int contacts(int spaceId, int maxContacts, @Nonnull BackendContactSink sink) { + if (maxContacts <= 0) { + return 0; + } + SpaceState state = requireSpace(spaceId); + return emitContacts(state, + RapierNative.getContactsLimitedNative(state.nativeSpaceHandle, maxContacts), + sink); + } + + @Override + public int contactCount(int spaceId) { + SpaceState state = requireSpace(spaceId); + float[] raw = RapierNative.getContactsNative(state.nativeSpaceHandle); + return raw != null ? raw.length / CONTACT_FLOATS : 0; + } + + @Override + public void runtimeStats(int spaceId, @Nonnull BackendRuntimeStatsSink sink) { + SpaceState state = requireSpace(spaceId); + int[] values = RapierNative.getRuntimeStatsNative(state.nativeSpaceHandle); + if (values == null || values.length < RUNTIME_STATS_VALUES) { + sink.accept(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false); + return; + } + sink.accept(values[0], + values[1], + values[2], + values[3], + values[4], + values[5], + values[6], + values[7], + values[8], + values[9], + true); + } + + @Override + public void resetStepPhaseStats(int spaceId) { + RapierNative.resetStepPhaseStatsNative(requireSpace(spaceId).nativeSpaceHandle); + } + + @Override + public void stepPhaseStats(int spaceId, @Nonnull BackendStepPhaseStatsSink sink) { + SpaceState state = requireSpace(spaceId); + long[] values = RapierNative.getStepPhaseStatsNative(state.nativeSpaceHandle); + if (values == null || values.length < STEP_PHASE_STATS_VALUES) { + sink.accept(0L, 0L, 0L, 0L, 0L, 0L, false); + return; + } + sink.accept(values[0], values[1], values[2], values[3], values[4], values[5], true); + } + + @Override + public boolean supportsContinuousCollision(int spaceId) { + requireSpace(spaceId); + return true; + } + + @Override + public boolean supportsSolverTuning(int spaceId) { + requireSpace(spaceId); + return true; + } + + @Override + public boolean supportsActivationTuning(int spaceId) { + requireSpace(spaceId); + return true; + } + + @Override + public void applySolverTuning(int spaceId, @Nonnull PhysicsSolverTuning tuning) { + Objects.requireNonNull(tuning, "tuning"); + SpaceState state = requireSpace(spaceId); + state.applySolverTuning(tuning.solverIterations(), + state.internalPgsIterations, + tuning.stabilizationIterations(), + state.minIslandSize); + state.solverIterations = tuning.solverIterations(); + state.stabilizationIterations = tuning.stabilizationIterations(); + } + + @Override + public void applyActivationTuning(int spaceId, @Nonnull PhysicsActivationTuning tuning) { + Objects.requireNonNull(tuning, "tuning"); + SpaceState state = requireSpace(spaceId); + RapierNative.setDynamicSleepTuningNative(state.nativeSpaceHandle, + tuning.linearSleepThreshold(), + tuning.angularSleepThreshold(), + tuning.timeUntilSleep()); + } + + @Override + public void applyExtensionSettings(int spaceId, + @Nonnull PhysicsCapabilityId capabilityId, + @Nonnull BackendExtensionSettingsSource settings) { + Objects.requireNonNull(capabilityId, "capabilityId"); + Objects.requireNonNull(settings, "settings"); + SpaceState state = requireSpace(spaceId); + if (!RAPIER_SOLVER_EXTENSION_ID.equals(capabilityId)) { + return; + } + int[] internalPgsIterations = { state.internalPgsIterations }; + int[] minIslandSize = { state.minIslandSize }; + settings.forEachSetting((key, value) -> { + if (INTERNAL_PGS_ITERATIONS.equals(key)) { + internalPgsIterations[0] = parsePositive(value, key); + } else if (MIN_ISLAND_SIZE.equals(key)) { + minIslandSize[0] = parsePositive(value, key); + } + }); + state.applySolverTuning(state.solverIterations, + internalPgsIterations[0], + state.stabilizationIterations, + minIslandSize[0]); + state.internalPgsIterations = internalPgsIterations[0]; + state.minIslandSize = minIslandSize[0]; + } + + @Nonnull + private SpaceState requireSpace(int spaceId) { + SpaceState state = spaces.get(spaceId); + if (state == null || state.closed) { + throw new IllegalArgumentException("Physics space id=" + spaceId + + " is not registered"); + } + return state; + } + + @Nonnull + private static BodyState requireBody(@Nonnull SpaceState state, long bodyId) { + BodyState body = state.bodiesById.get(bodyId); + if (body == null) { + throw new IllegalArgumentException("Physics body id=" + bodyId + + " is not registered in space " + state.spaceId); + } + return body; + } + + @Nonnull + private static JointState requireJoint(@Nonnull SpaceState state, long jointId) { + JointState joint = state.jointsById.get(jointId); + if (joint == null) { + throw new IllegalArgumentException("Physics joint id=" + jointId + + " is not registered"); + } + return joint; + } + + private static void requireVoxelTerrain(@Nonnull BodyState body) { + if (body.shapeTypeCode != BackendRuntimeCodes.SHAPE_VOXELS) { + throw new IllegalArgumentException("Body must be a voxel terrain"); + } + } + + private static void attachBody(@Nonnull SpaceState state, + @Nonnull BodyState body, + long nativeBodyHandle) { + if (nativeBodyHandle == 0L) { + throw new IllegalStateException("Rapier returned a null native body handle"); + } + body.nativeBodyHandle = nativeBodyHandle; + state.bodiesById.put(body.bodyId, body); + state.bodyIdsByNativeHandle.put(nativeBodyHandle, body.bodyId); + } + + private static void removeAttachedJointState(@Nonnull SpaceState state, long bodyId) { + Iterator> iterator = state.jointsById.entrySet().iterator(); + while (iterator.hasNext()) { + JointState joint = iterator.next().getValue(); + if (joint.bodyAId != bodyId && joint.bodyBId != bodyId) { + continue; + } + iterator.remove(); + } + } + + private static boolean emitRayHit(@Nonnull SpaceState state, + @Nullable float[] raw, + int offset, + @Nonnull BackendRayHitSink sink) { + if (raw == null || offset + RAY_HIT_FLOATS > raw.length) { + return false; + } + Long bodyId = state.bodyIdsByNativeHandle.get(rawBitFloatPairToLong(raw[offset], + raw[offset + 1])); + if (bodyId == null) { + return false; + } + sink.accept(bodyId, + raw[offset + 2], + raw[offset + 3], + raw[offset + 4], + raw[offset + 5], + raw[offset + 6], + raw[offset + 7], + raw[offset + 8], + raw[offset + 9]); + return true; + } + + private static int emitContacts(@Nonnull SpaceState state, + @Nullable float[] raw, + @Nonnull BackendContactSink sink) { + if (raw == null) { + return 0; + } + int contacts = 0; + for (int offset = 0; offset + CONTACT_FLOATS <= raw.length; offset += CONTACT_FLOATS) { + Long bodyAId = state.bodyIdsByNativeHandle.get(rawBitFloatPairToLong(raw[offset], + raw[offset + 1])); + Long bodyBId = state.bodyIdsByNativeHandle.get(rawBitFloatPairToLong(raw[offset + 2], + raw[offset + 3])); + if (bodyAId == null || bodyBId == null) { + continue; + } + sink.accept(bodyAId, + bodyBId, + raw[offset + 4], + raw[offset + 5], + raw[offset + 6], + raw[offset + 7], + raw[offset + 8], + raw[offset + 9], + raw[offset + 10], + raw[offset + 11], + raw[offset + 12], + raw[offset + 13], + raw[offset + 14]); + contacts++; + } + return contacts; + } + + private static long rawBitFloatPairToLong(float upper, float lower) { + long upperBits = Float.floatToRawIntBits(upper); + long lowerBits = Float.floatToRawIntBits(lower) & 0xFFFFFFFFL; + return (upperBits << 32) | lowerBits; + } + + private static int parsePositive(@Nonnull String value, @Nonnull String key) { + int parsed; + try { + parsed = Integer.parseInt(value); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException("Rapier extension setting " + key + + " must be an integer", exception); + } + if (parsed < 1) { + throw new IllegalArgumentException("Rapier extension setting " + key + + " must be positive"); + } + return parsed; + } + + private static float adjustedMass(float mass, @Nonnull PhysicsBodyType bodyType) { + if (bodyType == PhysicsBodyType.STATIC) { + return 0.0f; + } + return mass <= 0.0f ? DEFAULT_DYNAMIC_MASS : mass; + } + + private static int adjustedBodyTypeCode(@Nonnull PhysicsBodyType bodyType, float mass) { + if (mass <= 0.0f) { + return BackendRuntimeCodes.BODY_STATIC; + } + return BackendRuntimeCodes.bodyTypeCode(bodyType); + } + + private static float centerOfMassOffsetY(@Nonnull ShapeType shapeType, + @Nonnull PhysicsAxis axis, + float halfExtentY, + float radius, + float halfHeight) { + return switch (shapeType) { + case BOX -> halfExtentY; + case SPHERE -> radius; + case CAPSULE -> axis == PhysicsAxis.Y ? halfHeight + radius : radius; + case CYLINDER, CONE -> axis == PhysicsAxis.Y ? halfHeight : radius; + case PLANE, VOXELS, UNKNOWN -> 0.0f; + }; + } + + @Nonnull + private static NormalizedRotation normalizeRotation(float x, float y, float z, float w) { + float lengthSquared = x * x + y * y + z * z + w * w; + if (lengthSquared == 0.0f) { + return new NormalizedRotation(0.0f, 0.0f, 0.0f, 1.0f); + } + float inverseLength = (float) (1.0 / Math.sqrt(lengthSquared)); + return new NormalizedRotation(x * inverseLength, + y * inverseLength, + z * inverseLength, + w * inverseLength); + } + + private record NormalizedRotation(float x, float y, float z, float w) { + } + + @Nonnull + private static NormalizedAxis normalizeAxis(float axisX, float axisY, float axisZ) { + float lengthSquared = axisX * axisX + axisY * axisY + axisZ * axisZ; + if (lengthSquared == 0.0f) { + return new NormalizedAxis(0.0f, 1.0f, 0.0f); + } + float inverseLength = (float) (1.0 / Math.sqrt(lengthSquared)); + return new NormalizedAxis(axisX * inverseLength, + axisY * inverseLength, + axisZ * inverseLength); + } + + private record NormalizedAxis(float x, float y, float z) { + } + + private static final class SnapshotSelection { + + private final SpaceState state; + private BodyState[] bodies = new BodyState[16]; + private int count; + + private SnapshotSelection(@Nonnull SpaceState state) { + this.state = state; + } + + private void add(long bodyId) { + BodyState body = state.bodiesById.get(bodyId); + if (body == null) { + return; + } + if (bodies.length <= count) { + BodyState[] grown = new BodyState[bodies.length * 2]; + System.arraycopy(bodies, 0, grown, 0, bodies.length); + bodies = grown; + } + state.ensureSnapshotCapacity(count + 1); + bodies[count] = body; + state.snapshotBodyHandles[count] = body.nativeBodyHandle; + count++; + } + } + + private static final class SpaceState { + + private final int spaceId; + private long nativeSpaceHandle; + private final Cleaner.Cleanable cleanable; + private final Map bodiesById = new HashMap<>(); + private final Map bodyIdsByNativeHandle = new HashMap<>(); + private final Map jointsById = new HashMap<>(); + private long[] snapshotBodyHandles = new long[0]; + private float[] snapshotBodyData = new float[0]; + private int solverIterations = DEFAULT_SOLVER_ITERATIONS; + private int internalPgsIterations = DEFAULT_INTERNAL_PGS_ITERATIONS; + private int stabilizationIterations = DEFAULT_STABILIZATION_ITERATIONS; + private int minIslandSize = DEFAULT_MIN_ISLAND_SIZE; + private boolean closed; + + private SpaceState(int spaceId, long nativeSpaceHandle) { + this.spaceId = spaceId; + this.nativeSpaceHandle = nativeSpaceHandle; + this.cleanable = CLEANER.register(this, new NativeSpaceCleanup(nativeSpaceHandle)); + } + + private void ensureSnapshotCapacity(int bodyCount) { + if (snapshotBodyHandles.length < bodyCount) { + int capacity = Math.max(bodyCount, Math.max(1, snapshotBodyHandles.length * 2)); + long[] grown = new long[capacity]; + System.arraycopy(snapshotBodyHandles, 0, grown, 0, snapshotBodyHandles.length); + snapshotBodyHandles = grown; + } + int floats = bodyCount * BODY_SNAPSHOT_FLOATS; + if (snapshotBodyData.length < floats) { + int capacity = Math.max(floats, Math.max(BODY_SNAPSHOT_FLOATS, + snapshotBodyData.length * 2)); + snapshotBodyData = new float[capacity]; + } + } + + private void applySolverTuning(int solverIterations, + int internalPgsIterations, + int stabilizationIterations, + int minIslandSize) { + RapierNative.setSolverTuningNative(nativeSpaceHandle, + solverIterations, + internalPgsIterations, + stabilizationIterations, + minIslandSize); + } + + private void close() { + if (closed) { + return; + } + closed = true; + bodiesById.clear(); + bodyIdsByNativeHandle.clear(); + jointsById.clear(); + cleanable.clean(); + nativeSpaceHandle = 0L; + } + } + + private static final class BodyState { + + private final long bodyId; + private long nativeBodyHandle; + private final int shapeTypeCode; + private final int axisCode; + private final float halfExtentX; + private final float halfExtentY; + private final float halfExtentZ; + private final float radius; + private final float halfHeight; + private final float centerOfMassOffsetY; + private int bodyTypeCode; + private float positionX; + private float positionY; + private float positionZ; + private float rotationX; + private float rotationY; + private float rotationZ; + private float rotationW; + private float linearVelocityX; + private float linearVelocityY; + private float linearVelocityZ; + private float angularVelocityX; + private float angularVelocityY; + private float angularVelocityZ; + private boolean sleeping; + private boolean sensor; + private float mass; + private float friction = 0.5f; + private float restitution; + private float linearDamping; + private float angularDamping; + private int collisionGroup = 1; + private int collisionMask = 1; + private boolean continuousCollisionEnabled; + + private BodyState(long bodyId, + int shapeTypeCode, + int axisCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + float centerOfMassOffsetY, + float mass, + int bodyTypeCode) { + this.bodyId = bodyId; + this.shapeTypeCode = shapeTypeCode; + this.axisCode = axisCode; + this.halfExtentX = halfExtentX; + this.halfExtentY = halfExtentY; + this.halfExtentZ = halfExtentZ; + this.radius = radius; + this.halfHeight = halfHeight; + this.centerOfMassOffsetY = centerOfMassOffsetY; + this.mass = mass; + this.bodyTypeCode = bodyTypeCode; + setRotation(0.0f, 0.0f, 0.0f, 1.0f); + } + + @Nonnull + private static BodyState regular(long bodyId, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float centerOfMassOffsetY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + BodyState body = new BodyState(bodyId, + shapeTypeCode, + axisCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + centerOfMassOffsetY, + mass, + bodyTypeCode); + body.setPosition(positionX, positionY, positionZ); + body.setRotation(rotationX, rotationY, rotationZ, rotationW); + return body; + } + + @Nonnull + private static BodyState voxels(long bodyId, + float voxelSizeX, + float voxelSizeY, + float voxelSizeZ, + float positionX, + float positionY, + float positionZ, + float friction, + float restitution, + int collisionGroup, + int collisionMask) { + BodyState body = new BodyState(bodyId, + BackendRuntimeCodes.SHAPE_VOXELS, + BackendRuntimeCodes.AXIS_Y, + voxelSizeX, + voxelSizeY, + voxelSizeZ, + -1.0f, + -1.0f, + 0.0f, + 0.0f, + BackendRuntimeCodes.BODY_STATIC); + body.setPosition(positionX, positionY, positionZ); + body.friction = friction; + body.restitution = restitution; + body.collisionGroup = collisionGroup; + body.collisionMask = collisionMask; + return body; + } + + private void setPosition(float x, float y, float z) { + positionX = x; + positionY = y; + positionZ = z; + } + + private void setRotation(float x, float y, float z, float w) { + NormalizedRotation rotation = normalizeRotation(x, y, z, w); + rotationX = rotation.x; + rotationY = rotation.y; + rotationZ = rotation.z; + rotationW = rotation.w; + } + + private void updateFromNative(@Nonnull float[] values, int offset) { + sleeping = values[offset + 14] != 0.0f; + positionX = values[offset]; + positionY = values[offset + 1]; + positionZ = values[offset + 2]; + setRotation(values[offset + 3], + values[offset + 4], + values[offset + 5], + values[offset + 6]); + linearVelocityX = values[offset + 7]; + linearVelocityY = values[offset + 8]; + linearVelocityZ = values[offset + 9]; + angularVelocityX = values[offset + 10]; + angularVelocityY = values[offset + 11]; + angularVelocityZ = values[offset + 12]; + int bodyTypeOrdinal = Math.round(values[offset + 13]); + PhysicsBodyType[] bodyTypes = PhysicsBodyType.values(); + if (bodyTypeOrdinal >= 0 && bodyTypeOrdinal < bodyTypes.length) { + bodyTypeCode = BackendRuntimeCodes.bodyTypeCode(bodyTypes[bodyTypeOrdinal]); + } + sensor = values[offset + 15] != 0.0f; + } + + private void emit(@Nonnull BackendBodySnapshotSink sink) { + sink.accept(bodyId, + shapeTypeCode, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + sleeping, + sensor, + mass, + friction, + restitution, + linearDamping, + angularDamping, + collisionGroup, + collisionMask, + continuousCollisionEnabled, + centerOfMassOffsetY, + shapeTypeCode == BackendRuntimeCodes.SHAPE_BOX, + shapeTypeCode == BackendRuntimeCodes.SHAPE_BOX ? halfExtentX : 0.0f, + shapeTypeCode == BackendRuntimeCodes.SHAPE_BOX ? halfExtentY : 0.0f, + shapeTypeCode == BackendRuntimeCodes.SHAPE_BOX ? halfExtentZ : 0.0f, + radius, + halfHeight, + axisCode); + } + } + + private record JointState(long jointId, + long nativeJointHandle, + int jointTypeCode, + long bodyAId, + long bodyBId) { + } + + private static final class NativeSpaceCleanup implements Runnable { + + private final long nativeSpaceHandle; + + private NativeSpaceCleanup(long nativeSpaceHandle) { + this.nativeSpaceHandle = nativeSpaceHandle; + } + + @Override + public void run() { + RapierNative.destroySpaceNative(nativeSpaceHandle); + } + } +} diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java index 3fce5814..8c0b2791 100644 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java +++ b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java @@ -3,13 +3,11 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntime; import javax.annotation.Nonnull; /** * Runtime-provider service entry point for Rapier. */ -@SuppressWarnings("removal") public final class RapierBackendRuntimeProvider implements PhysicsBackendRuntimeProvider { private final RapierBackend backend = new RapierBackend(); @@ -28,6 +26,6 @@ public void init() { @Nonnull @Override public PhysicsBackendRuntime createRuntime() { - return new LegacyPhysicsBackendRuntime(backend); + return new RapierBackendRuntime(); } } diff --git a/impulse-rapier/src/main/rust/src/joint_exports.rs b/impulse-rapier/src/main/rust/src/joint_exports.rs index f191be35..1f22d391 100644 --- a/impulse-rapier/src/main/rust/src/joint_exports.rs +++ b/impulse-rapier/src/main/rust/src/joint_exports.rs @@ -1,5 +1,49 @@ use super::*; +enum JointMutationFailure { + StaleJoint(jlong), + StaleImpulseJoint(jlong), +} + +fn throw_joint_mutation_failure( + env: &mut JNIEnv<'_>, + operation: &str, + failure: JointMutationFailure, +) { + let message = match failure { + JointMutationFailure::StaleJoint(joint_id) => { + format!("Rapier native {operation} failed: stale joint handle {joint_id}") + } + JointMutationFailure::StaleImpulseJoint(joint_id) => { + format!("Rapier native {operation} failed: stale impulse joint for joint {joint_id}") + } + }; + let _ = env.throw_new("java/lang/IllegalStateException", message); +} + +fn with_attached_joint_mutation( + env: &mut JNIEnv<'_>, + operation: &str, + space_handle: jlong, + joint_id: jlong, + f: F, +) where + F: FnOnce(&mut NativeSpace, JointEntry) -> Result<(), JointMutationFailure>, +{ + let result = with_space_checked(space_handle, |space| { + let entry = space + .joint(joint_id) + .ok_or(JointMutationFailure::StaleJoint(joint_id))?; + f(space, entry) + }); + + match result { + Ok(Ok(())) => {} + Ok(Err(failure)) => throw_joint_mutation_failure(env, operation, failure), + Err(failure) => throw_native_space_failure(env, operation, failure), + } +} + #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_addJointNative( _env: JNIEnv, @@ -101,35 +145,50 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_jointH #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_removeJointNative( - _env: JNIEnv, + mut env: JNIEnv, _class: JClass, space_handle: jlong, joint_id: jlong, ) { - with_space(space_handle, (), |space| { - if let Some(entry) = space.joints.remove(&joint_id) { + with_attached_joint_mutation( + &mut env, + "remove joint", + space_handle, + joint_id, + |space, entry| { + if space.impulse_joints.get(entry.joint).is_none() { + return Err(JointMutationFailure::StaleImpulseJoint(joint_id)); + } + space.joints.remove(&joint_id); space.impulse_joints.remove(entry.joint, true); - } - }); + Ok(()) + }, + ); } #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setJointEnabledNative( - _env: JNIEnv, + mut env: JNIEnv, _class: JClass, space_handle: jlong, joint_id: jlong, enabled: jboolean, ) { - with_space(space_handle, (), |space| { - if let Some(mut entry) = space.joint(joint_id) { + with_attached_joint_mutation( + &mut env, + "set joint enabled", + space_handle, + joint_id, + |space, mut entry| { entry.enabled = bool_from_jboolean(enabled); - if let Some(joint) = space.impulse_joints.get_mut(entry.joint, true) { - joint.data.set_enabled(entry.enabled); - } + let Some(joint) = space.impulse_joints.get_mut(entry.joint, true) else { + return Err(JointMutationFailure::StaleImpulseJoint(joint_id)); + }; + joint.data.set_enabled(entry.enabled); space.joints.insert(joint_id, entry); - } - }); + Ok(()) + }, + ); } #[no_mangle] @@ -149,15 +208,19 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_isJoin #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setJointLimitsNative( - _env: JNIEnv, + mut env: JNIEnv, _class: JClass, space_handle: jlong, joint_id: jlong, lower_limit: jfloat, upper_limit: jfloat, ) { - with_space(space_handle, (), |space| { - if let Some(entry) = space.joint(joint_id) { + with_attached_joint_mutation( + &mut env, + "set joint limits", + space_handle, + joint_id, + |space, entry| { let lower_limit = finite_or(lower_limit, 0.0); let upper_limit = finite_or(upper_limit, 0.0); let limits = if lower_limit <= upper_limit { @@ -165,24 +228,26 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setJoi } else { [upper_limit, lower_limit] }; - if let Some(joint) = space.impulse_joints.get_mut(entry.joint, true) { - match entry.joint_type { - JOINT_HINGE => { - joint.data.set_limits(JointAxis::AngX, limits); - } - JOINT_SLIDER => { - joint.data.set_limits(JointAxis::LinX, limits); - } - _ => {} + let Some(joint) = space.impulse_joints.get_mut(entry.joint, true) else { + return Err(JointMutationFailure::StaleImpulseJoint(joint_id)); + }; + match entry.joint_type { + JOINT_HINGE => { + joint.data.set_limits(JointAxis::AngX, limits); + } + JOINT_SLIDER => { + joint.data.set_limits(JointAxis::LinX, limits); } + _ => {} } - } - }); + Ok(()) + }, + ); } #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setJointMotorNative( - _env: JNIEnv, + mut env: JNIEnv, _class: JClass, space_handle: jlong, joint_id: jlong, @@ -190,35 +255,41 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setJoi target_velocity: jfloat, max_force: jfloat, ) { - with_space(space_handle, (), |space| { - if let Some(entry) = space.joint(joint_id) { - if let Some(joint) = space.impulse_joints.get_mut(entry.joint, true) { - let target_velocity = if bool_from_jboolean(enabled) { - finite_or(target_velocity, 0.0) - } else { - 0.0 - }; - let max_force = if bool_from_jboolean(enabled) { - finite_nonnegative(max_force) - } else { - 0.0 - }; - match entry.joint_type { - JOINT_HINGE => { - joint - .data - .set_motor_velocity(JointAxis::AngX, target_velocity, 1.0); - joint.data.set_motor_max_force(JointAxis::AngX, max_force); - } - JOINT_SLIDER => { - joint - .data - .set_motor_velocity(JointAxis::LinX, target_velocity, 1.0); - joint.data.set_motor_max_force(JointAxis::LinX, max_force); - } - _ => {} + with_attached_joint_mutation( + &mut env, + "set joint motor", + space_handle, + joint_id, + |space, entry| { + let Some(joint) = space.impulse_joints.get_mut(entry.joint, true) else { + return Err(JointMutationFailure::StaleImpulseJoint(joint_id)); + }; + let target_velocity = if bool_from_jboolean(enabled) { + finite_or(target_velocity, 0.0) + } else { + 0.0 + }; + let max_force = if bool_from_jboolean(enabled) { + finite_nonnegative(max_force) + } else { + 0.0 + }; + match entry.joint_type { + JOINT_HINGE => { + joint + .data + .set_motor_velocity(JointAxis::AngX, target_velocity, 1.0); + joint.data.set_motor_max_force(JointAxis::AngX, max_force); + } + JOINT_SLIDER => { + joint + .data + .set_motor_velocity(JointAxis::LinX, target_velocity, 1.0); + joint.data.set_motor_max_force(JointAxis::LinX, max_force); } + _ => {} } - } - }); + Ok(()) + }, + ); } diff --git a/impulse-rapier/src/main/rust/src/query_exports.rs b/impulse-rapier/src/main/rust/src/query_exports.rs index 9c9ceeda..8b7bae3a 100644 --- a/impulse-rapier/src/main/rust/src/query_exports.rs +++ b/impulse-rapier/src/main/rust/src/query_exports.rs @@ -120,7 +120,10 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_getCon if max_contacts <= 0 { Vec::new() } else { - contact_values(space_handle, (max_contacts as usize).min(MAX_CONTACT_POINTS)) + contact_values( + space_handle, + (max_contacts as usize).min(MAX_CONTACT_POINTS), + ) } }); float_array_or_null(&env, &values) diff --git a/impulse-rapier/src/main/rust/src/space_exports.rs b/impulse-rapier/src/main/rust/src/space_exports.rs index b015f840..4157ad69 100644 --- a/impulse-rapier/src/main/rust/src/space_exports.rs +++ b/impulse-rapier/src/main/rust/src/space_exports.rs @@ -32,16 +32,18 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_destro #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setGravityNative( - _env: JNIEnv, + mut env: JNIEnv, _class: JClass, space_handle: jlong, x: jfloat, y: jfloat, z: jfloat, ) { - with_space(space_handle, (), |space| { + if let Err(failure) = with_space_checked(space_handle, |space| { space.gravity = finite_vector_or_zero(x, y, z); - }); + }) { + throw_native_space_failure(&mut env, "set gravity", failure); + } } #[no_mangle] @@ -444,7 +446,7 @@ fn long_array_or_null(env: &JNIEnv<'_>, values: &[jlong]) -> jni::sys::jlongArra #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setSolverTuningNative( - _env: JNIEnv, + mut env: JNIEnv, _class: JClass, space_handle: jlong, solver_iterations: jint, @@ -452,28 +454,32 @@ pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setSol stabilization_iterations: jint, min_island_size: jint, ) { - with_space(space_handle, (), |space| { + if let Err(failure) = with_space_checked(space_handle, |space| { space.set_solver_tuning( positive_usize(solver_iterations), positive_usize(internal_pgs_iterations), non_negative_usize(stabilization_iterations), positive_usize(min_island_size), ); - }); + }) { + throw_native_space_failure(&mut env, "set solver tuning", failure); + } } #[no_mangle] pub extern "system" fn Java_dev_hytalemodding_impulse_rapier_RapierNative_setDynamicSleepTuningNative( - _env: JNIEnv, + mut env: JNIEnv, _class: JClass, space_handle: jlong, linear_threshold: jfloat, angular_threshold: jfloat, time_until_sleep: jfloat, ) { - with_space(space_handle, (), |space| { + if let Err(failure) = with_space_checked(space_handle, |space| { space.set_dynamic_sleep_tuning(linear_threshold, angular_threshold, time_until_sleep); - }); + }) { + throw_native_space_failure(&mut env, "set dynamic sleep tuning", failure); + } } fn positive_usize(value: jint) -> usize { diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java new file mode 100644 index 00000000..b1ee10bc --- /dev/null +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java @@ -0,0 +1,388 @@ +package dev.hytalemodding.impulse.rapier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; +import dev.hytalemodding.impulse.api.runtime.BackendJointType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntime; +import java.lang.reflect.Field; +import java.util.Map; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class RapierBackendRuntimeProviderTest { + + @Test + void providerCreatesIdOnlyRuntimeInsteadOfLegacyAdapter() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + + PhysicsBackendRuntime runtime = provider.createRuntime(); + + assertFalse(runtime instanceof LegacyPhysicsBackendRuntime); + } + + @Test + void runtimeSupportsPrimitiveBodySnapshotAndJointLifecycle() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int spaceId = runtime.createSpace(new SpaceId(70)); + try { + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + float[] gravity = new float[3]; + runtime.getGravity(spaceId, (x, y, z) -> { + gravity[0] = x; + gravity[1] = y; + gravity[2] = z; + }); + + long firstBodyId = runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + 1.0f, + 2.0f, + 3.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + long secondBodyId = runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + 2.0f, + 2.0f, + 3.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + CapturedSnapshot snapshot = new CapturedSnapshot(); + long jointId = runtime.createJoint(spaceId, + BackendRuntimeCodes.jointTypeCode(BackendJointType.FIXED), + firstBodyId, + secondBodyId, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f); + + assertEquals(-9.81f, gravity[1]); + assertEquals(2, runtime.bodyCount(spaceId)); + assertTrue(runtime.containsBody(spaceId, firstBodyId)); + assertTrue(runtime.bodySnapshot(spaceId, firstBodyId, snapshot)); + assertEquals(firstBodyId, snapshot.bodyId); + assertEquals(BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), snapshot.shapeTypeCode); + assertEquals(BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + snapshot.bodyTypeCode); + assertEquals(1.0f, snapshot.positionX); + assertEquals(2.0f, snapshot.positionY); + assertEquals(3.0f, snapshot.positionZ); + assertEquals(1, runtime.jointCount(spaceId)); + assertEquals(BackendRuntimeCodes.jointTypeCode(BackendJointType.FIXED), + runtime.jointType(spaceId, jointId)); + assertEquals(firstBodyId, runtime.jointBodyA(spaceId, jointId)); + assertEquals(secondBodyId, runtime.jointBodyB(spaceId, jointId)); + + runtime.removeJoint(spaceId, jointId); + runtime.removeBody(spaceId, firstBodyId); + + assertEquals(0, runtime.jointCount(spaceId)); + assertFalse(runtime.containsBody(spaceId, firstBodyId)); + assertEquals(1, runtime.bodyCount(spaceId)); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void failedNativeBodyRemovalKeepsJavaBodyStateForRetry() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int spaceId = runtime.createSpace(new SpaceId(71)); + long bodyId = createBox(runtime, spaceId, 1.0f, 2.0f, 3.0f); + long nativeSpaceHandle = nativeSpaceHandle(runtime, spaceId); + RapierNative.destroySpaceNative(nativeSpaceHandle); + try { + assertThrows(IllegalStateException.class, () -> runtime.removeBody(spaceId, bodyId)); + + assertTrue(runtime.containsBody(spaceId, bodyId)); + assertEquals(1, runtime.bodyCount(spaceId)); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void failedNativeJointRemovalKeepsJavaJointStateForRetry() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int spaceId = runtime.createSpace(new SpaceId(72)); + long firstBodyId = createBox(runtime, spaceId, 1.0f, 2.0f, 3.0f); + long secondBodyId = createBox(runtime, spaceId, 2.0f, 2.0f, 3.0f); + long jointId = runtime.createJoint(spaceId, + BackendRuntimeCodes.jointTypeCode(BackendJointType.FIXED), + firstBodyId, + secondBodyId, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f); + long nativeSpaceHandle = nativeSpaceHandle(runtime, spaceId); + RapierNative.destroySpaceNative(nativeSpaceHandle); + try { + assertThrows(IllegalStateException.class, () -> runtime.removeJoint(spaceId, jointId)); + + assertEquals(1, runtime.jointCount(spaceId)); + assertEquals(BackendRuntimeCodes.jointTypeCode(BackendJointType.FIXED), + runtime.jointType(spaceId, jointId)); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void failedNativeBodyMutationDoesNotAdvanceCachedState() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int spaceId = runtime.createSpace(new SpaceId(73)); + long bodyId = createBox(runtime, spaceId, 1.0f, 2.0f, 3.0f); + long nativeSpaceHandle = nativeSpaceHandle(runtime, spaceId); + RapierNative.destroySpaceNative(nativeSpaceHandle); + try { + assertThrows(IllegalStateException.class, + () -> runtime.setBodyPosition(spaceId, bodyId, 9.0f, 9.0f, 9.0f)); + + assertEquals(1.0f, cachedPositionX(runtime, spaceId, bodyId)); + assertEquals(2.0f, cachedPositionY(runtime, spaceId, bodyId)); + assertEquals(3.0f, cachedPositionZ(runtime, spaceId, bodyId)); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void closeDestroysAllCachedSpaces() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int firstSpaceId = runtime.createSpace(new SpaceId(74)); + int secondSpaceId = runtime.createSpace(new SpaceId(75)); + createBox(runtime, firstSpaceId, 1.0f, 2.0f, 3.0f); + createBox(runtime, secondSpaceId, 2.0f, 2.0f, 3.0f); + + runtime.close(); + + assertThrows(IllegalArgumentException.class, () -> runtime.bodyCount(firstSpaceId)); + assertThrows(IllegalArgumentException.class, () -> runtime.bodyCount(secondSpaceId)); + } + + private static long createBox(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + float positionX, + float positionY, + float positionZ) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + positionX, + positionY, + positionZ, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static long nativeSpaceHandle(@Nonnull PhysicsBackendRuntime runtime, int spaceId) { + Object state = spaceState(runtime, spaceId); + try { + Field handle = state.getClass().getDeclaredField("nativeSpaceHandle"); + handle.setAccessible(true); + return handle.getLong(state); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier native space handle", exception); + } + } + + private static float cachedPositionX(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + long bodyId) { + return cachedFloat(runtime, spaceId, bodyId, "positionX"); + } + + private static float cachedPositionY(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + long bodyId) { + return cachedFloat(runtime, spaceId, bodyId, "positionY"); + } + + private static float cachedPositionZ(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + long bodyId) { + return cachedFloat(runtime, spaceId, bodyId, "positionZ"); + } + + private static float cachedFloat(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + long bodyId, + @Nonnull String fieldName) { + Object body = bodyState(runtime, spaceId, bodyId); + try { + Field field = body.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.getFloat(body); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier body cache", exception); + } + } + + private static Object bodyState(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + long bodyId) { + Object state = spaceState(runtime, spaceId); + try { + Field bodies = state.getClass().getDeclaredField("bodiesById"); + bodies.setAccessible(true); + @SuppressWarnings("unchecked") + Map bodiesById = (Map) bodies.get(state); + Object body = bodiesById.get(bodyId); + if (body == null) { + throw new AssertionError("No cached body " + bodyId); + } + return body; + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier body cache", exception); + } + } + + private static Object spaceState(@Nonnull PhysicsBackendRuntime runtime, int spaceId) { + try { + Field spaces = runtime.getClass().getDeclaredField("spaces"); + spaces.setAccessible(true); + @SuppressWarnings("unchecked") + Map spacesById = (Map) spaces.get(runtime); + Object state = spacesById.get(spaceId); + if (state == null) { + throw new AssertionError("No cached space " + spaceId); + } + return state; + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier runtime cache", exception); + } + } + + private static final class CapturedSnapshot implements BackendBodySnapshotSink { + + private long bodyId; + private int shapeTypeCode; + private int bodyTypeCode; + private float positionX; + private float positionY; + private float positionZ; + + @Override + public void accept(long bodyId, + int shapeTypeCode, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping, + boolean sensor, + float mass, + float friction, + float restitution, + float linearDamping, + float angularDamping, + int collisionGroup, + int collisionMask, + boolean continuousCollisionEnabled, + float centerOfMassOffsetY, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode) { + this.bodyId = bodyId; + this.shapeTypeCode = shapeTypeCode; + this.bodyTypeCode = bodyTypeCode; + this.positionX = positionX; + this.positionY = positionY; + this.positionZ = positionZ; + } + } +} From d7c07a3c3286012707bfe58669bec0379f17602f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 18:52:08 +0200 Subject: [PATCH 491/534] refactor(core): use lombok schema version getter Signed-off-by: Blovien --- .../persistence/PersistentPhysicsStoreResource.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java index 85a52129..262f8c26 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java @@ -9,6 +9,7 @@ import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import lombok.Getter; import java.util.Arrays; import javax.annotation.Nonnull; @@ -85,6 +86,7 @@ public final class PersistentPhysicsStoreResource implements Resource Date: Sat, 20 Jun 2026 19:03:19 +0200 Subject: [PATCH 492/534] refactor(api)!: remove legacy backend contracts Signed-off-by: Blovien --- README.md | 16 +- build.gradle.kts | 4 +- .../hytalemodding/impulse/api/BackendId.java | 4 +- .../impulse/api/PhysicsBackend.java | 63 - .../PhysicsBackendBodyActivationEvent.java | 23 - .../api/PhysicsBackendContactEvent.java | 52 - .../impulse/api/PhysicsBackendEvent.java | 13 - .../impulse/api/PhysicsBackendEventSink.java | 46 - .../api/PhysicsBackendJointBreakEvent.java | 21 - .../impulse/api/PhysicsBody.java | 224 --- .../impulse/api/PhysicsBodySnapshot.java | 66 +- .../impulse/api/PhysicsContact.java | 38 - .../impulse/api/PhysicsJoint.java | 108 -- .../impulse/api/PhysicsJointType.java | 32 - .../impulse/api/PhysicsRayHit.java | 29 - .../impulse/api/PhysicsSpace.java | 432 ------ .../hytalemodding/impulse/api/SpaceId.java | 4 +- .../PhysicsActivationTuningCapability.java | 17 - .../PhysicsBackendEventsCapability.java | 29 - .../api/capability/PhysicsCapability.java | 8 - .../PhysicsCapabilityDescriptor.java | 28 - .../PhysicsContinuousCollisionCapability.java | 17 - .../PhysicsExtensionSettingsCapability.java | 22 - .../PhysicsSolverTuningCapability.java | 17 - .../PhysicsVoxelTerrainCapability.java | 44 - .../legacy/LegacyPhysicsBackendRuntime.java | 897 ------------ .../impulse/api/ImpulseRegistryTest.java | 27 - .../PhysicsBackendEventsCapabilityTest.java | 22 - .../PhysicsCapabilitySettingsTest.java | 16 - .../LegacyPhysicsBackendRuntimeTest.java | 535 -------- .../FakePhysicsBackendCapabilityTest.java | 44 - .../api/testsupport/FakePhysicsBackend.java | 805 ----------- impulse-bullet/build.gradle.kts | 131 -- .../impulse/bullet/BulletBackend.java | 106 -- .../bullet/BulletBackendRuntimeProvider.java | 39 - .../impulse/bullet/BulletBody.java | 578 -------- .../impulse/bullet/BulletJoint.java | 190 --- .../bullet/BulletNativeContactEvent.java | 53 - .../impulse/bullet/BulletNativeSpace.java | 164 --- .../impulse/bullet/BulletSpace.java | 643 --------- ....api.runtime.PhysicsBackendRuntimeProvider | 1 - impulse-core/README.md | 2 +- .../crucible/CrucibleBackendsTest.java | 18 +- ...hunkCollisionVoxelStitchingSystemTest.java | 8 +- .../debug/PhysicsContactDebugCapture.java | 9 +- .../systems/debug/PhysicsDebugSystemTest.java | 51 +- .../debug/PhysicsJointDebugCapture.java | 20 +- .../impulse/rapier/RapierBackend.java | 52 - .../rapier/RapierBackendRuntimeProvider.java | 6 +- .../impulse/rapier/RapierBody.java | 733 ---------- .../impulse/rapier/RapierJoint.java | 217 --- .../impulse/rapier/RapierSpace.java | 1220 ----------------- .../RapierBackendRuntimeProviderTest.java | 6 +- .../rapier/RapierBoundedContactsTest.java | 109 +- .../rapier/RapierNativeBodyRemovalTest.java | 137 +- .../rapier/RapierVoxelTerrainTest.java | 205 +-- settings.gradle.kts | 1 - 57 files changed, 400 insertions(+), 8002 deletions(-) delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java delete mode 100644 impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java delete mode 100644 impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java delete mode 100644 impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java delete mode 100644 impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java delete mode 100644 impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java delete mode 100644 impulse-bullet/build.gradle.kts delete mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackend.java delete mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java delete mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBody.java delete mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletJoint.java delete mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeContactEvent.java delete mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeSpace.java delete mode 100644 impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletSpace.java delete mode 100644 impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider delete mode 100644 impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackend.java delete mode 100644 impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBody.java delete mode 100644 impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierJoint.java delete mode 100644 impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java diff --git a/README.md b/README.md index a4071781..b8cc2148 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,10 @@ Impulse codebase is divided as follows: - **impulse-core** - Hytale ECS integration and backend communication. - **impulse-api** - backend-agnostic API layer and contracts. -- **impulse-native-loader** - legacy loader for PhysicsBackend +- **impulse-native-loader** - native library loader for backend provider jars. - **impulse-examples** - example plugins to understand the framework usage. Official physics backend implementations: -- **impulse-bullet** - Libbulletjme backend implementation. - **impulse-rapier** - Rapier backend with a small Rust/JNI native shim. ### Architecture @@ -69,10 +68,7 @@ flowchart TB subgraph API["impulse-api"] direction TB - Current["PhysicsBackend"] - WIP["Runtime / Provider"] - - Current ~~~ WIP + Runtime["PhysicsBackendRuntime"] end subgraph Backends["backends"] @@ -108,11 +104,9 @@ flowchart TB TickB ----> Dispatch TickN ----> Dispatch - Dispatch ----> Current - Dispatch -.-> WIP + Dispatch ----> Runtime - Current ----> Active - WIP -.-> Active + Runtime ----> Active Active ----> Step Step --> Router @@ -211,7 +205,7 @@ Crucible in-game tests are also provided. Run them in game with: Backend provider artifacts may include third-party native binaries so Impulse can load the backend at runtime. These artifacts are convenience packages for Impulse plugins; they are not the official upstream distribution channel for those native libraries. Download standalone -Bullet/Libbulletjme or Rapier binaries from their upstream projects instead. +Rapier binaries from their upstream project instead. ## Code style diff --git a/build.gradle.kts b/build.gradle.kts index 2afda4ea..5660c702 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,7 +67,7 @@ subprojects { } } -val backendProjectPaths = setOf(":impulse-bullet", ":impulse-rapier") +val backendProjectPaths = setOf(":impulse-rapier") val stagedBackendJarDirectory = layout.projectDirectory.dir("run/mods/impulse-backends") val stagedEarlyPluginJarDirectory = layout.projectDirectory.dir("run/earlyplugins") val physicsStoreEarlyPluginEnabled = providers.gradleProperty("impulse.physicsStoreEarlyPlugin") @@ -137,7 +137,6 @@ tasks.register("packageBackendPlatformJars") { group = "build" description = "Packages all per-platform and universal backend provider jars" dependsOn( - ":impulse-bullet:packageBulletBackendPlatformJars", ":impulse-rapier:packageRapierBackendPlatformJars" ) } @@ -148,7 +147,6 @@ tasks.register("headlessTest") { dependsOn( ":impulse-backend-api:test", ":impulse-native-loader:test", - ":impulse-bullet:test", ":impulse-rapier:test", ":impulse-core:test", ":impulse-examples:test", diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java index 147097e0..60a8c65f 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java @@ -3,9 +3,9 @@ import javax.annotation.Nonnull; /** - * PhysicsBackend Identifier to provide serialization options + * Physics backend identifier used for provider selection and persistence. * - * @param value the identifier, follows [backend_provider]:[backend_name], e.g. impulse:bullet + * @param value the identifier, follows [backend_provider]:[backend_name], e.g. impulse:rapier */ public record BackendId(@Nonnull String value) { diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java deleted file mode 100644 index caef2ae2..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackend.java +++ /dev/null @@ -1,63 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import java.util.logging.Level; -import javax.annotation.Nonnull; - -/** - * Backend factory and lifecycle hooks. - *

        - * This is a semantic contract, not a promise of identical simulation results. Backends may - * differ in solver details, contact ordering, and numerical edge cases. - *

          - *
        • {@link #init()} must be idempotent and safe when called multiple times.
        • - *
        • Backends are expected to be used through {@link Impulse}, which provides - * thread-safe one-time initialization.
        • - *
        • {@link #createSpace()} and {@link #createSpace(SpaceId)} may be called from multiple - * backend lanes after initialization. Implementations with mutable factory state must - * synchronize internally.
        • - *
        • Different spaces may run concurrently on different backend lanes. Each individual - * {@link PhysicsSpace} remains serialized by its own backend lane.
        • - *
        - */ -@Deprecated(forRemoval = true) -public interface PhysicsBackend { - - @Nonnull - BackendId getId(); - - /** - * Set the verbosity of the backend's internal library logging. - * Backends should map this level to their own logging system. - * The default implementation is a no-op. - * - * @param level the desired logging level; Level.OFF suppresses all internal logging, - * Level.INFO allows standard output, Level.FINEST enables verbose diagnostics - */ - default void setInternalLoggingLevel(@Nonnull Level level) { - } - - /** - * Initialize backend-global state such as native libraries. - */ - void init(); - - /** - * Create a new independent simulation space. This method may be called concurrently after - * {@link #init()} has completed. - */ - @Nonnull - PhysicsSpace createSpace(); - - /** - * Create a new independent simulation space with a specific logical id. - *

        - * This method may be called concurrently after {@link #init()} has completed. - * Implementations should preserve this id on the returned space object. - */ - @Nonnull - default PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - throw new UnsupportedOperationException( - "Legacy physics backend " + getId() + " must override createSpace(SpaceId) " - + "to support explicit space ids"); - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java deleted file mode 100644 index 1f61757f..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendBodyActivationEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Copied body activation event emitted by a backend after a completed step. - */ -@Deprecated(forRemoval = true) -public record PhysicsBackendBodyActivationEvent(@Nonnull PhysicsBodyActivationPhase phase, - @Nonnull PhysicsBody body) implements PhysicsBackendEvent { - - public PhysicsBackendBodyActivationEvent { - phase = Objects.requireNonNull(phase, "phase"); - body = Objects.requireNonNull(body, "body"); - } - - @Nonnull - @Override - public PhysicsBackendEventKind kind() { - return phase.eventKind(); - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java deleted file mode 100644 index 0a2ee642..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendContactEvent.java +++ /dev/null @@ -1,52 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Copied contact event emitted by a backend after a completed step. - */ -@Deprecated(forRemoval = true) -public record PhysicsBackendContactEvent(@Nonnull PhysicsContactPhase phase, - @Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f pointOnA, - @Nonnull Vector3f pointOnB, - @Nonnull Vector3f normalOnB, - float distance, - float impulse) implements PhysicsBackendEvent { - - public PhysicsBackendContactEvent { - phase = Objects.requireNonNull(phase, "phase"); - bodyA = Objects.requireNonNull(bodyA, "bodyA"); - bodyB = Objects.requireNonNull(bodyB, "bodyB"); - pointOnA = new Vector3f(Objects.requireNonNull(pointOnA, "pointOnA")); - pointOnB = new Vector3f(Objects.requireNonNull(pointOnB, "pointOnB")); - normalOnB = new Vector3f(Objects.requireNonNull(normalOnB, "normalOnB")); - } - - @Nonnull - @Override - public PhysicsBackendEventKind kind() { - return phase.eventKind(); - } - - @Nonnull - @Override - public Vector3f pointOnA() { - return new Vector3f(pointOnA); - } - - @Nonnull - @Override - public Vector3f pointOnB() { - return new Vector3f(pointOnB); - } - - @Nonnull - @Override - public Vector3f normalOnB() { - return new Vector3f(normalOnB); - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java deleted file mode 100644 index 32c89354..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import javax.annotation.Nonnull; - -/** - * Copied backend event produced by a completed backend step. - */ -@Deprecated(forRemoval = true) -public interface PhysicsBackendEvent { - - @Nonnull - PhysicsBackendEventKind kind(); -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java deleted file mode 100644 index cad977d2..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventSink.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Sink for copied backend events emitted into a bounded post-step batch. - */ -@Deprecated(forRemoval = true) -public interface PhysicsBackendEventSink { - - int capacity(); - - int size(); - - int droppedEventCount(); - - boolean offer(@Nonnull PhysicsBackendEvent event); - - default boolean contact(@Nonnull PhysicsContactPhase phase, - @Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f pointOnA, - @Nonnull Vector3f pointOnB, - @Nonnull Vector3f normalOnB, - float distance, - float impulse) { - return offer(new PhysicsBackendContactEvent(phase, - bodyA, - bodyB, - pointOnA, - pointOnB, - normalOnB, - distance, - impulse)); - } - - default boolean bodyActivation(@Nonnull PhysicsBodyActivationPhase phase, - @Nonnull PhysicsBody body) { - return offer(new PhysicsBackendBodyActivationEvent(phase, body)); - } - - default boolean jointBreak(@Nonnull PhysicsJoint joint) { - return offer(new PhysicsBackendJointBreakEvent(joint)); - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java deleted file mode 100644 index 305999b8..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendJointBreakEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Copied joint-break event emitted by a backend after a completed step. - */ -@Deprecated(forRemoval = true) -public record PhysicsBackendJointBreakEvent(@Nonnull PhysicsJoint joint) implements PhysicsBackendEvent { - - public PhysicsBackendJointBreakEvent { - joint = Objects.requireNonNull(joint, "joint"); - } - - @Nonnull - @Override - public PhysicsBackendEventKind kind() { - return PhysicsBackendEventKind.JOINT_BROKEN; - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java deleted file mode 100644 index 6c1989b1..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBody.java +++ /dev/null @@ -1,224 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Backend-agnostic rigid body facade. - * TODO: rigid body for now, soft body and particles need to be addressed - *

        - * The API defines shared behavior for different backends, but backends may still differ in - * solver details, contact reporting, and exact motion response. - */ -@Deprecated(forRemoval = true) -public interface PhysicsBody { - - void setPosition(float x, float y, float z); - - void setPosition(@Nonnull Vector3f pos); - - @Nonnull - Vector3f getPosition(); - - /** - * Copy the current world-space center of mass into the provided vector. - * Backends should override this to avoid allocating on hot sync paths. - */ - default void getPosition(@Nonnull Vector3f out) { - out.set(getPosition()); - } - - void setRotation(float x, float y, float z, float w); - - void setRotation(@Nonnull Quaternionf rot); - - @Nonnull - Quaternionf getRotation(); - - /** - * Copy the current world-space rotation into the provided quaternion. - * Backends should override this to avoid allocating on hot sync paths. - */ - default void getRotation(@Nonnull Quaternionf out) { - out.set(getRotation()); - } - - void setRestitution(float restitution); - - float getRestitution(); - - void setFriction(float friction); - - float getFriction(); - - @Nonnull - PhysicsBodyType getBodyType(); - - void setBodyType(@Nonnull PhysicsBodyType bodyType); - - boolean isStatic(); - - default boolean isDynamic() { - return getBodyType() == PhysicsBodyType.DYNAMIC; - } - - boolean isKinematic(); - - void setKinematic(boolean kinematic); - - void activate(); - - boolean isActive(); - - boolean isSleeping(); - - void sleep(); - - float getMass(); - - void setMass(float mass); - - @Nonnull - Vector3f getLinearVelocity(); - - /** - * Copy the current linear velocity into the provided vector. - */ - default void getLinearVelocity(@Nonnull Vector3f out) { - out.set(getLinearVelocity()); - } - - void setLinearVelocity(@Nonnull Vector3f vel); - - void setLinearVelocity(float x, float y, float z); - - @Nonnull - Vector3f getAngularVelocity(); - - /** - * Copy the current angular velocity into the provided vector. - */ - default void getAngularVelocity(@Nonnull Vector3f out) { - out.set(getAngularVelocity()); - } - - void setAngularVelocity(@Nonnull Vector3f vel); - - void setAngularVelocity(float x, float y, float z); - - float getLinearDamping(); - - void setLinearDamping(float damping); - - float getAngularDamping(); - - void setAngularDamping(float damping); - - default void setDamping(float linearDamping, float angularDamping) { - setLinearDamping(linearDamping); - setAngularDamping(angularDamping); - } - - void applyCentralForce(@Nonnull Vector3f force); - - void applyCentralForce(float x, float y, float z); - - void applyForce(@Nonnull Vector3f force, @Nonnull Vector3f offset); - - /** - * Applies force at a world-space offset without requiring caller-side vector allocation. - * Backends should override this to avoid the default temporary vector fallback. - */ - default void applyForce(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - applyForce(new Vector3f(x, y, z), new Vector3f(offsetX, offsetY, offsetZ)); - } - - void applyCentralImpulse(@Nonnull Vector3f impulse); - - void applyCentralImpulse(float x, float y, float z); - - void applyImpulse(@Nonnull Vector3f impulse, @Nonnull Vector3f offset); - - /** - * Applies impulse at a world-space offset without requiring caller-side vector allocation. - * Backends should override this to avoid the default temporary vector fallback. - */ - default void applyImpulse(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - applyImpulse(new Vector3f(x, y, z), new Vector3f(offsetX, offsetY, offsetZ)); - } - - void applyTorque(@Nonnull Vector3f torque); - - /** - * Applies torque without requiring caller-side vector allocation. - * Backends should override this to avoid the default temporary vector fallback. - */ - default void applyTorque(float x, float y, float z) { - applyTorque(new Vector3f(x, y, z)); - } - - void applyTorqueImpulse(@Nonnull Vector3f torqueImpulse); - - /** - * Applies torque impulse without requiring caller-side vector allocation. - * Backends should override this to avoid the default temporary vector fallback. - */ - default void applyTorqueImpulse(float x, float y, float z) { - applyTorqueImpulse(new Vector3f(x, y, z)); - } - - void clearForces(); - - /** - * Returns whether this body is configured as a sensor/trigger. - *

        - * Sensor bodies participate in overlap/contact callbacks but do not produce - * normal physical contact response or collision resolution. - */ - boolean isSensor(); - - /** - * Sets whether this body should behave as a sensor/trigger. - *

        - * When enabled, the body can overlap other bodies without acting as a solid - * collider. - */ - void setSensor(boolean sensor); - - int getCollisionGroup(); - - int getCollisionMask(); - - void setCollisionFilter(int group, int mask); - - boolean isContinuousCollisionEnabled(); - - void setContinuousCollisionEnabled(boolean enabled); - - @Nonnull - ShapeType getShapeType(); - - @Nullable - Vector3f getBoxHalfExtents(); - - float getSphereRadius(); - - float getHalfHeight(); - - @Nonnull - PhysicsAxis getShapeAxis(); - - float getCenterOfMassOffsetY(); -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java index c1e4afbb..75ab51a0 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java @@ -9,9 +9,8 @@ /** * Immutable copy of body state captured from live backend state. * - *

        Snapshots deliberately contain shape metadata instead of a live {@link PhysicsBody} handle so - * they can be published to world-thread readers and debug systems without escaping backend - * ownership.

        + *

        Snapshots deliberately contain shape metadata instead of live backend handles so they can be + * published to world-thread readers and debug systems without escaping backend ownership.

        */ public final class PhysicsBodySnapshot { @@ -302,67 +301,6 @@ public static PhysicsBodySnapshot of(float positionX, shapeAxis); } - @Nonnull - public static PhysicsBodySnapshot from(@Nonnull PhysicsBody body) { - return from(body, null); - } - - @Nonnull - public static PhysicsBodySnapshot from(@Nonnull PhysicsBody body, - @Nullable PhysicsBodySnapshot previous) { - Objects.requireNonNull(body, "body"); - boolean sleeping = body.isSleeping(); - if (sleeping && previous != null && previous.sleeping()) { - return previous; - } - - Vector3f position = new Vector3f(); - Quaternionf rotation = new Quaternionf(); - Vector3f linearVelocity = new Vector3f(); - Vector3f angularVelocity = new Vector3f(); - body.getPosition(position); - body.getRotation(rotation); - PhysicsBodyType bodyType = body.getBodyType(); - if (!sleeping && bodyType != PhysicsBodyType.STATIC) { - body.getLinearVelocity(linearVelocity); - body.getAngularVelocity(angularVelocity); - } - Vector3f boxHalfExtents = body.getBoxHalfExtents(); - return new PhysicsBodySnapshot(position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z, - bodyType, - sleeping, - body.isSensor(), - body.getMass(), - body.getFriction(), - body.getRestitution(), - body.getLinearDamping(), - body.getAngularDamping(), - body.getCollisionGroup(), - body.getCollisionMask(), - body.isContinuousCollisionEnabled(), - body.getCenterOfMassOffsetY(), - body.getShapeType(), - boxHalfExtents != null, - boxHalfExtents != null ? boxHalfExtents.x : 0.0f, - boxHalfExtents != null ? boxHalfExtents.y : 0.0f, - boxHalfExtents != null ? boxHalfExtents.z : 0.0f, - body.getSphereRadius(), - body.getHalfHeight(), - body.getShapeAxis()); - } - @Nonnull public Vector3f position() { return new Vector3f(positionX, positionY, positionZ); diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java deleted file mode 100644 index dcaae11d..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContact.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -@Deprecated(forRemoval = true) -public record PhysicsContact(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f pointOnA, - @Nonnull Vector3f pointOnB, - @Nonnull Vector3f normalOnB, - float distance, - float impulse) { - - public PhysicsContact { - pointOnA = new Vector3f(pointOnA); - pointOnB = new Vector3f(pointOnB); - normalOnB = new Vector3f(normalOnB); - } - - @Nonnull - @Override - public Vector3f pointOnA() { - return new Vector3f(pointOnA); - } - - @Nonnull - @Override - public Vector3f pointOnB() { - return new Vector3f(pointOnB); - } - - @Nonnull - @Override - public Vector3f normalOnB() { - return new Vector3f(normalOnB); - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java deleted file mode 100644 index eefe396f..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJoint.java +++ /dev/null @@ -1,108 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * A live joint constraint between two bodies. - * Anchors are local to each body. - * Axis is only meaningful for hinge and slider joints. - * Limits and motor controls are mainly for hinge and slider joints. - */ -@Deprecated(forRemoval = true) -public interface PhysicsJoint { - - @Nonnull - PhysicsJointType getType(); - - @Nonnull - PhysicsBody getBodyA(); - - @Nonnull - PhysicsBody getBodyB(); - - boolean isEnabled(); - - void setEnabled(boolean enabled); - - @Nonnull - Vector3f getAnchorA(); - - @Nonnull - Vector3f getAnchorB(); - - /** - * Return the joint axis when the joint type uses one. - * Fixed, point, and spring joints may return null. - */ - @Nullable - Vector3f getAxis(); - - /** - * Return the lower joint limit when the joint supports limits. - * Hinge and slider joints use this value. - */ - float getLowerLimit(); - - /** - * Return the upper joint limit when the joint supports limits. - * Hinge and slider joints use this value. - */ - float getUpperLimit(); - - /** - * Set the joint limits when the joint supports them. - * Backends may ignore this for joints that do not use limits. - */ - void setLimits(float lowerLimit, float upperLimit); - - /** - * Return whether the motor is enabled for joints that support motors. - */ - boolean isMotorEnabled(); - - /** - * Enable or disable the motor when the joint supports motors. - */ - void setMotorEnabled(boolean enabled); - - /** - * Return the configured motor target velocity. - */ - float getMotorTargetVelocity(); - - /** - * Return the configured motor max force. - */ - float getMotorMaxForce(); - - /** - * Configure the joint motor when the joint supports motors. - */ - void setMotor(float targetVelocity, float maxForce); - - /** - * Return configured spring rest length for spring joints. - * Returns {@link Float#NaN} for non-spring joints. - */ - default float getSpringRestLength() { - return Float.NaN; - } - - /** - * Return configured spring stiffness for spring joints. - * Returns {@link Float#NaN} for non-spring joints. - */ - default float getSpringStiffness() { - return Float.NaN; - } - - /** - * Return configured spring damping for spring joints. - * Returns {@link Float#NaN} for non-spring joints. - */ - default float getSpringDamping() { - return Float.NaN; - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java deleted file mode 100644 index aa549cfb..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsJointType.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.hytalemodding.impulse.api; - -/** - * Joint kinds supported by the shared API. - */ -public enum PhysicsJointType { - - /** - * Locks two bodies together - */ - FIXED, - - /** - * Keeps one anchor between the bodies and allows free rotation - */ - POINT, - - /** - * Rotation around one axis - */ - HINGE, - - /** - * Movement along a direction/axis - */ - SLIDER, - - /** - * Spring behavior between two bodies - */ - SPRING -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java deleted file mode 100644 index a474f8be..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRayHit.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -@Deprecated(forRemoval = true) -public record PhysicsRayHit(@Nonnull PhysicsBody body, - @Nonnull Vector3f point, - @Nonnull Vector3f normal, - float fraction, - float distance) { - - public PhysicsRayHit { - point = new Vector3f(point); - normal = new Vector3f(normal); - } - - @Nonnull - @Override - public Vector3f point() { - return new Vector3f(point); - } - - @Nonnull - @Override - public Vector3f normal() { - return new Vector3f(normal); - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java deleted file mode 100644 index e7f2332c..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsSpace.java +++ /dev/null @@ -1,432 +0,0 @@ -package dev.hytalemodding.impulse.api; - -import dev.hytalemodding.impulse.api.capability.PhysicsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityDescriptor; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Live simulation container for a physics backend. - *

        - * One world can have different physics spaces of different space size. - *

        - *

        - * Backends are expected to agree on the meaning of these operations, but not on identical - * numerical output. Contact ordering, solver settling, and ray hit details can differ slightly - * between implementations. - *

          - *
        • This type must not be assumed to be thread-safe.
        • - *
        • All mutations, stepping, and live snapshots must happen from a single serialized backend - * lane.
        • - *
        • The backend lane is a logical execution context, not a public Java thread identity. It may - * be backed by pooled executor lanes, but it must not execute the same space concurrently.
        • - *
        • If other threads need to interact, queue work onto the backend lane.
        • - *
        - */ -@Deprecated(forRemoval = true) -public interface PhysicsSpace { - - @Nonnull - SpaceId id(); - - @Nonnull - BackendId backendId(); - - void step(float dt); - - default void step(float dt, @Nonnull PhysicsBackendEventSink events) { - step(dt); - } - - void setGravity(float x, float y, float z); - - @Nonnull - Vector3f getGravity(); - - void addBody(@Nonnull PhysicsBody body); - - void removeBody(@Nonnull PhysicsBody body); - - @Nonnull - List getBodies(); - - /** - * Returns the number of bodies in this space. - * - *

        Default implementation delegates to {@link #getBodies()}. - * Backends should override to avoid list allocation.

        - */ - default int bodyCount() { - return getBodies().size(); - } - - /** - * Iterates all bodies without allocating a copied list. - * - *

        Default implementation delegates to {@link #getBodies()}. - * Backends should override to iterate internal lists directly.

        - */ - default void forEachBody(@Nonnull Consumer consumer) { - for (PhysicsBody body : getBodies()) { - consumer.accept(body); - } - } - - /** - * Returns whether the given body is currently attached to this space. - * - *

        Default implementation delegates to {@link #getBodies()}. - * Backends should override to avoid list allocation.

        - */ - default boolean containsBody(@Nonnull PhysicsBody body) { - return getBodies().contains(body); - } - - /** - * Publishes backend-lane body snapshots for systems that must not repeatedly - * read mutable backend bodies. - */ - default void snapshotBodies(@Nonnull Consumer consumer) { - snapshotBodies(body -> null, consumer); - } - - /** - * Publishes backend-lane body snapshots, allowing callers to provide the last - * published snapshot so backends can avoid stable sleeping-body refreshes. - */ - default void snapshotBodies(@Nonnull Function previousSnapshots, - @Nonnull Consumer consumer) { - snapshotBodies(previousSnapshots, (_, snapshot) -> consumer.accept(snapshot)); - } - - /** - * Publishes backend-lane body snapshots with the live body available only during the callback. - */ - default void snapshotBodies(@Nonnull Function previousSnapshots, - @Nonnull BiConsumer consumer) { - forEachBody(body -> consumer.accept(body, PhysicsBodySnapshot.from(body, previousSnapshots.apply(body)))); - } - - /** - * Publishes backend-lane snapshots for a caller-selected subset of bodies. - * - *

        This lets backends batch-read only bodies that higher-level systems actually - * need to publish. Callers should pass bodies that currently belong to this space.

        - */ - default void snapshotBodies(@Nonnull Iterable selectedBodies, - @Nonnull Function previousSnapshots, - @Nonnull Consumer consumer) { - snapshotBodies(selectedBodies, previousSnapshots, (_, snapshot) -> consumer.accept(snapshot)); - } - - /** - * Publishes backend-lane snapshots for a caller-selected subset of bodies with the live body - * available only during the callback. - */ - default void snapshotBodies(@Nonnull Iterable selectedBodies, - @Nonnull Function previousSnapshots, - @Nonnull BiConsumer consumer) { - for (PhysicsBody body : selectedBodies) { - consumer.accept(body, PhysicsBodySnapshot.from(body, previousSnapshots.apply(body))); - } - } - - /** - * Returns optional backend runtime counters for diagnostics. - * - *

        The default implementation reports no backend-specific runtime counters so - * existing backends do not need to synthesize opaque internal state.

        - */ - @Nonnull - default PhysicsRuntimeStats getRuntimeStats() { - return PhysicsRuntimeStats.unavailable(); - } - - /** - * Resets backend-native phase counters collected by {@link #getStepPhaseStats()}. - * - *

        Backends that do not expose phase timings may keep the default no-op behavior.

        - */ - default void resetStepPhaseStats() { - } - - /** - * Returns backend-native phase timings collected since the last reset. - * - *

        These counters are intended for profiling only. Unsupported backends should return - * {@link PhysicsStepPhaseStats#unavailable()}.

        - */ - @Nonnull - default PhysicsStepPhaseStats getStepPhaseStats() { - return PhysicsStepPhaseStats.unavailable(); - } - - /** - * Returns an optional backend-specific capability for this space. - * - *

        The default implementation supports spaces that directly implement a capability - * interface. Backends that expose separate capability objects should override this method.

        - */ - @Nonnull - default Optional getCapability(@Nonnull Class type) { - Objects.requireNonNull(type, "type"); - if (type.isInstance(this)) { - return Optional.of(type.cast(this)); - } - return Optional.empty(); - } - - /** - * Returns metadata for backend-specific capabilities exposed by this space. - */ - @Nonnull - default List getCapabilityDescriptors() { - return List.of(); - } - - @Nonnull - PhysicsBody createStaticPlane(float groundY); - - @Nonnull - PhysicsBody createBox(float halfX, float halfY, float halfZ, float mass); - - @Nonnull - PhysicsBody createBox(@Nonnull Vector3f halfExtents, float mass); - - @Nonnull - PhysicsBody createSphere(float radius, float mass); - - @Nonnull - PhysicsBody createCapsule(float radius, float halfHeight, @Nonnull PhysicsAxis axis, - float mass); - - @Nonnull - PhysicsBody createCylinder(float radius, float halfHeight, @Nonnull PhysicsAxis axis, - float mass); - - @Nonnull - PhysicsBody createCone(float radius, float halfHeight, @Nonnull PhysicsAxis axis, float mass); - - @Nonnull - Optional raycastClosest(@Nonnull Vector3f from, @Nonnull Vector3f to); - - @Nonnull - List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f to); - - @Nonnull - List getContacts(); - - @Nonnull - default List getContacts(int maxContacts) { - if (maxContacts <= 0) { - return List.of(); - } - List contacts = getContacts(); - if (contacts.size() <= maxContacts) { - return contacts; - } - return List.copyOf(contacts.subList(0, maxContacts)); - } - - /** - * Returns the number of active contacts in this space. - * - *

        Default implementation delegates to {@link #getContacts()}. - * Backends should override to avoid contact-list allocation.

        - */ - default int contactCount() { - return getContacts().size(); - } - - /** - * Create a fixed joint. - * Anchors are local to each body. - * The joint locks the bodies together with no relative translation or rotation. - */ - @Nonnull - PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB); - - @Nonnull - default PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ) { - return createFixedJoint(bodyA, - bodyB, - new Vector3f(anchorAX, anchorAY, anchorAZ), - new Vector3f(anchorBX, anchorBY, anchorBZ)); - } - - /** - * Create a point joint. - * Anchors are local to each body. - * The joint keeps the two anchors together but allows free rotation. - */ - @Nonnull - PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB); - - @Nonnull - default PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ) { - return createPointJoint(bodyA, - bodyB, - new Vector3f(anchorAX, anchorAY, anchorAZ), - new Vector3f(anchorBX, anchorBY, anchorBZ)); - } - - /** - * Create a hinge joint. - * Anchors are local to each body. - * Axis describes the hinge axis in joint local space. - * The joint allows rotation around that axis. - */ - @Nonnull - PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis); - - @Nonnull - default PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ) { - return createHingeJoint(bodyA, - bodyB, - new Vector3f(anchorAX, anchorAY, anchorAZ), - new Vector3f(anchorBX, anchorBY, anchorBZ), - new Vector3f(axisX, axisY, axisZ)); - } - - /** - * Create a slider joint. - * Anchors are local to each body. - * Axis describes the slide axis in joint local space. - * The joint allows translation along that axis. - */ - @Nonnull - PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis); - - @Nonnull - default PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ) { - return createSliderJoint(bodyA, - bodyB, - new Vector3f(anchorAX, anchorAY, anchorAZ), - new Vector3f(anchorBX, anchorBY, anchorBZ), - new Vector3f(axisX, axisY, axisZ)); - } - - /** - * Create a spring joint. - * Anchors are local to each body. - * Rest length, stiffness, and damping define the spring behavior. - */ - @Nonnull - PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping); - - @Nonnull - default PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float restLength, - float stiffness, - float damping) { - return createSpringJoint(bodyA, - bodyB, - new Vector3f(anchorAX, anchorAY, anchorAZ), - new Vector3f(anchorBX, anchorBY, anchorBZ), - restLength, - stiffness, - damping); - } - - void removeJoint(@Nonnull PhysicsJoint joint); - - @Nonnull - List getJoints(); - - /** - * Returns the number of joints in this space. - * - *

        Default implementation delegates to {@link #getJoints()}. - * Backends should override to avoid list allocation.

        - */ - default int jointCount() { - return getJoints().size(); - } - - /** - * Iterates all joints without allocating a copied list. - * - *

        Default implementation delegates to {@link #getJoints()}. - * Backends should override to iterate internal lists directly.

        - */ - default void forEachJoint(@Nonnull Consumer consumer) { - for (PhysicsJoint joint : getJoints()) { - consumer.accept(joint); - } - } - - /** - * Release backend resources for this space. - *

        - * Default implementation is a no-op. - */ - default void close() { - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java index ff40d645..e926165b 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java @@ -3,9 +3,9 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * PhysicsSpace identifier + * Physics space identifier. * - * @param value + * @param value backend-local numeric space id */ public record SpaceId(int value) { diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java deleted file mode 100644 index 73266af1..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuningCapability.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -import javax.annotation.Nonnull; - -/** - * Optional backend capability for tuning dynamic body sleep behavior. - */ -@Deprecated(forRemoval = true) -public interface PhysicsActivationTuningCapability extends PhysicsCapability { - - PhysicsCapabilityDescriptor DESCRIPTOR = new PhysicsCapabilityDescriptor( - new PhysicsCapabilityId("impulse:activation_tuning"), - "Activation tuning", - "Configures dynamic body sleep thresholds"); - - void setActivationTuning(@Nonnull PhysicsActivationTuning tuning); -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java deleted file mode 100644 index 75340cae..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapability.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -import dev.hytalemodding.impulse.api.PhysicsBackendEventKind; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import java.util.Set; -import javax.annotation.Nonnull; - -/** - * Optional backend capability indicating which post-step backend events may be emitted. - */ -@Deprecated(forRemoval = true) -public interface PhysicsBackendEventsCapability extends PhysicsCapability { - - PhysicsCapabilityDescriptor DESCRIPTOR = new PhysicsCapabilityDescriptor( - new PhysicsCapabilityId("impulse:backend_events"), - "Backend events", - "Reports backend event kinds emitted during physics steps"); - - @Nonnull - Set supportedEventKinds(); - - default boolean supportsEventKind(@Nonnull PhysicsBackendEventKind kind) { - return supportedEventKinds().contains(kind); - } - - default boolean supportsContactPhase(@Nonnull PhysicsContactPhase phase) { - return supportsEventKind(phase.eventKind()); - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java deleted file mode 100644 index 3861c68d..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapability.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -/** - * Marker for optional backend capabilities exposed by a physics space. - */ -@Deprecated(forRemoval = true) -public interface PhysicsCapability { -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java deleted file mode 100644 index f454dd6e..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityDescriptor.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -import java.util.Objects; -import javax.annotation.Nonnull; - -/** - * Public metadata for an optional backend capability. - * - * @param id stable capability identifier - * @param displayName concise user-facing name - * @param description concise capability description - */ -public record PhysicsCapabilityDescriptor(@Nonnull PhysicsCapabilityId id, - @Nonnull String displayName, - @Nonnull String description) { - - public PhysicsCapabilityDescriptor { - Objects.requireNonNull(id, "id"); - Objects.requireNonNull(displayName, "displayName"); - Objects.requireNonNull(description, "description"); - if (displayName.isBlank()) { - throw new IllegalArgumentException("displayName cannot be blank"); - } - if (description.isBlank()) { - throw new IllegalArgumentException("description cannot be blank"); - } - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java deleted file mode 100644 index 9e56f85f..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsContinuousCollisionCapability.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -/** - * Optional backend capability indicating support for continuous collision detection. - */ -@Deprecated(forRemoval = true) -public interface PhysicsContinuousCollisionCapability extends PhysicsCapability { - - PhysicsCapabilityDescriptor DESCRIPTOR = new PhysicsCapabilityDescriptor( - new PhysicsCapabilityId("impulse:continuous_collision"), - "Continuous collision", - "Supports continuous collision detection on bodies"); - - default boolean supportsContinuousCollision() { - return true; - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java deleted file mode 100644 index 98ea6e71..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsExtensionSettingsCapability.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -import java.util.Map; -import javax.annotation.Nonnull; - -/** - * Optional backend capability for applying capability-keyed extension settings. - * - *

        The setting values are strings so core can persist and forward unknown backend - * settings without depending on backend-owned Java types.

        - */ -@Deprecated(forRemoval = true) -public interface PhysicsExtensionSettingsCapability extends PhysicsCapability { - - PhysicsCapabilityDescriptor DESCRIPTOR = new PhysicsCapabilityDescriptor( - new PhysicsCapabilityId("impulse:extension_settings"), - "Extension settings", - "Applies capability-keyed backend extension settings"); - - void applyExtensionSettings(@Nonnull PhysicsCapabilityId capabilityId, - @Nonnull Map values); -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java deleted file mode 100644 index 3bb80975..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuningCapability.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -import javax.annotation.Nonnull; - -/** - * Optional backend capability for tuning solver cost versus stability. - */ -@Deprecated(forRemoval = true) -public interface PhysicsSolverTuningCapability extends PhysicsCapability { - - PhysicsCapabilityDescriptor DESCRIPTOR = new PhysicsCapabilityDescriptor( - new PhysicsCapabilityId("impulse:solver_tuning"), - "Solver tuning", - "Configures solver and stabilization iterations"); - - void setSolverTuning(@Nonnull PhysicsSolverTuning tuning); -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java deleted file mode 100644 index d2d44c55..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsVoxelTerrainCapability.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -import dev.hytalemodding.impulse.api.PhysicsBody; -import javax.annotation.Nonnull; - -/** - * Optional backend capability for native voxel terrain collision. - */ -@Deprecated(forRemoval = true) -public interface PhysicsVoxelTerrainCapability extends PhysicsCapability { - - PhysicsCapabilityDescriptor DESCRIPTOR = new PhysicsCapabilityDescriptor( - new PhysicsCapabilityId("impulse:voxel_terrain"), - "Voxel terrain", - "Creates native static voxel terrain bodies"); - - /** - * Creates a static terrain body made from occupied voxel cells. - * - *

        The {@code voxelCoordinates} array stores triples of local integer grid coordinates: - * {@code x0, y0, z0, x1, y1, z1, ...}. The returned body can then be positioned at the - * section/world origin like any other static body.

        - */ - @Nonnull - PhysicsBody createVoxelTerrain(float voxelSizeX, - float voxelSizeY, - float voxelSizeZ, - @Nonnull int[] voxelCoordinates); - - /** - * Couples two adjacent voxel terrain bodies so the backend can treat their shared boundary - * as continuous terrain instead of two unrelated voxel sets. - * - *

        The shift is expressed in voxel units from {@code bodyA}'s local voxel origin to - * {@code bodyB}'s local voxel origin. Backends that do not use native adjacency hints can keep - * the default no-op behavior.

        - */ - default void combineVoxelTerrains(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - int shiftX, - int shiftY, - int shiftZ) { - } -} diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java deleted file mode 100644 index 289e4841..00000000 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntime.java +++ /dev/null @@ -1,897 +0,0 @@ -package dev.hytalemodding.impulse.api.runtime.legacy; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.PhysicsRayHit; -import dev.hytalemodding.impulse.api.PhysicsRuntimeStats; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuningCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; -import dev.hytalemodding.impulse.api.capability.PhysicsContinuousCollisionCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsExtensionSettingsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuningCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsVoxelTerrainCapability; -import dev.hytalemodding.impulse.api.runtime.BackendBodyIdSource; -import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; -import dev.hytalemodding.impulse.api.runtime.BackendContactSink; -import dev.hytalemodding.impulse.api.runtime.BackendExtensionSettingsSource; -import dev.hytalemodding.impulse.api.runtime.BackendJointType; -import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeStatsSink; -import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; -import dev.hytalemodding.impulse.api.runtime.BackendVec3Sink; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Id-only runtime facade over the legacy live-object backend API. - */ -@Deprecated(forRemoval = true) -public final class LegacyPhysicsBackendRuntime implements PhysicsBackendRuntime { - - @Nonnull - private final PhysicsBackend backend; - private final Map spaces = new HashMap<>(); - private long nextBodyId = 1L; - private long nextJointId = 1L; - - public LegacyPhysicsBackendRuntime(@Nonnull PhysicsBackend backend) { - this.backend = Objects.requireNonNull(backend, "backend"); - } - - @Override - public int createSpace(@Nonnull SpaceId requestedId) { - Objects.requireNonNull(requestedId, "requestedId"); - if (spaces.containsKey(requestedId.value())) { - throw new IllegalArgumentException("Physics space id=" + requestedId + " is already registered"); - } - PhysicsSpace space = backend.createSpace(requestedId); - if (!requestedId.equals(space.id())) { - try { - space.close(); - } catch (RuntimeException ignored) { - } - throw new IllegalStateException("Backend " + backend.getId() - + " created space id " + space.id() + " but expected " + requestedId); - } - spaces.put(requestedId.value(), new SpaceState(space)); - return requestedId.value(); - } - - @Override - public void destroySpace(int spaceId) { - SpaceState state = spaces.remove(spaceId); - if (state != null) { - state.space.close(); - } - } - - @Override - public void close() { - RuntimeException failure = null; - for (Integer spaceId : new ArrayList<>(spaces.keySet())) { - try { - destroySpace(spaceId); - } catch (RuntimeException exception) { - if (failure == null) { - failure = exception; - } else { - failure.addSuppressed(exception); - } - } - } - if (failure != null) { - throw failure; - } - } - - @Override - public void step(int spaceId, float dt) { - requireSpace(spaceId).space.step(dt); - } - - @Override - public void setGravity(int spaceId, float x, float y, float z) { - requireSpace(spaceId).space.setGravity(x, y, z); - } - - @Override - public void getGravity(int spaceId, @Nonnull BackendVec3Sink sink) { - Vector3f gravity = requireSpace(spaceId).space.getGravity(); - sink.accept(gravity.x, gravity.y, gravity.z); - } - - @Override - public long createBody(int spaceId, - int shapeTypeCode, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - int axisCode, - float groundY, - float mass, - int bodyTypeCode, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW) { - SpaceState state = requireSpace(spaceId); - PhysicsBody body = createLiveBody(state.space, - shapeTypeCode, - halfExtentX, - halfExtentY, - halfExtentZ, - radius, - halfHeight, - axisCode, - groundY, - mass); - body.setBodyType(BackendRuntimeCodes.bodyType(bodyTypeCode)); - body.setPosition(positionX, positionY, positionZ); - body.setRotation(rotationX, rotationY, rotationZ, rotationW); - long bodyId = nextBodyId++; - state.space.addBody(body); - state.bodiesById.put(bodyId, body); - state.bodyIdsByBody.put(body, bodyId); - return bodyId; - } - - @Override - public boolean supportsVoxelTerrain(int spaceId) { - return requireSpace(spaceId).space.getCapability(PhysicsVoxelTerrainCapability.class).isPresent(); - } - - @Override - public long createVoxelTerrain(int spaceId, - float voxelSizeX, - float voxelSizeY, - float voxelSizeZ, - @Nonnull int[] voxelCoordinates, - float positionX, - float positionY, - float positionZ, - float friction, - float restitution, - int collisionGroup, - int collisionMask) { - Objects.requireNonNull(voxelCoordinates, "voxelCoordinates"); - SpaceState state = requireSpace(spaceId); - PhysicsVoxelTerrainCapability capability = requireVoxelTerrainCapability(state); - PhysicsBody body = capability.createVoxelTerrain(voxelSizeX, - voxelSizeY, - voxelSizeZ, - voxelCoordinates); - body.setBodyType(PhysicsBodyType.STATIC); - body.setPosition(positionX, positionY, positionZ); - body.setFriction(friction); - body.setRestitution(restitution); - body.setCollisionFilter(collisionGroup, collisionMask); - - long bodyId = nextBodyId++; - state.space.addBody(body); - state.bodiesById.put(bodyId, body); - state.bodyIdsByBody.put(body, bodyId); - return bodyId; - } - - @Override - public void combineVoxelTerrains(int spaceId, - long bodyAId, - long bodyBId, - int shiftX, - int shiftY, - int shiftZ) { - SpaceState state = requireSpace(spaceId); - PhysicsVoxelTerrainCapability capability = requireVoxelTerrainCapability(state); - capability.combineVoxelTerrains(requireBody(spaceId, bodyAId), - requireBody(spaceId, bodyBId), - shiftX, - shiftY, - shiftZ); - } - - @Override - public void removeBody(int spaceId, long bodyId) { - SpaceState state = requireSpace(spaceId); - PhysicsBody body = state.bodiesById.remove(bodyId); - if (body == null) { - return; - } - state.bodyIdsByBody.remove(body); - state.space.removeBody(body); - } - - @Override - public int bodyCount(int spaceId) { - return requireSpace(spaceId).space.bodyCount(); - } - - @Override - public boolean containsBody(int spaceId, long bodyId) { - return requireSpace(spaceId).bodiesById.containsKey(bodyId); - } - - @Override - public boolean bodySnapshot(int spaceId, - long bodyId, - @Nonnull BackendBodySnapshotSink sink) { - SpaceState state = requireSpace(spaceId); - PhysicsBody body = state.bodiesById.get(bodyId); - if (body == null) { - return false; - } - emitBodySnapshot(bodyId, PhysicsBodySnapshot.from(body), sink); - return true; - } - - @Override - public void snapshotBodies(int spaceId, - @Nonnull BackendBodyIdSource bodyIds, - @Nonnull BackendBodySnapshotSink sink) { - SpaceState state = requireSpace(spaceId); - List selectedBodies = state.selectedSnapshotBodies; - selectedBodies.clear(); - bodyIds.forEachBodyId(bodyId -> { - PhysicsBody body = state.bodiesById.get(bodyId); - if (body != null) { - selectedBodies.add(body); - } - }); - if (selectedBodies.isEmpty()) { - return; - } - try { - state.space.snapshotBodies(selectedBodies, - body -> null, - (body, snapshot) -> { - Long bodyId = state.bodyIdsByBody.get(body); - if (bodyId != null) { - emitBodySnapshot(bodyId, snapshot, sink); - } - }); - } finally { - selectedBodies.clear(); - } - } - - @Override - public void setBodyTransform(int spaceId, - long bodyId, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW) { - PhysicsBody body = requireBody(spaceId, bodyId); - body.setPosition(positionX, positionY, positionZ); - body.setRotation(rotationX, rotationY, rotationZ, rotationW); - } - - @Override - public void setBodyPosition(int spaceId, long bodyId, float x, float y, float z) { - requireBody(spaceId, bodyId).setPosition(x, y, z); - } - - @Override - public void setBodyVelocity(int spaceId, - long bodyId, - float linearX, - float linearY, - float linearZ, - float angularX, - float angularY, - float angularZ) { - PhysicsBody body = requireBody(spaceId, bodyId); - body.setLinearVelocity(linearX, linearY, linearZ); - body.setAngularVelocity(angularX, angularY, angularZ); - } - - @Override - public void setBodyType(int spaceId, long bodyId, int bodyTypeCode) { - requireBody(spaceId, bodyId).setBodyType(BackendRuntimeCodes.bodyType(bodyTypeCode)); - } - - @Override - public void setBodyDamping(int spaceId, long bodyId, float linearDamping, float angularDamping) { - requireBody(spaceId, bodyId).setDamping(linearDamping, angularDamping); - } - - @Override - public void setBodyFriction(int spaceId, long bodyId, float friction) { - requireBody(spaceId, bodyId).setFriction(friction); - } - - @Override - public void setBodyRestitution(int spaceId, long bodyId, float restitution) { - requireBody(spaceId, bodyId).setRestitution(restitution); - } - - @Override - public void setBodyCollisionFilter(int spaceId, long bodyId, int group, int mask) { - requireBody(spaceId, bodyId).setCollisionFilter(group, mask); - } - - @Override - public void setBodySensor(int spaceId, long bodyId, boolean sensor) { - requireBody(spaceId, bodyId).setSensor(sensor); - } - - @Override - public void setBodyContinuousCollision(int spaceId, long bodyId, boolean enabled) { - requireBody(spaceId, bodyId).setContinuousCollisionEnabled(enabled); - } - - @Override - public boolean isBodyContinuousCollisionEnabled(int spaceId, long bodyId) { - return requireBody(spaceId, bodyId).isContinuousCollisionEnabled(); - } - - @Override - public void activateBody(int spaceId, long bodyId) { - requireBody(spaceId, bodyId).activate(); - } - - @Override - public void sleepBody(int spaceId, long bodyId) { - requireBody(spaceId, bodyId).sleep(); - } - - @Override - public void applyBodyImpulse(int spaceId, - long bodyId, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - PhysicsBody body = requireBody(spaceId, bodyId); - if (torque) { - body.applyTorqueImpulse(x, y, z); - } else if (hasOffset) { - body.applyImpulse(x, y, z, offsetX, offsetY, offsetZ); - } else { - body.applyCentralImpulse(x, y, z); - } - } - - @Override - public void applyBodyForce(int spaceId, - long bodyId, - float x, - float y, - float z, - boolean hasOffset, - float offsetX, - float offsetY, - float offsetZ, - boolean torque) { - PhysicsBody body = requireBody(spaceId, bodyId); - if (torque) { - body.applyTorque(x, y, z); - } else if (hasOffset) { - body.applyForce(x, y, z, offsetX, offsetY, offsetZ); - } else { - body.applyCentralForce(x, y, z); - } - } - - @Override - public long createJoint(int spaceId, - int jointTypeCode, - long bodyAId, - long bodyBId, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - SpaceState state = requireSpace(spaceId); - PhysicsBody bodyA = requireBody(spaceId, bodyAId); - PhysicsBody bodyB = requireBody(spaceId, bodyBId); - PhysicsJoint joint = createLiveJoint(state.space, - bodyA, - bodyB, - jointTypeCode, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - restLength, - stiffness, - damping, - lowerLimit, - upperLimit, - motorEnabled, - motorTargetVelocity, - motorMaxForce); - long jointId = nextJointId++; - state.jointsById.put(jointId, joint); - return jointId; - } - - @Override - public void removeJoint(int spaceId, long jointId) { - SpaceState state = requireSpace(spaceId); - PhysicsJoint joint = state.jointsById.remove(jointId); - if (joint == null) { - return; - } - state.space.removeJoint(joint); - } - - @Override - public int jointCount(int spaceId) { - return requireSpace(spaceId).space.jointCount(); - } - - @Override - public int jointType(int spaceId, long jointId) { - return BackendRuntimeCodes.jointTypeCode(toBackendJointType(requireJoint(spaceId, jointId).getType())); - } - - @Override - public long jointBodyA(int spaceId, long jointId) { - SpaceState state = requireSpace(spaceId); - PhysicsBody body = requireJoint(state, jointId).getBodyA(); - Long bodyId = state.bodyIdsByBody.get(body); - return bodyId != null ? bodyId : -1L; - } - - @Override - public long jointBodyB(int spaceId, long jointId) { - SpaceState state = requireSpace(spaceId); - PhysicsBody body = requireJoint(state, jointId).getBodyB(); - Long bodyId = state.bodyIdsByBody.get(body); - return bodyId != null ? bodyId : -1L; - } - - @Override - public boolean raycastClosest(int spaceId, - float fromX, - float fromY, - float fromZ, - float toX, - float toY, - float toZ, - @Nonnull BackendRayHitSink sink) { - SpaceState state = requireSpace(spaceId); - return state.space.raycastClosest(new Vector3f(fromX, fromY, fromZ), new Vector3f(toX, toY, toZ)) - .map(hit -> emitRayHit(state, hit, sink)) - .orElse(false); - } - - @Override - public int raycastAll(int spaceId, - float fromX, - float fromY, - float fromZ, - float toX, - float toY, - float toZ, - @Nonnull BackendRayHitSink sink) { - SpaceState state = requireSpace(spaceId); - int hits = 0; - for (PhysicsRayHit hit : state.space.raycastAll(new Vector3f(fromX, fromY, fromZ), - new Vector3f(toX, toY, toZ))) { - if (emitRayHit(state, hit, sink)) { - hits++; - } - } - return hits; - } - - @Override - public int contacts(int spaceId, @Nonnull BackendContactSink sink) { - SpaceState state = requireSpace(spaceId); - int contacts = 0; - for (PhysicsContact contact : state.space.getContacts()) { - Long bodyAId = state.bodyIdsByBody.get(contact.bodyA()); - Long bodyBId = state.bodyIdsByBody.get(contact.bodyB()); - if (bodyAId != null && bodyBId != null) { - Vector3f pointOnA = contact.pointOnA(); - Vector3f pointOnB = contact.pointOnB(); - Vector3f normalOnB = contact.normalOnB(); - sink.accept(bodyAId, - bodyBId, - pointOnA.x, - pointOnA.y, - pointOnA.z, - pointOnB.x, - pointOnB.y, - pointOnB.z, - normalOnB.x, - normalOnB.y, - normalOnB.z, - contact.distance(), - contact.impulse()); - contacts++; - } - } - return contacts; - } - - @Override - public int contacts(int spaceId, int maxContacts, @Nonnull BackendContactSink sink) { - if (maxContacts <= 0) { - return 0; - } - SpaceState state = requireSpace(spaceId); - int contacts = 0; - for (PhysicsContact contact : state.space.getContacts(maxContacts)) { - Long bodyAId = state.bodyIdsByBody.get(contact.bodyA()); - Long bodyBId = state.bodyIdsByBody.get(contact.bodyB()); - if (bodyAId != null && bodyBId != null) { - Vector3f pointOnA = contact.pointOnA(); - Vector3f pointOnB = contact.pointOnB(); - Vector3f normalOnB = contact.normalOnB(); - sink.accept(bodyAId, - bodyBId, - pointOnA.x, - pointOnA.y, - pointOnA.z, - pointOnB.x, - pointOnB.y, - pointOnB.z, - normalOnB.x, - normalOnB.y, - normalOnB.z, - contact.distance(), - contact.impulse()); - contacts++; - } - } - return contacts; - } - - @Override - public int contactCount(int spaceId) { - return requireSpace(spaceId).space.contactCount(); - } - - @Override - public void runtimeStats(int spaceId, @Nonnull BackendRuntimeStatsSink sink) { - PhysicsRuntimeStats stats = requireSpace(spaceId).space.getRuntimeStats(); - sink.accept(stats.bodyCount(), - stats.colliderCount(), - stats.activeBodyCount(), - stats.contactPairCount(), - stats.contactManifoldCount(), - stats.contactPointCount(), - stats.dynamicDynamicContactPairCount(), - stats.terrainContactPairCount(), - stats.activeIslandCount(), - stats.jointCount(), - stats.available()); - } - - @Override - public void resetStepPhaseStats(int spaceId) { - requireSpace(spaceId).space.resetStepPhaseStats(); - } - - @Override - public void stepPhaseStats(int spaceId, @Nonnull BackendStepPhaseStatsSink sink) { - PhysicsStepPhaseStats stats = requireSpace(spaceId).space.getStepPhaseStats(); - sink.accept(stats.stepNanos(), - stats.broadPhaseNanos(), - stats.narrowPhaseNanos(), - stats.solverNanos(), - stats.ccdNanos(), - stats.snapshotNanos(), - stats.available()); - } - - @Override - public boolean supportsContinuousCollision(int spaceId) { - return requireSpace(spaceId).space.getCapability(PhysicsContinuousCollisionCapability.class).isPresent(); - } - - @Override - public boolean supportsSolverTuning(int spaceId) { - return requireSpace(spaceId).space.getCapability(PhysicsSolverTuningCapability.class).isPresent(); - } - - @Override - public boolean supportsActivationTuning(int spaceId) { - return requireSpace(spaceId).space.getCapability(PhysicsActivationTuningCapability.class).isPresent(); - } - - @Override - public void applySolverTuning(int spaceId, @Nonnull PhysicsSolverTuning tuning) { - requireSpace(spaceId).space.getCapability(PhysicsSolverTuningCapability.class) - .ifPresent(capability -> capability.setSolverTuning(tuning)); - } - - @Override - public void applyActivationTuning(int spaceId, @Nonnull PhysicsActivationTuning tuning) { - requireSpace(spaceId).space.getCapability(PhysicsActivationTuningCapability.class) - .ifPresent(capability -> capability.setActivationTuning(tuning)); - } - - @Override - public void applyExtensionSettings(int spaceId, - @Nonnull PhysicsCapabilityId capabilityId, - @Nonnull BackendExtensionSettingsSource settings) { - requireSpace(spaceId).space.getCapability(PhysicsExtensionSettingsCapability.class) - .ifPresent(capability -> { - Map copied = new HashMap<>(); - settings.forEachSetting(copied::put); - capability.applyExtensionSettings(capabilityId, copied); - }); - } - - @Nonnull - private SpaceState requireSpace(int spaceId) { - SpaceState state = spaces.get(spaceId); - if (state == null) { - throw new IllegalArgumentException("Physics space id=" + spaceId + " is not registered"); - } - return state; - } - - @Nonnull - private PhysicsBody requireBody(int spaceId, long bodyId) { - PhysicsBody body = requireSpace(spaceId).bodiesById.get(bodyId); - if (body == null) { - throw new IllegalArgumentException("Physics body id=" + bodyId + " is not registered in space " + spaceId); - } - return body; - } - - @Nonnull - private static PhysicsVoxelTerrainCapability requireVoxelTerrainCapability(@Nonnull SpaceState state) { - return state.space.getCapability(PhysicsVoxelTerrainCapability.class) - .orElseThrow(() -> new UnsupportedOperationException("Legacy backend runtime does not support voxel terrain")); - } - - @Nonnull - private PhysicsJoint requireJoint(int spaceId, long jointId) { - return requireJoint(requireSpace(spaceId), jointId); - } - - @Nonnull - private static PhysicsJoint requireJoint(@Nonnull SpaceState state, long jointId) { - PhysicsJoint joint = state.jointsById.get(jointId); - if (joint == null) { - throw new IllegalArgumentException("Physics joint id=" + jointId + " is not registered"); - } - return joint; - } - - @Nonnull - private static PhysicsBody createLiveBody(@Nonnull PhysicsSpace space, - int shapeTypeCode, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - int axisCode, - float groundY, - float mass) { - ShapeType shapeType = BackendRuntimeCodes.shapeType(shapeTypeCode); - PhysicsAxis axis = BackendRuntimeCodes.axis(axisCode); - return switch (shapeType) { - case BOX -> space.createBox(halfExtentX, halfExtentY, halfExtentZ, mass); - case SPHERE -> space.createSphere(radius, mass); - case CAPSULE -> space.createCapsule(radius, halfHeight, axis, mass); - case CYLINDER -> space.createCylinder(radius, halfHeight, axis, mass); - case CONE -> space.createCone(radius, halfHeight, axis, mass); - case PLANE -> space.createStaticPlane(groundY); - default -> throw new IllegalArgumentException("Unsupported shape " + shapeType); - }; - } - - @Nonnull - private static PhysicsJoint createLiveJoint(@Nonnull PhysicsSpace space, - @Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - int jointTypeCode, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping, - float lowerLimit, - float upperLimit, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce) { - BackendJointType type = BackendRuntimeCodes.jointType(jointTypeCode); - PhysicsJoint joint = switch (type) { - case FIXED -> space.createFixedJoint(bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ); - case POINT -> space.createPointJoint(bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ); - case HINGE -> space.createHingeJoint(bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ); - case SLIDER -> space.createSliderJoint(bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ); - case SPRING -> space.createSpringJoint(bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - restLength, - stiffness, - damping); - }; - if (type == BackendJointType.HINGE || type == BackendJointType.SLIDER) { - joint.setLimits(lowerLimit, upperLimit); - joint.setMotor(motorTargetVelocity, motorMaxForce); - joint.setMotorEnabled(motorEnabled); - } - return joint; - } - - private static boolean emitRayHit(@Nonnull SpaceState state, - @Nonnull PhysicsRayHit hit, - @Nonnull BackendRayHitSink sink) { - Long bodyId = state.bodyIdsByBody.get(hit.body()); - if (bodyId == null) { - return false; - } - Vector3f point = hit.point(); - Vector3f normal = hit.normal(); - sink.accept(bodyId, - point.x, - point.y, - point.z, - normal.x, - normal.y, - normal.z, - hit.fraction(), - hit.distance()); - return true; - } - - private static void emitBodySnapshot(long bodyId, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull BackendBodySnapshotSink sink) { - sink.accept(bodyId, - BackendRuntimeCodes.shapeTypeCode(snapshot.shapeType()), - BackendRuntimeCodes.bodyTypeCode(snapshot.bodyType()), - snapshot.positionX(), - snapshot.positionY(), - snapshot.positionZ(), - snapshot.rotationX(), - snapshot.rotationY(), - snapshot.rotationZ(), - snapshot.rotationW(), - snapshot.linearVelocityX(), - snapshot.linearVelocityY(), - snapshot.linearVelocityZ(), - snapshot.angularVelocityX(), - snapshot.angularVelocityY(), - snapshot.angularVelocityZ(), - snapshot.sleeping(), - snapshot.sensor(), - snapshot.mass(), - snapshot.friction(), - snapshot.restitution(), - snapshot.linearDamping(), - snapshot.angularDamping(), - snapshot.collisionGroup(), - snapshot.collisionMask(), - snapshot.continuousCollisionEnabled(), - snapshot.centerOfMassOffsetY(), - snapshot.hasBoxHalfExtents(), - snapshot.boxHalfExtentX(), - snapshot.boxHalfExtentY(), - snapshot.boxHalfExtentZ(), - snapshot.sphereRadius(), - snapshot.halfHeight(), - BackendRuntimeCodes.axisCode(snapshot.shapeAxis())); - } - - @Nonnull - private static BackendJointType toBackendJointType(@Nonnull PhysicsJointType type) { - return switch (type) { - case FIXED -> BackendJointType.FIXED; - case POINT -> BackendJointType.POINT; - case HINGE -> BackendJointType.HINGE; - case SLIDER -> BackendJointType.SLIDER; - case SPRING -> BackendJointType.SPRING; - }; - } - - private static final class SpaceState { - - @Nonnull - private final PhysicsSpace space; - private final Map bodiesById = new HashMap<>(); - private final Map bodyIdsByBody = new IdentityHashMap<>(); - private final Map jointsById = new HashMap<>(); - private final List selectedSnapshotBodies = new ArrayList<>(); - - private SpaceState(@Nonnull PhysicsSpace space) { - this.space = Objects.requireNonNull(space, "space"); - } - } -} diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java index c6fc5bcc..0059c556 100644 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java +++ b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java @@ -7,7 +7,6 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import java.util.HashSet; import java.util.List; @@ -102,32 +101,6 @@ void createsRuntimesConcurrentlyThroughRegistry() throws Exception { } } - @Test - void defaultExplicitSpaceCreationDoesNotSilentlyDropRequestedId() { - PhysicsBackend backend = new PhysicsBackend() { - @Nonnull - @Override - public BackendId getId() { - return new BackendId(uniqueId()); - } - - @Override - public void init() { - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return new FakePhysicsBackend(getId()).createSpace(); - } - }; - - UnsupportedOperationException failure = assertThrows(UnsupportedOperationException.class, - () -> backend.createSpace(new SpaceId(Integer.MAX_VALUE))); - - assertTrue(failure.getMessage().contains("must override createSpace(SpaceId)")); - } - private static String uniqueId() { return "test:backend-" + ID_COUNTER.incrementAndGet(); } diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java deleted file mode 100644 index c3808082..00000000 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsBackendEventsCapabilityTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.api.capability; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsBackendEventKind; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import java.util.Set; -import org.junit.jupiter.api.Test; - -class PhysicsBackendEventsCapabilityTest { - - @Test - void mapsSupportedContactPhasesThroughBackendEventKinds() { - PhysicsBackendEventsCapability capability = - () -> Set.of(PhysicsBackendEventKind.CONTACT_STARTED, PhysicsBackendEventKind.CONTACT_ENDED); - - assertTrue(capability.supportsContactPhase(PhysicsContactPhase.STARTED)); - assertTrue(capability.supportsContactPhase(PhysicsContactPhase.ENDED)); - assertFalse(capability.supportsContactPhase(PhysicsContactPhase.FORCE)); - } -} diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java index b3ddb5a4..2b12e430 100644 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java +++ b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java @@ -7,22 +7,6 @@ class PhysicsCapabilitySettingsTest { - @Test - void descriptorRejectsBlankDisplayName() { - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> new PhysicsCapabilityDescriptor(new PhysicsCapabilityId("impulse:test"), " ", "Test capability")); - - assertEquals("displayName cannot be blank", exception.getMessage()); - } - - @Test - void descriptorRejectsBlankDescription() { - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> new PhysicsCapabilityDescriptor(new PhysicsCapabilityId("impulse:test"), "Test", " ")); - - assertEquals("description cannot be blank", exception.getMessage()); - } - @Test void rejectsNonPositiveSolverIterations() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java deleted file mode 100644 index 1a3e5378..00000000 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/runtime/legacy/LegacyPhysicsBackendRuntimeTest.java +++ /dev/null @@ -1,535 +0,0 @@ -package dev.hytalemodding.impulse.api.runtime.legacy; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.capability.PhysicsVoxelTerrainCapability; -import dev.hytalemodding.impulse.api.runtime.BackendContactSink; -import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; -import dev.hytalemodding.impulse.api.runtime.BackendJointType; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class LegacyPhysicsBackendRuntimeTest { - - @Test - void wrapsLegacyBackendWithNumericBodyAndJointIds() { - LegacyPhysicsBackendRuntime runtime = - new LegacyPhysicsBackendRuntime(new FakePhysicsBackend("impulse:test")); - - int spaceId = runtime.createSpace(new SpaceId(9001)); - long bodyA = runtime.createBody(spaceId, - BackendRuntimeCodes.SHAPE_SPHERE, - 0.0f, - 0.0f, - 0.0f, - 0.5f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 1.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), - 0.0f, - 3.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - long bodyB = runtime.createBody(spaceId, - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 1.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), - 1.0f, - 3.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - long joint = runtime.createJoint(spaceId, - BackendRuntimeCodes.JOINT_POINT, - bodyA, - bodyB, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - false, - 0.0f, - 0.0f); - - assertEquals(9001, spaceId); - assertNotEquals(bodyA, bodyB); - assertTrue(bodyA > 0L); - assertTrue(bodyB > 0L); - assertTrue(joint > 0L); - assertEquals(2, runtime.bodyCount(spaceId)); - assertEquals(1, runtime.jointCount(spaceId)); - - runtime.setBodyFriction(spaceId, bodyA, 0.65f); - runtime.setBodyRestitution(spaceId, bodyA, 0.15f); - runtime.setBodyDamping(spaceId, bodyA, 0.2f, 0.3f); - runtime.setBodyCollisionFilter(spaceId, bodyA, 2, 3); - runtime.setBodyContinuousCollision(spaceId, bodyA, true); - - CapturedBodySnapshot snapshot = new CapturedBodySnapshot(); - boolean snapshotPresent = runtime.bodySnapshot(spaceId, - bodyA, - snapshot); - - assertTrue(snapshotPresent); - assertEquals(ShapeType.SPHERE, BackendRuntimeCodes.shapeType(snapshot.shapeTypeCode)); - assertEquals(BackendRuntimeCodes.AXIS_Y, snapshot.axisCode); - assertEquals(1.0f, snapshot.mass, 0.0001f); - assertEquals(0.65f, snapshot.friction, 0.0001f); - assertEquals(0.15f, snapshot.restitution, 0.0001f); - assertEquals(0.2f, snapshot.linearDamping, 0.0001f); - assertEquals(0.3f, snapshot.angularDamping, 0.0001f); - assertEquals(2, snapshot.collisionGroup); - assertEquals(3, snapshot.collisionMask); - assertTrue(snapshot.continuousCollisionEnabled); - assertEquals(BackendRuntimeCodes.jointTypeCode(BackendJointType.POINT), runtime.jointType(spaceId, joint)); - assertEquals(bodyA, runtime.jointBodyA(spaceId, joint)); - assertEquals(bodyB, runtime.jointBodyB(spaceId, joint)); - } - - @Test - void boundedContactsTruncateLegacyContactList() { - FakePhysicsBackend backend = new FakePhysicsBackend("impulse:test-contacts"); - LegacyPhysicsBackendRuntime runtime = new LegacyPhysicsBackendRuntime(backend); - int spaceId = runtime.createSpace(new SpaceId(9101)); - long bodyAId = createBox(runtime, spaceId, 0.0f); - long bodyBId = createBox(runtime, spaceId, 2.0f); - InMemoryPhysicsSpace space = backend.createdSpaces().getFirst(); - PhysicsBody bodyA = space.getBodies().get(0); - PhysicsBody bodyB = space.getBodies().get(1); - space.addContact(contact(bodyA, bodyB, 0.0f)); - space.addContact(contact(bodyA, bodyB, 1.0f)); - space.addContact(contact(bodyA, bodyB, 2.0f)); - CountingContactSink sink = new CountingContactSink(); - - int emitted = runtime.contacts(spaceId, 2, sink); - - assertEquals(2, emitted); - assertEquals(2, sink.count()); - assertEquals(bodyAId, sink.firstBodyAId()); - assertEquals(bodyBId, sink.firstBodyBId()); - } - - @Test - void boundedContactsRejectNonPositiveLimitWithoutEmittingContacts() { - FakePhysicsBackend backend = new FakePhysicsBackend("impulse:test-zero-contacts"); - LegacyPhysicsBackendRuntime runtime = new LegacyPhysicsBackendRuntime(backend); - int spaceId = runtime.createSpace(new SpaceId(9102)); - createBox(runtime, spaceId, 0.0f); - createBox(runtime, spaceId, 2.0f); - InMemoryPhysicsSpace space = backend.createdSpaces().getFirst(); - space.addContact(contact(space.getBodies().get(0), space.getBodies().get(1), 0.0f)); - CountingContactSink sink = new CountingContactSink(); - - int emitted = runtime.contacts(spaceId, 0, sink); - - assertEquals(0, emitted); - assertEquals(0, sink.count()); - } - - @Test - void createSpaceRejectsLegacyBackendThatReturnsDifferentExplicitId() { - LegacyPhysicsBackendRuntime runtime = - new LegacyPhysicsBackendRuntime(new MismatchedExplicitSpaceBackend("impulse:test-mismatch")); - - IllegalStateException failure = assertThrows(IllegalStateException.class, - () -> runtime.createSpace(new SpaceId(9004))); - - assertTrue(failure.getMessage().contains("created space id")); - assertTrue(failure.getMessage().contains("9004")); - } - - @Test - void legacyRuntimeRejectsVoxelTerrainWhenCapabilityMissing() { - LegacyPhysicsBackendRuntime runtime = - new LegacyPhysicsBackendRuntime(new FakePhysicsBackend("impulse:test-voxel")); - int spaceId = runtime.createSpace(new SpaceId(9002)); - - assertFalse(runtime.supportsVoxelTerrain(spaceId)); - assertThrows(UnsupportedOperationException.class, - () -> runtime.createVoxelTerrain(spaceId, - 1.0f, - 1.0f, - 1.0f, - new int[] { 0, 0, 0 }, - 0.0f, - 0.0f, - 0.0f, - 0.75f, - 0.0f, - 1, - -1)); - assertThrows(UnsupportedOperationException.class, - () -> runtime.combineVoxelTerrains(spaceId, 1L, 2L, 16, 0, 0)); - } - - @Test - void legacyRuntimePassesThroughVoxelTerrainCapability() { - RecordingVoxelBackend backend = new RecordingVoxelBackend("impulse:test-voxel-passthrough"); - LegacyPhysicsBackendRuntime runtime = new LegacyPhysicsBackendRuntime(backend); - int spaceId = runtime.createSpace(new SpaceId(9003)); - - assertTrue(runtime.supportsVoxelTerrain(spaceId)); - long first = runtime.createVoxelTerrain(spaceId, - 1.0f, - 1.0f, - 1.0f, - new int[] { 0, 0, 0 }, - 8.0f, - 16.0f, - 24.0f, - 0.75f, - 0.0f, - 2, - 4); - long second = runtime.createVoxelTerrain(spaceId, - 1.0f, - 1.0f, - 1.0f, - new int[] { 1, 0, 0 }, - 24.0f, - 16.0f, - 24.0f, - 0.5f, - 0.1f, - 8, - 16); - - assertEquals(2, runtime.bodyCount(spaceId)); - assertTrue(runtime.containsBody(spaceId, first)); - PhysicsBody firstBody = backend.voxelBodies().getFirst(); - assertEquals(8.0f, firstBody.getPosition().x); - assertEquals(16.0f, firstBody.getPosition().y); - assertEquals(24.0f, firstBody.getPosition().z); - assertEquals(0.75f, firstBody.getFriction()); - assertEquals(0.0f, firstBody.getRestitution()); - assertEquals(2, firstBody.getCollisionGroup()); - assertEquals(4, firstBody.getCollisionMask()); - - runtime.combineVoxelTerrains(spaceId, first, second, 16, 0, 0); - - assertEquals(List.of(new CombineCall(firstBody, backend.voxelBodies().get(1), 16, 0, 0)), - backend.combineCalls()); - } - - private static long createBox(@Nonnull LegacyPhysicsBackendRuntime runtime, - int spaceId, - float positionX) { - return runtime.createBody(spaceId, - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 1.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), - positionX, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - } - - @Nonnull - private static PhysicsContact contact(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float offset) { - return new PhysicsContact(bodyA, - bodyB, - new Vector3f(offset, 0.0f, 0.0f), - new Vector3f(offset, 1.0f, 0.0f), - new Vector3f(0.0f, 1.0f, 0.0f), - -0.1f, - 1.0f); - } - - private record CombineCall(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - int shiftX, - int shiftY, - int shiftZ) { - } - - private static final class CountingContactSink implements BackendContactSink { - - private int count; - private long firstBodyAId = -1L; - private long firstBodyBId = -1L; - - @Override - public void accept(long bodyAId, - long bodyBId, - float pointAX, - float pointAY, - float pointAZ, - float pointBX, - float pointBY, - float pointBZ, - float normalBX, - float normalBY, - float normalBZ, - float distance, - float impulse) { - if (count == 0) { - firstBodyAId = bodyAId; - firstBodyBId = bodyBId; - } - count++; - } - - private int count() { - return count; - } - - private long firstBodyAId() { - return firstBodyAId; - } - - private long firstBodyBId() { - return firstBodyBId; - } - } - - private static final class CapturedBodySnapshot implements BackendBodySnapshotSink { - - private int shapeTypeCode = BackendRuntimeCodes.SHAPE_UNKNOWN; - private int axisCode = -1; - private float mass; - private float friction; - private float restitution; - private float linearDamping; - private float angularDamping; - private int collisionGroup; - private int collisionMask; - private boolean continuousCollisionEnabled; - - @Override - public void accept(long bodyId, - int shapeTypeCode, - int bodyTypeCode, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - float linearVelocityX, - float linearVelocityY, - float linearVelocityZ, - float angularVelocityX, - float angularVelocityY, - float angularVelocityZ, - boolean sleeping, - boolean sensor, - float mass, - float friction, - float restitution, - float linearDamping, - float angularDamping, - int collisionGroup, - int collisionMask, - boolean continuousCollisionEnabled, - float centerOfMassOffsetY, - boolean hasBoxHalfExtents, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - int axisCode) { - this.shapeTypeCode = shapeTypeCode; - this.axisCode = axisCode; - this.mass = mass; - this.friction = friction; - this.restitution = restitution; - this.linearDamping = linearDamping; - this.angularDamping = angularDamping; - this.collisionGroup = collisionGroup; - this.collisionMask = collisionMask; - this.continuousCollisionEnabled = continuousCollisionEnabled; - } - } - - private static final class MismatchedExplicitSpaceBackend implements PhysicsBackend { - - @Nonnull - private final BackendId id; - @Nonnull - private final FakePhysicsBackend delegate; - - private MismatchedExplicitSpaceBackend(@Nonnull String id) { - this.id = new BackendId(id); - this.delegate = new FakePhysicsBackend(this.id); - } - - @Nonnull - @Override - public BackendId getId() { - return id; - } - - @Override - public void init() { - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return delegate.createSpace(); - } - - @Nonnull - @Override - public PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - return delegate.createSpace(new SpaceId(spaceId.value() + 1)); - } - } - - private static final class RecordingVoxelBackend implements PhysicsBackend { - - @Nonnull - private final BackendId id; - @Nonnull - private final FakePhysicsBackend delegate; - @Nonnull - private final RecordingVoxelCapability voxelCapability = new RecordingVoxelCapability(); - - private RecordingVoxelBackend(@Nonnull String id) { - this.id = new BackendId(id); - this.delegate = new FakePhysicsBackend(this.id); - } - - @Nonnull - @Override - public BackendId getId() { - return id; - } - - @Override - public void init() { - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return createSpace(SpaceId.next()); - } - - @Nonnull - @Override - public PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - PhysicsSpace space = delegate.createSpace(spaceId); - return (PhysicsSpace) Proxy.newProxyInstance(PhysicsSpace.class.getClassLoader(), - new Class[] { PhysicsSpace.class }, - new RecordingVoxelSpace(space, voxelCapability)); - } - - @Nonnull - private List voxelBodies() { - return List.copyOf(voxelCapability.voxelBodies); - } - - @Nonnull - private List combineCalls() { - return List.copyOf(voxelCapability.combineCalls); - } - } - - private record RecordingVoxelSpace(@Nonnull PhysicsSpace delegate, - @Nonnull RecordingVoxelCapability voxelCapability) - implements InvocationHandler { - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if ("getCapability".equals(method.getName()) && args != null && args.length == 1 - && args[0] == PhysicsVoxelTerrainCapability.class) { - return Optional.of(voxelCapability); - } - try { - return method.invoke(delegate, args); - } catch (InvocationTargetException exception) { - throw exception.getCause(); - } - } - } - - private static final class RecordingVoxelCapability implements PhysicsVoxelTerrainCapability { - - private final List voxelBodies = new ArrayList<>(); - private final List combineCalls = new ArrayList<>(); - - @Nonnull - @Override - public PhysicsBody createVoxelTerrain(float voxelSizeX, - float voxelSizeY, - float voxelSizeZ, - @Nonnull int[] voxelCoordinates) { - PhysicsBody body = new FakePhysicsBackend("impulse:test-voxel-body") - .createSpace() - .createBox(0.5f, 0.5f, 0.5f, 0.0f); - voxelBodies.add(body); - return body; - } - - @Override - public void combineVoxelTerrains(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - int shiftX, - int shiftY, - int shiftZ) { - combineCalls.add(new CombineCall(bodyA, bodyB, shiftX, shiftY, shiftZ)); - } - } -} diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java deleted file mode 100644 index 3952d383..00000000 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendCapabilityTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.hytalemodding.impulse.api.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuningCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuningCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsVoxelTerrainCapability; -import org.junit.jupiter.api.Test; - -class FakePhysicsBackendCapabilityTest { - - @Test - void appliesSolverAndActivationSettingsThroughCapabilities() { - FakePhysicsBackend backend = new FakePhysicsBackend("test:fake"); - PhysicsSpace space = backend.createSpace(); - - PhysicsSolverTuningCapability solverTuning = space.getCapability(PhysicsSolverTuningCapability.class) - .orElseThrow(); - PhysicsActivationTuningCapability activationTuning = space.getCapability(PhysicsActivationTuningCapability.class) - .orElseThrow(); - - solverTuning.setSolverTuning(new PhysicsSolverTuning(12, 3)); - activationTuning.setActivationTuning(new PhysicsActivationTuning(0.25f, 0.5f, 1.75f)); - - FakePhysicsBackend.InMemoryPhysicsSpace inMemorySpace = backend.createdSpaces().get(0); - assertEquals(12, inMemorySpace.getSolverIterations()); - assertEquals(3, inMemorySpace.getStabilizationIterations()); - assertEquals(0.25f, inMemorySpace.getSleepLinearThreshold(), 0.0001f); - assertEquals(0.5f, inMemorySpace.getSleepAngularThreshold(), 0.0001f); - assertEquals(1.75f, inMemorySpace.getSleepTimeUntilSleep(), 0.0001f); - } - - @Test - void doesNotExposeVoxelCapability() { - FakePhysicsBackend backend = new FakePhysicsBackend("test:fake"); - PhysicsSpace space = backend.createSpace(); - - assertTrue(space.getCapability(PhysicsVoxelTerrainCapability.class).isEmpty()); - } -} diff --git a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java deleted file mode 100644 index fd774d45..00000000 --- a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackend.java +++ /dev/null @@ -1,805 +0,0 @@ -package dev.hytalemodding.impulse.api.testsupport; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsBackendEventSink; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.PhysicsRayHit; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuningCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityDescriptor; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuningCapability; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Small in-memory backend for resource lifecycle tests that need real API objects - * without loading a native physics implementation. - */ -public final class FakePhysicsBackend implements PhysicsBackend { - - private final BackendId id; - private final List createdSpaces = new ArrayList<>(); - - public FakePhysicsBackend(@Nonnull String id) { - this(new BackendId(id)); - } - - public FakePhysicsBackend(@Nonnull BackendId id) { - this.id = id; - } - - @Nonnull - @Override - public BackendId getId() { - return id; - } - - @Override - public void init() { - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return createSpace(SpaceId.next()); - } - - @Nonnull - @Override - public PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - InMemoryPhysicsSpace space = new InMemoryPhysicsSpace(spaceId, id); - createdSpaces.add(space); - return space; - } - - @Nonnull - public List createdSpaces() { - return List.copyOf(createdSpaces); - } - - public static final class InMemoryPhysicsSpace implements PhysicsSpace, - PhysicsSolverTuningCapability, - PhysicsActivationTuningCapability { - - private final SpaceId id; - private final BackendId backendId; - private final List bodies = new ArrayList<>(); - private final List joints = new ArrayList<>(); - private final List contacts = new ArrayList<>(); - private final Vector3f gravity = new Vector3f(); - private boolean closed; - private int solverIterations; - private int stabilizationIterations; - private float sleepLinearThreshold; - private float sleepAngularThreshold; - private float sleepTimeUntilSleep; - - private InMemoryPhysicsSpace(@Nonnull SpaceId id, @Nonnull BackendId backendId) { - this.id = id; - this.backendId = backendId; - } - - @Nonnull - @Override - public SpaceId id() { - return id; - } - - @Nonnull - @Override - public BackendId backendId() { - return backendId; - } - - @Override - public void step(float dt) { - } - - @Override - public void step(float dt, @Nonnull PhysicsBackendEventSink events) { - for (PhysicsContact contact : contacts) { - events.contact(PhysicsContactPhase.OBSERVED, - contact.bodyA(), - contact.bodyB(), - contact.pointOnA(), - contact.pointOnB(), - contact.normalOnB(), - contact.distance(), - contact.impulse()); - } - } - - @Override - public void setGravity(float x, float y, float z) { - gravity.set(x, y, z); - } - - @Nonnull - @Override - public Vector3f getGravity() { - return new Vector3f(gravity); - } - - @Override - public void addBody(@Nonnull PhysicsBody body) { - bodies.add(body); - } - - @Override - public void removeBody(@Nonnull PhysicsBody body) { - bodies.remove(body); - } - - @Nonnull - @Override - public List getBodies() { - return new ArrayList<>(bodies); - } - - @Override - public int bodyCount() { - return bodies.size(); - } - - @Nonnull - @Override - public Optional getCapability(@Nonnull Class type) { - Objects.requireNonNull(type, "type"); - if (type == PhysicsSolverTuningCapability.class || type == PhysicsActivationTuningCapability.class) { - return Optional.of(type.cast(this)); - } - return Optional.empty(); - } - - @Nonnull - @Override - public List getCapabilityDescriptors() { - return List.of(PhysicsSolverTuningCapability.DESCRIPTOR, PhysicsActivationTuningCapability.DESCRIPTOR); - } - - @Nonnull - @Override - public PhysicsBody createStaticPlane(float groundY) { - InMemoryPhysicsBody body = new InMemoryPhysicsBody(ShapeType.PLANE, PhysicsBodyType.STATIC); - body.position.y = groundY; - return body; - } - - @Nonnull - @Override - public PhysicsBody createBox(float halfX, float halfY, float halfZ, float mass) { - InMemoryPhysicsBody body = new InMemoryPhysicsBody(ShapeType.BOX, PhysicsBodyType.DYNAMIC); - body.halfExtents.set(halfX, halfY, halfZ); - body.mass = mass; - return body; - } - - @Nonnull - @Override - public PhysicsBody createBox(@Nonnull Vector3f halfExtents, float mass) { - return createBox(halfExtents.x, halfExtents.y, halfExtents.z, mass); - } - - @Nonnull - @Override - public PhysicsBody createSphere(float radius, float mass) { - InMemoryPhysicsBody body = new InMemoryPhysicsBody(ShapeType.SPHERE, PhysicsBodyType.DYNAMIC); - body.radius = radius; - body.mass = mass; - return body; - } - - @Nonnull - @Override - public PhysicsBody createCapsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return createRoundHeightBody(ShapeType.CAPSULE, radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return createRoundHeightBody(ShapeType.CYLINDER, radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCone(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return createRoundHeightBody(ShapeType.CONE, radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public Optional raycastClosest(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return Optional.empty(); - } - - @Nonnull - @Override - public List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f to) { - return List.of(); - } - - @Nonnull - @Override - public List getContacts() { - return new ArrayList<>(contacts); - } - - public void addContact(@Nonnull PhysicsContact contact) { - contacts.add(contact); - } - - @Nonnull - @Override - public PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - return addJoint(PhysicsJointType.FIXED, bodyA, bodyB, anchorA, anchorB, null); - } - - @Nonnull - @Override - public PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - return addJoint(PhysicsJointType.POINT, bodyA, bodyB, anchorA, anchorB, null); - } - - @Nonnull - @Override - public PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - return addJoint(PhysicsJointType.HINGE, bodyA, bodyB, anchorA, anchorB, axis); - } - - @Nonnull - @Override - public PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - return addJoint(PhysicsJointType.SLIDER, bodyA, bodyB, anchorA, anchorB, axis); - } - - @Nonnull - @Override - public PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping) { - return addJoint(PhysicsJointType.SPRING, bodyA, bodyB, anchorA, anchorB, null); - } - - @Override - public void removeJoint(@Nonnull PhysicsJoint joint) { - joints.remove(joint); - } - - @Nonnull - @Override - public List getJoints() { - return new ArrayList<>(joints); - } - - @Override - public int jointCount() { - return joints.size(); - } - - @Override - public void close() { - closed = true; - } - - @Override - public void setSolverTuning(@Nonnull PhysicsSolverTuning tuning) { - Objects.requireNonNull(tuning, "tuning"); - solverIterations = tuning.solverIterations(); - stabilizationIterations = tuning.stabilizationIterations(); - } - - @Override - public void setActivationTuning(@Nonnull PhysicsActivationTuning tuning) { - Objects.requireNonNull(tuning, "tuning"); - sleepLinearThreshold = tuning.linearSleepThreshold(); - sleepAngularThreshold = tuning.angularSleepThreshold(); - sleepTimeUntilSleep = tuning.timeUntilSleep(); - } - - public boolean isClosed() { - return closed; - } - - public int getSolverIterations() { - return solverIterations; - } - - public int getStabilizationIterations() { - return stabilizationIterations; - } - - public float getSleepLinearThreshold() { - return sleepLinearThreshold; - } - - public float getSleepAngularThreshold() { - return sleepAngularThreshold; - } - - public float getSleepTimeUntilSleep() { - return sleepTimeUntilSleep; - } - - @Nonnull - private PhysicsBody createRoundHeightBody(@Nonnull ShapeType shapeType, - float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - InMemoryPhysicsBody body = new InMemoryPhysicsBody(shapeType, PhysicsBodyType.DYNAMIC); - body.radius = radius; - body.halfHeight = halfHeight; - body.axis = axis; - body.mass = mass; - return body; - } - - @Nonnull - private PhysicsJoint addJoint(@Nonnull PhysicsJointType type, - @Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nullable Vector3f axis) { - PhysicsJoint joint = new InMemoryPhysicsJoint(type, bodyA, bodyB, anchorA, anchorB, axis); - joints.add(joint); - return joint; - } - } - - private static final class InMemoryPhysicsBody implements PhysicsBody { - - private final ShapeType shapeType; - private final Vector3f position = new Vector3f(); - private final Quaternionf rotation = new Quaternionf(); - private final Vector3f linearVelocity = new Vector3f(); - private final Vector3f angularVelocity = new Vector3f(); - private final Vector3f halfExtents = new Vector3f(); - private PhysicsBodyType bodyType; - private PhysicsAxis axis = PhysicsAxis.Y; - private float mass = 1.0f; - private float radius; - private float halfHeight; - private float restitution; - private float friction; - private float linearDamping; - private float angularDamping; - private boolean sensor; - private boolean continuousCollision; - private int collisionGroup; - private int collisionMask; - - private InMemoryPhysicsBody(@Nonnull ShapeType shapeType, @Nonnull PhysicsBodyType bodyType) { - this.shapeType = shapeType; - this.bodyType = bodyType; - } - - @Override - public void setPosition(float x, float y, float z) { - position.set(x, y, z); - } - - @Override - public void setPosition(@Nonnull Vector3f pos) { - position.set(pos); - } - - @Nonnull - @Override - public Vector3f getPosition() { - return new Vector3f(position); - } - - @Override - public void setRotation(float x, float y, float z, float w) { - rotation.set(x, y, z, w); - } - - @Override - public void setRotation(@Nonnull Quaternionf rot) { - rotation.set(rot); - } - - @Nonnull - @Override - public Quaternionf getRotation() { - return new Quaternionf(rotation); - } - - @Override - public void setRestitution(float restitution) { - this.restitution = restitution; - } - - @Override - public float getRestitution() { - return restitution; - } - - @Override - public void setFriction(float friction) { - this.friction = friction; - } - - @Override - public float getFriction() { - return friction; - } - - @Nonnull - @Override - public PhysicsBodyType getBodyType() { - return bodyType; - } - - @Override - public void setBodyType(@Nonnull PhysicsBodyType bodyType) { - this.bodyType = bodyType; - } - - @Override - public boolean isStatic() { - return bodyType == PhysicsBodyType.STATIC; - } - - @Override - public boolean isKinematic() { - return bodyType == PhysicsBodyType.KINEMATIC; - } - - @Override - public void setKinematic(boolean kinematic) { - bodyType = kinematic ? PhysicsBodyType.KINEMATIC : PhysicsBodyType.DYNAMIC; - } - - @Override - public void activate() { - } - - @Override - public boolean isActive() { - return true; - } - - @Override - public boolean isSleeping() { - return false; - } - - @Override - public void sleep() { - } - - @Override - public float getMass() { - return mass; - } - - @Override - public void setMass(float mass) { - this.mass = mass; - } - - @Nonnull - @Override - public Vector3f getLinearVelocity() { - return new Vector3f(linearVelocity); - } - - @Override - public void setLinearVelocity(@Nonnull Vector3f vel) { - linearVelocity.set(vel); - } - - @Override - public void setLinearVelocity(float x, float y, float z) { - linearVelocity.set(x, y, z); - } - - @Nonnull - @Override - public Vector3f getAngularVelocity() { - return new Vector3f(angularVelocity); - } - - @Override - public void setAngularVelocity(@Nonnull Vector3f vel) { - angularVelocity.set(vel); - } - - @Override - public void setAngularVelocity(float x, float y, float z) { - angularVelocity.set(x, y, z); - } - - @Override - public float getLinearDamping() { - return linearDamping; - } - - @Override - public void setLinearDamping(float damping) { - linearDamping = damping; - } - - @Override - public float getAngularDamping() { - return angularDamping; - } - - @Override - public void setAngularDamping(float damping) { - angularDamping = damping; - } - - @Override - public void applyCentralForce(@Nonnull Vector3f force) { - } - - @Override - public void applyCentralForce(float x, float y, float z) { - } - - @Override - public void applyForce(@Nonnull Vector3f force, @Nonnull Vector3f offset) { - } - - @Override - public void applyCentralImpulse(@Nonnull Vector3f impulse) { - } - - @Override - public void applyCentralImpulse(float x, float y, float z) { - } - - @Override - public void applyImpulse(@Nonnull Vector3f impulse, @Nonnull Vector3f offset) { - } - - @Override - public void applyTorque(@Nonnull Vector3f torque) { - } - - @Override - public void applyTorqueImpulse(@Nonnull Vector3f torqueImpulse) { - } - - @Override - public void clearForces() { - } - - @Override - public boolean isSensor() { - return sensor; - } - - @Override - public void setSensor(boolean sensor) { - this.sensor = sensor; - } - - @Override - public int getCollisionGroup() { - return collisionGroup; - } - - @Override - public int getCollisionMask() { - return collisionMask; - } - - @Override - public void setCollisionFilter(int group, int mask) { - collisionGroup = group; - collisionMask = mask; - } - - @Override - public boolean isContinuousCollisionEnabled() { - return continuousCollision; - } - - @Override - public void setContinuousCollisionEnabled(boolean enabled) { - continuousCollision = enabled; - } - - @Nonnull - @Override - public ShapeType getShapeType() { - return shapeType; - } - - @Nonnull - @Override - public Vector3f getBoxHalfExtents() { - return new Vector3f(halfExtents); - } - - @Override - public float getSphereRadius() { - return radius; - } - - @Override - public float getHalfHeight() { - return halfHeight; - } - - @Nonnull - @Override - public PhysicsAxis getShapeAxis() { - return axis; - } - - @Override - public float getCenterOfMassOffsetY() { - return 0.0f; - } - } - - private static final class InMemoryPhysicsJoint implements PhysicsJoint { - - private final PhysicsJointType type; - private final PhysicsBody bodyA; - private final PhysicsBody bodyB; - private final Vector3f anchorA; - private final Vector3f anchorB; - @Nullable - private final Vector3f axis; - private boolean enabled = true; - private float lowerLimit; - private float upperLimit; - private boolean motorEnabled; - private float motorTargetVelocity; - private float motorMaxForce; - - private InMemoryPhysicsJoint(@Nonnull PhysicsJointType type, - @Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nullable Vector3f axis) { - this.type = type; - this.bodyA = bodyA; - this.bodyB = bodyB; - this.anchorA = new Vector3f(anchorA); - this.anchorB = new Vector3f(anchorB); - this.axis = axis != null ? new Vector3f(axis) : null; - } - - @Nonnull - @Override - public PhysicsJointType getType() { - return type; - } - - @Nonnull - @Override - public PhysicsBody getBodyA() { - return bodyA; - } - - @Nonnull - @Override - public PhysicsBody getBodyB() { - return bodyB; - } - - @Override - public boolean isEnabled() { - return enabled; - } - - @Override - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - @Nonnull - @Override - public Vector3f getAnchorA() { - return new Vector3f(anchorA); - } - - @Nonnull - @Override - public Vector3f getAnchorB() { - return new Vector3f(anchorB); - } - - @Nullable - @Override - public Vector3f getAxis() { - return axis != null ? new Vector3f(axis) : null; - } - - @Override - public float getLowerLimit() { - return lowerLimit; - } - - @Override - public float getUpperLimit() { - return upperLimit; - } - - @Override - public void setLimits(float lowerLimit, float upperLimit) { - this.lowerLimit = lowerLimit; - this.upperLimit = upperLimit; - } - - @Override - public boolean isMotorEnabled() { - return motorEnabled; - } - - @Override - public void setMotorEnabled(boolean enabled) { - motorEnabled = enabled; - } - - @Override - public float getMotorTargetVelocity() { - return motorTargetVelocity; - } - - @Override - public float getMotorMaxForce() { - return motorMaxForce; - } - - @Override - public void setMotor(float targetVelocity, float maxForce) { - motorTargetVelocity = targetVelocity; - motorMaxForce = maxForce; - } - } -} diff --git a/impulse-bullet/build.gradle.kts b/impulse-bullet/build.gradle.kts deleted file mode 100644 index 78259c7f..00000000 --- a/impulse-bullet/build.gradle.kts +++ /dev/null @@ -1,131 +0,0 @@ -import org.gradle.api.file.FileCollection -import org.gradle.api.file.DuplicatesStrategy -import org.gradle.jvm.tasks.Jar -import java.io.File - -plugins { - id("java-library") -} - -data class BulletBackendPlatform( - val taskSuffix: String, - val archiveClassifier: String, - val nativeArtifact: String -) - -val bulletBackendPlatforms = listOf( - BulletBackendPlatform("LinuxX64", "linux-x86_64", "Libbulletjme-Linux64"), - BulletBackendPlatform("LinuxArm64", "linux-arm64", "Libbulletjme-Linux_ARM64"), - BulletBackendPlatform("OsxArm64", "osx-arm64", "Libbulletjme-MacOSX_ARM64"), - BulletBackendPlatform("WindowsX64", "windows-x86_64", "Libbulletjme-Windows64") -) -val impulseLicenseFile = rootProject.layout.projectDirectory.file("LICENSE") -val libbulletjmeLicenseFile = rootProject.layout.projectDirectory.file("licenses/LIBBULLETJME_LICENSE") - -dependencies { - api(project(":impulse-backend-api")) - - implementation(project(":impulse-native-loader")) - implementation(libs.libbulletjme) - runtimeOnly(variantOf(libs.libbulletjme.native) { classifier("SpRelease") }) - - compileOnly(libs.lombok) - annotationProcessor(libs.lombok) - -} - -val bulletNativeConfigurations = bulletBackendPlatforms.associateWith { platform -> - configurations.create("bulletNative${platform.taskSuffix}") { - isCanBeConsumed = false - isCanBeResolved = true - isTransitive = false - } -} - -bulletBackendPlatforms.forEach { platform -> - dependencies.add(bulletNativeConfigurations.getValue(platform).name, - "com.github.stephengold:${platform.nativeArtifact}:${libs.versions.libbulletjme.get()}:SpRelease") -} - -fun runtimeClasspathWithoutBundledApi(): FileCollection { - return configurations.runtimeClasspath.get() - .filter { file -> !file.name.startsWith("impulse-backend-api-") } -} - -fun runtimeClasspathWithoutBundledApiAndHostNative(): FileCollection { - return runtimeClasspathWithoutBundledApi() - .filter { file -> !isBulletNativeJar(file) } -} - -fun isBulletNativeJar(file: File): Boolean { - return file.name.startsWith("Libbulletjme-") && file.name.contains("-Sp") -} - -fun Jar.expandRuntimeClasspath(runtimeClasspath: FileCollection) { - dependsOn(runtimeClasspath.buildDependencies) - from({ - runtimeClasspath.map { file -> if (file.isDirectory) file else zipTree(file) } - }) -} - -fun Jar.includeBackendRuntime() { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - expandRuntimeClasspath(runtimeClasspathWithoutBundledApiAndHostNative()) - exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") -} - -fun Jar.includeBackendLicenseNotices() { - from(impulseLicenseFile) { - into("META-INF/licenses/impulse") - rename { "LICENSE" } - } - from(libbulletjmeLicenseFile) { - into("META-INF/licenses/libbulletjme") - rename { "LICENSE" } - } -} - -tasks.jar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - expandRuntimeClasspath(runtimeClasspathWithoutBundledApi()) - includeBackendLicenseNotices() - exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") -} - -val platformJarTasks = bulletBackendPlatforms.map { platform -> - tasks.register("packageBulletBackend${platform.taskSuffix}") { - group = "build" - description = "Packages the Bullet backend provider jar for ${platform.archiveClassifier}" - archiveClassifier.set(platform.archiveClassifier) - - from(sourceSets.main.get().output) - includeBackendRuntime() - includeBackendLicenseNotices() - from({ - bulletNativeConfigurations.getValue(platform).files - .map { file -> if (file.isDirectory) file else zipTree(file) } - }) - } -} - -tasks.register("packageBulletBackendUniversal") { - group = "build" - description = "Packages the Bullet backend provider jar with every configured native library" - archiveClassifier.set("universal") - - from(sourceSets.main.get().output) - includeBackendRuntime() - includeBackendLicenseNotices() - from({ - bulletNativeConfigurations.values - .flatMap { configuration -> configuration.files } - .map { file -> if (file.isDirectory) file else zipTree(file) } - }) -} - -tasks.register("packageBulletBackendPlatformJars") { - group = "build" - description = "Packages all Bullet per-platform backend jars plus the universal jar" - dependsOn(platformJarTasks) - dependsOn(tasks.named("packageBulletBackendUniversal")) -} diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackend.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackend.java deleted file mode 100644 index 5fd4623e..00000000 --- a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackend.java +++ /dev/null @@ -1,106 +0,0 @@ -package dev.hytalemodding.impulse.bullet; - -import com.jme3.bullet.util.NativeLibrary; -import com.jme3.bullet.PhysicsSpace.BroadphaseType; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.internal.nativelib.NativeLibraryLoader; -import dev.hytalemodding.impulse.internal.nativelib.NativeLibraryResource; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nonnull; - -/** - * TODO: right now the BulletBackend has an additional bulletjme layer on top of Bullet natives - */ -public final class BulletBackend implements PhysicsBackend { - - public static final BackendId ID = new BackendId("impulse:bullet"); - private static final Logger LOGGER = Logger.getLogger("Impulse"); - private static final AtomicInteger SPACES_CREATED = new AtomicInteger(0); - private static final Object NATIVE_LOAD_LOCK = new Object(); - private static volatile boolean nativeLibraryLoaded; - - private volatile Level internalLoggingLevel = Level.WARNING; - - private volatile boolean initialized; - - @Nonnull - @Override - public BackendId getId() { - return ID; - } - - @Override - public void setInternalLoggingLevel(@Nonnull Level level) { - this.internalLoggingLevel = level; - if (initialized) { - applyInternalLoggingLevel(level); - } - } - - private void applyInternalLoggingLevel(@Nonnull Level level) { - // Set on both parent and the specific PhysicsRigidBody logger, - // since child loggers can have their own level independent of parent. - Logger.getLogger("com.jme3.bullet").setLevel(level); - Logger.getLogger("com.jme3.bullet.objects.PhysicsRigidBody").setLevel(level); - } - - @Override - public synchronized void init() { - if (initialized) { - return; - } - - loadNativeLibrary(); - NativeLibrary.setStartupMessageEnabled(false); - - initialized = true; - applyInternalLoggingLevel(internalLoggingLevel); - LOGGER.log(Level.INFO, "Bullet backend initialized"); - } - - private static void loadNativeLibrary() { - if (nativeLibraryLoaded) { - return; - } - - synchronized (NATIVE_LOAD_LOCK) { - if (nativeLibraryLoaded) { - return; - } - - try { - NativeLibraryLoader.load(BulletBackend.class, - "bullet", - NativeLibraryResource.forCurrentPlatform("bulletjme")); - } catch (IllegalArgumentException | IllegalStateException exception) { - throw new IllegalStateException("Failed to load the Libbulletjme native library", - exception); - } - - nativeLibraryLoaded = true; - } - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return createSpace(SpaceId.next()); - } - - @Nonnull - @Override - public PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - int count = SPACES_CREATED.incrementAndGet(); - LOGGER.log(Level.FINE, - "Creating Bullet physics space #" + count + " on thread " - + Thread.currentThread().getName()); - - return new BulletSpace(spaceId, this, - new BulletNativeSpace(BroadphaseType.DBVT)); - } -} diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java deleted file mode 100644 index e15bd2a6..00000000 --- a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBackendRuntimeProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.hytalemodding.impulse.bullet; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntime; -import java.util.logging.Level; -import javax.annotation.Nonnull; - -/** - * Runtime-provider service entry point for Bullet. - */ -@SuppressWarnings("removal") -public final class BulletBackendRuntimeProvider implements PhysicsBackendRuntimeProvider { - - private final BulletBackend backend = new BulletBackend(); - - @Nonnull - @Override - public BackendId getId() { - return BulletBackend.ID; - } - - @Override - public void init() { - backend.init(); - } - - @Override - public void setInternalLoggingLevel(@Nonnull Level level) { - backend.setInternalLoggingLevel(level); - } - - @Nonnull - @Override - public PhysicsBackendRuntime createRuntime() { - return new LegacyPhysicsBackendRuntime(backend); - } -} diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBody.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBody.java deleted file mode 100644 index b4ff6340..00000000 --- a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletBody.java +++ /dev/null @@ -1,578 +0,0 @@ -package dev.hytalemodding.impulse.bullet; - -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Quaternion; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -public final class BulletBody implements PhysicsBody { - - private final PhysicsRigidBody body; - private final com.jme3.math.Vector3f jmeVectorScratch = new com.jme3.math.Vector3f(); - private final Quaternion jmeQuaternionScratch = new Quaternion(); - private BulletSpace owner; - private boolean attachedToSpace; - private boolean invalidated; - - BulletBody(@Nonnull PhysicsRigidBody body) { - this.body = body; - } - - @Override - public void setPosition(float x, float y, float z) { - body.setPhysicsLocation(toJme(x, y, z)); - } - - @Override - public void setPosition(@Nonnull Vector3f pos) { - setPosition(pos.x, pos.y, pos.z); - } - - @Nonnull - @Override - public Vector3f getPosition() { - Vector3f out = new Vector3f(); - getPosition(out); - return out; - } - - @Override - public void getPosition(@Nonnull Vector3f out) { - com.jme3.math.Vector3f position = body.getPhysicsLocation(jmeVectorScratch); - out.set(position.x, position.y, position.z); - } - - @Override - public void setRotation(float x, float y, float z, float w) { - body.setPhysicsRotation(new Quaternion(x, y, z, w)); - } - - @Override - public void setRotation(@Nonnull Quaternionf rot) { - setRotation(rot.x, rot.y, rot.z, rot.w); - } - - @Nonnull - @Override - public Quaternionf getRotation() { - Quaternionf out = new Quaternionf(); - getRotation(out); - return out; - } - - @Override - public void getRotation(@Nonnull Quaternionf out) { - Quaternion rotation = body.getPhysicsRotation(jmeQuaternionScratch); - out.set(rotation.getX(), rotation.getY(), rotation.getZ(), rotation.getW()); - } - - @Override - public void setRestitution(float restitution) { - body.setRestitution(restitution); - } - - @Override - public float getRestitution() { - return body.getRestitution(); - } - - @Override - public void setFriction(float friction) { - body.setFriction(friction); - } - - @Override - public float getFriction() { - return body.getFriction(); - } - - @Nonnull - @Override - public PhysicsBodyType getBodyType() { - if (body.isStatic()) { - return PhysicsBodyType.STATIC; - } - if (body.isKinematic()) { - return PhysicsBodyType.KINEMATIC; - } - return PhysicsBodyType.DYNAMIC; - } - - @Override - public void setBodyType(@Nonnull PhysicsBodyType bodyType) { - switch (bodyType) { - case STATIC -> { - body.setKinematic(false); - body.setMass(com.jme3.bullet.objects.PhysicsBody.massForStatic); - } - case DYNAMIC -> { - if (body.getMass() <= 0f) { - body.setMass(1f); - } - body.setKinematic(false); - } - case KINEMATIC -> { - if (body.getMass() <= 0f) { - body.setMass(1f); - } - body.setKinematic(true); - } - } - } - - @Override - public boolean isStatic() { - return body.isStatic(); - } - - @Override - public boolean isKinematic() { - return body.isKinematic(); - } - - @Override - public void setKinematic(boolean kinematic) { - if (kinematic) { - setBodyType(PhysicsBodyType.KINEMATIC); - } else if (!body.isStatic()) { - body.setKinematic(false); - } - } - - @Override - public void activate() { - body.activate(); - } - - @Override - public boolean isActive() { - return body.isActive(); - } - - @Override - public boolean isSleeping() { - return !body.isActive(); - } - - @Override - public void sleep() { - body.setLinearVelocity(toJme(0f, 0f, 0f)); - body.setAngularVelocity(toJme(0f, 0f, 0f)); - body.setEnableSleep(true); - } - - @Override - public float getMass() { - return body.getMass(); - } - - @Override - public void setMass(float mass) { - body.setMass(mass); - } - - @Nonnull - @Override - public Vector3f getLinearVelocity() { - Vector3f out = new Vector3f(); - getLinearVelocity(out); - return out; - } - - @Override - public void getLinearVelocity(@Nonnull Vector3f out) { - com.jme3.math.Vector3f velocity = body.getLinearVelocity(jmeVectorScratch); - out.set(velocity.x, velocity.y, velocity.z); - } - - @Override - public void setLinearVelocity(@Nonnull Vector3f vel) { - setLinearVelocity(vel.x, vel.y, vel.z); - } - - @Override - public void setLinearVelocity(float x, float y, float z) { - body.setLinearVelocity(toJme(x, y, z)); - } - - @Nonnull - @Override - public Vector3f getAngularVelocity() { - Vector3f out = new Vector3f(); - getAngularVelocity(out); - return out; - } - - @Override - public void getAngularVelocity(@Nonnull Vector3f out) { - com.jme3.math.Vector3f velocity = body.getAngularVelocity(jmeVectorScratch); - out.set(velocity.x, velocity.y, velocity.z); - } - - @Override - public void setAngularVelocity(@Nonnull Vector3f vel) { - setAngularVelocity(vel.x, vel.y, vel.z); - } - - @Override - public void setAngularVelocity(float x, float y, float z) { - body.setAngularVelocity(toJme(x, y, z)); - } - - @Override - public float getLinearDamping() { - return body.getLinearDamping(); - } - - @Override - public void setLinearDamping(float damping) { - body.setLinearDamping(damping); - } - - @Override - public float getAngularDamping() { - return body.getAngularDamping(); - } - - @Override - public void setAngularDamping(float damping) { - body.setAngularDamping(damping); - } - - @Override - public void applyCentralForce(@Nonnull Vector3f force) { - applyCentralForce(force.x, force.y, force.z); - } - - @Override - public void applyCentralForce(float x, float y, float z) { - body.applyCentralForce(toJme(x, y, z)); - } - - @Override - public void applyForce(@Nonnull Vector3f force, @Nonnull Vector3f offset) { - applyForce(force.x, force.y, force.z, offset.x, offset.y, offset.z); - } - - @Override - public void applyForce(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - body.applyForce(toJme(x, y, z), toJme(offsetX, offsetY, offsetZ)); - } - - @Override - public void applyCentralImpulse(@Nonnull Vector3f impulse) { - applyCentralImpulse(impulse.x, impulse.y, impulse.z); - } - - @Override - public void applyCentralImpulse(float x, float y, float z) { - body.applyCentralImpulse(toJme(x, y, z)); - } - - @Override - public void applyImpulse(@Nonnull Vector3f impulse, @Nonnull Vector3f offset) { - applyImpulse(impulse.x, impulse.y, impulse.z, offset.x, offset.y, offset.z); - } - - @Override - public void applyImpulse(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - body.applyImpulse(toJme(x, y, z), toJme(offsetX, offsetY, offsetZ)); - } - - @Override - public void applyTorque(@Nonnull Vector3f torque) { - applyTorque(torque.x, torque.y, torque.z); - } - - @Override - public void applyTorque(float x, float y, float z) { - body.applyTorque(toJme(x, y, z)); - } - - @Override - public void applyTorqueImpulse(@Nonnull Vector3f torqueImpulse) { - applyTorqueImpulse(torqueImpulse.x, torqueImpulse.y, torqueImpulse.z); - } - - @Override - public void applyTorqueImpulse(float x, float y, float z) { - body.applyTorqueImpulse(toJme(x, y, z)); - } - - @Override - public void clearForces() { - body.clearForces(); - } - - @Override - public boolean isSensor() { - return !body.isContactResponse(); - } - - @Override - public void setSensor(boolean sensor) { - body.setContactResponse(!sensor); - } - - @Override - public int getCollisionGroup() { - return body.getCollisionGroup(); - } - - @Override - public int getCollisionMask() { - return body.getCollideWithGroups(); - } - - @Override - public void setCollisionFilter(int group, int mask) { - body.setCollisionGroup(group); - body.setCollideWithGroups(mask); - } - - @Override - public boolean isContinuousCollisionEnabled() { - return body.getCcdMotionThreshold() > 0f; - } - - @Override - public void setContinuousCollisionEnabled(boolean enabled) { - if (enabled) { - body.setCcdMotionThreshold(0.0001f); - body.setCcdSweptSphereRadius(Math.max(0.001f, estimateCcdRadius())); - } else { - body.setCcdMotionThreshold(0f); - body.setCcdSweptSphereRadius(0f); - } - } - - @Nonnull - @Override - public ShapeType getShapeType() { - CollisionShape shape = body.getCollisionShape(); - if (shape instanceof BoxCollisionShape) { - return ShapeType.BOX; - } - if (shape instanceof SphereCollisionShape) { - return ShapeType.SPHERE; - } - if (shape instanceof CapsuleCollisionShape) { - return ShapeType.CAPSULE; - } - if (shape instanceof CylinderCollisionShape) { - return ShapeType.CYLINDER; - } - if (shape instanceof ConeCollisionShape) { - return ShapeType.CONE; - } - if (shape instanceof PlaneCollisionShape) { - return ShapeType.PLANE; - } - return ShapeType.UNKNOWN; - } - - @Nullable - @Override - public Vector3f getBoxHalfExtents() { - CollisionShape shape = body.getCollisionShape(); - if (shape instanceof BoxCollisionShape box) { - return fromJme(box.getHalfExtents(new com.jme3.math.Vector3f())); - } - if (shape instanceof CylinderCollisionShape cylinder) { - return fromJme(cylinder.getHalfExtents(new com.jme3.math.Vector3f())); - } - return null; - } - - @Override - public float getSphereRadius() { - CollisionShape shape = body.getCollisionShape(); - if (shape instanceof SphereCollisionShape sphere) { - return sphere.getRadius(); - } - if (shape instanceof CapsuleCollisionShape capsule) { - return capsule.getRadius(); - } - if (shape instanceof CylinderCollisionShape cylinder) { - com.jme3.math.Vector3f halfExtents = cylinder.getHalfExtents( - new com.jme3.math.Vector3f()); - return switch (cylinder.getAxis()) { - case 0 -> Math.max(halfExtents.y, halfExtents.z); - case 1 -> Math.max(halfExtents.x, halfExtents.z); - case 2 -> Math.max(halfExtents.x, halfExtents.y); - default -> Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z)); - }; - } - if (shape instanceof ConeCollisionShape cone) { - return cone.getRadius(); - } - return -1f; - } - - @Override - public float getHalfHeight() { - CollisionShape shape = body.getCollisionShape(); - if (shape instanceof CapsuleCollisionShape capsule) { - return capsule.getHeight() * 0.5f; - } - if (shape instanceof CylinderCollisionShape cylinder) { - return cylinder.getHeight() * 0.5f; - } - if (shape instanceof ConeCollisionShape cone) { - return cone.getHeight() * 0.5f; - } - return -1f; - } - - @Nonnull - @Override - public PhysicsAxis getShapeAxis() { - CollisionShape shape = body.getCollisionShape(); - if (shape instanceof CapsuleCollisionShape capsule) { - return PhysicsAxis.fromIndex(capsule.getAxis()); - } - if (shape instanceof CylinderCollisionShape cylinder) { - return PhysicsAxis.fromIndex(cylinder.getAxis()); - } - if (shape instanceof ConeCollisionShape cone) { - return PhysicsAxis.fromIndex(cone.getAxis()); - } - return PhysicsAxis.Y; - } - - @Override - public float getCenterOfMassOffsetY() { - CollisionShape shape = body.getCollisionShape(); - if (shape instanceof BoxCollisionShape box) { - return box.getHalfExtents(new com.jme3.math.Vector3f()).y; - } - if (shape instanceof SphereCollisionShape sphere) { - return sphere.getRadius(); - } - if (shape instanceof CapsuleCollisionShape capsule) { - return capsule.getAxis() == PhysicsAxis.Y.index() - ? capsule.getHeight() * 0.5f + capsule.getRadius() - : capsule.getRadius(); - } - if (shape instanceof CylinderCollisionShape cylinder) { - return cylinder.getAxis() == PhysicsAxis.Y.index() - ? cylinder.getHeight() * 0.5f - : cylinder.maxRadius(); - } - if (shape instanceof ConeCollisionShape cone) { - return cone.getAxis() == PhysicsAxis.Y.index() - ? cone.getHeight() * 0.5f - : cone.maxRadius(); - } - return 0f; - } - - @Nonnull - PhysicsRigidBody getRigidBody() { - return body; - } - - long getNativeId() { - return body.nativeId(); - } - - @Nullable - BulletSpace getOwner() { - return owner; - } - - boolean isOwnedBy(@Nonnull BulletSpace space) { - return owner == space; - } - - boolean isAttachedToSpace() { - return attachedToSpace; - } - - boolean isInvalidated() { - return invalidated; - } - - void bindTo(@Nonnull BulletSpace space) { - requireNotInvalidated(); - if (owner != null && owner != space) { - throw new IllegalStateException("Body belongs to another bullet space"); - } - owner = space; - } - - void markAttachedTo(@Nonnull BulletSpace space) { - bindTo(space); - if (attachedToSpace) { - throw new IllegalStateException("Body is already attached to a bullet space"); - } - attachedToSpace = true; - } - - void detachFrom(@Nonnull BulletSpace space) { - if (owner == space) { - attachedToSpace = false; - owner = null; - } - } - - void invalidateFrom(@Nonnull BulletSpace space) { - if (owner == space) { - attachedToSpace = false; - owner = null; - invalidated = true; - } - } - - void requireNotInvalidated() { - if (invalidated) { - throw new IllegalStateException("Body has been invalidated"); - } - } - - private float estimateCcdRadius() { - return switch (getShapeType()) { - case BOX, CYLINDER -> { - Vector3f half = getBoxHalfExtents(); - yield half != null ? Math.min(half.x, Math.min(half.y, half.z)) : 0.05f; - } - case SPHERE, CAPSULE, CONE -> Math.max(0.001f, getSphereRadius()); - default -> 0.05f; - }; - } - - private static com.jme3.math.Vector3f toJme(@Nonnull Vector3f vector) { - return toJme(vector.x, vector.y, vector.z); - } - - private static com.jme3.math.Vector3f toJme(float x, float y, float z) { - return new com.jme3.math.Vector3f(x, y, z); - } - - private static Vector3f fromJme(@Nonnull com.jme3.math.Vector3f vector) { - return new Vector3f(vector.x, vector.y, vector.z); - } -} diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletJoint.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletJoint.java deleted file mode 100644 index 6cb9553c..00000000 --- a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletJoint.java +++ /dev/null @@ -1,190 +0,0 @@ -package dev.hytalemodding.impulse.bullet; - -import com.jme3.bullet.joints.Constraint; -import com.jme3.bullet.joints.HingeJoint; -import com.jme3.bullet.joints.SliderJoint; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -final class BulletJoint implements PhysicsJoint { - - private final PhysicsJointType type; - private final BulletBody bodyA; - private final BulletBody bodyB; - private final com.jme3.bullet.joints.PhysicsJoint joint; - private final Vector3f anchorA; - private final Vector3f anchorB; - private final Vector3f axis; - private float lowerLimit; - private float upperLimit; - private boolean motorEnabled; - private float motorTargetVelocity; - private float motorMaxForce; - private final float springRestLength; - private final float springStiffness; - private final float springDamping; - - BulletJoint(@Nonnull PhysicsJointType type, - @Nonnull BulletBody bodyA, - @Nonnull BulletBody bodyB, - @Nonnull com.jme3.bullet.joints.PhysicsJoint joint, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nullable Vector3f axis) { - this(type, bodyA, bodyB, joint, anchorA, anchorB, axis, - Float.NaN, Float.NaN, Float.NaN); - } - - BulletJoint(@Nonnull PhysicsJointType type, - @Nonnull BulletBody bodyA, - @Nonnull BulletBody bodyB, - @Nonnull com.jme3.bullet.joints.PhysicsJoint joint, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nullable Vector3f axis, - float springRestLength, - float springStiffness, - float springDamping) { - this.type = type; - this.bodyA = bodyA; - this.bodyB = bodyB; - this.joint = joint; - this.anchorA = new Vector3f(anchorA); - this.anchorB = new Vector3f(anchorB); - this.axis = axis != null ? new Vector3f(axis) : null; - this.springRestLength = springRestLength; - this.springStiffness = springStiffness; - this.springDamping = springDamping; - } - - @Nonnull - @Override - public PhysicsJointType getType() { - return type; - } - - @Nonnull - @Override - public PhysicsBody getBodyA() { - return bodyA; - } - - @Nonnull - @Override - public PhysicsBody getBodyB() { - return bodyB; - } - - @Override - public boolean isEnabled() { - return joint.isEnabled(); - } - - @Override - public void setEnabled(boolean enabled) { - if (joint instanceof Constraint constraint) { - constraint.setEnabled(enabled); - } - } - - @Nonnull - @Override - public Vector3f getAnchorA() { - return new Vector3f(anchorA); - } - - @Nonnull - @Override - public Vector3f getAnchorB() { - return new Vector3f(anchorB); - } - - @Nullable - @Override - public Vector3f getAxis() { - return axis != null ? new Vector3f(axis) : null; - } - - @Override - public float getLowerLimit() { - return lowerLimit; - } - - @Override - public float getUpperLimit() { - return upperLimit; - } - - @Override - public void setLimits(float lowerLimit, float upperLimit) { - this.lowerLimit = lowerLimit; - this.upperLimit = upperLimit; - if (joint instanceof HingeJoint hinge) { - hinge.setLimit(lowerLimit, upperLimit); - } else if (joint instanceof SliderJoint slider) { - slider.setLowerLinLimit(lowerLimit); - slider.setUpperLinLimit(upperLimit); - } - } - - @Override - public boolean isMotorEnabled() { - return motorEnabled; - } - - @Override - public void setMotorEnabled(boolean enabled) { - motorEnabled = enabled; - if (joint instanceof HingeJoint hinge) { - hinge.enableMotor(enabled, motorTargetVelocity, motorMaxForce); - } else if (joint instanceof SliderJoint slider) { - slider.setPoweredLinMotor(enabled); - } - } - - @Override - public float getMotorTargetVelocity() { - return motorTargetVelocity; - } - - @Override - public float getMotorMaxForce() { - return motorMaxForce; - } - - @Override - public void setMotor(float targetVelocity, float maxForce) { - motorTargetVelocity = targetVelocity; - motorMaxForce = maxForce; - if (joint instanceof HingeJoint hinge) { - hinge.enableMotor(motorEnabled, targetVelocity, maxForce); - } else if (joint instanceof SliderJoint slider) { - slider.setTargetLinMotorVelocity(targetVelocity); - slider.setMaxLinMotorForce(maxForce); - } - } - - @Override - public float getSpringRestLength() { - return springRestLength; - } - - @Override - public float getSpringStiffness() { - return springStiffness; - } - - @Override - public float getSpringDamping() { - return springDamping; - } - - @Nonnull - com.jme3.bullet.joints.PhysicsJoint getNativeJoint() { - return joint; - } -} diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeContactEvent.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeContactEvent.java deleted file mode 100644 index 386cb282..00000000 --- a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeContactEvent.java +++ /dev/null @@ -1,53 +0,0 @@ -package dev.hytalemodding.impulse.bullet; - -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -record BulletNativeContactEvent(@Nonnull PhysicsContactPhase phase, - long bodyAId, - long bodyBId, - @Nonnull Vector3f pointOnA, - @Nonnull Vector3f pointOnB, - @Nonnull Vector3f normalOnB, - float distance, - float impulse) { - - BulletNativeContactEvent { - phase = Objects.requireNonNull(phase, "phase"); - pointOnA = new Vector3f(Objects.requireNonNull(pointOnA, "pointOnA")); - pointOnB = new Vector3f(Objects.requireNonNull(pointOnB, "pointOnB")); - normalOnB = new Vector3f(Objects.requireNonNull(normalOnB, "normalOnB")); - } - - @Nonnull - BulletNativeContactEvent withPhase(@Nonnull PhysicsContactPhase phase) { - return new BulletNativeContactEvent(phase, - bodyAId, - bodyBId, - pointOnA, - pointOnB, - normalOnB, - distance, - impulse); - } - - @Nonnull - @Override - public Vector3f pointOnA() { - return new Vector3f(pointOnA); - } - - @Nonnull - @Override - public Vector3f pointOnB() { - return new Vector3f(pointOnB); - } - - @Nonnull - @Override - public Vector3f normalOnB() { - return new Vector3f(normalOnB); - } -} diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeSpace.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeSpace.java deleted file mode 100644 index 6fa9ad71..00000000 --- a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletNativeSpace.java +++ /dev/null @@ -1,164 +0,0 @@ -package dev.hytalemodding.impulse.bullet; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.StepFlag; -import com.jme3.bullet.collision.ManifoldPoints; -import com.jme3.bullet.collision.PersistentManifolds; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -final class BulletNativeSpace extends PhysicsSpace { - - private static final int CONTACT_EVENT_FLAGS = StepFlag.contactConceived - | StepFlag.contactStarted - | StepFlag.contactProcessed - | StepFlag.contactEnded; - private static final int MAX_CONTACT_EVENTS = 16_384; - private static final Vector3f ZERO_VECTOR = new Vector3f(); - - private final ArrayDeque contactEvents = new ArrayDeque<>(); - private final Map activeManifoldEvents = new HashMap<>(); - - BulletNativeSpace(@Nonnull BroadphaseType broadphaseType) { - super(broadphaseType); - } - - void updateWithContactEvents(float timeInterval, int maxSteps) { - contactEvents.clear(); - update(timeInterval, maxSteps, CONTACT_EVENT_FLAGS); - } - - @Nonnull - List drainContactEvents() { - if (contactEvents.isEmpty()) { - return List.of(); - } - List drained = new ArrayList<>(contactEvents); - contactEvents.clear(); - return drained; - } - - void forgetBody(long bodyId) { - Iterator> active = activeManifoldEvents.entrySet().iterator(); - while (active.hasNext()) { - BulletNativeContactEvent event = active.next().getValue(); - if (event.bodyAId() == bodyId || event.bodyBId() == bodyId) { - active.remove(); - } - } - contactEvents.removeIf(event -> event.bodyAId() == bodyId || event.bodyBId() == bodyId); - } - - @Override - public boolean onContactConceived(long pointId, - long manifoldId, - PhysicsCollisionObject pcoA, - PhysicsCollisionObject pcoB) { - boolean accepted = super.onContactConceived(pointId, manifoldId, pcoA, pcoB); - if (accepted) { - BulletNativeContactEvent event = eventFromPoint(PhysicsContactPhase.STARTED, - pcoA.nativeId(), - pcoB.nativeId(), - pointId); - activeManifoldEvents.put(manifoldId, event); - } - return accepted; - } - - @Override - public void onContactStarted(long manifoldId) { - super.onContactStarted(manifoldId); - BulletNativeContactEvent event = activeManifoldEvents.get(manifoldId); - if (event == null) { - event = eventFromManifold(PhysicsContactPhase.STARTED, manifoldId); - if (event != null) { - activeManifoldEvents.put(manifoldId, event); - } - } - if (event != null) { - addContactEvent(event.withPhase(PhysicsContactPhase.STARTED)); - } - } - - @Override - public void onContactProcessed(PhysicsCollisionObject pcoA, - PhysicsCollisionObject pcoB, - long pointId) { - super.onContactProcessed(pcoA, pcoB, pointId); - addContactEvent(eventFromPoint(PhysicsContactPhase.PERSISTED, - pcoA.nativeId(), - pcoB.nativeId(), - pointId)); - } - - @Override - public void onContactEnded(long manifoldId) { - super.onContactEnded(manifoldId); - BulletNativeContactEvent event = activeManifoldEvents.remove(manifoldId); - if (event != null) { - addContactEvent(event.withPhase(PhysicsContactPhase.ENDED)); - } - } - - private void addContactEvent(@Nonnull BulletNativeContactEvent event) { - if (contactEvents.size() < MAX_CONTACT_EVENTS) { - contactEvents.add(event); - } - } - - @Nullable - private static BulletNativeContactEvent eventFromManifold(@Nonnull PhysicsContactPhase phase, - long manifoldId) { - long bodyAId = PersistentManifolds.getBodyAId(manifoldId); - long bodyBId = PersistentManifolds.getBodyBId(manifoldId); - int pointCount = PersistentManifolds.countPoints(manifoldId); - if (pointCount <= 0) { - return new BulletNativeContactEvent(phase, - bodyAId, - bodyBId, - ZERO_VECTOR, - ZERO_VECTOR, - ZERO_VECTOR, - 0.0f, - 0.0f); - } - long pointId = PersistentManifolds.getPointId(manifoldId, 0); - return eventFromPoint(phase, bodyAId, bodyBId, pointId); - } - - @Nonnull - private static BulletNativeContactEvent eventFromPoint(@Nonnull PhysicsContactPhase phase, - long bodyAId, - long bodyBId, - long pointId) { - return new BulletNativeContactEvent(phase, - bodyAId, - bodyBId, - fromJmeVector(ManifoldPoints::getPositionWorldOnA, pointId), - fromJmeVector(ManifoldPoints::getPositionWorldOnB, pointId), - fromJmeVector(ManifoldPoints::getNormalWorldOnB, pointId), - ManifoldPoints.getDistance1(pointId), - ManifoldPoints.getAppliedImpulse(pointId)); - } - - @Nonnull - private static Vector3f fromJmeVector(@Nonnull ManifoldVectorReader reader, long pointId) { - com.jme3.math.Vector3f out = new com.jme3.math.Vector3f(); - reader.read(pointId, out); - return new Vector3f(out.x, out.y, out.z); - } - - @FunctionalInterface - private interface ManifoldVectorReader { - void read(long pointId, com.jme3.math.Vector3f out); - } -} diff --git a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletSpace.java b/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletSpace.java deleted file mode 100644 index 313a54d5..00000000 --- a/impulse-bullet/src/main/java/dev/hytalemodding/impulse/bullet/BulletSpace.java +++ /dev/null @@ -1,643 +0,0 @@ -package dev.hytalemodding.impulse.bullet; - -import com.jme3.bullet.collision.ManifoldPoints; -import com.jme3.bullet.collision.PersistentManifolds; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.PhysicsRayTestResult; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.joints.HingeJoint; -import com.jme3.bullet.joints.Point2PointJoint; -import com.jme3.bullet.joints.SixDofJoint; -import com.jme3.bullet.joints.SixDofSpringJoint; -import com.jme3.bullet.joints.SliderJoint; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Matrix3f; -import com.jme3.math.Plane; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBackendEventKind; -import dev.hytalemodding.impulse.api.PhysicsBackendEventSink; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.PhysicsRayHit; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.capability.PhysicsBackendEventsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityDescriptor; -import dev.hytalemodding.impulse.api.capability.PhysicsContinuousCollisionCapability; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -public final class BulletSpace implements PhysicsSpace { - - private static final PhysicsContinuousCollisionCapability CONTINUOUS_COLLISION_CAPABILITY = - new PhysicsContinuousCollisionCapability() { - }; - private static final PhysicsBackendEventsCapability BACKEND_EVENTS_CAPABILITY = - () -> Set.of(PhysicsBackendEventKind.CONTACT_STARTED, - PhysicsBackendEventKind.CONTACT_PERSISTED, - PhysicsBackendEventKind.CONTACT_ENDED); - private static final List CAPABILITY_DESCRIPTORS = List.of( - PhysicsContinuousCollisionCapability.DESCRIPTOR, - PhysicsBackendEventsCapability.DESCRIPTOR); - - private final SpaceId id; - private final BulletBackend backend; - private final BulletNativeSpace space; - private final Map bodiesByRigidBody = new IdentityHashMap<>(); - private final Map bodiesByNativeId = new HashMap<>(); - private final List bodies = new ArrayList<>(); - private final List joints = new ArrayList<>(); - private boolean closed; - - BulletSpace(@Nonnull SpaceId id, - @Nonnull BulletBackend backend, - @Nonnull BulletNativeSpace space) { - this.id = id; - this.backend = backend; - this.space = space; - } - - @Nonnull - @Override - public SpaceId id() { - return id; - } - - @Nonnull - @Override - public BackendId backendId() { - return backend.getId(); - } - - @Override - public void step(float dt) { - requireOpen(); - space.update(dt, 0); - } - - @Override - public void step(float dt, @Nonnull PhysicsBackendEventSink events) { - Objects.requireNonNull(events, "events"); - requireOpen(); - space.updateWithContactEvents(dt, 0); - for (BulletNativeContactEvent event : space.drainContactEvents()) { - BulletBody bodyA = bodiesByNativeId.get(event.bodyAId()); - BulletBody bodyB = bodiesByNativeId.get(event.bodyBId()); - if (bodyA == null || bodyB == null) { - continue; - } - events.contact(event.phase(), - bodyA, - bodyB, - event.pointOnA(), - event.pointOnB(), - event.normalOnB(), - event.distance(), - event.impulse()); - } - } - - @Override - public void setGravity(float x, float y, float z) { - requireOpen(); - space.setGravity(toJme(x, y, z)); - } - - @Nonnull - @Override - public Vector3f getGravity() { - requireOpen(); - return fromJme(space.getGravity(new com.jme3.math.Vector3f())); - } - - @Override - public void addBody(@Nonnull PhysicsBody body) { - requireOpen(); - if (!(body instanceof BulletBody bulletBody)) { - throw new IllegalArgumentException("Body does not belong to bullet backend"); - } - requireBodyAddable(bulletBody); - space.addCollisionObject(bulletBody.getRigidBody()); - trackBody(bulletBody); - bulletBody.markAttachedTo(this); - } - - @Override - public void removeBody(@Nonnull PhysicsBody body) { - requireOpen(); - if (!(body instanceof BulletBody bulletBody)) { - return; - } - if (!ownsBody(bulletBody)) { - return; - } - - removeAttachedJoints(bulletBody); - if (bulletBody.isAttachedToSpace()) { - space.removeCollisionObject(bulletBody.getRigidBody()); - } - space.forgetBody(bulletBody.getNativeId()); - untrackBody(bulletBody); - } - - @Nonnull - @Override - public List getBodies() { - requireOpen(); - for (PhysicsRigidBody rigidBody : space.getRigidBodyList()) { - wrapBody(rigidBody); - } - List attachedBodies = new ArrayList<>(space.getRigidBodyList().size()); - for (BulletBody body : bodies) { - if (body.isAttachedToSpace()) { - attachedBodies.add(body); - } - } - return attachedBodies; - } - - @Override - public int bodyCount() { - requireOpen(); - return space.getRigidBodyList().size(); - } - - @Override - public void forEachBody(@Nonnull Consumer consumer) { - requireOpen(); - for (PhysicsRigidBody rigidBody : space.getRigidBodyList()) { - consumer.accept(wrapBody(rigidBody)); - } - } - - @Override - public boolean containsBody(@Nonnull PhysicsBody body) { - requireOpen(); - return body instanceof BulletBody bulletBody && ownsBody(bulletBody) - && bulletBody.isAttachedToSpace(); - } - - @Override - public void snapshotBodies(@Nonnull Consumer consumer) { - snapshotBodies(_ -> null, consumer); - } - - @Override - public void snapshotBodies(@Nonnull Function previousSnapshots, - @Nonnull Consumer consumer) { - snapshotBodies(previousSnapshots, (_, snapshot) -> consumer.accept(snapshot)); - } - - @Override - public void snapshotBodies(@Nonnull Function previousSnapshots, - @Nonnull BiConsumer consumer) { - requireOpen(); - for (BulletBody body : bodies) { - if (body.isAttachedToSpace()) { - consumer.accept(body, PhysicsBodySnapshot.from(body, previousSnapshots.apply(body))); - } - } - } - - @Nonnull - @Override - public Optional getCapability(@Nonnull Class type) { - Objects.requireNonNull(type, "type"); - if (type == PhysicsContinuousCollisionCapability.class) { - return Optional.of(type.cast(CONTINUOUS_COLLISION_CAPABILITY)); - } - if (type == PhysicsBackendEventsCapability.class) { - return Optional.of(type.cast(BACKEND_EVENTS_CAPABILITY)); - } - return Optional.empty(); - } - - @Nonnull - @Override - public List getCapabilityDescriptors() { - return CAPABILITY_DESCRIPTORS; - } - - @Nonnull - @Override - public PhysicsBody createStaticPlane(float groundY) { - requireOpen(); - Plane plane = new Plane(com.jme3.math.Vector3f.UNIT_Y, 0.0f); - CollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody body = new PhysicsRigidBody(shape, - com.jme3.bullet.objects.PhysicsBody.massForStatic); - body.setPhysicsLocation(toJme(0.0f, groundY, 0.0f)); - return trackBody(new BulletBody(body)); - } - - @Nonnull - @Override - public PhysicsBody createBox(float halfX, float halfY, float halfZ, float mass) { - requireOpen(); - CollisionShape shape = new BoxCollisionShape(toJme(halfX, halfY, halfZ)); - return trackBody(new BulletBody(new PhysicsRigidBody(shape, mass))); - } - - @Nonnull - @Override - public PhysicsBody createBox(@Nonnull Vector3f halfExtents, float mass) { - return createBox(halfExtents.x, halfExtents.y, halfExtents.z, mass); - } - - @Nonnull - @Override - public PhysicsBody createSphere(float radius, float mass) { - requireOpen(); - return trackBody(new BulletBody(new PhysicsRigidBody(new SphereCollisionShape(radius), mass))); - } - - @Nonnull - @Override - public PhysicsBody createCapsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - requireOpen(); - CollisionShape shape = new CapsuleCollisionShape(radius, halfHeight * 2f, axis.index()); - return trackBody(new BulletBody(new PhysicsRigidBody(shape, mass))); - } - - @Nonnull - @Override - public PhysicsBody createCylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - requireOpen(); - CollisionShape shape = new CylinderCollisionShape(radius, halfHeight * 2f, axis.index()); - return trackBody(new BulletBody(new PhysicsRigidBody(shape, mass))); - } - - @Nonnull - @Override - public PhysicsBody createCone(float radius, float halfHeight, @Nonnull PhysicsAxis axis, float mass) { - requireOpen(); - CollisionShape shape = new ConeCollisionShape(radius, halfHeight * 2f, axis.index()); - return trackBody(new BulletBody(new PhysicsRigidBody(shape, mass))); - } - - @Nonnull - @Override - public Optional raycastClosest(@Nonnull Vector3f from, @Nonnull Vector3f to) { - requireOpen(); - List hits = raycastAll(from, to); - PhysicsRayHit closest = null; - for (PhysicsRayHit hit : hits) { - if (closest == null || hit.fraction() < closest.fraction()) { - closest = hit; - } - } - return Optional.ofNullable(closest); - } - - @Nonnull - @Override - public List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f to) { - requireOpen(); - List results = space.rayTest(toJme(from), toJme(to)); - List hits = new ArrayList<>(results.size()); - Vector3f delta = new Vector3f(to).sub(from); - float distance = delta.length(); - for (PhysicsRayTestResult result : results) { - PhysicsCollisionObject collisionObject = result.getCollisionObject(); - if (!(collisionObject instanceof PhysicsRigidBody rigidBody)) { - continue; - } - BulletBody body = wrapBody(rigidBody); - float fraction = result.getHitFraction(); - Vector3f point = new Vector3f(from).fma(fraction, delta); - Vector3f normal = fromJme(result.getHitNormalLocal(new com.jme3.math.Vector3f())); - hits.add(new PhysicsRayHit(body, point, normal, fraction, distance * fraction)); - } - return hits; - } - - @Nonnull - @Override - public List getContacts() { - requireOpen(); - long[] manifolds = space.listManifoldIds(); - List contacts = new ArrayList<>(); - for (long manifold : manifolds) { - BulletBody bodyA = bodiesByNativeId.get(PersistentManifolds.getBodyAId(manifold)); - BulletBody bodyB = bodiesByNativeId.get(PersistentManifolds.getBodyBId(manifold)); - if (bodyA == null || bodyB == null) { - continue; - } - int pointCount = PersistentManifolds.countPoints(manifold); - for (int i = 0; i < pointCount; i++) { - long pointId = PersistentManifolds.getPointId(manifold, i); - Vector3f pointOnA = fromJmeVector(ManifoldPoints::getPositionWorldOnA, pointId); - Vector3f pointOnB = fromJmeVector(ManifoldPoints::getPositionWorldOnB, pointId); - Vector3f normal = fromJmeVector(ManifoldPoints::getNormalWorldOnB, pointId); - contacts.add(new PhysicsContact(bodyA, bodyB, pointOnA, pointOnB, normal, - ManifoldPoints.getDistance1(pointId), - ManifoldPoints.getAppliedImpulse(pointId))); - } - } - return contacts; - } - - @Nonnull - @Override - public PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - requireOpen(); - BulletBody bulletA = requireBody(bodyA); - BulletBody bulletB = requireBody(bodyB); - SixDofJoint joint = new SixDofJoint(bulletA.getRigidBody(), bulletB.getRigidBody(), - toJme(anchorA), toJme(anchorB), true); - joint.setLinearLowerLimit(toJme(0f, 0f, 0f)); - joint.setLinearUpperLimit(toJme(0f, 0f, 0f)); - joint.setAngularLowerLimit(toJme(0f, 0f, 0f)); - joint.setAngularUpperLimit(toJme(0f, 0f, 0f)); - return addJoint(new BulletJoint(PhysicsJointType.FIXED, bulletA, bulletB, joint, - anchorA, anchorB, null)); - } - - @Nonnull - @Override - public PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - requireOpen(); - BulletBody bulletA = requireBody(bodyA); - BulletBody bulletB = requireBody(bodyB); - Point2PointJoint joint = new Point2PointJoint(bulletA.getRigidBody(), bulletB.getRigidBody(), - toJme(anchorA), toJme(anchorB)); - return addJoint(new BulletJoint(PhysicsJointType.POINT, bulletA, bulletB, joint, - anchorA, anchorB, null)); - } - - @Nonnull - @Override - public PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - requireOpen(); - BulletBody bulletA = requireBody(bodyA); - BulletBody bulletB = requireBody(bodyB); - Vector3f normalizedAxis = normalizedOrDefault(axis); - HingeJoint joint = new HingeJoint(bulletA.getRigidBody(), bulletB.getRigidBody(), - toJme(anchorA), toJme(anchorB), toJme(normalizedAxis), toJme(normalizedAxis)); - return addJoint(new BulletJoint(PhysicsJointType.HINGE, bulletA, bulletB, joint, - anchorA, anchorB, normalizedAxis)); - } - - @Nonnull - @Override - public PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - requireOpen(); - BulletBody bulletA = requireBody(bodyA); - BulletBody bulletB = requireBody(bodyB); - Vector3f normalizedAxis = normalizedOrDefault(axis); - Matrix3f basis = basisForAxis(normalizedAxis); - SliderJoint joint = new SliderJoint(bulletA.getRigidBody(), bulletB.getRigidBody(), - toJme(anchorA), toJme(anchorB), basis, basis, true); - return addJoint(new BulletJoint(PhysicsJointType.SLIDER, bulletA, bulletB, joint, - anchorA, anchorB, normalizedAxis)); - } - - @Nonnull - @Override - public PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping) { - requireOpen(); - BulletBody bulletA = requireBody(bodyA); - BulletBody bulletB = requireBody(bodyB); - SixDofSpringJoint joint = new SixDofSpringJoint(bulletA.getRigidBody(), bulletB.getRigidBody(), - toJme(anchorA), toJme(anchorB), Matrix3f.IDENTITY, - Matrix3f.IDENTITY, true); - joint.enableSpring(0, true); - joint.setStiffness(0, stiffness); - joint.setDamping(0, damping); - joint.setEquilibriumPoint(0); - BulletJoint wrapped = new BulletJoint(PhysicsJointType.SPRING, bulletA, bulletB, joint, - anchorA, anchorB, new Vector3f(1f, 0f, 0f), restLength, stiffness, damping); - wrapped.setLimits(-restLength, restLength); - return addJoint(wrapped); - } - - @Override - public void removeJoint(@Nonnull PhysicsJoint joint) { - requireOpen(); - if (!(joint instanceof BulletJoint bulletJoint)) { - return; - } - removeJointInternal(bulletJoint); - } - - @Nonnull - @Override - public List getJoints() { - requireOpen(); - return new ArrayList<>(joints); - } - - @Override - public int jointCount() { - requireOpen(); - return joints.size(); - } - - @Override - public void forEachJoint(@Nonnull Consumer consumer) { - requireOpen(); - for (BulletJoint joint : joints) { - consumer.accept(joint); - } - } - - @Override - public void close() { - if (closed) { - return; - } - - closed = true; - try { - for (BulletJoint joint : new ArrayList<>(joints)) { - removeJointInternal(joint); - } - - for (BulletBody body : new ArrayList<>(bodies)) { - if (body.isAttachedToSpace()) { - space.removeCollisionObject(body.getRigidBody()); - } - space.forgetBody(body.getNativeId()); - body.invalidateFrom(this); - } - bodies.clear(); - bodiesByRigidBody.clear(); - bodiesByNativeId.clear(); - } finally { - space.destroy(); - } - } - - private BulletJoint addJoint(@Nonnull BulletJoint joint) { - space.addJoint(joint.getNativeJoint()); - joints.add(joint); - return joint; - } - - private BulletBody requireBody(@Nonnull PhysicsBody body) { - if (!(body instanceof BulletBody bulletBody)) { - throw new IllegalArgumentException("Body does not belong to bullet backend"); - } - if (!ownsBody(bulletBody)) { - throw new IllegalArgumentException("Body does not belong to this bullet space"); - } - bulletBody.requireNotInvalidated(); - return bulletBody; - } - - private BulletBody trackBody(@Nonnull BulletBody body) { - body.bindTo(this); - if (!bodiesByRigidBody.containsKey(body.getRigidBody())) { - bodiesByRigidBody.put(body.getRigidBody(), body); - bodiesByNativeId.put(body.getNativeId(), body); - bodies.add(body); - } - return body; - } - - private void untrackBody(@Nonnull BulletBody body) { - bodiesByRigidBody.remove(body.getRigidBody()); - bodiesByNativeId.remove(body.getNativeId()); - bodies.remove(body); - body.detachFrom(this); - } - - private void removeAttachedJoints(@Nonnull BulletBody body) { - for (BulletJoint joint : new ArrayList<>(joints)) { - if (joint.getBodyA() != body && joint.getBodyB() != body) { - continue; - } - - removeJointInternal(joint); - } - } - - private BulletBody wrapBody(@Nonnull PhysicsRigidBody rigidBody) { - BulletBody existing = bodiesByRigidBody.get(rigidBody); - if (existing != null) { - return existing; - } - BulletBody body = trackBody(new BulletBody(rigidBody)); - body.markAttachedTo(this); - return body; - } - - private void removeJointInternal(@Nonnull BulletJoint joint) { - if (!joints.remove(joint)) { - return; - } - space.removeJoint(joint.getNativeJoint()); - } - - private void requireBodyAddable(@Nonnull BulletBody body) { - body.requireNotInvalidated(); - BulletSpace owner = body.getOwner(); - if (owner != null && owner != this) { - throw new IllegalArgumentException("Body belongs to another bullet space"); - } - if (body.isAttachedToSpace()) { - throw new IllegalStateException("Body is already attached to a bullet space"); - } - } - - private boolean ownsBody(@Nonnull BulletBody body) { - return body.isOwnedBy(this) && bodiesByRigidBody.get(body.getRigidBody()) == body; - } - - private void requireOpen() { - if (closed) { - throw new IllegalStateException("Bullet space is closed: " + id); - } - } - - private static Vector3f normalizedOrDefault(@Nonnull Vector3f axis) { - Vector3f normalized = new Vector3f(axis); - if (normalized.lengthSquared() == 0f) { - normalized.set(0f, 1f, 0f); - } - return normalized.normalize(); - } - - private static Matrix3f basisForAxis(@Nonnull Vector3f axis) { - Vector3f x = normalizedOrDefault(axis); - Vector3f up = Math.abs(x.y) < 0.9f - ? new Vector3f(0f, 1f, 0f) - : new Vector3f(1f, 0f, 0f); - Vector3f z = new Vector3f(x).cross(up).normalize(); - Vector3f y = new Vector3f(z).cross(x).normalize(); - Matrix3f basis = new Matrix3f(); - basis.fromAxes(toJme(x), toJme(y), toJme(z)); - return basis; - } - - private static com.jme3.math.Vector3f toJme(@Nonnull Vector3f vector) { - return toJme(vector.x, vector.y, vector.z); - } - - private static com.jme3.math.Vector3f toJme(float x, float y, float z) { - return new com.jme3.math.Vector3f(x, y, z); - } - - private static Vector3f fromJme(@Nonnull com.jme3.math.Vector3f vector) { - return new Vector3f(vector.x, vector.y, vector.z); - } - - private static Vector3f fromJmeVector(@Nonnull ManifoldVectorReader reader, long pointId) { - com.jme3.math.Vector3f out = new com.jme3.math.Vector3f(); - reader.read(pointId, out); - return fromJme(out); - } - - @FunctionalInterface - private interface ManifoldVectorReader { - void read(long pointId, com.jme3.math.Vector3f out); - } -} diff --git a/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider b/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider deleted file mode 100644 index 9a1c90a1..00000000 --- a/impulse-bullet/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider +++ /dev/null @@ -1 +0,0 @@ -dev.hytalemodding.impulse.bullet.BulletBackendRuntimeProvider diff --git a/impulse-core/README.md b/impulse-core/README.md index fdc159d5..e09f4bbe 100644 --- a/impulse-core/README.md +++ b/impulse-core/README.md @@ -19,7 +19,7 @@ services from jars anywhere under the configured Hytale `mods` directories. ## Event frames `PhysicsWorlds.latestEventFrame(physicsStore)` exposes the latest value-only physics event frame -for diagnostics. When collection is enabled, backends emit bounded post-step `PhysicsBackendEvent` +for diagnostics. When collection is enabled, backends emit bounded post-step event batches; core translates them to stable UUID-primary `PhysicsFrameEvent` values and copied PhysicsStore refs where available, then publishes one `PhysicsEventFramePublishedEvent` Hytale world event for the completed frame. diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java index d0577b3d..8ce9b56f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java @@ -12,30 +12,30 @@ class CrucibleBackendsTest { @Test void configuredBackendWinsWhenRegistered() { - BackendId bullet = new BackendId("impulse:bullet"); + BackendId jolt = new BackendId("impulse:jolt"); BackendId rapier = new BackendId("impulse:rapier"); - assertEquals(bullet, CrucibleBackends.selectBackendId(List.of( - provider(bullet), - provider(rapier)), "impulse:bullet")); + assertEquals(jolt, CrucibleBackends.selectBackendId(List.of( + provider(jolt), + provider(rapier)), "impulse:jolt")); } @Test void rapierIsPreferredWhenMultipleBackendsAreRegistered() { - BackendId bullet = new BackendId("impulse:bullet"); + BackendId jolt = new BackendId("impulse:jolt"); BackendId rapier = new BackendId("impulse:rapier"); assertEquals(rapier, CrucibleBackends.selectBackendId(List.of( - provider(bullet), + provider(jolt), provider(rapier)), null)); } @Test void singleBackendIsSelectedWhenRapierIsUnavailable() { - BackendId bullet = new BackendId("impulse:bullet"); + BackendId jolt = new BackendId("impulse:jolt"); - assertEquals(bullet, CrucibleBackends.selectBackendId(List.of( - provider(bullet)), null)); + assertEquals(jolt, CrucibleBackends.selectBackendId(List.of( + provider(jolt)), null)); } @Test diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java index ce3ee0f6..af0b4f56 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -140,9 +140,9 @@ void voxelRowsWithSameNumericSpaceHandleAreNotStitchedAcrossBackends() { EmptyResourceStorage.get()); try { UUID spaceUuid = uuid(11); - RuntimeFixture bulletRuntime = addBoundSpace(store, + RuntimeFixture joltRuntime = addBoundSpace(store, spaceUuid, - new BackendId("test:chunk-collision-bullet")); + new BackendId("test:chunk-collision-jolt")); RuntimeFixture rapierRuntime = addBoundSpace(store, uuid(12), new BackendId("test:chunk-collision-rapier")); @@ -151,7 +151,7 @@ void voxelRowsWithSameNumericSpaceHandleAreNotStitchedAcrossBackends() { String firstPayloadKey = "chunk-collision/cross-backend/0"; String secondPayloadKey = "chunk-collision/cross-backend/1"; addVoxelRow(store, - bulletRuntime, + joltRuntime, spaceUuid, firstSourceKey, firstPayloadKey, @@ -175,7 +175,7 @@ void voxelRowsWithSameNumericSpaceHandleAreNotStitchedAcrossBackends() { runStitchingSystem(store); assertEquals(List.of(), - bulletRuntime.backendRuntime().combineCalls(bulletRuntime.spaceHandle().value())); + joltRuntime.backendRuntime().combineCalls(joltRuntime.spaceHandle().value())); assertEquals(List.of(), rapierRuntime.backendRuntime().combineCalls(rapierRuntime.spaceHandle().value())); assertSoftSkipsEmpty(store); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java index 1f577c49..bb5e1918 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsContactDebugCapture.java @@ -1,7 +1,5 @@ package dev.hytalemodding.impulse.core.internal.systems.debug; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsSpace; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; @@ -15,7 +13,7 @@ private PhysicsContactDebugCapture() { @Nonnull static List collectVisibleContactPrimitives( - @Nonnull PhysicsSpace space, + @Nonnull List contacts, @Nonnull Vector3d center, double radius, int maxContacts) { @@ -24,7 +22,7 @@ static List collectVisibleContactPri } List primitives = new ArrayList<>(); double radiusSquared = radius * radius; - for (PhysicsContact contact : space.getContacts(maxContacts)) { + for (ContactDebugSource contact : contacts) { Vector3f pointOnB = contact.pointOnB(); Vector3d point = new Vector3d(pointOnB.x, pointOnB.y, pointOnB.z); if (point.distanceSquared(center) <= radiusSquared) { @@ -38,4 +36,7 @@ static List collectVisibleContactPri } return List.copyOf(primitives); } + + record ContactDebugSource(@Nonnull Vector3f pointOnB, @Nonnull Vector3f normalOnB) { + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java index 156cff50..8155a7f3 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystemTest.java @@ -4,18 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsSpace; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackend.InMemoryPhysicsSpace; import java.util.concurrent.CompletableFuture; import java.util.List; import java.util.UUID; @@ -280,20 +274,16 @@ void debugQueryCacheDoesNotSubmitDuplicateJointsWhilePending() { @Test void collectVisibleJointPrimitivesUsesAnchorsForDistanceAndRendering() { - PhysicsSpace space = new FakePhysicsBackend(new BackendId("test:debug-joints")) - .createSpace(new SpaceId(1)); - PhysicsBody bodyA = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody bodyB = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - bodyA.setPosition(1.0f, 0.0f, 0.0f); - bodyB.setPosition(5.0f, 0.0f, 0.0f); - space.createHingeJoint(bodyA, - bodyB, - new Vector3f(1.0f, 0.0f, 0.0f), - new Vector3f(-1.0f, 0.0f, 0.0f), - new Vector3f(0.0f, 1.0f, 0.0f)); + List joints = List.of( + new PhysicsJointDebugCapture.JointDebugSource( + new Vector3f(1.0f, 0.0f, 0.0f), + new Vector3f(5.0f, 0.0f, 0.0f), + new Vector3f(1.0f, 0.0f, 0.0f), + new Vector3f(-1.0f, 0.0f, 0.0f), + new Vector3f(0.0f, 1.0f, 0.0f))); List visible = - PhysicsJointDebugCapture.collectVisibleJointPrimitives(space, + PhysicsJointDebugCapture.collectVisibleJointPrimitives(joints, new Vector3d(3.0, 0.0, 0.0), 1.0, 4); @@ -306,11 +296,11 @@ void collectVisibleJointPrimitivesUsesAnchorsForDistanceAndRendering() { assertEquals(0.9, primitive.axis().y, 0.00001); assertEquals(0.0, primitive.axis().z, 0.00001); - assertTrue(PhysicsJointDebugCapture.collectVisibleJointPrimitives(space, + assertTrue(PhysicsJointDebugCapture.collectVisibleJointPrimitives(joints, new Vector3d(8.0, 0.0, 0.0), 1.0, 4).isEmpty()); - assertTrue(PhysicsJointDebugCapture.collectVisibleJointPrimitives(space, + assertTrue(PhysicsJointDebugCapture.collectVisibleJointPrimitives(joints, new Vector3d(3.0, 0.0, 0.0), 1.0, 0).isEmpty()); @@ -318,20 +308,13 @@ void collectVisibleJointPrimitivesUsesAnchorsForDistanceAndRendering() { @Test void collectVisibleContactPrimitivesCapturesPointsAndNormals() { - InMemoryPhysicsSpace space = (InMemoryPhysicsSpace) new FakePhysicsBackend( - new BackendId("test:debug-contacts")).createSpace(new SpaceId(2)); - PhysicsBody bodyA = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody bodyB = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - space.addContact(new PhysicsContact(bodyA, - bodyB, - new Vector3f(0.0f, 0.0f, 0.0f), - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(0.0f, 2.0f, 0.0f), - -0.1f, - 20.0f)); + List contacts = List.of( + new PhysicsContactDebugCapture.ContactDebugSource( + new Vector3f(1.0f, 2.0f, 3.0f), + new Vector3f(0.0f, 2.0f, 0.0f))); List visible = - PhysicsContactDebugCapture.collectVisibleContactPrimitives(space, + PhysicsContactDebugCapture.collectVisibleContactPrimitives(contacts, new Vector3d(1.0, 2.0, 3.0), 0.5, 4); @@ -343,11 +326,11 @@ void collectVisibleContactPrimitivesCapturesPointsAndNormals() { assertEquals(1.0, primitive.normal().y, 0.00001); assertEquals(0.0, primitive.normal().z, 0.00001); - assertTrue(PhysicsContactDebugCapture.collectVisibleContactPrimitives(space, + assertTrue(PhysicsContactDebugCapture.collectVisibleContactPrimitives(contacts, new Vector3d(3.0, 2.0, 3.0), 0.5, 4).isEmpty()); - assertTrue(PhysicsContactDebugCapture.collectVisibleContactPrimitives(space, + assertTrue(PhysicsContactDebugCapture.collectVisibleContactPrimitives(contacts, new Vector3d(1.0, 2.0, 3.0), 0.5, 0).isEmpty()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsJointDebugCapture.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsJointDebugCapture.java index 956f74af..2a8241fe 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsJointDebugCapture.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsJointDebugCapture.java @@ -1,10 +1,9 @@ package dev.hytalemodding.impulse.core.internal.systems.debug; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsSpace; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.joml.Vector3d; import org.joml.Vector3f; @@ -15,7 +14,7 @@ private PhysicsJointDebugCapture() { @Nonnull static List collectVisibleJointPrimitives( - @Nonnull PhysicsSpace space, + @Nonnull List joints, @Nonnull Vector3d center, double radius, int maxJoints) { @@ -24,12 +23,12 @@ static List collectVisibleJointPrimiti } List primitives = new ArrayList<>(); double radiusSquared = radius * radius; - for (PhysicsJoint joint : space.getJoints()) { - Vector3d anchorA = worldAnchor(joint.getBodyA().getPosition(), joint.getAnchorA()); - Vector3d anchorB = worldAnchor(joint.getBodyB().getPosition(), joint.getAnchorB()); + for (JointDebugSource joint : joints) { + Vector3d anchorA = worldAnchor(joint.bodyAPosition(), joint.anchorA()); + Vector3d anchorB = worldAnchor(joint.bodyBPosition(), joint.anchorB()); Vector3d midpoint = new Vector3d(anchorA).add(anchorB).mul(0.5); if (midpoint.distanceSquared(center) <= radiusSquared) { - Vector3f axis = joint.getAxis(); + Vector3f axis = joint.axis(); Vector3d axisDebug = axis != null ? new Vector3d(axis.x, axis.y, axis.z).normalize().mul(0.9) : null; @@ -49,4 +48,11 @@ private static Vector3d worldAnchor(@Nonnull Vector3f bodyPosition, bodyPosition.y + localAnchor.y, bodyPosition.z + localAnchor.z); } + + record JointDebugSource(@Nonnull Vector3f bodyAPosition, + @Nonnull Vector3f bodyBPosition, + @Nonnull Vector3f anchorA, + @Nonnull Vector3f anchorB, + @Nullable Vector3f axis) { + } } diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackend.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackend.java deleted file mode 100644 index bd018843..00000000 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackend.java +++ /dev/null @@ -1,52 +0,0 @@ -package dev.hytalemodding.impulse.rapier; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsBackend; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.SpaceId; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nonnull; - -public final class RapierBackend implements PhysicsBackend { - - public static final BackendId ID = new BackendId("impulse:rapier"); - private static final Logger LOGGER = Logger.getLogger("Impulse"); - private static final AtomicInteger SPACES_CREATED = new AtomicInteger(0); - - private volatile boolean initialized; - - @Nonnull - @Override - public BackendId getId() { - return ID; - } - - @Override - public synchronized void init() { - if (initialized) { - return; - } - - RapierNative.load(); - initialized = true; - LOGGER.log(Level.INFO, "Rapier backend initialized"); - } - - @Nonnull - @Override - public PhysicsSpace createSpace() { - return createSpace(SpaceId.next()); - } - - @Nonnull - @Override - public PhysicsSpace createSpace(@Nonnull SpaceId spaceId) { - int count = SPACES_CREATED.incrementAndGet(); - LOGGER.log(Level.FINE, - "Creating Rapier physics space #" + count + " on thread " - + Thread.currentThread().getName()); - return new RapierSpace(spaceId, this, RapierNative.createSpaceNative()); - } -} diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java index 8c0b2791..4924d853 100644 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java +++ b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java @@ -10,17 +10,17 @@ */ public final class RapierBackendRuntimeProvider implements PhysicsBackendRuntimeProvider { - private final RapierBackend backend = new RapierBackend(); + private static final BackendId ID = new BackendId("impulse:rapier"); @Nonnull @Override public BackendId getId() { - return RapierBackend.ID; + return ID; } @Override public void init() { - backend.init(); + RapierNative.load(); } @Nonnull diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBody.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBody.java deleted file mode 100644 index b525b73a..00000000 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBody.java +++ /dev/null @@ -1,733 +0,0 @@ -package dev.hytalemodding.impulse.rapier; - -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -public final class RapierBody implements PhysicsBody { - - private static final float DEFAULT_DYNAMIC_MASS = 1.0f; - - private final ShapeType shapeType; - private final Vector3f boxHalfExtents; - private final float sphereRadius; - private final float halfHeight; - private final PhysicsAxis axis; - private final float centerOfMassOffsetY; - private final Vector3f voxelSize; - private final int[] voxelCoordinates; - - private final Vector3f position = new Vector3f(); - private final Quaternionf rotation = new Quaternionf(); - private final Vector3f linearVelocity = new Vector3f(); - private final Vector3f angularVelocity = new Vector3f(); - private final float[] vec3Scratch = new float[3]; - private final float[] quatScratch = new float[4]; - private final float[] dampingScratch = new float[2]; - private final int[] collisionFilterScratch = new int[2]; - - private float mass; - private float friction = 0.5f; - private float restitution; - private float linearDamping; - private float angularDamping; - private PhysicsBodyType bodyType; - private boolean sensor; - private int collisionGroup = 1; - private int collisionMask = 1; - private boolean continuousCollisionEnabled; - - private RapierSpace space; - private long bodyHandle; - - private RapierBody(@Nonnull ShapeType shapeType, - @Nullable Vector3f boxHalfExtents, - float sphereRadius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float centerOfMassOffsetY, - @Nullable Vector3f voxelSize, - @Nullable int[] voxelCoordinates, - float mass) { - this.shapeType = shapeType; - this.boxHalfExtents = boxHalfExtents != null ? new Vector3f(boxHalfExtents) : null; - this.sphereRadius = sphereRadius; - this.halfHeight = halfHeight; - this.axis = axis; - this.centerOfMassOffsetY = centerOfMassOffsetY; - this.voxelSize = voxelSize != null ? new Vector3f(voxelSize) : null; - this.voxelCoordinates = voxelCoordinates != null ? voxelCoordinates.clone() : null; - this.mass = mass; - this.bodyType = mass <= 0f ? PhysicsBodyType.STATIC : PhysicsBodyType.DYNAMIC; - } - - @Nonnull - static RapierBody box(float halfX, float halfY, float halfZ, float mass) { - return new RapierBody(ShapeType.BOX, new Vector3f(halfX, halfY, halfZ), -1f, - -1f, PhysicsAxis.Y, halfY, null, null, mass); - } - - @Nonnull - static RapierBody sphere(float radius, float mass) { - return new RapierBody(ShapeType.SPHERE, null, radius, -1f, PhysicsAxis.Y, - radius, null, null, mass); - } - - @Nonnull - static RapierBody capsule(float radius, float halfHeight, @Nonnull PhysicsAxis axis, float mass) { - float offsetY = axis == PhysicsAxis.Y ? halfHeight + radius : radius; - return new RapierBody(ShapeType.CAPSULE, null, radius, halfHeight, axis, offsetY, - null, null, mass); - } - - @Nonnull - static RapierBody cylinder(float radius, float halfHeight, @Nonnull PhysicsAxis axis, float mass) { - float offsetY = axis == PhysicsAxis.Y ? halfHeight : radius; - return new RapierBody(ShapeType.CYLINDER, null, radius, halfHeight, axis, offsetY, - null, null, mass); - } - - @Nonnull - static RapierBody cone(float radius, float halfHeight, @Nonnull PhysicsAxis axis, float mass) { - float offsetY = axis == PhysicsAxis.Y ? halfHeight : radius; - return new RapierBody(ShapeType.CONE, null, radius, halfHeight, axis, offsetY, - null, null, mass); - } - - @Nonnull - static RapierBody staticPlane(float groundY) { - RapierBody body = new RapierBody(ShapeType.PLANE, null, -1f, -1f, PhysicsAxis.Y, - 0f, null, null, 0f); - body.position.set(0f, groundY, 0f); - return body; - } - - @Nonnull - static RapierBody voxelTerrain(float voxelSizeX, - float voxelSizeY, - float voxelSizeZ, - @Nonnull int[] voxelCoordinates) { - return new RapierBody(ShapeType.VOXELS, null, -1f, -1f, PhysicsAxis.Y, - 0f, new Vector3f(voxelSizeX, voxelSizeY, voxelSizeZ), voxelCoordinates, 0f); - } - - @Override - public void setPosition(float x, float y, float z) { - position.set(x, y, z); - if (isAttached()) { - RapierNative.setBodyPositionNative(getSpaceHandle(), bodyHandle, x, y, z); - } - } - - @Override - public void setPosition(@Nonnull Vector3f pos) { - setPosition(pos.x, pos.y, pos.z); - } - - @Nonnull - @Override - public Vector3f getPosition() { - Vector3f out = new Vector3f(); - getPosition(out); - return out; - } - - @Override - public void getPosition(@Nonnull Vector3f out) { - if (isAttached()) { - RapierNative.getBodyPositionNative(getSpaceHandle(), bodyHandle, vec3Scratch); - position.set(vec3Scratch[0], vec3Scratch[1], vec3Scratch[2]); - } - out.set(position); - } - - @Override - public void setRotation(float x, float y, float z, float w) { - setStoredRotation(x, y, z, w); - if (isAttached()) { - RapierNative.setBodyRotationNative(getSpaceHandle(), bodyHandle, - rotation.x, rotation.y, rotation.z, rotation.w); - } - } - - @Override - public void setRotation(@Nonnull Quaternionf rot) { - setRotation(rot.x, rot.y, rot.z, rot.w); - } - - @Nonnull - @Override - public Quaternionf getRotation() { - Quaternionf out = new Quaternionf(); - getRotation(out); - return out; - } - - @Override - public void getRotation(@Nonnull Quaternionf out) { - if (isAttached()) { - RapierNative.getBodyRotationNative(getSpaceHandle(), bodyHandle, quatScratch); - setStoredRotation(quatScratch[0], quatScratch[1], quatScratch[2], quatScratch[3]); - } - out.set(rotation); - } - - @Override - public void setRestitution(float restitution) { - this.restitution = restitution; - if (isAttached()) { - RapierNative.setBodyRestitutionNative(getSpaceHandle(), bodyHandle, restitution); - } - } - - @Override - public float getRestitution() { - return restitution; - } - - @Override - public void setFriction(float friction) { - this.friction = friction; - if (isAttached()) { - RapierNative.setBodyFrictionNative(getSpaceHandle(), bodyHandle, friction); - } - } - - @Override - public float getFriction() { - return friction; - } - - @Nonnull - @Override - public PhysicsBodyType getBodyType() { - if (isAttached()) { - bodyType = PhysicsBodyType.values()[RapierNative.getBodyTypeNative(getSpaceHandle(), bodyHandle)]; - } - return bodyType; - } - - @Override - public void setBodyType(@Nonnull PhysicsBodyType bodyType) { - if (bodyType == PhysicsBodyType.STATIC) { - if (mass != 0f) { - setMass(0f); - } - } else if (mass <= 0f) { - setMass(DEFAULT_DYNAMIC_MASS); - } - this.bodyType = bodyType; - if (isAttached()) { - RapierNative.setBodyTypeNative(getSpaceHandle(), bodyHandle, bodyType.ordinal()); - } - } - - @Override - public boolean isStatic() { - return getBodyType() == PhysicsBodyType.STATIC; - } - - @Override - public boolean isKinematic() { - return getBodyType() == PhysicsBodyType.KINEMATIC; - } - - @Override - public void setKinematic(boolean kinematic) { - if (kinematic) { - setBodyType(PhysicsBodyType.KINEMATIC); - } else if (!isStatic()) { - setBodyType(PhysicsBodyType.DYNAMIC); - } - } - - @Override - public void activate() { - if (isAttached()) { - RapierNative.activateBodyNative(getSpaceHandle(), bodyHandle); - } - } - - @Override - public boolean isActive() { - return !isSleeping(); - } - - @Override - public boolean isSleeping() { - return isAttached() && RapierNative.isBodySleepingNative(getSpaceHandle(), bodyHandle); - } - - @Override - public void sleep() { - if (isAttached()) { - RapierNative.sleepBodyNative(getSpaceHandle(), bodyHandle); - } - } - - @Override - public float getMass() { - if (isAttached()) { - mass = RapierNative.getBodyMassNative(getSpaceHandle(), bodyHandle); - } - return mass; - } - - @Override - public void setMass(float mass) { - this.mass = mass; - if (mass <= 0f) { - bodyType = PhysicsBodyType.STATIC; - } - if (isAttached()) { - RapierNative.setBodyMassNative(getSpaceHandle(), bodyHandle, mass); - } - } - - @Nonnull - @Override - public Vector3f getLinearVelocity() { - Vector3f out = new Vector3f(); - getLinearVelocity(out); - return out; - } - - @Override - public void getLinearVelocity(@Nonnull Vector3f out) { - if (isAttached()) { - RapierNative.getBodyLinearVelocityNative(getSpaceHandle(), bodyHandle, vec3Scratch); - linearVelocity.set(vec3Scratch[0], vec3Scratch[1], vec3Scratch[2]); - } - out.set(linearVelocity); - } - - @Override - public void setLinearVelocity(@Nonnull Vector3f vel) { - setLinearVelocity(vel.x, vel.y, vel.z); - } - - @Override - public void setLinearVelocity(float x, float y, float z) { - linearVelocity.set(x, y, z); - if (isAttached()) { - RapierNative.setBodyLinearVelocityNative(getSpaceHandle(), bodyHandle, x, y, z); - } - } - - @Nonnull - @Override - public Vector3f getAngularVelocity() { - Vector3f out = new Vector3f(); - getAngularVelocity(out); - return out; - } - - @Override - public void getAngularVelocity(@Nonnull Vector3f out) { - if (isAttached()) { - RapierNative.getBodyAngularVelocityNative(getSpaceHandle(), bodyHandle, vec3Scratch); - angularVelocity.set(vec3Scratch[0], vec3Scratch[1], vec3Scratch[2]); - } - out.set(angularVelocity); - } - - @Override - public void setAngularVelocity(@Nonnull Vector3f vel) { - setAngularVelocity(vel.x, vel.y, vel.z); - } - - @Override - public void setAngularVelocity(float x, float y, float z) { - angularVelocity.set(x, y, z); - if (isAttached()) { - RapierNative.setBodyAngularVelocityNative(getSpaceHandle(), bodyHandle, x, y, z); - } - } - - @Override - public float getLinearDamping() { - refreshDamping(); - return linearDamping; - } - - @Override - public void setLinearDamping(float damping) { - linearDamping = damping; - pushDamping(); - } - - @Override - public float getAngularDamping() { - refreshDamping(); - return angularDamping; - } - - @Override - public void setAngularDamping(float damping) { - angularDamping = damping; - pushDamping(); - } - - @Override - public void applyCentralForce(@Nonnull Vector3f force) { - applyCentralForce(force.x, force.y, force.z); - } - - @Override - public void applyCentralForce(float x, float y, float z) { - if (isAttached()) { - RapierNative.applyBodyCentralForceNative(getSpaceHandle(), bodyHandle, x, y, z); - } - } - - @Override - public void applyForce(@Nonnull Vector3f force, @Nonnull Vector3f offset) { - applyForce(force.x, force.y, force.z, offset.x, offset.y, offset.z); - } - - @Override - public void applyForce(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - if (isAttached()) { - RapierNative.applyBodyForceNative(getSpaceHandle(), bodyHandle, - x, y, z, offsetX, offsetY, offsetZ); - } - } - - @Override - public void applyCentralImpulse(@Nonnull Vector3f impulse) { - applyCentralImpulse(impulse.x, impulse.y, impulse.z); - } - - @Override - public void applyCentralImpulse(float x, float y, float z) { - if (isAttached()) { - RapierNative.applyBodyCentralImpulseNative(getSpaceHandle(), bodyHandle, x, y, z); - } - } - - @Override - public void applyImpulse(@Nonnull Vector3f impulse, @Nonnull Vector3f offset) { - applyImpulse(impulse.x, impulse.y, impulse.z, offset.x, offset.y, offset.z); - } - - @Override - public void applyImpulse(float x, - float y, - float z, - float offsetX, - float offsetY, - float offsetZ) { - if (isAttached()) { - RapierNative.applyBodyImpulseNative(getSpaceHandle(), bodyHandle, - x, y, z, offsetX, offsetY, offsetZ); - } - } - - @Override - public void applyTorque(@Nonnull Vector3f torque) { - applyTorque(torque.x, torque.y, torque.z); - } - - @Override - public void applyTorque(float x, float y, float z) { - if (isAttached()) { - RapierNative.applyBodyTorqueNative(getSpaceHandle(), bodyHandle, - x, y, z); - } - } - - @Override - public void applyTorqueImpulse(@Nonnull Vector3f torqueImpulse) { - applyTorqueImpulse(torqueImpulse.x, torqueImpulse.y, torqueImpulse.z); - } - - @Override - public void applyTorqueImpulse(float x, float y, float z) { - if (isAttached()) { - RapierNative.applyBodyTorqueImpulseNative(getSpaceHandle(), bodyHandle, - x, y, z); - } - } - - @Override - public void clearForces() { - if (isAttached()) { - RapierNative.clearBodyForcesNative(getSpaceHandle(), bodyHandle); - } - } - - @Override - public boolean isSensor() { - if (isAttached()) { - sensor = RapierNative.isBodySensorNative(getSpaceHandle(), bodyHandle); - } - return sensor; - } - - @Override - public void setSensor(boolean sensor) { - this.sensor = sensor; - if (isAttached()) { - RapierNative.setBodySensorNative(getSpaceHandle(), bodyHandle, sensor); - } - } - - @Override - public int getCollisionGroup() { - refreshCollisionFilter(); - return collisionGroup; - } - - @Override - public int getCollisionMask() { - refreshCollisionFilter(); - return collisionMask; - } - - @Override - public void setCollisionFilter(int group, int mask) { - collisionGroup = group; - collisionMask = mask; - if (isAttached()) { - RapierNative.setBodyCollisionFilterNative(getSpaceHandle(), bodyHandle, group, mask); - } - } - - @Override - public boolean isContinuousCollisionEnabled() { - if (isAttached()) { - continuousCollisionEnabled = RapierNative.isBodyCcdNative(getSpaceHandle(), bodyHandle); - } - return continuousCollisionEnabled; - } - - @Override - public void setContinuousCollisionEnabled(boolean enabled) { - continuousCollisionEnabled = enabled; - if (isAttached()) { - RapierNative.setBodyCcdNative(getSpaceHandle(), bodyHandle, enabled); - } - } - - @Nonnull - @Override - public ShapeType getShapeType() { - return shapeType; - } - - @Nullable - @Override - public Vector3f getBoxHalfExtents() { - return boxHalfExtents != null ? new Vector3f(boxHalfExtents) : null; - } - - @Override - public float getSphereRadius() { - return sphereRadius; - } - - @Override - public float getHalfHeight() { - return halfHeight; - } - - @Nonnull - @Override - public PhysicsAxis getShapeAxis() { - return axis; - } - - @Override - public float getCenterOfMassOffsetY() { - return centerOfMassOffsetY; - } - - boolean isAttached() { - return space != null && bodyHandle != 0L; - } - - boolean isAttachedTo(@Nonnull RapierSpace owner) { - return space == owner && bodyHandle != 0L; - } - - void attach(@Nonnull RapierSpace space, long bodyHandle) { - if (isAttached()) { - throw new IllegalStateException("Rapier body is already attached to a space"); - } - this.space = space; - this.bodyHandle = bodyHandle; - } - - void detach(@Nonnull RapierSpace owner) { - if (space != owner) { - return; - } - space = null; - bodyHandle = 0L; - } - - long getBodyHandle() { - return bodyHandle; - } - - float getStoredMass() { - return mass; - } - - float getStoredFriction() { - return friction; - } - - float getStoredRestitution() { - return restitution; - } - - float getStoredLinearDamping() { - return linearDamping; - } - - float getStoredAngularDamping() { - return angularDamping; - } - - boolean getStoredSensor() { - return sensor; - } - - int getStoredCollisionGroup() { - return collisionGroup; - } - - int getStoredCollisionMask() { - return collisionMask; - } - - boolean getStoredContinuousCollisionEnabled() { - return continuousCollisionEnabled; - } - - @Nonnull - PhysicsBodyType getStoredBodyType() { - return bodyType; - } - - @Nonnull - PhysicsBodySnapshot snapshotFromNative(@Nonnull float[] values, - int offset, - @Nullable PhysicsBodySnapshot previous) { - boolean nativeSleeping = values[offset + 14] != 0.0f; - if (nativeSleeping && previous != null && previous.sleeping()) { - return previous; - } - - position.set(values[offset], values[offset + 1], values[offset + 2]); - setStoredRotation(values[offset + 3], values[offset + 4], values[offset + 5], - values[offset + 6]); - linearVelocity.set(values[offset + 7], values[offset + 8], values[offset + 9]); - angularVelocity.set(values[offset + 10], values[offset + 11], values[offset + 12]); - - int bodyTypeOrdinal = Math.round(values[offset + 13]); - PhysicsBodyType[] bodyTypes = PhysicsBodyType.values(); - if (bodyTypeOrdinal >= 0 && bodyTypeOrdinal < bodyTypes.length) { - bodyType = bodyTypes[bodyTypeOrdinal]; - } - sensor = values[offset + 15] != 0.0f; - - return new PhysicsBodySnapshot(position, - rotation, - linearVelocity, - angularVelocity, - bodyType, - nativeSleeping, - sensor, - centerOfMassOffsetY, - shapeType, - boxHalfExtents, - sphereRadius, - halfHeight, - axis); - } - - @Nonnull - Vector3f getStoredPosition() { - return new Vector3f(position); - } - - @Nonnull - Quaternionf getStoredRotation() { - return new Quaternionf(rotation); - } - - @Nonnull - Vector3f getStoredLinearVelocity() { - return new Vector3f(linearVelocity); - } - - @Nonnull - Vector3f getStoredAngularVelocity() { - return new Vector3f(angularVelocity); - } - - @Nonnull - Vector3f getVoxelSize() { - if (voxelSize == null) { - throw new IllegalStateException("Body is not a voxel terrain body"); - } - return new Vector3f(voxelSize); - } - - @Nonnull - int[] getVoxelCoordinates() { - if (voxelCoordinates == null) { - throw new IllegalStateException("Body is not a voxel terrain body"); - } - return voxelCoordinates.clone(); - } - - private long getSpaceHandle() { - return space.getNativeSpaceHandle(); - } - - private void refreshDamping() { - if (isAttached()) { - RapierNative.getBodyDampingNative(getSpaceHandle(), bodyHandle, dampingScratch); - linearDamping = dampingScratch[0]; - angularDamping = dampingScratch[1]; - } - } - - private void pushDamping() { - if (isAttached()) { - RapierNative.setBodyDampingNative(getSpaceHandle(), bodyHandle, - linearDamping, angularDamping); - } - } - - private void refreshCollisionFilter() { - if (isAttached()) { - RapierNative.getBodyCollisionFilterNative(getSpaceHandle(), bodyHandle, collisionFilterScratch); - collisionGroup = collisionFilterScratch[0]; - collisionMask = collisionFilterScratch[1]; - } - } - - private void setStoredRotation(float x, float y, float z, float w) { - rotation.set(x, y, z, w); - if (rotation.lengthSquared() == 0f) { - rotation.identity(); - return; - } - rotation.normalize(); - } -} diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierJoint.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierJoint.java deleted file mode 100644 index 85d27d9b..00000000 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierJoint.java +++ /dev/null @@ -1,217 +0,0 @@ -package dev.hytalemodding.impulse.rapier; - -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -final class RapierJoint implements PhysicsJoint { - - private final RapierSpace space; - private final PhysicsJointType type; - private final RapierBody bodyA; - private final RapierBody bodyB; - private final long jointHandle; - private final float anchorAX; - private final float anchorAY; - private final float anchorAZ; - private final float anchorBX; - private final float anchorBY; - private final float anchorBZ; - private final float axisX; - private final float axisY; - private final float axisZ; - private float lowerLimit; - private float upperLimit; - private boolean motorEnabled; - private float motorTargetVelocity; - private float motorMaxForce; - private final float springRestLength; - private final float springStiffness; - private final float springDamping; - private boolean valid = true; - - RapierJoint(@Nonnull RapierSpace space, - @Nonnull PhysicsJointType type, - @Nonnull RapierBody bodyA, - @Nonnull RapierBody bodyB, - long jointHandle, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float springRestLength, - float springStiffness, - float springDamping) { - this.space = space; - this.type = type; - this.bodyA = bodyA; - this.bodyB = bodyB; - this.jointHandle = jointHandle; - this.anchorAX = anchorAX; - this.anchorAY = anchorAY; - this.anchorAZ = anchorAZ; - this.anchorBX = anchorBX; - this.anchorBY = anchorBY; - this.anchorBZ = anchorBZ; - this.axisX = axisX; - this.axisY = axisY; - this.axisZ = axisZ; - this.springRestLength = springRestLength; - this.springStiffness = springStiffness; - this.springDamping = springDamping; - } - - @Nonnull - @Override - public PhysicsJointType getType() { - return type; - } - - @Nonnull - @Override - public PhysicsBody getBodyA() { - return bodyA; - } - - @Nonnull - @Override - public PhysicsBody getBodyB() { - return bodyB; - } - - @Override - public boolean isEnabled() { - if (!canUseNative()) { - return false; - } - return RapierNative.isJointEnabledNative(space.getNativeSpaceHandle(), jointHandle); - } - - @Override - public void setEnabled(boolean enabled) { - if (!canUseNative()) { - return; - } - RapierNative.setJointEnabledNative(space.getNativeSpaceHandle(), jointHandle, enabled); - } - - @Nonnull - @Override - public Vector3f getAnchorA() { - return new Vector3f(anchorAX, anchorAY, anchorAZ); - } - - @Nonnull - @Override - public Vector3f getAnchorB() { - return new Vector3f(anchorBX, anchorBY, anchorBZ); - } - - @Nullable - @Override - public Vector3f getAxis() { - return new Vector3f(axisX, axisY, axisZ); - } - - @Override - public float getLowerLimit() { - return lowerLimit; - } - - @Override - public float getUpperLimit() { - return upperLimit; - } - - @Override - public void setLimits(float lowerLimit, float upperLimit) { - this.lowerLimit = lowerLimit; - this.upperLimit = upperLimit; - if (!canUseNative()) { - return; - } - RapierNative.setJointLimitsNative(space.getNativeSpaceHandle(), jointHandle, - lowerLimit, upperLimit); - } - - @Override - public boolean isMotorEnabled() { - return motorEnabled; - } - - @Override - public void setMotorEnabled(boolean enabled) { - motorEnabled = enabled; - pushMotor(); - } - - @Override - public float getMotorTargetVelocity() { - return motorTargetVelocity; - } - - @Override - public float getMotorMaxForce() { - return motorMaxForce; - } - - @Override - public void setMotor(float targetVelocity, float maxForce) { - motorTargetVelocity = targetVelocity; - motorMaxForce = maxForce; - pushMotor(); - } - - @Override - public float getSpringRestLength() { - return springRestLength; - } - - @Override - public float getSpringStiffness() { - return springStiffness; - } - - @Override - public float getSpringDamping() { - return springDamping; - } - - long getJointHandle() { - return jointHandle; - } - - boolean belongsTo(@Nonnull RapierSpace owner) { - return space == owner; - } - - boolean isValidIn(@Nonnull RapierSpace owner) { - return belongsTo(owner) && valid && !space.isClosed(); - } - - void invalidate(@Nonnull RapierSpace owner) { - if (space == owner) { - valid = false; - } - } - - private void pushMotor() { - if (!canUseNative()) { - return; - } - RapierNative.setJointMotorNative(space.getNativeSpaceHandle(), jointHandle, - motorEnabled, motorTargetVelocity, motorMaxForce); - } - - private boolean canUseNative() { - return valid && !space.isClosed(); - } -} diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java deleted file mode 100644 index b3a39f23..00000000 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierSpace.java +++ /dev/null @@ -1,1220 +0,0 @@ -package dev.hytalemodding.impulse.rapier; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBackendEventKind; -import dev.hytalemodding.impulse.api.PhysicsBackendEventSink; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import dev.hytalemodding.impulse.api.PhysicsJoint; -import dev.hytalemodding.impulse.api.PhysicsJointType; -import dev.hytalemodding.impulse.api.PhysicsRayHit; -import dev.hytalemodding.impulse.api.PhysicsRuntimeStats; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuningCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsBackendEventsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityDescriptor; -import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; -import dev.hytalemodding.impulse.api.capability.PhysicsContinuousCollisionCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsExtensionSettingsCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; -import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuningCapability; -import dev.hytalemodding.impulse.api.capability.PhysicsVoxelTerrainCapability; -import java.lang.ref.Cleaner; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -public final class RapierSpace implements PhysicsSpace { - - private static final Cleaner CLEANER = Cleaner.create(); - private static final PhysicsCapabilityId RAPIER_SOLVER_EXTENSION_ID = - new PhysicsCapabilityId("impulse:rapier_solver"); - private static final String INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; - private static final String MIN_ISLAND_SIZE = "minIslandSize"; - private static final PhysicsCapabilityDescriptor RAPIER_SOLVER_EXTENSION_DESCRIPTOR = - new PhysicsCapabilityDescriptor(RAPIER_SOLVER_EXTENSION_ID, - "Rapier solver", - "Configures Rapier-specific solver batching settings"); - private static final List CAPABILITY_DESCRIPTORS = List.of( - PhysicsSolverTuningCapability.DESCRIPTOR, - PhysicsActivationTuningCapability.DESCRIPTOR, - PhysicsContinuousCollisionCapability.DESCRIPTOR, - PhysicsBackendEventsCapability.DESCRIPTOR, - PhysicsVoxelTerrainCapability.DESCRIPTOR, - PhysicsExtensionSettingsCapability.DESCRIPTOR, - RAPIER_SOLVER_EXTENSION_DESCRIPTOR); - private static final int DEFAULT_SOLVER_ITERATIONS = 4; - private static final int DEFAULT_INTERNAL_PGS_ITERATIONS = 1; - private static final int DEFAULT_STABILIZATION_ITERATIONS = 1; - private static final int DEFAULT_MIN_ISLAND_SIZE = 128; - private static final int RAY_HIT_FLOATS = 10; - private static final int CONTACT_FLOATS = 15; - private static final int CONTACT_EVENT_FLOATS = 16; - private static final int BODY_SNAPSHOT_FLOATS = 16; - private static final int RUNTIME_STATS_VALUES = 10; - private static final int STEP_PHASE_STATS_VALUES = 6; - - private final SpaceId id; - private final RapierBackend backend; - private long nativeSpaceHandle; - private final Cleaner.Cleanable cleanable; - private final PhysicsSolverTuningCapability solverTuningCapability = - new RapierSolverTuningCapability(); - private final PhysicsActivationTuningCapability activationTuningCapability = - new RapierActivationTuningCapability(); - private final PhysicsContinuousCollisionCapability continuousCollisionCapability = - new PhysicsContinuousCollisionCapability() { - }; - private final PhysicsBackendEventsCapability backendEventsCapability = - () -> Set.of(PhysicsBackendEventKind.CONTACT_STARTED, - PhysicsBackendEventKind.CONTACT_ENDED, - PhysicsBackendEventKind.CONTACT_FORCE); - private final PhysicsVoxelTerrainCapability voxelTerrainCapability = - new RapierVoxelTerrainCapability(); - private final PhysicsExtensionSettingsCapability extensionSettingsCapability = - new RapierExtensionSettingsCapability(); - private final List bodies = new ArrayList<>(); - private final Map bodiesByHandle = new HashMap<>(); - private final List joints = new ArrayList<>(); - private long[] snapshotBodyHandles = new long[0]; - private float[] snapshotBodyData = new float[0]; - private RapierBody[] selectedSnapshotBodies = new RapierBody[0]; - private int solverIterations = DEFAULT_SOLVER_ITERATIONS; - private int internalPgsIterations = DEFAULT_INTERNAL_PGS_ITERATIONS; - private int stabilizationIterations = DEFAULT_STABILIZATION_ITERATIONS; - private int minIslandSize = DEFAULT_MIN_ISLAND_SIZE; - private boolean closed; - - RapierSpace(@Nonnull SpaceId id, @Nonnull RapierBackend backend, long nativeSpaceHandle) { - if (nativeSpaceHandle == 0L) { - throw new IllegalStateException("Rapier returned a null native space handle"); - } - this.id = id; - this.backend = backend; - this.nativeSpaceHandle = nativeSpaceHandle; - this.cleanable = CLEANER.register(this, new NativeSpaceCleanup(nativeSpaceHandle)); - } - - @Nonnull - @Override - public SpaceId id() { - return id; - } - - @Nonnull - @Override - public BackendId backendId() { - return backend.getId(); - } - - @Override - public void step(float dt) { - if (dt <= 0f) { - return; - } - ensureOpen(); - if (!RapierNative.stepNative(nativeSpaceHandle, dt)) { - throw new IllegalStateException("Rapier native step failed"); - } - } - - @Override - public void step(float dt, @Nonnull PhysicsBackendEventSink events) { - if (dt <= 0f) { - return; - } - ensureOpen(); - float[] raw = RapierNative.stepContactEventsNative(nativeSpaceHandle, dt); - if (raw == null) { - throw new IllegalStateException("Rapier native contact event step failed"); - } - for (int i = 0; i + CONTACT_EVENT_FLOATS <= raw.length; i += CONTACT_EVENT_FLOATS) { - PhysicsContactPhase phase = contactPhase(raw[i]); - if (phase == null) { - continue; - } - RapierBody bodyA = bodiesByHandle.get(rawBitFloatPairToLong(raw[i + 1], raw[i + 2])); - RapierBody bodyB = bodiesByHandle.get(rawBitFloatPairToLong(raw[i + 3], raw[i + 4])); - if (bodyA == null || bodyB == null) { - continue; - } - events.contact(phase, - bodyA, - bodyB, - new Vector3f(raw[i + 5], raw[i + 6], raw[i + 7]), - new Vector3f(raw[i + 8], raw[i + 9], raw[i + 10]), - new Vector3f(raw[i + 11], raw[i + 12], raw[i + 13]), - raw[i + 14], - raw[i + 15]); - } - } - - private static PhysicsContactPhase contactPhase(float rawPhase) { - return switch ((int) rawPhase) { - case 0 -> PhysicsContactPhase.STARTED; - case 2 -> PhysicsContactPhase.ENDED; - case 4 -> PhysicsContactPhase.FORCE; - default -> null; - }; - } - - private void setSolverTuning(int solverIterations, - int internalPgsIterations, - int stabilizationIterations, - int minIslandSize) { - ensureOpen(); - this.solverIterations = solverIterations; - this.internalPgsIterations = internalPgsIterations; - this.stabilizationIterations = stabilizationIterations; - this.minIslandSize = minIslandSize; - RapierNative.setSolverTuningNative(nativeSpaceHandle, - solverIterations, - internalPgsIterations, - stabilizationIterations, - minIslandSize); - } - - private void setDynamicSleepTuning(float linearThreshold, - float angularThreshold, - float timeUntilSleep) { - ensureOpen(); - RapierNative.setDynamicSleepTuningNative(nativeSpaceHandle, - linearThreshold, - angularThreshold, - timeUntilSleep); - } - - private void applyCurrentSolverTuning() { - setSolverTuning(solverIterations, - internalPgsIterations, - stabilizationIterations, - minIslandSize); - } - - @Override - public void setGravity(float x, float y, float z) { - ensureOpen(); - RapierNative.setGravityNative(nativeSpaceHandle, x, y, z); - } - - @Nonnull - @Override - public Vector3f getGravity() { - ensureOpen(); - float[] out = new float[3]; - RapierNative.getGravityNative(nativeSpaceHandle, out); - return new Vector3f(out[0], out[1], out[2]); - } - - @Override - public void addBody(@Nonnull PhysicsBody body) { - ensureOpen(); - if (!(body instanceof RapierBody rapierBody)) { - throw new IllegalArgumentException("Body does not belong to rapier backend"); - } - if (rapierBody.isAttached()) { - throw new IllegalStateException("Rapier body is already attached to a space"); - } - - long nativeBodyHandle = addNativeBody(rapierBody); - if (nativeBodyHandle == 0L) { - throw new IllegalStateException("Rapier returned a null native body handle"); - } - - rapierBody.attach(this, nativeBodyHandle); - bodies.add(rapierBody); - bodiesByHandle.put(nativeBodyHandle, rapierBody); - } - - @Override - public void removeBody(@Nonnull PhysicsBody body) { - if (closed) { - return; - } - if (!(body instanceof RapierBody rapierBody)) { - return; - } - if (!rapierBody.isAttachedTo(this)) { - if (rapierBody.isAttached()) { - throw new IllegalArgumentException("Rapier body belongs to another space"); - } - return; - } - - removeAttachedJoints(rapierBody); - long handle = rapierBody.getBodyHandle(); - RapierNative.removeBodyNative(nativeSpaceHandle, handle); - rapierBody.detach(this); - bodies.remove(rapierBody); - bodiesByHandle.remove(handle); - } - - @Nonnull - @Override - public List getBodies() { - return new ArrayList<>(bodies); - } - - @Override - public int bodyCount() { - return bodies.size(); - } - - @Override - public void forEachBody(@Nonnull Consumer consumer) { - for (RapierBody body : bodies) { - consumer.accept(body); - } - } - - @Override - public boolean containsBody(@Nonnull PhysicsBody body) { - return body instanceof RapierBody rapierBody && rapierBody.isAttachedTo(this); - } - - @Nonnull - @Override - public Optional getCapability(@Nonnull Class type) { - Objects.requireNonNull(type, "type"); - if (type == PhysicsSolverTuningCapability.class) { - return Optional.of(type.cast(solverTuningCapability)); - } - if (type == PhysicsActivationTuningCapability.class) { - return Optional.of(type.cast(activationTuningCapability)); - } - if (type == PhysicsContinuousCollisionCapability.class) { - return Optional.of(type.cast(continuousCollisionCapability)); - } - if (type == PhysicsBackendEventsCapability.class) { - return Optional.of(type.cast(backendEventsCapability)); - } - if (type == PhysicsVoxelTerrainCapability.class) { - return Optional.of(type.cast(voxelTerrainCapability)); - } - if (type == PhysicsExtensionSettingsCapability.class) { - return Optional.of(type.cast(extensionSettingsCapability)); - } - return Optional.empty(); - } - - @Nonnull - @Override - public List getCapabilityDescriptors() { - return CAPABILITY_DESCRIPTORS; - } - - @Override - public void snapshotBodies(@Nonnull Consumer consumer) { - snapshotBodies(_ -> null, consumer); - } - - @Override - public void snapshotBodies( - @Nonnull Function previousSnapshots, - @Nonnull Consumer consumer) { - snapshotBodies(previousSnapshots, (_, snapshot) -> consumer.accept(snapshot)); - } - - @Override - public void snapshotBodies( - @Nonnull Function previousSnapshots, - @Nonnull BiConsumer consumer) { - ensureOpen(); - int count = bodies.size(); - if (count == 0) { - return; - } - - ensureSnapshotCapacity(count); - for (int i = 0; i < count; i++) { - snapshotBodyHandles[i] = bodies.get(i).getBodyHandle(); - } - - int written = RapierNative.snapshotBodiesNative(nativeSpaceHandle, - snapshotBodyHandles, - count, - snapshotBodyData); - if (written < 0) { - throw new IllegalStateException("Rapier native snapshot failed"); - } - int limit = Math.clamp(written, 0, count); - for (int i = 0; i < limit; i++) { - RapierBody body = bodies.get(i); - consumer.accept(body, body.snapshotFromNative(snapshotBodyData, - i * BODY_SNAPSHOT_FLOATS, - previousSnapshots.apply(body))); - } - for (int i = limit; i < count; i++) { - RapierBody body = bodies.get(i); - consumer.accept(body, PhysicsBodySnapshot.from(body, previousSnapshots.apply(body))); - } - } - - @Override - public void snapshotBodies(@Nonnull Iterable selectedBodies, - @Nonnull Function previousSnapshots, - @Nonnull Consumer consumer) { - snapshotBodies(selectedBodies, previousSnapshots, - (_, snapshot) -> consumer.accept(snapshot)); - } - - @Override - public void snapshotBodies(@Nonnull Iterable selectedBodies, - @Nonnull Function previousSnapshots, - @Nonnull BiConsumer consumer) { - ensureOpen(); - int count = collectSelectedSnapshotBodies(selectedBodies, previousSnapshots, consumer); - if (count == 0) { - return; - } - - try { - int written = RapierNative.snapshotBodiesNative(nativeSpaceHandle, - snapshotBodyHandles, - count, - snapshotBodyData); - if (written < 0) { - throw new IllegalStateException("Rapier native snapshot failed"); - } - int limit = Math.clamp(written, 0, count); - for (int i = 0; i < limit; i++) { - RapierBody body = selectedSnapshotBodies[i]; - consumer.accept(body, body.snapshotFromNative(snapshotBodyData, - i * BODY_SNAPSHOT_FLOATS, - previousSnapshots.apply(body))); - } - for (int i = limit; i < count; i++) { - RapierBody body = selectedSnapshotBodies[i]; - consumer.accept(body, - PhysicsBodySnapshot.from(body, previousSnapshots.apply(body))); - } - } finally { - for (int i = 0; i < count; i++) { - selectedSnapshotBodies[i] = null; - } - } - } - - private int collectSelectedSnapshotBodies( - @Nonnull Iterable selectedBodies, - @Nonnull Function previousSnapshots, - @Nonnull BiConsumer consumer) { - int count = 0; - for (PhysicsBody body : selectedBodies) { - if (!(body instanceof RapierBody rapierBody) || !rapierBody.isAttachedTo(this)) { - consumer.accept(body, - PhysicsBodySnapshot.from(body, previousSnapshots.apply(body))); - continue; - } - - ensureSnapshotCapacity(count + 1); - selectedSnapshotBodies[count] = rapierBody; - snapshotBodyHandles[count] = rapierBody.getBodyHandle(); - count++; - } - return count; - } - - private void ensureSnapshotCapacity(int count) { - if (snapshotBodyHandles.length < count) { - int capacity = Math.max(count, snapshotBodyHandles.length * 2); - snapshotBodyHandles = Arrays.copyOf(snapshotBodyHandles, capacity); - } - int floats = count * BODY_SNAPSHOT_FLOATS; - if (snapshotBodyData.length < floats) { - int capacity = Math.max(floats, snapshotBodyData.length * 2); - snapshotBodyData = new float[capacity]; - } - if (selectedSnapshotBodies.length < count) { - int capacity = Math.max(count, selectedSnapshotBodies.length * 2); - selectedSnapshotBodies = Arrays.copyOf(selectedSnapshotBodies, capacity); - } - } - - @Nonnull - @Override - public PhysicsRuntimeStats getRuntimeStats() { - ensureOpen(); - int[] values = RapierNative.getRuntimeStatsNative(nativeSpaceHandle); - if (values == null || values.length < RUNTIME_STATS_VALUES) { - return PhysicsRuntimeStats.unavailable(); - } - return PhysicsRuntimeStats.available(values[0], - values[1], - values[2], - values[3], - values[4], - values[5], - values[6], - values[7], - values[8], - values[9]); - } - - @Override - public void resetStepPhaseStats() { - ensureOpen(); - RapierNative.resetStepPhaseStatsNative(nativeSpaceHandle); - } - - @Nonnull - @Override - public PhysicsStepPhaseStats getStepPhaseStats() { - ensureOpen(); - long[] values = RapierNative.getStepPhaseStatsNative(nativeSpaceHandle); - if (values == null || values.length < STEP_PHASE_STATS_VALUES) { - return PhysicsStepPhaseStats.unavailable(); - } - return PhysicsStepPhaseStats.available(values[0], - values[1], - values[2], - values[3], - values[4], - values[5]); - } - - @Nonnull - @Override - public PhysicsBody createStaticPlane(float groundY) { - return RapierBody.staticPlane(groundY); - } - - @Nonnull - @Override - public PhysicsBody createBox(float halfX, float halfY, float halfZ, float mass) { - return RapierBody.box(halfX, halfY, halfZ, mass); - } - - @Nonnull - @Override - public PhysicsBody createBox(@Nonnull Vector3f halfExtents, float mass) { - return createBox(halfExtents.x, halfExtents.y, halfExtents.z, mass); - } - - @Nonnull - private PhysicsBody createVoxelTerrain(float voxelSizeX, - float voxelSizeY, - float voxelSizeZ, - @Nonnull int[] voxelCoordinates) { - return RapierBody.voxelTerrain(voxelSizeX, voxelSizeY, voxelSizeZ, voxelCoordinates); - } - - private void combineVoxelTerrains(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - int shiftX, - int shiftY, - int shiftZ) { - ensureOpen(); - RapierBody rapierBodyA = requireAttachedBody(bodyA); - RapierBody rapierBodyB = requireAttachedBody(bodyB); - if (rapierBodyA == rapierBodyB) { - throw new IllegalArgumentException("Cannot combine a voxel terrain body with itself"); - } - requireVoxelTerrain(rapierBodyA); - requireVoxelTerrain(rapierBodyB); - if (!RapierNative.combineVoxelTerrainNative(nativeSpaceHandle, - rapierBodyA.getBodyHandle(), - rapierBodyB.getBodyHandle(), - shiftX, - shiftY, - shiftZ)) { - throw new IllegalStateException("Rapier native voxel terrain combine failed"); - } - } - - private static void requireVoxelTerrain(@Nonnull RapierBody body) { - if (body.getShapeType() != ShapeType.VOXELS) { - throw new IllegalArgumentException("Body must be a voxel terrain"); - } - } - - @Nonnull - @Override - public PhysicsBody createSphere(float radius, float mass) { - return RapierBody.sphere(radius, mass); - } - - @Nonnull - @Override - public PhysicsBody createCapsule(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return RapierBody.capsule(radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCylinder(float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float mass) { - return RapierBody.cylinder(radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public PhysicsBody createCone(float radius, float halfHeight, @Nonnull PhysicsAxis axis, - float mass) { - return RapierBody.cone(radius, halfHeight, axis, mass); - } - - @Nonnull - @Override - public Optional raycastClosest(@Nonnull Vector3f from, @Nonnull Vector3f to) { - ensureOpen(); - List hits = raycastAll(from, to); - PhysicsRayHit closest = null; - for (PhysicsRayHit hit : hits) { - if (closest == null || hit.fraction() < closest.fraction()) { - closest = hit; - } - } - return Optional.ofNullable(closest); - } - - public long rawBitFloatPairToLong(float upper, float lower) { - long upperBits = Float.floatToRawIntBits(upper); - long lowerBits = Float.floatToRawIntBits(lower) & 0xFFFFFFFFL; - - return (upperBits << 32) | lowerBits; - } - - @Nonnull - @Override - public List raycastAll(@Nonnull Vector3f from, @Nonnull Vector3f to) { - ensureOpen(); - float[] raw = RapierNative.raycastAllNative(nativeSpaceHandle, - from.x, from.y, from.z, to.x, to.y, to.z); - List hits = new ArrayList<>(raw.length / RAY_HIT_FLOATS); - for (int i = 0; i + RAY_HIT_FLOATS <= raw.length; i += RAY_HIT_FLOATS) { - RapierBody body = bodiesByHandle.get(rawBitFloatPairToLong(raw[i], raw[i + 1])); - if (body == null) { - continue; - } - Vector3f point = new Vector3f(raw[i + 2], raw[i + 3], raw[i + 4]); - Vector3f normal = new Vector3f(raw[i + 5], raw[i + 6], raw[i + 7]); - hits.add(new PhysicsRayHit(body, point, normal, raw[i + 8], raw[i + 9])); - } - return hits; - } - - @Nonnull - @Override - public List getContacts() { - ensureOpen(); - return contactsFromRaw(RapierNative.getContactsNative(nativeSpaceHandle)); - } - - @Nonnull - @Override - public List getContacts(int maxContacts) { - ensureOpen(); - if (maxContacts <= 0) { - return List.of(); - } - return contactsFromRaw(RapierNative.getContactsLimitedNative(nativeSpaceHandle, - maxContacts)); - } - - @Nonnull - private List contactsFromRaw(@Nonnull float[] raw) { - List contacts = new ArrayList<>(raw.length / CONTACT_FLOATS); - for (int i = 0; i + CONTACT_FLOATS <= raw.length; i += CONTACT_FLOATS) { - RapierBody bodyA = bodiesByHandle.get(rawBitFloatPairToLong(raw[i], raw[i + 1])); - RapierBody bodyB = bodiesByHandle.get(rawBitFloatPairToLong(raw[i + 2], raw[i + 3])); - if (bodyA == null || bodyB == null) { - continue; - } - Vector3f pointA = new Vector3f(raw[i + 4], raw[i + 5], raw[i + 6]); - Vector3f pointB = new Vector3f(raw[i + 7], raw[i + 8], raw[i + 9]); - Vector3f normal = new Vector3f(raw[i + 10], raw[i + 11], raw[i + 12]); - contacts.add(new PhysicsContact(bodyA, bodyB, pointA, pointB, normal, - raw[i + 13], raw[i + 14])); - } - return contacts; - } - - @Nonnull - @Override - public PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - return createFixedJoint(bodyA, - bodyB, - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z); - } - - @Nonnull - @Override - public PhysicsJoint createFixedJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ) { - return createJoint(PhysicsJointType.FIXED, - bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - 0f, - 1f, - 0f, - 0f, - 0f, - 0f); - } - - @Nonnull - @Override - public PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB) { - return createPointJoint(bodyA, - bodyB, - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z); - } - - @Nonnull - @Override - public PhysicsJoint createPointJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ) { - return createJoint(PhysicsJointType.POINT, - bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - 0f, - 1f, - 0f, - 0f, - 0f, - 0f); - } - - @Nonnull - @Override - public PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - return createHingeJoint(bodyA, - bodyB, - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z); - } - - @Nonnull - @Override - public PhysicsJoint createHingeJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ) { - return createJoint(PhysicsJointType.HINGE, - bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - 0f, - 0f, - 0f); - } - - @Nonnull - @Override - public PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis) { - return createSliderJoint(bodyA, - bodyB, - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - axis.x, - axis.y, - axis.z); - } - - @Nonnull - @Override - public PhysicsJoint createSliderJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ) { - return createJoint(PhysicsJointType.SLIDER, - bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - axisX, - axisY, - axisZ, - 0f, - 0f, - 0f); - } - - @Nonnull - @Override - public PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - float restLength, - float stiffness, - float damping) { - return createSpringJoint(bodyA, - bodyB, - anchorA.x, - anchorA.y, - anchorA.z, - anchorB.x, - anchorB.y, - anchorB.z, - restLength, - stiffness, - damping); - } - - @Nonnull - @Override - public PhysicsJoint createSpringJoint(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float restLength, - float stiffness, - float damping) { - return createJoint(PhysicsJointType.SPRING, - bodyA, - bodyB, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - 0f, - 1f, - 0f, - restLength, - stiffness, - damping); - } - - @Override - public void removeJoint(@Nonnull PhysicsJoint joint) { - if (closed) { - return; - } - if (!(joint instanceof RapierJoint rapierJoint)) { - return; - } - if (!rapierJoint.belongsTo(this)) { - throw new IllegalArgumentException("Rapier joint belongs to another space"); - } - if (!rapierJoint.isValidIn(this)) { - joints.remove(rapierJoint); - return; - } - RapierNative.removeJointNative(nativeSpaceHandle, rapierJoint.getJointHandle()); - joints.remove(rapierJoint); - rapierJoint.invalidate(this); - } - - @Nonnull - @Override - public List getJoints() { - return new ArrayList<>(joints); - } - - @Override - public int jointCount() { - return joints.size(); - } - - @Override - public void forEachJoint(@Nonnull Consumer consumer) { - for (RapierJoint joint : joints) { - consumer.accept(joint); - } - } - - long getNativeSpaceHandle() { - ensureOpen(); - return nativeSpaceHandle; - } - - boolean isClosed() { - return closed; - } - - @Override - public void close() { - if (closed) { - return; - } - closed = true; - for (RapierJoint joint : new ArrayList<>(joints)) { - joint.invalidate(this); - } - joints.clear(); - for (RapierBody body : new ArrayList<>(bodies)) { - body.detach(this); - } - bodies.clear(); - bodiesByHandle.clear(); - cleanable.clean(); - nativeSpaceHandle = 0L; - } - - private void removeAttachedJoints(@Nonnull RapierBody body) { - for (RapierJoint joint : new ArrayList<>(joints)) { - if (joint.getBodyA() != body && joint.getBodyB() != body) { - continue; - } - - RapierNative.removeJointNative(nativeSpaceHandle, joint.getJointHandle()); - joints.remove(joint); - joint.invalidate(this); - } - } - - private long addNativeBody(@Nonnull RapierBody body) { - if (body.getShapeType() == ShapeType.VOXELS) { - Vector3f position = body.getStoredPosition(); - Vector3f voxelSize = body.getVoxelSize(); - return RapierNative.addVoxelTerrainNative(nativeSpaceHandle, - voxelSize.x, - voxelSize.y, - voxelSize.z, - body.getVoxelCoordinates(), - position.x, - position.y, - position.z, - body.getStoredFriction(), - body.getStoredRestitution(), - body.getStoredCollisionGroup(), - body.getStoredCollisionMask()); - } - - Vector3f halfExtents = body.getBoxHalfExtents(); - if (halfExtents == null) { - halfExtents = new Vector3f(); - } - Vector3f position = body.getStoredPosition(); - Quaternionf rotation = body.getStoredRotation(); - Vector3f linearVelocity = body.getStoredLinearVelocity(); - Vector3f angularVelocity = body.getStoredAngularVelocity(); - - return RapierNative.addBodyNative(nativeSpaceHandle, - body.getShapeType().ordinal(), - halfExtents.x, - halfExtents.y, - halfExtents.z, - body.getSphereRadius(), - body.getHalfHeight(), - body.getShapeAxis().index(), - body.getStoredBodyType().ordinal(), - body.getStoredMass(), - position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - linearVelocity.x, - linearVelocity.y, - linearVelocity.z, - angularVelocity.x, - angularVelocity.y, - angularVelocity.z, - body.getStoredFriction(), - body.getStoredRestitution(), - body.getStoredLinearDamping(), - body.getStoredAngularDamping(), - body.getStoredSensor(), - body.getStoredCollisionGroup(), - body.getStoredCollisionMask(), - body.getStoredContinuousCollisionEnabled()); - } - - private PhysicsJoint createJoint(@Nonnull PhysicsJointType type, - @Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - float anchorAX, - float anchorAY, - float anchorAZ, - float anchorBX, - float anchorBY, - float anchorBZ, - float axisX, - float axisY, - float axisZ, - float restLength, - float stiffness, - float damping) { - ensureOpen(); - RapierBody rapierA = requireAttachedBody(bodyA); - RapierBody rapierB = requireAttachedBody(bodyB); - float normalizedAxisX = axisX; - float normalizedAxisY = axisY; - float normalizedAxisZ = axisZ; - float axisLengthSquared = axisX * axisX + axisY * axisY + axisZ * axisZ; - if (axisLengthSquared == 0f) { - normalizedAxisX = 0f; - normalizedAxisY = 1f; - normalizedAxisZ = 0f; - } else { - float inverseLength = (float) (1.0 / Math.sqrt(axisLengthSquared)); - normalizedAxisX *= inverseLength; - normalizedAxisY *= inverseLength; - normalizedAxisZ *= inverseLength; - } - long handle = RapierNative.addJointNative(nativeSpaceHandle, - type.ordinal(), - rapierA.getBodyHandle(), - rapierB.getBodyHandle(), - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - normalizedAxisX, - normalizedAxisY, - normalizedAxisZ, - restLength, - stiffness, - damping); - if (handle == 0L) { - throw new IllegalStateException("Rapier returned a null native joint handle"); - } - RapierJoint joint = new RapierJoint(this, type, rapierA, rapierB, handle, - anchorAX, - anchorAY, - anchorAZ, - anchorBX, - anchorBY, - anchorBZ, - normalizedAxisX, - normalizedAxisY, - normalizedAxisZ, - restLength, - stiffness, - damping); - joints.add(joint); - return joint; - } - - private RapierBody requireAttachedBody(@Nonnull PhysicsBody body) { - if (!(body instanceof RapierBody rapierBody)) { - throw new IllegalArgumentException("Body does not belong to rapier backend"); - } - if (!rapierBody.isAttachedTo(this)) { - if (rapierBody.isAttached()) { - throw new IllegalArgumentException("Rapier body belongs to another space"); - } - throw new IllegalStateException("Rapier joint bodies must be added to a space first"); - } - return rapierBody; - } - - private void ensureOpen() { - if (closed || nativeSpaceHandle == 0L) { - throw new IllegalStateException("Rapier space is closed"); - } - } - - private final class RapierSolverTuningCapability implements PhysicsSolverTuningCapability { - - @Override - public void setSolverTuning(@Nonnull PhysicsSolverTuning tuning) { - Objects.requireNonNull(tuning, "tuning"); - solverIterations = tuning.solverIterations(); - stabilizationIterations = tuning.stabilizationIterations(); - applyCurrentSolverTuning(); - } - } - - private final class RapierActivationTuningCapability implements PhysicsActivationTuningCapability { - - @Override - public void setActivationTuning(@Nonnull PhysicsActivationTuning tuning) { - Objects.requireNonNull(tuning, "tuning"); - setDynamicSleepTuning(tuning.linearSleepThreshold(), - tuning.angularSleepThreshold(), - tuning.timeUntilSleep()); - } - } - - private final class RapierVoxelTerrainCapability implements PhysicsVoxelTerrainCapability { - - @Nonnull - @Override - public PhysicsBody createVoxelTerrain(float voxelSizeX, - float voxelSizeY, - float voxelSizeZ, - @Nonnull int[] voxelCoordinates) { - return RapierSpace.this.createVoxelTerrain(voxelSizeX, - voxelSizeY, - voxelSizeZ, - voxelCoordinates); - } - - @Override - public void combineVoxelTerrains(@Nonnull PhysicsBody bodyA, - @Nonnull PhysicsBody bodyB, - int shiftX, - int shiftY, - int shiftZ) { - RapierSpace.this.combineVoxelTerrains(bodyA, bodyB, shiftX, shiftY, shiftZ); - } - } - - private final class RapierExtensionSettingsCapability implements PhysicsExtensionSettingsCapability { - - @Override - public void applyExtensionSettings(@Nonnull PhysicsCapabilityId capabilityId, - @Nonnull Map values) { - Objects.requireNonNull(capabilityId, "capabilityId"); - Objects.requireNonNull(values, "values"); - if (!RAPIER_SOLVER_EXTENSION_ID.equals(capabilityId)) { - return; - } - internalPgsIterations = parsePositive(values, - INTERNAL_PGS_ITERATIONS, - internalPgsIterations); - minIslandSize = parsePositive(values, - MIN_ISLAND_SIZE, - minIslandSize); - applyCurrentSolverTuning(); - } - - private int parsePositive(@Nonnull Map values, - @Nonnull String key, - int fallback) { - String value = values.get(key); - if (value == null) { - return fallback; - } - int parsed; - try { - parsed = Integer.parseInt(value); - } catch (NumberFormatException exception) { - throw new IllegalArgumentException("Rapier extension setting " + key - + " must be an integer", exception); - } - if (parsed < 1) { - throw new IllegalArgumentException("Rapier extension setting " + key - + " must be positive"); - } - return parsed; - } - } - - private static final class NativeSpaceCleanup implements Runnable { - - private final long nativeSpaceHandle; - - private NativeSpaceCleanup(long nativeSpaceHandle) { - this.nativeSpaceHandle = nativeSpaceHandle; - } - - @Override - public void run() { - RapierNative.destroySpaceNative(nativeSpaceHandle); - } - } -} diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java index b1ee10bc..b4cb206f 100644 --- a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -12,7 +13,6 @@ import dev.hytalemodding.impulse.api.runtime.BackendJointType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.api.runtime.legacy.LegacyPhysicsBackendRuntime; import java.lang.reflect.Field; import java.util.Map; import javax.annotation.Nonnull; @@ -21,13 +21,13 @@ class RapierBackendRuntimeProviderTest { @Test - void providerCreatesIdOnlyRuntimeInsteadOfLegacyAdapter() { + void providerCreatesIdOnlyRuntime() { RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); provider.init(); PhysicsBackendRuntime runtime = provider.createRuntime(); - assertFalse(runtime instanceof LegacyPhysicsBackendRuntime); + assertInstanceOf(RapierBackendRuntime.class, runtime); } @Test diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java index 805c2900..5ac00476 100644 --- a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java @@ -3,53 +3,116 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsContact; -import dev.hytalemodding.impulse.api.PhysicsSpace; -import java.util.List; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendContactSink; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import org.junit.jupiter.api.Test; class RapierBoundedContactsTest { @Test void getContactsWithLimitReturnsAtMostRequestedContacts() { - RapierBackend backend = new RapierBackend(); - backend.init(); - PhysicsSpace space = backend.createSpace(); + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int spaceId = runtime.createSpace(new SpaceId(1)); try { - space.setGravity(0.0f, -9.81f, 0.0f); - addStaticFloor(space, 8, 8); - addRestingBoxes(space, 8, 8); + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + addStaticFloor(runtime, spaceId, 8, 8); + addRestingBoxes(runtime, spaceId, 8, 8); for (int i = 0; i < 180; i++) { - space.step(1.0f / 30.0f); + runtime.step(spaceId, 1.0f / 30.0f); } - List contacts = space.getContacts(5); + int contacts = runtime.contacts(spaceId, 5, new CountingContactSink()); - assertFalse(contacts.isEmpty()); - assertTrue(contacts.size() <= 5); + assertFalse(contacts == 0); + assertTrue(contacts <= 5); } finally { - space.close(); + runtime.destroySpace(spaceId); } } - private static void addStaticFloor(PhysicsSpace space, int width, int depth) { + private static void addStaticFloor(PhysicsBackendRuntime runtime, + int spaceId, + int width, + int depth) { for (int x = 0; x < width; x++) { for (int z = 0; z < depth; z++) { - PhysicsBody body = space.createBox(0.5f, 0.5f, 0.5f, 0.0f); - body.setPosition(x, 0.0f, z); - space.addBody(body); + createBox(runtime, + spaceId, + x, + 0.0f, + z, + 0.5f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC)); } } } - private static void addRestingBoxes(PhysicsSpace space, int width, int depth) { + private static void addRestingBoxes(PhysicsBackendRuntime runtime, + int spaceId, + int width, + int depth) { for (int x = 0; x < width; x++) { for (int z = 0; z < depth; z++) { - PhysicsBody body = space.createBox(0.45f, 0.45f, 0.45f, 1.0f); - body.setPosition(x, 1.05f, z); - space.addBody(body); + createBox(runtime, + spaceId, + x, + 1.05f, + z, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC)); } } } + + private static void createBox(PhysicsBackendRuntime runtime, + int spaceId, + float x, + float y, + float z, + float mass, + int bodyTypeCode) { + runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + mass, + bodyTypeCode, + x, + y, + z, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static final class CountingContactSink implements BackendContactSink { + + @Override + public void accept(long bodyAId, + long bodyBId, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + } + } } diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java index ee580de3..95e8179e 100644 --- a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java @@ -2,34 +2,139 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import dev.hytalemodding.impulse.api.PhysicsBody; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import org.joml.Vector3f; +import dev.hytalemodding.impulse.api.runtime.BackendJointType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import java.lang.reflect.Field; +import java.util.Map; +import javax.annotation.Nonnull; import org.junit.jupiter.api.Test; class RapierNativeBodyRemovalTest { @Test void nativeBodyRemovalDropsAttachedJointHandles() { - RapierBackend backend = new RapierBackend(); - backend.init(); - RapierSpace space = (RapierSpace) backend.createSpace(new SpaceId(4)); + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int spaceId = runtime.createSpace(new SpaceId(4)); try { - PhysicsBody first = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - PhysicsBody second = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - space.addBody(first); - space.addBody(second); - space.createFixedJoint(first, second, new Vector3f(), new Vector3f()); + long firstBodyId = createBox(runtime, spaceId); + long secondBodyId = createBox(runtime, spaceId); + runtime.createJoint(spaceId, + BackendRuntimeCodes.jointTypeCode(BackendJointType.FIXED), + firstBodyId, + secondBodyId, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f); + long nativeSpaceHandle = nativeSpaceHandle(runtime, spaceId); + long nativeBodyHandle = nativeBodyHandle(runtime, spaceId, firstBodyId); - assertEquals(1, RapierNative.jointHandleCountNative(space.getNativeSpaceHandle())); + assertEquals(1, RapierNative.jointHandleCountNative(nativeSpaceHandle)); - RapierNative.removeBodyNative(space.getNativeSpaceHandle(), - ((RapierBody) first).getBodyHandle()); + RapierNative.removeBodyNative(nativeSpaceHandle, nativeBodyHandle); - assertEquals(0, RapierNative.jointHandleCountNative(space.getNativeSpaceHandle())); - assertEquals(0, space.getRuntimeStats().jointCount()); + assertEquals(0, RapierNative.jointHandleCountNative(nativeSpaceHandle)); } finally { - space.close(); + runtime.destroySpace(spaceId); + } + } + + private static long createBox(@Nonnull PhysicsBackendRuntime runtime, int spaceId) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static long nativeSpaceHandle(@Nonnull PhysicsBackendRuntime runtime, int spaceId) { + Object state = spaceState(runtime, spaceId); + try { + Field handle = state.getClass().getDeclaredField("nativeSpaceHandle"); + handle.setAccessible(true); + return handle.getLong(state); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier native space handle", exception); + } + } + + private static long nativeBodyHandle(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + long bodyId) { + Object body = bodyState(runtime, spaceId, bodyId); + try { + Field handle = body.getClass().getDeclaredField("nativeBodyHandle"); + handle.setAccessible(true); + return handle.getLong(body); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier native body handle", exception); + } + } + + private static Object bodyState(@Nonnull PhysicsBackendRuntime runtime, + int spaceId, + long bodyId) { + Object state = spaceState(runtime, spaceId); + try { + Field bodies = state.getClass().getDeclaredField("bodiesById"); + bodies.setAccessible(true); + @SuppressWarnings("unchecked") + Map bodiesById = (Map) bodies.get(state); + Object body = bodiesById.get(bodyId); + if (body == null) { + throw new AssertionError("No cached body " + bodyId); + } + return body; + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier body cache", exception); + } + } + + private static Object spaceState(@Nonnull PhysicsBackendRuntime runtime, int spaceId) { + try { + Field spaces = runtime.getClass().getDeclaredField("spaces"); + spaces.setAccessible(true); + @SuppressWarnings("unchecked") + Map spacesById = (Map) spaces.get(runtime); + Object state = spacesById.get(spaceId); + if (state == null) { + throw new AssertionError("No cached space " + spaceId); + } + return state; + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Unable to inspect Rapier runtime cache", exception); } } } diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java index 0a0dcb52..ed00eb1c 100644 --- a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java @@ -3,10 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import dev.hytalemodding.impulse.api.PhysicsBody; -import dev.hytalemodding.impulse.api.PhysicsSpace; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.capability.PhysicsVoxelTerrainCapability; +import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -14,60 +16,54 @@ class RapierVoxelTerrainTest { @Test void dynamicBoxRestsOnNativeVoxelFloor() { - RapierBackend backend = new RapierBackend(); - backend.init(); - PhysicsSpace space = backend.createSpace(new SpaceId(10)); + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(10)); try { - space.setGravity(0.0f, -9.81f, 0.0f); - addVoxelFloor(space, 0.0f); - PhysicsBody box = addDynamicBox(space, 8.0f, 3.0f, 8.0f); + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + addVoxelFloor(runtime, spaceId, 0.0f); + long boxId = addDynamicBox(runtime, spaceId, 8.0f, 3.0f, 8.0f); - StepResult result = stepAndTrackMinimumY(space, box, 240); + StepResult result = stepAndTrackMinimumY(runtime, spaceId, boxId, 240); assertFalse(result.fellThrough()); } finally { - space.close(); + runtime.destroySpace(spaceId); } } @Test void dynamicBoxRestsNearNativeVoxelSectionEdge() { - RapierBackend backend = new RapierBackend(); - backend.init(); - PhysicsSpace space = backend.createSpace(new SpaceId(11)); + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(11)); try { - space.setGravity(0.0f, -9.81f, 0.0f); - addVoxelFloor(space, 0.0f); - PhysicsBody box = addDynamicBox(space, 15.75f, 3.0f, 8.0f); + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + addVoxelFloor(runtime, spaceId, 0.0f); + long boxId = addDynamicBox(runtime, spaceId, 15.75f, 3.0f, 8.0f); - StepResult result = stepAndTrackMinimumY(space, box, 240); + StepResult result = stepAndTrackMinimumY(runtime, spaceId, boxId, 240); assertFalse(result.fellThrough()); } finally { - space.close(); + runtime.destroySpace(spaceId); } } @Test void dynamicBoxRestsAcrossStitchedNativeVoxelSections() { - RapierBackend backend = new RapierBackend(); - backend.init(); - PhysicsSpace space = backend.createSpace(new SpaceId(12)); + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(12)); try { - space.setGravity(0.0f, -9.81f, 0.0f); - PhysicsVoxelTerrainCapability voxelTerrain = space - .getCapability(PhysicsVoxelTerrainCapability.class) - .orElseThrow(); - PhysicsBody first = addVoxelFloor(space, 0.0f); - PhysicsBody second = addVoxelFloor(space, 16.0f); - voxelTerrain.combineVoxelTerrains(first, second, 16, 0, 0); - PhysicsBody box = addDynamicBox(space, 16.0f, 3.0f, 8.0f); + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + long first = addVoxelFloor(runtime, spaceId, 0.0f); + long second = addVoxelFloor(runtime, spaceId, 16.0f); + runtime.combineVoxelTerrains(spaceId, first, second, 16, 0, 0); + long boxId = addDynamicBox(runtime, spaceId, 16.0f, 3.0f, 8.0f); - StepResult result = stepAndTrackMinimumY(space, box, 240); + StepResult result = stepAndTrackMinimumY(runtime, spaceId, boxId, 240); assertFalse(result.fellThrough()); } finally { - space.close(); + runtime.destroySpace(spaceId); } } @@ -80,61 +76,52 @@ void combineVoxelTerrainNativeReportsInvalidSpaceHandle() { @Test void combineVoxelTerrainsRejectsNonVoxelBodies() { - RapierBackend backend = new RapierBackend(); - backend.init(); - PhysicsSpace space = backend.createSpace(new SpaceId(2)); + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(2)); try { - PhysicsVoxelTerrainCapability voxelTerrain = space - .getCapability(PhysicsVoxelTerrainCapability.class) - .orElseThrow(); - PhysicsBody voxelBody = voxelTerrain.createVoxelTerrain(1.0f, - 1.0f, - 1.0f, - new int[] {0, 0, 0}); - PhysicsBody box = space.createBox(0.5f, 0.5f, 0.5f, 1.0f); - space.addBody(voxelBody); - space.addBody(box); + long voxelBody = addVoxelFloor(runtime, spaceId, 0.0f); + long box = addDynamicBox(runtime, spaceId, 0.0f, 1.0f, 0.0f); assertThrows(IllegalArgumentException.class, - () -> voxelTerrain.combineVoxelTerrains(voxelBody, box, 1, 0, 0)); + () -> runtime.combineVoxelTerrains(spaceId, voxelBody, box, 1, 0, 0)); } finally { - space.close(); + runtime.destroySpace(spaceId); } } @Test void combineVoxelTerrainsRejectsSameBody() { - RapierBackend backend = new RapierBackend(); - backend.init(); - PhysicsSpace space = backend.createSpace(new SpaceId(3)); + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(3)); try { - PhysicsVoxelTerrainCapability voxelTerrain = space - .getCapability(PhysicsVoxelTerrainCapability.class) - .orElseThrow(); - PhysicsBody voxelBody = voxelTerrain.createVoxelTerrain(1.0f, - 1.0f, - 1.0f, - new int[] {0, 0, 0}); - space.addBody(voxelBody); + long voxelBody = addVoxelFloor(runtime, spaceId, 0.0f); assertThrows(IllegalArgumentException.class, - () -> voxelTerrain.combineVoxelTerrains(voxelBody, voxelBody, 1, 0, 0)); + () -> runtime.combineVoxelTerrains(spaceId, voxelBody, voxelBody, 1, 0, 0)); } finally { - space.close(); + runtime.destroySpace(spaceId); } } - private static PhysicsBody addVoxelFloor(PhysicsSpace space, float originX) { - PhysicsVoxelTerrainCapability voxelTerrain = space - .getCapability(PhysicsVoxelTerrainCapability.class) - .orElseThrow(); - PhysicsBody floor = voxelTerrain.createVoxelTerrain(1.0f, + private static PhysicsBackendRuntime runtime() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + return provider.createRuntime(); + } + + private static long addVoxelFloor(PhysicsBackendRuntime runtime, int spaceId, float originX) { + return runtime.createVoxelTerrain(spaceId, 1.0f, 1.0f, - voxelFloorCoordinates(16, 16)); - floor.setPosition(originX, 0.0f, 0.0f); - space.addBody(floor); - return floor; + 1.0f, + voxelFloorCoordinates(16, 16), + originX, + 0.0f, + 0.0f, + 0.5f, + 0.0f, + 1, + 1); } private static int[] voxelFloorCoordinates(int width, int depth) { @@ -150,22 +137,39 @@ private static int[] voxelFloorCoordinates(int width, int depth) { return coordinates; } - private static PhysicsBody addDynamicBox(PhysicsSpace space, float x, float y, float z) { - PhysicsBody box = space.createBox(0.45f, 0.45f, 0.45f, 1.0f); - box.setPosition(x, y, z); - space.addBody(box); - return box; + private static long addDynamicBox(PhysicsBackendRuntime runtime, int spaceId, float x, float y, float z) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.45f, + 0.45f, + 0.45f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + x, + y, + z, + 0.0f, + 0.0f, + 0.0f, + 1.0f); } - private static StepResult stepAndTrackMinimumY(PhysicsSpace space, - PhysicsBody body, + private static StepResult stepAndTrackMinimumY(PhysicsBackendRuntime runtime, + int spaceId, + long bodyId, int steps) { + CapturedSnapshot snapshot = new CapturedSnapshot(); float minY = Float.POSITIVE_INFINITY; for (int i = 0; i < steps; i++) { - space.step(1.0f / 30.0f); - minY = Math.min(minY, body.getPosition().y); + runtime.step(spaceId, 1.0f / 30.0f); + runtime.bodySnapshot(spaceId, bodyId, snapshot); + minY = Math.min(minY, snapshot.position.y); } - return new StepResult(body.getPosition(), minY); + return new StepResult(new Vector3f(snapshot.position), minY); } private record StepResult(Vector3f finalPosition, float minY) { @@ -174,4 +178,47 @@ private boolean fellThrough() { return finalPosition.y < 1.0f || minY < 0.75f; } } + + private static final class CapturedSnapshot implements BackendBodySnapshotSink { + + private final Vector3f position = new Vector3f(); + + @Override + public void accept(long bodyId, + int shapeTypeCode, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping, + boolean sensor, + float mass, + float friction, + float restitution, + float linearDamping, + float angularDamping, + int collisionGroup, + int collisionMask, + boolean continuousCollisionEnabled, + float centerOfMassOffsetY, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode) { + position.set(positionX, positionY, positionZ); + } + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 57e1feed..fb4d4045 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,7 +28,6 @@ rootProject.name = "impulse" include("impulse-backend-api") include("impulse-native-loader") -include("impulse-bullet") include("impulse-rapier") include("impulse-core") include("impulse-examples") From 3848ebaf3ecd03cccf34fca584d0f4392a942f08 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sat, 20 Jun 2026 19:35:26 +0200 Subject: [PATCH 493/534] feat(jolt): add Panama-backed native runtime Signed-off-by: Blovien --- .github/workflows/backend-artifacts.yml | 101 +- build.gradle.kts | 5 +- impulse-jolt/README.md | 106 ++ impulse-jolt/build.gradle.kts | 287 +++++ impulse-jolt/src/main/cpp/CMakeLists.txt | 76 ++ impulse-jolt/src/main/cpp/impulse_jolt.cpp | 1127 +++++++++++++++++ .../impulse/jolt/JoltBackend.java | 38 + .../impulse/jolt/JoltBackendRuntime.java | 754 +++++++++++ .../jolt/JoltBackendRuntimeProvider.java | 31 + .../impulse/jolt/JoltBodySnapshot.java | 153 +++ .../impulse/jolt/JoltNative.java | 31 + .../impulse/jolt/JoltNativeLibrary.java | 192 +++ .../impulse/jolt/PanamaJoltNativeLibrary.java | 828 ++++++++++++ ....api.runtime.PhysicsBackendRuntimeProvider | 1 + .../jolt/JoltBackendRuntimeContractTest.java | 280 ++++ .../jolt/JoltBackendRuntimeProviderTest.java | 18 + .../impulse/jolt/JoltBodyLifecycleTest.java | 83 ++ .../impulse/jolt/JoltBodySnapshotTest.java | 85 ++ .../jolt/JoltMaterialAndFilterTest.java | 44 + .../jolt/JoltNativeAbiIntegrationTest.java | 74 ++ .../JoltNativePhysicsIntegrationTest.java | 67 + .../jolt/JoltNativeQueryIntegrationTest.java | 222 ++++ .../impulse/jolt/JoltQueryMappingTest.java | 155 +++ .../impulse/jolt/JoltSpaceLifecycleTest.java | 220 ++++ .../impulse/jolt/JoltTestNativeLibrary.java | 651 ++++++++++ licenses/JOLT_PHYSICS_LICENSE | 13 + settings.gradle.kts | 1 + 27 files changed, 5634 insertions(+), 9 deletions(-) create mode 100644 impulse-jolt/README.md create mode 100644 impulse-jolt/build.gradle.kts create mode 100644 impulse-jolt/src/main/cpp/CMakeLists.txt create mode 100644 impulse-jolt/src/main/cpp/impulse_jolt.cpp create mode 100644 impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java create mode 100644 impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java create mode 100644 impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java create mode 100644 impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java create mode 100644 impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java create mode 100644 impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java create mode 100644 impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java create mode 100644 impulse-jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java create mode 100644 licenses/JOLT_PHYSICS_LICENSE diff --git a/.github/workflows/backend-artifacts.yml b/.github/workflows/backend-artifacts.yml index 075d897e..161884ff 100644 --- a/.github/workflows/backend-artifacts.yml +++ b/.github/workflows/backend-artifacts.yml @@ -7,6 +7,66 @@ permissions: contents: read jobs: + jolt-native: + name: Jolt native ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} + + strategy: + fail-fast: false + matrix: + include: + - platform: linux-x86_64 + runner: ubuntu-22.04 + resource-path: linux/x86_64 + library: libimpulse_jolt.so + gradle-command: ./gradlew :impulse-jolt:stageJoltNativeResource + - platform: linux-arm64 + runner: ubuntu-24.04-arm + resource-path: linux/arm64 + library: libimpulse_jolt.so + gradle-command: ./gradlew :impulse-jolt:stageJoltNativeResource + - platform: osx-arm64 + runner: macos-15 + resource-path: osx/arm64 + library: libimpulse_jolt.dylib + gradle-command: ./gradlew :impulse-jolt:stageJoltNativeResource + - platform: windows-x86_64 + runner: windows-2025 + resource-path: windows/x86_64 + library: impulse_jolt.dll + gradle-command: ./gradlew.bat :impulse-jolt:stageJoltNativeResource + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Java 25 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '25' + + - name: Set up Gradle cache + uses: gradle/actions/setup-gradle@v4 + + - name: Build Jolt native resource + shell: bash + run: ${{ matrix.gradle-command }} + + - name: Stage Jolt native artifact + shell: bash + run: | + artifact_dir="jolt-native-artifact/native/${{ matrix.resource-path }}" + mkdir -p "$artifact_dir" + cp "impulse-jolt/build/generated/jolt-native/native/${{ matrix.resource-path }}/${{ matrix.library }}" "$artifact_dir/" + + - name: Upload Jolt native resource + uses: actions/upload-artifact@v4 + with: + name: jolt-native-${{ matrix.platform }} + path: jolt-native-artifact + if-no-files-found: error + rapier-native: name: Rapier native ${{ matrix.platform }} runs-on: ${{ matrix.runner }} @@ -81,7 +141,9 @@ jobs: backend-jars: name: Backend provider jars runs-on: ubuntu-24.04 - needs: rapier-native + needs: + - jolt-native + - rapier-native steps: - name: Check out repository @@ -102,6 +164,12 @@ jobs: pattern: rapier-native-* path: impulse-rapier/build/ci/rapier-native-downloads + - name: Download Jolt native resources + uses: actions/download-artifact@v4 + with: + pattern: jolt-native-* + path: impulse-jolt/build/ci/jolt-native-downloads + - name: Normalize Rapier native resource tree shell: bash run: | @@ -115,9 +183,24 @@ jobs: done find impulse-rapier/build/ci/rapier-native/native -type f | sort + - name: Normalize Jolt native resource tree + shell: bash + run: | + mkdir -p impulse-jolt/build/ci/jolt-native/native + for dir in impulse-jolt/build/ci/jolt-native-downloads/jolt-native-*; do + if [ -d "$dir/native" ]; then + cp -R "$dir/native/." impulse-jolt/build/ci/jolt-native/native/ + else + cp -R "$dir/." impulse-jolt/build/ci/jolt-native/native/ + fi + done + find impulse-jolt/build/ci/jolt-native/native -type f | sort + - name: Build backend provider jars run: > ./gradlew packageBackendPlatformJars + -PbuildJoltNative=false + -Pimpulse.joltNativeResourceRoot=${{ github.workspace }}/impulse-jolt/build/ci/jolt-native -PbuildRapierNative=false -Pimpulse.rapierNativeResourceRoot=${{ github.workspace }}/impulse-rapier/build/ci/rapier-native @@ -129,7 +212,9 @@ jobs: These backend provider jars may include third-party native binaries so Impulse can load the backend at runtime. - They are convenience packages for Impulse plugins and are not the official upstream distribution channel for those native libraries. Download standalone Bullet/Libbulletjme or Rapier binaries from their upstream projects instead. + They are convenience packages for Impulse plugins and are not the official upstream distribution channel for those native libraries. Download standalone Rapier binaries from its upstream project instead. + + Jolt backend jars include binaries built from upstream Jolt Physics source for Impulse's native ABI. See LICENSE and licenses/ in this artifact for packaged license notices. EOF @@ -141,13 +226,13 @@ jobs: path: | BACKEND_ARTIFACT_NOTICE.md LICENSE - licenses/LIBBULLETJME_LICENSE + licenses/JOLT_PHYSICS_LICENSE licenses/RAPIER_RUST_BACKEND_LICENSES - impulse-bullet/build/libs/impulse-bullet-*-linux-x86_64.jar - impulse-bullet/build/libs/impulse-bullet-*-linux-arm64.jar - impulse-bullet/build/libs/impulse-bullet-*-osx-arm64.jar - impulse-bullet/build/libs/impulse-bullet-*-windows-x86_64.jar - impulse-bullet/build/libs/impulse-bullet-*-universal.jar + impulse-jolt/build/libs/impulse-jolt-*-linux-x86_64.jar + impulse-jolt/build/libs/impulse-jolt-*-linux-arm64.jar + impulse-jolt/build/libs/impulse-jolt-*-osx-arm64.jar + impulse-jolt/build/libs/impulse-jolt-*-windows-x86_64.jar + impulse-jolt/build/libs/impulse-jolt-*-universal.jar impulse-rapier/build/libs/impulse-rapier-*-linux-x86_64.jar impulse-rapier/build/libs/impulse-rapier-*-linux-arm64.jar impulse-rapier/build/libs/impulse-rapier-*-osx-arm64.jar diff --git a/build.gradle.kts b/build.gradle.kts index 5660c702..d700c58e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,7 +67,7 @@ subprojects { } } -val backendProjectPaths = setOf(":impulse-rapier") +val backendProjectPaths = setOf(":impulse-jolt", ":impulse-rapier") val stagedBackendJarDirectory = layout.projectDirectory.dir("run/mods/impulse-backends") val stagedEarlyPluginJarDirectory = layout.projectDirectory.dir("run/earlyplugins") val physicsStoreEarlyPluginEnabled = providers.gradleProperty("impulse.physicsStoreEarlyPlugin") @@ -137,6 +137,7 @@ tasks.register("packageBackendPlatformJars") { group = "build" description = "Packages all per-platform and universal backend provider jars" dependsOn( + ":impulse-jolt:packageJoltBackendPlatformJars", ":impulse-rapier:packageRapierBackendPlatformJars" ) } @@ -147,6 +148,7 @@ tasks.register("headlessTest") { dependsOn( ":impulse-backend-api:test", ":impulse-native-loader:test", + ":impulse-jolt:test", ":impulse-rapier:test", ":impulse-core:test", ":impulse-examples:test", @@ -166,6 +168,7 @@ gradle.projectsEvaluated { val runTask = this as JavaExec runTask.standardInput = System.`in` + runTask.jvmArgs("--enable-native-access=ALL-UNNAMED") // hytale-gradle can omit project resources from run task classpaths. val toolRuntimeClasspaths = hytaleToolProjectPaths.map { path -> diff --git a/impulse-jolt/README.md b/impulse-jolt/README.md new file mode 100644 index 00000000..68739d59 --- /dev/null +++ b/impulse-jolt/README.md @@ -0,0 +1,106 @@ +# Impulse Jolt Backend + +`impulse-jolt` is the Jolt backend provider module for Impulse. + +The backend id is `impulse:jolt`. The module is discovered through the same +`PhysicsBackendRuntimeProvider` service mechanism used by the other backend provider jars. + +## Native Strategy + +The integration strategy is to build and package a C++ `impulse_jolt` native resource through +Gradle using CMake and the standard Impulse native resource layout: + +```text +native/// +``` + +Resource names: + +- Linux: `native/linux/x86_64/libimpulse_jolt.so` +- Linux ARM64: `native/linux/arm64/libimpulse_jolt.so` +- macOS ARM64: `native/osx/arm64/libimpulse_jolt.dylib` +- Windows x86_64: `native/windows/x86_64/impulse_jolt.dll` + +Gradle properties: + +- `buildJoltNative` +- `joltCxx` +- `joltCmake` +- `joltCmakeGenerator` +- `joltPhysicsGitTag` +- `impulse.joltNativeResourceRoot` + +The default local native build uses CMake FetchContent to build upstream Jolt Physics from the +pinned tag `v5.5.0`. `joltPhysicsGitTag` can override that tag, and +`impulse.joltNativeResourceRoot` can still supply prebuilt native resources instead of building +locally. + +The Java boundary uses Java 25 Project Panama/FFM downcalls rather than JNI. The packaged native +library is expected to export these C ABI symbols: + +- `impulse_jolt_create_space` +- `impulse_jolt_destroy_space` +- `impulse_jolt_step` +- `impulse_jolt_set_gravity` +- `impulse_jolt_get_gravity` +- `impulse_jolt_create_body` +- `impulse_jolt_remove_body` +- `impulse_jolt_contains_body` +- `impulse_jolt_body_snapshot` +- `impulse_jolt_set_body_transform` +- `impulse_jolt_set_body_position` +- `impulse_jolt_set_body_velocity` +- `impulse_jolt_set_body_type` +- `impulse_jolt_set_body_damping` +- `impulse_jolt_set_body_friction` +- `impulse_jolt_set_body_restitution` +- `impulse_jolt_set_body_collision_filter` +- `impulse_jolt_set_body_sensor` +- `impulse_jolt_set_body_continuous_collision` +- `impulse_jolt_is_body_continuous_collision_enabled` +- `impulse_jolt_activate_body` +- `impulse_jolt_sleep_body` +- `impulse_jolt_apply_body_impulse` +- `impulse_jolt_apply_body_force` +- `impulse_jolt_raycast_closest` +- `impulse_jolt_raycast_all` +- `impulse_jolt_contacts` +- `impulse_jolt_contact_count` +- `impulse_jolt_body_count` +- `impulse_jolt_joint_count` + +Space lifecycle, gravity, stepping, body lifecycle/mutation/snapshots, raycasts, contact queries, +body count, joint count, and runtime stats are wired through that ABI. Java-assigned body ids are +stable within the runtime and map to opaque native body handles that wrap Jolt `BodyID` values. +Query results map native body handles back to those Java-assigned ids before calling Impulse sinks. +The current native implementation uses Jolt `PhysicsSystem`/`BodyInterface` for real rigid body +simulation, including broadphase, narrow phase, contact solving, gravity, forces, impulses, +activation, sensor state, motion quality, friction, restitution, raycasts, and dynamic bodies +resting on static collision. Contact queries are backed by a native Jolt `ContactListener` active +contact registry. + +Joint, contact-event, voxel terrain, and advanced capability operations still fail or return +explicit unsupported results until their native paths are implemented and tested. Jolt is staged as +a backend provider jar for explicit runtime selection, but it is not production-complete until +those paths and server runtime validation pass. + +`impulse_jolt_body_snapshot` writes two output buffers: + +- float buffer, 24 entries: position xyz, rotation xyzw, linear velocity xyz, angular velocity xyz, + mass, friction, restitution, linear damping, angular damping, center-of-mass Y offset, box half + extents xyz, radius, half height +- int buffer, 9 entries: shape type code, body type code, sleeping flag, sensor flag, collision + group, collision mask, continuous-collision flag, has-box-half-extents flag, axis code + +Raycast buffers: + +- body-handle buffer: one native body handle per hit +- float buffer, 8 entries per hit: point xyz, normal xyz, fraction, distance + +Contact buffers: + +- body-handle buffer, 2 entries per contact: body A handle, body B handle +- float buffer, 11 entries per contact: point A xyz, point B xyz, normal B xyz, distance, impulse + +The contact impulse field is currently reported as `0.0` because Jolt's contact listener does not +provide the solved impulse on the query path used here. diff --git a/impulse-jolt/build.gradle.kts b/impulse-jolt/build.gradle.kts new file mode 100644 index 00000000..25e792c1 --- /dev/null +++ b/impulse-jolt/build.gradle.kts @@ -0,0 +1,287 @@ +import org.gradle.api.GradleException +import org.gradle.api.file.FileCollection +import org.gradle.api.file.DuplicatesStrategy +import org.gradle.jvm.tasks.Jar + +plugins { + id("java-library") +} + +data class JoltBackendPlatform( + val taskSuffix: String, + val archiveClassifier: String, + val resourceOs: String, + val resourceArch: String, + val libraryName: String +) { + val nativeResourcePath: String = "native/$resourceOs/$resourceArch" + val nativeResourceEntry: String = "$nativeResourcePath/$libraryName" +} + +val joltBackendPlatforms = listOf( + JoltBackendPlatform("LinuxX64", "linux-x86_64", "linux", "x86_64", "libimpulse_jolt.so"), + JoltBackendPlatform("LinuxArm64", "linux-arm64", "linux", "arm64", "libimpulse_jolt.so"), + JoltBackendPlatform("OsxArm64", "osx-arm64", "osx", "arm64", "libimpulse_jolt.dylib"), + JoltBackendPlatform("WindowsX64", "windows-x86_64", "windows", "x86_64", "impulse_jolt.dll") +) +val impulseLicenseFile = rootProject.layout.projectDirectory.file("LICENSE") +val joltPhysicsLicenseFile = rootProject.layout.projectDirectory.file("licenses/JOLT_PHYSICS_LICENSE") +val nativeResourceOs = detectNativeResourceOs() +val nativeResourceArch = detectNativeResourceArch(nativeResourceOs) +val nativeResourcePath = "native/$nativeResourceOs/$nativeResourceArch" +val nativeLibraryName = nativeLibraryNameFor(nativeResourceOs) +val nativeSourceDirectory = layout.projectDirectory.dir("src/main/cpp") +val nativeCmakeFile = nativeSourceDirectory.file("CMakeLists.txt") +val nativeSourceFile = nativeSourceDirectory.file("impulse_jolt.cpp") +val cmakeBuildDirectory = layout.buildDirectory.dir("cmake/jolt") +val nativeOutputDirectory = layout.buildDirectory.dir("native/jolt") +val nativeOutputFile = nativeOutputDirectory.map { directory -> directory.file(nativeLibraryName) } +val generatedJoltNativeResourceRoot = layout.buildDirectory.dir("generated/jolt-native") +val providedJoltNativeResourceRoot = providers.gradleProperty("impulse.joltNativeResourceRoot") +val joltNativeResourceRoot = providedJoltNativeResourceRoot + .map { path -> file(path) } + .orElse(generatedJoltNativeResourceRoot.map { directory -> directory.asFile }) +val cmakeExecutable = providers.gradleProperty("joltCmake").orElse("cmake") +val cxxCompiler = providers.gradleProperty("joltCxx") +val joltPhysicsGitTag = providers.gradleProperty("joltPhysicsGitTag").orElse("v5.5.0") +val ninjaAvailable = commandAvailable("ninja") +val cmakeGenerator = providers.gradleProperty("joltCmakeGenerator") + .orElse(if (ninjaAvailable) "Ninja" else "") +val cmakeAvailable = commandAvailable(cmakeExecutable.get()) +val gitAvailable = commandAvailable("git") +val buildNative = providers.gradleProperty("buildJoltNative") + .map { it.toBoolean() } + .orElse(cmakeAvailable && gitAvailable) + +fun commandAvailable(command: String): Boolean { + return try { + ProcessBuilder(command, "--version") + .redirectOutput(ProcessBuilder.Redirect.DISCARD) + .redirectError(ProcessBuilder.Redirect.DISCARD) + .start() + .waitFor() == 0 + } catch (_: Exception) { + false + } +} + +fun detectNativeResourceOs(): String { + val osName = System.getProperty("os.name").lowercase() + return when { + osName.contains("linux") -> "linux" + osName.contains("mac") || osName.contains("darwin") -> "osx" + osName.contains("windows") -> "windows" + else -> throw GradleException("Unsupported Jolt native packaging OS: " + + System.getProperty("os.name")) + } +} + +fun detectNativeResourceArch(resourceOs: String): String { + val osArch = System.getProperty("os.arch").lowercase() + val resourceArch = when (osArch) { + "amd64", "x86_64" -> "x86_64" + "aarch64", "arm64" -> "arm64" + else -> throw GradleException("Unsupported Jolt native packaging architecture: " + + System.getProperty("os.arch")) + } + if (resourceOs == "windows" && resourceArch != "x86_64") { + throw GradleException("Unsupported Jolt native packaging platform: " + + System.getProperty("os.name") + " " + System.getProperty("os.arch")) + } + return resourceArch +} + +fun nativeLibraryNameFor(resourceOs: String): String { + return when (resourceOs) { + "windows" -> "impulse_jolt.dll" + "osx" -> "libimpulse_jolt.dylib" + else -> "libimpulse_jolt.so" + } +} + +fun runtimeClasspathWithoutBundledApi(): FileCollection { + return configurations.runtimeClasspath.get() + .filter { file -> !file.name.startsWith("impulse-backend-api-") } +} + +fun Jar.expandRuntimeClasspath(runtimeClasspath: FileCollection) { + dependsOn(runtimeClasspath.buildDependencies) + from({ + runtimeClasspath.map { file -> if (file.isDirectory) file else zipTree(file) } + }) +} + +fun Jar.includeBackendRuntime() { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + expandRuntimeClasspath(runtimeClasspathWithoutBundledApi()) + exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") +} + +fun Jar.includeBackendLicenseNotices() { + from(impulseLicenseFile) { + into("META-INF/licenses/impulse") + rename { "LICENSE" } + } + from(joltPhysicsLicenseFile) { + into("META-INF/licenses/jolt-physics") + rename { "LICENSE" } + } +} + +fun Jar.includeJoltNativeResource(platform: JoltBackendPlatform) { + val archiveClassifier = platform.archiveClassifier + val nativeResourcePath = platform.nativeResourcePath + val nativeResourceEntry = platform.nativeResourceEntry + + from(joltNativeResourceRoot.map { root -> + root.resolve(nativeResourcePath) + }) { + into(nativeResourcePath) + } + doFirst { + val nativeResource = joltNativeResourceRoot.get().resolve(nativeResourceEntry) + if (!nativeResource.isFile) { + throw GradleException("Missing Jolt native resource for " + + archiveClassifier + ": " + nativeResource.absolutePath + + ". Build it on a matching runner or provide -Pimpulse.joltNativeResourceRoot.") + } + } +} + +val configureJoltNative by tasks.registering(Exec::class) { + onlyIf { buildNative.get() } + + inputs.file(nativeCmakeFile) + inputs.file(nativeSourceFile) + inputs.property("joltPhysicsGitTag", joltPhysicsGitTag) + inputs.property("joltCmake", cmakeExecutable) + inputs.property("joltCmakeGenerator", cmakeGenerator) + inputs.property("joltCxx", cxxCompiler.orNull ?: "") + outputs.file(cmakeBuildDirectory.map { directory -> directory.file("CMakeCache.txt") }) + + doFirst { + cmakeBuildDirectory.get().asFile.mkdirs() + nativeOutputDirectory.get().asFile.mkdirs() + val command = mutableListOf( + cmakeExecutable.get(), + "-S", + nativeSourceDirectory.asFile.absolutePath, + "-B", + cmakeBuildDirectory.get().asFile.absolutePath, + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${nativeOutputDirectory.get().asFile.absolutePath}", + "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${nativeOutputDirectory.get().asFile.absolutePath}", + "-DJOLT_PHYSICS_GIT_TAG=${joltPhysicsGitTag.get()}" + ) + val generator = cmakeGenerator.get().trim() + if (generator.isNotEmpty()) { + command.addAll(listOf("-G", generator)) + } + cxxCompiler.orNull?.trim() + ?.takeIf { it.isNotEmpty() } + ?.let { command.add("-DCMAKE_CXX_COMPILER=$it") } + commandLine(command) + } +} + +val compileJoltNative by tasks.registering(Exec::class) { + dependsOn(configureJoltNative) + onlyIf { buildNative.get() } + + inputs.file(nativeCmakeFile) + inputs.file(nativeSourceFile) + inputs.property("joltPhysicsGitTag", joltPhysicsGitTag) + inputs.property("joltCmake", cmakeExecutable) + inputs.property("joltCmakeGenerator", cmakeGenerator) + inputs.property("joltCxx", cxxCompiler.orNull ?: "") + outputs.file(nativeOutputFile) + + doFirst { + val command = mutableListOf( + cmakeExecutable.get(), + "--build", + cmakeBuildDirectory.get().asFile.absolutePath, + "--config", + "Release", + "--target", + "impulse_jolt" + ) + commandLine(command) + } +} + +val stageJoltNativeResource by tasks.registering(Copy::class) { + dependsOn(compileJoltNative) + onlyIf { buildNative.get() } + + from(nativeOutputFile) + into(generatedJoltNativeResourceRoot.map { it.dir(nativeResourcePath) }) +} + +sourceSets { + main { + resources.srcDir(joltNativeResourceRoot) + } +} + +tasks.jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + expandRuntimeClasspath(runtimeClasspathWithoutBundledApi()) + includeBackendLicenseNotices() + exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") +} + +tasks.processResources { + if (!providedJoltNativeResourceRoot.isPresent) { + dependsOn(stageJoltNativeResource) + } +} + +val platformJarTasks = joltBackendPlatforms.map { platform -> + tasks.register("packageJoltBackend${platform.taskSuffix}") { + group = "build" + description = "Packages the Jolt backend provider jar for ${platform.archiveClassifier}" + archiveClassifier.set(platform.archiveClassifier) + + if (!providedJoltNativeResourceRoot.isPresent + && platform.resourceOs == nativeResourceOs + && platform.resourceArch == nativeResourceArch) { + dependsOn(stageJoltNativeResource) + } + + from(sourceSets.main.get().output) { + exclude("native/**") + } + includeBackendRuntime() + includeBackendLicenseNotices() + includeJoltNativeResource(platform) + } +} + +tasks.register("packageJoltBackendUniversal") { + group = "build" + description = "Packages the Jolt backend provider jar with every configured native library" + archiveClassifier.set("universal") + + from(sourceSets.main.get().output) { + exclude("native/**") + } + includeBackendRuntime() + includeBackendLicenseNotices() + joltBackendPlatforms.forEach { platform -> + includeJoltNativeResource(platform) + } +} + +tasks.register("packageJoltBackendPlatformJars") { + group = "build" + description = "Packages all Jolt per-platform backend jars plus the universal jar" + dependsOn(platformJarTasks) + dependsOn(tasks.named("packageJoltBackendUniversal")) +} + +dependencies { + api(project(":impulse-backend-api")) + + implementation(project(":impulse-native-loader")) +} diff --git a/impulse-jolt/src/main/cpp/CMakeLists.txt b/impulse-jolt/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..403005e0 --- /dev/null +++ b/impulse-jolt/src/main/cpp/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) + +project(impulse_jolt_native LANGUAGES CXX) + +include(FetchContent) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) + +set(JOLT_PHYSICS_GIT_TAG "v5.5.0" CACHE STRING "Jolt Physics git tag to build.") + +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(DOUBLE_PRECISION OFF CACHE BOOL "" FORCE) +set(GENERATE_DEBUG_SYMBOLS OFF CACHE BOOL "" FORCE) +set(OVERRIDE_CXX_FLAGS OFF CACHE BOOL "" FORCE) +set(CROSS_PLATFORM_DETERMINISTIC OFF CACHE BOOL "" FORCE) +set(INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE) +set(FLOATING_POINT_EXCEPTIONS_ENABLED OFF CACHE BOOL "" FORCE) +set(CPP_EXCEPTIONS_ENABLED OFF CACHE BOOL "" FORCE) +set(CPP_RTTI_ENABLED OFF CACHE BOOL "" FORCE) +set(OBJECT_LAYER_BITS 16 CACHE STRING "" FORCE) +set(ENABLE_ALL_WARNINGS OFF CACHE BOOL "" FORCE) +set(DEBUG_RENDERER_IN_DEBUG_AND_RELEASE OFF CACHE BOOL "" FORCE) +set(PROFILER_IN_DEBUG_AND_RELEASE OFF CACHE BOOL "" FORCE) +set(ENABLE_OBJECT_STREAM OFF CACHE BOOL "" FORCE) +set(ENABLE_INSTALL OFF CACHE BOOL "" FORCE) +set(TARGET_UNIT_TESTS OFF CACHE BOOL "" FORCE) +set(TARGET_HELLO_WORLD OFF CACHE BOOL "" FORCE) +set(TARGET_PERFORMANCE_TEST OFF CACHE BOOL "" FORCE) +set(TARGET_SAMPLES OFF CACHE BOOL "" FORCE) +set(TARGET_VIEWER OFF CACHE BOOL "" FORCE) + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$") + set(USE_SSE4_1 OFF CACHE BOOL "" FORCE) + set(USE_SSE4_2 OFF CACHE BOOL "" FORCE) + set(USE_AVX OFF CACHE BOOL "" FORCE) + set(USE_AVX2 OFF CACHE BOOL "" FORCE) + set(USE_AVX512 OFF CACHE BOOL "" FORCE) + set(USE_LZCNT OFF CACHE BOOL "" FORCE) + set(USE_TZCNT OFF CACHE BOOL "" FORCE) + set(USE_F16C OFF CACHE BOOL "" FORCE) + set(USE_FMADD OFF CACHE BOOL "" FORCE) +endif() + +FetchContent_Declare( + JoltPhysics + GIT_REPOSITORY "https://github.com/jrouwe/JoltPhysics.git" + GIT_TAG "${JOLT_PHYSICS_GIT_TAG}" + GIT_SHALLOW TRUE + SOURCE_SUBDIR "Build" +) +FetchContent_MakeAvailable(JoltPhysics) + +add_library(impulse_jolt SHARED impulse_jolt.cpp) +target_include_directories(impulse_jolt PRIVATE "${JoltPhysics_SOURCE_DIR}/..") +target_link_libraries(impulse_jolt PRIVATE Jolt) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(IMPULSE_JOLT_EXPORT_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/impulse_jolt_exports.map") + file(WRITE "${IMPULSE_JOLT_EXPORT_SCRIPT}" "IMPULSE_JOLT_1.0 {\n global:\n impulse_jolt_*;\n local:\n *;\n};\n") + target_link_options(impulse_jolt PRIVATE + "-Wl,--exclude-libs,ALL" + "-Wl,--version-script=${IMPULSE_JOLT_EXPORT_SCRIPT}" + ) +endif() + +set_target_properties(impulse_jolt PROPERTIES + OUTPUT_NAME "impulse_jolt" + PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}" + SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}" + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON +) diff --git a/impulse-jolt/src/main/cpp/impulse_jolt.cpp b/impulse-jolt/src/main/cpp/impulse_jolt.cpp new file mode 100644 index 00000000..1b536483 --- /dev/null +++ b/impulse-jolt/src/main/cpp/impulse_jolt.cpp @@ -0,0 +1,1127 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#define IMPULSE_JOLT_EXPORT __declspec(dllexport) +#else +#define IMPULSE_JOLT_EXPORT __attribute__((visibility("default"))) +#endif + +namespace { + +constexpr int SHAPE_BOX = 1; +constexpr int SHAPE_SPHERE = 2; +constexpr int SHAPE_CAPSULE = 3; +constexpr int SHAPE_CYLINDER = 4; +constexpr int SHAPE_CONE = 5; +constexpr int SHAPE_PLANE = 6; + +constexpr int BODY_STATIC = 1; +constexpr int BODY_DYNAMIC = 2; +constexpr int BODY_KINEMATIC = 3; + +constexpr int AXIS_X = 1; +constexpr int AXIS_Y = 2; +constexpr int AXIS_Z = 3; + +constexpr float MIN_SHAPE_SIZE = 0.001F; +constexpr std::uint32_t DEFAULT_COLLISION_GROUP = 1; +constexpr int RAY_HIT_FLOAT_COUNT = 8; +constexpr int CONTACT_BODY_HANDLE_COUNT = 2; +constexpr int CONTACT_FLOAT_COUNT = 11; + +struct Space; + +struct ContactRecord { + JPH::SubShapeIDPair key; + std::uint64_t body_a_handle = 0; + std::uint64_t body_b_handle = 0; + float point_ax = 0.0F; + float point_ay = 0.0F; + float point_az = 0.0F; + float point_bx = 0.0F; + float point_by = 0.0F; + float point_bz = 0.0F; + float normal_bx = 0.0F; + float normal_by = 0.0F; + float normal_bz = 0.0F; + float distance = 0.0F; + float impulse = 0.0F; +}; + +class ImpulseContactListener final : public JPH::ContactListener { +public: + explicit ImpulseContactListener(Space* owner) + : owner(owner) { + } + + void OnContactAdded(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) override; + + void OnContactPersisted(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) override; + + void OnContactRemoved(const JPH::SubShapeIDPair& sub_shape_pair) override; + +private: + Space* owner; +}; + +struct BodyState { + JPH::BodyID body_id; + int shape_type = 0; + int body_type = 0; + bool sensor = false; + float mass = 0.0F; + float friction = 0.0F; + float restitution = 0.0F; + float linear_damping = 0.0F; + float angular_damping = 0.0F; + int collision_group = 0; + int collision_mask = 0; + bool continuous_collision = false; + float center_of_mass_offset_y = 0.0F; + bool has_box_half_extents = false; + float half_extent_x = 0.0F; + float half_extent_y = 0.0F; + float half_extent_z = 0.0F; + float radius = 0.0F; + float half_height = 0.0F; + int axis = AXIS_Y; +}; + +struct Space { + JPH::BroadPhaseLayerInterfaceMask broad_phase_layer_interface; + JPH::ObjectVsBroadPhaseLayerFilterMask object_vs_broadphase_layer_filter; + JPH::ObjectLayerPairFilterMask object_layer_filter; + JPH::PhysicsSystem physics_system; + ImpulseContactListener contact_listener; + JPH::TempAllocatorImpl temp_allocator; + JPH::JobSystemThreadPool job_system; + std::unordered_map bodies; + std::unordered_map body_handles_by_jolt_id; + std::mutex contact_mutex; + std::vector contacts; + + Space() + : broad_phase_layer_interface(1), + object_vs_broadphase_layer_filter(broad_phase_layer_interface), + contact_listener(this), + temp_allocator(10 * 1024 * 1024), + job_system(JPH::cMaxPhysicsJobs, + JPH::cMaxPhysicsBarriers, + std::max(1U, std::thread::hardware_concurrency())) { + broad_phase_layer_interface.ConfigureLayer( + JPH::BroadPhaseLayer(0), + JPH::ObjectLayerPairFilterMask::cMask, + 0); + physics_system.Init(65536, + 0, + 65536, + 10240, + broad_phase_layer_interface, + object_vs_broadphase_layer_filter, + object_layer_filter); + physics_system.SetContactListener(&contact_listener); + physics_system.SetGravity(JPH::Vec3(0.0F, -9.81F, 0.0F)); + } + + ~Space() { + JPH::BodyInterface& body_interface = physics_system.GetBodyInterface(); + for (auto& [_, body] : bodies) { + if (!body.body_id.IsInvalid()) { + if (body_interface.IsAdded(body.body_id)) { + body_interface.RemoveBody(body.body_id); + } + body_interface.DestroyBody(body.body_id); + } + } + } + + void replace_contact_records(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold); + + void erase_contact_records(const JPH::SubShapeIDPair& key); + + void erase_contact_records_for_body_handle(std::uint64_t body_handle); +}; + +std::once_flag jolt_init_once; +std::mutex registry_mutex; +std::uint64_t next_space_handle = 1; +std::uint64_t next_body_handle = 1001; +std::unordered_map> spaces; + +void ensure_jolt_initialized() { + std::call_once(jolt_init_once, [] { + JPH::RegisterDefaultAllocator(); + if (JPH::Factory::sInstance == nullptr) { + JPH::Factory::sInstance = new JPH::Factory(); + } + JPH::RegisterTypes(); + }); +} + +float positive(float value) { + return std::max(value, MIN_SHAPE_SIZE); +} + +Space* find_space(std::uint64_t handle) { + auto iterator = spaces.find(handle); + return iterator == spaces.end() ? nullptr : iterator->second.get(); +} + +BodyState* find_body(Space& space, std::uint64_t handle) { + auto iterator = space.bodies.find(handle); + return iterator == space.bodies.end() ? nullptr : &iterator->second; +} + +std::uint64_t native_handle_for_body_id(const Space& space, const JPH::BodyID& body_id) { + auto iterator = space.body_handles_by_jolt_id.find(body_id.GetIndexAndSequenceNumber()); + return iterator == space.body_handles_by_jolt_id.end() ? 0 : iterator->second; +} + +JPH::EMotionType motion_type(int body_type) { + switch (body_type) { + case BODY_STATIC: + return JPH::EMotionType::Static; + case BODY_KINEMATIC: + return JPH::EMotionType::Kinematic; + default: + return JPH::EMotionType::Dynamic; + } +} + +JPH::ObjectLayer object_layer(int collision_group, int collision_mask) { + constexpr std::uint32_t mask_bits = JPH::ObjectLayerPairFilterMask::cMask; + std::uint32_t group = static_cast(collision_group) & mask_bits; + std::uint32_t mask = static_cast(collision_mask) & mask_bits; + if (group == 0) { + group = DEFAULT_COLLISION_GROUP; + } + if (mask == 0) { + mask = mask_bits; + } + return JPH::ObjectLayerPairFilterMask::sGetObjectLayer(group, mask); +} + +float center_of_mass_offset_y(int shape_type, + float half_extent_y, + float radius, + float half_height, + int axis) { + switch (shape_type) { + case SHAPE_BOX: + return half_extent_y; + case SHAPE_SPHERE: + return radius; + case SHAPE_CAPSULE: + return axis == AXIS_Y ? radius + half_height : radius; + case SHAPE_CYLINDER: + return axis == AXIS_Y ? half_height : radius; + case SHAPE_CONE: + return axis == AXIS_Y ? half_height : radius; + default: + return 0.0F; + } +} + +JPH::Quat axis_rotation(int axis) { + switch (axis) { + case AXIS_X: + return JPH::Quat::sRotation(JPH::Vec3::sAxisZ(), -0.5F * JPH::JPH_PI); + case AXIS_Z: + return JPH::Quat::sRotation(JPH::Vec3::sAxisX(), 0.5F * JPH::JPH_PI); + default: + return JPH::Quat::sIdentity(); + } +} + +JPH::ShapeRefC rotated_for_axis(JPH::ShapeRefC shape, int axis) { + if (axis == AXIS_Y || shape == nullptr) { + return shape; + } + return new JPH::RotatedTranslatedShape(JPH::Vec3::sZero(), axis_rotation(axis), shape); +} + +JPH::ShapeRefC create_shape(int shape_type, + float half_extent_x, + float half_extent_y, + float half_extent_z, + float radius, + float half_height, + int axis, + float ground_y) { + switch (shape_type) { + case SHAPE_BOX: + return new JPH::BoxShape(JPH::Vec3(positive(half_extent_x), + positive(half_extent_y), + positive(half_extent_z))); + case SHAPE_SPHERE: + return new JPH::SphereShape(positive(radius)); + case SHAPE_CAPSULE: + return rotated_for_axis( + new JPH::CapsuleShape(positive(half_height), positive(radius)), + axis); + case SHAPE_CYLINDER: + return rotated_for_axis( + new JPH::CylinderShape(positive(half_height), positive(radius)), + axis); + case SHAPE_CONE: { + JPH::TaperedCylinderShapeSettings settings(positive(half_height), + 0.0F, + positive(radius)); + JPH::Shape::ShapeResult result = settings.Create(); + if (result.HasError()) { + return nullptr; + } + return rotated_for_axis(result.Get(), axis); + } + case SHAPE_PLANE: + return new JPH::PlaneShape( + JPH::Plane::sFromPointAndNormal(JPH::Vec3(0.0F, ground_y, 0.0F), + JPH::Vec3::sAxisY())); + default: + return nullptr; + } +} + +void write_snapshot(Space& space, const BodyState& body, float* floats, int* ints) { + JPH::BodyInterface& body_interface = space.physics_system.GetBodyInterface(); + JPH::RVec3 position = JPH::RVec3::sZero(); + JPH::Quat rotation = JPH::Quat::sIdentity(); + JPH::Vec3 linear_velocity = JPH::Vec3::sZero(); + JPH::Vec3 angular_velocity = JPH::Vec3::sZero(); + float linear_damping = body.linear_damping; + float angular_damping = body.angular_damping; + + body_interface.GetPositionAndRotation(body.body_id, position, rotation); + body_interface.GetLinearAndAngularVelocity(body.body_id, linear_velocity, angular_velocity); + JPH::BodyLockRead lock(space.physics_system.GetBodyLockInterface(), body.body_id); + if (lock.Succeeded()) { + const JPH::MotionProperties* motion_properties = + lock.GetBody().GetMotionPropertiesUnchecked(); + if (motion_properties != nullptr) { + linear_damping = motion_properties->GetLinearDamping(); + angular_damping = motion_properties->GetAngularDamping(); + } + } + + floats[0] = static_cast(position.GetX()); + floats[1] = static_cast(position.GetY()); + floats[2] = static_cast(position.GetZ()); + floats[3] = rotation.GetX(); + floats[4] = rotation.GetY(); + floats[5] = rotation.GetZ(); + floats[6] = rotation.GetW(); + floats[7] = linear_velocity.GetX(); + floats[8] = linear_velocity.GetY(); + floats[9] = linear_velocity.GetZ(); + floats[10] = angular_velocity.GetX(); + floats[11] = angular_velocity.GetY(); + floats[12] = angular_velocity.GetZ(); + floats[13] = body.mass; + floats[14] = body_interface.GetFriction(body.body_id); + floats[15] = body_interface.GetRestitution(body.body_id); + floats[16] = linear_damping; + floats[17] = angular_damping; + floats[18] = body.center_of_mass_offset_y; + floats[19] = body.half_extent_x; + floats[20] = body.half_extent_y; + floats[21] = body.half_extent_z; + floats[22] = body.radius; + floats[23] = body.half_height; + + ints[0] = body.shape_type; + ints[1] = body.body_type; + ints[2] = body_interface.IsActive(body.body_id) ? 0 : 1; + ints[3] = body_interface.IsSensor(body.body_id) ? 1 : 0; + ints[4] = body.collision_group; + ints[5] = body.collision_mask; + ints[6] = body_interface.GetMotionQuality(body.body_id) + == JPH::EMotionQuality::LinearCast + ? 1 + : 0; + ints[7] = body.has_box_half_extents ? 1 : 0; + ints[8] = body.axis; +} + +void Space::replace_contact_records(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold) { + std::uint64_t body_a_handle = native_handle_for_body_id(*this, body1.GetID()); + std::uint64_t body_b_handle = native_handle_for_body_id(*this, body2.GetID()); + if (body_a_handle == 0 || body_b_handle == 0) { + return; + } + + JPH::SubShapeIDPair key(body1.GetID(), + manifold.mSubShapeID1, + body2.GetID(), + manifold.mSubShapeID2); + std::lock_guard lock(contact_mutex); + contacts.erase(std::remove_if(contacts.begin(), + contacts.end(), + [&key](const ContactRecord& record) { + return record.key == key; + }), + contacts.end()); + + for (JPH::uint index = 0; index < manifold.mRelativeContactPointsOn1.size(); index++) { + JPH::RVec3 point_a = manifold.GetWorldSpaceContactPointOn1(index); + JPH::RVec3 point_b = manifold.GetWorldSpaceContactPointOn2(index); + ContactRecord record; + record.key = key; + record.body_a_handle = body_a_handle; + record.body_b_handle = body_b_handle; + record.point_ax = static_cast(point_a.GetX()); + record.point_ay = static_cast(point_a.GetY()); + record.point_az = static_cast(point_a.GetZ()); + record.point_bx = static_cast(point_b.GetX()); + record.point_by = static_cast(point_b.GetY()); + record.point_bz = static_cast(point_b.GetZ()); + record.normal_bx = manifold.mWorldSpaceNormal.GetX(); + record.normal_by = manifold.mWorldSpaceNormal.GetY(); + record.normal_bz = manifold.mWorldSpaceNormal.GetZ(); + record.distance = -manifold.mPenetrationDepth; + record.impulse = 0.0F; + contacts.push_back(record); + } +} + +void Space::erase_contact_records(const JPH::SubShapeIDPair& key) { + std::lock_guard lock(contact_mutex); + contacts.erase(std::remove_if(contacts.begin(), + contacts.end(), + [&key](const ContactRecord& record) { + return record.key == key; + }), + contacts.end()); +} + +void Space::erase_contact_records_for_body_handle(std::uint64_t body_handle) { + std::lock_guard lock(contact_mutex); + contacts.erase(std::remove_if(contacts.begin(), + contacts.end(), + [body_handle](const ContactRecord& record) { + return record.body_a_handle == body_handle + || record.body_b_handle == body_handle; + }), + contacts.end()); +} + +void ImpulseContactListener::OnContactAdded(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) { + (void) settings; + if (owner != nullptr) { + owner->replace_contact_records(body1, body2, manifold); + } +} + +void ImpulseContactListener::OnContactPersisted(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) { + (void) settings; + if (owner != nullptr) { + owner->replace_contact_records(body1, body2, manifold); + } +} + +void ImpulseContactListener::OnContactRemoved(const JPH::SubShapeIDPair& sub_shape_pair) { + if (owner != nullptr) { + owner->erase_contact_records(sub_shape_pair); + } +} + +bool write_ray_hit(Space& space, + const JPH::RRayCast& ray, + const JPH::RayCastResult& hit, + int index, + std::int64_t* body_handles, + float* hits) { + std::uint64_t body_handle = native_handle_for_body_id(space, hit.mBodyID); + if (body_handle == 0) { + return false; + } + + JPH::RVec3 point = ray.GetPointOnRay(hit.mFraction); + JPH::Vec3 normal = JPH::Vec3::sZero(); + JPH::BodyLockRead lock(space.physics_system.GetBodyLockInterface(), hit.mBodyID); + if (lock.Succeeded()) { + normal = lock.GetBody().GetWorldSpaceSurfaceNormal(hit.mSubShapeID2, point); + } + + body_handles[index] = static_cast(body_handle); + int offset = index * RAY_HIT_FLOAT_COUNT; + hits[offset] = static_cast(point.GetX()); + hits[offset + 1] = static_cast(point.GetY()); + hits[offset + 2] = static_cast(point.GetZ()); + hits[offset + 3] = normal.GetX(); + hits[offset + 4] = normal.GetY(); + hits[offset + 5] = normal.GetZ(); + hits[offset + 6] = hit.mFraction; + hits[offset + 7] = ray.mDirection.Length() * hit.mFraction; + return true; +} + +} // namespace + +extern "C" { + +IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_space() { + ensure_jolt_initialized(); + std::lock_guard lock(registry_mutex); + const std::uint64_t handle = next_space_handle++; + spaces.emplace(handle, std::make_unique()); + return static_cast(handle); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_destroy_space(std::int64_t space_handle) { + std::lock_guard lock(registry_mutex); + return spaces.erase(static_cast(space_handle)) > 0 ? 1 : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_step(std::int64_t space_handle, float dt) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr || !std::isfinite(dt) || dt <= 0.0F) { + return 0; + } + const int collision_steps = std::max(1, static_cast(std::ceil(dt * 60.0F))); + space->physics_system.Update(dt, + collision_steps, + &space->temp_allocator, + &space->job_system); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_gravity(std::int64_t space_handle, float x, float y, float z) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + space->physics_system.SetGravity(JPH::Vec3(x, y, z)); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_get_gravity(std::int64_t space_handle, float* out) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr || out == nullptr) { + return 0; + } + JPH::Vec3 gravity = space->physics_system.GetGravity(); + out[0] = gravity.GetX(); + out[1] = gravity.GetY(); + out[2] = gravity.GetZ(); + return 1; +} + +IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_body(std::int64_t space_handle, + int shape_type, + float half_extent_x, + float half_extent_y, + float half_extent_z, + float radius, + float half_height, + int axis, + float ground_y, + float mass, + int body_type, + float position_x, + float position_y, + float position_z, + float rotation_x, + float rotation_y, + float rotation_z, + float rotation_w) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + + JPH::ShapeRefC shape = create_shape(shape_type, + half_extent_x, + half_extent_y, + half_extent_z, + radius, + half_height, + axis, + ground_y); + if (shape == nullptr) { + return 0; + } + + BodyState body; + body.shape_type = shape_type; + body.body_type = body_type; + body.mass = mass; + body.center_of_mass_offset_y = center_of_mass_offset_y(shape_type, + half_extent_y, + radius, + half_height, + axis); + body.has_box_half_extents = shape_type == SHAPE_BOX + && half_extent_x > 0.0F + && half_extent_y > 0.0F + && half_extent_z > 0.0F; + body.half_extent_x = half_extent_x; + body.half_extent_y = half_extent_y; + body.half_extent_z = half_extent_z; + body.radius = radius; + body.half_height = half_height; + body.axis = axis; + + JPH::BodyCreationSettings settings(shape, + JPH::RVec3(position_x, position_y, position_z), + JPH::Quat(rotation_x, rotation_y, rotation_z, rotation_w), + motion_type(body_type), + object_layer(body.collision_group, body.collision_mask)); + settings.mFriction = body.friction; + settings.mRestitution = body.restitution; + settings.mLinearDamping = body.linear_damping; + settings.mAngularDamping = body.angular_damping; + settings.mAllowDynamicOrKinematic = true; + settings.mIsSensor = body.sensor; + if (mass > 0.0F && body_type != BODY_STATIC) { + settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia; + settings.mMassPropertiesOverride.mMass = mass; + } + + JPH::BodyID body_id = + space->physics_system.GetBodyInterface().CreateAndAddBody(settings, + body_type == BODY_STATIC + ? JPH::EActivation::DontActivate + : JPH::EActivation::Activate); + if (body_id.IsInvalid()) { + return 0; + } + body.body_id = body_id; + + const std::uint64_t body_handle = next_body_handle++; + space->bodies.emplace(body_handle, body); + space->body_handles_by_jolt_id.emplace(body_id.GetIndexAndSequenceNumber(), body_handle); + return static_cast(body_handle); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_remove_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + auto iterator = space->bodies.find(static_cast(body_handle)); + if (iterator == space->bodies.end()) { + return 1; + } + JPH::BodyInterface& body_interface = space->physics_system.GetBodyInterface(); + if (body_interface.IsAdded(iterator->second.body_id)) { + body_interface.RemoveBody(iterator->second.body_id); + } + body_interface.DestroyBody(iterator->second.body_id); + space->body_handles_by_jolt_id.erase(iterator->second.body_id.GetIndexAndSequenceNumber()); + space->erase_contact_records_for_body_handle(static_cast(body_handle)); + space->bodies.erase(iterator); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_contains_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + return space != nullptr + && find_body(*space, static_cast(body_handle)) != nullptr + ? 1 + : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_body_snapshot(std::int64_t space_handle, + std::int64_t body_handle, + float* floats, + int* ints) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr || floats == nullptr || ints == nullptr) { + return 0; + } + BodyState* body = find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + write_snapshot(*space, *body, floats, ints); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_transform(std::int64_t space_handle, + std::int64_t body_handle, + float position_x, + float position_y, + float position_z, + float rotation_x, + float rotation_y, + float rotation_z, + float rotation_w) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->physics_system.GetBodyInterface().SetPositionAndRotation(body->body_id, + JPH::RVec3(position_x, position_y, position_z), + JPH::Quat(rotation_x, rotation_y, rotation_z, rotation_w), + JPH::EActivation::Activate); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_position(std::int64_t space_handle, + std::int64_t body_handle, + float x, + float y, + float z) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->physics_system.GetBodyInterface().SetPosition(body->body_id, + JPH::RVec3(x, y, z), + JPH::EActivation::Activate); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_velocity(std::int64_t space_handle, + std::int64_t body_handle, + float linear_x, + float linear_y, + float linear_z, + float angular_x, + float angular_y, + float angular_z) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->physics_system.GetBodyInterface().SetLinearAndAngularVelocity(body->body_id, + JPH::Vec3(linear_x, linear_y, linear_z), + JPH::Vec3(angular_x, angular_y, angular_z)); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_type(std::int64_t space_handle, std::int64_t body_handle, int body_type) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->body_type = body_type; + space->physics_system.GetBodyInterface().SetMotionType(body->body_id, + motion_type(body_type), + body_type == BODY_STATIC ? JPH::EActivation::DontActivate : JPH::EActivation::Activate); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_damping(std::int64_t space_handle, + std::int64_t body_handle, + float linear, + float angular) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->linear_damping = std::max(0.0F, linear); + body->angular_damping = std::max(0.0F, angular); + JPH::BodyLockWrite body_lock(space->physics_system.GetBodyLockInterface(), body->body_id); + if (body_lock.Succeeded()) { + JPH::MotionProperties* motion_properties = + body_lock.GetBody().GetMotionPropertiesUnchecked(); + if (motion_properties != nullptr) { + motion_properties->SetLinearDamping(body->linear_damping); + motion_properties->SetAngularDamping(body->angular_damping); + } + } + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_friction(std::int64_t space_handle, + std::int64_t body_handle, + float friction) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->friction = friction; + space->physics_system.GetBodyInterface().SetFriction(body->body_id, friction); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_restitution(std::int64_t space_handle, + std::int64_t body_handle, + float restitution) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->restitution = restitution; + space->physics_system.GetBodyInterface().SetRestitution(body->body_id, restitution); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_collision_filter(std::int64_t space_handle, + std::int64_t body_handle, + int group, + int mask) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->collision_group = group; + body->collision_mask = mask; + space->physics_system.GetBodyInterface().SetObjectLayer(body->body_id, + object_layer(group, mask)); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_sensor(std::int64_t space_handle, std::int64_t body_handle, int sensor) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->sensor = sensor != 0; + space->physics_system.GetBodyInterface().SetIsSensor(body->body_id, body->sensor); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_continuous_collision(std::int64_t space_handle, + std::int64_t body_handle, + int enabled) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->continuous_collision = enabled != 0; + space->physics_system.GetBodyInterface().SetMotionQuality(body->body_id, + body->continuous_collision + ? JPH::EMotionQuality::LinearCast + : JPH::EMotionQuality::Discrete); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_is_body_continuous_collision_enabled(std::int64_t space_handle, + std::int64_t body_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + return space->physics_system.GetBodyInterface().GetMotionQuality(body->body_id) + == JPH::EMotionQuality::LinearCast + ? 1 + : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_activate_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->physics_system.GetBodyInterface().ActivateBody(body->body_id); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_sleep_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->physics_system.GetBodyInterface().DeactivateBody(body->body_id); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_apply_body_impulse(std::int64_t space_handle, + std::int64_t body_handle, + float x, + float y, + float z, + int has_offset, + float offset_x, + float offset_y, + float offset_z, + int torque) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + JPH::BodyInterface& body_interface = space->physics_system.GetBodyInterface(); + JPH::Vec3 value(x, y, z); + if (torque != 0) { + body_interface.AddAngularImpulse(body->body_id, value); + } else if (has_offset != 0) { + body_interface.AddImpulse(body->body_id, + value, + JPH::RVec3(offset_x, offset_y, offset_z)); + } else { + body_interface.AddImpulse(body->body_id, value); + } + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_apply_body_force(std::int64_t space_handle, + std::int64_t body_handle, + float x, + float y, + float z, + int has_offset, + float offset_x, + float offset_y, + float offset_z, + int torque) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : find_body(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + JPH::BodyInterface& body_interface = space->physics_system.GetBodyInterface(); + JPH::Vec3 value(x, y, z); + if (torque != 0) { + body_interface.AddTorque(body->body_id, value, JPH::EActivation::Activate); + } else if (has_offset != 0) { + body_interface.AddForce(body->body_id, + value, + JPH::RVec3(offset_x, offset_y, offset_z), + JPH::EActivation::Activate); + } else { + body_interface.AddForce(body->body_id, value, JPH::EActivation::Activate); + } + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_raycast_closest(std::int64_t space_handle, + float from_x, + float from_y, + float from_z, + float to_x, + float to_y, + float to_z, + std::int64_t* body_handles, + float* hits) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr || body_handles == nullptr || hits == nullptr) { + return 0; + } + + JPH::Vec3 direction(to_x - from_x, to_y - from_y, to_z - from_z); + if (direction.LengthSq() <= 0.0F) { + return 0; + } + JPH::RRayCast ray(JPH::RVec3(from_x, from_y, from_z), direction); + JPH::RayCastResult hit; + if (!space->physics_system.GetNarrowPhaseQuery().CastRay(ray, hit)) { + return 0; + } + return write_ray_hit(*space, ray, hit, 0, body_handles, hits) ? 1 : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_raycast_all(std::int64_t space_handle, + float from_x, + float from_y, + float from_z, + float to_x, + float to_y, + float to_z, + int max_hits, + std::int64_t* body_handles, + float* hits) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr || max_hits <= 0 || body_handles == nullptr || hits == nullptr) { + return 0; + } + + JPH::Vec3 direction(to_x - from_x, to_y - from_y, to_z - from_z); + if (direction.LengthSq() <= 0.0F) { + return 0; + } + + JPH::RRayCast ray(JPH::RVec3(from_x, from_y, from_z), direction); + JPH::RayCastSettings settings; + JPH::ClosestHitPerBodyCollisionCollector collector; + space->physics_system.GetNarrowPhaseQuery().CastRay(ray, settings, collector); + collector.Sort(); + + int emitted = 0; + for (const JPH::RayCastResult& hit : collector.mHits) { + if (emitted >= max_hits) { + break; + } + if (write_ray_hit(*space, ray, hit, emitted, body_handles, hits)) { + emitted++; + } + } + return emitted; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_contacts(std::int64_t space_handle, + int max_contacts, + std::int64_t* body_handles, + float* contacts) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr || max_contacts <= 0 || body_handles == nullptr || contacts == nullptr) { + return 0; + } + + std::lock_guard contact_lock(space->contact_mutex); + int emitted = 0; + for (const ContactRecord& contact : space->contacts) { + if (emitted >= max_contacts) { + break; + } + int body_offset = emitted * CONTACT_BODY_HANDLE_COUNT; + body_handles[body_offset] = static_cast(contact.body_a_handle); + body_handles[body_offset + 1] = static_cast(contact.body_b_handle); + + int contact_offset = emitted * CONTACT_FLOAT_COUNT; + contacts[contact_offset] = contact.point_ax; + contacts[contact_offset + 1] = contact.point_ay; + contacts[contact_offset + 2] = contact.point_az; + contacts[contact_offset + 3] = contact.point_bx; + contacts[contact_offset + 4] = contact.point_by; + contacts[contact_offset + 5] = contact.point_bz; + contacts[contact_offset + 6] = contact.normal_bx; + contacts[contact_offset + 7] = contact.normal_by; + contacts[contact_offset + 8] = contact.normal_bz; + contacts[contact_offset + 9] = contact.distance; + contacts[contact_offset + 10] = contact.impulse; + emitted++; + } + return emitted; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_contact_count(std::int64_t space_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + std::lock_guard contact_lock(space->contact_mutex); + return static_cast(space->contacts.size()); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_body_count(std::int64_t space_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + return space == nullptr ? 0 : static_cast(space->bodies.size()); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_joint_count(std::int64_t space_handle) { + std::lock_guard lock(registry_mutex); + return find_space(static_cast(space_handle)) == nullptr ? 0 : 0; +} + +} // extern "C" diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java new file mode 100644 index 00000000..d8745c11 --- /dev/null +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java @@ -0,0 +1,38 @@ +package dev.hytalemodding.impulse.jolt; + +import dev.hytalemodding.impulse.api.BackendId; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nonnull; + +public final class JoltBackend { + + public static final BackendId ID = new BackendId("impulse:jolt"); + private static final Logger LOGGER = Logger.getLogger("Impulse"); + + private volatile boolean initialized; + private JoltNativeLibrary nativeLibrary; + + @Nonnull + public BackendId getId() { + return ID; + } + + public synchronized void init() { + if (initialized) { + return; + } + + nativeLibrary = JoltNative.open(); + initialized = true; + LOGGER.log(Level.INFO, "Jolt backend initialized"); + } + + @Nonnull + synchronized JoltNativeLibrary nativeLibrary() { + if (!initialized) { + init(); + } + return nativeLibrary; + } +} diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java new file mode 100644 index 00000000..98a31a5e --- /dev/null +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java @@ -0,0 +1,754 @@ +package dev.hytalemodding.impulse.jolt; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; +import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; +import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; +import dev.hytalemodding.impulse.api.runtime.BackendBodyIdSource; +import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; +import dev.hytalemodding.impulse.api.runtime.BackendContactSink; +import dev.hytalemodding.impulse.api.runtime.BackendExtensionSettingsSource; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeStatsSink; +import dev.hytalemodding.impulse.api.runtime.BackendStepPhaseStatsSink; +import dev.hytalemodding.impulse.api.runtime.BackendVec3Sink; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class JoltBackendRuntime implements PhysicsBackendRuntime { + + private static final String UNSUPPORTED_MESSAGE = + "Jolt runtime operation is not implemented yet"; + + private final JoltBackend backend; + @Nullable + private final JoltNativeLibrary fixedNativeLibrary; + private final Map spaces = new HashMap<>(); + private final float[] gravityScratch = new float[3]; + private final long[] closestRayBodyHandleScratch = new long[1]; + private final float[] closestRayHitScratch = new float[JoltNativeLibrary.RAY_HIT_FLOAT_COUNT]; + private final JoltBodySnapshot bodySnapshotScratch = new JoltBodySnapshot(); + + JoltBackendRuntime(@Nonnull JoltBackend backend) { + this(backend, null); + } + + JoltBackendRuntime(@Nonnull JoltBackend backend, @Nullable JoltNativeLibrary nativeLibrary) { + this.backend = Objects.requireNonNull(backend, "backend"); + this.fixedNativeLibrary = nativeLibrary; + } + + @Override + public int createSpace(@Nonnull SpaceId requestedId) { + Objects.requireNonNull(requestedId, "requestedId"); + int spaceId = requestedId.value(); + if (spaces.containsKey(spaceId)) { + throw new IllegalStateException("Jolt space already exists: " + spaceId); + } + long handle = nativeLibrary().createSpace(); + if (handle == 0L) { + throw new IllegalStateException("Jolt native library returned a null space handle"); + } + spaces.put(spaceId, new RuntimeSpace(handle)); + return spaceId; + } + + @Override + public void destroySpace(int spaceId) { + RuntimeSpace space = spaces.remove(spaceId); + if (space != null) { + nativeLibrary().destroySpace(space.handle); + } + } + + @Override + public void close() { + RuntimeException failure = null; + for (Integer spaceId : new ArrayList<>(spaces.keySet())) { + try { + destroySpace(spaceId); + } catch (RuntimeException exception) { + if (failure == null) { + failure = exception; + } else { + failure.addSuppressed(exception); + } + } + } + if (failure != null) { + throw failure; + } + } + + @Override + public void step(int spaceId, float dt) { + long handle = requireSpaceHandle(spaceId); + if (dt <= 0.0f) { + return; + } + nativeLibrary().step(handle, dt); + } + + @Override + public void setGravity(int spaceId, float x, float y, float z) { + nativeLibrary().setGravity(requireSpaceHandle(spaceId), x, y, z); + } + + @Override + public void getGravity(int spaceId, @Nonnull BackendVec3Sink sink) { + Objects.requireNonNull(sink, "sink"); + nativeLibrary().getGravity(requireSpaceHandle(spaceId), gravityScratch); + sink.accept(gravityScratch[0], gravityScratch[1], gravityScratch[2]); + } + + @Override + public long createBody(int spaceId, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + RuntimeSpace space = requireSpace(spaceId); + validateBodyShapeCode(shapeTypeCode); + BackendRuntimeCodes.axis(axisCode); + BackendRuntimeCodes.bodyType(bodyTypeCode); + long bodyHandle = nativeLibrary().createBody(space.handle, + shapeTypeCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode, + groundY, + mass, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW); + if (bodyHandle == 0L) { + throw new IllegalStateException("Jolt native library returned a null body handle"); + } + long bodyId = space.nextBodyId++; + space.bodyHandles.put(bodyId, bodyHandle); + space.bodyIdsByHandle.put(bodyHandle, bodyId); + return bodyId; + } + + @Override + public boolean supportsVoxelTerrain(int spaceId) { + requireSpaceHandle(spaceId); + return false; + } + + @Override + public long createVoxelTerrain(int spaceId, + float voxelSizeX, + float voxelSizeY, + float voxelSizeZ, + @Nonnull int[] voxelCoordinates, + float positionX, + float positionY, + float positionZ, + float friction, + float restitution, + int collisionGroup, + int collisionMask) { + Objects.requireNonNull(voxelCoordinates, "voxelCoordinates"); + requireSpaceHandle(spaceId); + throw unsupported(); + } + + @Override + public void combineVoxelTerrains(int spaceId, + long bodyAId, + long bodyBId, + int shiftX, + int shiftY, + int shiftZ) { + requireSpaceHandle(spaceId); + throw unsupported(); + } + + @Override + public void removeBody(int spaceId, long bodyId) { + RuntimeSpace space = requireSpace(spaceId); + Long bodyHandle = space.bodyHandles.remove(bodyId); + if (bodyHandle != null) { + space.bodyIdsByHandle.remove(bodyHandle); + nativeLibrary().removeBody(space.handle, bodyHandle); + } + } + + @Override + public int bodyCount(int spaceId) { + return nativeLibrary().bodyCount(requireSpaceHandle(spaceId)); + } + + @Override + public boolean containsBody(int spaceId, long bodyId) { + RuntimeSpace space = requireSpace(spaceId); + Long bodyHandle = space.bodyHandles.get(bodyId); + return bodyHandle != null && nativeLibrary().containsBody(space.handle, bodyHandle); + } + + @Override + public boolean bodySnapshot(int spaceId, long bodyId, @Nonnull BackendBodySnapshotSink sink) { + Objects.requireNonNull(sink, "sink"); + RuntimeSpace space = requireSpace(spaceId); + Long bodyHandle = space.bodyHandles.get(bodyId); + if (bodyHandle == null) { + return false; + } + bodySnapshotScratch.clear(); + if (!nativeLibrary().bodySnapshot(space.handle, bodyHandle, bodySnapshotScratch)) { + return false; + } + bodySnapshotScratch.emit(bodyId, sink); + return true; + } + + @Override + public void snapshotBodies(int spaceId, + @Nonnull BackendBodyIdSource bodyIds, + @Nonnull BackendBodySnapshotSink sink) { + Objects.requireNonNull(bodyIds, "bodyIds"); + Objects.requireNonNull(sink, "sink"); + RuntimeSpace space = requireSpace(spaceId); + bodyIds.forEachBodyId(bodyId -> emitBodySnapshotIfPresent(space, bodyId, sink)); + } + + @Override + public void setBodyTransform(int spaceId, + long bodyId, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyTransform(space.handle, + requireBodyHandle(space, bodyId), + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW); + } + + @Override + public void setBodyPosition(int spaceId, long bodyId, float x, float y, float z) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyPosition(space.handle, requireBodyHandle(space, bodyId), x, y, z); + } + + @Override + public void setBodyVelocity(int spaceId, + long bodyId, + float linearX, + float linearY, + float linearZ, + float angularX, + float angularY, + float angularZ) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyVelocity(space.handle, + requireBodyHandle(space, bodyId), + linearX, + linearY, + linearZ, + angularX, + angularY, + angularZ); + } + + @Override + public void setBodyType(int spaceId, long bodyId, int bodyTypeCode) { + RuntimeSpace space = requireSpace(spaceId); + BackendRuntimeCodes.bodyType(bodyTypeCode); + nativeLibrary().setBodyType(space.handle, requireBodyHandle(space, bodyId), bodyTypeCode); + } + + @Override + public void setBodyDamping(int spaceId, long bodyId, float linearDamping, float angularDamping) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyDamping(space.handle, + requireBodyHandle(space, bodyId), + linearDamping, + angularDamping); + } + + @Override + public void setBodyFriction(int spaceId, long bodyId, float friction) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyFriction(space.handle, requireBodyHandle(space, bodyId), friction); + } + + @Override + public void setBodyRestitution(int spaceId, long bodyId, float restitution) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyRestitution(space.handle, + requireBodyHandle(space, bodyId), + restitution); + } + + @Override + public void setBodyCollisionFilter(int spaceId, long bodyId, int group, int mask) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyCollisionFilter(space.handle, + requireBodyHandle(space, bodyId), + group, + mask); + } + + @Override + public void setBodySensor(int spaceId, long bodyId, boolean sensor) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodySensor(space.handle, requireBodyHandle(space, bodyId), sensor); + } + + @Override + public void setBodyContinuousCollision(int spaceId, long bodyId, boolean enabled) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().setBodyContinuousCollision(space.handle, + requireBodyHandle(space, bodyId), + enabled); + } + + @Override + public boolean isBodyContinuousCollisionEnabled(int spaceId, long bodyId) { + RuntimeSpace space = requireSpace(spaceId); + return nativeLibrary().isBodyContinuousCollisionEnabled(space.handle, + requireBodyHandle(space, bodyId)); + } + + @Override + public void activateBody(int spaceId, long bodyId) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().activateBody(space.handle, requireBodyHandle(space, bodyId)); + } + + @Override + public void sleepBody(int spaceId, long bodyId) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().sleepBody(space.handle, requireBodyHandle(space, bodyId)); + } + + @Override + public void applyBodyImpulse(int spaceId, + long bodyId, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().applyBodyImpulse(space.handle, + requireBodyHandle(space, bodyId), + x, + y, + z, + hasOffset, + offsetX, + offsetY, + offsetZ, + torque); + } + + @Override + public void applyBodyForce(int spaceId, + long bodyId, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + RuntimeSpace space = requireSpace(spaceId); + nativeLibrary().applyBodyForce(space.handle, + requireBodyHandle(space, bodyId), + x, + y, + z, + hasOffset, + offsetX, + offsetY, + offsetZ, + torque); + } + + @Override + public long createJoint(int spaceId, + int jointTypeCode, + long bodyAId, + long bodyBId, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisX, + float axisY, + float axisZ, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + boolean motorEnabled, + float motorTargetVelocity, + float motorMaxForce) { + requireSpaceHandle(spaceId); + BackendRuntimeCodes.jointType(jointTypeCode); + throw unsupported(); + } + + @Override + public void removeJoint(int spaceId, long jointId) { + requireSpaceHandle(spaceId); + throw unsupported(); + } + + @Override + public int jointCount(int spaceId) { + return nativeLibrary().jointCount(requireSpaceHandle(spaceId)); + } + + @Override + public int jointType(int spaceId, long jointId) { + requireSpaceHandle(spaceId); + throw unsupported(); + } + + @Override + public long jointBodyA(int spaceId, long jointId) { + requireSpaceHandle(spaceId); + throw unsupported(); + } + + @Override + public long jointBodyB(int spaceId, long jointId) { + requireSpaceHandle(spaceId); + throw unsupported(); + } + + @Override + public boolean raycastClosest(int spaceId, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + @Nonnull BackendRayHitSink sink) { + Objects.requireNonNull(sink, "sink"); + RuntimeSpace space = requireSpace(spaceId); + int hitCount = nativeLibrary().raycastClosest(space.handle, + fromX, + fromY, + fromZ, + toX, + toY, + toZ, + closestRayBodyHandleScratch, + closestRayHitScratch); + return hitCount > 0 && emitRayHit(space, + closestRayBodyHandleScratch[0], + closestRayHitScratch, + 0, + sink); + } + + @Override + public int raycastAll(int spaceId, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + @Nonnull BackendRayHitSink sink) { + Objects.requireNonNull(sink, "sink"); + RuntimeSpace space = requireSpace(spaceId); + int maxHits = nativeLibrary().bodyCount(space.handle); + if (maxHits <= 0) { + return 0; + } + long[] bodyHandles = new long[maxHits]; + float[] hits = new float[maxHits * JoltNativeLibrary.RAY_HIT_FLOAT_COUNT]; + int nativeHits = nativeLibrary().raycastAll(space.handle, + fromX, + fromY, + fromZ, + toX, + toY, + toZ, + maxHits, + bodyHandles, + hits); + int emitted = 0; + int boundedHits = Math.min(Math.max(nativeHits, 0), maxHits); + for (int index = 0; index < boundedHits; index++) { + if (emitRayHit(space, + bodyHandles[index], + hits, + index * JoltNativeLibrary.RAY_HIT_FLOAT_COUNT, + sink)) { + emitted++; + } + } + return emitted; + } + + @Override + public int contacts(int spaceId, @Nonnull BackendContactSink sink) { + Objects.requireNonNull(sink, "sink"); + RuntimeSpace space = requireSpace(spaceId); + int maxContacts = nativeLibrary().contactCount(space.handle); + return emitContacts(space, maxContacts, sink); + } + + @Override + public int contacts(int spaceId, int maxContacts, @Nonnull BackendContactSink sink) { + Objects.requireNonNull(sink, "sink"); + RuntimeSpace space = requireSpace(spaceId); + return emitContacts(space, maxContacts, sink); + } + + @Override + public int contactCount(int spaceId) { + return nativeLibrary().contactCount(requireSpaceHandle(spaceId)); + } + + @Override + public void runtimeStats(int spaceId, @Nonnull BackendRuntimeStatsSink sink) { + Objects.requireNonNull(sink, "sink"); + long handle = requireSpaceHandle(spaceId); + int bodyCount = nativeLibrary().bodyCount(handle); + int jointCount = nativeLibrary().jointCount(handle); + sink.accept(bodyCount, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + jointCount, + true); + } + + @Override + public void resetStepPhaseStats(int spaceId) { + requireSpaceHandle(spaceId); + } + + @Override + public void stepPhaseStats(int spaceId, @Nonnull BackendStepPhaseStatsSink sink) { + Objects.requireNonNull(sink, "sink"); + requireSpaceHandle(spaceId); + sink.accept(0L, 0L, 0L, 0L, 0L, 0L, false); + } + + @Override + public boolean supportsContinuousCollision(int spaceId) { + requireSpaceHandle(spaceId); + return true; + } + + @Override + public boolean supportsSolverTuning(int spaceId) { + requireSpaceHandle(spaceId); + return false; + } + + @Override + public boolean supportsActivationTuning(int spaceId) { + requireSpaceHandle(spaceId); + return false; + } + + @Override + public void applySolverTuning(int spaceId, @Nonnull PhysicsSolverTuning tuning) { + Objects.requireNonNull(tuning, "tuning"); + requireSpaceHandle(spaceId); + } + + @Override + public void applyActivationTuning(int spaceId, @Nonnull PhysicsActivationTuning tuning) { + Objects.requireNonNull(tuning, "tuning"); + requireSpaceHandle(spaceId); + } + + @Override + public void applyExtensionSettings(int spaceId, + @Nonnull PhysicsCapabilityId capabilityId, + @Nonnull BackendExtensionSettingsSource settings) { + Objects.requireNonNull(capabilityId, "capabilityId"); + Objects.requireNonNull(settings, "settings"); + requireSpaceHandle(spaceId); + } + + @Nonnull + JoltBackend backend() { + return backend; + } + + private long requireSpaceHandle(int spaceId) { + return requireSpace(spaceId).handle; + } + + @Nonnull + private RuntimeSpace requireSpace(int spaceId) { + RuntimeSpace space = spaces.get(spaceId); + if (space == null) { + throw new IllegalArgumentException("Unknown Jolt space id: " + spaceId); + } + return space; + } + + private long requireBodyHandle(@Nonnull RuntimeSpace space, long bodyId) { + Long bodyHandle = space.bodyHandles.get(bodyId); + if (bodyHandle == null) { + throw new IllegalArgumentException("Unknown Jolt body id: " + bodyId); + } + return bodyHandle; + } + + private void emitBodySnapshotIfPresent(@Nonnull RuntimeSpace space, + long bodyId, + @Nonnull BackendBodySnapshotSink sink) { + Long bodyHandle = space.bodyHandles.get(bodyId); + if (bodyHandle == null) { + return; + } + bodySnapshotScratch.clear(); + if (nativeLibrary().bodySnapshot(space.handle, bodyHandle, bodySnapshotScratch)) { + bodySnapshotScratch.emit(bodyId, sink); + } + } + + private boolean emitRayHit(@Nonnull RuntimeSpace space, + long bodyHandle, + @Nonnull float[] hit, + int offset, + @Nonnull BackendRayHitSink sink) { + Long bodyId = space.bodyIdsByHandle.get(bodyHandle); + if (bodyId == null) { + return false; + } + sink.accept(bodyId, + hit[offset], + hit[offset + 1], + hit[offset + 2], + hit[offset + 3], + hit[offset + 4], + hit[offset + 5], + hit[offset + 6], + hit[offset + 7]); + return true; + } + + private int emitContacts(@Nonnull RuntimeSpace space, + int maxContacts, + @Nonnull BackendContactSink sink) { + if (maxContacts <= 0) { + return 0; + } + long[] bodyHandles = new long[maxContacts * JoltNativeLibrary.CONTACT_BODY_HANDLE_COUNT]; + float[] contacts = new float[maxContacts * JoltNativeLibrary.CONTACT_FLOAT_COUNT]; + int nativeContacts = nativeLibrary().contacts(space.handle, + maxContacts, + bodyHandles, + contacts); + int emitted = 0; + int boundedContacts = Math.min(Math.max(nativeContacts, 0), maxContacts); + for (int index = 0; index < boundedContacts; index++) { + int bodyOffset = index * JoltNativeLibrary.CONTACT_BODY_HANDLE_COUNT; + Long bodyAId = space.bodyIdsByHandle.get(bodyHandles[bodyOffset]); + Long bodyBId = space.bodyIdsByHandle.get(bodyHandles[bodyOffset + 1]); + if (bodyAId == null || bodyBId == null) { + continue; + } + int contactOffset = index * JoltNativeLibrary.CONTACT_FLOAT_COUNT; + sink.accept(bodyAId, + bodyBId, + contacts[contactOffset], + contacts[contactOffset + 1], + contacts[contactOffset + 2], + contacts[contactOffset + 3], + contacts[contactOffset + 4], + contacts[contactOffset + 5], + contacts[contactOffset + 6], + contacts[contactOffset + 7], + contacts[contactOffset + 8], + contacts[contactOffset + 9], + contacts[contactOffset + 10]); + emitted++; + } + return emitted; + } + + private static void validateBodyShapeCode(int shapeTypeCode) { + switch (BackendRuntimeCodes.shapeType(shapeTypeCode)) { + case BOX, SPHERE, CAPSULE, CYLINDER, CONE, PLANE -> { + } + case VOXELS, UNKNOWN -> throw new IllegalArgumentException( + "Unsupported Jolt body shape code: " + shapeTypeCode); + } + } + + @Nonnull + private JoltNativeLibrary nativeLibrary() { + return fixedNativeLibrary != null ? fixedNativeLibrary : backend.nativeLibrary(); + } + + private static UnsupportedOperationException unsupported() { + return new UnsupportedOperationException(UNSUPPORTED_MESSAGE); + } + + private static final class RuntimeSpace { + + private final long handle; + private final Map bodyHandles = new HashMap<>(); + private final Map bodyIdsByHandle = new HashMap<>(); + private long nextBodyId = 1L; + + private RuntimeSpace(long handle) { + this.handle = handle; + } + } +} diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java new file mode 100644 index 00000000..56c53677 --- /dev/null +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java @@ -0,0 +1,31 @@ +package dev.hytalemodding.impulse.jolt; + +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; +import javax.annotation.Nonnull; + +/** + * Runtime-provider service entry point for Jolt. + */ +public final class JoltBackendRuntimeProvider implements PhysicsBackendRuntimeProvider { + + private final JoltBackend backend = new JoltBackend(); + + @Nonnull + @Override + public BackendId getId() { + return JoltBackend.ID; + } + + @Override + public void init() { + backend.init(); + } + + @Nonnull + @Override + public PhysicsBackendRuntime createRuntime() { + return new JoltBackendRuntime(backend); + } +} diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java new file mode 100644 index 00000000..eb7699d3 --- /dev/null +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java @@ -0,0 +1,153 @@ +package dev.hytalemodding.impulse.jolt; + +import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; +import javax.annotation.Nonnull; + +final class JoltBodySnapshot { + + static final int FLOAT_FIELD_COUNT = 24; + static final int INT_FIELD_COUNT = 9; + + int shapeTypeCode; + int bodyTypeCode; + float positionX; + float positionY; + float positionZ; + float rotationX; + float rotationY; + float rotationZ; + float rotationW; + float linearVelocityX; + float linearVelocityY; + float linearVelocityZ; + float angularVelocityX; + float angularVelocityY; + float angularVelocityZ; + boolean sleeping; + boolean sensor; + float mass; + float friction; + float restitution; + float linearDamping; + float angularDamping; + int collisionGroup; + int collisionMask; + boolean continuousCollisionEnabled; + float centerOfMassOffsetY; + boolean hasBoxHalfExtents; + float halfExtentX; + float halfExtentY; + float halfExtentZ; + float radius; + float halfHeight; + int axisCode; + + void clear() { + shapeTypeCode = 0; + bodyTypeCode = 0; + positionX = 0.0f; + positionY = 0.0f; + positionZ = 0.0f; + rotationX = 0.0f; + rotationY = 0.0f; + rotationZ = 0.0f; + rotationW = 0.0f; + linearVelocityX = 0.0f; + linearVelocityY = 0.0f; + linearVelocityZ = 0.0f; + angularVelocityX = 0.0f; + angularVelocityY = 0.0f; + angularVelocityZ = 0.0f; + sleeping = false; + sensor = false; + mass = 0.0f; + friction = 0.0f; + restitution = 0.0f; + linearDamping = 0.0f; + angularDamping = 0.0f; + collisionGroup = 0; + collisionMask = 0; + continuousCollisionEnabled = false; + centerOfMassOffsetY = 0.0f; + hasBoxHalfExtents = false; + halfExtentX = 0.0f; + halfExtentY = 0.0f; + halfExtentZ = 0.0f; + radius = 0.0f; + halfHeight = 0.0f; + axisCode = 0; + } + + void copyFrom(@Nonnull JoltBodySnapshot other) { + shapeTypeCode = other.shapeTypeCode; + bodyTypeCode = other.bodyTypeCode; + positionX = other.positionX; + positionY = other.positionY; + positionZ = other.positionZ; + rotationX = other.rotationX; + rotationY = other.rotationY; + rotationZ = other.rotationZ; + rotationW = other.rotationW; + linearVelocityX = other.linearVelocityX; + linearVelocityY = other.linearVelocityY; + linearVelocityZ = other.linearVelocityZ; + angularVelocityX = other.angularVelocityX; + angularVelocityY = other.angularVelocityY; + angularVelocityZ = other.angularVelocityZ; + sleeping = other.sleeping; + sensor = other.sensor; + mass = other.mass; + friction = other.friction; + restitution = other.restitution; + linearDamping = other.linearDamping; + angularDamping = other.angularDamping; + collisionGroup = other.collisionGroup; + collisionMask = other.collisionMask; + continuousCollisionEnabled = other.continuousCollisionEnabled; + centerOfMassOffsetY = other.centerOfMassOffsetY; + hasBoxHalfExtents = other.hasBoxHalfExtents; + halfExtentX = other.halfExtentX; + halfExtentY = other.halfExtentY; + halfExtentZ = other.halfExtentZ; + radius = other.radius; + halfHeight = other.halfHeight; + axisCode = other.axisCode; + } + + void emit(long bodyId, @Nonnull BackendBodySnapshotSink sink) { + sink.accept(bodyId, + shapeTypeCode, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearVelocityX, + linearVelocityY, + linearVelocityZ, + angularVelocityX, + angularVelocityY, + angularVelocityZ, + sleeping, + sensor, + mass, + friction, + restitution, + linearDamping, + angularDamping, + collisionGroup, + collisionMask, + continuousCollisionEnabled, + centerOfMassOffsetY, + hasBoxHalfExtents, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode); + } +} diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java new file mode 100644 index 00000000..012e4a50 --- /dev/null +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java @@ -0,0 +1,31 @@ +package dev.hytalemodding.impulse.jolt; + +import dev.hytalemodding.impulse.internal.nativelib.NativeLibraryLoader; +import dev.hytalemodding.impulse.internal.nativelib.NativeLibraryResource; +import java.lang.foreign.SymbolLookup; + +final class JoltNative { + + private static final String LIBRARY_NAME = "impulse_jolt"; + private static JoltNativeLibrary library; + + private JoltNative() { + } + + static synchronized JoltNativeLibrary open() { + if (library != null) { + return library; + } + + try { + NativeLibraryLoader.load(JoltNative.class, + "jolt", + NativeLibraryResource.forCurrentPlatform(LIBRARY_NAME)); + } catch (IllegalArgumentException | IllegalStateException exception) { + throw new IllegalStateException("Failed to load the Jolt native library", exception); + } + + library = PanamaJoltNativeLibrary.open(SymbolLookup.loaderLookup()); + return library; + } +} diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java new file mode 100644 index 00000000..6e8e9e46 --- /dev/null +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java @@ -0,0 +1,192 @@ +package dev.hytalemodding.impulse.jolt; + +interface JoltNativeLibrary { + + int RAY_HIT_FLOAT_COUNT = 8; + int CONTACT_BODY_HANDLE_COUNT = 2; + int CONTACT_FLOAT_COUNT = 11; + + long createSpace(); + + void destroySpace(long spaceHandle); + + void step(long spaceHandle, float dt); + + void setGravity(long spaceHandle, float x, float y, float z); + + void getGravity(long spaceHandle, float[] out); + + default long createBody(long spaceHandle, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + throw unsupportedBodyAbi(); + } + + default void removeBody(long spaceHandle, long bodyHandle) { + throw unsupportedBodyAbi(); + } + + default boolean containsBody(long spaceHandle, long bodyHandle) { + throw unsupportedBodyAbi(); + } + + default boolean bodySnapshot(long spaceHandle, long bodyHandle, JoltBodySnapshot out) { + throw unsupportedBodyAbi(); + } + + default void setBodyTransform(long spaceHandle, + long bodyHandle, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + throw unsupportedBodyAbi(); + } + + default void setBodyPosition(long spaceHandle, long bodyHandle, float x, float y, float z) { + throw unsupportedBodyAbi(); + } + + default void setBodyVelocity(long spaceHandle, + long bodyHandle, + float linearX, + float linearY, + float linearZ, + float angularX, + float angularY, + float angularZ) { + throw unsupportedBodyAbi(); + } + + default void setBodyType(long spaceHandle, long bodyHandle, int bodyTypeCode) { + throw unsupportedBodyAbi(); + } + + default void setBodyDamping(long spaceHandle, long bodyHandle, float linearDamping, float angularDamping) { + throw unsupportedBodyAbi(); + } + + default void setBodyFriction(long spaceHandle, long bodyHandle, float friction) { + throw unsupportedBodyAbi(); + } + + default void setBodyRestitution(long spaceHandle, long bodyHandle, float restitution) { + throw unsupportedBodyAbi(); + } + + default void setBodyCollisionFilter(long spaceHandle, long bodyHandle, int group, int mask) { + throw unsupportedBodyAbi(); + } + + default void setBodySensor(long spaceHandle, long bodyHandle, boolean sensor) { + throw unsupportedBodyAbi(); + } + + default void setBodyContinuousCollision(long spaceHandle, long bodyHandle, boolean enabled) { + throw unsupportedBodyAbi(); + } + + default boolean isBodyContinuousCollisionEnabled(long spaceHandle, long bodyHandle) { + throw unsupportedBodyAbi(); + } + + default void activateBody(long spaceHandle, long bodyHandle) { + throw unsupportedBodyAbi(); + } + + default void sleepBody(long spaceHandle, long bodyHandle) { + throw unsupportedBodyAbi(); + } + + default void applyBodyImpulse(long spaceHandle, + long bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + throw unsupportedBodyAbi(); + } + + default void applyBodyForce(long spaceHandle, + long bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + throw unsupportedBodyAbi(); + } + + default int raycastClosest(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + long[] bodyHandleOut, + float[] hitOut) { + throw unsupportedQueryAbi(); + } + + default int raycastAll(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + int maxHits, + long[] bodyHandles, + float[] hits) { + throw unsupportedQueryAbi(); + } + + default int contacts(long spaceHandle, + int maxContacts, + long[] bodyHandles, + float[] contacts) { + throw unsupportedQueryAbi(); + } + + default int contactCount(long spaceHandle) { + throw unsupportedQueryAbi(); + } + + int bodyCount(long spaceHandle); + + int jointCount(long spaceHandle); + + private static UnsupportedOperationException unsupportedBodyAbi() { + return new UnsupportedOperationException("Jolt native body ABI is not implemented"); + } + + private static UnsupportedOperationException unsupportedQueryAbi() { + return new UnsupportedOperationException("Jolt native query ABI is not implemented"); + } +} diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java new file mode 100644 index 00000000..16d8e635 --- /dev/null +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java @@ -0,0 +1,828 @@ +package dev.hytalemodding.impulse.jolt; + +import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_FLOAT; +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; + +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.invoke.MethodHandle; +import java.util.NoSuchElementException; +import java.util.Objects; +import javax.annotation.Nonnull; + +final class PanamaJoltNativeLibrary implements JoltNativeLibrary { + + private static final Linker LINKER = Linker.nativeLinker(); + private static final int GRAVITY_COMPONENTS = 3; + + private final MethodHandle createSpace; + private final MethodHandle destroySpace; + private final MethodHandle step; + private final MethodHandle setGravity; + private final MethodHandle getGravity; + private final MethodHandle createBody; + private final MethodHandle removeBody; + private final MethodHandle containsBody; + private final MethodHandle bodySnapshot; + private final MethodHandle setBodyTransform; + private final MethodHandle setBodyPosition; + private final MethodHandle setBodyVelocity; + private final MethodHandle setBodyType; + private final MethodHandle setBodyDamping; + private final MethodHandle setBodyFriction; + private final MethodHandle setBodyRestitution; + private final MethodHandle setBodyCollisionFilter; + private final MethodHandle setBodySensor; + private final MethodHandle setBodyContinuousCollision; + private final MethodHandle isBodyContinuousCollisionEnabled; + private final MethodHandle activateBody; + private final MethodHandle sleepBody; + private final MethodHandle applyBodyImpulse; + private final MethodHandle applyBodyForce; + private final MethodHandle raycastClosest; + private final MethodHandle raycastAll; + private final MethodHandle contacts; + private final MethodHandle contactCount; + private final MethodHandle bodyCount; + private final MethodHandle jointCount; + + private PanamaJoltNativeLibrary(@Nonnull SymbolLookup symbols) { + Objects.requireNonNull(symbols, "symbols"); + createSpace = downcall(symbols, + "impulse_jolt_create_space", + FunctionDescriptor.of(JAVA_LONG)); + destroySpace = downcall(symbols, + "impulse_jolt_destroy_space", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG)); + step = downcall(symbols, + "impulse_jolt_step", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_FLOAT)); + setGravity = downcall(symbols, + "impulse_jolt_set_gravity", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_FLOAT, JAVA_FLOAT, JAVA_FLOAT)); + getGravity = downcall(symbols, + "impulse_jolt_get_gravity", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS)); + createBody = downcall(symbols, + "impulse_jolt_create_body", + FunctionDescriptor.of(JAVA_LONG, + JAVA_LONG, + JAVA_INT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT)); + removeBody = downcall(symbols, + "impulse_jolt_remove_body", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG)); + containsBody = downcall(symbols, + "impulse_jolt_contains_body", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG)); + bodySnapshot = downcall(symbols, + "impulse_jolt_body_snapshot", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, ADDRESS, ADDRESS)); + setBodyTransform = downcall(symbols, + "impulse_jolt_set_body_transform", + FunctionDescriptor.of(JAVA_INT, + JAVA_LONG, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT)); + setBodyPosition = downcall(symbols, + "impulse_jolt_set_body_position", + FunctionDescriptor.of(JAVA_INT, + JAVA_LONG, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT)); + setBodyVelocity = downcall(symbols, + "impulse_jolt_set_body_velocity", + FunctionDescriptor.of(JAVA_INT, + JAVA_LONG, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT)); + setBodyType = downcall(symbols, + "impulse_jolt_set_body_type", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_INT)); + setBodyDamping = downcall(symbols, + "impulse_jolt_set_body_damping", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_FLOAT, JAVA_FLOAT)); + setBodyFriction = downcall(symbols, + "impulse_jolt_set_body_friction", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_FLOAT)); + setBodyRestitution = downcall(symbols, + "impulse_jolt_set_body_restitution", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_FLOAT)); + setBodyCollisionFilter = downcall(symbols, + "impulse_jolt_set_body_collision_filter", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_INT, JAVA_INT)); + setBodySensor = downcall(symbols, + "impulse_jolt_set_body_sensor", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_INT)); + setBodyContinuousCollision = downcall(symbols, + "impulse_jolt_set_body_continuous_collision", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_INT)); + isBodyContinuousCollisionEnabled = downcall(symbols, + "impulse_jolt_is_body_continuous_collision_enabled", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG)); + activateBody = downcall(symbols, + "impulse_jolt_activate_body", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG)); + sleepBody = downcall(symbols, + "impulse_jolt_sleep_body", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG)); + applyBodyImpulse = downcall(symbols, + "impulse_jolt_apply_body_impulse", + FunctionDescriptor.of(JAVA_INT, + JAVA_LONG, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT)); + applyBodyForce = downcall(symbols, + "impulse_jolt_apply_body_force", + FunctionDescriptor.of(JAVA_INT, + JAVA_LONG, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT)); + raycastClosest = downcall(symbols, + "impulse_jolt_raycast_closest", + FunctionDescriptor.of(JAVA_INT, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + ADDRESS, + ADDRESS)); + raycastAll = downcall(symbols, + "impulse_jolt_raycast_all", + FunctionDescriptor.of(JAVA_INT, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT, + ADDRESS, + ADDRESS)); + contacts = downcall(symbols, + "impulse_jolt_contacts", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_INT, ADDRESS, ADDRESS)); + contactCount = downcall(symbols, + "impulse_jolt_contact_count", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG)); + bodyCount = downcall(symbols, + "impulse_jolt_body_count", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG)); + jointCount = downcall(symbols, + "impulse_jolt_joint_count", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG)); + } + + @Nonnull + static PanamaJoltNativeLibrary open(@Nonnull SymbolLookup symbols) { + return new PanamaJoltNativeLibrary(symbols); + } + + @Override + public long createSpace() { + try { + return (long) createSpace.invokeExact(); + } catch (Throwable throwable) { + throw nativeFailure("create space", throwable); + } + } + + @Override + public void destroySpace(long spaceHandle) { + requireSuccess("destroy space", invokeStatus(destroySpace, spaceHandle)); + } + + @Override + public void step(long spaceHandle, float dt) { + requireSuccess("step space", invokeStatus(step, spaceHandle, dt)); + } + + @Override + public void setGravity(long spaceHandle, float x, float y, float z) { + requireSuccess("set gravity", invokeStatus(setGravity, spaceHandle, x, y, z)); + } + + @Override + public void getGravity(long spaceHandle, float[] out) { + if (out.length < GRAVITY_COMPONENTS) { + throw new IllegalArgumentException("Gravity output must have at least 3 entries"); + } + try (Arena arena = Arena.ofConfined()) { + MemorySegment output = arena.allocate(JAVA_FLOAT, GRAVITY_COMPONENTS); + requireSuccess("get gravity", invokeStatus(getGravity, spaceHandle, output)); + out[0] = output.getAtIndex(JAVA_FLOAT, 0); + out[1] = output.getAtIndex(JAVA_FLOAT, 1); + out[2] = output.getAtIndex(JAVA_FLOAT, 2); + } + } + + @Override + public long createBody(long spaceHandle, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + return invokeLong("create body", + createBody, + spaceHandle, + shapeTypeCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode, + groundY, + mass, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW); + } + + @Override + public void removeBody(long spaceHandle, long bodyHandle) { + requireSuccess("remove body", invokeStatus(removeBody, spaceHandle, bodyHandle)); + } + + @Override + public boolean containsBody(long spaceHandle, long bodyHandle) { + return invokeInt("contains body", containsBody, spaceHandle, bodyHandle) != 0; + } + + @Override + public boolean bodySnapshot(long spaceHandle, long bodyHandle, JoltBodySnapshot out) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment floats = arena.allocate(JAVA_FLOAT, JoltBodySnapshot.FLOAT_FIELD_COUNT); + MemorySegment ints = arena.allocate(JAVA_INT, JoltBodySnapshot.INT_FIELD_COUNT); + int status = invokeStatus(bodySnapshot, spaceHandle, bodyHandle, floats, ints); + if (status == 0) { + return false; + } + readSnapshot(floats, ints, out); + return true; + } + } + + @Override + public void setBodyTransform(long spaceHandle, + long bodyHandle, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + requireSuccess("set body transform", + invokeStatus("set body transform", + setBodyTransform, + spaceHandle, + bodyHandle, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW)); + } + + @Override + public void setBodyPosition(long spaceHandle, long bodyHandle, float x, float y, float z) { + requireSuccess("set body position", + invokeStatus("set body position", setBodyPosition, spaceHandle, bodyHandle, x, y, z)); + } + + @Override + public void setBodyVelocity(long spaceHandle, + long bodyHandle, + float linearX, + float linearY, + float linearZ, + float angularX, + float angularY, + float angularZ) { + requireSuccess("set body velocity", + invokeStatus("set body velocity", + setBodyVelocity, + spaceHandle, + bodyHandle, + linearX, + linearY, + linearZ, + angularX, + angularY, + angularZ)); + } + + @Override + public void setBodyType(long spaceHandle, long bodyHandle, int bodyTypeCode) { + requireSuccess("set body type", + invokeStatus(setBodyType, spaceHandle, bodyHandle, bodyTypeCode)); + } + + @Override + public void setBodyDamping(long spaceHandle, + long bodyHandle, + float linearDamping, + float angularDamping) { + requireSuccess("set body damping", + invokeStatus("set body damping", + setBodyDamping, + spaceHandle, + bodyHandle, + linearDamping, + angularDamping)); + } + + @Override + public void setBodyFriction(long spaceHandle, long bodyHandle, float friction) { + requireSuccess("set body friction", + invokeStatus("set body friction", setBodyFriction, spaceHandle, bodyHandle, friction)); + } + + @Override + public void setBodyRestitution(long spaceHandle, long bodyHandle, float restitution) { + requireSuccess("set body restitution", + invokeStatus("set body restitution", + setBodyRestitution, + spaceHandle, + bodyHandle, + restitution)); + } + + @Override + public void setBodyCollisionFilter(long spaceHandle, long bodyHandle, int group, int mask) { + requireSuccess("set body collision filter", + invokeStatus("set body collision filter", + setBodyCollisionFilter, + spaceHandle, + bodyHandle, + group, + mask)); + } + + @Override + public void setBodySensor(long spaceHandle, long bodyHandle, boolean sensor) { + requireSuccess("set body sensor", + invokeStatus(setBodySensor, spaceHandle, bodyHandle, sensor ? 1 : 0)); + } + + @Override + public void setBodyContinuousCollision(long spaceHandle, long bodyHandle, boolean enabled) { + requireSuccess("set body continuous collision", + invokeStatus(setBodyContinuousCollision, spaceHandle, bodyHandle, enabled ? 1 : 0)); + } + + @Override + public boolean isBodyContinuousCollisionEnabled(long spaceHandle, long bodyHandle) { + return invokeInt("is body continuous collision enabled", + isBodyContinuousCollisionEnabled, + spaceHandle, + bodyHandle) != 0; + } + + @Override + public void activateBody(long spaceHandle, long bodyHandle) { + requireSuccess("activate body", invokeStatus(activateBody, spaceHandle, bodyHandle)); + } + + @Override + public void sleepBody(long spaceHandle, long bodyHandle) { + requireSuccess("sleep body", invokeStatus(sleepBody, spaceHandle, bodyHandle)); + } + + @Override + public void applyBodyImpulse(long spaceHandle, + long bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + requireSuccess("apply body impulse", + invokeStatus("apply body impulse", + applyBodyImpulse, + spaceHandle, + bodyHandle, + x, + y, + z, + hasOffset ? 1 : 0, + offsetX, + offsetY, + offsetZ, + torque ? 1 : 0)); + } + + @Override + public void applyBodyForce(long spaceHandle, + long bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + requireSuccess("apply body force", + invokeStatus("apply body force", + applyBodyForce, + spaceHandle, + bodyHandle, + x, + y, + z, + hasOffset ? 1 : 0, + offsetX, + offsetY, + offsetZ, + torque ? 1 : 0)); + } + + @Override + public int raycastClosest(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + long[] bodyHandleOut, + float[] hitOut) { + if (bodyHandleOut.length < 1) { + throw new IllegalArgumentException("Closest ray body handle output must have at least 1 entry"); + } + if (hitOut.length < JoltNativeLibrary.RAY_HIT_FLOAT_COUNT) { + throw new IllegalArgumentException("Closest ray hit output must have at least 8 entries"); + } + try (Arena arena = Arena.ofConfined()) { + MemorySegment bodyHandles = arena.allocate(JAVA_LONG, 1); + MemorySegment hits = arena.allocate(JAVA_FLOAT, JoltNativeLibrary.RAY_HIT_FLOAT_COUNT); + int count = invokeInt("raycast closest", + raycastClosest, + spaceHandle, + fromX, + fromY, + fromZ, + toX, + toY, + toZ, + bodyHandles, + hits); + if (count <= 0) { + return 0; + } + bodyHandleOut[0] = bodyHandles.getAtIndex(JAVA_LONG, 0); + readFloats(hits, hitOut, JoltNativeLibrary.RAY_HIT_FLOAT_COUNT); + return count; + } + } + + @Override + public int raycastAll(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + int maxHits, + long[] bodyHandles, + float[] hits) { + if (maxHits <= 0) { + return 0; + } + if (bodyHandles.length < maxHits) { + throw new IllegalArgumentException("Raycast body handle output is smaller than maxHits"); + } + int floatCount = maxHits * JoltNativeLibrary.RAY_HIT_FLOAT_COUNT; + if (hits.length < floatCount) { + throw new IllegalArgumentException("Raycast hit output is smaller than maxHits"); + } + try (Arena arena = Arena.ofConfined()) { + MemorySegment nativeBodyHandles = arena.allocate(JAVA_LONG, maxHits); + MemorySegment nativeHits = arena.allocate(JAVA_FLOAT, floatCount); + int count = invokeInt("raycast all", + raycastAll, + spaceHandle, + fromX, + fromY, + fromZ, + toX, + toY, + toZ, + maxHits, + nativeBodyHandles, + nativeHits); + int boundedCount = Math.min(Math.max(count, 0), maxHits); + readLongs(nativeBodyHandles, bodyHandles, boundedCount); + readFloats(nativeHits, + hits, + boundedCount * JoltNativeLibrary.RAY_HIT_FLOAT_COUNT); + return count; + } + } + + @Override + public int contacts(long spaceHandle, int maxContacts, long[] bodyHandles, float[] contacts) { + if (maxContacts <= 0) { + return 0; + } + int bodyHandleCount = maxContacts * JoltNativeLibrary.CONTACT_BODY_HANDLE_COUNT; + if (bodyHandles.length < bodyHandleCount) { + throw new IllegalArgumentException("Contact body handle output is smaller than maxContacts"); + } + int floatCount = maxContacts * JoltNativeLibrary.CONTACT_FLOAT_COUNT; + if (contacts.length < floatCount) { + throw new IllegalArgumentException("Contact output is smaller than maxContacts"); + } + try (Arena arena = Arena.ofConfined()) { + MemorySegment nativeBodyHandles = arena.allocate(JAVA_LONG, bodyHandleCount); + MemorySegment nativeContacts = arena.allocate(JAVA_FLOAT, floatCount); + int count = invokeInt("contacts", + this.contacts, + spaceHandle, + maxContacts, + nativeBodyHandles, + nativeContacts); + int boundedCount = Math.min(Math.max(count, 0), maxContacts); + readLongs(nativeBodyHandles, + bodyHandles, + boundedCount * JoltNativeLibrary.CONTACT_BODY_HANDLE_COUNT); + readFloats(nativeContacts, + contacts, + boundedCount * JoltNativeLibrary.CONTACT_FLOAT_COUNT); + return count; + } + } + + @Override + public int contactCount(long spaceHandle) { + return invokeInt(contactCount, spaceHandle); + } + + @Override + public int bodyCount(long spaceHandle) { + return invokeInt(bodyCount, spaceHandle); + } + + @Override + public int jointCount(long spaceHandle) { + return invokeInt(jointCount, spaceHandle); + } + + @Nonnull + private static MethodHandle downcall(@Nonnull SymbolLookup symbols, + @Nonnull String symbol, + @Nonnull FunctionDescriptor descriptor) { + try { + return LINKER.downcallHandle(symbols.findOrThrow(symbol), descriptor); + } catch (NoSuchElementException exception) { + throw new IllegalStateException("Jolt native symbol is missing: " + symbol, exception); + } + } + + private static int invokeStatus(@Nonnull MethodHandle handle, long spaceHandle) { + try { + return (int) handle.invokeExact(spaceHandle); + } catch (Throwable throwable) { + throw nativeFailure("invoke native status function", throwable); + } + } + + private static int invokeStatus(@Nonnull MethodHandle handle, long spaceHandle, float value) { + try { + return (int) handle.invokeExact(spaceHandle, value); + } catch (Throwable throwable) { + throw nativeFailure("invoke native status function", throwable); + } + } + + private static int invokeStatus(@Nonnull MethodHandle handle, + long spaceHandle, + float x, + float y, + float z) { + try { + return (int) handle.invokeExact(spaceHandle, x, y, z); + } catch (Throwable throwable) { + throw nativeFailure("invoke native status function", throwable); + } + } + + private static int invokeStatus(@Nonnull MethodHandle handle, + long spaceHandle, + @Nonnull MemorySegment output) { + try { + return (int) handle.invokeExact(spaceHandle, output); + } catch (Throwable throwable) { + throw nativeFailure("invoke native status function", throwable); + } + } + + private static int invokeStatus(@Nonnull MethodHandle handle, + long spaceHandle, + long bodyHandle, + @Nonnull MemorySegment floats, + @Nonnull MemorySegment ints) { + try { + return (int) handle.invokeExact(spaceHandle, bodyHandle, floats, ints); + } catch (Throwable throwable) { + throw nativeFailure("invoke native status function", throwable); + } + } + + private static int invokeStatus(@Nonnull MethodHandle handle, long spaceHandle, long bodyHandle) { + try { + return (int) handle.invokeExact(spaceHandle, bodyHandle); + } catch (Throwable throwable) { + throw nativeFailure("invoke native status function", throwable); + } + } + + private static int invokeStatus(@Nonnull MethodHandle handle, + long spaceHandle, + long bodyHandle, + int value) { + try { + return (int) handle.invokeExact(spaceHandle, bodyHandle, value); + } catch (Throwable throwable) { + throw nativeFailure("invoke native status function", throwable); + } + } + + private static int invokeStatus(@Nonnull String operation, + @Nonnull MethodHandle handle, + Object... args) { + try { + return (int) handle.invokeWithArguments(args); + } catch (Throwable throwable) { + throw nativeFailure(operation, throwable); + } + } + + private static int invokeInt(@Nonnull MethodHandle handle, long spaceHandle) { + try { + return (int) handle.invokeExact(spaceHandle); + } catch (Throwable throwable) { + throw nativeFailure("invoke native int function", throwable); + } + } + + private static int invokeInt(@Nonnull String operation, + @Nonnull MethodHandle handle, + Object... args) { + try { + return (int) handle.invokeWithArguments(args); + } catch (Throwable throwable) { + throw nativeFailure(operation, throwable); + } + } + + private static long invokeLong(@Nonnull String operation, + @Nonnull MethodHandle handle, + Object... args) { + try { + return (long) handle.invokeWithArguments(args); + } catch (Throwable throwable) { + throw nativeFailure(operation, throwable); + } + } + + private static void readSnapshot(@Nonnull MemorySegment floats, + @Nonnull MemorySegment ints, + @Nonnull JoltBodySnapshot out) { + out.positionX = floats.getAtIndex(JAVA_FLOAT, 0); + out.positionY = floats.getAtIndex(JAVA_FLOAT, 1); + out.positionZ = floats.getAtIndex(JAVA_FLOAT, 2); + out.rotationX = floats.getAtIndex(JAVA_FLOAT, 3); + out.rotationY = floats.getAtIndex(JAVA_FLOAT, 4); + out.rotationZ = floats.getAtIndex(JAVA_FLOAT, 5); + out.rotationW = floats.getAtIndex(JAVA_FLOAT, 6); + out.linearVelocityX = floats.getAtIndex(JAVA_FLOAT, 7); + out.linearVelocityY = floats.getAtIndex(JAVA_FLOAT, 8); + out.linearVelocityZ = floats.getAtIndex(JAVA_FLOAT, 9); + out.angularVelocityX = floats.getAtIndex(JAVA_FLOAT, 10); + out.angularVelocityY = floats.getAtIndex(JAVA_FLOAT, 11); + out.angularVelocityZ = floats.getAtIndex(JAVA_FLOAT, 12); + out.mass = floats.getAtIndex(JAVA_FLOAT, 13); + out.friction = floats.getAtIndex(JAVA_FLOAT, 14); + out.restitution = floats.getAtIndex(JAVA_FLOAT, 15); + out.linearDamping = floats.getAtIndex(JAVA_FLOAT, 16); + out.angularDamping = floats.getAtIndex(JAVA_FLOAT, 17); + out.centerOfMassOffsetY = floats.getAtIndex(JAVA_FLOAT, 18); + out.halfExtentX = floats.getAtIndex(JAVA_FLOAT, 19); + out.halfExtentY = floats.getAtIndex(JAVA_FLOAT, 20); + out.halfExtentZ = floats.getAtIndex(JAVA_FLOAT, 21); + out.radius = floats.getAtIndex(JAVA_FLOAT, 22); + out.halfHeight = floats.getAtIndex(JAVA_FLOAT, 23); + out.shapeTypeCode = ints.getAtIndex(JAVA_INT, 0); + out.bodyTypeCode = ints.getAtIndex(JAVA_INT, 1); + out.sleeping = ints.getAtIndex(JAVA_INT, 2) != 0; + out.sensor = ints.getAtIndex(JAVA_INT, 3) != 0; + out.collisionGroup = ints.getAtIndex(JAVA_INT, 4); + out.collisionMask = ints.getAtIndex(JAVA_INT, 5); + out.continuousCollisionEnabled = ints.getAtIndex(JAVA_INT, 6) != 0; + out.hasBoxHalfExtents = ints.getAtIndex(JAVA_INT, 7) != 0; + out.axisCode = ints.getAtIndex(JAVA_INT, 8); + } + + private static void readLongs(@Nonnull MemorySegment source, long[] target, int count) { + for (int index = 0; index < count; index++) { + target[index] = source.getAtIndex(JAVA_LONG, index); + } + } + + private static void readFloats(@Nonnull MemorySegment source, float[] target, int count) { + for (int index = 0; index < count; index++) { + target[index] = source.getAtIndex(JAVA_FLOAT, index); + } + } + + private static void requireSuccess(@Nonnull String operation, int status) { + if (status == 0) { + throw new IllegalStateException("Jolt native operation failed: " + operation); + } + } + + @Nonnull + private static IllegalStateException nativeFailure(@Nonnull String operation, + @Nonnull Throwable throwable) { + return new IllegalStateException("Failed to " + operation + " through Jolt native library", + throwable); + } +} diff --git a/impulse-jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider b/impulse-jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider new file mode 100644 index 00000000..e8fb8222 --- /dev/null +++ b/impulse-jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider @@ -0,0 +1 @@ +dev.hytalemodding.impulse.jolt.JoltBackendRuntimeProvider diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java new file mode 100644 index 00000000..0f2cde1e --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java @@ -0,0 +1,280 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.capability.PhysicsActivationTuning; +import dev.hytalemodding.impulse.api.capability.PhysicsCapabilityId; +import dev.hytalemodding.impulse.api.capability.PhysicsSolverTuning; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import org.junit.jupiter.api.Test; + +class JoltBackendRuntimeContractTest { + + @Test + void validCreateBodyCodesReachDeferredBodyImplementation() { + JoltBackendRuntime runtime = runtimeWithSpace(1); + + int[] shapes = { + BackendRuntimeCodes.SHAPE_BOX, + BackendRuntimeCodes.SHAPE_SPHERE, + BackendRuntimeCodes.SHAPE_CAPSULE, + BackendRuntimeCodes.SHAPE_CYLINDER, + BackendRuntimeCodes.SHAPE_CONE, + BackendRuntimeCodes.SHAPE_PLANE + }; + int[] axes = { + BackendRuntimeCodes.AXIS_X, + BackendRuntimeCodes.AXIS_Y, + BackendRuntimeCodes.AXIS_Z + }; + int[] bodyTypes = { + BackendRuntimeCodes.BODY_STATIC, + BackendRuntimeCodes.BODY_DYNAMIC, + BackendRuntimeCodes.BODY_KINEMATIC + }; + + for (int shape : shapes) { + for (int axis : axes) { + for (int bodyType : bodyTypes) { + assertThrows(UnsupportedOperationException.class, + () -> createBody(runtime, 1, shape, axis, bodyType)); + } + } + } + } + + @Test + void invalidCreateBodyCodesFailBeforeDeferredBodyImplementation() { + JoltBackendRuntime runtime = runtimeWithSpace(2); + + assertThrows(IllegalArgumentException.class, + () -> createBody(runtime, 2, 999, BackendRuntimeCodes.AXIS_Y, BackendRuntimeCodes.BODY_DYNAMIC)); + assertThrows(IllegalArgumentException.class, + () -> createBody(runtime, 2, BackendRuntimeCodes.SHAPE_BOX, 999, BackendRuntimeCodes.BODY_DYNAMIC)); + assertThrows(IllegalArgumentException.class, + () -> createBody(runtime, 2, BackendRuntimeCodes.SHAPE_BOX, BackendRuntimeCodes.AXIS_Y, 999)); + } + + @Test + void unsupportedCreateBodyShapesFailAsContractInputErrors() { + JoltBackendRuntime runtime = runtimeWithSpace(3); + + assertThrows(IllegalArgumentException.class, + () -> createBody(runtime, + 3, + BackendRuntimeCodes.SHAPE_UNKNOWN, + BackendRuntimeCodes.AXIS_Y, + BackendRuntimeCodes.BODY_DYNAMIC)); + assertThrows(IllegalArgumentException.class, + () -> createBody(runtime, + 3, + BackendRuntimeCodes.SHAPE_VOXELS, + BackendRuntimeCodes.AXIS_Y, + BackendRuntimeCodes.BODY_STATIC)); + } + + @Test + void validCreateJointCodesReachDeferredJointImplementation() { + JoltBackendRuntime runtime = runtimeWithSpace(4); + + int[] jointTypes = { + BackendRuntimeCodes.JOINT_FIXED, + BackendRuntimeCodes.JOINT_POINT, + BackendRuntimeCodes.JOINT_HINGE, + BackendRuntimeCodes.JOINT_SLIDER, + BackendRuntimeCodes.JOINT_SPRING + }; + + for (int jointType : jointTypes) { + assertThrows(UnsupportedOperationException.class, + () -> createJoint(runtime, 4, jointType)); + } + } + + @Test + void invalidCreateJointCodeFailsBeforeDeferredJointImplementation() { + JoltBackendRuntime runtime = runtimeWithSpace(5); + + assertThrows(IllegalArgumentException.class, () -> createJoint(runtime, 5, 999)); + } + + @Test + void unsupportedCapabilityProbesReturnFalseAndSettingsMutationsNoop() { + JoltBackendRuntime runtime = runtimeWithSpace(6); + + assertFalse(runtime.supportsVoxelTerrain(6)); + assertTrue(runtime.supportsContinuousCollision(6)); + assertFalse(runtime.supportsSolverTuning(6)); + assertFalse(runtime.supportsActivationTuning(6)); + + assertDoesNotThrow(() -> runtime.applySolverTuning(6, new PhysicsSolverTuning(8, 2))); + assertDoesNotThrow(() -> runtime.applyActivationTuning(6, + new PhysicsActivationTuning(0.05f, 0.1f, 0.5f))); + assertDoesNotThrow(() -> runtime.applyExtensionSettings(6, + new PhysicsCapabilityId("impulse:test"), + consumer -> { + consumer.accept("key", "value"); + })); + } + + @Test + void unimplementedEmptyQueriesReturnEmptyResults() { + JoltBackendRuntime runtime = runtimeWithSpace(7); + + assertFalse(runtime.raycastClosest(7, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + -1.0f, + 0.0f, + (_, _, _, _, _, _, _, _, _) -> { + })); + assertEquals(0, + runtime.raycastAll(7, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + -1.0f, + 0.0f, + (_, _, _, _, _, _, _, _, _) -> { + })); + assertEquals(0, runtime.contacts(7, (_, _, _, _, _, _, _, _, _, _, _, _, _) -> { + })); + assertEquals(0, runtime.contactCount(7)); + } + + private static JoltBackendRuntime runtimeWithSpace(int spaceId) { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new NoopNativeLibrary()); + runtime.createSpace(new SpaceId(spaceId)); + return runtime; + } + + private static long createBody(JoltBackendRuntime runtime, + int spaceId, + int shape, + int axis, + int bodyType) { + return runtime.createBody(spaceId, + shape, + 0.5f, + 0.5f, + 0.5f, + 0.25f, + 0.5f, + axis, + 0.0f, + 1.0f, + bodyType, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static long createJoint(JoltBackendRuntime runtime, int spaceId, int jointType) { + return runtime.createJoint(spaceId, + jointType, + 1L, + 2L, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 1.0f, + 10.0f, + 0.5f, + -1.0f, + 1.0f, + false, + 0.0f, + 0.0f); + } + + private static final class NoopNativeLibrary implements JoltNativeLibrary { + + private long nextHandle = 1L; + + @Override + public long createSpace() { + return nextHandle++; + } + + @Override + public void destroySpace(long spaceHandle) { + } + + @Override + public void step(long spaceHandle, float dt) { + } + + @Override + public void setGravity(long spaceHandle, float x, float y, float z) { + } + + @Override + public void getGravity(long spaceHandle, float[] out) { + } + + @Override + public int bodyCount(long spaceHandle) { + return 0; + } + + @Override + public int raycastClosest(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + long[] bodyHandleOut, + float[] hitOut) { + return 0; + } + + @Override + public int raycastAll(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + int maxHits, + long[] bodyHandles, + float[] hits) { + return 0; + } + + @Override + public int contacts(long spaceHandle, int maxContacts, long[] bodyHandles, float[] contacts) { + return 0; + } + + @Override + public int contactCount(long spaceHandle) { + return 0; + } + + @Override + public int jointCount(long spaceHandle) { + return 0; + } + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java new file mode 100644 index 00000000..82f9f9fc --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java @@ -0,0 +1,18 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import dev.hytalemodding.impulse.api.BackendId; +import org.junit.jupiter.api.Test; + +class JoltBackendRuntimeProviderTest { + + @Test + void providerExposesJoltBackendIdAndCreatesRuntime() { + JoltBackendRuntimeProvider provider = new JoltBackendRuntimeProvider(); + + assertEquals(new BackendId("impulse:jolt"), provider.getId()); + assertNotNull(provider.createRuntime()); + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java new file mode 100644 index 00000000..344c8eb7 --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java @@ -0,0 +1,83 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import org.junit.jupiter.api.Test; + +class JoltBodyLifecycleTest { + + @Test + void createBodyReturnsStableJavaBodyIdAndStoresNativeHandle() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(11)); + + long bodyA = createBox(runtime, spaceId, 1.0f); + long bodyB = createBox(runtime, spaceId, 2.0f); + long spaceHandle = nativeLibrary.firstSpaceHandle(); + long nativeBodyA = nativeLibrary.nativeBodyHandle(spaceHandle, 0); + long nativeBodyB = nativeLibrary.nativeBodyHandle(spaceHandle, 1); + + assertEquals(1L, bodyA); + assertEquals(2L, bodyB); + assertNotEquals(nativeBodyA, bodyA); + assertNotEquals(nativeBodyB, bodyB); + assertEquals(2, runtime.bodyCount(spaceId)); + assertTrue(runtime.containsBody(spaceId, bodyA)); + assertTrue(runtime.containsBody(spaceId, bodyB)); + } + + @Test + void removeBodyDestroysNativeHandleAndMakesJavaIdStale() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(12)); + long bodyId = createBox(runtime, spaceId, 1.0f); + long nativeBodyHandle = nativeLibrary.nativeBodyHandle(nativeLibrary.firstSpaceHandle(), 0); + + runtime.removeBody(spaceId, bodyId); + + assertEquals(nativeBodyHandle, nativeLibrary.lastRemovedBodyHandle()); + assertFalse(runtime.containsBody(spaceId, bodyId)); + assertEquals(0, runtime.bodyCount(spaceId)); + assertFalse(runtime.bodySnapshot(spaceId, + bodyId, + new JoltTestNativeLibrary.CapturedBodySnapshot())); + } + + @Test + void unknownBodyMutationFailsBeforeNativeCall() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(13)); + + assertThrows(IllegalArgumentException.class, + () -> runtime.setBodyPosition(spaceId, 404L, 1.0f, 2.0f, 3.0f)); + } + + static long createBox(JoltBackendRuntime runtime, int spaceId, float positionY) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.75f, + 1.25f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 2.0f, + BackendRuntimeCodes.BODY_DYNAMIC, + 1.0f, + positionY, + 3.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java new file mode 100644 index 00000000..5955c00f --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java @@ -0,0 +1,85 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class JoltBodySnapshotTest { + + @Test + void bodySnapshotReportsNativeBodyStateWithJavaBodyId() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(21)); + long bodyId = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + + runtime.setBodyTransform(spaceId, bodyId, 2.0f, 3.0f, 4.0f, 0.1f, 0.2f, 0.3f, 0.9f); + runtime.setBodyVelocity(spaceId, bodyId, 5.0f, 6.0f, 7.0f, 0.5f, 0.6f, 0.7f); + runtime.setBodyType(spaceId, bodyId, BackendRuntimeCodes.BODY_KINEMATIC); + runtime.sleepBody(spaceId, bodyId); + + JoltTestNativeLibrary.CapturedBodySnapshot snapshot = + new JoltTestNativeLibrary.CapturedBodySnapshot(); + boolean present = runtime.bodySnapshot(spaceId, bodyId, snapshot); + + assertTrue(present); + assertEquals(bodyId, snapshot.bodyId); + assertEquals(BackendRuntimeCodes.SHAPE_BOX, snapshot.shapeTypeCode); + assertEquals(BackendRuntimeCodes.BODY_KINEMATIC, snapshot.bodyTypeCode); + assertEquals(2.0f, snapshot.positionX); + assertEquals(3.0f, snapshot.positionY); + assertEquals(4.0f, snapshot.positionZ); + assertEquals(0.1f, snapshot.rotationX); + assertEquals(0.2f, snapshot.rotationY); + assertEquals(0.3f, snapshot.rotationZ); + assertEquals(0.9f, snapshot.rotationW); + assertEquals(5.0f, snapshot.linearVelocityX); + assertEquals(6.0f, snapshot.linearVelocityY); + assertEquals(7.0f, snapshot.linearVelocityZ); + assertEquals(0.5f, snapshot.angularVelocityX); + assertEquals(0.6f, snapshot.angularVelocityY); + assertEquals(0.7f, snapshot.angularVelocityZ); + assertTrue(snapshot.sleeping); + assertEquals(2.0f, snapshot.mass); + assertTrue(snapshot.hasBoxHalfExtents); + assertEquals(0.5f, snapshot.halfExtentX); + assertEquals(0.75f, snapshot.halfExtentY); + assertEquals(1.25f, snapshot.halfExtentZ); + assertEquals(BackendRuntimeCodes.AXIS_Y, snapshot.axisCode); + } + + @Test + void snapshotBodiesEmitsOnlyKnownRequestedBodies() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(22)); + long bodyA = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyB = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + List emitted = new ArrayList<>(); + + runtime.snapshotBodies(spaceId, + consumer -> { + consumer.accept(bodyA); + consumer.accept(404L); + consumer.accept(bodyB); + }, + (bodyId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) -> + emitted.add(bodyId)); + + assertEquals(List.of(bodyA, bodyB), emitted); + } + + @Test + void missingBodySnapshotReturnsFalse() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(23)); + + assertFalse(runtime.bodySnapshot(spaceId, + 404L, + new JoltTestNativeLibrary.CapturedBodySnapshot())); + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java new file mode 100644 index 00000000..c86c2c99 --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java @@ -0,0 +1,44 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import org.junit.jupiter.api.Test; + +class JoltMaterialAndFilterTest { + + @Test + void materialFilterSensorAndActivationRoundTripThroughSnapshot() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(31)); + long bodyId = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + + runtime.setBodyDamping(spaceId, bodyId, 0.2f, 0.3f); + runtime.setBodyFriction(spaceId, bodyId, 0.65f); + runtime.setBodyRestitution(spaceId, bodyId, 0.15f); + runtime.setBodyCollisionFilter(spaceId, bodyId, 2, 3); + runtime.setBodySensor(spaceId, bodyId, true); + runtime.setBodyContinuousCollision(spaceId, bodyId, true); + runtime.sleepBody(spaceId, bodyId); + runtime.activateBody(spaceId, bodyId); + runtime.applyBodyImpulse(spaceId, bodyId, 1.0f, 0.0f, 0.0f, false, 0.0f, 0.0f, 0.0f, false); + runtime.applyBodyForce(spaceId, bodyId, 0.0f, 1.0f, 0.0f, false, 0.0f, 0.0f, 0.0f, false); + + JoltTestNativeLibrary.CapturedBodySnapshot snapshot = + new JoltTestNativeLibrary.CapturedBodySnapshot(); + runtime.bodySnapshot(spaceId, bodyId, snapshot); + + assertEquals(0.2f, snapshot.linearDamping); + assertEquals(0.3f, snapshot.angularDamping); + assertEquals(0.65f, snapshot.friction); + assertEquals(0.15f, snapshot.restitution); + assertEquals(2, snapshot.collisionGroup); + assertEquals(3, snapshot.collisionMask); + assertTrue(snapshot.sensor); + assertTrue(snapshot.continuousCollisionEnabled); + assertTrue(runtime.isBodyContinuousCollisionEnabled(spaceId, bodyId)); + assertFalse(snapshot.sleeping); + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java new file mode 100644 index 00000000..ed4f731a --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java @@ -0,0 +1,74 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import org.junit.jupiter.api.Test; + +class JoltNativeAbiIntegrationTest { + + @Test + void nativeLibraryRunsSpaceAndBodyAbi() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(41)); + runtime.setGravity(spaceId, 0.0f, -10.0f, 0.0f); + + long bodyId = runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_SPHERE, + 0.0f, + 0.0f, + 0.0f, + 0.5f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 2.0f, + BackendRuntimeCodes.BODY_DYNAMIC, + 0.0f, + 10.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + + runtime.setBodyFriction(spaceId, bodyId, 0.4f); + runtime.setBodyRestitution(spaceId, bodyId, 0.2f); + runtime.setBodyCollisionFilter(spaceId, bodyId, 3, 7); + runtime.setBodySensor(spaceId, bodyId, true); + runtime.setBodyVelocity(spaceId, bodyId, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + runtime.step(spaceId, 0.1f); + + JoltTestNativeLibrary.CapturedBodySnapshot snapshot = + new JoltTestNativeLibrary.CapturedBodySnapshot(); + assertTrue(runtime.bodySnapshot(spaceId, bodyId, snapshot)); + assertEquals(bodyId, snapshot.bodyId); + assertEquals(BackendRuntimeCodes.SHAPE_SPHERE, snapshot.shapeTypeCode); + assertEquals(BackendRuntimeCodes.BODY_DYNAMIC, snapshot.bodyTypeCode); + assertEquals(0.5f, snapshot.radius); + assertEquals(0.4f, snapshot.friction); + assertEquals(0.2f, snapshot.restitution); + assertEquals(3, snapshot.collisionGroup); + assertEquals(7, snapshot.collisionMask); + assertTrue(snapshot.sensor); + assertTrue(snapshot.positionY < 10.0f); + assertTrue(snapshot.linearVelocityY < 0.0f); + assertEquals(1, runtime.bodyCount(spaceId)); + + runtime.removeBody(spaceId, bodyId); + + assertFalse(runtime.containsBody(spaceId, bodyId)); + assertEquals(0, runtime.bodyCount(spaceId)); + assertFalse(runtime.bodySnapshot(spaceId, + bodyId, + new JoltTestNativeLibrary.CapturedBodySnapshot())); + + runtime.destroySpace(spaceId); + + assertThrows(IllegalArgumentException.class, () -> runtime.bodyCount(spaceId)); + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java new file mode 100644 index 00000000..b3779067 --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java @@ -0,0 +1,67 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import org.junit.jupiter.api.Test; + +class JoltNativePhysicsIntegrationTest { + + @Test + void dynamicBoxRestsOnStaticBoxFloor() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(51)); + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 20.0f, + 0.5f, + 20.0f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 0.0f, + BackendRuntimeCodes.BODY_STATIC, + 0.0f, + -0.5f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + long boxId = runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.BODY_DYNAMIC, + 0.0f, + 4.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + + for (int step = 0; step < 300; step++) { + runtime.step(spaceId, 1.0f / 60.0f); + } + + JoltTestNativeLibrary.CapturedBodySnapshot snapshot = + new JoltTestNativeLibrary.CapturedBodySnapshot(); + assertTrue(runtime.bodySnapshot(spaceId, boxId, snapshot)); + assertTrue(snapshot.positionY > 0.45f, + "dynamic box should rest on the static floor, y=" + snapshot.positionY); + assertTrue(snapshot.positionY < 0.75f, + "dynamic box should stay close to its resting height, y=" + snapshot.positionY); + + runtime.destroySpace(spaceId); + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java new file mode 100644 index 00000000..3e458e6d --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java @@ -0,0 +1,222 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendContactSink; +import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class JoltNativeQueryIntegrationTest { + + @Test + void raycastClosestAndAllReturnBodyIdsInDistanceOrder() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(63)); + long nearBody = createStaticBox(runtime, spaceId, 0.0f, 1.0f, -1.0f); + long farBody = createStaticBox(runtime, spaceId, 0.0f, 1.0f, 2.0f); + RayHitRecorder closest = new RayHitRecorder(); + RayHitRecorder all = new RayHitRecorder(); + + boolean hasHit = runtime.raycastClosest(spaceId, + 0.0f, + 1.0f, + -5.0f, + 0.0f, + 1.0f, + 5.0f, + closest); + int hitCount = runtime.raycastAll(spaceId, + 0.0f, + 1.0f, + -5.0f, + 0.0f, + 1.0f, + 5.0f, + all); + + assertTrue(hasHit); + assertEquals(nearBody, closest.firstBodyId()); + assertTrue(closest.firstDistance() > 0.0f); + assertEquals(2, hitCount); + assertEquals(List.of(nearBody, farBody), all.bodyIds()); + assertTrue(all.distances().get(0) < all.distances().get(1)); + + runtime.destroySpace(spaceId); + } + + @Test + void contactsExposeCurrentBodyPairAndRespectLimit() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(64)); + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + long floor = createStaticBox(runtime, spaceId, 0.0f, -0.5f, 0.0f, 20.0f, 0.5f, 20.0f); + long box = createDynamicBox(runtime, spaceId, 0.0f, 3.0f, 0.0f); + + for (int step = 0; step < 240 && runtime.contactCount(spaceId) == 0; step++) { + runtime.step(spaceId, 1.0f / 60.0f); + } + + ContactRecorder all = new ContactRecorder(); + int contactCount = runtime.contactCount(spaceId); + int emitted = runtime.contacts(spaceId, all); + ContactRecorder bounded = new ContactRecorder(); + int boundedEmitted = runtime.contacts(spaceId, 1, bounded); + + assertTrue(contactCount > 0); + assertEquals(contactCount, emitted); + assertTrue(all.containsPair(floor, box)); + assertEquals(1, boundedEmitted); + assertEquals(1, bounded.count()); + + runtime.destroySpace(spaceId); + } + + private static long createStaticBox(JoltBackendRuntime runtime, + int spaceId, + float positionX, + float positionY, + float positionZ) { + return createStaticBox(runtime, + spaceId, + positionX, + positionY, + positionZ, + 0.5f, + 0.5f, + 0.5f); + } + + private static long createStaticBox(JoltBackendRuntime runtime, + int spaceId, + float positionX, + float positionY, + float positionZ, + float halfExtentX, + float halfExtentY, + float halfExtentZ) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + halfExtentX, + halfExtentY, + halfExtentZ, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 0.0f, + BackendRuntimeCodes.BODY_STATIC, + positionX, + positionY, + positionZ, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static long createDynamicBox(JoltBackendRuntime runtime, + int spaceId, + float positionX, + float positionY, + float positionZ) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.BODY_DYNAMIC, + positionX, + positionY, + positionZ, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static final class RayHitRecorder implements BackendRayHitSink { + + private final List bodyIds = new ArrayList<>(); + private final List distances = new ArrayList<>(); + + @Override + public void accept(long bodyId, + float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float fraction, + float distance) { + bodyIds.add(bodyId); + distances.add(distance); + } + + private long firstBodyId() { + return bodyIds.getFirst(); + } + + private float firstDistance() { + return distances.getFirst(); + } + + private List bodyIds() { + return bodyIds; + } + + private List distances() { + return distances; + } + } + + private static final class ContactRecorder implements BackendContactSink { + + private final List bodyAIds = new ArrayList<>(); + private final List bodyBIds = new ArrayList<>(); + + @Override + public void accept(long bodyAId, + long bodyBId, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + bodyAIds.add(bodyAId); + bodyBIds.add(bodyBId); + } + + private boolean containsPair(long bodyAId, long bodyBId) { + for (int index = 0; index < bodyAIds.size(); index++) { + long actualA = bodyAIds.get(index); + long actualB = bodyBIds.get(index); + if ((actualA == bodyAId && actualB == bodyBId) + || (actualA == bodyBId && actualB == bodyAId)) { + return true; + } + } + return false; + } + + private int count() { + return bodyAIds.size(); + } + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java new file mode 100644 index 00000000..3c74d6cb --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java @@ -0,0 +1,155 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendContactSink; +import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class JoltQueryMappingTest { + + @Test + void raycastsMapNativeBodyHandlesBackToRuntimeBodyIds() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(61)); + long bodyAId = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyBId = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + long spaceHandle = nativeLibrary.firstSpaceHandle(); + long bodyAHandle = nativeLibrary.nativeBodyHandle(spaceHandle, 0); + long bodyBHandle = nativeLibrary.nativeBodyHandle(spaceHandle, 1); + nativeLibrary.addRayHit(spaceHandle, bodyAHandle, 0.0f, 1.0f, -1.0f, 0.25f, 4.0f); + nativeLibrary.addRayHit(spaceHandle, bodyBHandle, 0.0f, 1.0f, 2.0f, 0.55f, 7.0f); + RayHitRecorder closest = new RayHitRecorder(); + RayHitRecorder all = new RayHitRecorder(); + + boolean hit = runtime.raycastClosest(spaceId, + 0.0f, + 1.0f, + -5.0f, + 0.0f, + 1.0f, + 5.0f, + closest); + int hitCount = runtime.raycastAll(spaceId, + 0.0f, + 1.0f, + -5.0f, + 0.0f, + 1.0f, + 5.0f, + all); + + assertTrue(hit); + assertEquals(bodyAId, closest.firstBodyId()); + assertEquals(0.25f, closest.firstFraction()); + assertEquals(2, hitCount); + assertEquals(List.of(bodyAId, bodyBId), all.bodyIds()); + } + + @Test + void contactsMapNativeBodyHandlesAndRespectLimit() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(62)); + long bodyAId = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyBId = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + long spaceHandle = nativeLibrary.firstSpaceHandle(); + long bodyAHandle = nativeLibrary.nativeBodyHandle(spaceHandle, 0); + long bodyBHandle = nativeLibrary.nativeBodyHandle(spaceHandle, 1); + nativeLibrary.addContact(spaceHandle, bodyAHandle, bodyBHandle, 0.5f, -0.1f); + nativeLibrary.addContact(spaceHandle, bodyBHandle, bodyAHandle, 0.6f, -0.2f); + ContactRecorder contacts = new ContactRecorder(); + + int emitted = runtime.contacts(spaceId, 1, contacts); + + assertEquals(2, runtime.contactCount(spaceId)); + assertEquals(1, emitted); + assertEquals(1, contacts.count()); + assertEquals(bodyAId, contacts.firstBodyAId()); + assertEquals(bodyBId, contacts.firstBodyBId()); + assertEquals(-0.1f, contacts.firstDistance()); + } + + private static final class RayHitRecorder implements BackendRayHitSink { + + private final List bodyIds = new ArrayList<>(); + private final List fractions = new ArrayList<>(); + + @Override + public void accept(long bodyId, + float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float fraction, + float distance) { + bodyIds.add(bodyId); + fractions.add(fraction); + } + + private long firstBodyId() { + return bodyIds.getFirst(); + } + + private float firstFraction() { + return fractions.getFirst(); + } + + private List bodyIds() { + return bodyIds; + } + } + + private static final class ContactRecorder implements BackendContactSink { + + private long firstBodyAId; + private long firstBodyBId; + private float firstDistance; + private int count; + + @Override + public void accept(long bodyAId, + long bodyBId, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + if (count == 0) { + firstBodyAId = bodyAId; + firstBodyBId = bodyBId; + firstDistance = distance; + } + count++; + } + + private long firstBodyAId() { + return firstBodyAId; + } + + private long firstBodyBId() { + return firstBodyBId; + } + + private float firstDistance() { + return firstDistance; + } + + private int count() { + return count; + } + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java new file mode 100644 index 00000000..eef60dd3 --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java @@ -0,0 +1,220 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import dev.hytalemodding.impulse.api.SpaceId; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class JoltSpaceLifecycleTest { + + @Test + void createSpaceUsesRequestedIdAndNativeHandle() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + + int spaceId = runtime.createSpace(new SpaceId(42)); + + assertEquals(42, spaceId); + assertEquals(1, nativeLibrary.createCalls); + assertEquals(1L, nativeLibrary.handleFor(42)); + assertEquals(0, runtime.bodyCount(42)); + assertEquals(0, runtime.jointCount(42)); + } + + @Test + void gravityRoundTripUsesNativeSpaceHandle() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + runtime.createSpace(new SpaceId(7)); + + runtime.setGravity(7, 0.0f, -4.0f, 1.0f); + float[] gravity = new float[3]; + runtime.getGravity(7, (x, y, z) -> { + gravity[0] = x; + gravity[1] = y; + gravity[2] = z; + }); + + assertArrayEquals(new float[] {0.0f, -4.0f, 1.0f}, gravity); + } + + @Test + void nonPositiveStepDoesNotCallNativeStep() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + runtime.createSpace(new SpaceId(3)); + + runtime.step(3, 0.0f); + runtime.step(3, -1.0f); + + assertEquals(0, nativeLibrary.stepCalls); + } + + @Test + void nonPositiveStepStillRequiresKnownSpace() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + + assertThrows(IllegalArgumentException.class, () -> runtime.step(404, 0.0f)); + assertEquals(0, nativeLibrary.stepCalls); + } + + @Test + void positiveStepCallsNativeStepWithHandle() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + runtime.createSpace(new SpaceId(4)); + + runtime.step(4, 1.0f / 20.0f); + + assertEquals(1, nativeLibrary.stepCalls); + assertEquals(1L, nativeLibrary.lastStepHandle); + assertEquals(1.0f / 20.0f, nativeLibrary.lastStepDt); + } + + @Test + void destroySpaceReleasesNativeHandleAndRejectsFurtherAccess() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + runtime.createSpace(new SpaceId(9)); + + runtime.destroySpace(9); + + assertEquals(1, nativeLibrary.destroyCalls); + assertEquals(1L, nativeLibrary.destroyedHandle); + assertThrows(IllegalArgumentException.class, () -> runtime.bodyCount(9)); + } + + @Test + void closeReleasesEveryRegisteredNativeSpace() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + runtime.createSpace(new SpaceId(10)); + runtime.createSpace(new SpaceId(11)); + + runtime.close(); + + assertEquals(2, nativeLibrary.destroyCalls); + assertThrows(IllegalArgumentException.class, () -> runtime.bodyCount(10)); + assertThrows(IllegalArgumentException.class, () -> runtime.bodyCount(11)); + } + + @Test + void runtimeStatsReportsNativeCounts() { + InMemoryNativeLibrary nativeLibrary = new InMemoryNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + runtime.createSpace(new SpaceId(6)); + nativeLibrary.space(1L).bodyCount = 12; + nativeLibrary.space(1L).jointCount = 5; + + int[] counts = new int[2]; + boolean[] available = new boolean[1]; + runtime.runtimeStats(6, (bodyCount, + _, + _, + _, + _, + _, + _, + _, + _, + jointCount, + statsAvailable) -> { + counts[0] = bodyCount; + counts[1] = jointCount; + available[0] = statsAvailable; + }); + + assertArrayEquals(new int[] {12, 5}, counts); + assertEquals(true, available[0]); + } + + private static final class InMemoryNativeLibrary implements JoltNativeLibrary { + + private final Map spaces = new HashMap<>(); + private long nextHandle = 1L; + private int createCalls; + private int destroyCalls; + private long destroyedHandle; + private int stepCalls; + private long lastStepHandle; + private float lastStepDt; + + @Override + public long createSpace() { + long handle = nextHandle++; + spaces.put(handle, new SpaceState()); + createCalls++; + return handle; + } + + @Override + public void destroySpace(long spaceHandle) { + spaces.remove(spaceHandle); + destroyedHandle = spaceHandle; + destroyCalls++; + } + + @Override + public void step(long spaceHandle, float dt) { + requireSpace(spaceHandle); + lastStepHandle = spaceHandle; + lastStepDt = dt; + stepCalls++; + } + + @Override + public void setGravity(long spaceHandle, float x, float y, float z) { + SpaceState space = requireSpace(spaceHandle); + space.gravity[0] = x; + space.gravity[1] = y; + space.gravity[2] = z; + } + + @Override + public void getGravity(long spaceHandle, float[] out) { + SpaceState space = requireSpace(spaceHandle); + out[0] = space.gravity[0]; + out[1] = space.gravity[1]; + out[2] = space.gravity[2]; + } + + @Override + public int bodyCount(long spaceHandle) { + return requireSpace(spaceHandle).bodyCount; + } + + @Override + public int jointCount(long spaceHandle) { + return requireSpace(spaceHandle).jointCount; + } + + private long handleFor(int spaceId) { + assertEquals(42, spaceId); + return spaces.keySet().iterator().next(); + } + + private SpaceState space(long handle) { + return requireSpace(handle); + } + + private SpaceState requireSpace(long spaceHandle) { + SpaceState space = spaces.get(spaceHandle); + if (space == null) { + throw new IllegalArgumentException("Unknown test native space handle: " + spaceHandle); + } + return space; + } + } + + private static final class SpaceState { + + private final float[] gravity = new float[] {0.0f, -9.81f, 0.0f}; + private int bodyCount; + private int jointCount; + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java new file mode 100644 index 00000000..0c0387a4 --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java @@ -0,0 +1,651 @@ +package dev.hytalemodding.impulse.jolt; + +import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +final class JoltTestNativeLibrary implements JoltNativeLibrary { + + private final Map spaces = new HashMap<>(); + private long nextSpaceHandle = 1L; + private long nextBodyHandle = 1001L; + private long lastRemovedBodyHandle; + + @Override + public long createSpace() { + long handle = nextSpaceHandle++; + spaces.put(handle, new SpaceState()); + return handle; + } + + @Override + public void destroySpace(long spaceHandle) { + spaces.remove(spaceHandle); + } + + @Override + public void step(long spaceHandle, float dt) { + requireSpace(spaceHandle); + } + + @Override + public void setGravity(long spaceHandle, float x, float y, float z) { + SpaceState space = requireSpace(spaceHandle); + space.gravity[0] = x; + space.gravity[1] = y; + space.gravity[2] = z; + } + + @Override + public void getGravity(long spaceHandle, float[] out) { + SpaceState space = requireSpace(spaceHandle); + out[0] = space.gravity[0]; + out[1] = space.gravity[1]; + out[2] = space.gravity[2]; + } + + @Override + public long createBody(long spaceHandle, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + SpaceState space = requireSpace(spaceHandle); + long bodyHandle = nextBodyHandle++; + BodyState body = new BodyState(); + body.snapshot.shapeTypeCode = shapeTypeCode; + body.snapshot.bodyTypeCode = bodyTypeCode; + body.snapshot.positionX = positionX; + body.snapshot.positionY = positionY; + body.snapshot.positionZ = positionZ; + body.snapshot.rotationX = rotationX; + body.snapshot.rotationY = rotationY; + body.snapshot.rotationZ = rotationZ; + body.snapshot.rotationW = rotationW; + body.snapshot.mass = mass; + body.snapshot.centerOfMassOffsetY = centerOfMassOffsetY(shapeTypeCode, + halfExtentY, + radius, + halfHeight, + axisCode); + body.snapshot.hasBoxHalfExtents = halfExtentX > 0.0f + && halfExtentY > 0.0f + && halfExtentZ > 0.0f; + body.snapshot.halfExtentX = halfExtentX; + body.snapshot.halfExtentY = halfExtentY; + body.snapshot.halfExtentZ = halfExtentZ; + body.snapshot.radius = radius; + body.snapshot.halfHeight = halfHeight; + body.snapshot.axisCode = axisCode; + body.groundY = groundY; + space.bodies.put(bodyHandle, body); + return bodyHandle; + } + + @Override + public void removeBody(long spaceHandle, long bodyHandle) { + requireSpace(spaceHandle).bodies.remove(bodyHandle); + lastRemovedBodyHandle = bodyHandle; + } + + @Override + public boolean containsBody(long spaceHandle, long bodyHandle) { + return requireSpace(spaceHandle).bodies.containsKey(bodyHandle); + } + + @Override + public boolean bodySnapshot(long spaceHandle, long bodyHandle, JoltBodySnapshot out) { + BodyState body = requireSpace(spaceHandle).bodies.get(bodyHandle); + if (body == null) { + return false; + } + out.copyFrom(body.snapshot); + return true; + } + + @Override + public void setBodyTransform(long spaceHandle, + long bodyHandle, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW) { + JoltBodySnapshot snapshot = requireBody(spaceHandle, bodyHandle).snapshot; + snapshot.positionX = positionX; + snapshot.positionY = positionY; + snapshot.positionZ = positionZ; + snapshot.rotationX = rotationX; + snapshot.rotationY = rotationY; + snapshot.rotationZ = rotationZ; + snapshot.rotationW = rotationW; + } + + @Override + public void setBodyPosition(long spaceHandle, long bodyHandle, float x, float y, float z) { + JoltBodySnapshot snapshot = requireBody(spaceHandle, bodyHandle).snapshot; + snapshot.positionX = x; + snapshot.positionY = y; + snapshot.positionZ = z; + } + + @Override + public void setBodyVelocity(long spaceHandle, + long bodyHandle, + float linearX, + float linearY, + float linearZ, + float angularX, + float angularY, + float angularZ) { + JoltBodySnapshot snapshot = requireBody(spaceHandle, bodyHandle).snapshot; + snapshot.linearVelocityX = linearX; + snapshot.linearVelocityY = linearY; + snapshot.linearVelocityZ = linearZ; + snapshot.angularVelocityX = angularX; + snapshot.angularVelocityY = angularY; + snapshot.angularVelocityZ = angularZ; + } + + @Override + public void setBodyType(long spaceHandle, long bodyHandle, int bodyTypeCode) { + requireBody(spaceHandle, bodyHandle).snapshot.bodyTypeCode = bodyTypeCode; + } + + @Override + public void setBodyDamping(long spaceHandle, long bodyHandle, float linearDamping, float angularDamping) { + JoltBodySnapshot snapshot = requireBody(spaceHandle, bodyHandle).snapshot; + snapshot.linearDamping = linearDamping; + snapshot.angularDamping = angularDamping; + } + + @Override + public void setBodyFriction(long spaceHandle, long bodyHandle, float friction) { + requireBody(spaceHandle, bodyHandle).snapshot.friction = friction; + } + + @Override + public void setBodyRestitution(long spaceHandle, long bodyHandle, float restitution) { + requireBody(spaceHandle, bodyHandle).snapshot.restitution = restitution; + } + + @Override + public void setBodyCollisionFilter(long spaceHandle, long bodyHandle, int group, int mask) { + JoltBodySnapshot snapshot = requireBody(spaceHandle, bodyHandle).snapshot; + snapshot.collisionGroup = group; + snapshot.collisionMask = mask; + } + + @Override + public void setBodySensor(long spaceHandle, long bodyHandle, boolean sensor) { + requireBody(spaceHandle, bodyHandle).snapshot.sensor = sensor; + } + + @Override + public void setBodyContinuousCollision(long spaceHandle, long bodyHandle, boolean enabled) { + requireBody(spaceHandle, bodyHandle).snapshot.continuousCollisionEnabled = enabled; + } + + @Override + public boolean isBodyContinuousCollisionEnabled(long spaceHandle, long bodyHandle) { + return requireBody(spaceHandle, bodyHandle).snapshot.continuousCollisionEnabled; + } + + @Override + public void activateBody(long spaceHandle, long bodyHandle) { + requireBody(spaceHandle, bodyHandle).snapshot.sleeping = false; + } + + @Override + public void sleepBody(long spaceHandle, long bodyHandle) { + requireBody(spaceHandle, bodyHandle).snapshot.sleeping = true; + } + + @Override + public void applyBodyImpulse(long spaceHandle, + long bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + requireBody(spaceHandle, bodyHandle).impulses++; + } + + @Override + public void applyBodyForce(long spaceHandle, + long bodyHandle, + float x, + float y, + float z, + boolean hasOffset, + float offsetX, + float offsetY, + float offsetZ, + boolean torque) { + requireBody(spaceHandle, bodyHandle).forces++; + } + + @Override + public int bodyCount(long spaceHandle) { + return requireSpace(spaceHandle).bodies.size(); + } + + @Override + public int jointCount(long spaceHandle) { + return 0; + } + + @Override + public int raycastClosest(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + long[] bodyHandleOut, + float[] hitOut) { + SpaceState space = requireSpace(spaceHandle); + if (space.rayHits.isEmpty()) { + return 0; + } + RayHit hit = space.rayHits.getFirst(); + bodyHandleOut[0] = hit.bodyHandle; + hit.copyTo(hitOut, 0); + return 1; + } + + @Override + public int raycastAll(long spaceHandle, + float fromX, + float fromY, + float fromZ, + float toX, + float toY, + float toZ, + int maxHits, + long[] bodyHandles, + float[] hits) { + SpaceState space = requireSpace(spaceHandle); + int emitted = Math.min(Math.max(maxHits, 0), space.rayHits.size()); + for (int index = 0; index < emitted; index++) { + RayHit hit = space.rayHits.get(index); + bodyHandles[index] = hit.bodyHandle; + hit.copyTo(hits, index * 8); + } + return emitted; + } + + @Override + public int contacts(long spaceHandle, int maxContacts, long[] bodyHandles, float[] contacts) { + SpaceState space = requireSpace(spaceHandle); + int emitted = Math.min(Math.max(maxContacts, 0), space.contacts.size()); + for (int index = 0; index < emitted; index++) { + Contact contact = space.contacts.get(index); + bodyHandles[index * 2] = contact.bodyAHandle; + bodyHandles[index * 2 + 1] = contact.bodyBHandle; + contact.copyTo(contacts, index * 11); + } + return emitted; + } + + @Override + public int contactCount(long spaceHandle) { + return requireSpace(spaceHandle).contacts.size(); + } + + void addRayHit(long spaceHandle, + long bodyHandle, + float pointX, + float pointY, + float pointZ, + float fraction, + float distance) { + requireSpace(spaceHandle).rayHits.add(new RayHit(bodyHandle, + pointX, + pointY, + pointZ, + 0.0f, + 1.0f, + 0.0f, + fraction, + distance)); + } + + void addContact(long spaceHandle, + long bodyAHandle, + long bodyBHandle, + float pointY, + float distance) { + requireSpace(spaceHandle).contacts.add(new Contact(bodyAHandle, + bodyBHandle, + 0.0f, + pointY, + 0.0f, + 0.0f, + pointY, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + distance, + 0.0f)); + } + + long nativeBodyHandle(long spaceHandle, int index) { + return requireSpace(spaceHandle).bodies.keySet().stream() + .skip(index) + .findFirst() + .orElseThrow(); + } + + long firstSpaceHandle() { + return spaces.keySet().iterator().next(); + } + + long lastRemovedBodyHandle() { + return lastRemovedBodyHandle; + } + + BodyState body(long spaceHandle, long bodyHandle) { + return requireBody(spaceHandle, bodyHandle); + } + + private SpaceState requireSpace(long spaceHandle) { + SpaceState space = spaces.get(spaceHandle); + if (space == null) { + throw new IllegalArgumentException("Unknown native space handle: " + spaceHandle); + } + return space; + } + + private BodyState requireBody(long spaceHandle, long bodyHandle) { + BodyState body = requireSpace(spaceHandle).bodies.get(bodyHandle); + if (body == null) { + throw new IllegalArgumentException("Unknown native body handle: " + bodyHandle); + } + return body; + } + + private static float centerOfMassOffsetY(int shapeTypeCode, + float halfExtentY, + float radius, + float halfHeight, + int axisCode) { + return switch (shapeTypeCode) { + case 1 -> halfExtentY; + case 2 -> radius; + case 3, 4 -> axisCode == 2 ? radius + halfHeight : radius; + case 5 -> axisCode == 2 ? halfHeight : radius; + default -> 0.0f; + }; + } + + private static final class SpaceState { + + private final Map bodies = new LinkedHashMap<>(); + private final float[] gravity = new float[] {0.0f, -9.81f, 0.0f}; + private final List rayHits = new ArrayList<>(); + private final List contacts = new ArrayList<>(); + } + + static final class BodyState { + + private final JoltBodySnapshot snapshot = new JoltBodySnapshot(); + private float groundY; + private int impulses; + private int forces; + + JoltBodySnapshot snapshot() { + return snapshot; + } + + float groundY() { + return groundY; + } + + int impulses() { + return impulses; + } + + int forces() { + return forces; + } + } + + static final class CapturedBodySnapshot implements BackendBodySnapshotSink { + + long bodyId; + int shapeTypeCode; + int bodyTypeCode; + float positionX; + float positionY; + float positionZ; + float rotationX; + float rotationY; + float rotationZ; + float rotationW; + float linearVelocityX; + float linearVelocityY; + float linearVelocityZ; + float angularVelocityX; + float angularVelocityY; + float angularVelocityZ; + boolean sleeping; + boolean sensor; + float mass; + float friction; + float restitution; + float linearDamping; + float angularDamping; + int collisionGroup; + int collisionMask; + boolean continuousCollisionEnabled; + float centerOfMassOffsetY; + boolean hasBoxHalfExtents; + float halfExtentX; + float halfExtentY; + float halfExtentZ; + float radius; + float halfHeight; + int axisCode; + int captures; + + @Override + public void accept(long bodyId, + int shapeTypeCode, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping, + boolean sensor, + float mass, + float friction, + float restitution, + float linearDamping, + float angularDamping, + int collisionGroup, + int collisionMask, + boolean continuousCollisionEnabled, + float centerOfMassOffsetY, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode) { + this.bodyId = bodyId; + this.shapeTypeCode = shapeTypeCode; + this.bodyTypeCode = bodyTypeCode; + this.positionX = positionX; + this.positionY = positionY; + this.positionZ = positionZ; + this.rotationX = rotationX; + this.rotationY = rotationY; + this.rotationZ = rotationZ; + this.rotationW = rotationW; + this.linearVelocityX = linearVelocityX; + this.linearVelocityY = linearVelocityY; + this.linearVelocityZ = linearVelocityZ; + this.angularVelocityX = angularVelocityX; + this.angularVelocityY = angularVelocityY; + this.angularVelocityZ = angularVelocityZ; + this.sleeping = sleeping; + this.sensor = sensor; + this.mass = mass; + this.friction = friction; + this.restitution = restitution; + this.linearDamping = linearDamping; + this.angularDamping = angularDamping; + this.collisionGroup = collisionGroup; + this.collisionMask = collisionMask; + this.continuousCollisionEnabled = continuousCollisionEnabled; + this.centerOfMassOffsetY = centerOfMassOffsetY; + this.hasBoxHalfExtents = hasBoxHalfExtents; + this.halfExtentX = halfExtentX; + this.halfExtentY = halfExtentY; + this.halfExtentZ = halfExtentZ; + this.radius = radius; + this.halfHeight = halfHeight; + this.axisCode = axisCode; + captures++; + } + } + + private static final class RayHit { + + private final long bodyHandle; + private final float pointX; + private final float pointY; + private final float pointZ; + private final float normalX; + private final float normalY; + private final float normalZ; + private final float fraction; + private final float distance; + + private RayHit(long bodyHandle, + float pointX, + float pointY, + float pointZ, + float normalX, + float normalY, + float normalZ, + float fraction, + float distance) { + this.bodyHandle = bodyHandle; + this.pointX = pointX; + this.pointY = pointY; + this.pointZ = pointZ; + this.normalX = normalX; + this.normalY = normalY; + this.normalZ = normalZ; + this.fraction = fraction; + this.distance = distance; + } + + private void copyTo(float[] values, int offset) { + values[offset] = pointX; + values[offset + 1] = pointY; + values[offset + 2] = pointZ; + values[offset + 3] = normalX; + values[offset + 4] = normalY; + values[offset + 5] = normalZ; + values[offset + 6] = fraction; + values[offset + 7] = distance; + } + } + + private static final class Contact { + + private final long bodyAHandle; + private final long bodyBHandle; + private final float pointAX; + private final float pointAY; + private final float pointAZ; + private final float pointBX; + private final float pointBY; + private final float pointBZ; + private final float normalBX; + private final float normalBY; + private final float normalBZ; + private final float distance; + private final float impulse; + + private Contact(long bodyAHandle, + long bodyBHandle, + float pointAX, + float pointAY, + float pointAZ, + float pointBX, + float pointBY, + float pointBZ, + float normalBX, + float normalBY, + float normalBZ, + float distance, + float impulse) { + this.bodyAHandle = bodyAHandle; + this.bodyBHandle = bodyBHandle; + this.pointAX = pointAX; + this.pointAY = pointAY; + this.pointAZ = pointAZ; + this.pointBX = pointBX; + this.pointBY = pointBY; + this.pointBZ = pointBZ; + this.normalBX = normalBX; + this.normalBY = normalBY; + this.normalBZ = normalBZ; + this.distance = distance; + this.impulse = impulse; + } + + private void copyTo(float[] values, int offset) { + values[offset] = pointAX; + values[offset + 1] = pointAY; + values[offset + 2] = pointAZ; + values[offset + 3] = pointBX; + values[offset + 4] = pointBY; + values[offset + 5] = pointBZ; + values[offset + 6] = normalBX; + values[offset + 7] = normalBY; + values[offset + 8] = normalBZ; + values[offset + 9] = distance; + values[offset + 10] = impulse; + } + } +} diff --git a/licenses/JOLT_PHYSICS_LICENSE b/licenses/JOLT_PHYSICS_LICENSE new file mode 100644 index 00000000..6546a990 --- /dev/null +++ b/licenses/JOLT_PHYSICS_LICENSE @@ -0,0 +1,13 @@ +Copyright 2021 Jorrit Rouwe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/settings.gradle.kts b/settings.gradle.kts index fb4d4045..67d58029 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ rootProject.name = "impulse" include("impulse-backend-api") include("impulse-native-loader") +include("impulse-jolt") include("impulse-rapier") include("impulse-core") include("impulse-examples") From af8af5e108652f6c89e59505d4edaee90750af99 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 09:24:37 +0200 Subject: [PATCH 494/534] refactor(physicsentity): own visual sync policy Signed-off-by: Blovien --- .../PhysicsKinematicControlSystem.java | 2 +- .../PhysicsChunkCollisionProducerSystem.java | 2 +- .../PhysicsEntityTypeRegistry.java | 4 +- .../PhysicsBodyAttachmentIndexSystem.java | 2 +- .../systems/sync/PhysicsSyncPolicy.java | 20 +- .../systems/sync/PhysicsSyncSystem.java | 240 +++++++++++++-- .../sync/PhysicsTransformAuthority.java | 2 +- .../body/PhysicsBodyRuntimeState.java | 27 ++ .../systems/debug/PhysicsDebugSystem.java | 2 +- .../PhysicsStoreEventPublicationSystem.java | 2 +- .../visual/VisualInterestCollector.java | 2 +- .../settings/PhysicsVisualSyncSettings.java | 2 +- .../systems/sync/PhysicsSyncPolicyTest.java | 13 +- .../systems/sync/PhysicsSyncSystemTest.java | 283 ++++++++++++++++++ .../systems/sync/PhysicsSyncSystemTest.java | 139 --------- 15 files changed, 562 insertions(+), 180 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/sync/PhysicsBodyAttachmentIndexSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/sync/PhysicsSyncPolicy.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/sync/PhysicsSyncSystem.java (50%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/sync/PhysicsTransformAuthority.java (86%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/sync/PhysicsSyncPolicyTest.java (96%) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java index 9ababfd4..c41eebde 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java @@ -20,7 +20,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java index fccdaf87..d391e376 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java @@ -29,7 +29,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java index eb26e676..bf4f9ceb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java @@ -12,8 +12,8 @@ import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.systems.debug.PhysicsDebugSystem; import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsBodyAttachmentIndexSystem; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsBodyAttachmentIndexSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsBodyAttachmentIndexSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsBodyAttachmentIndexSystem.java index fa7461e5..fe389ef5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsBodyAttachmentIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsBodyAttachmentIndexSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.sync; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java index 07873f56..0753661a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicy.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.sync; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; @@ -63,10 +63,10 @@ private PhysicsSyncPolicy() { static SyncRangeTier resolveRangeTier(@Nullable PhysicsVisualSyncSettings settings, @Nullable BodyVisualInterestState visualInterestState, boolean rangeLimitedVisual, - boolean controlled, + boolean kinematic, @Nonnull List playerInterests, @Nonnull Vector3f visualPosition) { - if (!rangeLimitedVisual || controlled) { + if (!rangeLimitedVisual || kinematic) { return SyncRangeTier.NEAR; } if (playerInterests.isEmpty()) { @@ -107,7 +107,7 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn @Nonnull Quaternionf rotation, boolean sleeping, boolean lowSpeed, - boolean controlled, + boolean kinematic, @Nonnull SyncRangeTier rangeTier) { if (!syncState.isInitialized()) { return SyncDecision.INITIAL; @@ -128,22 +128,22 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn float rotationDotThreshold; float keepaliveSeconds; int minimumIntervalTicks = 1; - if (rangeTier == SyncRangeTier.FAR && !controlled) { + if (rangeTier == SyncRangeTier.FAR && !kinematic) { positionThresholdSquared = MID_RANGE_POSITION_SYNC_THRESHOLD_SQUARED; rotationDotThreshold = MID_RANGE_ROTATION_SYNC_DOT_THRESHOLD; keepaliveSeconds = intervalSeconds(visualSyncSettings.getVisualFarSyncIntervalTicks()); minimumIntervalTicks = visualSyncSettings.getVisualFarSyncIntervalTicks(); - } else if (rangeTier == SyncRangeTier.MID && !controlled) { + } else if (rangeTier == SyncRangeTier.MID && !kinematic) { positionThresholdSquared = MID_RANGE_POSITION_SYNC_THRESHOLD_SQUARED; rotationDotThreshold = MID_RANGE_ROTATION_SYNC_DOT_THRESHOLD; keepaliveSeconds = MID_RANGE_KEEPALIVE_SECONDS; minimumIntervalTicks = visualSyncSettings.getVisualMidSyncIntervalTicks(); } else { - positionThresholdSquared = lowSpeed && !controlled + positionThresholdSquared = lowSpeed && !kinematic ? LOW_SPEED_POSITION_SYNC_THRESHOLD_SQUARED : POSITION_SYNC_THRESHOLD_SQUARED; - rotationDotThreshold = lowSpeed && !controlled + rotationDotThreshold = lowSpeed && !kinematic ? LOW_SPEED_ROTATION_SYNC_DOT_THRESHOLD : ROTATION_SYNC_DOT_THRESHOLD; - keepaliveSeconds = lowSpeed && !controlled + keepaliveSeconds = lowSpeed && !kinematic ? LOW_SPEED_KEEPALIVE_SECONDS : ACTIVE_KEEPALIVE_SECONDS; } @@ -165,7 +165,7 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn if (rangeTier == SyncRangeTier.MID) { return SyncDecision.SKIP_VISUAL_RANGE; } - return lowSpeed && !controlled ? SyncDecision.SKIP_VISUAL_DEADZONE + return lowSpeed && !kinematic ? SyncDecision.SKIP_VISUAL_DEADZONE : SyncDecision.SKIP_THRESHOLD; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java similarity index 50% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java index c55736fc..12269c47 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.sync; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -16,18 +16,29 @@ import com.hypixel.hytale.server.core.modules.entity.system.UpdateLocationSystems; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodySyncStateResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; +import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; @@ -45,6 +56,9 @@ */ public class PhysicsSyncSystem extends EntityTickingSystem { + private static final float TRANSFORM_POSITION_EPSILON = 0.000001f; + private static final float TRANSFORM_ROTATION_EPSILON = 0.000001f; + @Nonnull private final ComponentType attachmentType; @Nonnull @@ -59,7 +73,7 @@ public class PhysicsSyncSystem extends EntityTickingSystem { ); @Nonnull - private final ThreadLocal physicsStoreSnapshots = new ThreadLocal<>(); + private final ThreadLocal syncTickContext = new ThreadLocal<>(); /** * Hytale may run entity ticks in parallel. Each tick task needs independent temporary objects @@ -95,13 +109,13 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { ? profiling.beginSyncSample() : null; long startNanos = collector != null ? System.nanoTime() : 0L; try { - physicsStoreSnapshots.set(collectPhysicsStoreSnapshotResource(store)); + syncTickContext.set(collectSyncTickContext(store)); super.tick(dt, systemIndex, store); } finally { if (collector != null) { profiling.finishSyncSample(collector, System.nanoTime() - startNanos); } - physicsStoreSnapshots.remove(); + syncTickContext.remove(); } } @@ -123,21 +137,45 @@ public void tick(float dt, if (collector != null) { collector.incrementBodiesInspected(); } - PhysicsSnapshotResource snapshotResource = physicsStoreSnapshots.get(); + SyncTickContext context = syncTickContext.get(); PhysicsBodySnapshot physicsStoreSnapshot = resolvePhysicsStoreSnapshot(entityRef, attachment, - snapshotResource, + context.snapshotResource(), store); if (physicsStoreSnapshot != null) { if (!PhysicsTransformAuthority.shouldApplyBodyTransform(attachment)) { + if (collector != null) { + collector.incrementSkippedStatic(); + } return; } - applyPhysicsStoreSnapshot(transform, attachment, physicsStoreSnapshot, local); + PhysicsBodyRuntimeState.BodySyncState syncState = store.getResource( + PhysicsBodySyncStateResource.getResourceType()) + .getOrCreate(entityRef); + PhysicsVisualSyncSettings settings = + context.visualSyncSettings(physicsStoreSnapshot.spaceUuid()); + computeVisualPose(attachment, physicsStoreSnapshot, local); + boolean kinematic = physicsStoreSnapshot.bodyType() == PhysicsBodyType.KINEMATIC; + SyncResult result = applyComputedPhysicsStoreSnapshot(transform, + physicsStoreSnapshot, + local, + syncState, + settings, + resolveRangeTier(settings, + attachment, + context.playerInterests(), + local.visualPosition, + kinematic), + kinematic, + dt); if (collector != null) { - collector.incrementBodiesSynced(); + recordSyncResult(collector, result); } return; } + if (collector != null) { + collector.incrementSkippedMissingSpace(); + } } @Nullable @@ -180,21 +218,87 @@ private static boolean sameRef(@Nullable Ref first, } @Nonnull - private static PhysicsSnapshotResource collectPhysicsStoreSnapshotResource( + private static SyncTickContext collectSyncTickContext( @Nonnull Store store) { Store physics = PhysicsThreading.store(store.getExternalData().getWorld()); PhysicsThreading.requireWorldThread(physics, "read copied PhysicsStore sync snapshots"); - return physics.getResource( - PhysicsSnapshotResource.getResourceType()); + return new SyncTickContext(physics, + physics.getResource(PhysicsSnapshotResource.getResourceType()), + VisualInterestCollector.collectSyncInterests(store)); } - private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transform, + static boolean applyPhysicsStoreSnapshot(@Nonnull TransformComponent transform, @Nonnull BodyAttachmentComponent attachment, @Nonnull PhysicsBodySnapshot snapshot, @Nonnull Scratch scratch) { - scratch.position.set(snapshot.position()); - scratch.rotation.set(snapshot.rotation()); + computeVisualPose(attachment, snapshot, scratch); + return writeTransformIfChanged(transform, scratch); + } + + @Nonnull + static SyncResult applyPhysicsStoreSnapshot(@Nonnull TransformComponent transform, + @Nonnull BodyAttachmentComponent attachment, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull Scratch scratch, + @Nonnull PhysicsBodyRuntimeState.BodySyncState syncState, + @Nullable PhysicsVisualSyncSettings settings, + @Nonnull PhysicsSyncPolicy.SyncRangeTier rangeTier, + float dt) { + computeVisualPose(attachment, snapshot, scratch); + return applyComputedPhysicsStoreSnapshot(transform, + snapshot, + scratch, + syncState, + settings, + rangeTier, + false, + dt); + } + + @Nonnull + private static SyncResult applyComputedPhysicsStoreSnapshot( + @Nonnull TransformComponent transform, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull Scratch scratch, + @Nonnull PhysicsBodyRuntimeState.BodySyncState syncState, + @Nullable PhysicsVisualSyncSettings settings, + @Nonnull PhysicsSyncPolicy.SyncRangeTier rangeTier, + boolean kinematic, + float dt) { + if (!syncState.isInitializedFor(snapshot.bodyUuid())) { + syncState.clear(); + } + float snapshotMotion = syncState.recordSnapshotObservation(scratch.position); + boolean lowSpeed = !Float.isNaN(snapshotMotion) && snapshotMotion < 0.125f; + PhysicsSyncPolicy.SyncDecision decision = PhysicsSyncPolicy.resolveSyncDecision(syncState, + settings, + scratch.visualPosition, + scratch.visualRotation, + snapshot.sleeping(), + lowSpeed, + kinematic, + rangeTier); + if (!shouldWriteTransform(decision)) { + syncState.recordSkip(dt); + return new SyncResult(decision, false); + } + boolean changed = writeTransformIfChanged(transform, scratch); + syncState.recordSync(snapshot.bodyUuid(), + scratch.visualPosition, + scratch.visualRotation, + snapshot.sleeping()); + return new SyncResult(decision, changed); + } + + private static void computeVisualPose(@Nonnull BodyAttachmentComponent attachment, + @Nonnull PhysicsBodySnapshot snapshot, + @Nonnull Scratch scratch) { + scratch.position.set(snapshot.positionX(), snapshot.positionY(), snapshot.positionZ()); + scratch.rotation.set(snapshot.rotationX(), + snapshot.rotationY(), + snapshot.rotationZ(), + snapshot.rotationW()); PhysicsVisualPoseMath.visualPositionFromBodyPose(scratch.position, scratch.rotation, attachment.resolveVisualOriginOffsetY(snapshot.centerOfMassOffsetY()), @@ -203,14 +307,77 @@ private static void applyPhysicsStoreSnapshot(@Nonnull TransformComponent transf scratch.worldOffset); scratch.visualRotation.set(scratch.rotation); scratch.visualRotation.mul(attachment.getLocalRotationOffset()); + scratch.visualRotation.getEulerAnglesYXZ(scratch.euler); + } + + private static boolean writeTransformIfChanged(@Nonnull TransformComponent transform, + @Nonnull Scratch scratch) { + if (matchesTransform(transform, scratch.visualPosition, scratch.euler)) { + return false; + } transform.getPosition().set(scratch.visualPosition.x, scratch.visualPosition.y, scratch.visualPosition.z); - scratch.visualRotation.getEulerAnglesYXZ(scratch.euler); transform.getRotation().set(scratch.euler.x, scratch.euler.y, scratch.euler.z); + return true; + } + + private static boolean shouldWriteTransform( + @Nonnull PhysicsSyncPolicy.SyncDecision decision) { + return decision == PhysicsSyncPolicy.SyncDecision.INITIAL + || decision == PhysicsSyncPolicy.SyncDecision.THRESHOLD + || decision == PhysicsSyncPolicy.SyncDecision.TRANSITION + || decision == PhysicsSyncPolicy.SyncDecision.KEEPALIVE; + } + + @Nonnull + private static PhysicsSyncPolicy.SyncRangeTier resolveRangeTier( + @Nullable PhysicsVisualSyncSettings settings, + @Nonnull BodyAttachmentComponent attachment, + @Nonnull List playerInterests, + @Nonnull Vector3f visualPosition, + boolean kinematic) { + boolean generatedProxy = attachment.getLifecycle() == AttachmentLifecycle.GENERATED_PROXY; + boolean rangeLimitedVisual = generatedProxy + || settings != null && settings.isEntityVisualSyncCullingEnabled(); + return PhysicsSyncPolicy.resolveRangeTier(settings, + null, + rangeLimitedVisual, + kinematic, + playerInterests, + visualPosition); } - private static final class Scratch { + private static void recordSyncResult( + @Nonnull PhysicsRuntimeProfilingResource.SyncCollector collector, + @Nonnull SyncResult result) { + if (result.transformChanged()) { + collector.incrementBodiesSynced(); + } + switch (result.decision()) { + case TRANSITION -> collector.incrementTransitionSyncs(); + case KEEPALIVE -> collector.incrementKeepaliveSyncs(); + case SKIP_SLEEPING -> collector.incrementSkippedSleeping(); + case SKIP_THRESHOLD -> collector.incrementSkippedThreshold(); + case SKIP_VISUAL_DEADZONE -> collector.incrementSkippedVisualDeadzone(); + case SKIP_VISUAL_RANGE -> collector.incrementSkippedVisualRange(); + default -> { + } + } + } + + private static boolean matchesTransform(@Nonnull TransformComponent transform, + @Nonnull Vector3f position, + @Nonnull Vector3f rotation) { + return Math.abs(transform.getPosition().x - position.x) <= TRANSFORM_POSITION_EPSILON + && Math.abs(transform.getPosition().y - position.y) <= TRANSFORM_POSITION_EPSILON + && Math.abs(transform.getPosition().z - position.z) <= TRANSFORM_POSITION_EPSILON + && Math.abs(transform.getRotation().x() - rotation.x) <= TRANSFORM_ROTATION_EPSILON + && Math.abs(transform.getRotation().y() - rotation.y) <= TRANSFORM_ROTATION_EPSILON + && Math.abs(transform.getRotation().z() - rotation.z) <= TRANSFORM_ROTATION_EPSILON; + } + + static final class Scratch { private final Vector3f position = new Vector3f(); private final Quaternionf rotation = new Quaternionf(); @@ -235,6 +402,43 @@ private PhysicsRuntimeProfilingResource.SyncCollector getSyncCollector( } } + record SyncResult(@Nonnull PhysicsSyncPolicy.SyncDecision decision, + boolean transformChanged) { + } + + private record SyncTickContext(@Nonnull Store physicsStore, + @Nonnull PhysicsSnapshotResource snapshotResource, + @Nonnull List playerInterests, + @Nonnull Map settingsBySpace) { + + private SyncTickContext(@Nonnull Store physicsStore, + @Nonnull PhysicsSnapshotResource snapshotResource, + @Nonnull List playerInterests) { + this(physicsStore, snapshotResource, playerInterests, new HashMap<>()); + } + + @Nullable + private PhysicsVisualSyncSettings visualSyncSettings(@Nonnull UUID spaceUuid) { + if (settingsBySpace.containsKey(spaceUuid)) { + return settingsBySpace.get(spaceUuid); + } + Ref spaceRef = physicsStore.getResource( + PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid); + PhysicsVisualSyncSettings settings = null; + if (spaceRef != null && spaceRef.isValid()) { + VisualSyncSettingsComponent component = physicsStore.getComponent(spaceRef, + VisualSyncSettingsComponent.getComponentType()); + settings = new PhysicsVisualSyncSettings(); + if (component != null) { + component.copyTo(settings); + } + } + settingsBySpace.put(spaceUuid, settings); + return settings; + } + } + @Nonnull @Override public Query getQuery() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsTransformAuthority.java similarity index 86% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsTransformAuthority.java index f96bdf80..6c1b325f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsTransformAuthority.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsTransformAuthority.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.sync; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java index 1774b4ea..20e293b5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java @@ -7,6 +7,7 @@ import org.joml.Quaternionf; import org.joml.Vector3f; import java.util.Map; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -50,12 +51,22 @@ public static final class BodySyncState { private boolean sleeping; @Getter private boolean snapshotObserved; + @Nullable + private UUID lastSyncedBodyUuid; @Getter private float secondsSinceSync; public void recordSync(@Nonnull Vector3f position, @Nonnull Quaternionf rotation, boolean sleeping) { + recordSync(null, position, rotation, sleeping); + } + + public void recordSync(@Nullable UUID bodyUuid, + @Nonnull Vector3f position, + @Nonnull Quaternionf rotation, + boolean sleeping) { + lastSyncedBodyUuid = bodyUuid; lastSyncedPosition.set(position); lastSyncedRotation.set(rotation); initialized = true; @@ -63,6 +74,22 @@ public void recordSync(@Nonnull Vector3f position, secondsSinceSync = 0.0f; } + public boolean isInitializedFor(@Nonnull UUID bodyUuid) { + return initialized + && (lastSyncedBodyUuid == null || lastSyncedBodyUuid.equals(bodyUuid)); + } + + public void clear() { + initialized = false; + sleeping = false; + snapshotObserved = false; + lastSyncedBodyUuid = null; + secondsSinceSync = 0.0f; + lastSyncedPosition.zero(); + lastSyncedRotation.identity(); + lastObservedSnapshotPosition.zero(); + } + public float recordSnapshotObservation(@Nonnull Vector3f position) { if (!snapshotObserved) { lastObservedSnapshotPosition.set(position); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index fec65348..4e4cbd1a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -37,7 +37,7 @@ import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index 8f6b0a65..c04f483b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource.StepSample; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java index b67c7892..3e04c3ed 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java @@ -11,7 +11,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualInterestResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime; -import dev.hytalemodding.impulse.core.internal.systems.sync.PhysicsSyncPolicy; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncPolicy; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java index ada5b91e..10cc542e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/PhysicsVisualSyncSettings.java @@ -210,7 +210,7 @@ public class PhysicsVisualSyncSettings { /** * If enabled, entity-backed physics body transforms use the same player-interest culling - * as follower visuals. Controlled bodies are always synced. + * as follower visuals. Kinematic bodies are always synced. */ @Setter @Getter diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicyTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java similarity index 96% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicyTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java index 53fe3431..ab1a2298 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncPolicyTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.sync; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -177,7 +177,7 @@ void farRangeLodUsesConfiguredIntervalWhenCutoffIsDisabled() { } @Test - void controlledBodiesBypassLowSpeedDeadzoneThresholds() { + void kinematicBodiesBypassLowSpeedDeadzoneThresholds() { PhysicsBodyRuntimeState.BodySyncState syncState = initializedState(false); assertEquals(PhysicsSyncPolicy.SyncDecision.THRESHOLD, @@ -238,7 +238,7 @@ void sleepingBodiesSkipAfterThresholdAndKeepaliveChecks() { } @Test - void rangeTierReturnsNearForNonLimitedOrControlledVisuals() { + void rangeTierReturnsNearForNonLimitedOrKinematicVisuals() { PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); List players = interests(new Vector3f(100.0f, 0.0f, 0.0f)); @@ -257,6 +257,13 @@ void rangeTierReturnsNearForNonLimitedOrControlledVisuals() { true, players, new Vector3f())); + assertEquals(PhysicsSyncPolicy.SyncRangeTier.NEAR, + PhysicsSyncPolicy.resolveRangeTier(settings, + null, + true, + true, + List.of(), + new Vector3f())); } @Test diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java new file mode 100644 index 00000000..3fd061b5 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java @@ -0,0 +1,283 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; +import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaterniond; +import org.joml.Quaternionf; +import org.joml.Vector3d; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class PhysicsSyncSystemTest { + + @Test + void visualPredictionSecondsClampToConfiguredWindow() { + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualSnapshotPredictionEnabled(true); + settings.setVisualSnapshotPredictionMaxSeconds(0.05f); + + assertEquals(0.05f, + PhysicsSyncPolicy.visualPredictionSeconds(settings, + 1_100_000_000L, + 1_000_000_000L), + 0.0001f); + } + + @Test + void visualPredictionSecondsStayZeroWhenDisabledOrMissingFrame() { + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualSnapshotPredictionEnabled(true); + + assertEquals(0.0f, + PhysicsSyncPolicy.visualPredictionSeconds(settings, 1_100_000_000L, 0L), + 0.0001f); + settings.setVisualSnapshotPredictionEnabled(false); + assertEquals(0.0f, + PhysicsSyncPolicy.visualPredictionSeconds(settings, + 1_100_000_000L, + 1_000_000_000L), + 0.0001f); + } + + @Test + void bodyTransformSyncOnlyAppliesToBodyAuthoritativeAttachments() { + UUID bodyUuid = UUID.randomUUID(); + + assertTrue(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, + TransformAuthority.BODY, + AttachmentLifecycle.EXTERNAL_ENTITY))); + assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, + TransformAuthority.CONTROLLER, + AttachmentLifecycle.EXTERNAL_ENTITY))); + assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, + TransformAuthority.ENTITY_KINEMATIC, + AttachmentLifecycle.EXTERNAL_ENTITY))); + } + + @Test + void bodyTransformSyncSkipsUnchangedHytaleTransformWrites() { + UUID bodyUuid = UUID.randomUUID(); + TransformComponent transform = new TransformComponent(); + BodyAttachmentComponent attachment = new BodyAttachmentComponent(bodyUuid, + TransformAuthority.BODY, + AttachmentLifecycle.EXTERNAL_ENTITY); + PhysicsBodySnapshot snapshot = PhysicsBodySnapshot.of(bodyUuid, + UUID.randomUUID(), + PhysicsBodyType.DYNAMIC, + 1.0f, + 2.0f, + 3.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + PhysicsSyncSystem.Scratch scratch = new PhysicsSyncSystem.Scratch(); + + assertTrue(PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot, + scratch)); + assertFalse(PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot, + scratch)); + assertEquals(1.0, transform.getPosition().x, 0.0001); + assertEquals(2.0, transform.getPosition().y, 0.0001); + assertEquals(3.0, transform.getPosition().z, 0.0001); + assertEquals(0.0f, transform.getRotation().x(), 0.0001f); + assertEquals(0.0f, transform.getRotation().y(), 0.0001f); + assertEquals(0.0f, transform.getRotation().z(), 0.0001f); + } + + @Test + void policyBackedSyncSkipsGeneratedProxyFarFromPlayers() { + UUID bodyUuid = UUID.randomUUID(); + UUID spaceUuid = UUID.randomUUID(); + TransformComponent transform = new TransformComponent(); + BodyAttachmentComponent attachment = new BodyAttachmentComponent(bodyUuid, + TransformAuthority.BODY, + AttachmentLifecycle.GENERATED_PROXY); + PhysicsSyncSystem.Scratch scratch = new PhysicsSyncSystem.Scratch(); + PhysicsBodyRuntimeState.BodySyncState syncState = + new PhysicsBodyRuntimeState.BodySyncState(); + + PhysicsSyncSystem.SyncResult initial = PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot(bodyUuid, spaceUuid, 10.0f, false), + scratch, + syncState, + new PhysicsVisualSyncSettings(), + PhysicsSyncPolicy.SyncRangeTier.NEAR, + 0.05f); + PhysicsSyncSystem.SyncResult skipped = PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot(bodyUuid, spaceUuid, 10.5f, false), + scratch, + syncState, + new PhysicsVisualSyncSettings(), + PhysicsSyncPolicy.SyncRangeTier.FAR, + 0.05f); + + assertEquals(PhysicsSyncPolicy.SyncDecision.INITIAL, initial.decision()); + assertTrue(initial.transformChanged()); + assertEquals(PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_RANGE, skipped.decision()); + assertFalse(skipped.transformChanged()); + assertEquals(10.0, transform.getPosition().x, 0.0001); + assertEquals(0.05f, syncState.getSecondsSinceSync(), 0.0001f); + } + + @Test + void policyBackedSyncTreatsRetargetedBodyAsInitial() { + UUID firstBodyUuid = UUID.randomUUID(); + UUID secondBodyUuid = UUID.randomUUID(); + UUID spaceUuid = UUID.randomUUID(); + TransformComponent transform = new TransformComponent(); + BodyAttachmentComponent attachment = new BodyAttachmentComponent(firstBodyUuid, + TransformAuthority.BODY, + AttachmentLifecycle.EXTERNAL_ENTITY); + PhysicsSyncSystem.Scratch scratch = new PhysicsSyncSystem.Scratch(); + PhysicsBodyRuntimeState.BodySyncState syncState = + new PhysicsBodyRuntimeState.BodySyncState(); + + PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot(firstBodyUuid, spaceUuid, 10.0f, false), + scratch, + syncState, + new PhysicsVisualSyncSettings(), + PhysicsSyncPolicy.SyncRangeTier.NEAR, + 0.05f); + PhysicsSyncSystem.SyncResult retargeted = PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot(secondBodyUuid, spaceUuid, 10.01f, false), + scratch, + syncState, + new PhysicsVisualSyncSettings(), + PhysicsSyncPolicy.SyncRangeTier.NEAR, + 0.05f); + + assertEquals(PhysicsSyncPolicy.SyncDecision.INITIAL, retargeted.decision()); + assertTrue(retargeted.transformChanged()); + assertEquals(10.01, transform.getPosition().x, 0.0001); + } + + @Test + void visualPositionKeepsCenterOfMassOffsetWorldUp() { + Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, + 20.0f, + 30.0f), + new Quaternionf().rotateZ((float) (Math.PI / 2.0)), + 0.5f, + new Vector3f(), + new Vector3f()); + + assertEquals(10.0f, visualPosition.x, 0.0001f); + assertEquals(19.5f, visualPosition.y, 0.0001f); + assertEquals(30.0f, visualPosition.z, 0.0001f); + } + + @Test + void visualPositionRotatesLocalAttachmentOffset() { + Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, + 20.0f, + 30.0f), + new Quaternionf().rotateZ((float) (Math.PI / 2.0)), + 0.5f, + new Vector3f(1.0f, 0.0f, 0.0f), + new Vector3f()); + + assertEquals(10.0f, visualPosition.x, 0.0001f); + assertEquals(20.5f, visualPosition.y, 0.0001f); + assertEquals(30.0f, visualPosition.z, 0.0001f); + } + + @Test + void bodyCenterInvertsWorldUpCenterOfMassOffsetAndRotatedLocalOffset() { + Vector3f bodyCenter = new Vector3f(10.0f, 20.0f, 30.0f); + Quaternionf bodyRotation = new Quaternionf().rotateZ((float) (Math.PI / 2.0)); + float centerOfMassOffsetY = 0.5f; + Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(bodyCenter, + bodyRotation, + centerOfMassOffsetY, + new Vector3f(1.0f, 0.0f, 0.0f), + new Vector3f()); + + Vector3d invertedCenter = PhysicsVisualPoseMath.bodyCenterFromVisualPose( + new Vector3d(visualPosition.x, visualPosition.y, visualPosition.z), + new Quaterniond(bodyRotation), + centerOfMassOffsetY, + new Vector3f(1.0f, 0.0f, 0.0f), + new Vector3d()); + + assertEquals(bodyCenter.x, invertedCenter.x, 0.0001f); + assertEquals(bodyCenter.y, invertedCenter.y, 0.0001f); + assertEquals(bodyCenter.z, invertedCenter.z, 0.0001f); + } + + @Test + void attachmentVisualOriginOffsetOverridesBodyShapeOffset() { + BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity( + UUID.randomUUID(), + new Vector3f(0.0f, -0.5f, 0.0f), + new Quaternionf(), + 0.5f); + + Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, + 20.0f, + 30.0f), + new Quaternionf(), + attachment.resolveVisualOriginOffsetY(1.0f), + attachment.getLocalPositionOffset(), + new Vector3f()); + + assertEquals(10.0f, visualPosition.x, 0.0001f); + assertEquals(19.0f, visualPosition.y, 0.0001f); + assertEquals(30.0f, visualPosition.z, 0.0001f); + assertEquals(0.5f, attachment.clone().getVisualOriginOffsetY(), 0.0001f); + } + + private static PhysicsBodySnapshot snapshot(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + float positionX, + boolean sleeping) { + return PhysicsBodySnapshot.of(bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + positionX, + 2.0f, + 3.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + sleeping); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java deleted file mode 100644 index f051e47b..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/sync/PhysicsSyncSystemTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.systems.sync; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import java.util.UUID; -import org.joml.Quaterniond; -import org.joml.Quaternionf; -import org.joml.Vector3d; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsSyncSystemTest { - - @Test - void visualPredictionSecondsClampToConfiguredWindow() { - PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); - settings.setVisualSnapshotPredictionEnabled(true); - settings.setVisualSnapshotPredictionMaxSeconds(0.05f); - - assertEquals(0.05f, - PhysicsSyncPolicy.visualPredictionSeconds(settings, - 1_100_000_000L, - 1_000_000_000L), - 0.0001f); - } - - @Test - void visualPredictionSecondsStayZeroWhenDisabledOrMissingFrame() { - PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); - settings.setVisualSnapshotPredictionEnabled(true); - - assertEquals(0.0f, - PhysicsSyncPolicy.visualPredictionSeconds(settings, 1_100_000_000L, 0L), - 0.0001f); - settings.setVisualSnapshotPredictionEnabled(false); - assertEquals(0.0f, - PhysicsSyncPolicy.visualPredictionSeconds(settings, - 1_100_000_000L, - 1_000_000_000L), - 0.0001f); - } - - @Test - void bodyTransformSyncOnlyAppliesToBodyAuthoritativeAttachments() { - UUID bodyUuid = UUID.randomUUID(); - - assertTrue(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, - TransformAuthority.BODY, - AttachmentLifecycle.EXTERNAL_ENTITY))); - assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, - TransformAuthority.CONTROLLER, - AttachmentLifecycle.EXTERNAL_ENTITY))); - assertFalse(PhysicsTransformAuthority.shouldApplyBodyTransform(new BodyAttachmentComponent(bodyUuid, - TransformAuthority.ENTITY_KINEMATIC, - AttachmentLifecycle.EXTERNAL_ENTITY))); - } - - @Test - void visualPositionKeepsCenterOfMassOffsetWorldUp() { - Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, - 20.0f, - 30.0f), - new Quaternionf().rotateZ((float) (Math.PI / 2.0)), - 0.5f, - new Vector3f(), - new Vector3f()); - - assertEquals(10.0f, visualPosition.x, 0.0001f); - assertEquals(19.5f, visualPosition.y, 0.0001f); - assertEquals(30.0f, visualPosition.z, 0.0001f); - } - - @Test - void visualPositionRotatesLocalAttachmentOffset() { - Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, - 20.0f, - 30.0f), - new Quaternionf().rotateZ((float) (Math.PI / 2.0)), - 0.5f, - new Vector3f(1.0f, 0.0f, 0.0f), - new Vector3f()); - - assertEquals(10.0f, visualPosition.x, 0.0001f); - assertEquals(20.5f, visualPosition.y, 0.0001f); - assertEquals(30.0f, visualPosition.z, 0.0001f); - } - - @Test - void bodyCenterInvertsWorldUpCenterOfMassOffsetAndRotatedLocalOffset() { - Vector3f bodyCenter = new Vector3f(10.0f, 20.0f, 30.0f); - Quaternionf bodyRotation = new Quaternionf().rotateZ((float) (Math.PI / 2.0)); - float centerOfMassOffsetY = 0.5f; - Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(bodyCenter, - bodyRotation, - centerOfMassOffsetY, - new Vector3f(1.0f, 0.0f, 0.0f), - new Vector3f()); - - Vector3d invertedCenter = PhysicsVisualPoseMath.bodyCenterFromVisualPose( - new Vector3d(visualPosition.x, visualPosition.y, visualPosition.z), - new Quaterniond(bodyRotation), - centerOfMassOffsetY, - new Vector3f(1.0f, 0.0f, 0.0f), - new Vector3d()); - - assertEquals(bodyCenter.x, invertedCenter.x, 0.0001f); - assertEquals(bodyCenter.y, invertedCenter.y, 0.0001f); - assertEquals(bodyCenter.z, invertedCenter.z, 0.0001f); - } - - @Test - void attachmentVisualOriginOffsetOverridesBodyShapeOffset() { - BodyAttachmentComponent attachment = BodyAttachmentComponent.externalEntity( - UUID.randomUUID(), - new Vector3f(0.0f, -0.5f, 0.0f), - new Quaternionf(), - 0.5f); - - Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, - 20.0f, - 30.0f), - new Quaternionf(), - attachment.resolveVisualOriginOffsetY(1.0f), - attachment.getLocalPositionOffset(), - new Vector3f()); - - assertEquals(10.0f, visualPosition.x, 0.0001f); - assertEquals(19.0f, visualPosition.y, 0.0001f); - assertEquals(30.0f, visualPosition.z, 0.0001f); - assertEquals(0.5f, attachment.clone().getVisualOriginOffsetY(), 0.0001f); - } -} From f7a9f711e11b90a0d38c198eebbc2c1f049ad637 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 10:17:48 +0200 Subject: [PATCH 495/534] fix(core): harden runtime identity restore state Reset transient runtime, identity, snapshot, and event state before holder hydration. Add UUID-backed runtime metadata indexes, compact snapshot cursor coverage, and optional PhysicsChunk payload handling for body binding. Signed-off-by: Blovien --- .../FakePhysicsBackendRuntimeProvider.java | 5 + .../physicschunk/PhysicsChunkStoreTypes.java | 28 ++- .../PhysicsRestoreStatusResource.java | 16 +- .../resources/PhysicsRuntimeResource.java | 89 +++++---- .../resources/PhysicsSnapshotResource.java | 178 ++++++++++++++++++ .../CompletedStepPublicationSystem.java | 2 +- .../systems/PersistenceHydrationSystem.java | 13 ++ .../systems/binding/BodyBindingSystem.java | 14 +- .../systems/binding/SpaceBindingSystem.java | 8 +- .../PhysicsStoreEventPublicationSystem.java | 1 + .../PhysicsStoreHolderPersistenceTest.java | 138 +++++++++++++- .../PhysicsStoreResourceIndexTest.java | 53 ++++++ .../systems/BodyBindingSystemTest.java | 142 ++++++++++++++ .../binding/SpaceBindingSystemTest.java | 4 - 14 files changed, 618 insertions(+), 73 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java diff --git a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java index 9d7ae760..2f3b2b3b 100644 --- a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java +++ b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java @@ -126,6 +126,11 @@ public void destroySpace(int spaceId) { spaces.remove(spaceId); } + @Override + public void close() { + spaces.clear(); + } + @Override public void step(int spaceId, float dt) { requireSpace(spaceId); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java index 60293d04..6af547f1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java @@ -79,23 +79,35 @@ public static void clearPhysicsStoreRuntimeResources(@Nonnull Store store) { + return resourceIfPresent(store, PhysicsChunkCollisionPayloadResource.getResourceType()); + } + private static > void clearIfPresent( @Nonnull Store store, @Nullable ResourceType type, @Nonnull Consumer clear) { + T resource = resourceIfPresent(store, type); + if (resource != null) { + clear.accept(resource); + } + } + + @Nullable + private static > T resourceIfPresent( + @Nonnull Store store, + @Nullable ResourceType type) { if (type == null) { - return; + return null; } - T resource; try { type.validate(); - resource = store.getResource(type); + return store.getResource(type); } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException | IllegalStateException _) { - // Optional PhysicsChunk resources can be unregistered before the core shutdown hook runs. - return; - } - if (resource != null) { - clear.accept(resource); + // PhysicsChunk resources are optional when the PhysicsChunk module is not registered. + return null; } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java index 22f32b92..e2b7fe1f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java @@ -5,6 +5,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; +import lombok.Getter; import javax.annotation.Nonnull; /** @@ -12,8 +13,11 @@ */ public final class PhysicsRestoreStatusResource implements Resource { + @Getter private boolean pending; + @Getter private boolean failed; + @Getter private boolean hydrated; @Nonnull private String failureMessage = ""; @@ -23,10 +27,6 @@ public final class PhysicsRestoreStatusResource implements Resource { private final Map jointMetadataByKey = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map jointKeysByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final Int2ObjectOpenHashMap chunkCollisionPayloadKeysByRowIndex = new Int2ObjectOpenHashMap<>(); @Nonnull @@ -85,6 +88,9 @@ public final class PhysicsRuntimeResource implements Resource { private final Map bodySnapshotMetadataByKey = new Object2ObjectOpenHashMap<>(); @Nonnull + private final Map bodySnapshotKeysByUuid = + new Object2ObjectOpenHashMap<>(); + @Nonnull private final List pendingBodyOperations = new ArrayList<>(); @Nonnull private final Int2ObjectOpenHashMap> pendingSpaceSettingsByRowIndex = @@ -271,9 +277,14 @@ public void putBodySnapshotMetadata(@Nonnull BackendId backendId, @Nonnull Ref bodyRef, @Nonnull UUID spaceUuid) { BackendBodyKey key = new BackendBodyKey(backendId, spaceHandle, handle); + BodySnapshotMetadata previousMetadata = bodySnapshotMetadataByKey.get(key); + if (previousMetadata != null) { + bodySnapshotKeysByUuid.remove(previousMetadata.bodyUuid(), key); + } removeBodyMetadataForUuid(bodyUuid, key, bodyRef); bodySnapshotMetadataByKey.put(key, new BodySnapshotMetadata(bodyUuid, bodyRef, spaceUuid)); + bodySnapshotKeysByUuid.put(bodyUuid, key); } public void putBodyHitMetadata(@Nonnull BackendId backendId, @@ -395,9 +406,14 @@ public void putJointMetadata(@Nonnull BackendId backendId, @Nonnull UUID jointUuid, @Nonnull Ref jointRef) { BackendJointKey key = new BackendJointKey(backendId, spaceHandle, handle); + JointMetadata previousMetadata = jointMetadataByKey.get(key); + if (previousMetadata != null) { + jointKeysByUuid.remove(previousMetadata.jointUuid(), key); + } removeJointMetadataForUuid(jointUuid, key, jointRef); jointMetadataByKey.put(key, new JointMetadata(jointUuid, jointRef)); + jointKeysByUuid.put(jointUuid, key); } @Nullable @@ -512,10 +528,12 @@ public void clear() { jointRefsByRowIndex.clear(); backendIdsByJointRowIndex.clear(); jointMetadataByKey.clear(); + jointKeysByUuid.clear(); chunkCollisionPayloadKeysByRowIndex.clear(); bodyHandlesBySpaceKey.clear(); bodyHitMetadataByKey.clear(); bodySnapshotMetadataByKey.clear(); + bodySnapshotKeysByUuid.clear(); pendingBodyOperations.clear(); pendingSpaceSettingsByRowIndex.clear(); started = false; @@ -634,6 +652,7 @@ private void removeBodyMetadata(@Nonnull BackendBodyKey key) { if (metadata == null) { return; } + bodySnapshotKeysByUuid.remove(metadata.bodyUuid(), key); int rowIndex = metadata.bodyRef().getIndex(); bodyRefsByRowIndex.remove(rowIndex); bodyHandlesByRowIndex.remove(rowIndex); @@ -645,24 +664,21 @@ private void removeBodyMetadata(@Nonnull BackendBodyKey key) { private void removeBodyMetadataForUuid(@Nonnull UUID bodyUuid, @Nonnull BackendBodyKey replacementKey, @Nonnull Ref replacementRef) { - List removedKeys = new ArrayList<>(); - bodySnapshotMetadataByKey.forEach((key, metadata) -> { - if (!key.equals(replacementKey) && metadata.bodyUuid().equals(bodyUuid)) { - removedKeys.add(key); - } - }); - for (BackendBodyKey removedKey : removedKeys) { - removeBodyHandleFromSpaceIndex(removedKey); - bodyHitMetadataByKey.remove(removedKey); - BodySnapshotMetadata metadata = bodySnapshotMetadataByKey.remove(removedKey); - if (metadata != null && !sameRow(metadata.bodyRef(), replacementRef)) { - int rowIndex = metadata.bodyRef().getIndex(); - bodyRefsByRowIndex.remove(rowIndex); - bodyHandlesByRowIndex.remove(rowIndex); - bodySpaceHandlesByRowIndex.remove(rowIndex); - backendIdsByBodyRowIndex.remove(rowIndex); - chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); - } + BackendBodyKey removedKey = bodySnapshotKeysByUuid.get(bodyUuid); + if (removedKey == null || removedKey.equals(replacementKey)) { + return; + } + removeBodyHandleFromSpaceIndex(removedKey); + bodyHitMetadataByKey.remove(removedKey); + BodySnapshotMetadata metadata = bodySnapshotMetadataByKey.remove(removedKey); + bodySnapshotKeysByUuid.remove(bodyUuid, removedKey); + if (metadata != null && !sameRow(metadata.bodyRef(), replacementRef)) { + int rowIndex = metadata.bodyRef().getIndex(); + bodyRefsByRowIndex.remove(rowIndex); + bodyHandlesByRowIndex.remove(rowIndex); + bodySpaceHandlesByRowIndex.remove(rowIndex); + backendIdsByBodyRowIndex.remove(rowIndex); + chunkCollisionPayloadKeysByRowIndex.remove(rowIndex); } } @@ -671,6 +687,7 @@ private void removeJointMetadata(@Nonnull BackendJointKey key) { if (metadata == null) { return; } + jointKeysByUuid.remove(metadata.jointUuid(), key); int rowIndex = metadata.jointRef().getIndex(); jointHandlesByRowIndex.remove(rowIndex); jointSpaceHandlesByRowIndex.remove(rowIndex); @@ -681,21 +698,18 @@ private void removeJointMetadata(@Nonnull BackendJointKey key) { private void removeJointMetadataForUuid(@Nonnull UUID jointUuid, @Nonnull BackendJointKey replacementKey, @Nonnull Ref replacementRef) { - List removedKeys = new ArrayList<>(); - jointMetadataByKey.forEach((key, metadata) -> { - if (!key.equals(replacementKey) && metadata.jointUuid().equals(jointUuid)) { - removedKeys.add(key); - } - }); - for (BackendJointKey removedKey : removedKeys) { - JointMetadata metadata = jointMetadataByKey.remove(removedKey); - if (metadata != null && !sameRow(metadata.jointRef(), replacementRef)) { - int rowIndex = metadata.jointRef().getIndex(); - jointHandlesByRowIndex.remove(rowIndex); - jointSpaceHandlesByRowIndex.remove(rowIndex); - jointRefsByRowIndex.remove(rowIndex); - backendIdsByJointRowIndex.remove(rowIndex); - } + BackendJointKey removedKey = jointKeysByUuid.get(jointUuid); + if (removedKey == null || removedKey.equals(replacementKey)) { + return; + } + JointMetadata metadata = jointMetadataByKey.remove(removedKey); + jointKeysByUuid.remove(jointUuid, removedKey); + if (metadata != null && !sameRow(metadata.jointRef(), replacementRef)) { + int rowIndex = metadata.jointRef().getIndex(); + jointHandlesByRowIndex.remove(rowIndex); + jointSpaceHandlesByRowIndex.remove(rowIndex); + jointRefsByRowIndex.remove(rowIndex); + backendIdsByJointRowIndex.remove(rowIndex); } } @@ -741,6 +755,13 @@ public void destroyBackendBindings() { failure = appendShutdownFailure(failure, exception); } } + for (PhysicsBackendRuntime runtime : new ArrayList<>(runtimesByBackend.values())) { + try { + runtime.close(); + } catch (RuntimeException exception) { + failure = appendShutdownFailure(failure, exception); + } + } clear(); if (failure != null) { throw failure; @@ -806,11 +827,13 @@ public PhysicsRuntimeResource clone() { copy.jointRefsByRowIndex.putAll(jointRefsByRowIndex); copy.backendIdsByJointRowIndex.putAll(backendIdsByJointRowIndex); copy.jointMetadataByKey.putAll(jointMetadataByKey); + copy.jointKeysByUuid.putAll(jointKeysByUuid); copy.chunkCollisionPayloadKeysByRowIndex.putAll(chunkCollisionPayloadKeysByRowIndex); bodyHandlesBySpaceKey.forEach((key, bodyHandles) -> copy.bodyHandlesBySpaceKey.put(key, new LongArrayList(bodyHandles))); copy.bodyHitMetadataByKey.putAll(bodyHitMetadataByKey); copy.bodySnapshotMetadataByKey.putAll(bodySnapshotMetadataByKey); + copy.bodySnapshotKeysByUuid.putAll(bodySnapshotKeysByUuid); copy.pendingBodyOperations.addAll(pendingBodyOperations); copy.pendingSpaceSettingsByRowIndex.putAll(pendingSpaceSettingsByRowIndex); copy.registrationTopologyGeneration = registrationTopologyGeneration; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java index c008614b..3a7b061f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -34,6 +35,20 @@ public PhysicsSnapshotFrame getLatestFrame() { return snapshot.frame(); } + public long latestSequence() { + return snapshot.sequence(); + } + + /** + * Iterates the compact published body frame without materializing snapshot objects. + * + *

        The cursor instance is reused during iteration. Consumers must read the needed values + * synchronously and must not retain the cursor after the callback returns.

        + */ + public void forEachBodyCursor(@Nonnull Consumer consumer) { + snapshot.forEachBodyCursor(Objects.requireNonNull(consumer, "consumer")); + } + @Nullable public PhysicsBodySnapshot getBody(@Nonnull UUID bodyUuid) { return snapshot.body(Objects.requireNonNull(bodyUuid, "bodyUuid")); @@ -284,11 +299,174 @@ private PhysicsBodySnapshot body(int index) { sleeping[index]); } + private void forEachBodyCursor(@Nonnull Consumer consumer) { + PublishedBodyCursor cursor = new PublishedBodyCursor(this); + for (int index = 0; index < bodyCount(); index++) { + cursor.index = index; + consumer.accept(cursor); + } + } + private float value(int bodyIndex, int valueIndex) { return values[bodyIndex * FLOAT_STRIDE + valueIndex]; } } + public interface BodyCursor { + + @Nullable + Ref bodyRef(); + + @Nonnull + UUID bodyUuid(); + + @Nonnull + UUID spaceUuid(); + + @Nonnull + PhysicsBodyType bodyType(); + + float positionX(); + + float positionY(); + + float positionZ(); + + float rotationX(); + + float rotationY(); + + float rotationZ(); + + float rotationW(); + + float linearVelocityX(); + + float linearVelocityY(); + + float linearVelocityZ(); + + float angularVelocityX(); + + float angularVelocityY(); + + float angularVelocityZ(); + + float centerOfMassOffsetY(); + + boolean sleeping(); + } + + private static final class PublishedBodyCursor implements BodyCursor { + + @Nonnull + private final PublishedSnapshot snapshot; + private int index; + + private PublishedBodyCursor(@Nonnull PublishedSnapshot snapshot) { + this.snapshot = snapshot; + } + + @Nullable + @Override + public Ref bodyRef() { + return snapshot.bodyRefs[index]; + } + + @Nonnull + @Override + public UUID bodyUuid() { + return snapshot.bodyUuids[index]; + } + + @Nonnull + @Override + public UUID spaceUuid() { + return snapshot.spaceUuids[index]; + } + + @Nonnull + @Override + public PhysicsBodyType bodyType() { + return snapshot.bodyTypes[index]; + } + + @Override + public float positionX() { + return snapshot.value(index, PublishedSnapshot.POSITION_X); + } + + @Override + public float positionY() { + return snapshot.value(index, PublishedSnapshot.POSITION_Y); + } + + @Override + public float positionZ() { + return snapshot.value(index, PublishedSnapshot.POSITION_Z); + } + + @Override + public float rotationX() { + return snapshot.value(index, PublishedSnapshot.ROTATION_X); + } + + @Override + public float rotationY() { + return snapshot.value(index, PublishedSnapshot.ROTATION_Y); + } + + @Override + public float rotationZ() { + return snapshot.value(index, PublishedSnapshot.ROTATION_Z); + } + + @Override + public float rotationW() { + return snapshot.value(index, PublishedSnapshot.ROTATION_W); + } + + @Override + public float linearVelocityX() { + return snapshot.value(index, PublishedSnapshot.LINEAR_VELOCITY_X); + } + + @Override + public float linearVelocityY() { + return snapshot.value(index, PublishedSnapshot.LINEAR_VELOCITY_Y); + } + + @Override + public float linearVelocityZ() { + return snapshot.value(index, PublishedSnapshot.LINEAR_VELOCITY_Z); + } + + @Override + public float angularVelocityX() { + return snapshot.value(index, PublishedSnapshot.ANGULAR_VELOCITY_X); + } + + @Override + public float angularVelocityY() { + return snapshot.value(index, PublishedSnapshot.ANGULAR_VELOCITY_Y); + } + + @Override + public float angularVelocityZ() { + return snapshot.value(index, PublishedSnapshot.ANGULAR_VELOCITY_Z); + } + + @Override + public float centerOfMassOffsetY() { + return snapshot.value(index, PublishedSnapshot.CENTER_OF_MASS_OFFSET_Y); + } + + @Override + public boolean sleeping() { + return snapshot.sleeping[index]; + } + } + private static boolean sameRef(@Nullable Ref first, @Nonnull Ref second) { return first != null diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 9026a628..82ea378c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -61,7 +61,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) input.dtCapHit()); } List bodies = completed.bodySnapshots(); - long nextSequence = snapshot.getLatestFrame().sequence() + 1L; + long nextSequence = snapshot.latestSequence() + 1L; float frameDt = input != null ? input.submittedDtSeconds() : dt; PhysicsSnapshotFrame frame = new PhysicsSnapshotFrame(nextSequence, frameDt, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index b69e48e7..7e990997 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -20,7 +20,11 @@ import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -59,6 +63,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } try { + prepareTransientRestoreState(store); PhysicsStoreHolderStorage.LoadResult holderLoad = PhysicsStoreHolderStorage.load(store); if (holderLoad.present()) { restore.markComplete(); @@ -87,6 +92,14 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) restore.markHydrated(); } + private static void prepareTransientRestoreState(@Nonnull Store store) { + store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings(); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); + store.getExternalData().clearUuidIndex(); + store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); + store.getResource(PhysicsEventResource.getResourceType()).clear(); + } + private static void hydrateRows(@Nonnull Store store, @Nonnull PersistentPhysicsStoreResource persistent) { for (PersistentSpaceDto dto : persistent.getSpaces()) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java index 1bfa3fff..a75c73fe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -59,8 +60,8 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = store.getResource( - PhysicsChunkCollisionPayloadResource.getResourceType()); + PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = + PhysicsChunkStoreTypes.collisionPayloadsIfPresent(store); PhysicsIdentityIndexResource identity = store.getResource(PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = @@ -69,7 +70,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + @Nullable PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ArchetypeChunk chunk) { @@ -103,7 +104,7 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, } private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + @Nullable PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Ref bodyRef, @@ -226,7 +227,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, } } - private static long createVoxelBody(@Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + private static long createVoxelBody(@Nullable PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendSpaceHandle spaceHandle, @Nonnull ShapeComponent shape, @@ -237,6 +238,9 @@ private static long createVoxelBody(@Nonnull PhysicsChunkCollisionPayloadResourc if (payloadKey.isBlank() || !backendRuntime.supportsVoxelTerrain(spaceHandle.value())) { return Long.MIN_VALUE; } + if (chunkCollisionPayloads == null) { + return Long.MIN_VALUE; + } ChunkCollisionPayload payload = chunkCollisionPayloads.get(payloadKey); if (payload == null || !payload.hasFullCubeVoxels()) { return Long.MIN_VALUE; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java index 1ee49a2e..310a0f4d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.api.Impulse; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -55,19 +54,16 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsStepMode stepMode = store.getResource(PhysicsWorldSettingsResource.getResourceType()) .getSettings() .getStepMode(); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindChunk(runtime, compatibility, identity, restore, stepMode, chunk); + (chunk, _) -> bindChunk(runtime, compatibility, restore, stepMode, chunk); store.forEachChunk(systemIndex, collector); } private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull PhysicsStepMode stepMode, @Nonnull ArchetypeChunk chunk) { @@ -88,7 +84,6 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, } bindSpace(runtime, compatibility, - identity, restore, stepMode, spaceRef, @@ -120,7 +115,6 @@ private static void validateBoundSpaceBackend(@Nonnull PhysicsRuntimeResource ru private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull PhysicsStepMode stepMode, @Nonnull Ref ref, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index c04f483b..5bd2c683 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -67,6 +67,7 @@ private boolean markPublished(@Nonnull Store store, long frameSeque private static void recordProfiling(@Nonnull Store store, @Nonnull Store physics) { + assert PhysicsRuntimeProfilingResource.getResourceType() != null; PhysicsRuntimeProfilingResource runtimeProfiling = store.getResource( PhysicsRuntimeProfilingResource.getResourceType()); if (!runtimeProfiling.isEnabled()) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java index 45371bc3..7d485360 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java @@ -25,16 +25,25 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.early.PhysicsStoreHooks; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; @@ -75,6 +84,7 @@ class PhysicsStoreHolderPersistenceTest { private static final UUID GENERATED_BODY_UUID = uuid(4); private static final UUID JOINT_UUID = uuid(5); private static final UUID LEGACY_SPACE_UUID = uuid(6); + private static final BackendId HOLDER_BACKEND_ID = new BackendId("test:holder-persistence"); @TempDir Path tempDir; @@ -276,6 +286,124 @@ void holderHydrationRejectsBodyWithoutSavedSpaceWithoutAddingPartialRows() { } } + @Test + void registeredStoreReloadBindsSavedBodiesOnce() { + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider(HOLDER_BACKEND_ID, false, false); + Impulse.registerRuntimeProvider(provider); + Path savePath = tempDir.resolve("registered-reload"); + + StoreFixture source = registeredStore("registered-reload-source", savePath); + try { + Ref spaceRef = addSpace(source.store(), SPACE_UUID); + addBody(source.store(), BODY_A_UUID, spaceRef, null); + addBody(source.store(), BODY_B_UUID, spaceRef, null); + + source.store().tick(0.0f); + + BackendSpaceHandle sourceHandle = source.store() + .getResource(PhysicsRuntimeResource.getResourceType()) + .getSpaceHandle(spaceRef); + assertNotNull(sourceHandle); + assertEquals(1, provider.createdRuntimes().size()); + assertEquals(2, provider.createdRuntimes().get(0).bodyCount(sourceHandle.value())); + + PhysicsStoreHooks.shutdown(source.store().getExternalData()); + } finally { + source.close(); + } + + StoreFixture target = registeredStore("registered-reload-target", savePath); + try { + target.store().tick(0.0f); + + PhysicsRestoreStatusResource restore = target.store().getResource( + PhysicsRestoreStatusResource.getResourceType()); + assertTrue(restore.isHydrated()); + assertFalse(restore.isFailed()); + assertTrue(restore.getSoftSkipsByReason().isEmpty()); + List rowUuids = rowUuids(target.store()); + assertEquals(3, rowUuids.size()); + assertTrue(rowUuids.contains(SPACE_UUID)); + assertTrue(rowUuids.contains(BODY_A_UUID)); + assertTrue(rowUuids.contains(BODY_B_UUID)); + + Ref spaceRef = target.store() + .getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(SPACE_UUID); + assertNotNull(spaceRef); + PhysicsRuntimeResource runtime = target.store().getResource( + PhysicsRuntimeResource.getResourceType()); + BackendSpaceHandle handle = runtime.getSpaceHandle(spaceRef); + assertNotNull(handle); + assertEquals(2, provider.createdRuntimes().size()); + FakePhysicsBackendRuntime reloadedRuntime = provider.createdRuntimes().get(1); + assertTrue(reloadedRuntime.hasSpace(handle.value())); + assertEquals(2, reloadedRuntime.bodyCount(handle.value())); + + target.store().tick(0.05f); + target.store() + .getResource(PhysicsStepSchedulerResource.getResourceType()) + .whenIdle() + .toCompletableFuture() + .join(); + target.store().tick(0.0f); + + assertEquals(2, target.store() + .getResource(PhysicsSnapshotResource.getResourceType()) + .getLatestFrame() + .bodies() + .size()); + } finally { + target.close(); + } + } + + @Test + void holderHydrationClosesStaleUntrackedBackendRuntimeBeforeRebinding() { + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider(HOLDER_BACKEND_ID, false, false); + Impulse.registerRuntimeProvider(provider); + Path savePath = tempDir.resolve("stale-backend-runtime"); + SpaceId compatibilitySpaceId = new SpaceId(2); + + StoreFixture source = store("stale-runtime-source", savePath); + try { + Ref spaceRef = addSpace(source.store(), SPACE_UUID); + addBody(source.store(), BODY_A_UUID, spaceRef, null); + PhysicsStoreHolderStorage.save(source.store()).join(); + } finally { + source.close(); + } + + StoreFixture target = registeredStore("stale-runtime-target", savePath); + try { + FakePhysicsBackendRuntime staleRuntime = (FakePhysicsBackendRuntime) + provider.createRuntime(); + staleRuntime.createSpace(compatibilitySpaceId); + target.store() + .getResource(PhysicsRuntimeResource.getResourceType()) + .putRuntime(HOLDER_BACKEND_ID, staleRuntime); + target.store() + .getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(compatibilitySpaceId, SPACE_UUID); + + target.store().tick(0.0f); + + PhysicsRestoreStatusResource restore = target.store().getResource( + PhysicsRestoreStatusResource.getResourceType()); + assertTrue(restore.isHydrated()); + assertFalse(restore.isFailed(), restore.getFailureMessage()); + assertFalse(staleRuntime.hasSpace(compatibilitySpaceId.value())); + assertEquals(2, provider.createdRuntimes().size()); + FakePhysicsBackendRuntime reboundRuntime = provider.createdRuntimes().get(1); + assertTrue(reboundRuntime.hasSpace(compatibilitySpaceId.value())); + assertEquals(1, reboundRuntime.bodyCount(compatibilitySpaceId.value())); + } finally { + target.close(); + } + } + @Nonnull private static StoreFixture store(@Nonnull String worldName, @Nonnull Path savePath) { ComponentRegistry registry = new ComponentRegistry<>(); @@ -285,6 +413,7 @@ private static StoreFixture store(@Nonnull String worldName, @Nonnull Path saveP PhysicsResourceTypes.registerResourceTypes(proxy); PhysicsStore physicsStore = new PhysicsStore(world(worldName, savePath)); Store store = registry.addStore(physicsStore, EmptyResourceStorage.get()); + setField(physicsStore, PhysicsStore.class, "store", store); return new StoreFixture(registry, store); } @@ -294,9 +423,11 @@ private static StoreFixture registeredStore(@Nonnull String worldName, @Nonnull ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); PhysicsStoreRegistration.register(proxy); PhysicsStore physicsStore = new PhysicsStore(world(worldName, savePath)); Store store = registry.addStore(physicsStore, EmptyResourceStorage.get()); + setField(physicsStore, PhysicsStore.class, "store", store); return new StoreFixture(registry, store); } @@ -312,8 +443,7 @@ private static Ref addSpace(@Nonnull Store store, @Nonnull UUID spaceUuid) { Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, spaceUuid, - new SpaceComponent(new BackendId("test:holder-persistence"), - new Vector3f(0.0f, -9.81f, 0.0f))), + new SpaceComponent(HOLDER_BACKEND_ID, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(ref); return ref; @@ -488,7 +618,9 @@ private record StoreFixture(@Nonnull ComponentRegistry registry, @Nonnull Store store) { private void close() { - registry.removeStore(store); + if (!store.isShutdown()) { + registry.removeStore(store); + } registry.shutdown(); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index fe68da89..e084b122 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -1,9 +1,11 @@ package dev.hytalemodding.impulse.core.internal.resources; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; @@ -238,6 +240,30 @@ void runtimeBodyHandleReplacementRemovesPreviousSpaceIndexEntry() { runtime.getBodySnapshotMetadata(backendId, spaceHandle, secondHandle.value()).bodyUuid()); } + @Test + void runtimeBindsLargeDistinctBodyMetadataSetWithoutQuadraticDuplicateScan() { + PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000033"); + BackendId backendId = new BackendId("test:runtime-body-metadata-scale"); + BackendSpaceHandle spaceHandle = new BackendSpaceHandle(71); + Ref spaceRef = new TestRef(70); + runtime.putSpaceHandle(spaceRef, backendId, spaceHandle); + runtime.putSpaceMetadata(backendId, spaceHandle, spaceUuid, spaceRef); + + for (int index = 0; index < 20_000; index++) { + Ref bodyRef = new TestRef(1_000 + index); + BackendBodyHandle bodyHandle = new BackendBodyHandle(10_000L + index); + runtime.putBodyHandle(bodyRef, spaceRef, spaceHandle, bodyHandle); + runtime.putBodySnapshotMetadata(backendId, + spaceHandle, + bodyHandle, + new UUID(0L, 1_000_000L + index), + bodyRef, + spaceUuid); + } + assertEquals(20_000, runtime.bodyHandleCount(backendId, spaceHandle)); + } + @Test void runtimeRefreshRebuildsRefIndexesFromScopedBackendMetadata() { PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); @@ -342,6 +368,33 @@ void snapshotResourceIndexesLatestPublishedFrameByBodyUuid() { assertNull(resource.getBody(bodyUuid)); } + @Test + void snapshotResourceCursorReadsCompactBodyStateWithoutFrameMaterialization() { + PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000026"); + UUID firstBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000027"); + UUID secondBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000028"); + Ref firstBodyRef = new TestRef(27); + Ref secondBodyRef = new TestRef(28); + PhysicsBodySnapshot first = snapshot(firstBodyRef, firstBodyUuid, spaceUuid); + PhysicsBodySnapshot second = snapshot(secondBodyRef, secondBodyUuid, spaceUuid); + resource.publish(new PhysicsSnapshotFrame(14L, 0.05f, List.of(first, second))); + + List visited = new ArrayList<>(); + resource.forEachBodyCursor(cursor -> { + visited.add(cursor.bodyUuid()); + assertEquals(spaceUuid, cursor.spaceUuid()); + assertEquals(PhysicsBodyType.KINEMATIC, cursor.bodyType()); + assertTrue(cursor.bodyRef() == firstBodyRef || cursor.bodyRef() == secondBodyRef); + assertTrue(cursor.positionX() > 0.0f); + assertEquals(0.0f, cursor.centerOfMassOffsetY(), 0.0001f); + assertFalse(cursor.sleeping()); + }); + + assertEquals(14L, resource.latestSequence()); + assertEquals(List.of(firstBodyUuid, secondBodyUuid), visited); + } + @Test void snapshotResourceRemovesMultipleBodiesInOneBatch() { PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java new file mode 100644 index 00000000..3b28d82e --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java @@ -0,0 +1,142 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import java.util.ArrayList; +import java.util.UUID; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class BodyBindingSystemTest { + + private static final BackendId BACKEND_ID = new BackendId("test:body-binding-no-physicschunk"); + + @Test + void nonVoxelBodyBindingDoesNotRequirePhysicsChunkPayloadResource() { + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider(BACKEND_ID, false, false); + Impulse.registerRuntimeProvider(provider); + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + proxy.registerSystem(new BodyBindingSystem()); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("body-binding-no-physicschunk-test")), + EmptyResourceStorage.get()); + try { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + Ref spaceRef = addSpace(store, uuid(1)); + Ref bodyRef = addBody(store, uuid(2), uuid(1), spaceRef); + + assertDoesNotThrow(() -> store.tick(0.0f)); + + assertFalse(restore.isFailed(), restore.getFailureMessage()); + assertEquals(0, restore.getSoftSkipsByReason().size()); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceRef); + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyRef); + assertNotNull(spaceHandle); + assertNotNull(bodyHandle); + FakePhysicsBackendRuntime backendRuntime = provider.createdRuntimes().get(0); + assertEquals(1, backendRuntime.bodyCount(spaceHandle.value())); + } finally { + if (!store.isShutdown()) { + registry.removeStore(store); + } + registry.shutdown(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + } + } + + private static Ref addSpace(Store store, UUID spaceUuid) { + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(BACKEND_ID, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + return spaceRef; + } + + private static Ref addBody(Store store, + UUID bodyUuid, + UUID spaceUuid, + Ref spaceRef) { + BodyComponent body = new BodyComponent(spaceUuid); + body.setSpaceRef(spaceRef); + TargetComponent target = new TargetComponent(); + target.setActive(true); + Ref bodyRef = store.addEntity(PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false), + target, + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.6f, 0.1f), + new CollisionFilterComponent(PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.ALL)), + AddReason.SPAWN); + assertNotNull(bodyRef); + return bodyRef; + } + + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java index b4271465..df3a0a48 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java @@ -96,7 +96,6 @@ private static void runSpaceBindingSystem(@Nonnull Store store) { Method bindChunk = SpaceBindingSystem.class.getDeclaredMethod("bindChunk", PhysicsRuntimeResource.class, PhysicsSpaceCompatibilityIndexResource.class, - PhysicsIdentityIndexResource.class, PhysicsRestoreStatusResource.class, PhysicsStepMode.class, ArchetypeChunk.class); @@ -105,8 +104,6 @@ private static void runSpaceBindingSystem(@Nonnull Store store) { PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); PhysicsStepMode stepMode = @@ -117,7 +114,6 @@ private static void runSpaceBindingSystem(@Nonnull Store store) { (chunk, _) -> invoke(bindChunk, runtime, compatibility, - identity, restore, stepMode, chunk); From 79aa2c068de13d49c2c731ccecff8c5b5266369a Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 10:18:01 +0200 Subject: [PATCH 496/534] fix(core): delete spaces on owner lane Move the destructive delete path behind a world-thread helper, preserve populated-space guards, and cover invalid, missing, unbound, empty, and registered-body cases without private-command reflection. Signed-off-by: Blovien --- .../core/internal/commands/SpaceCommand.java | 131 ++++++------ .../internal/commands/SpaceDeleteSupport.java | 135 ++++++++++++ .../commands/SpaceCommandDeleteTest.java | 202 ++++++++++++++++++ 3 files changed, 400 insertions(+), 68 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index c138f8fe..6654bf40 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -1,6 +1,5 @@ package dev.hytalemodding.impulse.core.internal.commands; -import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; @@ -17,12 +16,11 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.internal.commands.SpaceDeleteSupport.DeleteResult; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; @@ -31,6 +29,7 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -171,76 +170,66 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext context, return CompletableFuture.completedFuture(null); } - Store physicsStore = PhysicsThreading.store(world); - SpaceSelection.SelectedSpace selectedSpace = SpaceSelection.resolveStoreSpace(context, - world, - spaceArg); - if (selectedSpace == null) { - return CompletableFuture.completedFuture(null); - } - SpaceId spaceId = selectedSpace.spaceId(); - - /* - * Backend-only bodies can be generated by systems such as streaming PhysicsChunk collision. - * Those bodies belong to the space/cache lifecycle and are removed when the space is - * deleted. Registered bodies are gameplay/runtime resources addressed by durable body - * UUID or live PhysicsStore entity ref, so they still require an explicit clean/destroy - * before deleting the space. - */ - int registeredBodies = countRegisteredBodies(physicsStore, spaceId); - return PhysicsAsync.acceptOnWorldThread(world, - PhysicsDiagnostics.spaceSummariesAsync(world, selectedSpace.spaceRef()), - summaries -> deleteIfEmpty(context, - world, - physicsStore, - selectedSpace.spaceRef(), - spaceId, - spaceId.value(), - registeredBodies, - summaries)); + int rawSpaceId = spaceArg.get(context); + return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + "delete PhysicsStore space", + physicsStore -> SpaceDeleteSupport.deleteOnWorldThread(world, + physicsStore, + rawSpaceId)) + .handle((result, failure) -> { + sendDeleteResult(context, world, result, failure); + return (Void) null; + }) + .toCompletableFuture(); } - private static void deleteIfEmpty(@Nonnull CommandContext context, + private static void sendDeleteResult(@Nonnull CommandContext context, @Nonnull World world, - @Nonnull Store physicsStore, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - int rawSpaceId, - int registeredBodies, - @Nonnull List summaries) { - SpaceCounts counts = countSpaceContents(summaries, spaceId); - int backendBodies = counts.bodies(); - int joints = counts.joints(); - if (registeredBodies > 0 || joints > 0) { - context.sendMessage(Message.raw("Physics space id=" + rawSpaceId - + " is not empty (" + registeredBodies + " registered bodies, " - + backendBodies + " backend bodies, " + joints + " joints)." - + " Use /impulse clean for populated worlds, then delete empty spaces.")); + @Nullable DeleteResult result, + @Nullable Throwable failure) { + Runnable sender = () -> { + if (failure != null) { + Throwable cause = unwrap(failure); + String message = cause.getMessage() != null + ? cause.getMessage() + : cause.toString(); + context.sendMessage(Message.raw("Failed to delete physics space: " + + message)); + return; + } + if (result == null) { + context.sendMessage(Message.raw("Failed to delete physics space.")); + return; + } + switch (result.outcome()) { + case INVALID -> context.sendMessage(Message.raw( + "Space id must be a positive integer.")); + case MISSING -> context.sendMessage(Message.raw("No physics space id=" + + result.rawSpaceId() + + " exists in world " + world.getName() + ".")); + case UNBOUND -> context.sendMessage(Message.raw("PhysicsStore space id=" + + result.rawSpaceId() + + " is not bound in world " + world.getName() + ".")); + case NOT_EMPTY -> context.sendMessage(Message.raw("Physics space id=" + + result.rawSpaceId() + + " is not empty (" + result.registeredBodies() + + " registered bodies, " + result.backendBodies() + + " backend bodies, " + result.joints() + " joints)." + + " Use /impulse clean for populated worlds, then delete empty spaces.")); + case DELETED -> context.sendMessage(Message.raw("Deleted physics space id=" + + result.rawSpaceId() + + " with " + result.backendBodies() + + " backend bodies and " + result.joints() + " joints.")); + } + }; + if (world.isInThread()) { + sender.run(); return; } - - PhysicsChunkCollision.clearSpace(world, physicsStore, spaceRef); - PhysicsSpaces.removeWithContents(physicsStore, spaceId); - context.sendMessage(Message.raw("Deleted physics space id=" + rawSpaceId - + " with " + backendBodies + " backend bodies and " + joints + " joints.")); + world.execute(sender); } } - @Nonnull - private static SpaceCounts countSpaceContents(@Nonnull List summaries, - @Nonnull SpaceId spaceId) { - return summaries.stream() - .filter(summary -> summary.spaceId().equals(spaceId)) - .findFirst() - .map(summary -> new SpaceCounts(summary.bodyCount(), summary.jointCount())) - .orElseGet(() -> new SpaceCounts(0, 0)); - } - - private static int countRegisteredBodies(@Nonnull Store physicsStore, - @Nonnull SpaceId spaceId) { - return PhysicsBodies.registrationCount(physicsStore, spaceId); - } - @Nullable private static BackendId parseBackendId(@Nonnull CommandContext context, @Nonnull OptionalArg backendArg, @@ -289,13 +278,19 @@ private static String availableBackendIds() { return backendIds.isEmpty() ? "" : String.join(", ", backendIds); } - private record SpaceCounts(int bodies, int joints) { - } - private record SpaceListEntry(@Nonnull SpaceId spaceId, @Nonnull String backendId, int bodies, int joints, @Nonnull PhysicsChunkCollisionMode physicsChunkMode) { } + + @Nonnull + private static Throwable unwrap(@Nonnull Throwable failure) { + if (failure instanceof CompletionException completionException + && completionException.getCause() != null) { + return completionException.getCause(); + } + return failure; + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java new file mode 100644 index 00000000..592acf2b --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java @@ -0,0 +1,135 @@ +package dev.hytalemodding.impulse.core.internal.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; + +final class SpaceDeleteSupport { + + private SpaceDeleteSupport() { + } + + @Nonnull + static DeleteResult deleteOnWorldThread( + @Nonnull World world, + @Nonnull Store physicsStore, + int rawSpaceId) { + PhysicsThreading.requireWorldThread(physicsStore, "delete PhysicsStore space"); + if (rawSpaceId <= 0) { + return DeleteResult.invalid(rawSpaceId); + } + + PhysicsSpaceCompatibilityIndexResource compatibility = physicsStore.getResource( + PhysicsSpaceCompatibilityIndexResource.getResourceType()); + SpaceId spaceId = SpaceSelection.specifiedSpaceId(compatibility, rawSpaceId); + if (spaceId == null) { + return DeleteResult.missing(rawSpaceId); + } + + UUID spaceUuid = compatibility.getSpaceUuid(spaceId); + Ref spaceRef = spaceUuid != null + ? physicsStore.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(spaceUuid) + : null; + if (spaceRef == null || spaceRef.getStore() != physicsStore || !spaceRef.isValid()) { + return DeleteResult.unbound(rawSpaceId); + } + + /* + * Backend-only bodies can be generated by systems such as streaming PhysicsChunk collision. + * Those bodies belong to the space/cache lifecycle and are removed when the space/cache is + * deleted. Registered bodies are gameplay/runtime resources addressed by durable body + * UUID or live PhysicsStore entity ref, so they still require an explicit clean/destroy + * before deleting the space. + */ + int registeredBodies = PhysicsBodies.registrationCount(physicsStore, spaceId); + List summaries = PhysicsDiagnostics.spaceSummaries(physicsStore, + spaceRef); + SpaceCounts counts = countSpaceContents(summaries, spaceId); + int backendBodies = counts.bodies(); + int joints = counts.joints(); + if (registeredBodies > 0 || joints > 0) { + return DeleteResult.notEmpty(rawSpaceId, + registeredBodies, + backendBodies, + joints); + } + + PhysicsChunkCollision.clearSpace(world, physicsStore, spaceRef); + PhysicsSpaces.removeWithContents(physicsStore, spaceId); + return DeleteResult.deleted(rawSpaceId, backendBodies, joints); + } + + @Nonnull + private static SpaceCounts countSpaceContents(@Nonnull List summaries, + @Nonnull SpaceId spaceId) { + return summaries.stream() + .filter(summary -> summary.spaceId().equals(spaceId)) + .findFirst() + .map(summary -> new SpaceCounts(summary.bodyCount(), summary.jointCount())) + .orElseGet(() -> new SpaceCounts(0, 0)); + } + + enum DeleteOutcome { + INVALID, + MISSING, + UNBOUND, + NOT_EMPTY, + DELETED + } + + record DeleteResult(@Nonnull DeleteOutcome outcome, + int rawSpaceId, + int registeredBodies, + int backendBodies, + int joints) { + + @Nonnull + private static DeleteResult invalid(int rawSpaceId) { + return new DeleteResult(DeleteOutcome.INVALID, rawSpaceId, 0, 0, 0); + } + + @Nonnull + private static DeleteResult missing(int rawSpaceId) { + return new DeleteResult(DeleteOutcome.MISSING, rawSpaceId, 0, 0, 0); + } + + @Nonnull + private static DeleteResult unbound(int rawSpaceId) { + return new DeleteResult(DeleteOutcome.UNBOUND, rawSpaceId, 0, 0, 0); + } + + @Nonnull + private static DeleteResult notEmpty(int rawSpaceId, + int registeredBodies, + int backendBodies, + int joints) { + return new DeleteResult(DeleteOutcome.NOT_EMPTY, + rawSpaceId, + registeredBodies, + backendBodies, + joints); + } + + @Nonnull + private static DeleteResult deleted(int rawSpaceId, int backendBodies, int joints) { + return new DeleteResult(DeleteOutcome.DELETED, rawSpaceId, 0, backendBodies, joints); + } + } + + private record SpaceCounts(int bodies, int joints) { + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java new file mode 100644 index 00000000..5b278a5c --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java @@ -0,0 +1,202 @@ +package dev.hytalemodding.impulse.core.internal.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class SpaceCommandDeleteTest { + + private static final BackendId BACKEND_ID = new BackendId("test:space-delete"); + + @Test + void deleteCoreReportsInvalidMissingAndUnboundSpaces() { + StoreFixture fixture = store("space-delete-invalid"); + try { + Store store = fixture.store(); + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(92); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(new SpaceId(92), spaceUuid); + + assertEquals(SpaceDeleteSupport.DeleteOutcome.INVALID, + deleteOnWorldThread(store, 0).outcome()); + assertEquals(SpaceDeleteSupport.DeleteOutcome.MISSING, + deleteOnWorldThread(store, 93).outcome()); + assertEquals(SpaceDeleteSupport.DeleteOutcome.UNBOUND, + deleteOnWorldThread(store, 92).outcome()); + } finally { + fixture.close(); + } + } + + @Test + void deleteCoreRemovesEmptySpaceOnWorldThread() { + StoreFixture fixture = store("space-delete-core"); + try { + Store store = fixture.store(); + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(91); + Ref spaceRef = addSpace(store, spaceUuid); + bindSpaceId(store, new SpaceId(91), spaceUuid, spaceRef); + + SpaceDeleteSupport.DeleteResult result = deleteOnWorldThread(store, 91); + + assertEquals(SpaceDeleteSupport.DeleteOutcome.DELETED, result.outcome()); + assertEquals(91, result.rawSpaceId()); + assertEquals(0, result.backendBodies()); + assertEquals(0, result.joints()); + assertFalse(spaceRef.isValid()); + } finally { + fixture.close(); + } + } + + @Test + void deleteCoreRejectsRegisteredBodies() { + StoreFixture fixture = store("space-delete-not-empty"); + try { + Store store = fixture.store(); + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(94); + Ref spaceRef = addSpace(store, spaceUuid); + bindSpaceId(store, new SpaceId(94), spaceUuid, spaceRef); + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(PhysicsBodySnapshot.of(uuid(95), + spaceUuid, + PhysicsBodyType.DYNAMIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false)))); + + SpaceDeleteSupport.DeleteResult result = deleteOnWorldThread(store, 94); + + assertEquals(SpaceDeleteSupport.DeleteOutcome.NOT_EMPTY, result.outcome()); + assertEquals(1, result.registeredBodies()); + assertEquals(0, result.backendBodies()); + assertEquals(0, result.joints()); + assertTrue(spaceRef.isValid()); + } finally { + fixture.close(); + } + } + + private static void bindSpaceId(@Nonnull Store store, + @Nonnull SpaceId spaceId, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef) { + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(spaceId, spaceUuid); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putUuid(spaceUuid, spaceRef); + } + + @Nonnull + private static StoreFixture store(@Nonnull String worldName) { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world(worldName)), + EmptyResourceStorage.get()); + return new StoreFixture(registry, store); + } + + @Nonnull + private static SpaceDeleteSupport.DeleteResult deleteOnWorldThread( + @Nonnull Store store, + int rawSpaceId) { + return SpaceDeleteSupport.deleteOnWorldThread(store.getExternalData().getWorld(), + store, + rawSpaceId); + } + + @Nonnull + private static Ref addSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(BACKEND_ID, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(ref); + return ref; + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private record StoreFixture(@Nonnull ComponentRegistry registry, + @Nonnull Store store) { + + private void close() { + if (!store.isShutdown()) { + registry.removeStore(store); + } + registry.shutdown(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + } + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } +} From 61faddc7f953a9d507796e591c20a35bb962593a Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 10:18:16 +0200 Subject: [PATCH 497/534] fix(core): keep drop-pending dt cadence steady Track dropped pending ticks so drop_pending_dt does not resubmit a large catch-up dt after the owner lane clears. Keep accumulate_pending_dt behavior covered separately. Signed-off-by: Blovien --- .../StepSchedulingSettingCommand.java | 4 +- .../PhysicsStepSchedulerResource.java | 17 ++- .../settings/PhysicsStepSchedulingMode.java | 4 +- .../PhysicsStepSchedulerResourceTest.java | 108 +++++++++++++++++- .../PhysicsStepSchedulingModeTest.java | 2 +- .../commands/stress/StressBodiesCommand.java | 1 + 6 files changed, 127 insertions(+), 9 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java index dab1390c..9101550f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepSchedulingSettingCommand.java @@ -22,7 +22,7 @@ public class StepSchedulingSettingCommand extends AbstractAsyncPlayerCommand { private final OptionalArg modeArg = this.withOptionalArg( "mode", - "Step scheduling mode: drop_pending_dt or accumulate_pending_dt", + "Step scheduling mode: drop_pending_dt keeps cadence steady; accumulate_pending_dt catches up", ArgTypes.STRING); public StepSchedulingSettingCommand() { @@ -50,7 +50,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, mode = PhysicsStepSchedulingMode.parse(modeArg.get(ctx)); } catch (IllegalArgumentException exception) { ctx.sender().sendMessage(Message.raw("Unknown step scheduling mode. Use one of: " - + "drop_pending_dt, accumulate_pending_dt.")); + + "drop_pending_dt (steady/no catch-up), accumulate_pending_dt (catch-up).")); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java index 06bb001a..f21363d3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java @@ -38,6 +38,8 @@ public final class PhysicsStepSchedulerResource implements Resource 0.0f) { + float steadyDtSeconds = Math.min(candidateDtSeconds, lastSubmittedDtSeconds); + droppedDtSeconds = candidateDtSeconds - steadyDtSeconds; + candidateDtSeconds = steadyDtSeconds; + } backlogDtSeconds = 0.0f; } SubmittedDt submittedDt = capSubmittedDt(candidateDtSeconds, maxSubmittedDtSeconds); + if (submittedDt.submittedDtSeconds() > 0.0f) { + lastSubmittedDtSeconds = submittedDt.submittedDtSeconds(); + } backlogDtSeconds = 0.0f; + droppedPendingDtSinceLastStep = false; return new StepInput(inputDtSeconds, submittedDt.submittedDtSeconds(), backlogDtSeconds, - submittedDt.droppedDtSeconds(), + droppedDtSeconds + submittedDt.droppedDtSeconds(), submittedDt.dtCapHit()); } @@ -157,8 +170,10 @@ private StepInput accumulatePendingDt(float dtSeconds, return new StepInput(0.0f, 0.0f, backlogDtSeconds, 0.0f, false); } if (mode != PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT) { + droppedPendingDtSinceLastStep = true; return new StepInput(inputDtSeconds, 0.0f, backlogDtSeconds, inputDtSeconds, false); } + droppedPendingDtSinceLastStep = false; SubmittedDt submittedDt = capSubmittedDt(backlogDtSeconds + inputDtSeconds, maxSubmittedDtSeconds); backlogDtSeconds = submittedDt.submittedDtSeconds(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java index 7267ca3a..6d921767 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsStepSchedulingMode.java @@ -9,7 +9,7 @@ */ public enum PhysicsStepSchedulingMode { /** - * Pending store tick steps do not add their {@code dt} to the next accepted step. + * Pending store tick steps and post-skip catch-up {@code dt} are dropped. */ DROP_PENDING_DT("drop_pending_dt"), @@ -34,7 +34,7 @@ public String getSerializedName() { @Nonnull public String describePendingStepBehavior() { return switch (this) { - case DROP_PENDING_DT -> "drop dt while a store tick step is pending"; + case DROP_PENDING_DT -> "drop pending dt and prevent post-skip catch-up"; case ACCUMULATE_PENDING_DT -> "accumulate pending dt for one capped catch-up step"; }; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java index adf2dc58..3f9c1d7a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java @@ -59,6 +59,97 @@ void submittedStepRunsAsynchronouslyAndSkipsWhilePending() throws Exception { scheduler.close(); } + @Test + void dropPendingDtDoesNotCatchUpWithLargePostSkipDt() throws Exception { + PhysicsStepSchedulerResource scheduler = new PhysicsStepSchedulerResource(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + PhysicsStepSchedulerResource.StepInput input = scheduler.acceptStepInput(0.05f, + PhysicsStepSchedulingMode.DROP_PENDING_DT, + 0.50f); + assertEquals(0.05f, input.submittedDtSeconds(), 0.0001f); + assertTrue(scheduler.submitStep(input, () -> { + entered.countDown(); + awaitOrFail(release); + return new PhysicsStepSchedulerResource.CompletedStep(1, + 1, + 10L, + PhysicsStepPhaseStats.unavailable()); + }, 10L)); + assertTrue(entered.await(5, TimeUnit.SECONDS)); + + PhysicsStepSchedulerResource.TickDecision skipped = scheduler.beforeStoreTick(0.05f, + PhysicsStepSchedulingMode.DROP_PENDING_DT, + 0.50f, + 20L); + assertFalse(skipped.shouldTick()); + assertEquals(0.05f, skipped.droppedBacklogDtSeconds(), 0.0001f); + + release.countDown(); + scheduler.whenIdle().toCompletableFuture().get(5, TimeUnit.SECONDS); + PhysicsStepSchedulerResource.TickDecision allowed = scheduler.beforeStoreTick(0.20f, + PhysicsStepSchedulingMode.DROP_PENDING_DT, + 0.50f, + 30L); + assertTrue(allowed.shouldTick()); + + PhysicsStepSchedulerResource.StepInput postSkipInput = scheduler.acceptStepInput(0.20f, + PhysicsStepSchedulingMode.DROP_PENDING_DT, + 0.50f); + assertEquals(0.20f, postSkipInput.inputDtSeconds(), 0.0001f); + assertEquals(0.05f, postSkipInput.submittedDtSeconds(), 0.0001f); + assertEquals(0.0f, postSkipInput.backlogDtSeconds(), 0.0001f); + assertEquals(0.15f, postSkipInput.droppedBacklogDtSeconds(), 0.0001f); + assertFalse(postSkipInput.dtCapHit()); + scheduler.close(); + } + + @Test + void accumulatePendingDtStillCatchesUpAfterPendingSkip() throws Exception { + PhysicsStepSchedulerResource scheduler = new PhysicsStepSchedulerResource(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + PhysicsStepSchedulerResource.StepInput input = scheduler.acceptStepInput(0.05f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.50f); + assertTrue(scheduler.submitStep(input, () -> { + entered.countDown(); + awaitOrFail(release); + return new PhysicsStepSchedulerResource.CompletedStep(1, + 1, + 10L, + PhysicsStepPhaseStats.unavailable()); + }, 10L)); + assertTrue(entered.await(5, TimeUnit.SECONDS)); + + PhysicsStepSchedulerResource.TickDecision skipped = scheduler.beforeStoreTick(0.05f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.50f, + 20L); + assertFalse(skipped.shouldTick()); + assertEquals(0.05f, skipped.backlogDtSeconds(), 0.0001f); + + release.countDown(); + scheduler.whenIdle().toCompletableFuture().get(5, TimeUnit.SECONDS); + PhysicsStepSchedulerResource.TickDecision allowed = scheduler.beforeStoreTick(0.20f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.50f, + 30L); + assertTrue(allowed.shouldTick()); + + PhysicsStepSchedulerResource.StepInput postSkipInput = scheduler.acceptStepInput(0.20f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.50f); + assertEquals(0.20f, postSkipInput.inputDtSeconds(), 0.0001f); + assertEquals(0.25f, postSkipInput.submittedDtSeconds(), 0.0001f); + assertEquals(0.0f, postSkipInput.backlogDtSeconds(), 0.0001f); + assertEquals(0.0f, postSkipInput.droppedBacklogDtSeconds(), 0.0001f); + assertFalse(postSkipInput.dtCapHit()); + scheduler.close(); + } + @Test void whenIdleCompletesAfterPendingStepFinishes() throws Exception { PhysicsStepSchedulerResource scheduler = new PhysicsStepSchedulerResource(); @@ -89,10 +180,21 @@ void whenIdleCompletesAfterPendingStepFinishes() throws Exception { private static PhysicsStepSchedulerResource.TickDecision awaitAllowedTick( PhysicsStepSchedulerResource scheduler) throws InterruptedException { + return awaitAllowedTick(scheduler, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.05f, + 0.10f); + } + + private static PhysicsStepSchedulerResource.TickDecision awaitAllowedTick( + PhysicsStepSchedulerResource scheduler, + PhysicsStepSchedulingMode mode, + float dt, + float maxSubmittedDt) throws InterruptedException { for (int attempt = 0; attempt < 50; attempt++) { - PhysicsStepSchedulerResource.TickDecision decision = scheduler.beforeStoreTick(0.05f, - PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, - 0.10f, + PhysicsStepSchedulerResource.TickDecision decision = scheduler.beforeStoreTick(dt, + mode, + maxSubmittedDt, 30L + attempt); if (decision.shouldTick()) { return decision; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java index 597f33f9..bd2c5cce 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulingModeTest.java @@ -26,7 +26,7 @@ void rejectsUnknownSerializedNames() { @Test void describesPendingStepBehavior() { - assertEquals("drop dt while a store tick step is pending", + assertEquals("drop pending dt and prevent post-skip catch-up", PhysicsStepSchedulingMode.DROP_PENDING_DT.describePendingStepBehavior()); assertEquals("accumulate pending dt for one capped catch-up step", PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT.describePendingStepBehavior()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 188a2f72..59cdd9f8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -248,6 +248,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + " prewarmedSections=" + prewarmedSections + " step=" + worldSettings.getStepMode().getSerializedName() + "/" + worldSettings.getSimulationSteps() + + " scheduling=" + worldSettings.getStepSchedulingMode().getSerializedName() + " maxStepDt=" + String.format(Locale.ROOT, "%.3f", worldSettings.getMaxStepDt()) + " visuals=" + mode.visualDescription() + (mode == StressMode.ENTITY ? " blockType=" + visualSettings.blockType() : "") From 0d88b30a00a9f57fb7c08d43c3a7da7d7985efc2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 10:18:29 +0200 Subject: [PATCH 498/534] perf(physicschunk): batch generated collision cleanup Collect dynamic body streaming targets through the compact snapshot cursor and batch generated-row removals by source key. Preserve existing generated rows when a replacement upsert is invalid or unbound. Signed-off-by: Blovien --- .../PhysicsChunkCollisionProducerSystem.java | 150 ++++++++----- .../ChunkCollisionMutationDrainSystem.java | 135 ++++++++---- ...ChunkCollisionMutationDrainSystemTest.java | 204 ++++++++++++++++++ 3 files changed, 393 insertions(+), 96 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java index d391e376..77f1597f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java @@ -27,12 +27,11 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource.BodyCursor; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -109,7 +108,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { } streaming.retainSpaces(retainedSpaces, queue); - PhysicsSnapshotFrame physicsFrame = snapshotResource.getLatestFrame(); + Map> bodyTargetsBySpace = + collectDynamicBodyTargetsBySpace(streaming, + spaces, + snapshotResource, + currentTick, + snapshot); for (PhysicsChunkSpaceSettings settings : spaces) { if (snapshot != null) { snapshot.incrementStreamingSpaces(); @@ -119,7 +123,7 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) { queue, settings, playerPositions, - physicsFrame, + bodyTargetsBySpace.getOrDefault(settings.spaceUuid(), List.of()), currentTick, snapshot); } @@ -136,7 +140,7 @@ private static void processSpace(@Nonnull World world, @Nonnull PhysicsChunkCollisionMutationQueueResource queue, @Nonnull PhysicsChunkSpaceSettings settings, @Nonnull List playerPositions, - @Nonnull PhysicsSnapshotFrame physicsFrame, + @Nonnull List bodyTargets, long currentTick, @Nullable Snapshot snapshot) { LongSet visitedSections = new LongOpenHashSet(); @@ -159,11 +163,7 @@ private static void processSpace(@Nonnull World world, } } - for (BodyStreamingTarget target : collectDynamicBodyTargets(streaming, - settings, - physicsFrame, - currentTick, - snapshot)) { + for (BodyStreamingTarget target : bodyTargets) { int sectionsBefore = visitedSections.size(); streaming.ensureAround(world, settings.spaceUuid(), @@ -197,63 +197,87 @@ private static void processSpace(@Nonnull World world, } @Nonnull - private static List collectDynamicBodyTargets( + private static Map> collectDynamicBodyTargetsBySpace( @Nonnull PhysicsChunkCollisionStreamingResource streaming, - @Nonnull PhysicsChunkSpaceSettings settings, - @Nonnull PhysicsSnapshotFrame physicsFrame, + @Nonnull List spaces, + @Nonnull PhysicsSnapshotResource snapshotResource, long currentTick, @Nullable Snapshot snapshot) { - Map uniqueTargets = - new Object2ObjectOpenHashMap<>(); - int spatialCandidates = 0; - int dynamicCandidates = 0; - for (PhysicsBodySnapshot body : physicsFrame.bodies()) { - if (!body.spaceUuid().equals(settings.spaceUuid())) { - continue; - } - spatialCandidates++; - if (body.bodyType() != PhysicsBodyType.DYNAMIC) { - continue; - } - Ref bodyRef = body.bodyRef(); - if (bodyRef == null || !bodyRef.isValid()) { - continue; - } - dynamicCandidates++; - Vector3f position = body.position(); - PhysicsChunkStreamingBounds bounds = PhysicsChunkStreamingBounds.from(position.x, - position.y, - position.z, - settings.bodyRadius()); - TargetRefreshDecision decision = streaming.shouldRefreshBodyTarget(settings.spaceUuid(), - bodyRef, - bounds, - body.sleeping(), - currentTick, - settings.ttlTicks(), - snapshot); - if (!decision.refresh()) { - continue; - } + Map settingsBySpace = new Object2ObjectOpenHashMap<>(); + Map accumulators = new Object2ObjectOpenHashMap<>(); + for (PhysicsChunkSpaceSettings settings : spaces) { + settingsBySpace.put(settings.spaceUuid(), settings); + accumulators.put(settings.spaceUuid(), new BodyTargetAccumulator()); + } + snapshotResource.forEachBodyCursor(body -> collectDynamicBodyTarget(streaming, + settingsBySpace, + accumulators, + body, + currentTick, + snapshot)); - BodyStreamingTarget target = uniqueTargets.get(bounds); - if (target == null) { - target = new BodyStreamingTarget(new Vector3d(position.x, position.y, position.z), - bounds, - new ArrayList<>()); - uniqueTargets.put(bounds, target); - } else if (snapshot != null) { - snapshot.incrementBodyTargetDedupeSkips(); + Map> targetsBySpace = new Object2ObjectOpenHashMap<>(); + if (snapshot != null) { + for (BodyTargetAccumulator accumulator : accumulators.values()) { + snapshot.addBodySpatialIndexCandidates(accumulator.spatialCandidates); + snapshot.addBodyStreamingCandidates(accumulator.dynamicCandidates); + snapshot.addBodyStreamingTargets(accumulator.targets.size()); } - target.refreshes().add(new BodyStreamingRefresh(bodyRef, body.sleeping())); } + for (Map.Entry entry : accumulators.entrySet()) { + targetsBySpace.put(entry.getKey(), new ArrayList<>(entry.getValue().targets.values())); + } + return targetsBySpace; + } - if (snapshot != null) { - snapshot.addBodySpatialIndexCandidates(spatialCandidates); - snapshot.addBodyStreamingCandidates(dynamicCandidates); - snapshot.addBodyStreamingTargets(uniqueTargets.size()); + private static void collectDynamicBodyTarget( + @Nonnull PhysicsChunkCollisionStreamingResource streaming, + @Nonnull Map settingsBySpace, + @Nonnull Map accumulators, + @Nonnull BodyCursor body, + long currentTick, + @Nullable Snapshot snapshot) { + PhysicsChunkSpaceSettings settings = settingsBySpace.get(body.spaceUuid()); + if (settings == null) { + return; + } + BodyTargetAccumulator accumulator = accumulators.get(settings.spaceUuid()); + accumulator.spatialCandidates++; + if (body.bodyType() != PhysicsBodyType.DYNAMIC) { + return; + } + Ref bodyRef = body.bodyRef(); + if (bodyRef == null || !bodyRef.isValid()) { + return; + } + accumulator.dynamicCandidates++; + PhysicsChunkStreamingBounds bounds = PhysicsChunkStreamingBounds.from(body.positionX(), + body.positionY(), + body.positionZ(), + settings.bodyRadius()); + TargetRefreshDecision decision = streaming.shouldRefreshBodyTarget(settings.spaceUuid(), + bodyRef, + bounds, + body.sleeping(), + currentTick, + settings.ttlTicks(), + snapshot); + if (!decision.refresh()) { + return; + } + + BodyStreamingTarget target = accumulator.targets.get(bounds); + if (target == null) { + target = new BodyStreamingTarget(new Vector3d(body.positionX(), + body.positionY(), + body.positionZ()), + bounds, + new ArrayList<>()); + accumulator.targets.put(bounds, target); + } else if (snapshot != null) { + snapshot.incrementBodyTargetDedupeSkips(); } - return new ArrayList<>(uniqueTargets.values()); + target.refreshes().add(new BodyStreamingRefresh(bodyRef, body.sleeping())); } @Nonnull @@ -345,4 +369,12 @@ private record BodyStreamingRefresh(@Nonnull Ref bodyRef, boolean sleeping) { } + private static final class BodyTargetAccumulator { + + private final Map targets = + new Object2ObjectOpenHashMap<>(); + private int spatialCandidates; + private int dynamicCandidates; + } + } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java index 9eb7bcdd..6881fe35 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java @@ -39,6 +39,8 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -85,15 +87,21 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsChunkCollisionPayloadResource.getResourceType()); PhysicsChunkSettingsIndexResource settingsIndex = store.getResource( PhysicsChunkSettingsIndexResource.getResourceType()); - - applyRemovals(store, runtime, identity, chunkCollisionPayloads, mutations); - applyUpserts(store, + List upserts = prepareUpserts(store, runtime, identity, - chunkCollisionPayloads, settingsIndex, restore, mutations); + + removeGeneratedRows(store, + runtime, + identity, + removalKeys(mutations, upserts)); + applyPreparedUpserts(store, + identity, + chunkCollisionPayloads, + upserts); } @Nonnull @@ -108,31 +116,45 @@ private static List coalesceLastMutationPerSource( return new ArrayList<>(latest.values()); } - private static void applyRemovals(@Nonnull Store store, - @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, - @Nonnull List mutations) { + @Nonnull + private static Map> removalKeys( + @Nonnull List mutations, + @Nonnull List upserts) { + Map> keys = new Object2ObjectOpenHashMap<>(); for (ChunkCollisionMutation mutation : mutations) { if (mutation.remove()) { - removeGeneratedRows(store, runtime, identity, mutation); - removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); + addRemovalKey(keys, mutation); } } + for (PreparedUpsert upsert : upserts) { + addRemovalKey(keys, upsert.mutation()); + } + return keys; + } + + private static void addRemovalKey(@Nonnull Map> keys, + @Nonnull ChunkCollisionMutation mutation) { + keys.computeIfAbsent(mutation.spaceUuid(), _ -> new ObjectOpenHashSet<>()) + .add(mutation.sourceKey()); } - private static void applyUpserts(@Nonnull Store store, + @Nonnull + private static List prepareUpserts(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsChunkSettingsIndexResource settingsIndex, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List mutations) { + List upserts = new ArrayList<>(); for (ChunkCollisionMutation mutation : mutations) { if (!mutation.remove() && isFreshUpsert(settingsIndex, mutation)) { - applyUpsert(store, runtime, identity, chunkCollisionPayloads, restore, mutation); + PreparedUpsert upsert = prepareUpsert(store, runtime, identity, restore, mutation); + if (upsert != null) { + upserts.add(upsert); + } } } + return upserts; } private static boolean isFreshUpsert(@Nonnull PhysicsChunkSettingsIndexResource settingsIndex, @@ -142,17 +164,17 @@ private static boolean isFreshUpsert(@Nonnull PhysicsChunkSettingsIndexResource && settingsIndex.settings(mutation.spaceUuid()) != null; } - private static void applyUpsert(@Nonnull Store store, + @Nullable + private static PreparedUpsert prepareUpsert(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ChunkCollisionMutation mutation) { ChunkCollisionPayload payload = mutation.payload(); if (payload == null || payload.isEmpty()) { restore.recordSoftSkip("Chunk collision upsert payload is missing: " + mutation.sourceKey()); - return; + return null; } Ref spaceRef = PhysicsStoreSystemSupport.refForUuid(identity, mutation.spaceUuid()); @@ -163,34 +185,56 @@ private static void applyUpsert(@Nonnull Store store, if (spaceRef == null || spaceHandle == null || backendRuntime == null) { restore.recordSoftSkip("Chunk collision references unbound space: " + mutation.sourceKey()); - return; + return null; } boolean nativeVoxel = payload.nativeVoxelCollisionEnabled() && payload.hasFullCubeVoxels() && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); MaterialComponent material = material(store, spaceRef); CollisionFilterComponent filter = filter(store, spaceRef); - removeGeneratedRows(store, runtime, identity, mutation); + return new PreparedUpsert(mutation, payload, spaceRef, material, filter, nativeVoxel); + } + + private static void applyPreparedUpserts(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + @Nonnull List upserts) { + for (PreparedUpsert upsert : upserts) { + applyPreparedUpsert(store, identity, chunkCollisionPayloads, upsert); + } + } + + private static void applyPreparedUpsert(@Nonnull Store store, + @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, + @Nonnull PreparedUpsert upsert) { + ChunkCollisionMutation mutation = upsert.mutation(); + ChunkCollisionPayload payload = upsert.payload(); removePayload(chunkCollisionPayloads, mutation.payloadResourceKey()); - if (nativeVoxel) { + if (upsert.nativeVoxel()) { chunkCollisionPayloads.put(mutation.payloadResourceKey(), voxelPayload(payload)); - addNativeVoxelBody(store, identity, spaceRef, mutation, material, filter); + addNativeVoxelBody(store, + identity, + upsert.spaceRef(), + mutation, + upsert.material(), + upsert.filter()); } else { addBoxBodies(store, identity, - spaceRef, + upsert.spaceRef(), mutation, - material, - filter, + upsert.material(), + upsert.filter(), payload.mergedFullCubeBoxes(), PartKind.BOX); } addBoxBodies(store, identity, - spaceRef, + upsert.spaceRef(), mutation, - material, - filter, + upsert.material(), + upsert.filter(), payload.detailBoxes(), PartKind.DETAIL_BOX); } @@ -342,11 +386,13 @@ private static CollisionFilterComponent filter(@Nonnull Store stor private static void removeGeneratedRows(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull ChunkCollisionMutation mutation) { - List rows = collectGeneratedRows(store, mutation); + @Nonnull Map> keys) { + if (keys.isEmpty()) { + return; + } + List rows = collectGeneratedRows(store, keys); rows.sort((first, second) -> Integer.compare(second.ref().getIndex(), first.ref().getIndex())); - boolean removedAny = false; List bodyEntityRemovals = new ArrayList<>(rows.size()); for (GeneratedRow row : rows) { @@ -354,9 +400,8 @@ private static void removeGeneratedRows(@Nonnull Store store, bodyEntityRemovals.add(new PhysicsStoreRowCleanup.BodyEntityRemoval(row.uuid(), row.ref(), row.payloadResourceKey())); - removedAny = true; } - if (removedAny) { + if (!bodyEntityRemovals.isEmpty()) { PhysicsStoreRowCleanup.removeBodyEntities(store, bodyEntityRemovals); PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); } @@ -364,7 +409,7 @@ private static void removeGeneratedRows(@Nonnull Store store, @Nonnull private static List collectGeneratedRows(@Nonnull Store store, - @Nonnull ChunkCollisionMutation mutation) { + @Nonnull Map> keys) { ConcurrentLinkedQueue rows = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { UUID rowUuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); @@ -376,7 +421,7 @@ private static List collectGeneratedRows(@Nonnull Store(rows); } - private static boolean matchesSource(@Nonnull ChunkCollisionMutation mutation, + private static boolean containsRemovalKey( + @Nonnull Map> keys, @Nonnull BodyComponent body, @Nonnull ChunkCollisionSourceComponent source) { - return mutation.spaceUuid().equals(body.getSpaceUuid()) - && mutation.sourceKey().equals(source.getSourceKey()); + ObjectOpenHashSet sourceKeys = keys.get(body.getSpaceUuid()); + return sourceKeys != null && sourceKeys.contains(source.getSourceKey()); } private static void removePayload(@Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @@ -430,4 +476,19 @@ private record MutationKey(@Nonnull UUID spaceUuid, Objects.requireNonNull(sourceKey, "sourceKey"); } } + + private record PreparedUpsert(@Nonnull ChunkCollisionMutation mutation, + @Nonnull ChunkCollisionPayload payload, + @Nonnull Ref spaceRef, + @Nonnull MaterialComponent material, + @Nonnull CollisionFilterComponent filter, + boolean nativeVoxel) { + private PreparedUpsert { + Objects.requireNonNull(mutation, "mutation"); + Objects.requireNonNull(payload, "payload"); + Objects.requireNonNull(spaceRef, "spaceRef"); + Objects.requireNonNull(material, "material"); + Objects.requireNonNull(filter, "filter"); + } + } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java index ccfaa309..cbdc4411 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java @@ -335,6 +335,201 @@ void removeDeletesGeneratedRowsAndPayloadResource() { } } + @Test + void removeDeletesManyGeneratedSourcesWithoutPerSourceFullStoreScans() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + registerTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-remove-scale-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(71); + addBoundSpace(store, + spaceUuid, + new BackendId("test:chunk-collision-drain-remove-scale")); + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + int sources = 4_096; + for (int index = 0; index < sources; index++) { + String sourceKey = "scale:" + index; + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + index, + 1, + 2, + "chunk-collision/scale/" + index, + boxPayload(index, 2.0, 3.0))); + } + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + assertNotNull(generatedBodyRef(store, spaceUuid, "scale:0", PartKind.BOX, 0)); + assertNotNull(generatedBodyRef(store, + spaceUuid, + "scale:" + (sources - 1), + PartKind.BOX, + 0)); + + for (int index = 0; index < sources; index++) { + queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, + "scale:" + index, + index, + 1, + 2)); + } + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + for (int index = 0; index < sources; index++) { + assertNull(generatedBodyRef(store, spaceUuid, "scale:" + index, PartKind.BOX, 0)); + } + assertSoftSkipsEmpty(store); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void invalidFreshReplacementKeepsExistingGeneratedRows() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + registerTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-invalid-replace-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(72); + Ref spaceRef = addBoundSpace(store, + spaceUuid, + new BackendId("test:chunk-collision-drain-invalid-replace")); + String sourceKey = "invalid:replace"; + String payloadKey = "chunk-collision/invalid/replace"; + BoxPayload initialBox = new BoxPayload(1.0, 2.0, 3.0, 0.5, 0.5, 0.5); + ChunkCollisionPayload initialPayload = new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(initialBox), + List.of(), + false, + List.of()); + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 0, + 1, + 2, + payloadKey, + initialPayload)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + ChunkCollisionPayload emptyPayload = new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(), + List.of(), + false, + List.of()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 0, + 1, + 2, + payloadKey, + emptyPayload)); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertGeneratedBox(store, + spaceUuid, + spaceRef, + sourceKey, + payloadKey, + PartKind.BOX, + 0, + initialBox); + assertSoftSkip(store, + "Chunk collision upsert payload is missing: " + sourceKey, + 1); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void unboundFreshReplacementKeepsExistingGeneratedRows() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + registerTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("chunk-collision-drain-unbound-replace-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(73); + Ref spaceRef = addBoundSpace(store, + spaceUuid, + new BackendId("test:chunk-collision-drain-unbound-replace")); + String sourceKey = "unbound:replace"; + String payloadKey = "chunk-collision/unbound/replace"; + BoxPayload initialBox = new BoxPayload(1.0, 2.0, 3.0, 0.5, 0.5, 0.5); + + PhysicsChunkCollisionMutationQueueResource queue = store.getResource( + PhysicsChunkCollisionMutationQueueResource.getResourceType()); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 0, + 1, + 2, + payloadKey, + new ChunkCollisionPayload(1.0f, + 1.0f, + 1.0f, + new int[0], + List.of(initialBox), + List.of(), + false, + List.of()))); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + store.getResource(PhysicsRuntimeResource.getResourceType()) + .removeSpaceHandle(spaceRef); + queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, + sourceKey, + 0, + 1, + 2, + payloadKey, + boxPayload(4.0, 5.0, 6.0))); + new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); + + assertEquals(0, queue.size()); + assertGeneratedBox(store, + spaceUuid, + spaceRef, + sourceKey, + payloadKey, + PartKind.BOX, + 0, + initialBox); + assertSoftSkip(store, + "Chunk collision references unbound space: " + sourceKey, + 1); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Test void staleLifecycleUpsertDoesNotCreateGeneratedRows() { ComponentRegistry registry = new ComponentRegistry<>(); @@ -884,6 +1079,15 @@ private static void assertSoftSkipsEmpty(@Nonnull Store store) { .size()); } + private static void assertSoftSkip(@Nonnull Store store, + @Nonnull String reason, + int count) { + var softSkips = store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .getSoftSkipsByReason(); + assertEquals(1, softSkips.size()); + assertEquals(count, softSkips.getInt(reason)); + } + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { try { Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); From b37a64a80a39a48674469202fe6035b9d2fee87f Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 10:18:45 +0200 Subject: [PATCH 499/534] feat(jolt): implement native joint lifecycle Wire Java joint ids to opaque native joint handles, export create/remove joint ABI calls, track native constraints, and remove attached joint state when bodies are removed. Cover same-body rejection and default zero hinge/slider limits. Signed-off-by: Blovien --- .../physics/PhysicsStoreRowCleanup.java | 2 +- impulse-jolt/README.md | 19 +- impulse-jolt/src/main/cpp/impulse_jolt.cpp | 318 +++++++++++++++++- .../impulse/jolt/JoltBackendRuntime.java | 99 +++++- .../impulse/jolt/JoltNativeLibrary.java | 32 ++ .../impulse/jolt/PanamaJoltNativeLibrary.java | 81 +++++ .../jolt/JoltBackendRuntimeContractTest.java | 4 +- .../impulse/jolt/JoltJointLifecycleTest.java | 178 ++++++++++ .../jolt/JoltNativeAbiIntegrationTest.java | 257 ++++++++++++++ .../impulse/jolt/JoltTestNativeLibrary.java | 63 +++- 10 files changed, 1024 insertions(+), 29 deletions(-) create mode 100644 impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index 997ad76a..5f669124 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -51,7 +51,7 @@ public static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime return false; } PhysicsBackendRuntime backendRuntime = runtime.runtimeForJointRef(resolvedJointRef); - if (spaceHandle != null && backendRuntime != null) { + if (backendRuntime != null) { backendRuntime.removeJoint(spaceHandle.value(), jointHandle.value()); } runtime.removeJointHandle(resolvedJointRef); diff --git a/impulse-jolt/README.md b/impulse-jolt/README.md index 68739d59..b4d0458f 100644 --- a/impulse-jolt/README.md +++ b/impulse-jolt/README.md @@ -62,6 +62,8 @@ library is expected to export these C ABI symbols: - `impulse_jolt_sleep_body` - `impulse_jolt_apply_body_impulse` - `impulse_jolt_apply_body_force` +- `impulse_jolt_create_joint` +- `impulse_jolt_remove_joint` - `impulse_jolt_raycast_closest` - `impulse_jolt_raycast_all` - `impulse_jolt_contacts` @@ -69,20 +71,21 @@ library is expected to export these C ABI symbols: - `impulse_jolt_body_count` - `impulse_jolt_joint_count` -Space lifecycle, gravity, stepping, body lifecycle/mutation/snapshots, raycasts, contact queries, -body count, joint count, and runtime stats are wired through that ABI. Java-assigned body ids are -stable within the runtime and map to opaque native body handles that wrap Jolt `BodyID` values. -Query results map native body handles back to those Java-assigned ids before calling Impulse sinks. +Space lifecycle, gravity, stepping, body lifecycle/mutation/snapshots, joint lifecycle, raycasts, +contact queries, body count, joint count, and runtime stats are wired through that ABI. +Java-assigned body and joint ids are stable within the runtime and map to opaque native handles +that wrap Jolt `BodyID` and constraint values. Query results map native body handles back to those +Java-assigned ids before calling Impulse sinks. The current native implementation uses Jolt `PhysicsSystem`/`BodyInterface` for real rigid body simulation, including broadphase, narrow phase, contact solving, gravity, forces, impulses, activation, sensor state, motion quality, friction, restitution, raycasts, and dynamic bodies resting on static collision. Contact queries are backed by a native Jolt `ContactListener` active contact registry. -Joint, contact-event, voxel terrain, and advanced capability operations still fail or return -explicit unsupported results until their native paths are implemented and tested. Jolt is staged as -a backend provider jar for explicit runtime selection, but it is not production-complete until -those paths and server runtime validation pass. +Contact-event, voxel terrain, and advanced capability operations still fail or return explicit +unsupported results until their native paths are implemented and tested. Jolt is staged as a backend +provider jar for explicit runtime selection, but it is not production-complete until those paths and +server runtime validation pass. `impulse_jolt_body_snapshot` writes two output buffers: diff --git a/impulse-jolt/src/main/cpp/impulse_jolt.cpp b/impulse-jolt/src/main/cpp/impulse_jolt.cpp index 1b536483..9f64e3c7 100644 --- a/impulse-jolt/src/main/cpp/impulse_jolt.cpp +++ b/impulse-jolt/src/main/cpp/impulse_jolt.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -61,11 +67,21 @@ constexpr int BODY_STATIC = 1; constexpr int BODY_DYNAMIC = 2; constexpr int BODY_KINEMATIC = 3; +constexpr int JOINT_FIXED = 1; +constexpr int JOINT_POINT = 2; +constexpr int JOINT_HINGE = 3; +constexpr int JOINT_SLIDER = 4; +constexpr int JOINT_SPRING = 5; + constexpr int AXIS_X = 1; constexpr int AXIS_Y = 2; constexpr int AXIS_Z = 3; constexpr float MIN_SHAPE_SIZE = 0.001F; +constexpr float MIN_AXIS_LENGTH_SQUARED = 1.0e-6F; +constexpr std::uint32_t MAX_BODIES = 131072; +constexpr std::uint32_t MAX_BODY_PAIRS = 65536; +constexpr std::uint32_t MAX_CONTACT_CONSTRAINTS = 10240; constexpr std::uint32_t DEFAULT_COLLISION_GROUP = 1; constexpr int RAY_HIT_FLOAT_COUNT = 8; constexpr int CONTACT_BODY_HANDLE_COUNT = 2; @@ -135,6 +151,13 @@ struct BodyState { int axis = AXIS_Y; }; +struct JointState { + JPH::Ref constraint; + std::uint64_t body_a_handle = 0; + std::uint64_t body_b_handle = 0; + int joint_type = 0; +}; + struct Space { JPH::BroadPhaseLayerInterfaceMask broad_phase_layer_interface; JPH::ObjectVsBroadPhaseLayerFilterMask object_vs_broadphase_layer_filter; @@ -145,6 +168,7 @@ struct Space { JPH::JobSystemThreadPool job_system; std::unordered_map bodies; std::unordered_map body_handles_by_jolt_id; + std::unordered_map joints; std::mutex contact_mutex; std::vector contacts; @@ -160,10 +184,10 @@ struct Space { JPH::BroadPhaseLayer(0), JPH::ObjectLayerPairFilterMask::cMask, 0); - physics_system.Init(65536, + physics_system.Init(MAX_BODIES, 0, - 65536, - 10240, + MAX_BODY_PAIRS, + MAX_CONTACT_CONSTRAINTS, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_layer_filter); @@ -172,6 +196,12 @@ struct Space { } ~Space() { + for (auto& [_, joint] : joints) { + if (joint.constraint != nullptr) { + physics_system.RemoveConstraint(joint.constraint); + } + } + joints.clear(); JPH::BodyInterface& body_interface = physics_system.GetBodyInterface(); for (auto& [_, body] : bodies) { if (!body.body_id.IsInvalid()) { @@ -190,12 +220,17 @@ struct Space { void erase_contact_records(const JPH::SubShapeIDPair& key); void erase_contact_records_for_body_handle(std::uint64_t body_handle); + + void erase_constraints_for_body_handle(std::uint64_t body_handle); + + bool remove_joint_handle(std::uint64_t joint_handle); }; std::once_flag jolt_init_once; std::mutex registry_mutex; std::uint64_t next_space_handle = 1; std::uint64_t next_body_handle = 1001; +std::uint64_t next_joint_handle = 2001; std::unordered_map> spaces; void ensure_jolt_initialized() { @@ -332,6 +367,162 @@ JPH::ShapeRefC create_shape(int shape_type, } } +float finite_or(float value, float fallback) { + return std::isfinite(value) ? value : fallback; +} + +float non_negative(float value) { + return std::max(0.0F, finite_or(value, 0.0F)); +} + +JPH::RVec3 local_point(float x, float y, float z) { + return JPH::RVec3(finite_or(x, 0.0F), finite_or(y, 0.0F), finite_or(z, 0.0F)); +} + +JPH::Vec3 normalized_axis(float x, float y, float z) { + x = finite_or(x, 0.0F); + y = finite_or(y, 1.0F); + z = finite_or(z, 0.0F); + const float length_squared = x * x + y * y + z * z; + if (!std::isfinite(length_squared) || length_squared <= MIN_AXIS_LENGTH_SQUARED) { + return JPH::Vec3::sAxisY(); + } + const float inverse_length = 1.0F / std::sqrt(length_squared); + return JPH::Vec3(x * inverse_length, y * inverse_length, z * inverse_length); +} + +JPH::Vec3 normal_for_axis(JPH::Vec3Arg axis) { + const JPH::Vec3 reference = std::fabs(axis.GetY()) < 0.9F + ? JPH::Vec3::sAxisY() + : JPH::Vec3::sAxisX(); + JPH::Vec3 normal = axis.Cross(reference); + if (normal.LengthSq() <= MIN_AXIS_LENGTH_SQUARED) { + normal = axis.Cross(JPH::Vec3::sAxisZ()); + } + return normal.LengthSq() <= MIN_AXIS_LENGTH_SQUARED + ? JPH::Vec3::sAxisX() + : normal.Normalized(); +} + +void configure_spring(JPH::SpringSettings& settings, float stiffness, float damping) { + const float clamped_stiffness = non_negative(stiffness); + if (clamped_stiffness <= 0.0F) { + return; + } + settings.mMode = JPH::ESpringMode::StiffnessAndDamping; + settings.mStiffness = clamped_stiffness; + settings.mDamping = non_negative(damping); +} + +JPH::Ref create_joint_constraint(int joint_type, + JPH::Body& body_a, + JPH::Body& body_b, + float anchor_ax, + float anchor_ay, + float anchor_az, + float anchor_bx, + float anchor_by, + float anchor_bz, + float axis_x, + float axis_y, + float axis_z, + float rest_length, + float stiffness, + float damping, + float lower_limit, + float upper_limit, + int motor_enabled, + float motor_target_velocity, + float motor_max_force) { + const JPH::RVec3 anchor_a = local_point(anchor_ax, anchor_ay, anchor_az); + const JPH::RVec3 anchor_b = local_point(anchor_bx, anchor_by, anchor_bz); + const JPH::Vec3 axis = normalized_axis(axis_x, axis_y, axis_z); + const JPH::Vec3 normal = normal_for_axis(axis); + const float ordered_lower = std::min(finite_or(lower_limit, 0.0F), + finite_or(upper_limit, 0.0F)); + const float ordered_upper = std::max(finite_or(lower_limit, 0.0F), + finite_or(upper_limit, 0.0F)); + const bool explicit_limits = ordered_lower < ordered_upper; + + switch (joint_type) { + case JOINT_FIXED: { + JPH::FixedConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mAutoDetectPoint = false; + settings.mPoint1 = anchor_a; + settings.mPoint2 = anchor_b; + return settings.Create(body_a, body_b); + } + case JOINT_POINT: { + JPH::PointConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = anchor_a; + settings.mPoint2 = anchor_b; + return settings.Create(body_a, body_b); + } + case JOINT_HINGE: { + JPH::HingeConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = anchor_a; + settings.mPoint2 = anchor_b; + settings.mHingeAxis1 = settings.mHingeAxis2 = axis; + settings.mNormalAxis1 = settings.mNormalAxis2 = normal; + if (explicit_limits) { + settings.mLimitsMin = std::clamp(ordered_lower, -JPH::JPH_PI, 0.0F); + settings.mLimitsMax = std::clamp(ordered_upper, 0.0F, JPH::JPH_PI); + } + JPH::HingeConstraint* constraint = + static_cast(settings.Create(body_a, body_b)); + if (constraint != nullptr && motor_enabled != 0) { + if (motor_max_force > 0.0F) { + constraint->GetMotorSettings().SetTorqueLimit(motor_max_force); + } + constraint->SetMotorState(JPH::EMotorState::Velocity); + constraint->SetTargetAngularVelocity(finite_or(motor_target_velocity, 0.0F)); + } + return constraint; + } + case JOINT_SLIDER: { + JPH::SliderConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mAutoDetectPoint = false; + settings.mPoint1 = anchor_a; + settings.mPoint2 = anchor_b; + settings.mSliderAxis1 = settings.mSliderAxis2 = axis; + settings.mNormalAxis1 = settings.mNormalAxis2 = normal; + if (explicit_limits) { + settings.mLimitsMin = ordered_lower; + settings.mLimitsMax = ordered_upper; + } + JPH::SliderConstraint* constraint = + static_cast(settings.Create(body_a, body_b)); + if (constraint != nullptr && motor_enabled != 0) { + if (motor_max_force > 0.0F) { + constraint->GetMotorSettings().SetForceLimit(motor_max_force); + } + constraint->SetMotorState(JPH::EMotorState::Velocity); + constraint->SetTargetVelocity(finite_or(motor_target_velocity, 0.0F)); + } + return constraint; + } + case JOINT_SPRING: { + JPH::DistanceConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = anchor_a; + settings.mPoint2 = anchor_b; + const float rest = non_negative(rest_length); + if (rest > 0.0F) { + settings.mMinDistance = rest; + settings.mMaxDistance = rest; + } + configure_spring(settings.mLimitsSpringSettings, stiffness, damping); + return settings.Create(body_a, body_b); + } + default: + return nullptr; + } +} + void write_snapshot(Space& space, const BodyState& body, float* floats, int* ints) { JPH::BodyInterface& body_interface = space.physics_system.GetBodyInterface(); JPH::RVec3 position = JPH::RVec3::sZero(); @@ -456,6 +647,32 @@ void Space::erase_contact_records_for_body_handle(std::uint64_t body_handle) { contacts.end()); } +void Space::erase_constraints_for_body_handle(std::uint64_t body_handle) { + for (auto iterator = joints.begin(); iterator != joints.end();) { + JointState& joint = iterator->second; + if (joint.body_a_handle != body_handle && joint.body_b_handle != body_handle) { + ++iterator; + continue; + } + if (joint.constraint != nullptr) { + physics_system.RemoveConstraint(joint.constraint); + } + iterator = joints.erase(iterator); + } +} + +bool Space::remove_joint_handle(std::uint64_t joint_handle) { + auto iterator = joints.find(joint_handle); + if (iterator == joints.end()) { + return false; + } + if (iterator->second.constraint != nullptr) { + physics_system.RemoveConstraint(iterator->second.constraint); + } + joints.erase(iterator); + return true; +} + void ImpulseContactListener::OnContactAdded(const JPH::Body& body1, const JPH::Body& body2, const JPH::ContactManifold& manifold, @@ -665,6 +882,7 @@ IMPULSE_JOLT_EXPORT int impulse_jolt_remove_body(std::int64_t space_handle, std: if (iterator == space->bodies.end()) { return 1; } + space->erase_constraints_for_body_handle(static_cast(body_handle)); JPH::BodyInterface& body_interface = space->physics_system.GetBodyInterface(); if (body_interface.IsAdded(iterator->second.body_id)) { body_interface.RemoveBody(iterator->second.body_id); @@ -1113,6 +1331,97 @@ IMPULSE_JOLT_EXPORT int impulse_jolt_contact_count(std::int64_t space_handle) { return static_cast(space->contacts.size()); } +IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_joint(std::int64_t space_handle, + int joint_type, + std::int64_t body_a_handle, + std::int64_t body_b_handle, + float anchor_ax, + float anchor_ay, + float anchor_az, + float anchor_bx, + float anchor_by, + float anchor_bz, + float axis_x, + float axis_y, + float axis_z, + float rest_length, + float stiffness, + float damping, + float lower_limit, + float upper_limit, + int motor_enabled, + float motor_target_velocity, + float motor_max_force) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr || body_a_handle == body_b_handle) { + return 0; + } + BodyState* body_a = find_body(*space, static_cast(body_a_handle)); + BodyState* body_b = find_body(*space, static_cast(body_b_handle)); + if (body_a == nullptr || body_b == nullptr) { + return 0; + } + + JPH::BodyID body_ids[] = {body_a->body_id, body_b->body_id}; + JPH::BodyLockMultiWrite body_locks(space->physics_system.GetBodyLockInterface(), + body_ids, + 2); + JPH::Body* locked_body_a = body_locks.GetBody(0); + JPH::Body* locked_body_b = body_locks.GetBody(1); + if (locked_body_a == nullptr || locked_body_b == nullptr) { + return 0; + } + + JPH::Ref constraint = create_joint_constraint(joint_type, + *locked_body_a, + *locked_body_b, + anchor_ax, + anchor_ay, + anchor_az, + anchor_bx, + anchor_by, + anchor_bz, + axis_x, + axis_y, + axis_z, + rest_length, + stiffness, + damping, + lower_limit, + upper_limit, + motor_enabled, + motor_target_velocity, + motor_max_force); + if (constraint == nullptr) { + return 0; + } + body_locks.ReleaseLocks(); + + space->physics_system.AddConstraint(constraint); + space->physics_system.GetBodyInterface().ActivateBody(body_a->body_id); + space->physics_system.GetBodyInterface().ActivateBody(body_b->body_id); + + const std::uint64_t joint_handle = next_joint_handle++; + JointState joint; + joint.constraint = constraint; + joint.body_a_handle = static_cast(body_a_handle); + joint.body_b_handle = static_cast(body_b_handle); + joint.joint_type = joint_type; + space->joints.emplace(joint_handle, joint); + return static_cast(joint_handle); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_remove_joint(std::int64_t space_handle, std::int64_t joint_handle) { + std::lock_guard lock(registry_mutex); + Space* space = find_space(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + space->remove_joint_handle(static_cast(joint_handle)); + return 1; +} + IMPULSE_JOLT_EXPORT int impulse_jolt_body_count(std::int64_t space_handle) { std::lock_guard lock(registry_mutex); Space* space = find_space(static_cast(space_handle)); @@ -1121,7 +1430,8 @@ IMPULSE_JOLT_EXPORT int impulse_jolt_body_count(std::int64_t space_handle) { IMPULSE_JOLT_EXPORT int impulse_jolt_joint_count(std::int64_t space_handle) { std::lock_guard lock(registry_mutex); - return find_space(static_cast(space_handle)) == nullptr ? 0 : 0; + Space* space = find_space(static_cast(space_handle)); + return space == nullptr ? 0 : static_cast(space->joints.size()); } } // extern "C" diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java index 98a31a5e..4d9cff65 100644 --- a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java @@ -8,6 +8,7 @@ import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; import dev.hytalemodding.impulse.api.runtime.BackendContactSink; import dev.hytalemodding.impulse.api.runtime.BackendExtensionSettingsSource; +import dev.hytalemodding.impulse.api.runtime.BackendJointType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeStatsSink; @@ -199,6 +200,7 @@ public void removeBody(int spaceId, long bodyId) { if (bodyHandle != null) { space.bodyIdsByHandle.remove(bodyHandle); nativeLibrary().removeBody(space.handle, bodyHandle); + removeAttachedJointState(space, bodyId); } } @@ -430,38 +432,74 @@ public long createJoint(int spaceId, boolean motorEnabled, float motorTargetVelocity, float motorMaxForce) { - requireSpaceHandle(spaceId); - BackendRuntimeCodes.jointType(jointTypeCode); - throw unsupported(); + RuntimeSpace space = requireSpace(spaceId); + BackendJointType type = BackendRuntimeCodes.jointType(jointTypeCode); + if (bodyAId == bodyBId) { + throw new IllegalArgumentException("Jolt joint endpoints must reference distinct bodies"); + } + long bodyAHandle = requireBodyHandle(space, bodyAId); + long bodyBHandle = requireBodyHandle(space, bodyBId); + NormalizedAxis axis = normalizeAxis(axisX, axisY, axisZ); + long jointHandle = nativeLibrary().createJoint(space.handle, + BackendRuntimeCodes.jointTypeCode(type), + bodyAHandle, + bodyBHandle, + anchorAX, + anchorAY, + anchorAZ, + anchorBX, + anchorBY, + anchorBZ, + axis.x, + axis.y, + axis.z, + restLength, + stiffness, + damping, + lowerLimit, + upperLimit, + motorEnabled, + motorTargetVelocity, + motorMaxForce); + if (jointHandle == 0L) { + throw new IllegalStateException("Jolt native library returned a null joint handle"); + } + long jointId = space.nextJointId++; + space.jointsById.put(jointId, new JointState(jointHandle, + jointTypeCode, + bodyAId, + bodyBId)); + return jointId; } @Override public void removeJoint(int spaceId, long jointId) { - requireSpaceHandle(spaceId); - throw unsupported(); + RuntimeSpace space = requireSpace(spaceId); + JointState joint = space.jointsById.get(jointId); + if (joint != null) { + nativeLibrary().removeJoint(space.handle, joint.nativeJointHandle); + space.jointsById.remove(jointId); + } } @Override public int jointCount(int spaceId) { - return nativeLibrary().jointCount(requireSpaceHandle(spaceId)); + return requireSpace(spaceId).jointsById.size(); } @Override public int jointType(int spaceId, long jointId) { - requireSpaceHandle(spaceId); - throw unsupported(); + return requireJoint(requireSpace(spaceId), jointId).jointTypeCode; } @Override public long jointBodyA(int spaceId, long jointId) { - requireSpaceHandle(spaceId); - throw unsupported(); + return requireJoint(requireSpace(spaceId), jointId).bodyAId; } @Override public long jointBodyB(int spaceId, long jointId) { - requireSpaceHandle(spaceId); - throw unsupported(); + return requireJoint(requireSpace(spaceId), jointId).bodyBId; } @Override @@ -648,6 +686,20 @@ private long requireBodyHandle(@Nonnull RuntimeSpace space, long bodyId) { return bodyHandle; } + @Nonnull + private static JointState requireJoint(@Nonnull RuntimeSpace space, long jointId) { + JointState joint = space.jointsById.get(jointId); + if (joint == null) { + throw new IllegalArgumentException("Unknown Jolt joint id: " + jointId); + } + return joint; + } + + private static void removeAttachedJointState(@Nonnull RuntimeSpace space, long bodyId) { + space.jointsById.values().removeIf(joint -> joint.bodyAId == bodyId + || joint.bodyBId == bodyId); + } + private void emitBodySnapshotIfPresent(@Nonnull RuntimeSpace space, long bodyId, @Nonnull BackendBodySnapshotSink sink) { @@ -731,6 +783,18 @@ private static void validateBodyShapeCode(int shapeTypeCode) { } } + @Nonnull + private static NormalizedAxis normalizeAxis(float axisX, float axisY, float axisZ) { + float lengthSquared = axisX * axisX + axisY * axisY + axisZ * axisZ; + if (lengthSquared == 0.0f) { + return new NormalizedAxis(0.0f, 1.0f, 0.0f); + } + float inverseLength = (float) (1.0 / Math.sqrt(lengthSquared)); + return new NormalizedAxis(axisX * inverseLength, + axisY * inverseLength, + axisZ * inverseLength); + } + @Nonnull private JoltNativeLibrary nativeLibrary() { return fixedNativeLibrary != null ? fixedNativeLibrary : backend.nativeLibrary(); @@ -740,12 +804,23 @@ private static UnsupportedOperationException unsupported() { return new UnsupportedOperationException(UNSUPPORTED_MESSAGE); } + private record NormalizedAxis(float x, float y, float z) { + } + + private record JointState(long nativeJointHandle, + int jointTypeCode, + long bodyAId, + long bodyBId) { + } + private static final class RuntimeSpace { private final long handle; private final Map bodyHandles = new HashMap<>(); private final Map bodyIdsByHandle = new HashMap<>(); + private final Map jointsById = new HashMap<>(); private long nextBodyId = 1L; + private long nextJointId = 1L; private RuntimeSpace(long handle) { this.handle = handle; diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java index 6e8e9e46..a2191ac9 100644 --- a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java @@ -142,6 +142,34 @@ default void applyBodyForce(long spaceHandle, throw unsupportedBodyAbi(); } + default long createJoint(long spaceHandle, + int jointTypeCode, + long bodyAHandle, + long bodyBHandle, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisX, + float axisY, + float axisZ, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + boolean motorEnabled, + float motorTargetVelocity, + float motorMaxForce) { + throw unsupportedJointAbi(); + } + + default void removeJoint(long spaceHandle, long jointHandle) { + throw unsupportedJointAbi(); + } + default int raycastClosest(long spaceHandle, float fromX, float fromY, @@ -189,4 +217,8 @@ private static UnsupportedOperationException unsupportedBodyAbi() { private static UnsupportedOperationException unsupportedQueryAbi() { return new UnsupportedOperationException("Jolt native query ABI is not implemented"); } + + private static UnsupportedOperationException unsupportedJointAbi() { + return new UnsupportedOperationException("Jolt native joint ABI is not implemented"); + } } diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java index 16d8e635..476ebe51 100644 --- a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java @@ -44,6 +44,8 @@ final class PanamaJoltNativeLibrary implements JoltNativeLibrary { private final MethodHandle sleepBody; private final MethodHandle applyBodyImpulse; private final MethodHandle applyBodyForce; + private final MethodHandle createJoint; + private final MethodHandle removeJoint; private final MethodHandle raycastClosest; private final MethodHandle raycastAll; private final MethodHandle contacts; @@ -185,6 +187,33 @@ private PanamaJoltNativeLibrary(@Nonnull SymbolLookup symbols) { JAVA_FLOAT, JAVA_FLOAT, JAVA_INT)); + createJoint = downcall(symbols, + "impulse_jolt_create_joint", + FunctionDescriptor.of(JAVA_LONG, + JAVA_LONG, + JAVA_INT, + JAVA_LONG, + JAVA_LONG, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_FLOAT, + JAVA_INT, + JAVA_FLOAT, + JAVA_FLOAT)); + removeJoint = downcall(symbols, + "impulse_jolt_remove_joint", + FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG)); raycastClosest = downcall(symbols, "impulse_jolt_raycast_closest", FunctionDescriptor.of(JAVA_INT, @@ -513,6 +542,58 @@ public void applyBodyForce(long spaceHandle, torque ? 1 : 0)); } + @Override + public long createJoint(long spaceHandle, + int jointTypeCode, + long bodyAHandle, + long bodyBHandle, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisX, + float axisY, + float axisZ, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + boolean motorEnabled, + float motorTargetVelocity, + float motorMaxForce) { + return invokeLong("create joint", + createJoint, + spaceHandle, + jointTypeCode, + bodyAHandle, + bodyBHandle, + anchorAX, + anchorAY, + anchorAZ, + anchorBX, + anchorBY, + anchorBZ, + axisX, + axisY, + axisZ, + restLength, + stiffness, + damping, + lowerLimit, + upperLimit, + motorEnabled ? 1 : 0, + motorTargetVelocity, + motorMaxForce); + } + + @Override + public void removeJoint(long spaceHandle, long jointHandle) { + requireSuccess("remove joint", invokeStatus(removeJoint, spaceHandle, jointHandle)); + } + @Override public int raycastClosest(long spaceHandle, float fromX, diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java index 0f2cde1e..ae37d7e8 100644 --- a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java @@ -79,7 +79,7 @@ void unsupportedCreateBodyShapesFailAsContractInputErrors() { } @Test - void validCreateJointCodesReachDeferredJointImplementation() { + void validCreateJointCodesReachEndpointValidation() { JoltBackendRuntime runtime = runtimeWithSpace(4); int[] jointTypes = { @@ -91,7 +91,7 @@ void validCreateJointCodesReachDeferredJointImplementation() { }; for (int jointType : jointTypes) { - assertThrows(UnsupportedOperationException.class, + assertThrows(IllegalArgumentException.class, () -> createJoint(runtime, 4, jointType)); } } diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java new file mode 100644 index 00000000..3cd0e08d --- /dev/null +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java @@ -0,0 +1,178 @@ +package dev.hytalemodding.impulse.jolt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import org.junit.jupiter.api.Test; + +class JoltJointLifecycleTest { + + @Test + void createJointReturnsStableJavaJointIdAndStoresNativeHandle() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(21)); + long bodyA = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyB = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + + long jointId = createJoint(runtime, spaceId, BackendRuntimeCodes.JOINT_POINT, bodyA, bodyB); + long spaceHandle = nativeLibrary.firstSpaceHandle(); + long nativeJointHandle = nativeLibrary.nativeJointHandle(spaceHandle, 0); + + assertEquals(1L, jointId); + assertNotEquals(nativeJointHandle, jointId); + assertEquals(1, runtime.jointCount(spaceId)); + assertEquals(BackendRuntimeCodes.JOINT_POINT, runtime.jointType(spaceId, jointId)); + assertEquals(bodyA, runtime.jointBodyA(spaceId, jointId)); + assertEquals(bodyB, runtime.jointBodyB(spaceId, jointId)); + } + + @Test + void createJointSupportsEveryBackendJointCode() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(22)); + long bodyA = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyB = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + + int[] jointTypes = { + BackendRuntimeCodes.JOINT_FIXED, + BackendRuntimeCodes.JOINT_POINT, + BackendRuntimeCodes.JOINT_HINGE, + BackendRuntimeCodes.JOINT_SLIDER, + BackendRuntimeCodes.JOINT_SPRING + }; + + for (int index = 0; index < jointTypes.length; index++) { + long jointId = createJoint(runtime, spaceId, jointTypes[index], bodyA, bodyB); + + assertEquals(index + 1L, jointId); + assertEquals(jointTypes[index], runtime.jointType(spaceId, jointId)); + } + assertEquals(jointTypes.length, runtime.jointCount(spaceId)); + } + + @Test + void removeJointDestroysNativeHandleAndMakesJavaIdStale() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(23)); + long bodyA = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyB = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + long jointId = createJoint(runtime, spaceId, BackendRuntimeCodes.JOINT_FIXED, bodyA, bodyB); + long nativeJointHandle = nativeLibrary.nativeJointHandle(nativeLibrary.firstSpaceHandle(), 0); + + runtime.removeJoint(spaceId, jointId); + + assertEquals(nativeJointHandle, nativeLibrary.lastRemovedJointHandle()); + assertEquals(0, runtime.jointCount(spaceId)); + assertThrows(IllegalArgumentException.class, () -> runtime.jointType(spaceId, jointId)); + assertThrows(IllegalArgumentException.class, () -> runtime.jointBodyA(spaceId, jointId)); + assertThrows(IllegalArgumentException.class, () -> runtime.jointBodyB(spaceId, jointId)); + } + + @Test + void removingBodyRemovesAttachedJointState() { + JoltTestNativeLibrary nativeLibrary = new JoltTestNativeLibrary(); + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), nativeLibrary); + int spaceId = runtime.createSpace(new SpaceId(25)); + long bodyA = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyB = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + long jointId = createJoint(runtime, spaceId, BackendRuntimeCodes.JOINT_FIXED, bodyA, bodyB); + + runtime.removeBody(spaceId, bodyA); + + assertEquals(0, runtime.jointCount(spaceId)); + assertEquals(0, nativeLibrary.jointCount(nativeLibrary.firstSpaceHandle())); + assertThrows(IllegalArgumentException.class, () -> runtime.jointType(spaceId, jointId)); + } + + @Test + void sameBodyJointEndpointsFailBeforeNativeCall() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(26)); + long body = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + + assertThrows(IllegalArgumentException.class, + () -> createJoint(runtime, spaceId, BackendRuntimeCodes.JOINT_POINT, body, body)); + } + + @Test + void hingeAndSliderSupportDefaultZeroLimits() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(27)); + long bodyA = JoltBodyLifecycleTest.createBox(runtime, spaceId, 1.0f); + long bodyB = JoltBodyLifecycleTest.createBox(runtime, spaceId, 2.0f); + + long hinge = createJoint(runtime, + spaceId, + BackendRuntimeCodes.JOINT_HINGE, + bodyA, + bodyB, + 0.0f, + 0.0f); + long slider = createJoint(runtime, + spaceId, + BackendRuntimeCodes.JOINT_SLIDER, + bodyA, + bodyB, + 0.0f, + 0.0f); + + assertEquals(1L, hinge); + assertEquals(2L, slider); + assertEquals(2, runtime.jointCount(spaceId)); + } + + @Test + void unknownJointMutationIsANoopAndLookupFails() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend(), new JoltTestNativeLibrary()); + int spaceId = runtime.createSpace(new SpaceId(24)); + + runtime.removeJoint(spaceId, 404L); + + assertEquals(0, runtime.jointCount(spaceId)); + assertThrows(IllegalArgumentException.class, () -> runtime.jointType(spaceId, 404L)); + } + + private static long createJoint(JoltBackendRuntime runtime, + int spaceId, + int jointType, + long bodyA, + long bodyB) { + return createJoint(runtime, spaceId, jointType, bodyA, bodyB, -1.0f, 1.0f); + } + + private static long createJoint(JoltBackendRuntime runtime, + int spaceId, + int jointType, + long bodyA, + long bodyB, + float lowerLimit, + float upperLimit) { + return runtime.createJoint(spaceId, + jointType, + bodyA, + bodyB, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 1.0f, + 10.0f, + 0.5f, + lowerLimit, + upperLimit, + true, + 0.5f, + 2.0f); + } +} diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java index ed4f731a..54da3b7e 100644 --- a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.jolt; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -71,4 +72,260 @@ void nativeLibraryRunsSpaceAndBodyAbi() { assertThrows(IllegalArgumentException.class, () -> runtime.bodyCount(spaceId)); } + + @Test + void nativeLibraryCreatesRestoredShapeAndBodyTypeCombinations() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(43)); + int[] shapes = { + BackendRuntimeCodes.SHAPE_BOX, + BackendRuntimeCodes.SHAPE_SPHERE, + BackendRuntimeCodes.SHAPE_CAPSULE, + BackendRuntimeCodes.SHAPE_CYLINDER, + BackendRuntimeCodes.SHAPE_CONE, + BackendRuntimeCodes.SHAPE_PLANE + }; + int[] bodyTypes = { + BackendRuntimeCodes.BODY_STATIC, + BackendRuntimeCodes.BODY_DYNAMIC, + BackendRuntimeCodes.BODY_KINEMATIC + }; + + for (int shape : shapes) { + for (int bodyType : bodyTypes) { + long bodyId = assertDoesNotThrow(() -> runtime.createBody(spaceId, + shape, + 0.5f, + 0.5f, + 0.5f, + 0.5f, + 0.5f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + bodyType == BackendRuntimeCodes.BODY_DYNAMIC ? 1.0f : 0.0f, + bodyType, + 0.0f, + 2.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f), + "shape=" + shape + " bodyType=" + bodyType); + assertTrue(runtime.containsBody(spaceId, bodyId)); + runtime.removeBody(spaceId, bodyId); + } + } + + runtime.destroySpace(spaceId); + } + + @Test + void nativeLibraryCreatesBodiesPastPreviousRestoreScaleLimit() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(44)); + int bodyCount = 65_537; + + for (int index = 0; index < bodyCount; index++) { + long bodyId = assertDoesNotThrow(() -> runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 0.0f, + BackendRuntimeCodes.BODY_STATIC, + 0.0f, + 2.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f), + "body index=" + index); + assertTrue(runtime.containsBody(spaceId, bodyId)); + } + + assertEquals(bodyCount, runtime.bodyCount(spaceId)); + runtime.destroySpace(spaceId); + } + + @Test + void nativeLibraryRunsJointAbiForEveryJointType() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(42)); + long bodyA = runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 0.0f, + BackendRuntimeCodes.BODY_STATIC, + 0.0f, + 2.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + long bodyB = runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.BODY_DYNAMIC, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + + int[] jointTypes = { + BackendRuntimeCodes.JOINT_FIXED, + BackendRuntimeCodes.JOINT_POINT, + BackendRuntimeCodes.JOINT_HINGE, + BackendRuntimeCodes.JOINT_SLIDER, + BackendRuntimeCodes.JOINT_SPRING + }; + for (int jointType : jointTypes) { + long jointId = runtime.createJoint(spaceId, + jointType, + bodyA, + bodyB, + 0.0f, + -0.5f, + 0.0f, + 0.0f, + 0.5f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 1.0f, + 10.0f, + 0.5f, + -0.75f, + 0.75f, + true, + 0.25f, + 2.0f); + + assertEquals(1, runtime.jointCount(spaceId)); + assertEquals(1, nativeJointCount(runtime, spaceId)); + assertEquals(jointType, runtime.jointType(spaceId, jointId)); + assertEquals(bodyA, runtime.jointBodyA(spaceId, jointId)); + assertEquals(bodyB, runtime.jointBodyB(spaceId, jointId)); + + runtime.removeJoint(spaceId, jointId); + + assertEquals(0, runtime.jointCount(spaceId)); + assertEquals(0, nativeJointCount(runtime, spaceId)); + } + + runtime.destroySpace(spaceId); + } + + @Test + void nativeLibraryCreatesHingeAndSliderWithDefaultZeroLimits() { + JoltBackendRuntime runtime = new JoltBackendRuntime(new JoltBackend()); + int spaceId = runtime.createSpace(new SpaceId(45)); + long bodyA = runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 0.0f, + BackendRuntimeCodes.BODY_STATIC, + 0.0f, + 2.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + long bodyB = runtime.createBody(spaceId, + BackendRuntimeCodes.SHAPE_BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.BODY_DYNAMIC, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + + for (int jointType : new int[] { + BackendRuntimeCodes.JOINT_HINGE, + BackendRuntimeCodes.JOINT_SLIDER + }) { + long jointId = assertDoesNotThrow(() -> runtime.createJoint(spaceId, + jointType, + bodyA, + bodyB, + 0.0f, + -0.5f, + 0.0f, + 0.0f, + 0.5f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 1.0f, + 10.0f, + 0.5f, + 0.0f, + 0.0f, + true, + 0.25f, + 2.0f)); + + assertEquals(jointType, runtime.jointType(spaceId, jointId)); + runtime.removeJoint(spaceId, jointId); + } + + runtime.destroySpace(spaceId); + } + + private static int nativeJointCount(JoltBackendRuntime runtime, int spaceId) { + int[] jointCount = new int[1]; + runtime.runtimeStats(spaceId, (_, + _, + _, + _, + _, + _, + _, + _, + _, + joints, + _) -> jointCount[0] = joints); + return jointCount[0]; + } } diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java index 0c0387a4..24b9f3f0 100644 --- a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java +++ b/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java @@ -12,7 +12,9 @@ final class JoltTestNativeLibrary implements JoltNativeLibrary { private final Map spaces = new HashMap<>(); private long nextSpaceHandle = 1L; private long nextBodyHandle = 1001L; + private long nextJointHandle = 2001L; private long lastRemovedBodyHandle; + private long lastRemovedJointHandle; @Override public long createSpace() { @@ -100,7 +102,10 @@ public long createBody(long spaceHandle, @Override public void removeBody(long spaceHandle, long bodyHandle) { - requireSpace(spaceHandle).bodies.remove(bodyHandle); + SpaceState space = requireSpace(spaceHandle); + space.bodies.remove(bodyHandle); + space.joints.values().removeIf(joint -> joint.bodyAHandle() == bodyHandle + || joint.bodyBHandle() == bodyHandle); lastRemovedBodyHandle = bodyHandle; } @@ -254,7 +259,44 @@ public int bodyCount(long spaceHandle) { @Override public int jointCount(long spaceHandle) { - return 0; + return requireSpace(spaceHandle).joints.size(); + } + + @Override + public long createJoint(long spaceHandle, + int jointTypeCode, + long bodyAHandle, + long bodyBHandle, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisX, + float axisY, + float axisZ, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + boolean motorEnabled, + float motorTargetVelocity, + float motorMaxForce) { + SpaceState space = requireSpace(spaceHandle); + requireBody(spaceHandle, bodyAHandle); + requireBody(spaceHandle, bodyBHandle); + long jointHandle = nextJointHandle++; + space.joints.put(jointHandle, + new JointState(jointTypeCode, bodyAHandle, bodyBHandle)); + return jointHandle; + } + + @Override + public void removeJoint(long spaceHandle, long jointHandle) { + requireSpace(spaceHandle).joints.remove(jointHandle); + lastRemovedJointHandle = jointHandle; } @Override @@ -361,6 +403,13 @@ long nativeBodyHandle(long spaceHandle, int index) { .orElseThrow(); } + long nativeJointHandle(long spaceHandle, int index) { + return requireSpace(spaceHandle).joints.keySet().stream() + .skip(index) + .findFirst() + .orElseThrow(); + } + long firstSpaceHandle() { return spaces.keySet().iterator().next(); } @@ -369,6 +418,10 @@ long lastRemovedBodyHandle() { return lastRemovedBodyHandle; } + long lastRemovedJointHandle() { + return lastRemovedJointHandle; + } + BodyState body(long spaceHandle, long bodyHandle) { return requireBody(spaceHandle, bodyHandle); } @@ -406,11 +459,17 @@ private static float centerOfMassOffsetY(int shapeTypeCode, private static final class SpaceState { private final Map bodies = new LinkedHashMap<>(); + private final Map joints = new LinkedHashMap<>(); private final float[] gravity = new float[] {0.0f, -9.81f, 0.0f}; private final List rayHits = new ArrayList<>(); private final List contacts = new ArrayList<>(); } + private record JointState(int jointTypeCode, + long bodyAHandle, + long bodyBHandle) { + } + static final class BodyState { private final JoltBodySnapshot snapshot = new JoltBodySnapshot(); From e16952b3327ab4ab510b51936065cc30d8b7e1a8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 11:13:44 +0200 Subject: [PATCH 500/534] perf(core): publish compact physics snapshots Signed-off-by: Blovien --- .../resources/PhysicsSnapshotResource.java | 393 +++++++++++++----- .../PhysicsStepSchedulerResource.java | 33 +- .../CompletedStepPublicationSystem.java | 17 +- .../systems/StepSubmissionSystem.java | 22 +- .../PhysicsStepSchedulerResourceTest.java | 13 + .../PhysicsStoreResourceIndexTest.java | 94 ++++- .../CompletedStepPublicationSystemTest.java | 130 ++++++ 7 files changed, 571 insertions(+), 131 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java index 3a7b061f..cbb8a696 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsSnapshotResource.java @@ -10,7 +10,8 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -25,7 +26,7 @@ public final class PhysicsSnapshotResource implements Resource { @Nonnull - private volatile PublishedSnapshot snapshot = PublishedSnapshot.EMPTY; + private volatile CompactSnapshot snapshot = CompactSnapshot.EMPTY; public PhysicsSnapshotResource() { } @@ -39,6 +40,10 @@ public long latestSequence() { return snapshot.sequence(); } + public int bodyCount() { + return snapshot.bodyCount(); + } + /** * Iterates the compact published body frame without materializing snapshot objects. * @@ -64,7 +69,14 @@ public PhysicsBodySnapshot getBody(@Nonnull Ref bodyRef) { } public void publish(@Nonnull PhysicsSnapshotFrame frame) { - snapshot = PublishedSnapshot.fromFrame(Objects.requireNonNull(frame, "frame")); + snapshot = CompactSnapshot.fromFrame(Objects.requireNonNull(frame, "frame")); + } + + public void publish(long sequence, + float dt, + @Nonnull CompactSnapshot compactSnapshot) { + snapshot = Objects.requireNonNull(compactSnapshot, "compactSnapshot") + .withFrame(sequence, dt); } public void removeBody(@Nonnull UUID bodyUuid) { @@ -74,7 +86,7 @@ public void removeBody(@Nonnull UUID bodyUuid) { public void removeBodies(@Nonnull Collection bodyUuids) { Objects.requireNonNull(bodyUuids, "bodyUuids"); - PublishedSnapshot current = snapshot; + CompactSnapshot current = snapshot; if (bodyUuids.isEmpty()) { return; } @@ -92,24 +104,39 @@ public void removeBodies(@Nonnull Collection bodyUuids) { } public void clear() { - snapshot = PublishedSnapshot.EMPTY; + snapshot = CompactSnapshot.EMPTY; } @Nonnull - private static PublishedSnapshot withoutBodies(@Nonnull PublishedSnapshot current, + private static CompactSnapshot withoutBodies(@Nonnull CompactSnapshot current, @Nonnull ObjectOpenHashSet bodyUuids) { int bodyCount = Math.max(0, current.bodyCount() - bodyUuids.size()); - List bodies = new ArrayList<>(bodyCount); + CompactSnapshotBuilder builder = compactBuilder(bodyCount); for (int index = 0; index < current.bodyCount(); index++) { - PhysicsBodySnapshot body = current.body(index); - if (bodyUuids.contains(body.bodyUuid())) { + if (bodyUuids.contains(current.bodyUuids[index])) { continue; } - bodies.add(body); - } - return PublishedSnapshot.fromFrame(new PhysicsSnapshotFrame(current.sequence(), - current.dt(), - bodies)); + builder.addBody(current.bodyRefs[index], + current.bodyUuids[index], + current.spaceUuids[index], + current.bodyTypes[index], + current.value(index, CompactSnapshot.POSITION_X), + current.value(index, CompactSnapshot.POSITION_Y), + current.value(index, CompactSnapshot.POSITION_Z), + current.value(index, CompactSnapshot.ROTATION_X), + current.value(index, CompactSnapshot.ROTATION_Y), + current.value(index, CompactSnapshot.ROTATION_Z), + current.value(index, CompactSnapshot.ROTATION_W), + current.value(index, CompactSnapshot.LINEAR_VELOCITY_X), + current.value(index, CompactSnapshot.LINEAR_VELOCITY_Y), + current.value(index, CompactSnapshot.LINEAR_VELOCITY_Z), + current.value(index, CompactSnapshot.ANGULAR_VELOCITY_X), + current.value(index, CompactSnapshot.ANGULAR_VELOCITY_Y), + current.value(index, CompactSnapshot.ANGULAR_VELOCITY_Z), + current.value(index, CompactSnapshot.CENTER_OF_MASS_OFFSET_Y), + current.sleeping.get(index)); + } + return builder.build(current.sequence(), current.dt()); } @Nonnull @@ -125,16 +152,17 @@ public static ResourceType getResourceTyp return PhysicsResourceTypes.snapshotResourceType(); } - private record PublishedSnapshot(long sequence, - float dt, - @Nonnull Ref[] bodyRefs, - @Nonnull UUID[] bodyUuids, - @Nonnull UUID[] spaceUuids, - @Nonnull PhysicsBodyType[] bodyTypes, - @Nonnull float[] values, - @Nonnull boolean[] sleeping, - @Nonnull Object2IntOpenHashMap bodiesByUuid, - @Nonnull Int2IntOpenHashMap bodiesByRowIndex) { + @Nonnull + public static CompactSnapshotBuilder compactBuilder(int expectedBodies) { + return new CompactSnapshotBuilder(expectedBodies); + } + + @Nonnull + public static CompactSnapshot emptyCompactSnapshot() { + return CompactSnapshot.EMPTY; + } + + public static final class CompactSnapshot { private static final int POSITION_X = 0; private static final int POSITION_Y = 1; @@ -152,18 +180,59 @@ private record PublishedSnapshot(long sequence, private static final int CENTER_OF_MASS_OFFSET_Y = 13; private static final int FLOAT_STRIDE = 14; - private static final PublishedSnapshot EMPTY = empty(); + private static final CompactSnapshot EMPTY = empty(); + + private final long sequence; + private final float dt; + @Nonnull + private final Ref[] bodyRefs; + @Nonnull + private final UUID[] bodyUuids; + @Nonnull + private final UUID[] spaceUuids; + @Nonnull + private final PhysicsBodyType[] bodyTypes; + @Nonnull + private final float[] values; + @Nonnull + private final BitSet sleeping; + @Nonnull + private final Object2IntOpenHashMap bodiesByUuid; + @Nonnull + private final Int2IntOpenHashMap bodiesByRowIndex; + + private CompactSnapshot(long sequence, + float dt, + @Nonnull Ref[] bodyRefs, + @Nonnull UUID[] bodyUuids, + @Nonnull UUID[] spaceUuids, + @Nonnull PhysicsBodyType[] bodyTypes, + @Nonnull float[] values, + @Nonnull BitSet sleeping, + @Nonnull Object2IntOpenHashMap bodiesByUuid, + @Nonnull Int2IntOpenHashMap bodiesByRowIndex) { + this.sequence = Math.max(0L, sequence); + this.dt = Float.isFinite(dt) ? Math.max(0.0f, dt) : 0.0f; + this.bodyRefs = Objects.requireNonNull(bodyRefs, "bodyRefs"); + this.bodyUuids = Objects.requireNonNull(bodyUuids, "bodyUuids"); + this.spaceUuids = Objects.requireNonNull(spaceUuids, "spaceUuids"); + this.bodyTypes = Objects.requireNonNull(bodyTypes, "bodyTypes"); + this.values = Objects.requireNonNull(values, "values"); + this.sleeping = Objects.requireNonNull(sleeping, "sleeping"); + this.bodiesByUuid = Objects.requireNonNull(bodiesByUuid, "bodiesByUuid"); + this.bodiesByRowIndex = Objects.requireNonNull(bodiesByRowIndex, "bodiesByRowIndex"); + } @Nonnull - private static PublishedSnapshot empty() { - return new PublishedSnapshot(PhysicsSnapshotFrame.EMPTY.sequence(), + private static CompactSnapshot empty() { + return new CompactSnapshot(PhysicsSnapshotFrame.EMPTY.sequence(), PhysicsSnapshotFrame.EMPTY.dt(), emptyRefs(), new UUID[0], new UUID[0], new PhysicsBodyType[0], new float[0], - new boolean[0], + new BitSet(0), uuidIndex(0), rowIndex(0)); } @@ -174,56 +243,31 @@ private static Ref[] emptyRefs() { return (Ref[]) new Ref[0]; } - @SuppressWarnings("unchecked") @Nonnull - private static PublishedSnapshot fromFrame(@Nonnull PhysicsSnapshotFrame frame) { - int bodyCount = frame.bodies().size(); - Ref[] bodyRefs = (Ref[]) new Ref[bodyCount]; - UUID[] bodyUuids = new UUID[bodyCount]; - UUID[] spaceUuids = new UUID[bodyCount]; - PhysicsBodyType[] bodyTypes = new PhysicsBodyType[bodyCount]; - float[] values = new float[bodyCount * FLOAT_STRIDE]; - boolean[] sleeping = new boolean[bodyCount]; - Object2IntOpenHashMap bodiesByUuid = uuidIndex(bodyCount); - Int2IntOpenHashMap bodiesByRowIndex = rowIndex(bodyCount); - for (int index = 0; index < bodyCount; index++) { - PhysicsBodySnapshot body = frame.bodies().get(index); - bodyRefs[index] = body.bodyRef(); - bodyUuids[index] = body.bodyUuid(); - spaceUuids[index] = body.spaceUuid(); - bodyTypes[index] = body.bodyType(); - sleeping[index] = body.sleeping(); - values[index * FLOAT_STRIDE + POSITION_X] = body.positionX(); - values[index * FLOAT_STRIDE + POSITION_Y] = body.positionY(); - values[index * FLOAT_STRIDE + POSITION_Z] = body.positionZ(); - values[index * FLOAT_STRIDE + ROTATION_X] = body.rotationX(); - values[index * FLOAT_STRIDE + ROTATION_Y] = body.rotationY(); - values[index * FLOAT_STRIDE + ROTATION_Z] = body.rotationZ(); - values[index * FLOAT_STRIDE + ROTATION_W] = body.rotationW(); - values[index * FLOAT_STRIDE + LINEAR_VELOCITY_X] = body.linearVelocityX(); - values[index * FLOAT_STRIDE + LINEAR_VELOCITY_Y] = body.linearVelocityY(); - values[index * FLOAT_STRIDE + LINEAR_VELOCITY_Z] = body.linearVelocityZ(); - values[index * FLOAT_STRIDE + ANGULAR_VELOCITY_X] = body.angularVelocityX(); - values[index * FLOAT_STRIDE + ANGULAR_VELOCITY_Y] = body.angularVelocityY(); - values[index * FLOAT_STRIDE + ANGULAR_VELOCITY_Z] = body.angularVelocityZ(); - values[index * FLOAT_STRIDE + CENTER_OF_MASS_OFFSET_Y] = - body.centerOfMassOffsetY(); - bodiesByUuid.put(body.bodyUuid(), index); - Ref bodyRef = body.bodyRef(); - if (bodyRef != null) { - bodiesByRowIndex.put(bodyRef.getIndex(), index); - } + private static CompactSnapshot fromFrame(@Nonnull PhysicsSnapshotFrame frame) { + CompactSnapshotBuilder builder = compactBuilder(frame.bodies().size()); + for (PhysicsBodySnapshot body : frame.bodies()) { + builder.addBody(body.bodyRef(), + body.bodyUuid(), + body.spaceUuid(), + body.bodyType(), + body.positionX(), + body.positionY(), + body.positionZ(), + body.rotationX(), + body.rotationY(), + body.rotationZ(), + body.rotationW(), + body.linearVelocityX(), + body.linearVelocityY(), + body.linearVelocityZ(), + body.angularVelocityX(), + body.angularVelocityY(), + body.angularVelocityZ(), + body.centerOfMassOffsetY(), + body.sleeping()); } - return new PublishedSnapshot(frame.sequence(), - frame.dt(), - bodyRefs, - bodyUuids, - spaceUuids, - bodyTypes, - values, - sleeping, - bodiesByUuid, - bodiesByRowIndex); + return builder.build(frame.sequence(), frame.dt()); } @Nonnull @@ -240,20 +284,47 @@ private static Int2IntOpenHashMap rowIndex(int expected) { return index; } + @Nonnull + private CompactSnapshot withFrame(long sequence, float dt) { + if (bodyCount() == 0 + && sequence == PhysicsSnapshotFrame.EMPTY.sequence() + && dt == PhysicsSnapshotFrame.EMPTY.dt()) { + return EMPTY; + } + return new CompactSnapshot(sequence, + dt, + bodyRefs, + bodyUuids, + spaceUuids, + bodyTypes, + values, + sleeping, + bodiesByUuid, + bodiesByRowIndex); + } + + private long sequence() { + return sequence; + } + + private float dt() { + return dt; + } + @Nonnull private PhysicsSnapshotFrame frame() { if (bodyCount() == 0 && sequence == PhysicsSnapshotFrame.EMPTY.sequence() && dt == PhysicsSnapshotFrame.EMPTY.dt()) { return PhysicsSnapshotFrame.EMPTY; } - List bodies = new ArrayList<>(bodyCount()); + List bodies = new java.util.ArrayList<>(bodyCount()); for (int index = 0; index < bodyCount(); index++) { bodies.add(body(index)); } return new PhysicsSnapshotFrame(sequence, dt, bodies); } - private int bodyCount() { + public int bodyCount() { return bodyUuids.length; } @@ -296,7 +367,7 @@ private PhysicsBodySnapshot body(int index) { value(index, ANGULAR_VELOCITY_Y), value(index, ANGULAR_VELOCITY_Z), value(index, CENTER_OF_MASS_OFFSET_Y), - sleeping[index]); + sleeping.get(index)); } private void forEachBodyCursor(@Nonnull Consumer consumer) { @@ -312,6 +383,136 @@ private float value(int bodyIndex, int valueIndex) { } } + public static final class CompactSnapshotBuilder { + + @Nonnull + private Ref[] bodyRefs; + @Nonnull + private UUID[] bodyUuids; + @Nonnull + private UUID[] spaceUuids; + @Nonnull + private PhysicsBodyType[] bodyTypes; + @Nonnull + private float[] values; + @Nonnull + private BitSet sleeping; + private int size; + + @SuppressWarnings("unchecked") + private CompactSnapshotBuilder(int expectedBodies) { + int capacity = Math.max(0, expectedBodies); + bodyRefs = (Ref[]) new Ref[capacity]; + bodyUuids = new UUID[capacity]; + spaceUuids = new UUID[capacity]; + bodyTypes = new PhysicsBodyType[capacity]; + values = new float[capacity * CompactSnapshot.FLOAT_STRIDE]; + sleeping = new BitSet(capacity); + } + + public void addBody(@Nullable Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + float centerOfMassOffsetY, + boolean sleeping) { + ensureCapacity(size + 1); + int index = size++; + bodyRefs[index] = bodyRef; + bodyUuids[index] = Objects.requireNonNull(bodyUuid, "bodyUuid"); + spaceUuids[index] = Objects.requireNonNull(spaceUuid, "spaceUuid"); + bodyTypes[index] = Objects.requireNonNull(bodyType, "bodyType"); + this.sleeping.set(index, sleeping); + put(index, CompactSnapshot.POSITION_X, positionX); + put(index, CompactSnapshot.POSITION_Y, positionY); + put(index, CompactSnapshot.POSITION_Z, positionZ); + put(index, CompactSnapshot.ROTATION_X, rotationX); + put(index, CompactSnapshot.ROTATION_Y, rotationY); + put(index, CompactSnapshot.ROTATION_Z, rotationZ); + put(index, CompactSnapshot.ROTATION_W, rotationW); + put(index, CompactSnapshot.LINEAR_VELOCITY_X, linearVelocityX); + put(index, CompactSnapshot.LINEAR_VELOCITY_Y, linearVelocityY); + put(index, CompactSnapshot.LINEAR_VELOCITY_Z, linearVelocityZ); + put(index, CompactSnapshot.ANGULAR_VELOCITY_X, angularVelocityX); + put(index, CompactSnapshot.ANGULAR_VELOCITY_Y, angularVelocityY); + put(index, CompactSnapshot.ANGULAR_VELOCITY_Z, angularVelocityZ); + put(index, CompactSnapshot.CENTER_OF_MASS_OFFSET_Y, centerOfMassOffsetY); + } + + @Nonnull + public CompactSnapshot build() { + return build(PhysicsSnapshotFrame.EMPTY.sequence(), PhysicsSnapshotFrame.EMPTY.dt()); + } + + @Nonnull + private CompactSnapshot build(long sequence, float dt) { + if (size == 0) { + return CompactSnapshot.EMPTY.withFrame(sequence, dt); + } + Ref[] builtBodyRefs = trimRefs(bodyRefs, size); + UUID[] builtBodyUuids = Arrays.copyOf(bodyUuids, size); + UUID[] builtSpaceUuids = Arrays.copyOf(spaceUuids, size); + PhysicsBodyType[] builtBodyTypes = Arrays.copyOf(bodyTypes, size); + float[] builtValues = Arrays.copyOf(values, size * CompactSnapshot.FLOAT_STRIDE); + BitSet builtSleeping = sleeping.get(0, size); + Object2IntOpenHashMap bodiesByUuid = CompactSnapshot.uuidIndex(size); + Int2IntOpenHashMap bodiesByRowIndex = CompactSnapshot.rowIndex(size); + for (int index = 0; index < size; index++) { + bodiesByUuid.put(builtBodyUuids[index], index); + Ref bodyRef = builtBodyRefs[index]; + if (bodyRef != null) { + bodiesByRowIndex.put(bodyRef.getIndex(), index); + } + } + return new CompactSnapshot(sequence, + dt, + builtBodyRefs, + builtBodyUuids, + builtSpaceUuids, + builtBodyTypes, + builtValues, + builtSleeping, + bodiesByUuid, + bodiesByRowIndex); + } + + private void put(int bodyIndex, int valueIndex, float value) { + values[bodyIndex * CompactSnapshot.FLOAT_STRIDE + valueIndex] = value; + } + + private void ensureCapacity(int required) { + if (required <= bodyUuids.length) { + return; + } + int nextCapacity = Math.max(required, + Math.max(2, bodyUuids.length + (bodyUuids.length >> 1))); + bodyRefs = Arrays.copyOf(bodyRefs, nextCapacity); + bodyUuids = Arrays.copyOf(bodyUuids, nextCapacity); + spaceUuids = Arrays.copyOf(spaceUuids, nextCapacity); + bodyTypes = Arrays.copyOf(bodyTypes, nextCapacity); + values = Arrays.copyOf(values, nextCapacity * CompactSnapshot.FLOAT_STRIDE); + } + + @SuppressWarnings("unchecked") + @Nonnull + private static Ref[] trimRefs(@Nonnull Ref[] refs, int size) { + return Arrays.copyOf(refs, size); + } + } + public interface BodyCursor { @Nullable @@ -360,10 +561,10 @@ public interface BodyCursor { private static final class PublishedBodyCursor implements BodyCursor { @Nonnull - private final PublishedSnapshot snapshot; + private final CompactSnapshot snapshot; private int index; - private PublishedBodyCursor(@Nonnull PublishedSnapshot snapshot) { + private PublishedBodyCursor(@Nonnull CompactSnapshot snapshot) { this.snapshot = snapshot; } @@ -393,77 +594,77 @@ public PhysicsBodyType bodyType() { @Override public float positionX() { - return snapshot.value(index, PublishedSnapshot.POSITION_X); + return snapshot.value(index, CompactSnapshot.POSITION_X); } @Override public float positionY() { - return snapshot.value(index, PublishedSnapshot.POSITION_Y); + return snapshot.value(index, CompactSnapshot.POSITION_Y); } @Override public float positionZ() { - return snapshot.value(index, PublishedSnapshot.POSITION_Z); + return snapshot.value(index, CompactSnapshot.POSITION_Z); } @Override public float rotationX() { - return snapshot.value(index, PublishedSnapshot.ROTATION_X); + return snapshot.value(index, CompactSnapshot.ROTATION_X); } @Override public float rotationY() { - return snapshot.value(index, PublishedSnapshot.ROTATION_Y); + return snapshot.value(index, CompactSnapshot.ROTATION_Y); } @Override public float rotationZ() { - return snapshot.value(index, PublishedSnapshot.ROTATION_Z); + return snapshot.value(index, CompactSnapshot.ROTATION_Z); } @Override public float rotationW() { - return snapshot.value(index, PublishedSnapshot.ROTATION_W); + return snapshot.value(index, CompactSnapshot.ROTATION_W); } @Override public float linearVelocityX() { - return snapshot.value(index, PublishedSnapshot.LINEAR_VELOCITY_X); + return snapshot.value(index, CompactSnapshot.LINEAR_VELOCITY_X); } @Override public float linearVelocityY() { - return snapshot.value(index, PublishedSnapshot.LINEAR_VELOCITY_Y); + return snapshot.value(index, CompactSnapshot.LINEAR_VELOCITY_Y); } @Override public float linearVelocityZ() { - return snapshot.value(index, PublishedSnapshot.LINEAR_VELOCITY_Z); + return snapshot.value(index, CompactSnapshot.LINEAR_VELOCITY_Z); } @Override public float angularVelocityX() { - return snapshot.value(index, PublishedSnapshot.ANGULAR_VELOCITY_X); + return snapshot.value(index, CompactSnapshot.ANGULAR_VELOCITY_X); } @Override public float angularVelocityY() { - return snapshot.value(index, PublishedSnapshot.ANGULAR_VELOCITY_Y); + return snapshot.value(index, CompactSnapshot.ANGULAR_VELOCITY_Y); } @Override public float angularVelocityZ() { - return snapshot.value(index, PublishedSnapshot.ANGULAR_VELOCITY_Z); + return snapshot.value(index, CompactSnapshot.ANGULAR_VELOCITY_Z); } @Override public float centerOfMassOffsetY() { - return snapshot.value(index, PublishedSnapshot.CENTER_OF_MASS_OFFSET_Y); + return snapshot.value(index, CompactSnapshot.CENTER_OF_MASS_OFFSET_Y); } @Override public boolean sleeping() { - return snapshot.sleeping[index]; + return snapshot.sleeping.get(index); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java index f21363d3..08eee14d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResource.java @@ -5,7 +5,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import java.util.List; import java.util.Objects; @@ -268,7 +267,7 @@ public record CompletedStep(@Nullable StepInput input, long stepSubmitNanos, long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, - @Nonnull List bodySnapshots, + @Nonnull PhysicsSnapshotResource.CompactSnapshot bodySnapshot, @Nonnull List physicsEvents, int droppedBackendEventCount, @Nullable Throwable failure) { @@ -277,7 +276,12 @@ public CompletedStep(int spaces, int substeps, long stepSubmitNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats) { - this(spaces, substeps, stepSubmitNanos, 0L, nativePhaseStats, List.of()); + this(spaces, + substeps, + stepSubmitNanos, + 0L, + nativePhaseStats, + PhysicsSnapshotResource.emptyCompactSnapshot()); } public CompletedStep(int spaces, @@ -285,9 +289,15 @@ public CompletedStep(int spaces, long stepSubmitNanos, long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, - @Nonnull List bodySnapshots) { - this(spaces, substeps, stepSubmitNanos, snapshotNanos, nativePhaseStats, bodySnapshots, - List.of(), 0); + @Nonnull PhysicsSnapshotResource.CompactSnapshot bodySnapshot) { + this(spaces, + substeps, + stepSubmitNanos, + snapshotNanos, + nativePhaseStats, + bodySnapshot, + List.of(), + 0); } public CompletedStep(int spaces, @@ -295,7 +305,7 @@ public CompletedStep(int spaces, long stepSubmitNanos, long snapshotNanos, @Nonnull PhysicsStepPhaseStats nativePhaseStats, - @Nonnull List bodySnapshots, + @Nonnull PhysicsSnapshotResource.CompactSnapshot bodySnapshot, @Nonnull List physicsEvents, int droppedBackendEventCount) { this(null, @@ -304,7 +314,7 @@ public CompletedStep(int spaces, stepSubmitNanos, snapshotNanos, nativePhaseStats, - bodySnapshots, + bodySnapshot, physicsEvents, droppedBackendEventCount, null); @@ -316,8 +326,7 @@ public CompletedStep(int spaces, stepSubmitNanos = Math.max(0L, stepSubmitNanos); snapshotNanos = Math.max(0L, snapshotNanos); Objects.requireNonNull(nativePhaseStats, "nativePhaseStats"); - bodySnapshots = List.copyOf(Objects.requireNonNull(bodySnapshots, - "bodySnapshots")); + Objects.requireNonNull(bodySnapshot, "bodySnapshot"); physicsEvents = List.copyOf(Objects.requireNonNull(physicsEvents, "physicsEvents")); droppedBackendEventCount = Math.max(0, droppedBackendEventCount); @@ -331,7 +340,7 @@ private CompletedStep withInput(@Nonnull StepInput input) { stepSubmitNanos, snapshotNanos, nativePhaseStats, - bodySnapshots, + bodySnapshot, physicsEvents, droppedBackendEventCount, failure); @@ -345,7 +354,7 @@ private static CompletedStep failed(@Nonnull StepInput input, 0L, 0L, PhysicsStepPhaseStats.unavailable(), - List.of(), + PhysicsSnapshotResource.emptyCompactSnapshot(), List.of(), 0, Objects.requireNonNull(failure, "failure")); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java index 82ea378c..fb90c8eb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java @@ -16,9 +16,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; -import java.util.List; import java.util.Set; import javax.annotation.Nonnull; @@ -60,18 +57,16 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) input.droppedBacklogDtSeconds(), input.dtCapHit()); } - List bodies = completed.bodySnapshots(); + PhysicsSnapshotResource.CompactSnapshot bodySnapshot = completed.bodySnapshot(); long nextSequence = snapshot.latestSequence() + 1L; float frameDt = input != null ? input.submittedDtSeconds() : dt; - PhysicsSnapshotFrame frame = new PhysicsSnapshotFrame(nextSequence, - frameDt, - bodies); - snapshot.publish(frame); - profiling.recordSnapshot(completed.snapshotNanos(), bodies.size()); + snapshot.publish(nextSequence, frameDt, bodySnapshot); + int bodyCount = bodySnapshot.bodyCount(); + profiling.recordSnapshot(completed.snapshotNanos(), bodyCount); store.getResource(PhysicsEventResource.getResourceType()) - .publishStepFrame(frame.sequence(), + .publishStepFrame(nextSequence, Math.max(0L, store.getExternalData().getWorld().getTick()), - bodies.size(), + bodyCount, profiling.getStepSubmitNanos(), completed.snapshotNanos(), completed.physicsEvents(), diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index 6b4edfb9..485a7831 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -22,6 +22,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; @@ -32,7 +33,6 @@ import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import java.util.ArrayList; @@ -141,7 +141,7 @@ private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtim ? collectStepPhaseStats(bindings) : PhysicsStepPhaseStats.unavailable(); long snapshotStartNanos = profilingEnabled ? System.nanoTime() : 0L; - List bodySnapshots = collectOwnerLaneSnapshots(runtime, + PhysicsSnapshotResource.CompactSnapshot bodySnapshot = collectOwnerLaneSnapshot(runtime, bindings); long snapshotNanos = profilingEnabled ? System.nanoTime() - snapshotStartNanos : 0L; StepBackendEvents backendEvents = collectOwnerLaneBackendEvents(runtime, @@ -153,7 +153,7 @@ private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtim stepNanos, snapshotNanos, nativePhaseStats, - bodySnapshots, + bodySnapshot, backendEvents.physicsEvents(), backendEvents.droppedBackendEventCount()); } @@ -168,11 +168,11 @@ private static List runtimeStepBindings( } @Nonnull - private static List collectOwnerLaneSnapshots( + private static PhysicsSnapshotResource.CompactSnapshot collectOwnerLaneSnapshot( @Nonnull PhysicsRuntimeResource runtime, @Nonnull List bindings) { - List snapshots = new ArrayList<>(runtimeBodyHandleCount(runtime, - bindings)); + PhysicsSnapshotResource.CompactSnapshotBuilder snapshot = + PhysicsSnapshotResource.compactBuilder(runtimeBodyHandleCount(runtime, bindings)); for (RuntimeStepBinding binding : bindings) { binding.backendRuntime().snapshotBodies(binding.spaceHandle().value(), bodyIds -> runtime.forEachBodyHandle(binding.backendId(), @@ -212,7 +212,7 @@ private static List collectOwnerLaneSnapshots( _, _, _) -> collectOwnerLaneSnapshot(runtime, - snapshots, + snapshot, binding.backendId(), binding.spaceHandle(), bodyId, @@ -233,7 +233,7 @@ private static List collectOwnerLaneSnapshots( centerOfMassOffsetY, sleeping)); } - return snapshots; + return snapshot.build(); } private static int runtimeBodyHandleCount(@Nonnull PhysicsRuntimeResource runtime, @@ -246,7 +246,7 @@ private static int runtimeBodyHandleCount(@Nonnull PhysicsRuntimeResource runtim } private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull List snapshots, + @Nonnull PhysicsSnapshotResource.CompactSnapshotBuilder snapshot, @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle, long bodyId, @@ -272,7 +272,7 @@ private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource run if (metadata == null) { return; } - snapshots.add(PhysicsBodySnapshot.of(metadata.bodyRef(), + snapshot.addBody(metadata.bodyRef(), metadata.bodyUuid(), metadata.spaceUuid(), BackendRuntimeCodes.bodyType(bodyTypeCode), @@ -290,7 +290,7 @@ private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource run angularVelocityY, angularVelocityZ, centerOfMassOffsetY, - sleeping)); + sleeping); } @Nonnull diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java index 3f9c1d7a..ee1f85f5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStepSchedulerResourceTest.java @@ -8,6 +8,7 @@ import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -15,6 +16,18 @@ class PhysicsStepSchedulerResourceTest { + @Test + void completedStepCarriesCompactSnapshotPayloadInsteadOfBodySnapshotList() { + assertTrue(Arrays.stream(PhysicsStepSchedulerResource.CompletedStep.class.getRecordComponents()) + .map(component -> component.getGenericType().getTypeName()) + .noneMatch(typeName -> typeName.contains( + "dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot")), + "CompletedStep should not expose List in the owner-lane handoff"); + assertTrue(Arrays.stream(PhysicsStepSchedulerResource.CompletedStep.class.getDeclaredMethods()) + .noneMatch(method -> method.getName().equals("bodySnapshots")), + "CompletedStep should expose compact snapshot payloads, not bodySnapshots()"); + } + @Test void submittedStepRunsAsynchronouslyAndSkipsWhilePending() throws Exception { PhysicsStepSchedulerResource scheduler = new PhysicsStepSchedulerResource(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index e084b122..09d13a9c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -395,6 +395,68 @@ void snapshotResourceCursorReadsCompactBodyStateWithoutFrameMaterialization() { assertEquals(List.of(firstBodyUuid, secondBodyUuid), visited); } + @Test + void snapshotResourcePublishesCompactPayloadWithoutLegacyFrameMaterialization() { + PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000029"); + UUID firstBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000030"); + UUID secondBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000031"); + Ref firstBodyRef = new TestRef(30); + Ref secondBodyRef = new TestRef(31); + PhysicsBodySnapshot first = snapshot(firstBodyRef, firstBodyUuid, spaceUuid); + PhysicsBodySnapshot second = snapshot(secondBodyRef, secondBodyUuid, spaceUuid, true); + PhysicsSnapshotResource.CompactSnapshotBuilder builder = + PhysicsSnapshotResource.compactBuilder(2); + addCompactBody(builder, first); + addCompactBody(builder, second); + + resource.publish(15L, 0.06f, builder.build()); + + assertEquals(15L, resource.latestSequence()); + assertEquals(2, resource.bodyCount()); + assertSnapshotEquals(first, resource.getBody(firstBodyUuid)); + assertSnapshotEquals(second, resource.getBody(secondBodyRef)); + List visited = new ArrayList<>(); + resource.forEachBodyCursor(cursor -> visited.add(cursor.bodyUuid())); + assertEquals(List.of(firstBodyUuid, secondBodyUuid), visited); + + PhysicsSnapshotFrame frame = resource.getLatestFrame(); + assertEquals(15L, frame.sequence()); + assertEquals(0.06f, frame.dt()); + assertEquals(2, frame.bodies().size()); + assertSnapshotEquals(first, frame.bodies().getFirst()); + assertSnapshotEquals(second, frame.bodies().get(1)); + } + + @Test + void snapshotResourceRemovesBodiesFromCompactPayloadWithoutLegacyFrameRoundTrip() { + PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); + UUID spaceUuid = UUID.fromString("00000000-0000-0000-0000-000000000032"); + UUID removedBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000034"); + UUID retainedBodyUuid = UUID.fromString("00000000-0000-0000-0000-000000000035"); + Ref removedBodyRef = new TestRef(34); + Ref retainedBodyRef = new TestRef(35); + PhysicsBodySnapshot removed = snapshot(removedBodyRef, removedBodyUuid, spaceUuid); + PhysicsBodySnapshot retained = snapshot(retainedBodyRef, retainedBodyUuid, spaceUuid); + PhysicsSnapshotResource.CompactSnapshotBuilder builder = + PhysicsSnapshotResource.compactBuilder(2); + addCompactBody(builder, removed); + addCompactBody(builder, retained); + resource.publish(16L, 0.07f, builder.build()); + + resource.removeBody(removedBodyUuid); + + assertEquals(1, resource.bodyCount()); + assertNull(resource.getBody(removedBodyUuid)); + assertNull(resource.getBody(removedBodyRef)); + assertSnapshotEquals(retained, resource.getBody(retainedBodyUuid)); + assertSnapshotEquals(retained, resource.getBody(retainedBodyRef)); + assertEquals(16L, resource.getLatestFrame().sequence()); + assertEquals(0.07f, resource.getLatestFrame().dt()); + assertEquals(List.of(retainedBodyUuid), + resource.getLatestFrame().bodies().stream().map(PhysicsBodySnapshot::bodyUuid).toList()); + } + @Test void snapshotResourceRemovesMultipleBodiesInOneBatch() { PhysicsSnapshotResource resource = new PhysicsSnapshotResource(); @@ -477,6 +539,13 @@ public boolean isValid() { private static PhysicsBodySnapshot snapshot(Ref bodyRef, UUID bodyUuid, UUID spaceUuid) { + return snapshot(bodyRef, bodyUuid, spaceUuid, false); + } + + private static PhysicsBodySnapshot snapshot(Ref bodyRef, + UUID bodyUuid, + UUID spaceUuid, + boolean sleeping) { return new PhysicsBodySnapshot(bodyRef, bodyUuid, spaceUuid, @@ -486,7 +555,30 @@ private static PhysicsBodySnapshot snapshot(Ref bodyRef, new Vector3f(), new Vector3f(), 0.0f, - false); + sleeping); + } + + private static void addCompactBody(PhysicsSnapshotResource.CompactSnapshotBuilder builder, + PhysicsBodySnapshot body) { + builder.addBody(body.bodyRef(), + body.bodyUuid(), + body.spaceUuid(), + body.bodyType(), + body.positionX(), + body.positionY(), + body.positionZ(), + body.rotationX(), + body.rotationY(), + body.rotationZ(), + body.rotationW(), + body.linearVelocityX(), + body.linearVelocityY(), + body.linearVelocityZ(), + body.angularVelocityX(), + body.angularVelocityY(), + body.angularVelocityZ(), + body.centerOfMassOffsetY(), + body.sleeping()); } private static ChunkCollisionPayload chunkPayload(double centerX) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java new file mode 100644 index 00000000..2c66a251 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java @@ -0,0 +1,130 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsStepPhaseStats; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; +import dev.hytalemodding.impulse.core.plugin.events.PhysicsStepEvent; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import java.util.ArrayList; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class CompletedStepPublicationSystemTest { + + @Test + void completedStepPublishesCompactSnapshotAndRecordsCounts() throws Exception { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("completed-step-publication-compact")), + EmptyResourceStorage.get()); + try { + PhysicsStepSchedulerResource scheduler = store.getResource( + PhysicsStepSchedulerResource.getResourceType()); + PhysicsStepSchedulerResource.StepInput input = scheduler.acceptStepInput(0.05f, + PhysicsStepSchedulingMode.ACCUMULATE_PENDING_DT, + 0.10f); + UUID spaceUuid = uuid(1); + UUID firstBodyUuid = uuid(101); + UUID secondBodyUuid = uuid(102); + PhysicsSnapshotResource.CompactSnapshotBuilder builder = + PhysicsSnapshotResource.compactBuilder(2); + addBody(builder, firstBodyUuid, spaceUuid, PhysicsBodyType.DYNAMIC, 1.0f); + addBody(builder, secondBodyUuid, spaceUuid, PhysicsBodyType.STATIC, 2.0f); + PhysicsSnapshotResource.CompactSnapshot compactSnapshot = builder.build(); + + assertTrue(scheduler.submitStep(input, + () -> new PhysicsStepSchedulerResource.CompletedStep(3, + 9, + 123L, + 456L, + PhysicsStepPhaseStats.unavailable(), + compactSnapshot), + 10L)); + scheduler.whenIdle().toCompletableFuture().get(5, TimeUnit.SECONDS); + + new CompletedStepPublicationSystem().tick(0.25f, 0, store); + + PhysicsSnapshotResource snapshot = store.getResource( + PhysicsSnapshotResource.getResourceType()); + assertEquals(1L, snapshot.latestSequence()); + assertEquals(2, snapshot.bodyCount()); + assertEquals(0.05f, snapshot.getLatestFrame().dt(), 0.0001f); + PhysicsBodySnapshot firstBody = snapshot.getBody(firstBodyUuid); + assertNotNull(firstBody); + assertEquals(1.0f, firstBody.positionX(), 0.0001f); + + PhysicsProfilingResource.StepSample profiling = store.getResource( + PhysicsProfilingResource.getResourceType()).latestStepSample(); + assertEquals(123L, profiling.stepSubmitNanos()); + assertEquals(456L, profiling.snapshotNanos()); + assertEquals(2, profiling.publishedBodies()); + assertEquals(1, profiling.schedulerSamples()); + assertEquals(0.05f, profiling.schedulerSubmittedDtSeconds(), 0.0001f); + + PhysicsEventFrame eventFrame = store.getResource(PhysicsEventResource.getResourceType()) + .getLatestFrame(); + PhysicsStepEvent stepEvent = eventFrame.latestStep(); + assertNotNull(stepEvent); + assertEquals(1L, stepEvent.stepSequence()); + assertEquals(1L, stepEvent.snapshotFrameEpoch()); + assertEquals(2, stepEvent.bodyCount()); + assertEquals(123L, stepEvent.stepNanos()); + assertEquals(456L, stepEvent.snapshotNanos()); + } finally { + store.getResource(PhysicsStepSchedulerResource.getResourceType()).close(); + registry.removeStore(store); + registry.shutdown(); + } + } + + private static void addBody(@Nonnull PhysicsSnapshotResource.CompactSnapshotBuilder builder, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + @Nonnull PhysicsBodyType bodyType, + float positionX) { + builder.addBody(null, + bodyUuid, + spaceUuid, + bodyType, + positionX, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + + private static UUID uuid(long lowBits) { + return new UUID(0L, lowBits); + } +} From a800682c2d340177b70a5b307923643206448370 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 11:54:05 +0200 Subject: [PATCH 501/534] fix(physicschunk): preserve streaming cache clones Signed-off-by: Blovien --- ...hysicsChunkCollisionStreamingResource.java | 14 ++- .../PhysicsChunkMutationCache.java | 60 ++++++++++- .../physicschunk/ShapeTemplateCache.java | 7 ++ ...csChunkCollisionStreamingResourceTest.java | 100 ++++++++++++++++++ 4 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResourceTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java index 153e1c67..83152c73 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java @@ -28,13 +28,20 @@ public final class PhysicsChunkCollisionStreamingResource implements Resource { @Nonnull - private final PhysicsChunkMutationCache cache = new PhysicsChunkMutationCache(); + private final PhysicsChunkMutationCache cache; private long tick; @Nullable private static ResourceType resourceType; public PhysicsChunkCollisionStreamingResource() { + this(new PhysicsChunkMutationCache(), 0L); + } + + private PhysicsChunkCollisionStreamingResource(@Nonnull PhysicsChunkMutationCache cache, + long tick) { + this.cache = Objects.requireNonNull(cache, "cache"); + this.tick = tick; } public static void setResourceType( @@ -245,10 +252,7 @@ public synchronized int bodyCount(@Nonnull UUID spaceUuid) { @Nonnull @Override public synchronized PhysicsChunkCollisionStreamingResource clone() { - PhysicsChunkCollisionStreamingResource copy = - new PhysicsChunkCollisionStreamingResource(); - copy.tick = tick; - return copy; + return new PhysicsChunkCollisionStreamingResource(cache.copy(), tick); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java index 089eacb6..7f7819ad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java @@ -44,9 +44,27 @@ public final class PhysicsChunkMutationCache { private final Object2ObjectMap spaces = new Object2ObjectOpenHashMap<>(); @Nonnull - private final ShapeTemplateCache shapeTemplates = new ShapeTemplateCache(); + private final ShapeTemplateCache shapeTemplates; @Nonnull - private final SectionColliderBuilder sectionBuilder = new SectionColliderBuilder(shapeTemplates); + private final SectionColliderBuilder sectionBuilder; + + public PhysicsChunkMutationCache() { + this(new ShapeTemplateCache()); + } + + private PhysicsChunkMutationCache(@Nonnull ShapeTemplateCache shapeTemplates) { + this.shapeTemplates = Objects.requireNonNull(shapeTemplates, "shapeTemplates"); + this.sectionBuilder = new SectionColliderBuilder(shapeTemplates); + } + + @Nonnull + synchronized PhysicsChunkMutationCache copy() { + PhysicsChunkMutationCache copy = new PhysicsChunkMutationCache(shapeTemplates.copy()); + for (Object2ObjectMap.Entry entry : spaces.object2ObjectEntrySet()) { + copy.spaces.put(entry.getKey(), entry.getValue().copy()); + } + return copy; + } @Nonnull public synchronized PhysicsChunkBuildStats ensureAround(@Nonnull World world, @@ -782,6 +800,27 @@ private static final class SpaceCollisionCache { private final Int2ObjectOpenHashMap bodyTargetsByRowIndex = new Int2ObjectOpenHashMap<>(); + @Nonnull + private SpaceCollisionCache copy() { + SpaceCollisionCache copy = new SpaceCollisionCache(); + for (Long2ObjectMap.Entry entry : sections.long2ObjectEntrySet()) { + copy.sections.put(entry.getLongKey(), entry.getValue().copy()); + } + copy.missingBlockChunkBackoffs.putAll(missingBlockChunkBackoffs); + copy.missingBlockSectionBackoffs.putAll(missingBlockSectionBackoffs); + for (Object2ObjectMap.Entry entry + : bodyTargets.object2ObjectEntrySet()) { + copy.bodyTargets.put(entry.getKey(), entry.getValue().copy()); + } + for (Int2ObjectMap.Entry entry + : bodyTargetsByRowIndex.int2ObjectEntrySet()) { + RefBodyStreamingTarget row = entry.getValue(); + copy.bodyTargetsByRowIndex.put(entry.getIntKey(), + new RefBodyStreamingTarget(row.bodyRef(), row.target().copy())); + } + return copy; + } + private boolean isEmpty() { return sections.isEmpty() && missingBlockChunkBackoffs.isEmpty() @@ -820,6 +859,18 @@ private CachedSection(int chunkX, this.bodyCount = bodyCount; this.voxelTerrain = voxelTerrain; } + + @Nonnull + private CachedSection copy() { + return new CachedSection(chunkX, + sectionY, + chunkZ, + lastUsedTick, + neighborhoodSignature, + buildOptions, + bodyCount, + voxelTerrain); + } } private static final class CachedBodyStreamingTarget { @@ -839,6 +890,11 @@ private CachedBodyStreamingTarget(@Nonnull PhysicsChunkStreamingBounds bounds, this.lastSeenTick = lastSeenTick; this.lastRefreshTick = lastRefreshTick; } + + @Nonnull + private CachedBodyStreamingTarget copy() { + return new CachedBodyStreamingTarget(bounds, sleeping, lastSeenTick, lastRefreshTick); + } } private record RefBodyStreamingTarget(@Nonnull Ref bodyRef, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ShapeTemplateCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ShapeTemplateCache.java index 47d1047e..bc3dd01c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ShapeTemplateCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/ShapeTemplateCache.java @@ -36,6 +36,13 @@ int size() { return templates.size(); } + @Nonnull + ShapeTemplateCache copy() { + ShapeTemplateCache copy = new ShapeTemplateCache(); + copy.templates.putAll(templates); + return copy; + } + private static long key(int blockId, int rotation) { return ((long) blockId << Integer.SIZE) ^ (rotation & 0xFFFF_FFFFL); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResourceTest.java new file mode 100644 index 00000000..794e4fad --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResourceTest.java @@ -0,0 +1,100 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshReason; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class PhysicsChunkCollisionStreamingResourceTest { + + @Test + void clonePreservesDynamicBodyStreamingTargetState() { + UUID spaceUuid = uuid(1); + UUID bodyUuid = uuid(2); + PhysicsChunkStreamingBounds bounds = PhysicsChunkStreamingBounds.from(10.0f, + 65.0f, + -4.0f, + 4); + PhysicsChunkCollisionStreamingResource resource = + new PhysicsChunkCollisionStreamingResource(); + + TargetRefreshDecision initial = resource.shouldRefreshBodyTarget(spaceUuid, + bodyUuid, + bounds, + false, + 1L, + 100, + null); + assertTrue(initial.refresh()); + resource.recordBodyTargetRefresh(spaceUuid, bodyUuid, bounds, false, 1L); + + PhysicsChunkCollisionStreamingResource copy = resource.clone(); + TargetRefreshDecision copied = copy.shouldRefreshBodyTarget(spaceUuid, + bodyUuid, + bounds, + false, + 2L, + 100, + null); + + assertFalse(copied.refresh()); + assertEquals(TargetRefreshReason.STABLE_SKIP, copied.reason()); + } + + @Test + void clonePreservesRefBodyStreamingTargetState() { + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physicschunk-streaming-resource-ref-clone")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(3); + Ref bodyRef = new Ref<>(store, 11); + PhysicsChunkStreamingBounds bounds = PhysicsChunkStreamingBounds.from(32.0f, + 65.0f, + 16.0f, + 4); + PhysicsChunkCollisionStreamingResource resource = + new PhysicsChunkCollisionStreamingResource(); + + TargetRefreshDecision initial = resource.shouldRefreshBodyTarget(spaceUuid, + bodyRef, + bounds, + false, + 1L, + 100, + null); + assertTrue(initial.refresh()); + resource.recordBodyTargetRefresh(spaceUuid, bodyRef, bounds, false, 1L); + + PhysicsChunkCollisionStreamingResource copy = resource.clone(); + TargetRefreshDecision copied = copy.shouldRefreshBodyTarget(spaceUuid, + bodyRef, + bounds, + false, + 2L, + 100, + null); + + assertFalse(copied.refresh()); + assertEquals(TargetRefreshReason.STABLE_SKIP, copied.reason()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } +} From 8fd6589b4680198c0716dd0d6c1a11a3971d264d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 13:14:33 +0200 Subject: [PATCH 502/534] test(examples): remove stale unit coverage Signed-off-by: Blovien --- .../examples/commands/EventsCommandTest.java | 49 --- .../events/PhysicsEventTrackerTest.java | 51 --- .../explosive/ExplosiveBlockPolicyTest.java | 76 ---- .../explosive/ExplosiveBlockRuntimeTest.java | 367 ------------------ .../explosive/ExplosiveFuseComponentTest.java | 54 --- .../ExampleControlTestSupport.java | 23 -- .../utils/ExampleBlockEntityVisualsTest.java | 31 -- .../utils/ExamplePhysicsUtilsTest.java | 155 -------- 8 files changed, 806 deletions(-) delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicyTest.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntimeTest.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveFuseComponentTest.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisualsTest.java delete mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java deleted file mode 100644 index a6eed464..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/EventsCommandTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package dev.hytalemodding.impulse.examples.commands; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import dev.hytalemodding.impulse.examples.events.PhysicsEventSummary; -import java.util.List; -import java.util.UUID; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class EventsCommandTest { - - @Test - void formatsLatestContactEventSummary() { - UUID first = new UUID(1L, 2L); - UUID second = new UUID(3L, 4L); - PhysicsEventFrame frame = new PhysicsEventFrame(12L, - 34L, - 56L, - 78L, - 90L, - List.of(), - List.of(), - List.of(new PhysicsContactEvent(new SpaceId(5), - PhysicsContactPhase.OBSERVED, - first, - second, - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(4.0f, 5.0f, 6.0f), - new Vector3f(0.0f, 1.0f, 0.0f), - -0.125f, - 2.5f)), - 1); - - String summary = PhysicsEventSummary.format(frame); - - assertTrue(summary.contains("contacts=1")); - assertTrue(summary.contains("firstContact=observed")); - assertTrue(summary.contains("space=5")); - assertTrue(summary.contains("bodyA=" + first)); - assertTrue(summary.contains("bodyB=" + second)); - assertTrue(summary.contains("impulse=2.500")); - assertTrue(summary.contains("droppedBackendEvents=1")); - } -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java deleted file mode 100644 index daa1a615..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/events/PhysicsEventTrackerTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.hytalemodding.impulse.examples.events; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.hytalemodding.impulse.api.PhysicsContactPhase; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; -import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; -import java.util.List; -import java.util.UUID; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PhysicsEventTrackerTest { - - @Test - void tracksContactEventsFromPublishedFrames() { - PhysicsEventTracker.reset(); - UUID first = new UUID(1L, 2L); - UUID second = new UUID(3L, 4L); - PhysicsEventFrame frame = new PhysicsEventFrame(12L, - 34L, - 56L, - 78L, - 90L, - List.of(), - List.of(), - List.of(new PhysicsContactEvent(new SpaceId(5), - PhysicsContactPhase.OBSERVED, - first, - second, - new Vector3f(1.0f, 2.0f, 3.0f), - new Vector3f(4.0f, 5.0f, 6.0f), - new Vector3f(0.0f, 1.0f, 0.0f), - -0.125f, - 2.5f)), - 1); - - PhysicsEventTracker.record(frame); - - String snapshot = PhysicsEventTracker.snapshot(); - assertTrue(snapshot.contains("trackedFrames=1")); - assertTrue(snapshot.contains("trackedPhysicsEvents=1")); - assertTrue(snapshot.contains("trackedContacts=1")); - assertTrue(snapshot.contains("firstContact=observed")); - assertTrue(snapshot.contains("space=5")); - assertTrue(snapshot.contains("bodyA=" + first)); - assertTrue(snapshot.contains("bodyB=" + second)); - assertTrue(snapshot.contains("droppedBackendEvents=1")); - } -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicyTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicyTest.java deleted file mode 100644 index 4a4c8e87..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockPolicyTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package dev.hytalemodding.impulse.examples.explosive; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.math.shape.Box; -import com.hypixel.hytale.protocol.BlockMaterial; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class ExplosiveBlockPolicyTest { - - @Test - void outwardImpulsePushesAwayFromExplosionWithVerticalLift() { - Vector3f impulse = ExplosiveBlockPolicy.outwardImpulse( - new Vector3f(0.5f, 10.5f, 0.5f), - new Vector3f(2.5f, 10.5f, 0.5f), - 12.0f, - 0.35f); - - assertTrue(impulse.x > 11.0f); - assertTrue(impulse.y > 3.0f); - assertEquals(0.0f, impulse.z, 0.0001f); - } - - @Test - void centeredBlocksStillReceiveUpwardImpulse() { - Vector3f impulse = ExplosiveBlockPolicy.outwardImpulse( - new Vector3f(4.5f, 8.5f, 4.5f), - new Vector3f(4.5f, 8.5f, 4.5f), - 6.0f, - 0.5f); - - assertEquals(0.0f, impulse.x, 0.0001f); - assertEquals(6.0f, impulse.y, 0.0001f); - assertEquals(0.0f, impulse.z, 0.0001f); - } - - @Test - void ignoresAirAndUnknownBlocksWhenCreatingFragments() { - assertFalse(ExplosiveBlockPolicy.isFragmentCandidate(0)); - assertFalse(ExplosiveBlockPolicy.isFragmentCandidate(1)); - assertTrue(ExplosiveBlockPolicy.isFragmentCandidate(42)); - } - - @Test - void simpleFragmentsRequireSolidFullUnitCollisionBox() { - Box unitBox = new Box(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); - - assertTrue(ExplosiveBlockPolicy.isSimpleFullCubeFragmentBlock(42, - false, - BlockMaterial.Solid, - new Box[] { unitBox })); - assertFalse(ExplosiveBlockPolicy.isSimpleFullCubeFragmentBlock(0, - false, - BlockMaterial.Solid, - new Box[] { unitBox })); - assertFalse(ExplosiveBlockPolicy.isSimpleFullCubeFragmentBlock(42, - true, - BlockMaterial.Solid, - new Box[] { unitBox })); - assertFalse(ExplosiveBlockPolicy.isSimpleFullCubeFragmentBlock(42, - false, - BlockMaterial.Empty, - new Box[] { unitBox })); - assertFalse(ExplosiveBlockPolicy.isSimpleFullCubeFragmentBlock(42, - false, - BlockMaterial.Solid, - new Box[] { new Box(0.0, 0.0, 0.0, 1.0, 0.5, 1.0) })); - assertFalse(ExplosiveBlockPolicy.isSimpleFullCubeFragmentBlock(42, - false, - BlockMaterial.Solid, - new Box[] { unitBox, unitBox })); - } -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntimeTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntimeTest.java deleted file mode 100644 index 64f4f84d..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntimeTest.java +++ /dev/null @@ -1,367 +0,0 @@ -package dev.hytalemodding.impulse.examples.explosive; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.math.vector.Rotation3f; -import com.hypixel.hytale.protocol.EntityPart; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; -import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; -import com.hypixel.hytale.server.core.entity.ExplosionConfig; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.Nonnull; -import org.joml.Quaterniond; -import org.joml.Quaternionf; -import org.joml.Vector3d; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class ExplosiveBlockRuntimeTest { - - @Test - void explosionConfigDamagesEntitiesWithoutConsumingTerrain() throws ReflectiveOperationException { - ExplosionConfig config = ExplosiveBlockRuntime.explosionConfig(4); - - assertFalse(booleanField(config, "damageBlocks")); - assertTrue(booleanField(config, "damageEntities")); - assertEquals(0, intField(config, "blockDamageRadius")); - assertEquals(4.0f, floatField(config, "entityDamageRadius"), 0.0001f); - assertEquals(0.0f, floatField(config, "blockDropChance"), 0.0001f); - assertEquals("SFX_Goblin_Lobber_Bomb_Death", objectField(config, "soundEventId")); - - ModelParticle[] particles = (ModelParticle[]) objectField(config, "particles"); - assertEquals(1, particles.length); - assertEquals("Explosion_Medium", particles[0].getSystemId()); - assertEquals(EntityPart.Entity, particles[0].getTargetEntityPart()); - } - - @Test - void chunkBlockCoordinateConvertsWorldCoordinateToLocalCoordinate() { - assertEquals(0, ExplosiveBlockRuntime.chunkBlockCoordinate(32)); - assertEquals(15, ExplosiveBlockRuntime.chunkBlockCoordinate(47)); - assertEquals(31, ExplosiveBlockRuntime.chunkBlockCoordinate(-1)); - } - - @Test - void sourceExplosionCenterBiasesAbovePhysicsBodyCenter() { - Vector3d center = ExplosiveBlockRuntime.sourceExplosionCenter( - new Vector3d(12.5, 40.5, -3.5)); - - assertEquals(new Vector3d(12.5, 41.5, -3.5), center); - } - - @Test - void contactExplosionCenterStartsAboveTerrainContactPoint() { - Vector3d center = ExplosiveBlockRuntime.contactExplosionCenter( - new Vector3d(12.5, 41.0, -3.5)); - - assertEquals(new Vector3d(12.5, 41.5, -3.5), center); - } - - @Test - void fragmentOffsetsAreNearestFirstSphereInsteadOfBottomFirstScan() { - List offsets = - ExplosiveBlockRuntime.sphericalFragmentOffsets(2); - - assertEquals(new ExplosiveBlockRuntime.FragmentOffset(0, 0, 0, 0), offsets.getFirst()); - for (int i = 1; i < offsets.size(); i++) { - assertTrue(offsets.get(i - 1).distanceSquared() <= offsets.get(i).distanceSquared()); - } - - List firstSeven = offsets.subList(0, 7); - assertTrue(firstSeven.stream().anyMatch(offset -> offset.dy() > 0)); - assertTrue(firstSeven.stream().anyMatch(offset -> offset.dy() < 0)); - assertTrue(firstSeven.stream().noneMatch(offset -> offset.dy() < -1)); - } - - @Test - void largeSphereHasEnoughOffsetsForBigExplosionFragmentCap() { - List offsets = - ExplosiveBlockRuntime.sphericalFragmentOffsets(8); - - assertTrue(offsets.size() >= 1024); - } - - @Test - void faceConnectedFragmentsBecomeOneAabbGroup() { - List groups = ExplosiveBlockRuntime.groupFragments( - List.of( - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 1, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 2, 10, 0) - ), - new Vector3d(1.5, 10.5, 0.5), - 8); - - assertEquals(1, groups.size()); - ExplosiveBlockRuntime.FragmentGroup group = groups.getFirst(); - assertEquals("Hytale:block/stone", group.blockType()); - assertEquals(3, group.blockCount()); - assertEquals(new Vector3d(1.5, 10.5, 0.5), group.center()); - assertEquals(1.5f, group.halfExtentX(), 0.0001f); - assertEquals(0.5f, group.halfExtentY(), 0.0001f); - assertEquals(0.5f, group.halfExtentZ(), 0.0001f); - assertEquals(3.0f, group.mass(), 0.0001f); - } - - @Test - void groupedFragmentVisualsUseBlockBasePositionsAndSyncOffsets() { - List groups = ExplosiveBlockRuntime.groupFragments( - List.of( - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 1, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 2, 10, 0) - ), - new Vector3d(1.5, 10.5, 0.5), - 8); - - List visuals = groups.getFirst().visualBlocks(); - - assertEquals(3, visuals.size()); - assertVectorEquals(new Vector3d(1.5, 10.0, 0.5), visuals.getFirst().position()); - assertVectorEquals(new Vector3f(0.0f, 0.0f, 0.0f), visuals.get(0).localPositionOffset()); - assertVisualSyncsToSpawnPosition(groups.getFirst(), visuals.get(0)); - assertVectorEquals(new Vector3d(0.5, 10.0, 0.5), visuals.get(1).position()); - assertVectorEquals(new Vector3f(-1.0f, 0.0f, 0.0f), visuals.get(1).localPositionOffset()); - assertVisualSyncsToSpawnPosition(groups.getFirst(), visuals.get(1)); - assertVectorEquals(new Vector3d(2.5, 10.0, 0.5), visuals.get(2).position()); - assertVectorEquals(new Vector3f(1.0f, 0.0f, 0.0f), visuals.get(2).localPositionOffset()); - assertVisualSyncsToSpawnPosition(groups.getFirst(), visuals.get(2)); - } - - @Test - void groupedFragmentVisualsUseBlockTypeCenterForVisualBase() { - List groups = ExplosiveBlockRuntime.groupFragments( - List.of(new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/offset", - 4, - 10, - -2, - new Vector3d(0.25, 0.5, 0.75))), - new Vector3d(4.5, 10.5, -1.5), - 8); - - List visuals = groups.getFirst().visualBlocks(); - - assertEquals(1, visuals.size()); - assertVectorEquals(new Vector3d(4.25, 10.0, -1.25), visuals.getFirst().position()); - assertVectorEquals(new Vector3f(-0.25f, 0.0f, 0.25f), - visuals.getFirst().localPositionOffset()); - } - - @Test - void blockRotationUsesHytaleRotationTupleIndex() { - RotationTuple rotationTuple = RotationTuple.of(Rotation.Ninety, - Rotation.OneEighty, - Rotation.TwoSeventy); - Rotation3f hytaleRotation = new Rotation3f(); - rotationTuple.applyRotationTo(hytaleRotation); - Quaterniond expected = hytaleRotation.getQuaternion(new Quaterniond()); - - assertQuaternionEquals(expected, ExplosiveBlockRuntime.blockRotation(rotationTuple.index())); - } - - @Test - void groupedFragmentVisualsPreserveBlockLocalRotation() { - Quaternionf localRotation = new Quaternionf().rotateXYZ(0.25f, 0.5f, 0.75f); - List groups = ExplosiveBlockRuntime.groupFragments( - List.of(new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", - 0, - 10, - 0, - new Vector3d(0.5, 0.5, 0.5), - localRotation)), - new Vector3d(0.5, 10.5, 0.5), - 8); - - ExplosiveBlockRuntime.FragmentVisual visual = groups.getFirst().visualBlocks().getFirst(); - - assertQuaternionEquals(localRotation, visual.localRotationOffset()); - visual.localRotationOffset().identity(); - assertQuaternionEquals(localRotation, visual.localRotationOffset()); - } - - @Test - void verticalGroupedFragmentsKeepBlockHeightVisualSpacing() { - List groups = ExplosiveBlockRuntime.groupFragments( - List.of( - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 11, 0) - ), - new Vector3d(0.5, 10.5, 0.5), - 8); - - List visuals = groups.getFirst().visualBlocks(); - - assertEquals(2, visuals.size()); - assertVectorEquals(new Vector3d(0.5, 10.0, 0.5), visuals.getFirst().position()); - assertVectorEquals(new Vector3f(0.0f, -0.5f, 0.0f), visuals.get(0).localPositionOffset()); - assertVisualSyncsToSpawnPosition(groups.getFirst(), visuals.get(0)); - assertVectorEquals(new Vector3d(0.5, 11.0, 0.5), visuals.get(1).position()); - assertVectorEquals(new Vector3f(0.0f, 0.5f, 0.0f), visuals.get(1).localPositionOffset()); - assertVisualSyncsToSpawnPosition(groups.getFirst(), visuals.get(1)); - } - - @Test - void verticalGroupedFragmentVisualCentersRotateAroundGroupCenter() { - ExplosiveBlockRuntime.FragmentGroup group = ExplosiveBlockRuntime.groupFragments( - List.of( - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 11, 0) - ), - new Vector3d(0.5, 10.5, 0.5), - 8).getFirst(); - List visuals = group.visualBlocks(); - Quaternionf rotation = new Quaternionf().rotateZ((float) (Math.PI / 2.0)); - - assertVisualCenterAfterSyncEqualsRotatedLocalCenter(group, - visuals.get(0), - rotation, - new Vector3d(1.0, 11.0, 0.5)); - assertVisualCenterAfterSyncEqualsRotatedLocalCenter(group, - visuals.get(1), - rotation, - new Vector3d(0.0, 11.0, 0.5)); - } - - @Test - void disconnectedFragmentsRemainSeparateGroups() { - List groups = ExplosiveBlockRuntime.groupFragments( - List.of( - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/dirt", 4, 10, 0) - ), - new Vector3d(0.5, 10.5, 0.5), - 8); - - assertEquals(2, groups.size()); - assertEquals(new Vector3d(0.5, 10.5, 0.5), groups.get(0).center()); - assertEquals(new Vector3d(4.5, 10.5, 0.5), groups.get(1).center()); - } - - @Test - void largeConnectedComponentsSplitIntoBoundedGroups() { - List fragments = new ArrayList<>(); - for (int x = 0; x < 40; x++) { - fragments.add(new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", x, 10, 0)); - } - - List groups = ExplosiveBlockRuntime.groupFragments( - fragments, - new Vector3d(0.5, 10.5, 0.5), - 8); - - assertTrue(groups.size() > 1); - assertEquals(40, groups.stream().mapToInt(ExplosiveBlockRuntime.FragmentGroup::blockCount).sum()); - assertTrue(groups.stream() - .allMatch(group -> group.blockCount() <= ExplosiveBlockRuntime.MAX_BLOCKS_PER_FRAGMENT_GROUP)); - } - - @Test - void irregularComponentsSplitIntoSolidAabbGroups() { - List groups = ExplosiveBlockRuntime.groupFragments( - List.of( - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 1, 10, 0), - new ExplosiveBlockRuntime.FragmentBlock("Hytale:block/stone", 0, 10, 1) - ), - new Vector3d(0.5, 10.5, 0.5), - 8); - - assertEquals(3, groups.stream().mapToInt(ExplosiveBlockRuntime.FragmentGroup::blockCount).sum()); - assertTrue(groups.size() > 1); - assertTrue(groups.stream() - .allMatch(group -> group.aabbBlockVolume() == group.blockCount())); - } - - @Test - void terrainFragmentsDoNotCarryLandingExplosionState() { - assertNull(ExplosiveBlockRuntime.fragmentLandingExplosionState()); - } - - private static boolean booleanField(ExplosionConfig config, String fieldName) - throws ReflectiveOperationException { - Field field = field(fieldName); - return field.getBoolean(config); - } - - private static int intField(ExplosionConfig config, String fieldName) - throws ReflectiveOperationException { - Field field = field(fieldName); - return field.getInt(config); - } - - private static float floatField(ExplosionConfig config, String fieldName) - throws ReflectiveOperationException { - Field field = field(fieldName); - return field.getFloat(config); - } - - private static Object objectField(ExplosionConfig config, String fieldName) - throws ReflectiveOperationException { - Field field = field(fieldName); - return field.get(config); - } - - private static Field field(String fieldName) throws ReflectiveOperationException { - Field field = ExplosionConfig.class.getDeclaredField(fieldName); - field.setAccessible(true); - return field; - } - - private static void assertVectorEquals(@Nonnull Vector3d expected, @Nonnull Vector3d actual) { - assertEquals(expected.x, actual.x, 0.0001); - assertEquals(expected.y, actual.y, 0.0001); - assertEquals(expected.z, actual.z, 0.0001); - } - - private static void assertVectorEquals(@Nonnull Vector3f expected, @Nonnull Vector3f actual) { - assertEquals(expected.x, actual.x, 0.0001f); - assertEquals(expected.y, actual.y, 0.0001f); - assertEquals(expected.z, actual.z, 0.0001f); - } - - private static void assertQuaternionEquals(@Nonnull Quaterniond expected, - @Nonnull Quaternionf actual) { - assertEquals(expected.x, actual.x, 0.0001f); - assertEquals(expected.y, actual.y, 0.0001f); - assertEquals(expected.z, actual.z, 0.0001f); - assertEquals(expected.w, actual.w, 0.0001f); - } - - private static void assertQuaternionEquals(@Nonnull Quaternionf expected, - @Nonnull Quaternionf actual) { - assertEquals(expected.x, actual.x, 0.0001f); - assertEquals(expected.y, actual.y, 0.0001f); - assertEquals(expected.z, actual.z, 0.0001f); - assertEquals(expected.w, actual.w, 0.0001f); - } - - private static void assertVisualSyncsToSpawnPosition( - ExplosiveBlockRuntime.FragmentGroup group, - ExplosiveBlockRuntime.FragmentVisual visual) { - Vector3d center = group.center(); - Vector3f offset = visual.localPositionOffset(); - assertVectorEquals(visual.position(), new Vector3d( - center.x + offset.x, - center.y - visual.visualOriginOffsetY() + offset.y, - center.z + offset.z)); - } - - private static void assertVisualCenterAfterSyncEqualsRotatedLocalCenter( - ExplosiveBlockRuntime.FragmentGroup group, - ExplosiveBlockRuntime.FragmentVisual visual, - Quaternionf rotation, - Vector3d expectedCenter) { - Vector3f rotatedOffset = rotation.transform(visual.localPositionOffset(), new Vector3f()); - Vector3d syncedBasePosition = new Vector3d(group.center()) - .add(rotatedOffset.x, rotatedOffset.y, rotatedOffset.z) - .sub(0.0, visual.visualOriginOffsetY(), 0.0); - assertVectorEquals(expectedCenter, syncedBasePosition.add(0.0, visual.visualOriginOffsetY(), 0.0)); - } - -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveFuseComponentTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveFuseComponentTest.java deleted file mode 100644 index 7a8cf1d8..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveFuseComponentTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.hytalemodding.impulse.examples.explosive; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.joml.Vector3d; -import org.junit.jupiter.api.Test; - -class ExplosiveFuseComponentTest { - - @Test - void armStartsOneSecondFuseOnlyOnce() { - ExplosiveFuseComponent fuse = new ExplosiveFuseComponent(); - - assertFalse(fuse.isDue(119L)); - - assertTrue(fuse.arm(100L)); - assertFalse(fuse.isDue(119L)); - assertTrue(fuse.isDue(120L)); - - assertFalse(fuse.arm(105L)); - assertTrue(fuse.isDue(120L)); - } - - @Test - void verticalVelocityObservationArmsAfterFirstBounce() { - ExplosiveFuseComponent fuse = new ExplosiveFuseComponent(); - - assertFalse(fuse.observeVerticalVelocity(-0.1f, 10L)); - assertTrue(fuse.observeVerticalVelocity(-0.4f, 11L)); - assertFalse(fuse.isDue(31L)); - - Vector3d bounceCenter = new Vector3d(4.5, 8.5, -2.5); - assertTrue(fuse.observeVerticalVelocity(0.15f, 12L, bounceCenter)); - assertFalse(fuse.isDue(31L)); - assertTrue(fuse.isDue(32L)); - assertEquals(bounceCenter, fuse.explosionCenterOr(new Vector3d())); - assertFalse(fuse.observeVerticalVelocity(0.2f, 13L)); - } - - @Test - void verticalVelocityObservationArmsWhenFallingBodySettlesWithoutBounce() { - ExplosiveFuseComponent fuse = new ExplosiveFuseComponent(); - - assertTrue(fuse.observeVerticalVelocity(-0.4f, 11L)); - - Vector3d settledCenter = new Vector3d(4.5, 8.5, -2.5); - assertTrue(fuse.observeVerticalVelocity(0.0f, 12L, settledCenter)); - assertFalse(fuse.isDue(31L)); - assertTrue(fuse.isDue(32L)); - assertEquals(settledCenter, fuse.explosionCenterOr(new Vector3d())); - } -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java deleted file mode 100644 index 41b6455c..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/testsupport/ExampleControlTestSupport.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.hytalemodding.impulse.examples.testsupport; - -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; -import javax.annotation.Nonnull; - -public final class ExampleControlTestSupport { - - private ExampleControlTestSupport() { - } - - public static void enableControl(@Nonnull ComponentRegistry registry) { - ControlLifecycle.enable(); - ControlTypeRegistry.registerComponentTypes(registry); - } - - public static void clearControl() { - ControlLifecycle.disable(); - ControlTypeRegistry.clearComponentTypes(); - } -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisualsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisualsTest.java deleted file mode 100644 index c9d25497..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExampleBlockEntityVisualsTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.hytalemodding.impulse.examples.utils; - -import static org.junit.jupiter.api.Assertions.assertFalse; - -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; -import com.hypixel.hytale.server.core.modules.physics.component.Velocity; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import org.junit.jupiter.api.Test; - -class ExampleBlockEntityVisualsTest { - - @Test - void impulseOwnedBlockVisualsDoNotKeepHytaleVelocity() { - ComponentRegistry registry = new ComponentRegistry<>(); - ComponentType despawnType = - registry.registerComponent(DespawnComponent.class, DespawnComponent::new); - ComponentType velocityType = - registry.registerComponent(Velocity.class, Velocity::new); - Holder holder = registry.newHolder(); - holder.addComponent(despawnType, new DespawnComponent()); - holder.addComponent(velocityType, new Velocity()); - - ExampleBlockEntityVisuals.stripHytaleRuntimeComponents(holder, despawnType, velocityType); - - assertFalse(holder.getArchetype().contains(despawnType)); - assertFalse(holder.getArchetype().contains(velocityType)); - } -} diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java deleted file mode 100644 index b04d9a6b..00000000 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtilsTest.java +++ /dev/null @@ -1,155 +0,0 @@ -package dev.hytalemodding.impulse.examples.utils; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.server.core.modules.entity.EntityModule; -import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; -import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.examples.testsupport.ExampleControlTestSupport; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3d; -import org.joml.Vector3f; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class ExamplePhysicsUtilsTest { - - private ComponentRegistry registry; - private Object previousEntityModule; - - @BeforeEach - void registerComponentTypes() throws Exception { - previousEntityModule = staticField(EntityModule.class, "instance").get(null); - registry = new ComponentRegistry<>(); - registerEntityModuleTypes(); - ComponentRegistryProxy proxy = - new ComponentRegistryProxy<>(new ArrayList<>(), registry); - PhysicsEntityTypeRegistry.registerComponentTypes(proxy); - ExampleControlTestSupport.enableControl(registry); - } - - @AfterEach - void clearComponentTypes() throws Exception { - ExampleControlTestSupport.clearControl(); - PhysicsEntityTypeRegistry.clearEntityStoreTypes(); - staticField(EntityModule.class, "instance").set(null, previousEntityModule); - registry.shutdown(); - } - - private void registerEntityModuleTypes() throws Exception { - EntityModule entityModule = allocate(EntityModule.class); - setField(entityModule, - "transformComponentType", - registry.registerComponent(TransformComponent.class, - "Transform", - TransformComponent.CODEC)); - setField(entityModule, - "headRotationComponentType", - registry.registerComponent(HeadRotation.class, - "HeadRotation", - HeadRotation.CODEC)); - setField(entityModule, - "modelComponentType", - registry.registerComponent(ModelComponent.class, () -> new ModelComponent(null))); - staticField(EntityModule.class, "instance").set(null, entityModule); - } - - @Test - void physicsBodyCenterConvertsBackToVisualBasePosition() { - Vector3d visualPosition = ExamplePhysicsOriginMath.visualPositionFromBodyCenter(new Vector3d(1.0, 2.5, 3.0), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f)); - - assertEquals(1.0, visualPosition.x, 0.0001); - assertEquals(2.0, visualPosition.y, 0.0001); - assertEquals(3.0, visualPosition.z, 0.0001); - } - - @Test - void ecsAuthoredDynamicBodyHolderAddsControllableMarkerWhenControlIsAvailable() { - Holder holder = registry.newHolder(); - - ExamplePhysicsUtils.addControllableMarkerIfAvailable(holder, PhysicsBodyType.DYNAMIC); - - assertTrue(holder.getArchetype().contains(ImpulseControllableComponent.getComponentType())); - } - - @Test - @SuppressWarnings({"rawtypes", "unchecked"}) - void refAwareAttachmentsKeepDurableUuidAndRuntimeRef() throws Exception { - UUID bodyUuid = UUID.randomUUID(); - Ref bodyRef = new TestPhysicsRef(7); - - BodyAttachmentComponent external = ExamplePhysicsUtils.externalBodyAttachment(bodyUuid, - bodyRef); - BodyAttachmentComponent impulseOwned = ExamplePhysicsUtils.impulseOwnedBodyAttachment( - bodyUuid, - bodyRef, - new Vector3f(1.0f, 2.0f, 3.0f), - new Quaternionf(), - 0.25f); - - assertEquals(bodyUuid, external.getBodyUuid()); - assertSame(bodyRef, bodyRef(external)); - assertEquals(bodyUuid, impulseOwned.getBodyUuid()); - assertSame(bodyRef, bodyRef(impulseOwned)); - } - - @Nonnull - private static T allocate(@Nonnull Class type) throws Exception { - Class unsafeType = Class.forName("sun.misc.Unsafe"); - Field unsafeField = unsafeType.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Object unsafe = unsafeField.get(null); - return type.cast(unsafeType.getMethod("allocateInstance", Class.class) - .invoke(unsafe, type)); - } - - private static void setField(@Nonnull Object target, - @Nonnull String name, - @Nonnull Object value) throws Exception { - Field field = staticField(target.getClass(), name); - field.set(target, value); - } - - @Nonnull - private static Field staticField(@Nonnull Class owner, @Nonnull String name) throws Exception { - Field field = owner.getDeclaredField(name); - field.setAccessible(true); - return field; - } - - private static Object bodyRef(@Nonnull BodyAttachmentComponent attachment) throws Exception { - return BodyAttachmentComponent.class.getMethod("getBodyRef").invoke(attachment); - } - - @SuppressWarnings("rawtypes") - private static final class TestPhysicsRef extends Ref { - - private TestPhysicsRef(int index) { - super(null, index); - } - - @Override - public boolean isValid() { - return true; - } - } -} From cff751d8b5112e8d1f4f6cc2a687e68b1d611ab9 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 13:14:38 +0200 Subject: [PATCH 503/534] fix(core): delete spaces with chunk collision rows Signed-off-by: Blovien --- .../internal/commands/SpaceDeleteSupport.java | 30 +++++-- .../commands/SpaceCommandDeleteTest.java | 85 +++++++++++++++++++ 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java index 592acf2b..317a2221 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java @@ -12,6 +12,7 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.List; import java.util.UUID; @@ -49,13 +50,15 @@ static DeleteResult deleteOnWorldThread( } /* - * Backend-only bodies can be generated by systems such as streaming PhysicsChunk collision. - * Those bodies belong to the space/cache lifecycle and are removed when the space/cache is - * deleted. Registered bodies are gameplay/runtime resources addressed by durable body - * UUID or live PhysicsStore entity ref, so they still require an explicit clean/destroy - * before deleting the space. + * Streaming PhysicsChunk collision is represented as normal PhysicsStore body rows with + * copied snapshots. Those rows belong to the space/cache lifecycle and are removed with + * the space/cache. Non-PhysicsChunk body rows are gameplay/runtime resources addressed by + * durable body UUID or live PhysicsStore entity ref, so they still require explicit + * clean/destroy before deleting the space. */ - int registeredBodies = PhysicsBodies.registrationCount(physicsStore, spaceId); + int registeredBodies = registeredBodiesExcludingChunkCollision(physicsStore, + compatibility, + spaceId); List summaries = PhysicsDiagnostics.spaceSummaries(physicsStore, spaceRef); SpaceCounts counts = countSpaceContents(summaries, spaceId); @@ -73,6 +76,21 @@ static DeleteResult deleteOnWorldThread( return DeleteResult.deleted(rawSpaceId, backendBodies, joints); } + private static int registeredBodiesExcludingChunkCollision( + @Nonnull Store store, + @Nonnull PhysicsSpaceCompatibilityIndexResource compatibility, + @Nonnull SpaceId spaceId) { + int count = 0; + for (PhysicsBodySnapshot snapshot : PhysicsBodies.snapshotFrame(store).bodies()) { + if (!spaceId.equals(compatibility.getSpaceId(snapshot.spaceUuid())) + || PhysicsChunkCollision.isChunkCollisionBody(store, snapshot.bodyUuid())) { + continue; + } + count++; + } + return count; + } + @Nonnull private static SpaceCounts countSpaceContents(@Nonnull List summaries, @Nonnull SpaceId spaceId) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java index 5b278a5c..9c524bce 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java @@ -17,13 +17,17 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; @@ -126,6 +130,38 @@ void deleteCoreRejectsRegisteredBodies() { } } + @Test + void deleteCoreRemovesStreamingChunkCollisionBodies() { + StoreFixture fixture = store("space-delete-streaming-chunks"); + try { + Store store = fixture.store(); + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(96); + Ref spaceRef = addSpace(store, spaceUuid); + bindSpaceId(store, new SpaceId(96), spaceUuid, spaceRef); + UUID chunkBodyUuid = uuid(97); + Ref chunkBodyRef = addChunkCollisionBody(store, + spaceUuid, + spaceRef, + chunkBodyUuid); + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(chunkBodyUuid, spaceUuid)))); + assertEquals(1, PhysicsBodies.registrationCount(store, new SpaceId(96))); + + SpaceDeleteSupport.DeleteResult result = deleteOnWorldThread(store, 96); + + assertEquals(SpaceDeleteSupport.DeleteOutcome.DELETED, result.outcome()); + assertEquals(96, result.rawSpaceId()); + assertFalse(spaceRef.isValid()); + assertFalse(chunkBodyRef.isValid()); + assertEquals(0, PhysicsBodies.registrationCount(store, new SpaceId(96))); + } finally { + fixture.close(); + } + } + private static void bindSpaceId(@Nonnull Store store, @Nonnull SpaceId spaceId, @Nonnull UUID spaceUuid, @@ -170,6 +206,55 @@ private static Ref addSpace(@Nonnull Store store, return ref; } + @Nonnull + private static Ref addChunkCollisionBody(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull UUID bodyUuid) { + Ref ref = store.addEntity(PhysicsEntities.entityHolder(store, + bodyUuid), + AddReason.SPAWN); + assertNotNull(ref); + BodyComponent body = new BodyComponent(spaceUuid); + body.setSpaceRef(spaceRef); + store.putComponent(ref, BodyComponent.getComponentType(), body); + store.putComponent(ref, + ChunkCollisionSourceComponent.getComponentType(), + new ChunkCollisionSourceComponent("test-source", + 0, + 0, + 0, + "test-payload", + PartKind.BOX, + 0)); + store.getResource(PhysicsIdentityIndexResource.getResourceType()) + .putUuid(bodyUuid, ref); + return ref; + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid) { + return PhysicsBodySnapshot.of(bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + @Nonnull private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); From 0f241709d8497bad88789a031bf18951d232d988 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 13:14:45 +0200 Subject: [PATCH 504/534] feat(core): deprecate physics event plugin api Signed-off-by: Blovien --- .../events/PhysicsBodyActivationEvent.java | 3 ++ .../plugin/events/PhysicsContactEvent.java | 3 ++ .../events/PhysicsEventCollectionMode.java | 5 ++ .../core/plugin/events/PhysicsEventFrame.java | 3 ++ .../PhysicsEventFramePublishedEvent.java | 3 ++ .../core/plugin/events/PhysicsFrameEvent.java | 3 ++ .../plugin/events/PhysicsFrameEventKind.java | 6 +++ .../plugin/events/PhysicsJointBreakEvent.java | 3 ++ .../PhysicsSnapshotPublicationEvent.java | 3 ++ .../core/plugin/events/PhysicsStepEvent.java | 3 ++ .../core/plugin/events/package-info.java | 7 +++ .../physicsentity/PhysicsEntityTypes.java | 4 ++ .../core/plugin/physics/PhysicsWorlds.java | 8 +++ .../plugin/settings/PhysicsWorldSettings.java | 12 +++++ .../DeprecatedPhysicsEventPluginApiTest.java | 51 +++++++++++++++++++ 15 files changed, 117 insertions(+) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/package-info.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/events/DeprecatedPhysicsEventPluginApiTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java index 16d5b561..53a7cf45 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsBodyActivationEvent.java @@ -8,7 +8,10 @@ /** * Stable body activation event copied from a backend event batch. + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public record PhysicsBodyActivationEvent(@Nonnull SpaceId spaceId, @Nonnull PhysicsBodyActivationPhase phase, @Nonnull UUID bodyUuid) implements PhysicsFrameEvent { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java index 0e248364..e50a751d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsContactEvent.java @@ -9,7 +9,10 @@ /** * Stable contact event copied from a backend event batch. + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public record PhysicsContactEvent(@Nonnull SpaceId spaceId, @Nonnull PhysicsContactPhase phase, @Nonnull UUID bodyAUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventCollectionMode.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventCollectionMode.java index 9d1061a6..8ff3876f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventCollectionMode.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventCollectionMode.java @@ -5,16 +5,21 @@ /** * Controls which backend physics events are collected during store tick steps. + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public enum PhysicsEventCollectionMode { /** * Steps physics spaces without collecting backend event batches. */ + @Deprecated(since = "0.1.0", forRemoval = false) DISABLED("disabled"), /** * Collects backend contact events and publishes stable physics event frames. */ + @Deprecated(since = "0.1.0", forRemoval = false) CONTACTS("contacts"); @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java index 686fb131..ec6d0d1e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFrame.java @@ -25,7 +25,10 @@ * @param snapshotPublications copied reader-side snapshot-publication events in this frame * @param physicsEvents copied stable physics events in this frame * @param droppedBackendEventCount backend events dropped while bounded buffers were full + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public record PhysicsEventFrame(long frameSequence, long worldEpoch, long latestCapturedSnapshotFrameEpoch, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFramePublishedEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFramePublishedEvent.java index d4d2352c..1cc91eaf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFramePublishedEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsEventFramePublishedEvent.java @@ -6,7 +6,10 @@ /** * Hytale world event published once for each Impulse physics event frame. + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public final class PhysicsEventFramePublishedEvent extends EcsEvent { @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEvent.java index bad60185..82f5c28b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEvent.java @@ -4,7 +4,10 @@ /** * Stable Impulse event copied into a physics event frame. + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public interface PhysicsFrameEvent { @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEventKind.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEventKind.java index 092cc24f..9ec890fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEventKind.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsFrameEventKind.java @@ -2,9 +2,15 @@ /** * Stable Impulse frame-event discriminator. + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public enum PhysicsFrameEventKind { + @Deprecated(since = "0.1.0", forRemoval = false) CONTACT, + @Deprecated(since = "0.1.0", forRemoval = false) BODY_ACTIVATION, + @Deprecated(since = "0.1.0", forRemoval = false) JOINT_BREAK } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java index af3e58ae..4caebe53 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsJointBreakEvent.java @@ -8,7 +8,10 @@ /** * Stable joint-break event copied from a backend event batch. + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public record PhysicsJointBreakEvent(@Nonnull SpaceId spaceId, @Nonnull UUID jointUuid, @Nullable UUID bodyAUuid, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java index bf35e90b..83c7fc82 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsSnapshotPublicationEvent.java @@ -12,7 +12,10 @@ * @param publicationNanoTime monotonic nano time sampled when this publication event was created, * or {@code 0} when unavailable * @param appliedBodyCount number of body snapshots applied to reader-side stores + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public record PhysicsSnapshotPublicationEvent(long snapshotFrameEpoch, long worldEpoch, long stepSequence, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java index d4b48fd3..b2255255 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/PhysicsStepEvent.java @@ -14,7 +14,10 @@ * @param bodyCount number of body snapshots captured in the frame * @param stepNanos profiled step duration, or {@code 0} when profiling was disabled * @param snapshotNanos profiled snapshot capture duration, or {@code 0} when profiling was disabled + * + * @deprecated Physics event plugin API is deprecated without replacement. */ +@Deprecated(since = "0.1.0", forRemoval = false) public record PhysicsStepEvent(long stepSequence, long serverTick, long snapshotFrameEpoch, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/package-info.java new file mode 100644 index 00000000..7fd0efbe --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/events/package-info.java @@ -0,0 +1,7 @@ +/** + * Public physics event values and publication hooks. + * + * @deprecated Physics event plugin API is deprecated without replacement. + */ +@Deprecated(since = "0.1.0", forRemoval = false) +package dev.hytalemodding.impulse.core.plugin.events; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java index e85036b1..e2bfcd39 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/PhysicsEntityTypes.java @@ -40,6 +40,10 @@ public static ComponentType generate return PhysicsEntityTypeRegistry.generatedVisualProxyComponentType(); } + /** + * @deprecated Physics event plugin API is deprecated without replacement. + */ + @Deprecated(since = "0.1.0", forRemoval = false) @Nonnull public static WorldEventType physicsEventFramePublishedEventType() { return PhysicsEntityTypeRegistry.physicsEventFramePublishedEventType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsWorlds.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsWorlds.java index 48f41742..4421de48 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsWorlds.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsWorlds.java @@ -24,6 +24,10 @@ public final class PhysicsWorlds { private PhysicsWorlds() { } + /** + * @deprecated Physics event plugin API is deprecated without replacement. + */ + @Deprecated(since = "0.1.0", forRemoval = false) @Nonnull public static PhysicsEventFrame latestEventFrame(@Nonnull Store store) { Store checkedStore = requireWorldThread(store, @@ -31,6 +35,10 @@ public static PhysicsEventFrame latestEventFrame(@Nonnull Store st return checkedStore.getResource(PhysicsEventResource.getResourceType()).getLatestFrame(); } + /** + * @deprecated Physics event plugin API is deprecated without replacement. + */ + @Deprecated(since = "0.1.0", forRemoval = false) @Nonnull public static CompletionStage latestEventFrameAsync( @Nonnull World world) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java index 85901097..27c86d08 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/settings/PhysicsWorldSettings.java @@ -20,6 +20,10 @@ public class PhysicsWorldSettings { @Nonnull public static final PhysicsStepSchedulingMode DEFAULT_STEP_SCHEDULING_MODE = PhysicsStepSchedulingMode.DROP_PENDING_DT; + /** + * @deprecated Physics event plugin API is deprecated without replacement. + */ + @Deprecated(since = "0.1.0", forRemoval = false) @Nonnull public static final PhysicsEventCollectionMode DEFAULT_EVENT_COLLECTION_MODE = PhysicsEventCollectionMode.DISABLED; @@ -83,11 +87,19 @@ public void setStepSchedulingMode(@Nonnull PhysicsStepSchedulingMode stepSchedul "stepSchedulingMode"); } + /** + * @deprecated Physics event plugin API is deprecated without replacement. + */ + @Deprecated(since = "0.1.0", forRemoval = false) @Nonnull public PhysicsEventCollectionMode getEventCollectionMode() { return eventCollectionMode; } + /** + * @deprecated Physics event plugin API is deprecated without replacement. + */ + @Deprecated(since = "0.1.0", forRemoval = false) public void setEventCollectionMode(@Nonnull PhysicsEventCollectionMode eventCollectionMode) { this.eventCollectionMode = Objects.requireNonNull(eventCollectionMode, "eventCollectionMode"); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/events/DeprecatedPhysicsEventPluginApiTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/events/DeprecatedPhysicsEventPluginApiTest.java new file mode 100644 index 00000000..27b39d49 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/events/DeprecatedPhysicsEventPluginApiTest.java @@ -0,0 +1,51 @@ +package dev.hytalemodding.impulse.core.plugin.events; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; +import java.lang.reflect.AnnotatedElement; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") +class DeprecatedPhysicsEventPluginApiTest { + + @Test + void marksPhysicsEventValueApiDeprecated() throws NoSuchFieldException { + assertDeprecated(PhysicsEventFrame.class.getPackage()); + assertDeprecated(PhysicsBodyActivationEvent.class); + assertDeprecated(PhysicsContactEvent.class); + assertDeprecated(PhysicsEventCollectionMode.class); + assertDeprecated(PhysicsEventCollectionMode.DISABLED.getClass().getField("DISABLED")); + assertDeprecated(PhysicsEventCollectionMode.CONTACTS.getClass().getField("CONTACTS")); + assertDeprecated(PhysicsEventFrame.class); + assertDeprecated(PhysicsEventFramePublishedEvent.class); + assertDeprecated(PhysicsFrameEvent.class); + assertDeprecated(PhysicsFrameEventKind.class); + assertDeprecated(PhysicsFrameEventKind.CONTACT.getClass().getField("CONTACT")); + assertDeprecated(PhysicsFrameEventKind.BODY_ACTIVATION.getClass().getField("BODY_ACTIVATION")); + assertDeprecated(PhysicsFrameEventKind.JOINT_BREAK.getClass().getField("JOINT_BREAK")); + assertDeprecated(PhysicsJointBreakEvent.class); + assertDeprecated(PhysicsSnapshotPublicationEvent.class); + assertDeprecated(PhysicsStepEvent.class); + } + + @Test + void marksPhysicsEventAccessorsDeprecated() throws NoSuchMethodException, NoSuchFieldException { + assertDeprecated(PhysicsWorlds.class.getMethod("latestEventFrame", Store.class)); + assertDeprecated(PhysicsWorlds.class.getMethod("latestEventFrameAsync", World.class)); + assertDeprecated(PhysicsWorldSettings.class.getField("DEFAULT_EVENT_COLLECTION_MODE")); + assertDeprecated(PhysicsWorldSettings.class.getMethod("getEventCollectionMode")); + assertDeprecated(PhysicsWorldSettings.class.getMethod("setEventCollectionMode", + PhysicsEventCollectionMode.class)); + assertDeprecated(PhysicsEntityTypes.class.getMethod("physicsEventFramePublishedEventType")); + } + + private static void assertDeprecated(AnnotatedElement element) { + assertNotNull(element.getAnnotation(Deprecated.class), () -> element + " is not deprecated"); + } +} From bbf864d3a80fe7be95f7e68ea0e98ddbc50a10f6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 13:14:51 +0200 Subject: [PATCH 505/534] refactor(examples): localize physics store demo helpers Signed-off-by: Blovien --- .../examples/commands/ForcesCommand.java | 167 ++++---- .../examples/commands/JointsCommand.java | 171 ++++---- .../examples/commands/MaterialsCommand.java | 20 +- .../examples/commands/ShapesCommand.java | 22 +- .../stress}/BlockBodyBatchBuilder.java | 41 +- .../stress/StressBenchmarkCommand.java | 6 +- .../commands/stress/StressBodiesCommand.java | 6 +- .../commands/stress/StressBodyBatches.java | 221 +++++++++++ .../stress/StressRawBodiesCommand.java | 5 +- .../examples/utils/BlockBodyBatchResult.java | 34 -- .../utils/ExamplePhysicsOriginMath.java | 22 -- .../examples/utils/ExamplePhysicsUtils.java | 372 +----------------- 12 files changed, 448 insertions(+), 639 deletions(-) rename impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/{utils => commands/stress}/BlockBodyBatchBuilder.java (72%) create mode 100644 impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java delete mode 100644 impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java delete mode 100644 impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsOriginMath.java diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index 78757810..bb86b11c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.examples.commands; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -15,6 +16,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; @@ -22,7 +26,6 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.joml.Vector3d; import org.joml.Vector3f; @@ -51,19 +54,29 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw( + "Cannot spawn force demo because the target space is not bound in PhysicsStore.")); + return CompletableFuture.completedFuture(null); + } Vector3d origin = new Vector3d(playerPos).add(-2.0, 4.0, 4.0); Vector3d centralPosition = new Vector3d(origin); Vector3d offCenterPosition = new Vector3d(origin).add(2.0, 0.0, 0.0); Vector3d torquePosition = new Vector3d(origin).add(4.0, 0.0, 0.0); Vector3d forcePosition = new Vector3d(origin).add(6.0, 0.0, 0.0); - ForceDemoBodies bodies = tryCreatePhysicsStoreDemo(world, - spaceId, - centralPosition, - offCenterPosition, - torquePosition, - forcePosition); - if (bodies == null) { + ForceDemoBodies bodies; + try { + bodies = createPhysicsStoreDemo(physicsStore, + spaceRef, + spaceId, + centralPosition, + offCenterPosition, + torquePosition, + forcePosition); + } catch (IllegalStateException exception) { ctx.sender().sendMessage(Message.raw( "Cannot spawn force demo because the target space is not bound in PhysicsStore.")); return CompletableFuture.completedFuture(null); @@ -86,93 +99,81 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - @Nullable - private static ForceDemoBodies tryCreatePhysicsStoreDemo(@Nonnull World world, + @Nonnull + private static ForceDemoBodies createPhysicsStoreDemo(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d centralPosition, @Nonnull Vector3d offCenterPosition, @Nonnull Vector3d torquePosition, @Nonnull Vector3d forcePosition) { - Ref spaceRef; - try { - spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); - } catch (IllegalStateException exception) { - return null; - } - if (spaceRef == null) { - return null; - } - - try { - CreatedBlockBody central = spawnBox(world, - spaceRef, - spaceId, - centralPosition, - BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, - 4.0f, - 2.0f, - 0.0f, - false, - 0.0f, - 0.0f, - 0.0f)); - CreatedBlockBody offCenter = spawnBox(world, - spaceRef, - spaceId, - offCenterPosition, - BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, - 3.5f, - 0.0f, - 0.0f, - true, - 0.0f, - 0.5f, - 0.5f)); - CreatedBlockBody torque = spawnBox(world, - spaceRef, - spaceId, - torquePosition, - BodyCommandComponent.vector(BodyCommandComponent.Kind.TORQUE_IMPULSE, - 0.0f, - 0.0f, - 8.0f, - false, - 0.0f, - 0.0f, - 0.0f)); - CreatedBlockBody force = spawnBox(world, - spaceRef, - spaceId, - forcePosition, - BodyCommandComponent.vector(BodyCommandComponent.Kind.FORCE, - 30.0f, - 0.0f, - 0.0f, - false, - 0.0f, - 0.0f, - 0.0f)); - return new ForceDemoBodies(central, offCenter, torque, force); - } catch (IllegalStateException exception) { - return null; - } + CreatedBlockBody central = spawnBox(physicsStore, + spaceRef, + spaceId, + centralPosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, + 4.0f, + 2.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f)); + CreatedBlockBody offCenter = spawnBox(physicsStore, + spaceRef, + spaceId, + offCenterPosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.IMPULSE, + 3.5f, + 0.0f, + 0.0f, + true, + 0.0f, + 0.5f, + 0.5f)); + CreatedBlockBody torque = spawnBox(physicsStore, + spaceRef, + spaceId, + torquePosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.TORQUE_IMPULSE, + 0.0f, + 0.0f, + 8.0f, + false, + 0.0f, + 0.0f, + 0.0f)); + CreatedBlockBody force = spawnBox(physicsStore, + spaceRef, + spaceId, + forcePosition, + BodyCommandComponent.vector(BodyCommandComponent.Kind.FORCE, + 30.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f, + 0.0f)); + return new ForceDemoBodies(central, offCenter, torque, force); } - private static CreatedBlockBody spawnBox(@Nonnull World world, + private static CreatedBlockBody spawnBox(@Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, @Nonnull BodyCommandComponent command) { UUID bodyUuid = UUID.randomUUID(); - var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyEntity(spaceRef, - bodyUuid, - ExamplePhysicsUtils.toVector3f(position), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - RigidBodySpawnSettings.material(0.5f, 0.25f), - null), - command); + var bodyHolder = PhysicsBodyEntities.dynamicBodyHolder(spaceRef, + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), + PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), + 1.0f, + RigidBodySpawnSettings.material(0.5f, 0.25f), + null); + Ref bodyRef = physicsStore.addEntity(bodyHolder, AddReason.SPAWN); + assert bodyRef != null; + PhysicsBodies.appendCommand(physicsStore, bodyRef, command); return new CreatedBlockBody(bodyUuid, bodyRef, spaceId, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index b32eb97a..ca50594b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.examples.commands; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -13,7 +14,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.JointType; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; @@ -57,12 +61,22 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + Ref spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); + if (spaceRef == null) { + ctx.sender().sendMessage(Message.raw( + "Cannot spawn joint demo because the target space is not bound in PhysicsStore.")); + return CompletableFuture.completedFuture(null); + } Vector3d origin = new Vector3d(playerPos).add(-5.0, 5.0, 5.0); - List createdBodies = tryCreatePhysicsStoreDemo(world, - spaceId, - new Vector3d(origin)); - if (createdBodies == null) { + List createdBodies; + try { + createdBodies = createPhysicsStoreDemo(physicsStore, + spaceRef, + spaceId, + new Vector3d(origin)); + } catch (IllegalStateException exception) { ctx.sender().sendMessage(Message.raw( "Cannot spawn joint demo because the target space is not bound in PhysicsStore.")); return CompletableFuture.completedFuture(null); @@ -76,83 +90,90 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } - @Nullable - private static List tryCreatePhysicsStoreDemo(@Nonnull World world, + @Nonnull + private static List createPhysicsStoreDemo(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - Ref spaceRef; - try { - spaceRef = ExamplePhysicsUtils.resolveSpaceRef(world, spaceId); - } catch (IllegalStateException exception) { - return null; - } - if (spaceRef == null) { - return null; - } - List createdBodies = new ArrayList<>(10); - try { - createFixed(createdBodies, world, spaceRef, spaceId, new Vector3d(origin)); - createPoint(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(2.5, 0.0, 0.0)); - createHinge(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(5.0, 0.0, 0.0)); - createSlider(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(7.5, 0.0, 0.0)); - createSpring(createdBodies, world, spaceRef, spaceId, new Vector3d(origin).add(10.0, 0.0, 0.0)); - } catch (IllegalStateException exception) { - return null; - } + createFixed(createdBodies, physicsStore, spaceRef, spaceId, new Vector3d(origin)); + createPoint(createdBodies, + physicsStore, + spaceRef, + spaceId, + new Vector3d(origin).add(2.5, 0.0, 0.0)); + createHinge(createdBodies, + physicsStore, + spaceRef, + spaceId, + new Vector3d(origin).add(5.0, 0.0, 0.0)); + createSlider(createdBodies, + physicsStore, + spaceRef, + spaceId, + new Vector3d(origin).add(7.5, 0.0, 0.0)); + createSpring(createdBodies, + physicsStore, + spaceRef, + spaceId, + new Vector3d(origin).add(10.0, 0.0, 0.0)); return createdBodies; } private static void createFixed(@Nonnull List createdBodies, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); - CreatedBlockBody child = spawnBox(createdBodies, world, spaceRef, spaceId, + CreatedBlockBody anchor = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody child = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); - ExamplePhysicsUtils.addJoint(world, + JointComponent joint = joint(spaceRef, + anchor, + child, + JointType.FIXED, + new Vector3f(0.0f, -HALF_SIZE, 0.0f), + new Vector3f(0.0f, HALF_SIZE, 0.0f), + new Vector3f()); + Ref jointRef = physicsStore.addEntity(PhysicsEntities.jointHolder(physicsStore, UUID.randomUUID(), - joint(spaceRef, - anchor, - child, - JointType.FIXED, - new Vector3f(0.0f, -HALF_SIZE, 0.0f), - new Vector3f(0.0f, HALF_SIZE, 0.0f), - new Vector3f())); + joint), AddReason.SPAWN); + assert jointRef != null; } private static void createPoint(@Nonnull List createdBodies, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody anchor = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, origin, 0.0f); CreatedBlockBody bob = spawnBox(createdBodies, - world, + physicsStore, spaceRef, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f, new Vector3f(1.5f, 0.0f, 0.0f)); - ExamplePhysicsUtils.addJoint(world, + JointComponent joint = joint(spaceRef, + anchor, + bob, + JointType.POINT, + new Vector3f(0.0f, -HALF_SIZE, 0.0f), + new Vector3f(0.0f, HALF_SIZE, 0.0f), + new Vector3f()); + Ref jointRef = physicsStore.addEntity(PhysicsEntities.jointHolder(physicsStore, UUID.randomUUID(), - joint(spaceRef, - anchor, - bob, - JointType.POINT, - new Vector3f(0.0f, -HALF_SIZE, 0.0f), - new Vector3f(0.0f, HALF_SIZE, 0.0f), - new Vector3f())); + joint), AddReason.SPAWN); + assert jointRef != null; } private static void createHinge(@Nonnull List createdBodies, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); - CreatedBlockBody arm = spawnBox(createdBodies, world, spaceRef, spaceId, + CreatedBlockBody anchor = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody arm = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, new Vector3d(origin).add(0.0, -TOUCHING_SPACING, 0.0), 1.0f); JointComponent joint = joint(spaceRef, anchor, @@ -166,16 +187,19 @@ private static void createHinge(@Nonnull List createdBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.5f); joint.setMotorMaxForce(3.0f); - ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint); + Ref jointRef = physicsStore.addEntity(PhysicsEntities.jointHolder(physicsStore, + UUID.randomUUID(), + joint), AddReason.SPAWN); + assert jointRef != null; } private static void createSlider(@Nonnull List createdBodies, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); - CreatedBlockBody block = spawnBox(createdBodies, world, spaceRef, spaceId, + CreatedBlockBody anchor = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody block = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, new Vector3d(origin).add(TOUCHING_SPACING, 0.0, 0.0), 1.0f); JointComponent joint = joint(spaceRef, anchor, @@ -189,17 +213,20 @@ private static void createSlider(@Nonnull List createdBodies, joint.setMotorEnabled(true); joint.setMotorTargetVelocity(1.0f); joint.setMotorMaxForce(4.0f); - ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint); + Ref jointRef = physicsStore.addEntity(PhysicsEntities.jointHolder(physicsStore, + UUID.randomUUID(), + joint), AddReason.SPAWN); + assert jointRef != null; } private static void createSpring(@Nonnull List createdBodies, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d origin) { - CreatedBlockBody anchor = spawnBox(createdBodies, world, spaceRef, spaceId, origin, 0.0f); + CreatedBlockBody anchor = spawnBox(createdBodies, physicsStore, spaceRef, spaceId, origin, 0.0f); CreatedBlockBody bob = spawnBox(createdBodies, - world, + physicsStore, spaceRef, spaceId, new Vector3d(origin).add(0.0, -(TOUCHING_SPACING + SPRING_REST_LENGTH), 0.0), @@ -215,34 +242,38 @@ private static void createSpring(@Nonnull List createdBodies, joint.setSpringRestLength(SPRING_REST_LENGTH); joint.setSpringStiffness(20.0f); joint.setSpringDamping(2.0f); - ExamplePhysicsUtils.addJoint(world, UUID.randomUUID(), joint); + Ref jointRef = physicsStore.addEntity(PhysicsEntities.jointHolder(physicsStore, + UUID.randomUUID(), + joint), AddReason.SPAWN); + assert jointRef != null; } private static CreatedBlockBody spawnBox(@Nonnull List createdBodies, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass) { - return spawnBox(createdBodies, world, spaceRef, spaceId, position, mass, null); + return spawnBox(createdBodies, physicsStore, spaceRef, spaceId, position, mass, null); } private static CreatedBlockBody spawnBox(@Nonnull List createdBodies, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull Ref spaceRef, @Nonnull SpaceId spaceId, @Nonnull Vector3d position, float mass, @Nullable Vector3f linearVelocity) { UUID bodyUuid = UUID.randomUUID(); - var bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, - ExamplePhysicsUtils.bodyEntity(spaceRef, - bodyUuid, - ExamplePhysicsUtils.toVector3f(position), - PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), - mass, - RigidBodySpawnSettings.material(0.6f, 0.15f), - linearVelocity)); + var bodyHolder = PhysicsBodyEntities.dynamicBodyHolder(spaceRef, + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), + PhysicsShapeSpec.box(HALF_SIZE, HALF_SIZE, HALF_SIZE), + mass, + RigidBodySpawnSettings.material(0.6f, 0.15f), + linearVelocity); + Ref bodyRef = physicsStore.addEntity(bodyHolder, AddReason.SPAWN); + assert bodyRef != null; CreatedBlockBody created = new CreatedBlockBody(bodyUuid, bodyRef, spaceId, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index ee043f4e..657b71ac 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -13,6 +13,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -50,28 +52,31 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + PhysicsThreading.requireWorldThread(physicsStore, + "spawn material example PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-3.0, 5.0, 4.0); spawnSphere(store, - world, + physicsStore, time, space.spaceRef(), new Vector3d(origin), 0.05f, 0.9f, 3.0f); spawnSphere(store, - world, + physicsStore, time, space.spaceRef(), new Vector3d(origin).add(2.0, 0.0, 0.0), 0.95f, 0.9f, 3.0f); spawnSphere(store, - world, + physicsStore, time, space.spaceRef(), new Vector3d(origin).add(4.0, 0.0, 0.0), 0.5f, 0.0f, 2.0f); spawnSphere(store, - world, + physicsStore, time, space.spaceRef(), new Vector3d(origin).add(6.0, 0.0, 0.0), @@ -83,7 +88,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawnSphere(@Nonnull Store store, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull TimeResource time, @Nonnull Ref spaceRef, @Nonnull Vector3d position, @@ -91,14 +96,15 @@ private static void spawnSphere(@Nonnull Store store, float friction, float speed) { UUID bodyUuid = UUID.randomUUID(); - var bodyHolder = ExamplePhysicsUtils.bodyEntity(spaceRef, + var bodyHolder = PhysicsBodyEntities.dynamicBodyHolder(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), PhysicsShapeSpec.sphere(0.5f), 1.0f, RigidBodySpawnSettings.material(friction, restitution), new Vector3f(speed, 0.0f, 0.0f)); - Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, bodyHolder); + Ref bodyRef = physicsStore.addEntity(bodyHolder, AddReason.SPAWN); + assert bodyRef != null; store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index ad1197cd..0c19b8fc 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -14,6 +14,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.UUID; @@ -51,38 +53,41 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, return CompletableFuture.completedFuture(null); } TimeResource time = store.getResource(TimeResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + PhysicsThreading.requireWorldThread(physicsStore, + "spawn shape example PhysicsStore body entities"); Vector3d origin = new Vector3d(playerPos).add(-4.0, 3.0, 3.0); spawn(store, - world, + physicsStore, time, space.spaceRef(), ShapeType.BOX, PhysicsAxis.Y, origin, 0); spawn(store, - world, + physicsStore, time, space.spaceRef(), ShapeType.SPHERE, PhysicsAxis.Y, origin, 2); spawn(store, - world, + physicsStore, time, space.spaceRef(), ShapeType.CAPSULE, PhysicsAxis.Y, origin, 4); spawn(store, - world, + physicsStore, time, space.spaceRef(), ShapeType.CYLINDER, PhysicsAxis.Y, origin, 6); spawn(store, - world, + physicsStore, time, space.spaceRef(), ShapeType.CONE, @@ -94,7 +99,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } private static void spawn(@Nonnull Store store, - @Nonnull World world, + @Nonnull Store physicsStore, @Nonnull TimeResource time, @Nonnull Ref spaceRef, @Nonnull ShapeType type, @@ -103,14 +108,15 @@ private static void spawn(@Nonnull Store store, int xOffset) { Vector3d position = new Vector3d(origin).add(xOffset, 0.0, 0.0); UUID bodyUuid = UUID.randomUUID(); - var bodyHolder = ExamplePhysicsUtils.bodyEntity(spaceRef, + var bodyHolder = PhysicsBodyEntities.dynamicBodyHolder(spaceRef, bodyUuid, ExamplePhysicsUtils.toVector3f(position), shape(type, axis), 1.0f, RigidBodySpawnSettings.material(0.7f, 0.35f), null); - Ref bodyRef = ExamplePhysicsUtils.addPhysicsStoreBody(world, bodyHolder); + Ref bodyRef = physicsStore.addEntity(bodyHolder, AddReason.SPAWN); + assert bodyRef != null; store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder( time, bodyRef, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/BlockBodyBatchBuilder.java similarity index 72% rename from impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java rename to impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/BlockBodyBatchBuilder.java index 40c2cb9b..2ca49b19 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchBuilder.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/BlockBodyBatchBuilder.java @@ -1,11 +1,11 @@ -package dev.hytalemodding.impulse.examples.utils; +package dev.hytalemodding.impulse.examples.commands.stress; -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Objects; import java.util.UUID; +import javax.annotation.Nonnull; -public final class BlockBodyBatchBuilder { +final class BlockBodyBatchBuilder { private static final int POSITION_STRIDE = 3; @@ -24,7 +24,7 @@ public final class BlockBodyBatchBuilder { } @Nonnull - public BlockBodyBatchBuilder addBody(float positionX, + BlockBodyBatchBuilder addBody(float positionX, float positionY, float positionZ) { return addBody(bodyUuidRunId, @@ -35,38 +35,7 @@ public BlockBodyBatchBuilder addBody(float positionX, } @Nonnull - public BlockBodyBatchBuilder addBody(@Nonnull UUID bodyUuid, - float positionX, - float positionY, - float positionZ) { - Objects.requireNonNull(bodyUuid, "bodyUuid"); - return addBody(bodyUuid.getMostSignificantBits(), - bodyUuid.getLeastSignificantBits(), - positionX, - positionY, - positionZ); - } - - @Nonnull - public UUID body(float positionX, - float positionY, - float positionZ) { - long leastSignificantBits = size + 1L; - addBody(bodyUuidRunId, leastSignificantBits, positionX, positionY, positionZ); - return new UUID(bodyUuidRunId, leastSignificantBits); - } - - @Nonnull - public UUID body(@Nonnull UUID bodyUuid, - float positionX, - float positionY, - float positionZ) { - addBody(bodyUuid, positionX, positionY, positionZ); - return bodyUuid; - } - - @Nonnull - public BlockBodyBatchBuilder addBody(long bodyUuidMostSignificantBits, + BlockBodyBatchBuilder addBody(long bodyUuidMostSignificantBits, long bodyUuidLeastSignificantBits, float positionX, float positionY, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index ad40fc17..6e94e818 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -165,8 +165,8 @@ private static BenchmarkSpawnTiming spawnRaw(@Nonnull World world, int count) { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - ExamplePhysicsUtils.BodyEntityBatchTiming timing = - ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, + StressBodyBatches.BodyEntityBatchTiming timing = + StressBodyBatches.addDynamicBodyBatchMeasured(world, spaceRef, spaceId, count, @@ -197,7 +197,7 @@ private static BenchmarkSpawnTiming spawnEntities(@Nonnull Store st TimeResource time = store.getResource(TimeResource.getResourceType()); PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - ExamplePhysicsUtils.BlockBodyBatchTiming timing = ExamplePhysicsUtils.spawnBlockBodiesMeasured(store, + StressBodyBatches.BlockBodyBatchTiming timing = StressBodyBatches.spawnBlockBodiesMeasured(store, time, serverTick, spaceRef, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 59cdd9f8..45e2049b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -183,7 +183,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, if (mode == StressMode.ENTITY) { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); - ExamplePhysicsUtils.BlockBodyBatchTiming batchTiming = ExamplePhysicsUtils.spawnBlockBodiesMeasured(store, + StressBodyBatches.BlockBodyBatchTiming batchTiming = StressBodyBatches.spawnBlockBodiesMeasured(store, time, serverTick, spaceRef, @@ -206,8 +206,8 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } else { PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = detachedSpawnSettings(collisionPolicy); - ExamplePhysicsUtils.BodyEntityBatchTiming batchTiming = - ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, + StressBodyBatches.BodyEntityBatchTiming batchTiming = + StressBodyBatches.addDynamicBodyBatchMeasured(world, spaceRef, spaceId, count, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java new file mode 100644 index 00000000..dd0ce8ae --- /dev/null +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java @@ -0,0 +1,221 @@ +package dev.hytalemodding.impulse.examples.commands.stress; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3d; +import org.joml.Vector3f; + +final class StressBodyBatches { + + private StressBodyBatches() { + } + + @Nonnull + static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull Consumer builder) { + DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(spaceRef, + spaceId, + expectedBodies, + shape, + mass, + settings, + builder); + if (plan.isEmpty()) { + return new BodyEntityBatchTiming(0, plan.setupWallNanos(), 0L); + } + + long applyStartNanos = System.nanoTime(); + addPhysicsStoreBodies(PhysicsThreading.store(world), plan.bodies()); + long physicsStoreApplyNanos = System.nanoTime() - applyStartNanos; + return new BodyEntityBatchTiming(plan.count(), + plan.setupWallNanos(), + physicsStoreApplyNanos); + } + + @Nonnull + static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, + @Nonnull TimeResource time, + long serverTick, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nullable String blockType, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull Consumer builder) { + Objects.requireNonNull(spaceRef, "spaceRef"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(shape, "shape"); + Objects.requireNonNull(settings, "settings"); + + PhysicsThreading.requireWorldThread(spaceRef.getStore(), + "spawn stress PhysicsStore block body entities"); + if (!spaceRef.isValid()) { + throw new IllegalStateException("Cannot spawn block body batch because the target " + + "PhysicsStore space entity is no longer valid: " + spaceId.value()); + } + + BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); + Objects.requireNonNull(builder, "builder").accept(batch); + batch.seal(); + if (batch.isEmpty()) { + return new BlockBodyBatchTiming(0, 0L, 0L); + } + + List> bodyHolders = new ArrayList<>(batch.size()); + for (int i = 0; i < batch.size(); i++) { + UUID bodyUuid = batch.bodyUuid(i); + bodyHolders.add(PhysicsBodyEntities.dynamicBodyHolder(spaceRef, + bodyUuid, + new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), + shape, + mass, + settings, + null)); + } + + long physicsStoreApplyStartNanos = System.nanoTime(); + addPhysicsStoreBodies(spaceRef.getStore(), bodyHolders); + long physicsStoreApplyNanos = System.nanoTime() - physicsStoreApplyStartNanos; + + long visualAttachStartNanos = System.nanoTime(); + for (int i = 0; i < batch.size(); i++) { + UUID bodyUuid = batch.bodyUuid(i); + store.addEntity(ExamplePhysicsUtils.attachedPhysicsBlockEntityHolder(time, + null, + bodyUuid, + blockType, + new Vector3d(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), + new Vector3f(), + new Quaternionf(), + Float.NaN, + mass > 0.0f), + AddReason.SPAWN); + } + long visualAttachNanos = System.nanoTime() - visualAttachStartNanos; + return new BlockBodyBatchTiming(batch.size(), physicsStoreApplyNanos, visualAttachNanos); + } + + @Nonnull + private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + int expectedBodies, + @Nonnull PhysicsShapeSpec shape, + float mass, + @Nonnull RigidBodySpawnSettings settings, + @Nonnull Consumer builder) { + Objects.requireNonNull(spaceRef, "spaceRef"); + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(shape, "shape"); + Objects.requireNonNull(settings, "settings"); + + PhysicsThreading.requireWorldThread(spaceRef.getStore(), + "add stress dynamic PhysicsStore body entities"); + if (!spaceRef.isValid()) { + throw new IllegalStateException("Cannot add dynamic body entities because the target " + + "PhysicsStore space entity is no longer valid: " + spaceId.value()); + } + + long setupStartNanos = System.nanoTime(); + BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); + Objects.requireNonNull(builder, "builder").accept(batch); + batch.seal(); + if (batch.isEmpty()) { + return new DynamicBodyBatchPlan(List.of(), 0L); + } + + List> bodies = new ArrayList<>(batch.size()); + for (int i = 0; i < batch.size(); i++) { + UUID bodyUuid = batch.bodyUuid(i); + bodies.add(PhysicsBodyEntities.dynamicBodyHolder(spaceRef, + bodyUuid, + new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), + shape, + mass, + settings, + null)); + } + return new DynamicBodyBatchPlan(bodies, System.nanoTime() - setupStartNanos); + } + + private static void addPhysicsStoreBodies(@Nonnull Store store, + @Nonnull Iterable> bodyHolders) { + Objects.requireNonNull(bodyHolders, "bodyHolders"); + PhysicsThreading.requireWorldThread(store, "add stress PhysicsStore body entities"); + List> holders = new ArrayList<>(); + for (Holder holder : bodyHolders) { + holders.add(Objects.requireNonNull(holder, "holder")); + } + if (!holders.isEmpty()) { + @SuppressWarnings("unchecked") + Holder[] holderArray = holders.toArray(Holder[]::new); + store.addEntities(holderArray, AddReason.SPAWN); + } + } + + record BlockBodyBatchTiming(int count, + long physicsStoreApplyNanos, + long visualAttachNanos) { + + BlockBodyBatchTiming { + count = Math.max(0, count); + physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); + visualAttachNanos = Math.max(0L, visualAttachNanos); + } + } + + record BodyEntityBatchTiming(int count, + long setupWallNanos, + long physicsStoreApplyNanos) { + + BodyEntityBatchTiming { + count = Math.max(0, count); + setupWallNanos = Math.max(0L, setupWallNanos); + physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); + } + } + + private record DynamicBodyBatchPlan(@Nonnull List> bodies, + long setupWallNanos) { + + DynamicBodyBatchPlan { + bodies = List.copyOf(Objects.requireNonNull(bodies, "bodies")); + setupWallNanos = Math.max(0L, setupWallNanos); + } + + private int count() { + return bodies.size(); + } + + private boolean isEmpty() { + return bodies.isEmpty(); + } + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index 4cf3f8ab..ce060d77 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; -import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.BodyEntityBatchTiming; import java.util.Locale; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -74,7 +73,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); RigidBodySpawnSettings spawnSettings = RigidBodySpawnSettings.material(0.65f, 0.15f); long totalStartNanos = System.nanoTime(); - BodyEntityBatchTiming timing = ExamplePhysicsUtils.addDynamicBodyBatchMeasured(world, + StressBodyBatches.BodyEntityBatchTiming timing = StressBodyBatches.addDynamicBodyBatchMeasured(world, spaceRef, spaceId, count, @@ -102,7 +101,7 @@ private static String millis(long nanos) { } @Nonnull - private static String successMessage(@Nonnull BodyEntityBatchTiming timing, + private static String successMessage(@Nonnull StressBodyBatches.BodyEntityBatchTiming timing, long totalWallNanos) { return "PhysicsStore added body rows for " + timing.count() + " physics-only bodies: setupWallMs=" + millis(timing.setupWallNanos()) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java deleted file mode 100644 index 7d318d5f..00000000 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/BlockBodyBatchResult.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.hytalemodding.impulse.examples.utils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -record BlockBodyBatchResult(@Nullable ExamplePhysicsUtils.SpawnedBlockBody[] bodies, - int count, - long physicsStoreApplyNanos, - long visualAttachNanos) { - - BlockBodyBatchResult { - if (bodies != null && bodies.length != count) { - throw new IllegalArgumentException("Collected body count does not match batch count"); - } - count = Math.max(0, count); - physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); - visualAttachNanos = Math.max(0L, visualAttachNanos); - } - - @Nonnull - ExamplePhysicsUtils.SpawnedBlockBody[] collectedBodies() { - if (bodies == null) { - throw new IllegalStateException("Block body batch did not collect body results"); - } - return bodies; - } - - @Nonnull - ExamplePhysicsUtils.BlockBodyBatchTiming timing() { - return new ExamplePhysicsUtils.BlockBodyBatchTiming(count, - physicsStoreApplyNanos, - visualAttachNanos); - } -} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsOriginMath.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsOriginMath.java deleted file mode 100644 index f3ee08df..00000000 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsOriginMath.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.examples.utils; - -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.joml.Vector3d; - -final class ExamplePhysicsOriginMath { - - private ExamplePhysicsOriginMath() { - } - - @Nonnull - static Vector3d visualPositionFromBodyCenter(@Nonnull Vector3d bodyCenter, - @Nonnull PhysicsShapeSpec shape) { - Objects.requireNonNull(bodyCenter, "bodyCenter"); - Objects.requireNonNull(shape, "shape"); - return new Vector3d(bodyCenter.x, - bodyCenter.y - shape.centerOfMassOffsetY(), - bodyCenter.z); - } -} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 1aae7201..9d34aef9 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -11,7 +11,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; @@ -32,7 +31,6 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Quaternionf; @@ -114,22 +112,6 @@ public static Ref addPhysicsStoreBody(@Nonnull World world, return bodyRef; } - public static void addPhysicsStoreBodies(@Nonnull World world, - @Nonnull Iterable> bodyHolders) { - Objects.requireNonNull(bodyHolders, "bodyHolders"); - Store store = PhysicsThreading.store(world); - PhysicsThreading.requireWorldThread(store, "add PhysicsStore body entities"); - List> holders = new ArrayList<>(); - for (Holder holder : bodyHolders) { - holders.add(Objects.requireNonNull(holder, "holder")); - } - if (!holders.isEmpty()) { - @SuppressWarnings("unchecked") - Holder[] holderArray = holders.toArray(Holder[]::new); - store.addEntities(holderArray, AddReason.SPAWN); - } - } - @Nonnull private static Ref addPhysicsStoreBody(@Nonnull Store store, @Nonnull Holder holder) { @@ -195,133 +177,7 @@ public static Holder bodyEntity(@Nonnull Ref spaceRe linearVelocity); } - @Nonnull - public static BodyEntityBatchTiming addDynamicBodyBatchMeasured(@Nonnull World world, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder) { - DynamicBodyBatchPlan plan = dynamicBodyBatchPlan(spaceRef, - spaceId, - expectedBodies, - shape, - mass, - settings, - builder); - if (plan.isEmpty()) { - return new BodyEntityBatchTiming(0, plan.setupWallNanos(), 0L); - } - - long applyStartNanos = System.nanoTime(); - addPhysicsStoreBodies(world, plan.bodies()); - long physicsStoreApplyNanos = System.nanoTime() - applyStartNanos; - return new BodyEntityBatchTiming(plan.count(), - plan.setupWallNanos(), - physicsStoreApplyNanos); - } - - @Nonnull - private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull World world, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder) { - Objects.requireNonNull(world, "world"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(shape, "shape"); - Objects.requireNonNull(settings, "settings"); - - long setupStartNanos = System.nanoTime(); - BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); - Objects.requireNonNull(builder, "builder").accept(batch); - batch.seal(); - if (batch.isEmpty()) { - return new DynamicBodyBatchPlan(List.of(), 0L); - } - - Ref spaceRef = resolveSpaceRef(world, spaceId); - if (spaceRef == null) { - throw new IllegalStateException("Cannot add dynamic body entities because the target space is not " - + "bound in PhysicsStore: " + spaceId.value()); - } - - return dynamicBodyBatchPlan(spaceRef, - spaceId, - shape, - mass, - settings, - batch, - setupStartNanos); - } - - @Nonnull - private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder) { - Objects.requireNonNull(spaceRef, "spaceRef"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(shape, "shape"); - Objects.requireNonNull(settings, "settings"); - - PhysicsThreading.requireWorldThread(spaceRef.getStore(), - "add dynamic PhysicsStore body entities"); - if (!spaceRef.isValid()) { - throw new IllegalStateException("Cannot add dynamic body entities because the target " - + "PhysicsStore space entity is no longer valid: " + spaceId.value()); - } - - long setupStartNanos = System.nanoTime(); - BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); - Objects.requireNonNull(builder, "builder").accept(batch); - batch.seal(); - if (batch.isEmpty()) { - return new DynamicBodyBatchPlan(List.of(), 0L); - } - - return dynamicBodyBatchPlan(spaceRef, - spaceId, - shape, - mass, - settings, - batch, - setupStartNanos); - } - - @Nonnull - private static DynamicBodyBatchPlan dynamicBodyBatchPlan(@Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull BlockBodyBatchBuilder batch, - long setupStartNanos) { - List> bodies = new ArrayList<>(batch.size()); - for (int i = 0; i < batch.size(); i++) { - UUID bodyUuid = batch.bodyUuid(i); - bodies.add(PhysicsBodyEntities.bodyHolder(spaceRef, - bodyUuid, - new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), - shape, - PhysicsBodyType.DYNAMIC, - mass, - settings, - null)); - } - - return new DynamicBodyBatchPlan(bodies, System.nanoTime() - setupStartNanos); - } - - @Nonnull - public static SpawnedBlockBody attachBlockBody(@Nonnull Store store, + public static void attachBlockBody(@Nonnull Store store, @Nonnull TimeResource time, @Nonnull CreatedBlockBody created) { Ref bodyRef = created.bodyRef(); @@ -331,193 +187,13 @@ public static SpawnedBlockBody attachBlockBody(@Nonnull Store store throw new IllegalStateException("Cannot attach visual because PhysicsStore body entity " + "is no longer valid: " + created.bodyUuid()); } - Ref entity = spawnAttachedBlockEntity(store, + spawnAttachedBlockEntity(store, time, bodyRef, created.bodyUuid(), created.blockType(), new Vector3d(created.positionX(), created.positionY(), created.positionZ()), created.controllable()); - assert entity != null; - return new SpawnedBlockBody(created.bodyUuid(), created.spaceId(), entity); - } - - @Nonnull - public static BlockBodyBatchTiming spawnBlockBodiesMeasured(@Nonnull Store store, - @Nonnull TimeResource time, - long serverTick, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder) { - return spawnBlockBodiesInternal(store, - time, - serverTick, - spaceRef, - spaceId, - expectedBodies, - blockType, - shape, - mass, - settings, - builder, - false).timing(); - } - - @Nonnull - private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store store, - @Nonnull TimeResource time, - long serverTick, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder, - boolean collectBodies) { - BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); - Objects.requireNonNull(builder, "builder").accept(batch); - batch.seal(); - if (batch.isEmpty()) { - return new BlockBodyBatchResult(collectBodies ? new SpawnedBlockBody[0] : null, - 0, - 0L, - 0L); - } - - World world = store.getExternalData().getWorld(); - Ref spaceRef = resolveSpaceRef(world, spaceId); - if (spaceRef == null) { - throw new IllegalStateException("Cannot spawn block body batch because the target space is not " - + "bound in PhysicsStore: " + spaceId.value()); - } - - return spawnBlockBodiesInternal(store, - time, - serverTick, - spaceRef, - spaceId, - blockType, - shape, - mass, - settings, - batch, - collectBodies); - } - - @Nonnull - private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store store, - @Nonnull TimeResource time, - long serverTick, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - int expectedBodies, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull Consumer builder, - boolean collectBodies) { - Objects.requireNonNull(spaceRef, "spaceRef"); - Objects.requireNonNull(spaceId, "spaceId"); - Objects.requireNonNull(shape, "shape"); - Objects.requireNonNull(settings, "settings"); - - PhysicsThreading.requireWorldThread(spaceRef.getStore(), - "spawn PhysicsStore block body entities"); - if (!spaceRef.isValid()) { - throw new IllegalStateException("Cannot spawn block body batch because the target " - + "PhysicsStore space entity is no longer valid: " + spaceId.value()); - } - - BlockBodyBatchBuilder batch = new BlockBodyBatchBuilder(expectedBodies); - Objects.requireNonNull(builder, "builder").accept(batch); - batch.seal(); - if (batch.isEmpty()) { - return new BlockBodyBatchResult(collectBodies ? new SpawnedBlockBody[0] : null, - 0, - 0L, - 0L); - } - - return spawnBlockBodiesInternal(store, - time, - serverTick, - spaceRef, - spaceId, - blockType, - shape, - mass, - settings, - batch, - collectBodies); - } - - @Nonnull - private static BlockBodyBatchResult spawnBlockBodiesInternal(@Nonnull Store store, - @Nonnull TimeResource time, - long serverTick, - @Nonnull Ref spaceRef, - @Nonnull SpaceId spaceId, - @Nullable String blockType, - @Nonnull PhysicsShapeSpec shape, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nonnull BlockBodyBatchBuilder batch, - boolean collectBodies) { - World world = store.getExternalData().getWorld(); - List> bodyHolders = new ArrayList<>(batch.size()); - for (int i = 0; i < batch.size(); i++) { - UUID bodyUuid = batch.bodyUuid(i); - bodyHolders.add(bodyEntity(spaceRef, - bodyUuid, - new Vector3f(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), - shape, - mass, - settings, - null)); - } - - long physicsStoreApplyStartNanos = System.nanoTime(); - addPhysicsStoreBodies(world, bodyHolders); - long physicsStoreApplyNanos = System.nanoTime() - physicsStoreApplyStartNanos; - - long visualAttachStartNanos = System.nanoTime(); - SpawnedBlockBody[] spawned = collectBodies ? new SpawnedBlockBody[batch.size()] : null; - for (int i = 0; i < batch.size(); i++) { - UUID bodyUuid = batch.bodyUuid(i); - Ref entity = spawnAttachedBlockEntity(store, - time, - null, - bodyUuid, - blockType, - new Vector3d(batch.positionX(i), batch.positionY(i), batch.positionZ(i)), - mass > 0.0f); - if (spawned != null) { - assert entity != null; - spawned[i] = new SpawnedBlockBody(bodyUuid, spaceId, entity); - } - } - long visualAttachNanos = System.nanoTime() - visualAttachStartNanos; - return new BlockBodyBatchResult(spawned, - batch.size(), - physicsStoreApplyNanos, - visualAttachNanos); - } - - static void addControllableMarkerIfAvailable(@Nonnull Holder holder, - @Nonnull PhysicsBodyType bodyType) { - Objects.requireNonNull(holder, "holder"); - Objects.requireNonNull(bodyType, "bodyType"); - if (bodyType == PhysicsBodyType.DYNAMIC && PhysicsControlSessions.isAvailable()) { - holder.addComponent(ImpulseControllableComponent.getComponentType(), - new ImpulseControllableComponent()); - } } @Nullable @@ -635,11 +311,6 @@ public static Vector3f toVector3f(@Nonnull Vector3d vector) { return new Vector3f((float) vector.x, (float) vector.y, (float) vector.z); } - public record SpawnedBlockBody(@Nonnull UUID bodyUuid, - @Nonnull SpaceId spaceId, - @Nonnull Ref entity) { - } - public record SpaceSelection(@Nonnull SpaceId spaceId, @Nonnull Ref spaceRef) { @@ -649,45 +320,6 @@ public record SpaceSelection(@Nonnull SpaceId spaceId, } } - public record BlockBodyBatchTiming(int count, - long physicsStoreApplyNanos, - long visualAttachNanos) { - - public BlockBodyBatchTiming { - count = Math.max(0, count); - physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); - visualAttachNanos = Math.max(0L, visualAttachNanos); - } - } - - public record BodyEntityBatchTiming(int count, - long setupWallNanos, - long physicsStoreApplyNanos) { - - public BodyEntityBatchTiming { - count = Math.max(0, count); - setupWallNanos = Math.max(0L, setupWallNanos); - physicsStoreApplyNanos = Math.max(0L, physicsStoreApplyNanos); - } - } - - private record DynamicBodyBatchPlan(@Nonnull List> bodies, - long setupWallNanos) { - - DynamicBodyBatchPlan { - bodies = List.copyOf(Objects.requireNonNull(bodies, "bodies")); - setupWallNanos = Math.max(0L, setupWallNanos); - } - - private int count() { - return bodies.size(); - } - - private boolean isEmpty() { - return bodies.isEmpty(); - } - } - public record CreatedBlockBody(@Nonnull UUID bodyUuid, @Nonnull Ref bodyRef, @Nonnull SpaceId spaceId, From 2d723b9f7037c36788fd5f925821c5b7f1645b90 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 13:14:56 +0200 Subject: [PATCH 506/534] refactor(jolt): clamp raycast hit counts Signed-off-by: Blovien --- .../java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java index 4d9cff65..78733e2d 100644 --- a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java @@ -557,7 +557,7 @@ public int raycastAll(int spaceId, bodyHandles, hits); int emitted = 0; - int boundedHits = Math.min(Math.max(nativeHits, 0), maxHits); + int boundedHits = Math.clamp(nativeHits, 0, maxHits); for (int index = 0; index < boundedHits; index++) { if (emitRayHit(space, bodyHandles[index], From 5be29c27b0720d2b9979426e76d49fa7f1f9e724 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 14:48:06 +0200 Subject: [PATCH 507/534] refactor(core): move physics values into plugin physics Signed-off-by: Blovien --- .../impulse/core/internal/commands/SpaceCommand.java | 2 +- .../impulse/core/internal/commands/SpaceDeleteSupport.java | 2 +- .../core/internal/commands/perf/PerfStatsCommand.java | 2 +- .../internal/commands/settings/SolverSettingsCommand.java | 2 +- .../internal/commands/settings/StepModeSettingCommand.java | 2 +- .../core/internal/crucible/ImpulseApiCrucibleTests.java | 4 ++-- .../ImpulseDetachedStreamingBenchmarkCrucibleTests.java | 4 ++-- .../core/internal/crucible/ImpulseLiveCrucibleTests.java | 4 ++-- .../crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java | 4 ++-- .../core/internal/crucible/PhysicsStoreCrucibleSupport.java | 4 ++-- .../commands/PhysicsChunkPerfReportCommand.java | 2 +- .../core/internal/resources/PhysicsVisualRuntime.java | 2 +- .../impulse/core/plugin/persistence/PhysicsPersistence.java | 2 +- .../impulse/core/plugin/physics/PhysicsBackendAccess.java | 2 -- .../impulse/core/plugin/physics/PhysicsBodyEntities.java | 2 -- .../impulse/core/plugin/physics/PhysicsDiagnostics.java | 2 -- .../impulse/core/plugin/physics/PhysicsRaycasts.java | 3 --- .../plugin/{simulation => physics}/PhysicsShapeSpec.java | 2 +- .../{simulation => physics}/RaycastClosestBatchResult.java | 3 +-- .../plugin/{simulation/view => physics}/RaycastHitView.java | 2 +- .../core/plugin/{simulation => physics}/RaycastSegment.java | 2 +- .../{simulation => physics}/RigidBodySpawnSettings.java | 2 +- .../{simulation => physics}/SolverCapabilitySummary.java | 2 +- .../core/plugin/{simulation => physics}/SpaceSummary.java | 2 +- impulse-core/src/module-info/module-info.java | 2 -- .../internal/resources/BodyVisualInterestStateTest.java | 2 +- .../core/plugin/physics/PhysicsBodyEntitiesTest.java | 2 -- .../{simulation => physics}/PhysicsShapeSpecTest.java | 2 +- .../plugin/{simulation => physics}/RaycastHitViewTest.java | 3 +-- .../{simulation => physics}/RigidBodySpawnSettingsTest.java | 2 +- .../impulse/examples/commands/DropCommand.java | 4 ++-- .../impulse/examples/commands/ForcesCommand.java | 4 ++-- .../impulse/examples/commands/GrabCommand.java | 6 +++--- .../impulse/examples/commands/JointsCommand.java | 4 ++-- .../impulse/examples/commands/MaterialsCommand.java | 4 ++-- .../examples/commands/PhysicsStoreExampleCommands.java | 6 +++--- .../impulse/examples/commands/RaycastCommand.java | 2 +- .../impulse/examples/commands/ShapesCommand.java | 4 ++-- .../examples/commands/stress/StressBenchmarkCommand.java | 4 ++-- .../examples/commands/stress/StressBodiesCommand.java | 4 ++-- .../impulse/examples/commands/stress/StressBodyBatches.java | 4 ++-- .../examples/commands/stress/StressJointsCommand.java | 4 ++-- .../examples/commands/stress/StressRawBodiesCommand.java | 4 ++-- .../examples/commands/stress/StressRaycastCommand.java | 2 +- .../examples/commands/stress/StressShapesCommand.java | 4 ++-- .../impulse/examples/explosive/ExplosiveBlockRuntime.java | 4 ++-- .../impulse/examples/utils/ExamplePhysicsUtils.java | 4 ++-- 47 files changed, 63 insertions(+), 78 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/PhysicsShapeSpec.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/RaycastClosestBatchResult.java (90%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation/view => physics}/RaycastHitView.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/RaycastSegment.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/RigidBodySpawnSettings.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/SolverCapabilitySummary.java (91%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/SpaceSummary.java (98%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/PhysicsShapeSpecTest.java (94%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/RaycastHitViewTest.java (95%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/{simulation => physics}/RigidBodySpawnSettingsTest.java (95%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java index 6654bf40..2ec959c4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; import java.util.ArrayList; import java.util.Comparator; import java.util.List; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java index 317a2221..c1d8e2bb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; import java.util.List; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java index fbf92570..338c3239 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/perf/PerfStatsCommand.java @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index d44f87d7..68b3e501 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; +import dev.hytalemodding.impulse.core.plugin.physics.SolverCapabilitySummary; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java index b988cebd..e3af4617 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/StepModeSettingCommand.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java index aaa4ef69..ccfbe5c7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java @@ -21,8 +21,8 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 4219ad94..791bb53d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -36,8 +36,8 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java index d805fd11..5244ca78 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java @@ -23,8 +23,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import java.util.Comparator; import java.util.List; import java.util.Set; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java index c79da2f1..7b0aa8d5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java @@ -31,8 +31,8 @@ import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import java.util.ArrayList; import java.util.List; import java.util.Locale; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java index d7545049..f96e4e50 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java @@ -11,8 +11,8 @@ import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRuntimeCleaner; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java index 3293313f..442c9c46 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkPerfReportCommand.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java index 71f3070c..7b26fe73 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsVisualRuntime.java @@ -3,7 +3,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import dev.hytalemodding.impulse.core.plugin.physics.RaycastHitView; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index cecacf36..89d5dfa7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; +import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; import java.util.List; import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java index 52848029..1f86a182 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBackendAccess.java @@ -11,8 +11,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodyHitMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntities.java index db2e30d8..f74cbdde 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntities.java @@ -13,8 +13,6 @@ import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.util.Objects; import java.util.UUID; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java index 93a6cc8b..ec9c81fa 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java @@ -7,8 +7,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.plugin.simulation.SolverCapabilitySummary; -import dev.hytalemodding.impulse.core.plugin.simulation.SpaceSummary; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java index 15dfbd6c..2eeabde9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsRaycasts.java @@ -6,9 +6,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.runtime.BackendRayHitSink; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastClosestBatchResult; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsShapeSpec.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsShapeSpec.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsShapeSpec.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsShapeSpec.java index 70e9c8f7..51bd62f9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsShapeSpec.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsShapeSpec.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.ShapeType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchResult.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastClosestBatchResult.java similarity index 90% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchResult.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastClosestBatchResult.java index fd0a7145..c90c9159 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastClosestBatchResult.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastClosestBatchResult.java @@ -1,6 +1,5 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.Arrays; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitView.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitView.java index 6440e8bd..914a527f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/view/RaycastHitView.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitView.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation.view; +package dev.hytalemodding.impulse.core.plugin.physics; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastSegment.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastSegment.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastSegment.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastSegment.java index 3c6ed384..0890081d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastSegment.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastSegment.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import java.util.Objects; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodySpawnSettings.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RigidBodySpawnSettings.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodySpawnSettings.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RigidBodySpawnSettings.java index 629b856c..ff605d08 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodySpawnSettings.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/RigidBodySpawnSettings.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SolverCapabilitySummary.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/SolverCapabilitySummary.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SolverCapabilitySummary.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/SolverCapabilitySummary.java index 0835cecc..fd072b6b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SolverCapabilitySummary.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/SolverCapabilitySummary.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import dev.hytalemodding.impulse.api.SpaceId; import java.util.Objects; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/SpaceSummary.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/SpaceSummary.java index eec6b9f9..76db1d8a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/simulation/SpaceSummary.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/SpaceSummary.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 3fe98646..78aefd91 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -17,8 +17,6 @@ exports dev.hytalemodding.impulse.core.plugin.persistence; exports dev.hytalemodding.impulse.core.plugin.physics; exports dev.hytalemodding.impulse.core.plugin.settings; - exports dev.hytalemodding.impulse.core.plugin.simulation; - exports dev.hytalemodding.impulse.core.plugin.simulation.view; exports dev.hytalemodding.impulse.core.plugin.snapshot; exports dev.hytalemodding.impulse.core.plugin.snapshots; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java index f9f2c727..a92a12bb 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/BodyVisualInterestStateTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import dev.hytalemodding.impulse.core.plugin.physics.RaycastHitView; import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.joml.Vector3f; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java index 485af618..82b16ae4 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java @@ -26,8 +26,6 @@ import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsShapeSpecTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsShapeSpecTest.java similarity index 94% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsShapeSpecTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsShapeSpecTest.java index fcdcd9f2..54e16e82 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/PhysicsShapeSpecTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsShapeSpecTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastHitViewTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitViewTest.java similarity index 95% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastHitViewTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitViewTest.java index 89b72ab9..90ceb6e1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RaycastHitViewTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitViewTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -17,7 +17,6 @@ import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; import java.util.ArrayList; import org.joml.Vector3f; import org.junit.jupiter.api.Test; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodySpawnSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RigidBodySpawnSettingsTest.java similarity index 95% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodySpawnSettingsTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RigidBodySpawnSettingsTest.java index 79d7b8b2..30b059f9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/simulation/RigidBodySpawnSettingsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RigidBodySpawnSettingsTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.simulation; +package dev.hytalemodding.impulse.core.plugin.physics; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index 048063a9..f17c7a9b 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -23,8 +23,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java index bb86b11c..31a1ebca 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ForcesCommand.java @@ -19,8 +19,8 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.UUID; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index e80ac737..8168d7b3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -34,9 +34,9 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.components.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.RaycastHitView; import java.util.ArrayList; import java.util.List; import java.util.UUID; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java index ca50594b..313a06d3 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/JointsCommand.java @@ -20,8 +20,8 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; import java.util.ArrayList; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java index 657b71ac..031ac5e5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/MaterialsCommand.java @@ -15,8 +15,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index ece1a43e..9abf4d2c 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -30,9 +30,9 @@ import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventCollectionMode; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.RaycastHitView; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockComponent; import dev.hytalemodding.impulse.examples.explosive.ExplosiveBlockPolicy; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java index 01da3aa5..be521649 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/RaycastCommand.java @@ -15,7 +15,7 @@ import com.hypixel.hytale.server.core.util.TargetUtil; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsRaycasts; -import dev.hytalemodding.impulse.core.plugin.simulation.view.RaycastHitView; +import dev.hytalemodding.impulse.core.plugin.physics.RaycastHitView; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java index 0c19b8fc..33f73ea5 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ShapesCommand.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java index 6e94e818..26b27d89 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBenchmarkCommand.java @@ -15,8 +15,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.Locale; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java index 45e2049b..f3a01d01 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodiesCommand.java @@ -28,8 +28,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java index dd0ce8ae..3c0d4eeb 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressBodyBatches.java @@ -11,8 +11,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.ArrayList; import java.util.List; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java index 97f09aab..d15f9b2d 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressJointsCommand.java @@ -16,8 +16,8 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.JointType; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExampleBlockEntityVisuals; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java index ce060d77..d0077b86 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRawBodiesCommand.java @@ -12,8 +12,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.Locale; import java.util.concurrent.CompletableFuture; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java index d01eacc7..d0fdb37e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressRaycastCommand.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsRaycasts; -import dev.hytalemodding.impulse.core.plugin.simulation.RaycastSegment; +import dev.hytalemodding.impulse.core.plugin.physics.RaycastSegment; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.ArrayList; import java.util.List; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java index 85af67dc..a2961b06 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/stress/StressShapesCommand.java @@ -14,8 +14,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import java.util.UUID; import java.util.concurrent.CompletableFuture; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java index c616b7b5..20899915 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/explosive/ExplosiveBlockRuntime.java @@ -26,8 +26,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 9d34aef9..0ad12d07 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -24,8 +24,8 @@ import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.simulation.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.simulation.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; import java.util.ArrayList; import java.util.Comparator; import java.util.List; From 477386c98ef7a33cbc4ba1df8b3ca0dfccbbc7dd Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 14:48:17 +0200 Subject: [PATCH 508/534] perf(core): skip step submission without runtime spaces Signed-off-by: Blovien --- .../systems/StepSubmissionSystem.java | 8 +++- .../systems/StepSubmissionSystemTest.java | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java index 485a7831..f8fd4cec 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java @@ -67,10 +67,15 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (safeDt <= 0.0f) { return; } + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + List bindings = runtimeStepBindings(runtime); + if (bindings.isEmpty()) { + return; + } + PhysicsWorldSettingsResource settingsResource = store.getResource( PhysicsWorldSettingsResource.getResourceType()); PhysicsWorldSettings settings = settingsResource.getSettings(); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); PhysicsStepSchedulerResource scheduler = store.getResource( @@ -103,7 +108,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) if (profilingEnabled) { resetStepPhaseStats(runtime); } - List bindings = runtimeStepBindings(runtime); boolean collectBackendEvents = settings.getEventCollectionMode().collectsBackendEvents(); boolean submitted = scheduler.submitStep(input, () -> runOwnerStep(runtime, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java new file mode 100644 index 00000000..4922020f --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java @@ -0,0 +1,48 @@ +package dev.hytalemodding.impulse.core.internal.systems; + +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class StepSubmissionSystemTest { + + @Test + void tickWithNoRuntimeSpacesDoesNotSubmitOwnerLaneStep() throws Exception { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("step-submission-no-spaces")), + EmptyResourceStorage.get()); + try { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + + new StepSubmissionSystem().tick(0.05f, 0, store); + + PhysicsStepSchedulerResource scheduler = store.getResource( + PhysicsStepSchedulerResource.getResourceType()); + scheduler.whenIdle().toCompletableFuture().get(5, TimeUnit.SECONDS); + assertNull(scheduler.pollCompletedStep(), + "Zero-space physics ticks should not submit an owner-lane step"); + } finally { + store.getResource(PhysicsStepSchedulerResource.getResourceType()).close(); + registry.removeStore(store); + registry.shutdown(); + } + } +} From 35bf14f245a0b7606c196582f3cb676948127cf6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 14:48:28 +0200 Subject: [PATCH 509/534] fix(control): clear released anchor body state Signed-off-by: Blovien --- .../PhysicsStoreControlSessionMutations.java | 18 +- ...ysicsStoreControlSessionMutationsTest.java | 313 ++++++++++++++++++ 2 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 144b555d..116f1c9f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -6,14 +6,15 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3f; @@ -50,7 +51,7 @@ public static void applyRelease(@Nonnull Store store, Ref anchorBodyRef = session.getAnchorBodyRef(); if (anchorBodyRef != null) { - removeRow(physicsStore, anchorBodyRef); + removeBodyRow(physicsStore, anchorBodyRef); } } @@ -84,6 +85,19 @@ private static void disableJoint(@Nonnull Store store, store.putComponent(ref, JointComponent.getComponentType(), disabled); } + private static void removeBodyRow(@Nonnull Store store, + @Nonnull Ref ref) { + if (!isValidStoreRef(store, ref)) { + return; + } + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + if (uuid == null) { + removeRow(store, ref); + return; + } + PhysicsStoreRowCleanup.removeBodyEntity(store, uuid.getUuid(), ref, null); + } + private static void removeRow(@Nonnull Store store, @Nonnull Ref ref) { if (!isValidStoreRef(store, ref)) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java new file mode 100644 index 00000000..45a4ffbf --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java @@ -0,0 +1,313 @@ +package dev.hytalemodding.impulse.core.internal.modules.control.systems; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.PhysicsAxis; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointType; +import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; +import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; +import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; +import dev.hytalemodding.impulse.early.PhysicsStoreWorld; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; + +class PhysicsStoreControlSessionMutationsTest { + + private static final ObjenesisStd OBJENESIS = new ObjenesisStd(); + private static final BackendId BACKEND_ID = new BackendId("test:control-release"); + private static final SpaceId SPACE_ID = new SpaceId(42); + + @Test + void releaseClearsTemporaryAnchorBodyCopiedState() { + StoreFixture fixture = store("control-release-anchor-cleanup"); + Store physicsStore = fixture.physicsStore(); + Store entityStore = fixture.entityStore(); + try { + markCurrentThreadAsWorldThread(physicsStore); + UUID spaceUuid = uuid(1); + UUID controlledBodyUuid = uuid(2); + UUID anchorBodyUuid = uuid(3); + UUID controlJointUuid = uuid(4); + Ref spaceRef = addSpace(physicsStore, spaceUuid); + Ref controlledBodyRef = addBody(physicsStore, + spaceUuid, + spaceRef, + controlledBodyUuid, + 0.0f); + Ref anchorBodyRef = addBody(physicsStore, + spaceUuid, + spaceRef, + anchorBodyUuid, + 1.0f); + Ref controlJointRef = addJoint(physicsStore, + spaceRef, + controlledBodyRef, + anchorBodyRef, + controlJointUuid); + publishCopiedState(physicsStore, + spaceUuid, + controlledBodyUuid, + controlledBodyRef, + anchorBodyUuid, + anchorBodyRef); + + PhysicsControlSessionComponent session = new PhysicsControlSessionComponent( + controlledBodyRef, + anchorBodyRef, + controlJointRef, + null, + PhysicsBodyType.DYNAMIC, + 4.0f, + new Vector3f(), + new Vector3f()); + + PhysicsStoreControlSessionMutations.applyRelease(entityStore, session); + + assertFalse(anchorBodyRef.isValid()); + assertNull(physicsStore.getResource(PhysicsIdentityIndexResource.getResourceType()) + .getByUuid(anchorBodyUuid)); + assertNull(physicsStore.getResource(PhysicsSnapshotResource.getResourceType()) + .getBody(anchorBodyUuid)); + assertFalse(PhysicsBodies.isRegistered(physicsStore, anchorBodyUuid)); + assertNotNull(PhysicsBodies.snapshot(physicsStore, controlledBodyUuid)); + } finally { + fixture.close(); + } + } + + @Nonnull + private static StoreFixture store(@Nonnull String worldName) { + TestWorld world = OBJENESIS.newInstance(TestWorld.class); + setField(world, World.class, "name", worldName); + ComponentRegistry physicsRegistry = new ComponentRegistry<>(); + ComponentRegistryProxy physicsProxy = + new ComponentRegistryProxy<>(new ArrayList<>(), physicsRegistry); + PhysicsComponentTypeRegistry.registerComponentTypes(physicsProxy); + PhysicsResourceTypes.registerResourceTypes(physicsProxy); + PhysicsStore physicsStoreExternal = new PhysicsStore(world); + Store physicsStore = + physicsRegistry.addStore(physicsStoreExternal, EmptyResourceStorage.get()); + setField(physicsStoreExternal, PhysicsStore.class, "store", physicsStore); + world.physicsStore = physicsStoreExternal; + + ComponentRegistry entityRegistry = new ComponentRegistry<>(); + Store entityStore = entityRegistry.addStore(new EntityStore(world), + EmptyResourceStorage.get()); + return new StoreFixture(physicsRegistry, entityRegistry, physicsStore, entityStore); + } + + @Nonnull + private static Ref addSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + Ref ref = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(BACKEND_ID, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(ref); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(spaceUuid, ref); + store.getExternalData().putRefForUUID(spaceUuid, ref); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(SPACE_ID, spaceUuid); + return ref; + } + + @Nonnull + private static Ref addBody(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef, + @Nonnull UUID bodyUuid, + float positionX) { + BodyComponent body = new BodyComponent(spaceUuid); + body.setSpaceRef(spaceRef); + Ref ref = store.addEntity(PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 1.0f, 0.0f, 0.0f, false), + null, + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.5f, 0.1f), + new CollisionFilterComponent(0x01, 0x02)), + AddReason.SPAWN); + assertNotNull(ref); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(bodyUuid, ref); + store.getExternalData().putRefForUUID(bodyUuid, ref); + return ref; + } + + @Nonnull + private static Ref addJoint(@Nonnull Store store, + @Nonnull Ref spaceRef, + @Nonnull Ref controlledBodyRef, + @Nonnull Ref anchorBodyRef, + @Nonnull UUID jointUuid) { + JointComponent joint = PhysicsJointEntities.joint(spaceRef, + anchorBodyRef, + controlledBodyRef, + JointType.POINT, + new Vector3f(), + new Vector3f(), + new Vector3f()); + Ref ref = store.addEntity(PhysicsEntities.jointHolder(store, + jointUuid, + joint), + AddReason.SPAWN); + assertNotNull(ref); + store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(jointUuid, ref); + store.getExternalData().putRefForUUID(jointUuid, ref); + return ref; + } + + private static void publishCopiedState(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull UUID controlledBodyUuid, + @Nonnull Ref controlledBodyRef, + @Nonnull UUID anchorBodyUuid, + @Nonnull Ref anchorBodyRef) { + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(controlledBodyRef, controlledBodyUuid, spaceUuid, 0.0f), + snapshot(anchorBodyRef, anchorBodyUuid, spaceUuid, 1.0f)))); + } + + @Nonnull + private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, + @Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + float positionX) { + return PhysicsBodySnapshot.of(bodyRef, + bodyUuid, + spaceUuid, + PhysicsBodyType.DYNAMIC, + positionX, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false); + } + + @Nonnull + private static UUID uuid(long leastSignificantBits) { + return new UUID(0L, leastSignificantBits); + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } + + private static void setField(@Nonnull Object target, + @Nonnull Class owner, + @Nonnull String name, + Object value) { + try { + Field field = owner.getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Failed to set test field " + owner.getName() + "." + name, + exception); + } + } + + private static final class TestWorld extends World implements PhysicsStoreWorld { + + private PhysicsStore physicsStore; + + private TestWorld() throws IOException { + super("unused", Path.of("build/tmp/unused-control-release-world"), (WorldConfig) null); + } + + @Nonnull + @Override + public PhysicsStore getPhysicsStore() { + return physicsStore; + } + } + + private record StoreFixture( + @Nonnull ComponentRegistry physicsRegistry, + @Nonnull ComponentRegistry entityRegistry, + @Nonnull Store physicsStore, + @Nonnull Store entityStore) implements AutoCloseable { + + @Override + public void close() { + physicsRegistry.removeStore(physicsStore); + entityRegistry.removeStore(entityStore); + physicsRegistry.shutdown(); + entityRegistry.shutdown(); + } + } +} From 7e78b4c39a6199314cbb6802eb1db785a111e56c Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 14:48:45 +0200 Subject: [PATCH 510/534] fix(core): recover restore status after cleanup Signed-off-by: Blovien --- .../physics/PhysicsStoreRuntimeCleaner.java | 3 ++ .../physics/PhysicsTopologyMutations.java | 3 ++ .../PhysicsRestoreStatusResource.java | 8 ++++ .../PhysicsStoreTopologyMutationsTest.java | 39 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java index e97d2417..f60fe8f5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java @@ -6,6 +6,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -41,5 +42,7 @@ public static void clearAll(@Nonnull Store store) { store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()).clear(); + store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .markRecoveredFromCleanup(); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java index 36a7b9a5..88013fd3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java @@ -7,6 +7,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; @@ -66,6 +67,8 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( runtime.clearTransientBodyOperations(); int keptSpaces = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .size(); + store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .markRecoveredFromCleanup(); return new PhysicsRuntimeResetResult(removed.bodyCount(), removed.jointCount(), keptSpaces); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java index e2b7fe1f..6a8d59db 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRestoreStatusResource.java @@ -57,6 +57,14 @@ public void markHydrated() { hydrated = true; } + public void markRecoveredFromCleanup() { + pending = false; + failed = false; + hydrated = true; + failureMessage = ""; + softSkipsByReason.clear(); + } + public void recordSoftSkip(@Nonnull String reason) { softSkipsByReason.put(reason, softSkipsByReason.getInt(reason) + 1); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java index 8e490ab1..77dffcdf 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; @@ -22,12 +23,14 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -125,6 +128,42 @@ void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { } } + @Test + void clearBodiesKeepingSpacesRecoversFailedRestoreStatus() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("topology-clean-recovers-restore")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(1); + UUID bodyUuid = uuid(2); + BoundSpace space = addBoundSpace(store, + spaceUuid, + new BackendId("test:topology-clean-recovers-restore")); + Ref bodyRef = addBody(store, spaceUuid, space.ref(), bodyUuid); + bindBody(store, space, bodyUuid, bodyRef, 0.0f); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markFailed("backend binding failed"); + + PhysicsTopologyMutations.clearBodiesKeepingSpaces(store); + + assertFalse(restore.isFailed(), restore.getFailureMessage()); + assertFalse(restore.isPending()); + assertTrue(restore.isHydrated()); + } finally { + registry.removeStore(store); + registry.shutdown(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + } + } + @Nonnull private static BoundSpace addBoundSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, From 27d94aba104b25b4d109bdd261e99c8fdfbd791d Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 14:49:13 +0200 Subject: [PATCH 511/534] refactor(core): use holder-only physics persistence Signed-off-by: Blovien --- .../persistence/PersistentBodyDto.java | 175 ------- .../PersistentBodyRuntimeStateCodec.java | 324 ------------- .../PersistentBodyRuntimeStateDto.java | 73 --- .../persistence/PersistentColliderDto.java | 160 ------ .../persistence/PersistentJointDto.java | 280 ----------- .../persistence/PersistentMaterialDto.java | 66 --- .../PersistentPhysicsStorePreflight.java | 266 ---------- .../PersistentPhysicsStoreResource.java | 245 ---------- .../PersistentPhysicsStoreStorage.java | 63 --- .../persistence/PersistentShapeDto.java | 173 ------- .../persistence/PersistentSpaceDto.java | 457 ------------------ .../PhysicsStoreHolderStorage.java | 26 +- .../PhysicsStorePersistenceValidation.java | 75 --- .../resources/PhysicsResourceTypes.java | 11 - .../systems/PersistenceHydrationSystem.java | 261 +--------- .../persistence/PhysicsPersistence.java | 12 +- .../PersistentSpaceDtoSettingsTest.java | 132 ----- .../PersistentPhysicsStoreResourceTest.java | 203 -------- .../PhysicsStoreHolderPersistenceTest.java | 145 ++++-- 19 files changed, 108 insertions(+), 3039 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentColliderDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentMaterialDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentShapeDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStorePersistenceValidation.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PersistentPhysicsStoreResourceTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java deleted file mode 100644 index 3d550e07..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyDto.java +++ /dev/null @@ -1,175 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.codec.validation.Validators; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import java.util.Arrays; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -public final class PersistentBodyDto { - - private static final UUID[] EMPTY_UUIDS = new UUID[0]; - - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentBodyDto.class, PersistentBodyDto::new) - .append(new KeyedCodec<>("BodyUuid", Codec.UUID_BINARY), - (dto, value) -> dto.bodyUuid = value, - PersistentBodyDto::getBodyUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), - (dto, value) -> dto.spaceUuid = value, - PersistentBodyDto::getSpaceUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BodyType", new EnumCodec<>(PhysicsBodyType.class), false), - (dto, value) -> dto.bodyType = value != null ? value : PhysicsBodyType.DYNAMIC, - PersistentBodyDto::getBodyType) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Mass", Codec.FLOAT, false), - (dto, value) -> dto.mass = value != null ? value : 1.0f, - PersistentBodyDto::getMass) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted body mass must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("LinearDamping", Codec.FLOAT, false), - (dto, value) -> dto.linearDamping = value != null ? value : 0.0f, - PersistentBodyDto::getLinearDamping) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted body linear damping must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("AngularDamping", Codec.FLOAT, false), - (dto, value) -> dto.angularDamping = value != null ? value : 0.0f, - PersistentBodyDto::getAngularDamping) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted body angular damping must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("ContinuousCollision", Codec.BOOLEAN, false), - (dto, value) -> dto.continuousCollisionEnabled = value != null && value, - PersistentBodyDto::isContinuousCollisionEnabled) - .add() - .append(new KeyedCodec<>("ColliderUuids", - new ArrayCodec<>(Codec.UUID_BINARY, UUID[]::new), - false), - (dto, value) -> dto.colliderUuids = copyUuids(value), - PersistentBodyDto::getColliderUuids) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("RuntimeState", PersistentBodyRuntimeStateDto.CODEC, false), - (dto, value) -> dto.runtimeState = value != null - ? value.copy() - : new PersistentBodyRuntimeStateDto(), - PersistentBodyDto::getRuntimeState) - .addValidator(Validators.nonNull()) - .add() - .build(); - - @Nonnull - private UUID bodyUuid = new UUID(0L, 0L); - @Nonnull - private UUID spaceUuid = new UUID(0L, 0L); - @Nonnull - private PhysicsBodyType bodyType = PhysicsBodyType.DYNAMIC; - private float mass = 1.0f; - private float linearDamping; - private float angularDamping; - private boolean continuousCollisionEnabled; - @Nonnull - private UUID[] colliderUuids = EMPTY_UUIDS; - @Nonnull - private PersistentBodyRuntimeStateDto runtimeState = new PersistentBodyRuntimeStateDto(); - - public PersistentBodyDto() { - } - - public PersistentBodyDto(@Nonnull UUID bodyUuid, - @Nonnull UUID spaceUuid, - @Nonnull PhysicsBodyType bodyType, - float mass, - float linearDamping, - float angularDamping, - boolean continuousCollisionEnabled, - @Nonnull UUID[] colliderUuids, - @Nonnull PersistentBodyRuntimeStateDto runtimeState) { - this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.bodyType = Objects.requireNonNull(bodyType, "bodyType"); - this.mass = mass; - this.linearDamping = linearDamping; - this.angularDamping = angularDamping; - this.continuousCollisionEnabled = continuousCollisionEnabled; - this.colliderUuids = copyUuids(colliderUuids); - this.runtimeState = Objects.requireNonNull(runtimeState, "runtimeState").copy(); - } - - @Nonnull - public UUID getBodyUuid() { - return bodyUuid; - } - - @Nonnull - public UUID getSpaceUuid() { - return spaceUuid; - } - - @Nonnull - public PhysicsBodyType getBodyType() { - return bodyType; - } - - public float getMass() { - return mass; - } - - public float getLinearDamping() { - return linearDamping; - } - - public float getAngularDamping() { - return angularDamping; - } - - public boolean isContinuousCollisionEnabled() { - return continuousCollisionEnabled; - } - - @Nonnull - public UUID[] getColliderUuids() { - return copyUuids(colliderUuids); - } - - @Nonnull - public PersistentBodyRuntimeStateDto getRuntimeState() { - return runtimeState.copy(); - } - - @Nonnull - public PersistentBodyDto copy() { - return new PersistentBodyDto(bodyUuid, - spaceUuid, - bodyType, - mass, - linearDamping, - angularDamping, - continuousCollisionEnabled, - colliderUuids, - runtimeState); - } - - @Nonnull - private static UUID[] copyUuids(UUID[] values) { - if (values == null || values.length == 0) { - return EMPTY_UUIDS; - } - return Arrays.copyOf(values, values.length); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java deleted file mode 100644 index 48748692..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateCodec.java +++ /dev/null @@ -1,324 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.codec.schema.SchemaContext; -import com.hypixel.hytale.codec.schema.config.Schema; -import com.hypixel.hytale.codec.util.RawJsonReader; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Base64; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.bson.BsonBoolean; -import org.bson.BsonDocument; -import org.bson.BsonString; -import org.bson.BsonValue; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -final class PersistentBodyRuntimeStateCodec implements Codec { - - static final PersistentBodyRuntimeStateCodec INSTANCE = - new PersistentBodyRuntimeStateCodec(); - - private static final byte PACKED_VERSION = 1; - private static final int PACKED_FLOATS = 13; - private static final int PACKED_BYTES = 1 + PACKED_FLOATS * Float.BYTES + 1; - - private PersistentBodyRuntimeStateCodec() { - } - - @Override - public PersistentBodyRuntimeStateDto decode(@Nonnull BsonValue value, - @Nonnull ExtraInfo extraInfo) { - if (Codec.isNullBsonValue(value)) { - return new PersistentBodyRuntimeStateDto(); - } - if (value.isString()) { - return decodePacked(value.asString().getValue()); - } - if (value.isDocument()) { - return decodeLegacyDocument(value.asDocument()); - } - throw new IllegalArgumentException("Persistent body runtime state must be a string"); - } - - @Override - public BsonValue encode(@Nonnull PersistentBodyRuntimeStateDto value, - @Nonnull ExtraInfo extraInfo) { - return new BsonString(encodePacked(value)); - } - - @Override - public PersistentBodyRuntimeStateDto decodeJson(@Nonnull RawJsonReader reader, - @Nonnull ExtraInfo extraInfo) throws IOException { - reader.consumeWhiteSpace(); - int next = reader.peek(); - if (next == '"') { - return decodePacked(reader.readString()); - } - if (next == 'n') { - readNullToken(reader); - return new PersistentBodyRuntimeStateDto(); - } - if (next == '{') { - return decodeLegacyObject(reader); - } - throw new IOException("Persistent body runtime state must be a string or object"); - } - - @Override - public Schema toSchema(@Nonnull SchemaContext context) { - return Codec.STRING.toSchema(context); - } - - @Nonnull - private static String encodePacked(@Nonnull PersistentBodyRuntimeStateDto dto) { - Objects.requireNonNull(dto, "dto"); - ByteBuffer buffer = ByteBuffer.allocate(PACKED_BYTES).order(ByteOrder.BIG_ENDIAN); - buffer.put(PACKED_VERSION); - Vector3f position = dto.getPosition(); - buffer.putFloat(position.x).putFloat(position.y).putFloat(position.z); - Quaternionf rotation = dto.getRotation(); - buffer.putFloat(rotation.x).putFloat(rotation.y).putFloat(rotation.z).putFloat(rotation.w); - Vector3f linearVelocity = dto.getLinearVelocity(); - buffer.putFloat(linearVelocity.x).putFloat(linearVelocity.y).putFloat(linearVelocity.z); - Vector3f angularVelocity = dto.getAngularVelocity(); - buffer.putFloat(angularVelocity.x).putFloat(angularVelocity.y).putFloat(angularVelocity.z); - buffer.put((byte) (dto.isSleeping() ? 1 : 0)); - return Base64.getEncoder().encodeToString(buffer.array()); - } - - @Nonnull - private static PersistentBodyRuntimeStateDto decodePacked(@Nonnull String encoded) { - byte[] bytes = Base64.getDecoder().decode(encoded); - if (bytes.length != PACKED_BYTES) { - throw new IllegalArgumentException("Packed runtime state has invalid length"); - } - ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); - byte version = buffer.get(); - if (version != PACKED_VERSION) { - throw new IllegalArgumentException("Unsupported packed runtime state version"); - } - Vector3f position = new Vector3f(buffer.getFloat(), buffer.getFloat(), buffer.getFloat()); - Quaternionf rotation = new Quaternionf(buffer.getFloat(), - buffer.getFloat(), - buffer.getFloat(), - buffer.getFloat()); - Vector3f linearVelocity = new Vector3f(buffer.getFloat(), - buffer.getFloat(), - buffer.getFloat()); - Vector3f angularVelocity = new Vector3f(buffer.getFloat(), - buffer.getFloat(), - buffer.getFloat()); - boolean sleeping = buffer.get() != 0; - return new PersistentBodyRuntimeStateDto(position, - rotation, - linearVelocity, - angularVelocity, - sleeping); - } - - @Nonnull - private static PersistentBodyRuntimeStateDto decodeLegacyDocument(@Nonnull BsonDocument document) { - return new PersistentBodyRuntimeStateDto(vector(document.getDocument("Position", - new BsonDocument())), - quaternion(document.getDocument("Rotation", new BsonDocument())), - vector(document.getDocument("LinearVelocity", new BsonDocument())), - vector(document.getDocument("AngularVelocity", new BsonDocument())), - document.getBoolean("Sleeping", BsonBoolean.FALSE).getValue()); - } - - @Nonnull - private static Vector3f vector(@Nonnull BsonDocument document) { - return new Vector3f(number(document, "X", 0.0f), - number(document, "Y", 0.0f), - number(document, "Z", 0.0f)); - } - - @Nonnull - private static Quaternionf quaternion(@Nonnull BsonDocument document) { - return new Quaternionf(number(document, "X", 0.0f), - number(document, "Y", 0.0f), - number(document, "Z", 0.0f), - number(document, "W", 1.0f)); - } - - private static float number(@Nonnull BsonDocument document, - @Nonnull String key, - float fallback) { - BsonValue value = document.get(key); - return value != null && value.isNumber() ? (float) value.asNumber().doubleValue() : fallback; - } - - @Nonnull - private static PersistentBodyRuntimeStateDto decodeLegacyObject(@Nonnull RawJsonReader reader) - throws IOException { - Vector3f position = new Vector3f(); - Quaternionf rotation = new Quaternionf(); - Vector3f linearVelocity = new Vector3f(); - Vector3f angularVelocity = new Vector3f(); - boolean sleeping = false; - reader.expect('{'); - reader.consumeWhiteSpace(); - if (reader.tryConsume('}')) { - return new PersistentBodyRuntimeStateDto(position, - rotation, - linearVelocity, - angularVelocity, - sleeping); - } - while (true) { - reader.consumeWhiteSpace(); - String key = reader.readString(); - reader.consumeWhiteSpace(); - reader.expect(':'); - reader.consumeWhiteSpace(); - switch (key) { - case "Position" -> position = readVector(reader); - case "Rotation" -> rotation = readQuaternion(reader); - case "LinearVelocity" -> linearVelocity = readVector(reader); - case "AngularVelocity" -> angularVelocity = readVector(reader); - case "Sleeping" -> sleeping = readBooleanToken(reader); - default -> reader.skipValue(); - } - reader.consumeWhiteSpace(); - if (reader.tryConsume('}')) { - break; - } - reader.expect(','); - } - return new PersistentBodyRuntimeStateDto(position, - rotation, - linearVelocity, - angularVelocity, - sleeping); - } - - @Nonnull - private static Vector3f readVector(@Nonnull RawJsonReader reader) throws IOException { - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - reader.expect('{'); - reader.consumeWhiteSpace(); - if (reader.tryConsume('}')) { - return new Vector3f(); - } - while (true) { - reader.consumeWhiteSpace(); - String key = reader.readString(); - reader.consumeWhiteSpace(); - reader.expect(':'); - reader.consumeWhiteSpace(); - switch (key) { - case "X" -> x = readFloatToken(reader); - case "Y" -> y = readFloatToken(reader); - case "Z" -> z = readFloatToken(reader); - default -> reader.skipValue(); - } - reader.consumeWhiteSpace(); - if (reader.tryConsume('}')) { - break; - } - reader.expect(','); - } - return new Vector3f(x, y, z); - } - - @Nonnull - private static Quaternionf readQuaternion(@Nonnull RawJsonReader reader) throws IOException { - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - float w = 1.0f; - reader.expect('{'); - reader.consumeWhiteSpace(); - if (reader.tryConsume('}')) { - return new Quaternionf(); - } - while (true) { - reader.consumeWhiteSpace(); - String key = reader.readString(); - reader.consumeWhiteSpace(); - reader.expect(':'); - reader.consumeWhiteSpace(); - switch (key) { - case "X" -> x = readFloatToken(reader); - case "Y" -> y = readFloatToken(reader); - case "Z" -> z = readFloatToken(reader); - case "W" -> w = readFloatToken(reader); - default -> reader.skipValue(); - } - reader.consumeWhiteSpace(); - if (reader.tryConsume('}')) { - break; - } - reader.expect(','); - } - return new Quaternionf(x, y, z, w); - } - - private static float readFloatToken(@Nonnull RawJsonReader reader) throws IOException { - StringBuilder token = new StringBuilder(32); - while (true) { - int next = reader.peek(); - if (!isNumberCharacter(next)) { - break; - } - token.append((char) reader.read()); - } - if (token.isEmpty()) { - throw new IOException("Expected persisted float value"); - } - try { - return Float.parseFloat(token.toString()); - } catch (NumberFormatException exception) { - throw new IOException("Invalid persisted float value: " + token, exception); - } - } - - private static boolean readBooleanToken(@Nonnull RawJsonReader reader) throws IOException { - String token = readWordToken(reader); - return switch (token) { - case "true" -> true; - case "false" -> false; - default -> throw new IOException("Invalid persisted boolean value: " + token); - }; - } - - private static void readNullToken(@Nonnull RawJsonReader reader) throws IOException { - String token = readWordToken(reader); - if (!"null".equals(token)) { - throw new IOException("Invalid persisted null value: " + token); - } - } - - @Nonnull - private static String readWordToken(@Nonnull RawJsonReader reader) throws IOException { - StringBuilder token = new StringBuilder(5); - while (true) { - int next = reader.peek(); - if (!isWordCharacter(next)) { - break; - } - token.append((char) reader.read()); - } - return token.toString(); - } - - private static boolean isWordCharacter(int value) { - return value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'; - } - - private static boolean isNumberCharacter(int value) { - return value == '-' - || value == '+' - || value == '.' - || value == 'e' - || value == 'E' - || value >= '0' && value <= '9'; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java deleted file mode 100644 index 24001c15..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentBodyRuntimeStateDto.java +++ /dev/null @@ -1,73 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import java.util.Objects; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * Dynamic runtime body state overlaid from the latest completed backend snapshot. - */ -public final class PersistentBodyRuntimeStateDto { - - @Nonnull - public static final Codec CODEC = - PersistentBodyRuntimeStateCodec.INSTANCE; - - @Nonnull - private final Vector3f position = new Vector3f(); - @Nonnull - private final Quaternionf rotation = new Quaternionf(); - @Nonnull - private final Vector3f linearVelocity = new Vector3f(); - @Nonnull - private final Vector3f angularVelocity = new Vector3f(); - @Getter - private boolean sleeping; - - public PersistentBodyRuntimeStateDto() { - } - - public PersistentBodyRuntimeStateDto(@Nonnull Vector3f position, - @Nonnull Quaternionf rotation, - @Nonnull Vector3f linearVelocity, - @Nonnull Vector3f angularVelocity, - boolean sleeping) { - this.position.set(Objects.requireNonNull(position, "position")); - this.rotation.set(Objects.requireNonNull(rotation, "rotation")); - this.linearVelocity.set(Objects.requireNonNull(linearVelocity, "linearVelocity")); - this.angularVelocity.set(Objects.requireNonNull(angularVelocity, "angularVelocity")); - this.sleeping = sleeping; - } - - @Nonnull - public Vector3f getPosition() { - return new Vector3f(position); - } - - @Nonnull - public Quaternionf getRotation() { - return new Quaternionf(rotation); - } - - @Nonnull - public Vector3f getLinearVelocity() { - return new Vector3f(linearVelocity); - } - - @Nonnull - public Vector3f getAngularVelocity() { - return new Vector3f(angularVelocity); - } - - @Nonnull - public PersistentBodyRuntimeStateDto copy() { - return new PersistentBodyRuntimeStateDto(position, - rotation, - linearVelocity, - angularVelocity, - sleeping); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentColliderDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentColliderDto.java deleted file mode 100644 index c0f38484..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentColliderDto.java +++ /dev/null @@ -1,160 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.core.plugin.codec.ImpulseCodecs; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -public final class PersistentColliderDto { - - private static final Vector3f ZERO = new Vector3f(); - private static final Quaternionf IDENTITY = new Quaternionf(); - - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentColliderDto.class, PersistentColliderDto::new) - .append(new KeyedCodec<>("ColliderUuid", Codec.UUID_BINARY), - (dto, value) -> dto.colliderUuid = value, - PersistentColliderDto::getColliderUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BodyUuid", Codec.UUID_BINARY), - (dto, value) -> dto.bodyUuid = value, - PersistentColliderDto::getBodyUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("ShapeUuid", Codec.UUID_BINARY), - (dto, value) -> dto.shapeUuid = value, - PersistentColliderDto::getShapeUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("MaterialUuid", Codec.UUID_BINARY), - (dto, value) -> dto.materialUuid = value, - PersistentColliderDto::getMaterialUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("LocalPosition", Vector3fUtil.CODEC, false), - (dto, value) -> dto.localPosition.set(value != null ? value : ZERO), - PersistentColliderDto::getLocalPosition) - .addValidator(PhysicsStorePersistenceValidation.finiteVector( - "Persisted collider local position must be finite")) - .add() - .append(new KeyedCodec<>("LocalRotation", ImpulseCodecs.QUATERNIONF, false), - (dto, value) -> dto.localRotation.set(value != null ? value : IDENTITY), - PersistentColliderDto::getLocalRotation) - .add() - .append(new KeyedCodec<>("Sensor", Codec.BOOLEAN, false), - (dto, value) -> dto.sensor = value != null && value, - PersistentColliderDto::isSensor) - .add() - .append(new KeyedCodec<>("CollisionGroup", Codec.INTEGER, false), - (dto, value) -> dto.collisionGroup = value != null ? value : 0, - PersistentColliderDto::getCollisionGroup) - .add() - .append(new KeyedCodec<>("CollisionMask", Codec.INTEGER, false), - (dto, value) -> dto.collisionMask = value != null ? value : -1, - PersistentColliderDto::getCollisionMask) - .add() - .build(); - - @Nonnull - private UUID colliderUuid = new UUID(0L, 0L); - @Nonnull - private UUID bodyUuid = new UUID(0L, 0L); - @Nonnull - private UUID shapeUuid = new UUID(0L, 0L); - @Nonnull - private UUID materialUuid = new UUID(0L, 0L); - @Nonnull - private final Vector3f localPosition = new Vector3f(); - @Nonnull - private final Quaternionf localRotation = new Quaternionf(); - private boolean sensor; - private int collisionGroup; - private int collisionMask = -1; - - public PersistentColliderDto() { - } - - public PersistentColliderDto(@Nonnull UUID colliderUuid, - @Nonnull UUID bodyUuid, - @Nonnull UUID shapeUuid, - @Nonnull UUID materialUuid, - @Nonnull Vector3f localPosition, - @Nonnull Quaternionf localRotation, - boolean sensor, - int collisionGroup, - int collisionMask) { - this.colliderUuid = Objects.requireNonNull(colliderUuid, "colliderUuid"); - this.bodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); - this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); - this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); - this.localPosition.set(Objects.requireNonNull(localPosition, "localPosition")); - this.localRotation.set(Objects.requireNonNull(localRotation, "localRotation")); - this.sensor = sensor; - this.collisionGroup = collisionGroup; - this.collisionMask = collisionMask; - } - - @Nonnull - public UUID getColliderUuid() { - return colliderUuid; - } - - @Nonnull - public UUID getBodyUuid() { - return bodyUuid; - } - - @Nonnull - public UUID getShapeUuid() { - return shapeUuid; - } - - @Nonnull - public UUID getMaterialUuid() { - return materialUuid; - } - - @Nonnull - public Vector3f getLocalPosition() { - return new Vector3f(localPosition); - } - - @Nonnull - public Quaternionf getLocalRotation() { - return new Quaternionf(localRotation); - } - - public boolean isSensor() { - return sensor; - } - - public int getCollisionGroup() { - return collisionGroup; - } - - public int getCollisionMask() { - return collisionMask; - } - - @Nonnull - public PersistentColliderDto copy() { - return new PersistentColliderDto(colliderUuid, - bodyUuid, - shapeUuid, - materialUuid, - localPosition, - localRotation, - sensor, - collisionGroup, - collisionMask); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java deleted file mode 100644 index 4902aec3..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentJointDto.java +++ /dev/null @@ -1,280 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.core.plugin.components.JointType; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -public final class PersistentJointDto { - - private static final Vector3f ZERO = new Vector3f(); - - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentJointDto.class, PersistentJointDto::new) - .append(new KeyedCodec<>("JointUuid", Codec.UUID_BINARY), - (dto, value) -> dto.jointUuid = value, - PersistentJointDto::getJointUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), - (dto, value) -> dto.spaceUuid = value, - PersistentJointDto::getSpaceUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BodyAUuid", Codec.UUID_BINARY), - (dto, value) -> dto.bodyAUuid = value, - PersistentJointDto::getBodyAUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BodyBUuid", Codec.UUID_BINARY), - (dto, value) -> dto.bodyBUuid = value, - PersistentJointDto::getBodyBUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Type", new EnumCodec<>(JointType.class), false), - (dto, value) -> dto.type = value != null ? value : JointType.FIXED, - PersistentJointDto::getType) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("AnchorA", Vector3fUtil.CODEC, false), - (dto, value) -> dto.anchorA.set(value != null ? value : ZERO), - PersistentJointDto::getAnchorA) - .addValidator(PhysicsStorePersistenceValidation.finiteVector( - "Persisted joint anchor A must be finite")) - .add() - .append(new KeyedCodec<>("AnchorB", Vector3fUtil.CODEC, false), - (dto, value) -> dto.anchorB.set(value != null ? value : ZERO), - PersistentJointDto::getAnchorB) - .addValidator(PhysicsStorePersistenceValidation.finiteVector( - "Persisted joint anchor B must be finite")) - .add() - .append(new KeyedCodec<>("Axis", Vector3fUtil.CODEC, false), - (dto, value) -> dto.axis.set(value != null ? value : ZERO), - PersistentJointDto::getAxis) - .addValidator(PhysicsStorePersistenceValidation.finiteVector( - "Persisted joint axis must be finite")) - .add() - .append(new KeyedCodec<>("LowerLimit", Codec.FLOAT, false), - (dto, value) -> dto.lowerLimit = value != null ? value : 0.0f, - PersistentJointDto::getLowerLimit) - .addValidator(PhysicsStorePersistenceValidation.finiteFloat( - "Persisted joint lower limit must be finite")) - .add() - .append(new KeyedCodec<>("UpperLimit", Codec.FLOAT, false), - (dto, value) -> dto.upperLimit = value != null ? value : 0.0f, - PersistentJointDto::getUpperLimit) - .addValidator(PhysicsStorePersistenceValidation.finiteFloat( - "Persisted joint upper limit must be finite")) - .add() - .append(new KeyedCodec<>("Enabled", Codec.BOOLEAN, false), - (dto, value) -> dto.enabled = value == null || value, - PersistentJointDto::isEnabled) - .add() - .append(new KeyedCodec<>("MotorEnabled", Codec.BOOLEAN, false), - (dto, value) -> dto.motorEnabled = value != null && value, - PersistentJointDto::isMotorEnabled) - .add() - .append(new KeyedCodec<>("MotorTargetVelocity", Codec.FLOAT, false), - (dto, value) -> dto.motorTargetVelocity = value != null ? value : 0.0f, - PersistentJointDto::getMotorTargetVelocity) - .addValidator(PhysicsStorePersistenceValidation.finiteFloat( - "Persisted joint motor target velocity must be finite")) - .add() - .append(new KeyedCodec<>("MotorMaxForce", Codec.FLOAT, false), - (dto, value) -> dto.motorMaxForce = value != null ? value : 0.0f, - PersistentJointDto::getMotorMaxForce) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted joint motor max force must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("SpringRestLength", Codec.FLOAT, false), - (dto, value) -> dto.springRestLength = value != null ? value : 0.0f, - PersistentJointDto::getSpringRestLength) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted joint spring rest length must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("SpringStiffness", Codec.FLOAT, false), - (dto, value) -> dto.springStiffness = value != null ? value : 0.0f, - PersistentJointDto::getSpringStiffness) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted joint spring stiffness must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("SpringDamping", Codec.FLOAT, false), - (dto, value) -> dto.springDamping = value != null ? value : 0.0f, - PersistentJointDto::getSpringDamping) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted joint spring damping must be finite and >= 0")) - .add() - .build(); - - @Nonnull - private UUID jointUuid = new UUID(0L, 0L); - @Nonnull - private UUID spaceUuid = new UUID(0L, 0L); - @Nonnull - private UUID bodyAUuid = new UUID(0L, 0L); - @Nonnull - private UUID bodyBUuid = new UUID(0L, 0L); - @Nonnull - private JointType type = JointType.FIXED; - @Nonnull - private final Vector3f anchorA = new Vector3f(); - @Nonnull - private final Vector3f anchorB = new Vector3f(); - @Nonnull - private final Vector3f axis = new Vector3f(); - private float lowerLimit; - private float upperLimit; - private boolean enabled = true; - private boolean motorEnabled; - private float motorTargetVelocity; - private float motorMaxForce; - private float springRestLength; - private float springStiffness; - private float springDamping; - - public PersistentJointDto() { - } - - public PersistentJointDto(@Nonnull UUID jointUuid, - @Nonnull UUID spaceUuid, - @Nonnull UUID bodyAUuid, - @Nonnull UUID bodyBUuid, - @Nonnull JointType type, - @Nonnull Vector3f anchorA, - @Nonnull Vector3f anchorB, - @Nonnull Vector3f axis, - float lowerLimit, - float upperLimit, - boolean enabled, - boolean motorEnabled, - float motorTargetVelocity, - float motorMaxForce, - float springRestLength, - float springStiffness, - float springDamping) { - this.jointUuid = Objects.requireNonNull(jointUuid, "jointUuid"); - this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.bodyAUuid = Objects.requireNonNull(bodyAUuid, "bodyAUuid"); - this.bodyBUuid = Objects.requireNonNull(bodyBUuid, "bodyBUuid"); - this.type = Objects.requireNonNull(type, "type"); - this.anchorA.set(Objects.requireNonNull(anchorA, "anchorA")); - this.anchorB.set(Objects.requireNonNull(anchorB, "anchorB")); - this.axis.set(Objects.requireNonNull(axis, "axis")); - this.lowerLimit = lowerLimit; - this.upperLimit = upperLimit; - this.enabled = enabled; - this.motorEnabled = motorEnabled; - this.motorTargetVelocity = motorTargetVelocity; - this.motorMaxForce = motorMaxForce; - this.springRestLength = springRestLength; - this.springStiffness = springStiffness; - this.springDamping = springDamping; - } - - @Nonnull - public UUID getJointUuid() { - return jointUuid; - } - - @Nonnull - public UUID getSpaceUuid() { - return spaceUuid; - } - - @Nonnull - public UUID getBodyAUuid() { - return bodyAUuid; - } - - @Nonnull - public UUID getBodyBUuid() { - return bodyBUuid; - } - - @Nonnull - public JointType getType() { - return type; - } - - @Nonnull - public Vector3f getAnchorA() { - return new Vector3f(anchorA); - } - - @Nonnull - public Vector3f getAnchorB() { - return new Vector3f(anchorB); - } - - @Nonnull - public Vector3f getAxis() { - return new Vector3f(axis); - } - - public float getLowerLimit() { - return lowerLimit; - } - - public float getUpperLimit() { - return upperLimit; - } - - public boolean isEnabled() { - return enabled; - } - - public boolean isMotorEnabled() { - return motorEnabled; - } - - public float getMotorTargetVelocity() { - return motorTargetVelocity; - } - - public float getMotorMaxForce() { - return motorMaxForce; - } - - public float getSpringRestLength() { - return springRestLength; - } - - public float getSpringStiffness() { - return springStiffness; - } - - public float getSpringDamping() { - return springDamping; - } - - @Nonnull - public PersistentJointDto copy() { - PersistentJointDto copy = new PersistentJointDto(); - copy.jointUuid = Objects.requireNonNull(jointUuid, "jointUuid"); - copy.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - copy.bodyAUuid = Objects.requireNonNull(bodyAUuid, "bodyAUuid"); - copy.bodyBUuid = Objects.requireNonNull(bodyBUuid, "bodyBUuid"); - copy.type = type; - copy.anchorA.set(anchorA); - copy.anchorB.set(anchorB); - copy.axis.set(axis); - copy.lowerLimit = lowerLimit; - copy.upperLimit = upperLimit; - copy.enabled = enabled; - copy.motorEnabled = motorEnabled; - copy.motorTargetVelocity = motorTargetVelocity; - copy.motorMaxForce = motorMaxForce; - copy.springRestLength = springRestLength; - copy.springStiffness = springStiffness; - copy.springDamping = springDamping; - return copy; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentMaterialDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentMaterialDto.java deleted file mode 100644 index 169a5c8b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentMaterialDto.java +++ /dev/null @@ -1,66 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.validation.Validators; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -public final class PersistentMaterialDto { - - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentMaterialDto.class, PersistentMaterialDto::new) - .append(new KeyedCodec<>("MaterialUuid", Codec.UUID_BINARY), - (dto, value) -> dto.materialUuid = value, - PersistentMaterialDto::getMaterialUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Friction", Codec.FLOAT, false), - (dto, value) -> dto.friction = value != null ? value : 0.5f, - PersistentMaterialDto::getFriction) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted material friction must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("Restitution", Codec.FLOAT, false), - (dto, value) -> dto.restitution = value != null ? value : 0.0f, - PersistentMaterialDto::getRestitution) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted material restitution must be finite and >= 0")) - .add() - .build(); - - @Nonnull - private UUID materialUuid = new UUID(0L, 0L); - private float friction = 0.5f; - private float restitution; - - public PersistentMaterialDto() { - } - - public PersistentMaterialDto(@Nonnull UUID materialUuid, float friction, float restitution) { - this.materialUuid = Objects.requireNonNull(materialUuid, "materialUuid"); - this.friction = friction; - this.restitution = restitution; - } - - @Nonnull - public UUID getMaterialUuid() { - return materialUuid; - } - - public float getFriction() { - return friction; - } - - public float getRestitution() { - return restitution; - } - - @Nonnull - public PersistentMaterialDto copy() { - return new PersistentMaterialDto(materialUuid, friction, restitution); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java deleted file mode 100644 index 21290413..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStorePreflight.java +++ /dev/null @@ -1,266 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import javax.annotation.Nonnull; - -/** - * Preflight validation for persisted PhysicsStore DTOs before backend mutation. - */ -public final class PersistentPhysicsStorePreflight { - - private static final UUID NIL_UUID = new UUID(0L, 0L); - - private PersistentPhysicsStorePreflight() { - } - - @Nonnull - public static Result validate(@Nonnull PersistentPhysicsStoreResource resource) { - List errors = new ArrayList<>(); - if (resource.getSchemaVersion() != PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION) { - errors.add("Malformed PhysicsStore schema version: " + resource.getSchemaVersion()); - } - - Set spaces = collectSpaces(resource.getSpaces(), errors); - Set bodies = collectBodies(resource.getBodies(), spaces, errors); - Set shapes = collectShapes(resource.getShapes(), errors); - Set materials = collectMaterials(resource.getMaterials(), errors); - Set colliders = collectColliders(resource.getColliders(), - bodies, - shapes, - materials, - errors); - validateBodyColliderRefs(resource.getBodies(), colliders, errors); - validateJoints(resource.getJoints(), spaces, bodies, errors); - return new Result(errors.isEmpty(), errors); - } - - @Nonnull - private static Set collectSpaces(@Nonnull PersistentSpaceDto[] spaces, - @Nonnull List errors) { - Set seen = new HashSet<>(); - for (PersistentSpaceDto space : spaces) { - UUID uuid = space.getSpaceUuid(); - requireUuid("space", uuid, errors); - if (!seen.add(uuid)) { - errors.add("Duplicate PhysicsStore space UUID " + uuid); - } - if (space.getBackendId().isBlank()) { - errors.add("PhysicsStore space " + uuid + " has blank backend id"); - } else { - try { - Impulse.getRuntimeProvider(new BackendId(space.getBackendId())); - } catch (RuntimeException exception) { - errors.add("PhysicsStore space " + uuid - + " references unavailable backend id " + space.getBackendId()); - } - } - if (!PhysicsStorePersistenceValidation.isFinite(space.getGravity())) { - errors.add("PhysicsStore space " + uuid + " has non-finite gravity"); - } - if (space.getRadius() < 1 - || space.getRadius() - > PhysicsChunkCollisionSettings.MAX_RADIUS) { - errors.add("PhysicsStore space " + uuid - + " has invalid PhysicsChunk collision radius"); - } - if (space.getBodyRadius() < 1 - || space.getBodyRadius() - > PhysicsChunkCollisionSettings.MAX_BODY_RADIUS) { - errors.add("PhysicsStore space " + uuid - + " has invalid PhysicsChunk collision body radius"); - } - if (space.getTtlTicks() < 1 - || space.getTtlTicks() - > PhysicsChunkCollisionSettings.MAX_TTL_TICKS) { - errors.add("PhysicsStore space " + uuid - + " has invalid PhysicsChunk collision TTL"); - } - if (!Float.isFinite(space.getChunkCollisionFriction()) - || space.getChunkCollisionFriction() < 0.0f) { - errors.add("PhysicsStore space " + uuid - + " has invalid chunk collision friction"); - } - if (!Float.isFinite(space.getChunkCollisionRestitution()) - || space.getChunkCollisionRestitution() < 0.0f) { - errors.add("PhysicsStore space " + uuid - + " has invalid chunk collision restitution"); - } - try { - validateSpaceComponents(space); - } catch (RuntimeException exception) { - errors.add("PhysicsStore space " + uuid + " has invalid space settings: " - + exception.getMessage()); - } - } - return seen; - } - - private static void validateSpaceComponents(@Nonnull PersistentSpaceDto space) { - space.getChunkCollisionSettings(); - space.getChunkCollisionMaterial(); - space.getChunkCollisionFilter(); - space.getSolverSettings(); - space.getVisualSyncSettings(); - space.getVisualMaterializationSettings(); - space.getCollisionLodSettings(); - space.getExtensionSettings(); - } - - @Nonnull - private static Set collectBodies(@Nonnull PersistentBodyDto[] bodies, - @Nonnull Set spaces, - @Nonnull List errors) { - Set seen = new HashSet<>(); - for (PersistentBodyDto body : bodies) { - UUID uuid = body.getBodyUuid(); - requireUuid("body", uuid, errors); - if (!seen.add(uuid)) { - errors.add("Duplicate PhysicsStore body UUID " + uuid); - } - if (!spaces.contains(body.getSpaceUuid())) { - errors.add("Body " + uuid + " references missing space " + body.getSpaceUuid()); - } - if (!Float.isFinite(body.getMass()) || body.getMass() < 0.0f) { - errors.add("Body " + uuid + " has invalid mass"); - } - if (!Float.isFinite(body.getLinearDamping()) || body.getLinearDamping() < 0.0f) { - errors.add("Body " + uuid + " has invalid linear damping"); - } - if (!Float.isFinite(body.getAngularDamping()) || body.getAngularDamping() < 0.0f) { - errors.add("Body " + uuid + " has invalid angular damping"); - } - PersistentBodyRuntimeStateDto runtime = body.getRuntimeState(); - if (!PhysicsStorePersistenceValidation.isFinite(runtime.getPosition()) - || !PhysicsStorePersistenceValidation.isFinite(runtime.getRotation()) - || !PhysicsStorePersistenceValidation.isFinite(runtime.getLinearVelocity()) - || !PhysicsStorePersistenceValidation.isFinite(runtime.getAngularVelocity())) { - errors.add("Body " + uuid + " has non-finite runtime state"); - } - } - return seen; - } - - @Nonnull - private static Set collectShapes(@Nonnull PersistentShapeDto[] shapes, - @Nonnull List errors) { - Set seen = new HashSet<>(); - for (PersistentShapeDto shape : shapes) { - UUID uuid = shape.getShapeUuid(); - requireUuid("shape", uuid, errors); - if (!seen.add(uuid)) { - errors.add("Duplicate PhysicsStore shape UUID " + uuid); - } - if (!Float.isFinite(shape.getGroundY())) { - errors.add("Shape " + uuid + " has non-finite groundY"); - } - } - return seen; - } - - @Nonnull - private static Set collectMaterials(@Nonnull PersistentMaterialDto[] materials, - @Nonnull List errors) { - Set seen = new HashSet<>(); - for (PersistentMaterialDto material : materials) { - UUID uuid = material.getMaterialUuid(); - requireUuid("material", uuid, errors); - if (!seen.add(uuid)) { - errors.add("Duplicate PhysicsStore material UUID " + uuid); - } - if (!Float.isFinite(material.getFriction()) || material.getFriction() < 0.0f) { - errors.add("Material " + uuid + " has invalid friction"); - } - if (!Float.isFinite(material.getRestitution()) || material.getRestitution() < 0.0f) { - errors.add("Material " + uuid + " has invalid restitution"); - } - } - return seen; - } - - @Nonnull - private static Set collectColliders(@Nonnull PersistentColliderDto[] colliders, - @Nonnull Set bodies, - @Nonnull Set shapes, - @Nonnull Set materials, - @Nonnull List errors) { - Set seen = new HashSet<>(); - for (PersistentColliderDto collider : colliders) { - UUID uuid = collider.getColliderUuid(); - requireUuid("collider", uuid, errors); - if (!seen.add(uuid)) { - errors.add("Duplicate PhysicsStore collider UUID " + uuid); - } - if (!bodies.contains(collider.getBodyUuid())) { - errors.add("Collider " + uuid + " references missing body " - + collider.getBodyUuid()); - } - if (!shapes.contains(collider.getShapeUuid())) { - errors.add("Collider " + uuid + " references missing shape " - + collider.getShapeUuid()); - } - if (!materials.contains(collider.getMaterialUuid())) { - errors.add("Collider " + uuid + " references missing material " - + collider.getMaterialUuid()); - } - } - return seen; - } - - private static void validateBodyColliderRefs(@Nonnull PersistentBodyDto[] bodies, - @Nonnull Set colliders, - @Nonnull List errors) { - for (PersistentBodyDto body : bodies) { - for (UUID colliderUuid : body.getColliderUuids()) { - if (!colliders.contains(colliderUuid)) { - errors.add("Body " + body.getBodyUuid() - + " references missing collider " + colliderUuid); - } - } - } - } - - private static void validateJoints(@Nonnull PersistentJointDto[] joints, - @Nonnull Set spaces, - @Nonnull Set bodies, - @Nonnull List errors) { - Set seen = new HashSet<>(); - for (PersistentJointDto joint : joints) { - UUID uuid = joint.getJointUuid(); - requireUuid("joint", uuid, errors); - if (!seen.add(uuid)) { - errors.add("Duplicate PhysicsStore joint UUID " + uuid); - } - if (!spaces.contains(joint.getSpaceUuid())) { - errors.add("Joint " + uuid + " references missing space " - + joint.getSpaceUuid()); - } - } - } - - private static void requireUuid(@Nonnull String kind, - @Nonnull UUID uuid, - @Nonnull List errors) { - if (NIL_UUID.equals(uuid)) { - errors.add("PhysicsStore " + kind + " UUID cannot be nil"); - } - } - - public record Result(boolean valid, @Nonnull List errors) { - - public Result { - errors = List.copyOf(errors); - } - - @Nonnull - public static Result success() { - return new Result(true, List.of()); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java deleted file mode 100644 index 262f8c26..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreResource.java +++ /dev/null @@ -1,245 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; -import lombok.Getter; -import java.util.Arrays; -import javax.annotation.Nonnull; - -/** - * Canonical compact DTO persistence for PhysicsStore entities. - */ -public final class PersistentPhysicsStoreResource implements Resource { - - public static final int CURRENT_SCHEMA_VERSION = 2; - private static final PersistentSpaceDto[] EMPTY_SPACES = new PersistentSpaceDto[0]; - private static final PersistentBodyDto[] EMPTY_BODIES = new PersistentBodyDto[0]; - private static final PersistentColliderDto[] EMPTY_COLLIDERS = new PersistentColliderDto[0]; - private static final PersistentShapeDto[] EMPTY_SHAPES = new PersistentShapeDto[0]; - private static final PersistentMaterialDto[] EMPTY_MATERIALS = new PersistentMaterialDto[0]; - private static final PersistentJointDto[] EMPTY_JOINTS = new PersistentJointDto[0]; - - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentPhysicsStoreResource.class, - PersistentPhysicsStoreResource::new) - .append(new KeyedCodec<>("SchemaVersion", Codec.INTEGER, false), - PersistentPhysicsStoreResource::setSchemaVersion, - PersistentPhysicsStoreResource::getSchemaVersion) - .addValidator(Validators.nonNull()) - .addValidator(Validators.range(CURRENT_SCHEMA_VERSION, CURRENT_SCHEMA_VERSION)) - .add() - .append(new KeyedCodec<>("Spaces", - new ArrayCodec<>(PersistentSpaceDto.CODEC, PersistentSpaceDto[]::new), - false), - (resource, value) -> resource.spaces = copySpaces(value), - PersistentPhysicsStoreResource::getSpaces) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("Bodies", - new ArrayCodec<>(PersistentBodyDto.CODEC, PersistentBodyDto[]::new), - false), - (resource, value) -> resource.bodies = copyBodies(value), - PersistentPhysicsStoreResource::getBodies) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("Colliders", - new ArrayCodec<>(PersistentColliderDto.CODEC, PersistentColliderDto[]::new), - false), - (resource, value) -> resource.colliders = copyColliders(value), - PersistentPhysicsStoreResource::getColliders) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("Shapes", - new ArrayCodec<>(PersistentShapeDto.CODEC, PersistentShapeDto[]::new), - false), - (resource, value) -> resource.shapes = copyShapes(value), - PersistentPhysicsStoreResource::getShapes) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("Materials", - new ArrayCodec<>(PersistentMaterialDto.CODEC, PersistentMaterialDto[]::new), - false), - (resource, value) -> resource.materials = copyMaterials(value), - PersistentPhysicsStoreResource::getMaterials) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .append(new KeyedCodec<>("Joints", - new ArrayCodec<>(PersistentJointDto.CODEC, PersistentJointDto[]::new), - false), - (resource, value) -> resource.joints = copyJoints(value), - PersistentPhysicsStoreResource::getJoints) - .addValidator(Validators.nonNull()) - .addValidator(Validators.nonNullArrayElements()) - .add() - .build(); - - @Getter - private int schemaVersion = CURRENT_SCHEMA_VERSION; - @Nonnull - private PersistentSpaceDto[] spaces = EMPTY_SPACES; - @Nonnull - private PersistentBodyDto[] bodies = EMPTY_BODIES; - @Nonnull - private PersistentColliderDto[] colliders = EMPTY_COLLIDERS; - @Nonnull - private PersistentShapeDto[] shapes = EMPTY_SHAPES; - @Nonnull - private PersistentMaterialDto[] materials = EMPTY_MATERIALS; - @Nonnull - private PersistentJointDto[] joints = EMPTY_JOINTS; - - public PersistentPhysicsStoreResource() { - } - - public void setSchemaVersion(int schemaVersion) { - if (schemaVersion != CURRENT_SCHEMA_VERSION) { - throw new IllegalArgumentException("Schema version must be " - + CURRENT_SCHEMA_VERSION); - } - this.schemaVersion = schemaVersion; - } - - @Nonnull - public PersistentSpaceDto[] getSpaces() { - return copySpaces(spaces); - } - - public void setSpaces(@Nonnull PersistentSpaceDto[] spaces) { - this.spaces = copySpaces(spaces); - } - - @Nonnull - public PersistentBodyDto[] getBodies() { - return copyBodies(bodies); - } - - public void setBodies(@Nonnull PersistentBodyDto[] bodies) { - this.bodies = copyBodies(bodies); - } - - @Nonnull - public PersistentColliderDto[] getColliders() { - return copyColliders(colliders); - } - - public void setColliders(@Nonnull PersistentColliderDto[] colliders) { - this.colliders = copyColliders(colliders); - } - - @Nonnull - public PersistentShapeDto[] getShapes() { - return copyShapes(shapes); - } - - public void setShapes(@Nonnull PersistentShapeDto[] shapes) { - this.shapes = copyShapes(shapes); - } - - @Nonnull - public PersistentMaterialDto[] getMaterials() { - return copyMaterials(materials); - } - - public void setMaterials(@Nonnull PersistentMaterialDto[] materials) { - this.materials = copyMaterials(materials); - } - - @Nonnull - public PersistentJointDto[] getJoints() { - return copyJoints(joints); - } - - public void setJoints(@Nonnull PersistentJointDto[] joints) { - this.joints = copyJoints(joints); - } - - @Nonnull - public PersistentPhysicsStorePreflight.Result preflight() { - return PersistentPhysicsStorePreflight.validate(this); - } - - @Nonnull - @Override - public PersistentPhysicsStoreResource clone() { - PersistentPhysicsStoreResource copy = new PersistentPhysicsStoreResource(); - copy.schemaVersion = schemaVersion; - copy.spaces = copySpaces(spaces); - copy.bodies = copyBodies(bodies); - copy.colliders = copyColliders(colliders); - copy.shapes = copyShapes(shapes); - copy.materials = copyMaterials(materials); - copy.joints = copyJoints(joints); - return copy; - } - - @Nonnull - public static ResourceType getResourceType() { - return PhysicsResourceTypes.persistentStoreResourceType(); - } - - @Nonnull - private static PersistentSpaceDto[] copySpaces(PersistentSpaceDto[] values) { - if (values == null || values.length == 0) { - return EMPTY_SPACES; - } - return Arrays.stream(values).map(PersistentSpaceDto::copy).toArray(PersistentSpaceDto[]::new); - } - - @Nonnull - private static PersistentBodyDto[] copyBodies(PersistentBodyDto[] values) { - if (values == null || values.length == 0) { - return EMPTY_BODIES; - } - return Arrays.stream(values).map(PersistentBodyDto::copy).toArray(PersistentBodyDto[]::new); - } - - @Nonnull - private static PersistentColliderDto[] copyColliders(PersistentColliderDto[] values) { - if (values == null || values.length == 0) { - return EMPTY_COLLIDERS; - } - return Arrays.stream(values).map(PersistentColliderDto::copy) - .toArray(PersistentColliderDto[]::new); - } - - @Nonnull - private static PersistentShapeDto[] copyShapes(PersistentShapeDto[] values) { - if (values == null || values.length == 0) { - return EMPTY_SHAPES; - } - return Arrays.stream(values).map(PersistentShapeDto::copy) - .toArray(PersistentShapeDto[]::new); - } - - @Nonnull - private static PersistentMaterialDto[] copyMaterials(PersistentMaterialDto[] values) { - if (values == null || values.length == 0) { - return EMPTY_MATERIALS; - } - return Arrays.stream(values).map(PersistentMaterialDto::copy) - .toArray(PersistentMaterialDto[]::new); - } - - @Nonnull - private static PersistentJointDto[] copyJoints(PersistentJointDto[] values) { - if (values == null || values.length == 0) { - return EMPTY_JOINTS; - } - return Arrays.stream(values).map(PersistentJointDto::copy) - .toArray(PersistentJointDto[]::new); - } - -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java deleted file mode 100644 index f34008d0..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentPhysicsStoreStorage.java +++ /dev/null @@ -1,63 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import com.hypixel.hytale.server.core.util.BsonUtil; -import java.nio.file.Path; -import java.util.concurrent.CompletionException; -import javax.annotation.Nonnull; -import org.bson.BsonDocument; - -/** - * Compatibility reader for legacy DTO resource files. - */ -public final class PersistentPhysicsStoreStorage { - - private static final String RESOURCE_DIRECTORY = "resources"; - private static final String FILE_NAME = "PersistentPhysicsStore.json"; - - private PersistentPhysicsStoreStorage() { - } - - @Nonnull - public static LoadResult load(@Nonnull Store store) { - Path file = file(store.getExternalData()); - if (file == null) { - return LoadResult.missing(); - } - BsonDocument document; - try { - document = BsonUtil.readDocument(file).join(); - } catch (CompletionException exception) { - throw new IllegalStateException("Could not read legacy PhysicsStore DTO storage: " - + file, exception.getCause() != null ? exception.getCause() : exception); - } - if (document == null) { - return LoadResult.missing(); - } - PersistentPhysicsStoreResource resource = PersistentPhysicsStoreResource.CODEC.decode( - document, - new ExtraInfo()); - if (resource == null) { - throw new IllegalStateException("Legacy PhysicsStore DTO storage decoded to null: " - + file); - } - return new LoadResult(true, resource); - } - - @Nonnull - static Path file(@Nonnull PhysicsStore physicsStore) { - Path savePath = physicsStore.getWorld().getSavePath(); - return savePath.resolve(RESOURCE_DIRECTORY).resolve(FILE_NAME); - } - - public record LoadResult(boolean present, - @Nonnull PersistentPhysicsStoreResource resource) { - - @Nonnull - private static LoadResult missing() { - return new LoadResult(false, new PersistentPhysicsStoreResource()); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentShapeDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentShapeDto.java deleted file mode 100644 index 7f57dccb..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentShapeDto.java +++ /dev/null @@ -1,173 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.validation.Validators; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.ShapeType; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; - -public final class PersistentShapeDto { - - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentShapeDto.class, PersistentShapeDto::new) - .append(new KeyedCodec<>("ShapeUuid", Codec.UUID_BINARY), - (dto, value) -> dto.shapeUuid = value, - PersistentShapeDto::getShapeUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("ShapeType", new EnumCodec<>(ShapeType.class)), - (dto, value) -> dto.shapeType = value, - PersistentShapeDto::getShapeType) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("HalfExtentX", Codec.FLOAT, false), - (dto, value) -> dto.halfExtentX = value != null ? value : 0.0f, - PersistentShapeDto::getHalfExtentX) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted shape half extent X must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("HalfExtentY", Codec.FLOAT, false), - (dto, value) -> dto.halfExtentY = value != null ? value : 0.0f, - PersistentShapeDto::getHalfExtentY) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted shape half extent Y must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("HalfExtentZ", Codec.FLOAT, false), - (dto, value) -> dto.halfExtentZ = value != null ? value : 0.0f, - PersistentShapeDto::getHalfExtentZ) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted shape half extent Z must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("Radius", Codec.FLOAT, false), - (dto, value) -> dto.radius = value != null ? value : 0.0f, - PersistentShapeDto::getRadius) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted shape radius must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("HalfHeight", Codec.FLOAT, false), - (dto, value) -> dto.halfHeight = value != null ? value : 0.0f, - PersistentShapeDto::getHalfHeight) - .addValidator(PhysicsStorePersistenceValidation.nonNegativeFiniteFloat( - "Persisted shape half height must be finite and >= 0")) - .add() - .append(new KeyedCodec<>("Axis", new EnumCodec<>(PhysicsAxis.class), false), - (dto, value) -> dto.axis = value != null ? value : PhysicsAxis.Y, - PersistentShapeDto::getAxis) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("GroundY", Codec.FLOAT, false), - (dto, value) -> dto.groundY = value != null ? value : 0.0f, - PersistentShapeDto::getGroundY) - .addValidator(PhysicsStorePersistenceValidation.finiteFloat( - "Persisted shape ground Y must be finite")) - .add() - .append(new KeyedCodec<>("ResourceKey", Codec.STRING, false), - (dto, value) -> dto.resourceKey = value != null ? value : "", - PersistentShapeDto::getResourceKey) - .add() - .build(); - - @Nonnull - private UUID shapeUuid = new UUID(0L, 0L); - @Nonnull - private ShapeType shapeType = ShapeType.BOX; - private float halfExtentX; - private float halfExtentY; - private float halfExtentZ; - private float radius; - private float halfHeight; - @Nonnull - private PhysicsAxis axis = PhysicsAxis.Y; - private float groundY; - @Nonnull - private String resourceKey = ""; - - public PersistentShapeDto() { - } - - public PersistentShapeDto(@Nonnull UUID shapeUuid, - @Nonnull ShapeType shapeType, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - @Nonnull PhysicsAxis axis, - float groundY, - @Nonnull String resourceKey) { - this.shapeUuid = Objects.requireNonNull(shapeUuid, "shapeUuid"); - this.shapeType = Objects.requireNonNull(shapeType, "shapeType"); - this.halfExtentX = halfExtentX; - this.halfExtentY = halfExtentY; - this.halfExtentZ = halfExtentZ; - this.radius = radius; - this.halfHeight = halfHeight; - this.axis = Objects.requireNonNull(axis, "axis"); - this.groundY = groundY; - this.resourceKey = Objects.requireNonNull(resourceKey, "resourceKey"); - } - - @Nonnull - public UUID getShapeUuid() { - return shapeUuid; - } - - @Nonnull - public ShapeType getShapeType() { - return shapeType; - } - - public float getHalfExtentX() { - return halfExtentX; - } - - public float getHalfExtentY() { - return halfExtentY; - } - - public float getHalfExtentZ() { - return halfExtentZ; - } - - public float getRadius() { - return radius; - } - - public float getHalfHeight() { - return halfHeight; - } - - @Nonnull - public PhysicsAxis getAxis() { - return axis; - } - - public float getGroundY() { - return groundY; - } - - @Nonnull - public String getResourceKey() { - return resourceKey; - } - - @Nonnull - public PersistentShapeDto copy() { - return new PersistentShapeDto(shapeUuid, - shapeType, - halfExtentX, - halfExtentY, - halfExtentZ, - radius, - halfHeight, - axis, - groundY, - resourceKey); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java deleted file mode 100644 index 609ab17f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDto.java +++ /dev/null @@ -1,457 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.codec.validation.Validators; -import com.hypixel.hytale.math.vector.Vector3fUtil; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import java.util.Objects; -import java.util.UUID; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -public final class PersistentSpaceDto { - - @Nonnull - public static final BuilderCodec CODEC = - BuilderCodec.builder(PersistentSpaceDto.class, PersistentSpaceDto::new) - .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY), - (dto, value) -> dto.spaceUuid = value, - PersistentSpaceDto::getSpaceUuid) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("BackendId", Codec.STRING), - (dto, value) -> dto.backendId = value, - PersistentSpaceDto::getBackendId) - .addValidator(Validators.nonNull()) - .add() - .append(new KeyedCodec<>("Gravity", Vector3fUtil.CODEC), - (dto, value) -> dto.gravity.set(value), - PersistentSpaceDto::getGravity) - .addValidator(Validators.nonNull()) - .addValidator(PhysicsStorePersistenceValidation.finiteVector( - "Persisted PhysicsStore space gravity must be finite")) - .add() - .append(new KeyedCodec<>("PhysicsChunkTerrainMode", - new EnumCodec<>(PhysicsChunkCollisionMode.class), - false), - (dto, value) -> dto.chunkCollisionMode = value != null - ? value - : PhysicsChunkCollisionMode.NONE, - PersistentSpaceDto::getMode) - .add() - .append(new KeyedCodec<>("EntityChunkBoundaryMode", - new EnumCodec<>(EntityChunkBoundaryMode.class), - false), - (dto, value) -> dto.entityChunkBoundaryMode = value != null - ? value - : PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - PersistentSpaceDto::getEntityChunkBoundaryMode) - .add() - .append(new KeyedCodec<>("NativeVoxelCollision", Codec.BOOLEAN, false), - (dto, value) -> dto.nativeVoxelCollisionEnabled = value != null && value, - PersistentSpaceDto::isNativeVoxelCollisionEnabled) - .add() - .append(new KeyedCodec<>("ChunkCollisionRadius", Codec.INTEGER, false), - (dto, value) -> dto.chunkCollisionRadius = value != null - ? value - : PhysicsChunkCollisionSettings.DEFAULT_RADIUS, - PersistentSpaceDto::getRadius) - .add() - .append(new KeyedCodec<>("BodyChunkCollisionRadius", Codec.INTEGER, false), - (dto, value) -> dto.bodyChunkCollisionRadius = value != null - ? value - : PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, - PersistentSpaceDto::getBodyRadius) - .add() - .append(new KeyedCodec<>("ChunkCollisionTtlTicks", Codec.INTEGER, false), - (dto, value) -> dto.chunkCollisionTtlTicks = value != null - ? value - : PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, - PersistentSpaceDto::getTtlTicks) - .add() - .append(new KeyedCodec<>("ChunkCollisionFriction", Codec.FLOAT, false), - (dto, value) -> dto.chunkCollisionFriction = value != null - ? value - : PhysicsChunkCollisionDefaults.FRICTION, - PersistentSpaceDto::getChunkCollisionFriction) - .add() - .append(new KeyedCodec<>("ChunkCollisionRestitution", Codec.FLOAT, false), - (dto, value) -> dto.chunkCollisionRestitution = value != null - ? value - : PhysicsChunkCollisionDefaults.RESTITUTION, - PersistentSpaceDto::getChunkCollisionRestitution) - .add() - .append(new KeyedCodec<>("ChunkCollisionFilter", - CollisionFilterComponent.CODEC, - false), - (dto, value) -> dto.chunkCollisionFilter = value != null - ? value.clone() - : defaultChunkCollisionFilter(), - PersistentSpaceDto::getChunkCollisionFilter) - .add() - .append(new KeyedCodec<>("SolverSettings", SolverSettingsComponent.CODEC, false), - (dto, value) -> dto.solverSettings = value != null - ? value.clone() - : new SolverSettingsComponent(), - PersistentSpaceDto::getSolverSettings) - .add() - .append(new KeyedCodec<>("VisualSyncSettings", VisualSyncSettingsComponent.CODEC, false), - (dto, value) -> dto.visualSyncSettings = value != null - ? value.clone() - : new VisualSyncSettingsComponent(), - PersistentSpaceDto::getVisualSyncSettings) - .add() - .append(new KeyedCodec<>("VisualMaterializationSettings", - VisualMaterializationSettingsComponent.CODEC, - false), - (dto, value) -> dto.visualMaterializationSettings = value != null - ? value.clone() - : new VisualMaterializationSettingsComponent(), - PersistentSpaceDto::getVisualMaterializationSettings) - .add() - .append(new KeyedCodec<>("CollisionLodSettings", - CollisionLodSettingsComponent.CODEC, - false), - (dto, value) -> dto.collisionLodSettings = value != null - ? value.clone() - : new CollisionLodSettingsComponent(), - PersistentSpaceDto::getCollisionLodSettings) - .add() - .append(new KeyedCodec<>("ExtensionSettings", - ExtensionSettingsComponent.CODEC, - false), - (dto, value) -> dto.extensionSettings = value != null - ? value.clone() - : new ExtensionSettingsComponent(), - PersistentSpaceDto::getExtensionSettings) - .add() - .build(); - - @Nonnull - private UUID spaceUuid = new UUID(0L, 0L); - @Nonnull - private String backendId = ""; - @Nonnull - private final Vector3f gravity = new Vector3f(0.0f, -9.81f, 0.0f); - @Nonnull - private PhysicsChunkCollisionMode chunkCollisionMode = PhysicsChunkCollisionMode.NONE; - @Nonnull - private EntityChunkBoundaryMode entityChunkBoundaryMode = - PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE; - private boolean nativeVoxelCollisionEnabled = - PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED; - private int chunkCollisionRadius = - PhysicsChunkCollisionSettings.DEFAULT_RADIUS; - private int bodyChunkCollisionRadius = - PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS; - private int chunkCollisionTtlTicks = - PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS; - private float chunkCollisionFriction = PhysicsChunkCollisionDefaults.FRICTION; - private float chunkCollisionRestitution = - PhysicsChunkCollisionDefaults.RESTITUTION; - @Nonnull - private CollisionFilterComponent chunkCollisionFilter = defaultChunkCollisionFilter(); - @Nonnull - private SolverSettingsComponent solverSettings = new SolverSettingsComponent(); - @Nonnull - private VisualSyncSettingsComponent visualSyncSettings = new VisualSyncSettingsComponent(); - @Nonnull - private VisualMaterializationSettingsComponent visualMaterializationSettings = - new VisualMaterializationSettingsComponent(); - @Nonnull - private CollisionLodSettingsComponent collisionLodSettings = - new CollisionLodSettingsComponent(); - @Nonnull - private ExtensionSettingsComponent extensionSettings = new ExtensionSettingsComponent(); - - public PersistentSpaceDto() { - } - - public PersistentSpaceDto(@Nonnull UUID spaceUuid, - @Nonnull String backendId, - @Nonnull Vector3f gravity) { - this(spaceUuid, - backendId, - gravity, - PhysicsChunkCollisionMode.NONE, - PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - PhysicsChunkCollisionSettings.DEFAULT_NATIVE_VOXEL_COLLISION_ENABLED, - PhysicsChunkCollisionSettings.DEFAULT_RADIUS, - PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, - PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, - PhysicsChunkCollisionDefaults.FRICTION, - PhysicsChunkCollisionDefaults.RESTITUTION, - PhysicsChunkCollisionDefaults.COLLISION_GROUP, - PhysicsChunkCollisionDefaults.COLLISION_MASK, - new SolverSettingsComponent(), - new VisualSyncSettingsComponent(), - new VisualMaterializationSettingsComponent(), - new CollisionLodSettingsComponent(), - new ExtensionSettingsComponent()); - } - - public PersistentSpaceDto(@Nonnull UUID spaceUuid, - @Nonnull String backendId, - @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkCollisionMode chunkCollisionMode, - boolean nativeVoxelCollisionEnabled, - int chunkCollisionRadius, - int bodyChunkCollisionRadius, - int chunkCollisionTtlTicks, - float chunkCollisionFriction, - float chunkCollisionRestitution) { - this(spaceUuid, - backendId, - gravity, - chunkCollisionMode, - PhysicsChunkCollisionSettings.DEFAULT_ENTITY_CHUNK_BOUNDARY_MODE, - nativeVoxelCollisionEnabled, - chunkCollisionRadius, - bodyChunkCollisionRadius, - chunkCollisionTtlTicks, - chunkCollisionFriction, - chunkCollisionRestitution, - PhysicsChunkCollisionDefaults.COLLISION_GROUP, - PhysicsChunkCollisionDefaults.COLLISION_MASK, - new SolverSettingsComponent(), - new VisualSyncSettingsComponent(), - new VisualMaterializationSettingsComponent(), - new CollisionLodSettingsComponent(), - new ExtensionSettingsComponent()); - } - - public PersistentSpaceDto(@Nonnull UUID spaceUuid, - @Nonnull String backendId, - @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkCollisionMode chunkCollisionMode, - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelCollisionEnabled, - int chunkCollisionRadius, - int bodyChunkCollisionRadius, - int chunkCollisionTtlTicks, - float chunkCollisionFriction, - float chunkCollisionRestitution, - @Nonnull SolverSettingsComponent solverSettings, - @Nonnull VisualSyncSettingsComponent visualSyncSettings, - @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, - @Nonnull CollisionLodSettingsComponent collisionLodSettings, - @Nonnull ExtensionSettingsComponent extensionSettings) { - this(spaceUuid, - backendId, - gravity, - chunkCollisionMode, - entityChunkBoundaryMode, - nativeVoxelCollisionEnabled, - chunkCollisionRadius, - bodyChunkCollisionRadius, - chunkCollisionTtlTicks, - chunkCollisionFriction, - chunkCollisionRestitution, - PhysicsChunkCollisionDefaults.COLLISION_GROUP, - PhysicsChunkCollisionDefaults.COLLISION_MASK, - solverSettings, - visualSyncSettings, - visualMaterializationSettings, - collisionLodSettings, - extensionSettings); - } - - public PersistentSpaceDto(@Nonnull UUID spaceUuid, - @Nonnull String backendId, - @Nonnull Vector3f gravity, - @Nonnull PhysicsChunkCollisionMode chunkCollisionMode, - @Nonnull EntityChunkBoundaryMode entityChunkBoundaryMode, - boolean nativeVoxelCollisionEnabled, - int chunkCollisionRadius, - int bodyChunkCollisionRadius, - int chunkCollisionTtlTicks, - float chunkCollisionFriction, - float chunkCollisionRestitution, - int chunkCollisionGroup, - int chunkCollisionMask, - @Nonnull SolverSettingsComponent solverSettings, - @Nonnull VisualSyncSettingsComponent visualSyncSettings, - @Nonnull VisualMaterializationSettingsComponent visualMaterializationSettings, - @Nonnull CollisionLodSettingsComponent collisionLodSettings, - @Nonnull ExtensionSettingsComponent extensionSettings) { - this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); - this.backendId = Objects.requireNonNull(backendId, "backendId"); - this.gravity.set(Objects.requireNonNull(gravity, "gravity")); - this.chunkCollisionMode = Objects.requireNonNull(chunkCollisionMode, "chunkCollisionMode"); - this.entityChunkBoundaryMode = Objects.requireNonNull(entityChunkBoundaryMode, - "entityChunkBoundaryMode"); - this.nativeVoxelCollisionEnabled = nativeVoxelCollisionEnabled; - this.chunkCollisionRadius = chunkCollisionRadius; - this.bodyChunkCollisionRadius = bodyChunkCollisionRadius; - this.chunkCollisionTtlTicks = chunkCollisionTtlTicks; - this.chunkCollisionFriction = chunkCollisionFriction; - this.chunkCollisionRestitution = chunkCollisionRestitution; - this.chunkCollisionFilter = new CollisionFilterComponent(chunkCollisionGroup, - chunkCollisionMask); - this.solverSettings = Objects.requireNonNull(solverSettings, "solverSettings").clone(); - this.visualSyncSettings = Objects.requireNonNull(visualSyncSettings, - "visualSyncSettings").clone(); - this.visualMaterializationSettings = Objects.requireNonNull(visualMaterializationSettings, - "visualMaterializationSettings").clone(); - this.collisionLodSettings = Objects.requireNonNull(collisionLodSettings, - "collisionLodSettings").clone(); - this.extensionSettings = Objects.requireNonNull(extensionSettings, - "extensionSettings").clone(); - } - - @Nonnull - public UUID getSpaceUuid() { - return spaceUuid; - } - - @Nonnull - public String getBackendId() { - return backendId; - } - - @Nonnull - public Vector3f getGravity() { - return new Vector3f(gravity); - } - - @Nonnull - public PhysicsChunkCollisionMode getMode() { - return chunkCollisionMode; - } - - @Nonnull - public EntityChunkBoundaryMode getEntityChunkBoundaryMode() { - return entityChunkBoundaryMode; - } - - public boolean isNativeVoxelCollisionEnabled() { - return nativeVoxelCollisionEnabled; - } - - public int getRadius() { - return chunkCollisionRadius; - } - - public int getBodyRadius() { - return bodyChunkCollisionRadius; - } - - public int getTtlTicks() { - return chunkCollisionTtlTicks; - } - - public float getChunkCollisionFriction() { - return chunkCollisionFriction; - } - - public float getChunkCollisionRestitution() { - return chunkCollisionRestitution; - } - - public int getChunkCollisionGroup() { - return chunkCollisionFilter.getCollisionGroup(); - } - - public int getChunkCollisionMask() { - return chunkCollisionFilter.getCollisionMask(); - } - - @Nonnull - public ChunkCollisionSettingsComponent getChunkCollisionSettings() { - return new ChunkCollisionSettingsComponent(getMode(), - entityChunkBoundaryMode, - nativeVoxelCollisionEnabled, - chunkCollisionRadius, - bodyChunkCollisionRadius, - chunkCollisionTtlTicks); - } - - @Nonnull - public MaterialComponent getChunkCollisionMaterial() { - return new MaterialComponent(chunkCollisionFriction, chunkCollisionRestitution); - } - - public boolean isDefaultChunkCollisionMaterial() { - return Float.compare(chunkCollisionFriction, - PhysicsChunkCollisionDefaults.FRICTION) == 0 - && Float.compare(chunkCollisionRestitution, - PhysicsChunkCollisionDefaults.RESTITUTION) == 0; - } - - @Nonnull - public CollisionFilterComponent getChunkCollisionFilter() { - return chunkCollisionFilter.clone(); - } - - public boolean isDefaultChunkCollisionFilter() { - return getChunkCollisionGroup() == PhysicsChunkCollisionDefaults.COLLISION_GROUP - && getChunkCollisionMask() == PhysicsChunkCollisionDefaults.COLLISION_MASK; - } - - @Nonnull - public SolverSettingsComponent getSolverSettings() { - return solverSettings.clone(); - } - - @Nonnull - public VisualSyncSettingsComponent getVisualSyncSettings() { - return visualSyncSettings.clone(); - } - - @Nonnull - public VisualMaterializationSettingsComponent getVisualMaterializationSettings() { - return visualMaterializationSettings.clone(); - } - - @Nonnull - public CollisionLodSettingsComponent getCollisionLodSettings() { - return collisionLodSettings.clone(); - } - - @Nonnull - public ExtensionSettingsComponent getExtensionSettings() { - return extensionSettings.clone(); - } - - @Nonnull - public PersistentSpaceDto copy() { - return new PersistentSpaceDto(spaceUuid, - backendId, - gravity, - chunkCollisionMode, - entityChunkBoundaryMode, - nativeVoxelCollisionEnabled, - chunkCollisionRadius, - bodyChunkCollisionRadius, - chunkCollisionTtlTicks, - chunkCollisionFriction, - chunkCollisionRestitution, - getChunkCollisionGroup(), - getChunkCollisionMask(), - solverSettings, - visualSyncSettings, - visualMaterializationSettings, - collisionLodSettings, - extensionSettings); - } - - @Nonnull - private static CollisionFilterComponent defaultChunkCollisionFilter() { - return new CollisionFilterComponent(PhysicsChunkCollisionDefaults.COLLISION_GROUP, - PhysicsChunkCollisionDefaults.COLLISION_MASK); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java index fdabbf5e..e30a7785 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderStorage.java @@ -29,7 +29,7 @@ */ public final class PhysicsStoreHolderStorage { - private static final int SCHEMA_VERSION = 1; + public static final int SCHEMA_VERSION = 1; private static final String DIRECTORY = "physicsstore"; private static final String FILE_NAME = "holders.bson"; private static final String SCHEMA_VERSION_FIELD = "SchemaVersion"; @@ -48,19 +48,13 @@ public static CompletableFuture save(@Nonnull Store store) { List holderBlobs = PhysicsStoreHolderPersistence.capturePersistentHolderBlobs( store); byte[] document = BsonUtil.writeToBytes(document(holderBlobs)); - Path file = fileOrNull(store.getExternalData()); - if (file == null) { - return CompletableFuture.completedFuture(null); - } + Path file = file(store.getExternalData()); return CompletableFuture.runAsync(() -> writeBinaryAtomic(file, document)); } @Nonnull public static LoadResult load(@Nonnull Store store) { - Path file = fileOrNull(store.getExternalData()); - if (file == null) { - return LoadResult.missing(); - } + Path file = file(store.getExternalData()); if (!Files.exists(file)) { return LoadResult.missing(); } @@ -104,8 +98,8 @@ public static LoadResult load(@Nonnull Store store) { @Nonnull public static Summary summary(@Nonnull Store store) { - Path file = fileOrNull(store.getExternalData()); - if (file == null || !Files.exists(file)) { + Path file = file(store.getExternalData()); + if (!Files.exists(file)) { return Summary.missing(); } BsonDocument document; @@ -146,13 +140,13 @@ public static Summary summary(@Nonnull Store store) { @Nonnull static Path file(@Nonnull PhysicsStore physicsStore) { - return physicsStore.getWorld().getSavePath().resolve(DIRECTORY).resolve(FILE_NAME); + Path savePath = physicsStore.getWorld().getSavePath(); + return primaryFile(savePath); } - @Nullable - private static Path fileOrNull(@Nonnull PhysicsStore physicsStore) { - Path savePath = physicsStore.getWorld().getSavePath(); - return savePath != null ? savePath.resolve(DIRECTORY).resolve(FILE_NAME) : null; + @Nonnull + private static Path primaryFile(@Nonnull Path savePath) { + return savePath.resolve(DIRECTORY).resolve(FILE_NAME); } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStorePersistenceValidation.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStorePersistenceValidation.java deleted file mode 100644 index 5aa1ed48..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStorePersistenceValidation.java +++ /dev/null @@ -1,75 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import com.hypixel.hytale.codec.schema.SchemaContext; -import com.hypixel.hytale.codec.schema.config.Schema; -import com.hypixel.hytale.codec.validation.ValidationResults; -import com.hypixel.hytale.codec.validation.Validator; -import javax.annotation.Nonnull; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -final class PhysicsStorePersistenceValidation { - - private PhysicsStorePersistenceValidation() { - } - - @Nonnull - static Validator finiteFloat(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(Float value, ValidationResults results) { - if (value != null && !Float.isFinite(value)) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator nonNegativeFiniteFloat(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(Float value, ValidationResults results) { - if (value != null && (!Float.isFinite(value) || value < 0.0f)) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - @Nonnull - static Validator finiteVector(@Nonnull String message) { - return new Validator<>() { - @Override - public void accept(Vector3f value, ValidationResults results) { - if (value != null && !isFinite(value)) { - results.fail(message); - } - } - - @Override - public void updateSchema(SchemaContext context, Schema schema) { - } - }; - } - - static boolean isFinite(@Nonnull Vector3f value) { - return Float.isFinite(value.x) && Float.isFinite(value.y) && Float.isFinite(value.z); - } - - static boolean isFinite(@Nonnull Quaternionf value) { - return Float.isFinite(value.x) - && Float.isFinite(value.y) - && Float.isFinite(value.z) - && Float.isFinite(value.w) - && value.lengthSquared() > 0.0f; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 4ce5db34..596260f2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; import javax.annotation.Nonnull; @@ -32,8 +31,6 @@ public final class PhysicsResourceTypes { @Nullable private static ResourceType readQueueResourceType; @Nullable - private static ResourceType persistentStoreResourceType; - @Nullable private static ResourceType restoreStatusResourceType; @Nullable private static ResourceType profilingResourceType; @@ -70,9 +67,6 @@ public static void registerResourceTypes( readQueueResourceType = registry.registerResource( PhysicsStoreReadQueueResource.class, PhysicsStoreReadQueueResource::new); - persistentStoreResourceType = registry.registerResource( - PersistentPhysicsStoreResource.class, - PersistentPhysicsStoreResource::new); restoreStatusResourceType = registry.registerResource( PhysicsRestoreStatusResource.class, PhysicsRestoreStatusResource::new); @@ -125,11 +119,6 @@ public static ResourceType readQueu return readQueueResourceType; } - @Nonnull - public static ResourceType persistentStoreResourceType() { - return persistentStoreResourceType; - } - @Nonnull public static ResourceType restoreStatusResourceType() { return restoreStatusResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 7e990997..54312eee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -1,55 +1,20 @@ package dev.hytalemodding.impulse.core.internal.systems; -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentBodyRuntimeStateDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentColliderDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentJointDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentMaterialDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStorePreflight; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreStorage; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentShapeDto; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentSpaceDto; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; -import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; -import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.Map; import java.util.Set; -import java.util.UUID; import javax.annotation.Nonnull; /** - * Rehydrates persisted PhysicsStore DTOs into ECS rows before backend mutation is allowed. + * Rehydrates persisted PhysicsStore holder rows before backend mutation is allowed. */ public final class PersistenceHydrationSystem extends TickingSystem { @@ -64,32 +29,12 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) } try { prepareTransientRestoreState(store); - PhysicsStoreHolderStorage.LoadResult holderLoad = PhysicsStoreHolderStorage.load(store); - if (holderLoad.present()) { - restore.markComplete(); - restore.markHydrated(); - return; - } + PhysicsStoreHolderStorage.load(store); + restore.markComplete(); + restore.markHydrated(); } catch (RuntimeException exception) { restore.markFailed(exception.getMessage()); - return; - } - PersistentPhysicsStoreStorage.LoadResult persistentLoad; - try { - persistentLoad = PersistentPhysicsStoreStorage.load(store); - } catch (RuntimeException exception) { - restore.markFailed(exception.getMessage()); - return; - } - PersistentPhysicsStoreResource persistent = persistentLoad.resource(); - PersistentPhysicsStorePreflight.Result result = persistent.preflight(); - if (!result.valid()) { - restore.markFailed(String.join("; ", result.errors())); - return; } - hydrateRows(store, persistent); - restore.markComplete(); - restore.markHydrated(); } private static void prepareTransientRestoreState(@Nonnull Store store) { @@ -100,204 +45,6 @@ private static void prepareTransientRestoreState(@Nonnull Store st store.getResource(PhysicsEventResource.getResourceType()).clear(); } - private static void hydrateRows(@Nonnull Store store, - @Nonnull PersistentPhysicsStoreResource persistent) { - for (PersistentSpaceDto dto : persistent.getSpaces()) { - addSpace(store, dto); - } - addBodies(store, persistent); - for (PersistentJointDto dto : persistent.getJoints()) { - addJoint(store, dto); - } - } - - private static void addSpace(@Nonnull Store store, - @Nonnull PersistentSpaceDto dto) { - Holder holder = row(store, dto.getSpaceUuid()); - holder.addComponent(SpaceComponent.getComponentType(), - new SpaceComponent(new BackendId(dto.getBackendId()), dto.getGravity())); - addIfNonDefault(holder, - ChunkCollisionSettingsComponent.getComponentType(), - dto.getChunkCollisionSettings(), - dto.getChunkCollisionSettings().isDefault()); - addIfNonDefault(holder, - MaterialComponent.getComponentType(), - dto.getChunkCollisionMaterial(), - dto.isDefaultChunkCollisionMaterial()); - addIfNonDefault(holder, - CollisionFilterComponent.getComponentType(), - dto.getChunkCollisionFilter(), - dto.isDefaultChunkCollisionFilter()); - addIfNonDefault(holder, - SolverSettingsComponent.getComponentType(), - dto.getSolverSettings(), - dto.getSolverSettings().isDefault()); - addIfNonDefault(holder, - VisualSyncSettingsComponent.getComponentType(), - dto.getVisualSyncSettings(), - dto.getVisualSyncSettings().isDefault()); - addIfNonDefault(holder, - VisualMaterializationSettingsComponent.getComponentType(), - dto.getVisualMaterializationSettings(), - dto.getVisualMaterializationSettings().isDefault()); - addIfNonDefault(holder, - CollisionLodSettingsComponent.getComponentType(), - dto.getCollisionLodSettings(), - dto.getCollisionLodSettings().isDefault()); - addIfNonDefault(holder, - ExtensionSettingsComponent.getComponentType(), - dto.getExtensionSettings(), - dto.getExtensionSettings().isDefault()); - add(store, holder); - } - - private static > void addIfNonDefault( - @Nonnull Holder holder, - @Nonnull ComponentType componentType, - @Nonnull T component, - boolean defaultValue) { - if (!defaultValue) { - holder.addComponent(componentType, component); - } - } - - private static void addBodies(@Nonnull Store store, - @Nonnull PersistentPhysicsStoreResource persistent) { - Map collidersByUuid = new Object2ObjectOpenHashMap<>(); - Map collidersByBodyUuid = new Object2ObjectOpenHashMap<>(); - for (PersistentColliderDto collider : persistent.getColliders()) { - collidersByUuid.put(collider.getColliderUuid(), collider); - collidersByBodyUuid.putIfAbsent(collider.getBodyUuid(), collider); - } - Map shapesByUuid = new Object2ObjectOpenHashMap<>(); - for (PersistentShapeDto shape : persistent.getShapes()) { - shapesByUuid.put(shape.getShapeUuid(), shape); - } - Map materialsByUuid = new Object2ObjectOpenHashMap<>(); - for (PersistentMaterialDto material : persistent.getMaterials()) { - materialsByUuid.put(material.getMaterialUuid(), material); - } - for (PersistentBodyDto dto : persistent.getBodies()) { - addBody(store, - dto, - colliderFor(dto, collidersByUuid, collidersByBodyUuid), - shapesByUuid, - materialsByUuid); - } - } - - private static void addBody(@Nonnull Store store, - @Nonnull PersistentBodyDto dto, - PersistentColliderDto collider, - @Nonnull Map shapesByUuid, - @Nonnull Map materialsByUuid) { - Holder holder = row(store, dto.getBodyUuid()); - BodyComponent body = new BodyComponent(dto.getSpaceUuid()); - DynamicsComponent dynamics = new DynamicsComponent(dto.getBodyType(), - dto.getMass(), - dto.getLinearDamping(), - dto.getAngularDamping(), - dto.isContinuousCollisionEnabled()); - TargetComponent target = inactiveTarget(dto.getRuntimeState()); - if (collider != null) { - PersistentShapeDto shape = shapesByUuid.get(collider.getShapeUuid()); - PersistentMaterialDto material = materialsByUuid.get(collider.getMaterialUuid()); - if (shape != null && material != null) { - PhysicsEntities.addBodyComponents(holder, - body, - dynamics, - target, - new ColliderComponent(collider.getLocalPosition(), - collider.getLocalRotation(), - collider.isSensor()), - new ShapeComponent(shape.getShapeType(), - shape.getHalfExtentX(), - shape.getHalfExtentY(), - shape.getHalfExtentZ(), - shape.getRadius(), - shape.getHalfHeight(), - shape.getAxis(), - shape.getGroundY(), - shape.getResourceKey()), - new MaterialComponent(material.getFriction(), material.getRestitution()), - new CollisionFilterComponent(collider.getCollisionGroup(), - collider.getCollisionMask())); - } else { - holder.addComponent(BodyComponent.getComponentType(), body); - holder.addComponent(DynamicsComponent.getComponentType(), dynamics); - holder.addComponent(TargetComponent.getComponentType(), target); - } - } else { - holder.addComponent(BodyComponent.getComponentType(), body); - holder.addComponent(DynamicsComponent.getComponentType(), dynamics); - holder.addComponent(TargetComponent.getComponentType(), target); - } - add(store, holder); - } - - private static PersistentColliderDto colliderFor(@Nonnull PersistentBodyDto body, - @Nonnull Map collidersByUuid, - @Nonnull Map collidersByBodyUuid) { - for (UUID colliderUuid : body.getColliderUuids()) { - PersistentColliderDto collider = collidersByUuid.get(colliderUuid); - if (collider != null) { - return collider; - } - } - return collidersByBodyUuid.get(body.getBodyUuid()); - } - - private static void addJoint(@Nonnull Store store, - @Nonnull PersistentJointDto dto) { - Holder holder = row(store, dto.getJointUuid()); - JointComponent joint = new JointComponent(); - joint.setSpaceUuid(dto.getSpaceUuid()); - joint.setBodyAUuid(dto.getBodyAUuid()); - joint.setBodyBUuid(dto.getBodyBUuid()); - joint.setType(dto.getType()); - joint.setAnchorA(dto.getAnchorA()); - joint.setAnchorB(dto.getAnchorB()); - joint.setAxis(dto.getAxis()); - joint.setLowerLimit(dto.getLowerLimit()); - joint.setUpperLimit(dto.getUpperLimit()); - joint.setEnabled(dto.isEnabled()); - joint.setMotorEnabled(dto.isMotorEnabled()); - joint.setMotorTargetVelocity(dto.getMotorTargetVelocity()); - joint.setMotorMaxForce(dto.getMotorMaxForce()); - joint.setSpringRestLength(dto.getSpringRestLength()); - joint.setSpringStiffness(dto.getSpringStiffness()); - joint.setSpringDamping(dto.getSpringDamping()); - holder.addComponent(JointComponent.getComponentType(), joint); - add(store, holder); - } - - @Nonnull - private static TargetComponent inactiveTarget(@Nonnull PersistentBodyRuntimeStateDto dto) { - TargetComponent target = new TargetComponent(); - target.setActive(false); - target.setPosition(dto.getPosition()); - target.setRotation(dto.getRotation()); - target.setLinearVelocity(dto.getLinearVelocity()); - target.setAngularVelocity(dto.getAngularVelocity()); - target.setTransformEnabled(true); - target.setVelocityEnabled(true); - target.setActivate(!dto.isSleeping()); - return target; - } - - @Nonnull - private static Holder row(@Nonnull Store store, - @Nonnull UUID uuid) { - Holder holder = store.getRegistry().newHolder(); - holder.addComponent(UuidComponent.getComponentType(), new UuidComponent(uuid)); - return holder; - } - - private static void add(@Nonnull Store store, - @Nonnull Holder holder) { - store.addEntity(holder, AddReason.LOAD); - } - @Nonnull @Override public Set> getDependencies() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java index 89d5dfa7..bc37513f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/persistence/PhysicsPersistence.java @@ -3,8 +3,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreResource; -import dev.hytalemodding.impulse.core.internal.persistence.PersistentPhysicsStoreStorage; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -22,7 +20,7 @@ public final class PhysicsPersistence { public static final int CURRENT_SCHEMA_VERSION = - PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION; + PhysicsStoreHolderStorage.SCHEMA_VERSION; private static final String SAVE_SKIPPED_REASON = "authoritative-physics-store-holder-save-hook"; private static final String RESTORE_SKIPPED_REASON = @@ -142,13 +140,7 @@ private static SavedStateSummary savedStateSummary(@Nonnull Store holderSummary.bodies(), holderSummary.joints()); } - PersistentPhysicsStoreStorage.LoadResult legacy = PersistentPhysicsStoreStorage.load( - physicsStore); - PersistentPhysicsStoreResource resource = legacy.resource(); - return new SavedStateSummary(resource.getSchemaVersion(), - resource.getSpaces().length, - resource.getBodies().length, - resource.getJoints().length); + return new SavedStateSummary(CURRENT_SCHEMA_VERSION, 0, 0, 0); } @Nonnull diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java deleted file mode 100644 index 8b3c902e..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/persistence/PersistentSpaceDtoSettingsTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.codec.ExtraInfo; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; -import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; -import java.util.Objects; -import java.util.UUID; -import org.bson.BsonDocument; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; - -class PersistentSpaceDtoSettingsTest { - - @Test - void roundTripPreservesDetachedVisualCadenceSettingsAndPhysicsChunkKeys() { - PhysicsChunkCollisionSettings chunkCollision = new PhysicsChunkCollisionSettings(); - chunkCollision.setMode(PhysicsChunkCollisionMode.STREAMING); - chunkCollision.setNativeVoxelCollisionEnabled(true); - PhysicsVisualMaterializationSettings visualMaterialization = - new PhysicsVisualMaterializationSettings(); - visualMaterialization.setDetachedVisualInterestRefreshIntervalTicks(7); - visualMaterialization.setDetachedVisualCandidateRefreshIntervalTicks(9); - visualMaterialization.setDetachedVisualVisibilityCheckIntervalTicks(11); - PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), - "test:settings-persistence", - new Vector3f(0.0f, -9.81f, 0.0f), - chunkCollision.getMode(), - chunkCollision.getEntityChunkBoundaryMode(), - chunkCollision.isNativeVoxelCollisionEnabled(), - chunkCollision.getRadius(), - chunkCollision.getBodyRadius(), - chunkCollision.getTtlTicks(), - 0.85f, - 0.2f, - new SolverSettingsComponent(), - new VisualSyncSettingsComponent(), - new VisualMaterializationSettingsComponent(visualMaterialization), - new CollisionLodSettingsComponent(), - new ExtensionSettingsComponent()); - - BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); - - assertTrue(encoded.containsKey("PhysicsChunkTerrainMode")); - assertTrue(encoded.containsKey("ChunkCollisionRadius")); - assertTrue(encoded.containsKey("BodyChunkCollisionRadius")); - assertTrue(encoded.containsKey("ChunkCollisionTtlTicks")); - assertTrue(encoded.containsKey("NativeVoxelCollision")); - assertTrue(encoded.containsKey("ChunkCollisionFriction")); - assertTrue(encoded.containsKey("ChunkCollisionRestitution")); - assertTrue(encoded.containsKey("VisualMaterializationSettings")); - PersistentSpaceDto decodedState = Objects.requireNonNull( - PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())); - assertEquals(0.85f, decodedState.getChunkCollisionFriction(), 0.0001f); - assertEquals(0.2f, decodedState.getChunkCollisionRestitution(), 0.0001f); - - ChunkCollisionSettingsComponent decodedChunkCollision = - decodedState.getChunkCollisionSettings(); - assertEquals(PhysicsChunkCollisionMode.STREAMING, - decodedChunkCollision.getMode()); - assertTrue(decodedChunkCollision.isNativeVoxelCollisionEnabled()); - assertDetachedVisualCadence(decodedState.getVisualMaterializationSettings(), 7, 9, 11); - - PersistentSpaceDto copiedState = state.copy(); - assertEquals(0.85f, copiedState.getChunkCollisionFriction(), 0.0001f); - assertEquals(0.2f, copiedState.getChunkCollisionRestitution(), 0.0001f); - - ChunkCollisionSettingsComponent copiedChunkCollision = - copiedState.getChunkCollisionSettings(); - assertEquals(PhysicsChunkCollisionMode.STREAMING, - copiedChunkCollision.getMode()); - assertTrue(copiedChunkCollision.isNativeVoxelCollisionEnabled()); - assertDetachedVisualCadence(copiedState.getVisualMaterializationSettings(), 7, 9, 11); - } - - @Test - void roundTripPreservesChunkCollisionFilter() { - PhysicsChunkCollisionSettings chunkCollision = new PhysicsChunkCollisionSettings(); - PersistentSpaceDto state = new PersistentSpaceDto(UUID.randomUUID(), - "test:chunk-filter-persistence", - new Vector3f(0.0f, -9.81f, 0.0f), - chunkCollision.getMode(), - chunkCollision.getEntityChunkBoundaryMode(), - false, - PhysicsChunkCollisionSettings.DEFAULT_RADIUS, - PhysicsChunkCollisionSettings.DEFAULT_BODY_RADIUS, - PhysicsChunkCollisionSettings.DEFAULT_TTL_TICKS, - PhysicsChunkCollisionDefaults.FRICTION, - PhysicsChunkCollisionDefaults.RESTITUTION, - 0x40, - 0x03, - new SolverSettingsComponent(), - new VisualSyncSettingsComponent(), - new VisualMaterializationSettingsComponent(), - new CollisionLodSettingsComponent(), - new ExtensionSettingsComponent()); - - BsonDocument encoded = PersistentSpaceDto.CODEC.encode(state, new ExtraInfo()).asDocument(); - - assertTrue(encoded.containsKey("ChunkCollisionFilter")); - PersistentSpaceDto decoded = Objects.requireNonNull( - PersistentSpaceDto.CODEC.decode(encoded, new ExtraInfo())); - assertEquals(0x40, decoded.getChunkCollisionGroup()); - assertEquals(0x03, decoded.getChunkCollisionMask()); - assertEquals(0x40, state.copy().getChunkCollisionGroup()); - assertEquals(0x03, state.copy().getChunkCollisionMask()); - assertEquals(chunkCollision.getEntityChunkBoundaryMode(), - decoded.getChunkCollisionSettings().getEntityChunkBoundaryMode()); - } - - private static void assertDetachedVisualCadence(VisualMaterializationSettingsComponent settings, - int interestInterval, - int candidateInterval, - int visibilityInterval) { - assertEquals(interestInterval, - settings.getDetachedVisualInterestRefreshIntervalTicks()); - assertEquals(candidateInterval, - settings.getDetachedVisualCandidateRefreshIntervalTicks()); - assertEquals(visibilityInterval, - settings.getDetachedVisualVisibilityCheckIntervalTicks()); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PersistentPhysicsStoreResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PersistentPhysicsStoreResourceTest.java deleted file mode 100644 index 5ad524a7..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PersistentPhysicsStoreResourceTest.java +++ /dev/null @@ -1,203 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.hypixel.hytale.codec.ExtraInfo; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import org.bson.BsonDocument; -import org.bson.BsonDouble; -import org.bson.BsonInt32; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - -class PersistentPhysicsStoreResourceTest { - - private static final AtomicInteger BACKEND_COUNTER = new AtomicInteger(); - private static final UUID SPACE_UUID = - UUID.fromString("00000000-0000-0000-0000-000000000001"); - private static final UUID BODY_UUID = - UUID.fromString("00000000-0000-0000-0000-000000000002"); - private static final UUID SHAPE_UUID = - UUID.fromString("00000000-0000-0000-0000-000000000003"); - private static final UUID MATERIAL_UUID = - UUID.fromString("00000000-0000-0000-0000-000000000004"); - private static final UUID COLLIDER_UUID = - UUID.fromString("00000000-0000-0000-0000-000000000005"); - - @Test - void storeResourceCodecPreservesDtoRows() { - PersistentPhysicsStoreResource resource = validResource(registeredBackendId("codec")); - - BsonDocument encoded = PersistentPhysicsStoreResource.CODEC.encode(resource, - new ExtraInfo()).asDocument(); - PersistentPhysicsStoreResource decoded = PersistentPhysicsStoreResource.CODEC.decode(encoded, - new ExtraInfo()); - - assertEquals(PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION, - encoded.getInt32("SchemaVersion").getValue()); - assertNotNull(decoded); - assertEquals(1, decoded.getSpaces().length); - assertEquals(1, decoded.getBodies().length); - assertEquals(1, decoded.getColliders().length); - assertEquals(1, decoded.getShapes().length); - assertEquals(1, decoded.getMaterials().length); - assertEquals(BODY_UUID, decoded.getBodies()[0].getBodyUuid()); - assertEquals(COLLIDER_UUID, decoded.getBodies()[0].getColliderUuids()[0]); - } - - @Test - void storeResourceCodecRejectsOutdatedSchemaVersion() { - PersistentPhysicsStoreResource resource = validResource(registeredBackendId("old-schema")); - BsonDocument encoded = PersistentPhysicsStoreResource.CODEC.encode(resource, - new ExtraInfo()).asDocument(); - encoded.put("SchemaVersion", new BsonInt32(1)); - - assertValidationFails( - () -> PersistentPhysicsStoreResource.CODEC.decode(encoded, new ExtraInfo()), - "Must be greater than or equal to " - + PersistentPhysicsStoreResource.CURRENT_SCHEMA_VERSION); - } - - @Test - void preflightAcceptsAvailableRuntimeProviderWithoutLegacyBackendRegistration() { - PersistentPhysicsStoreResource resource = validResource(registeredBackendId("preflight")); - - PersistentPhysicsStorePreflight.Result result = resource.preflight(); - - assertTrue(result.valid(), () -> result.errors().toString()); - } - - @Test - void preflightRejectsDuplicateBodyUuidBeforeBackendHydration() { - PersistentPhysicsStoreResource resource = validResource(registeredBackendId("duplicate-body")); - PersistentBodyDto body = resource.getBodies()[0]; - resource.setBodies(new PersistentBodyDto[] { body, body.copy() }); - - PersistentPhysicsStorePreflight.Result result = resource.preflight(); - - assertTrue(result.errors().stream() - .anyMatch(error -> error.contains("Duplicate PhysicsStore body UUID"))); - } - - @Test - void bodyDtoCodecRejectsInvalidMassInsteadOfDefaulting() { - BsonDocument encoded = PersistentBodyDto.CODEC.encode(bodyDto(), new ExtraInfo()).asDocument(); - encoded.put("Mass", new BsonDouble(Double.NaN)); - - assertValidationFails( - () -> PersistentBodyDto.CODEC.decode(encoded, new ExtraInfo()), - "Persisted body mass must be finite and >= 0"); - } - - @Test - void shapeDtoCodecRejectsUnsupportedNonFinitePlaneGroundY() { - BsonDocument encoded = PersistentShapeDto.CODEC.encode(shapeDto(), new ExtraInfo()).asDocument(); - encoded.put("GroundY", new BsonDouble(Double.NaN)); - - assertValidationFails( - () -> PersistentShapeDto.CODEC.decode(encoded, new ExtraInfo()), - "Persisted shape ground Y must be finite"); - } - - private static PersistentPhysicsStoreResource validResource(String backendId) { - PersistentPhysicsStoreResource resource = new PersistentPhysicsStoreResource(); - resource.setSpaces(new PersistentSpaceDto[] { - new PersistentSpaceDto(SPACE_UUID, backendId, new Vector3f(0.0f, -9.81f, 0.0f)) - }); - resource.setBodies(new PersistentBodyDto[] { bodyDto() }); - resource.setShapes(new PersistentShapeDto[] { shapeDto() }); - resource.setMaterials(new PersistentMaterialDto[] { - new PersistentMaterialDto(MATERIAL_UUID, 0.5f, 0.0f) - }); - resource.setColliders(new PersistentColliderDto[] { - new PersistentColliderDto(COLLIDER_UUID, - BODY_UUID, - SHAPE_UUID, - MATERIAL_UUID, - new Vector3f(), - new Quaternionf(), - false, - 0, - -1) - }); - return resource; - } - - private static PersistentBodyDto bodyDto() { - return new PersistentBodyDto(BODY_UUID, - SPACE_UUID, - PhysicsBodyType.DYNAMIC, - 1.0f, - 0.0f, - 0.0f, - false, - new UUID[] { COLLIDER_UUID }, - new PersistentBodyRuntimeStateDto(new Vector3f(), - new Quaternionf(), - new Vector3f(), - new Vector3f(), - false)); - } - - private static PersistentShapeDto shapeDto() { - return new PersistentShapeDto(SHAPE_UUID, - ShapeType.BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - PhysicsAxis.Y, - 0.0f, - ""); - } - - private static String registeredBackendId(String suffix) { - String backendId = "test:persistent-store-" + suffix + "-" - + BACKEND_COUNTER.incrementAndGet(); - Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider(backendId)); - return backendId; - } - - private static void assertValidationFails(Executable executable, String expectedMessagePart) { - RuntimeException exception = assertThrows(RuntimeException.class, executable); - assertTrue(exceptionContainsMessage(exception, expectedMessagePart), - () -> "Expected validation message to contain: " + expectedMessagePart - + "\nActual: " + exceptionMessages(exception)); - } - - private static boolean exceptionContainsMessage(Throwable throwable, String expectedMessagePart) { - Throwable current = throwable; - while (current != null) { - if (current.getMessage() != null && current.getMessage().contains(expectedMessagePart)) { - return true; - } - current = current.getCause(); - } - return false; - } - - private static String exceptionMessages(Throwable throwable) { - StringBuilder messages = new StringBuilder(); - Throwable current = throwable; - while (current != null) { - if (!messages.isEmpty()) { - messages.append(" -> "); - } - messages.append(current.getMessage()); - current = current.getCause(); - } - return messages.toString(); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java index 7d485360..6a1480ff 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.hypixel.hytale.codec.ExtraInfo; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentRegistryProxy; @@ -69,8 +68,11 @@ import javax.annotation.Nonnull; import org.bson.BsonArray; import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; import org.bson.BsonDocument; +import org.bson.BsonDouble; import org.bson.BsonInt32; +import org.bson.BsonString; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -152,7 +154,7 @@ void holderBlobsPersistUuidBodyRowsWithSnapshotTargetsOnly() { } @Test - void hydrationPrefersHolderStorageOverLegacyDtoResource() { + void holderHydrationIgnoresLegacyDtoFileWhenHolderStorageExists() { StoreFixture source = store("holder-save-source", tempDir.resolve("save")); try { Ref spaceRef = addSpace(source.store(), SPACE_UUID); @@ -167,10 +169,7 @@ void hydrationPrefersHolderStorageOverLegacyDtoResource() { StoreFixture target = store("holder-save-target", tempDir.resolve("save")); try { - Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider( - "test:legacy-holder-fallback")); - writeLegacyDto(target.store(), - legacyResource(LEGACY_SPACE_UUID, "test:legacy-holder-fallback")); + writeLegacyDtoFile(target.store()); new PersistenceHydrationSystem().tick(0.0f, 0, target.store()); @@ -188,13 +187,47 @@ void hydrationPrefersHolderStorageOverLegacyDtoResource() { } @Test - void hydrationFallsBackToLegacyDtoWhenHolderStorageIsMissing() { + void holderStorageUsesWorldOwnedPhysicsStoreDirectory() { + Path savePath = tempDir.resolve("storage-layout"); + StoreFixture fixture = store("storage-layout", savePath); + try { + Path file = PhysicsStoreHolderStorage.file(fixture.store().getExternalData()); + + assertEquals(savePath.resolve("physicsstore").resolve("holders.bson"), + file); + } finally { + fixture.close(); + } + } + + @Test + void holderSaveCreatesWorldOwnedPhysicsStoreDirectory() { + Path savePath = tempDir.resolve("chunk-shaped-layout"); + StoreFixture fixture = store("chunk-shaped-layout", savePath); + try { + Ref spaceRef = addSpace(fixture.store(), SPACE_UUID); + addBody(fixture.store(), + BODY_A_UUID, + spaceRef, + null); + + PhysicsStoreHolderStorage.save(fixture.store()).join(); + + assertTrue(Files.exists(savePath.resolve("physicsstore") + .resolve("holders.bson"))); + assertTrue(Files.exists(savePath.resolve("physicsstore"))); + assertFalse(Files.exists(savePath.resolve("resources") + .resolve("PhysicsStoreHolders.bson"))); + } finally { + fixture.close(); + } + } + + @Test + void hydrationIgnoresLegacyDtoWhenHolderStorageIsMissing() { StoreFixture fixture = store("legacy-fallback", tempDir.resolve("legacy")); try { - Impulse.registerRuntimeProvider(new FakePhysicsBackendRuntimeProvider( - "test:legacy-only-fallback")); - writeLegacyDto(fixture.store(), - legacyResource(LEGACY_SPACE_UUID, "test:legacy-only-fallback")); + writeLegacyDtoFile(fixture.store()); new PersistenceHydrationSystem().tick(0.0f, 0, fixture.store()); @@ -203,39 +236,13 @@ void hydrationFallsBackToLegacyDtoWhenHolderStorageIsMissing() { assertTrue(restore.isHydrated()); assertFalse(restore.isFailed()); List rowUuids = rowUuids(fixture.store()); - assertTrue(rowUuids.contains(LEGACY_SPACE_UUID)); + assertFalse(rowUuids.contains(LEGACY_SPACE_UUID)); assertFalse(rowUuids.contains(SPACE_UUID)); } finally { fixture.close(); } } - @Test - void registeredPhysicsStoreTickDoesNotRewriteLegacyDtoResource() { - StoreFixture fixture = registeredStore("registered-no-dto-capture", - tempDir.resolve("registered")); - try { - fixture.store() - .getResource(PersistentPhysicsStoreResource.getResourceType()) - .setSpaces(new PersistentSpaceDto[] { - new PersistentSpaceDto(LEGACY_SPACE_UUID, - "test:legacy-sentinel", - new Vector3f(0.0f, -9.81f, 0.0f)) - }); - addSpace(fixture.store(), SPACE_UUID); - - fixture.store().tick(0.0f); - - PersistentSpaceDto[] spaces = fixture.store() - .getResource(PersistentPhysicsStoreResource.getResourceType()) - .getSpaces(); - assertEquals(1, spaces.length); - assertEquals(LEGACY_SPACE_UUID, spaces[0].getSpaceUuid()); - } finally { - fixture.close(); - } - } - @Test void holderHydrationRejectsDuplicateUuidWithoutAddingPartialRows() { StoreFixture fixture = store("holder-duplicate-uuid", @@ -527,26 +534,59 @@ private static void publishSnapshot(@Nonnull Store store, true)))); } + private static void writeLegacyDtoFile(@Nonnull Store store) { + Path file = store.getExternalData() + .getWorld() + .getSavePath() + .resolve("resources") + .resolve("PersistentPhysicsStore.json"); + BsonDocument space = new BsonDocument() + .append("SpaceUuid", uuidBinary(LEGACY_SPACE_UUID)) + .append("BackendId", new BsonString("test:legacy-only-fallback")) + .append("Gravity", vector(0.0f, -9.81f, 0.0f)); + BsonDocument document = new BsonDocument() + .append("SchemaVersion", new BsonInt32(2)) + .append("Spaces", new BsonArray(List.of(space))); + try { + Files.createDirectories(file.getParent()); + Files.write(file, BsonUtil.writeToBytes(document)); + } catch (IOException exception) { + throw new AssertionError("Failed to write legacy DTO fixture", exception); + } + } + @Nonnull - private static PersistentPhysicsStoreResource legacyResource(@Nonnull UUID spaceUuid, - @Nonnull String backendId) { - PersistentPhysicsStoreResource legacy = new PersistentPhysicsStoreResource(); - legacy.setSpaces(new PersistentSpaceDto[] { - new PersistentSpaceDto(spaceUuid, - backendId, - new Vector3f(0.0f, -9.81f, 0.0f)) - }); - return legacy; + private static BsonBinary uuidBinary(@Nonnull UUID uuid) { + byte[] bytes = new byte[16]; + writeLongBigEndian(bytes, 0, uuid.getMostSignificantBits()); + writeLongBigEndian(bytes, 8, uuid.getLeastSignificantBits()); + return new BsonBinary(BsonBinarySubType.UUID_STANDARD, bytes); } - private static void writeLegacyDto(@Nonnull Store store, - @Nonnull PersistentPhysicsStoreResource legacy) { - BsonUtil.writeDocument(PersistentPhysicsStoreStorage.file(store.getExternalData()), - PersistentPhysicsStoreResource.CODEC.encode(legacy, new ExtraInfo()).asDocument(), - false).join(); + private static void writeLongBigEndian(@Nonnull byte[] bytes, int offset, long value) { + for (int index = 7; index >= 0; index--) { + bytes[offset + index] = (byte) value; + value >>>= 8; + } + } + + @Nonnull + private static BsonDocument vector(float x, float y, float z) { + return new BsonDocument() + .append("X", new BsonDouble(x)) + .append("Y", new BsonDouble(y)) + .append("Z", new BsonDouble(z)); } private static void writeHolderStorage(@Nonnull Store store, + @Nonnull List> holders) { + writeHolderStorageFile(PhysicsStoreHolderStorage.file(store.getExternalData()), + store, + holders); + } + + private static void writeHolderStorageFile(@Nonnull Path file, + @Nonnull Store store, @Nonnull List> holders) { BsonArray holderBlobs = new BsonArray(); for (Holder holder : holders) { @@ -556,7 +596,6 @@ private static void writeHolderStorage(@Nonnull Store store, BsonDocument document = new BsonDocument() .append("SchemaVersion", new BsonInt32(1)) .append("Holders", holderBlobs); - Path file = PhysicsStoreHolderStorage.file(store.getExternalData()); try { Path parent = file.getParent(); if (parent != null) { From d77d9a5a25004991c905d839fe49230c34707920 Mon Sep 17 00:00:00 2001 From: Blovien Date: Sun, 21 Jun 2026 14:49:27 +0200 Subject: [PATCH 512/534] docs(physicschunk): remove empty package docs Signed-off-by: Blovien --- .../core/plugin/modules/physicschunk/package-info.java | 7 ------- .../plugin/modules/physicschunk/settings/package-info.java | 4 ---- 2 files changed, 11 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java deleted file mode 100644 index abf3e77b..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Public API for the bundled PhysicsChunk collision subplugin. - * - *

        Collision LOD settings live under - * {@code dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings}.

        - */ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java deleted file mode 100644 index b9173e12..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/settings/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Public settings owned by the bundled PhysicsChunk subplugin. - */ -package dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings; From 2ae234fe9e52a3d4476c17b6b50cccd635237c45 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:36:42 +0200 Subject: [PATCH 513/534] feat(commands): split explicit space lifecycle commands Signed-off-by: Blovien --- .../internal/commands/ImpulseCommand.java | 11 +- .../commands/ImpulseCommandTreeRegistry.java | 24 +- .../core/internal/commands/SpaceCommand.java | 296 ------------------ .../internal/commands/debug/DebugCommand.java | 14 +- .../commands/debug/DebugFlagCommand.java | 13 +- .../commands/debug/DebugToggleCommand.java | 7 +- .../settings/MaxStepDtSettingCommand.java | 3 +- .../settings/SolverSettingsCommand.java | 2 +- .../internal/commands/space/SpaceCommand.java | 85 +++++ .../commands/space/SpaceCreateCommand.java | 75 +++++ .../commands/space/SpaceDeleteCommand.java | 94 ++++++ .../{ => space}/SpaceDeleteSupport.java | 6 +- .../commands/space/SpaceListCommand.java | 82 +++++ .../commands/{ => space}/SpaceSelection.java | 5 +- .../commands/PhysicsChunkCommandSet.java | 13 + .../CleanCommandLifecycleGuardTest.java | 3 +- .../ImpulseCommandTreeRegistryTest.java | 5 + .../commands/PhysicsChunkCommandSetTest.java | 7 + .../{ => space}/SpaceCommandDeleteTest.java | 9 +- .../{ => space}/SpaceSelectionTest.java | 2 +- 20 files changed, 420 insertions(+), 336 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceDeleteCommand.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/{ => space}/SpaceDeleteSupport.java (95%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceListCommand.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/{ => space}/SpaceSelection.java (95%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/{ => space}/SpaceCommandDeleteTest.java (96%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/{ => space}/SpaceSelectionTest.java (96%) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java index dd82f355..f061e851 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommand.java @@ -6,21 +6,18 @@ import dev.hytalemodding.impulse.core.internal.commands.debug.DebugCommand; import dev.hytalemodding.impulse.core.internal.commands.perf.PerfCommand; import dev.hytalemodding.impulse.core.internal.commands.settings.SettingsCommand; +import dev.hytalemodding.impulse.core.internal.commands.space.SpaceCommand; import java.util.Collection; -import java.util.List; import javax.annotation.Nonnull; public class ImpulseCommand extends AbstractCommandCollection { - public ImpulseCommand() { - this(List.of()); - } - - ImpulseCommand(@Nonnull Collection settingsCommands) { + ImpulseCommand(@Nonnull Collection debugCommands, + @Nonnull Collection settingsCommands) { super("impulse", "Impulse runtime commands"); addSubCommand(new BackendCommand()); addSubCommand(new CleanCommand()); - addSubCommand(new DebugCommand()); + addSubCommand(new DebugCommand(debugCommands)); addSubCommand(new PerfCommand()); SettingsCommand settingsCommand = new SettingsCommand(); for (AbstractCommand command : settingsCommands) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistry.java index 752632c7..cb3cd29b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistry.java @@ -22,6 +22,8 @@ public final class ImpulseCommandTreeRegistry { private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); private static final Map> ROOT_COMMANDS = new LinkedHashMap<>(); + private static final Map> DEBUG_COMMANDS = + new LinkedHashMap<>(); private static final Map> SETTINGS_COMMANDS = new LinkedHashMap<>(); @@ -73,6 +75,21 @@ public static synchronized void unregisterRootSubCommand(@Nonnull String id) { } } + public static synchronized void registerDebugSubCommand(@Nonnull String id, + @Nonnull Supplier supplier) { + if (DEBUG_COMMANDS.containsKey(id)) { + return; + } + DEBUG_COMMANDS.put(id, Objects.requireNonNull(supplier, "supplier")); + rebuildIfRegistered(); + } + + public static synchronized void unregisterDebugSubCommand(@Nonnull String id) { + if (DEBUG_COMMANDS.remove(id) != null) { + rebuildIfRegistered(); + } + } + public static synchronized void registerSettingsSubCommand(@Nonnull String id, @Nonnull Supplier supplier) { if (SETTINGS_COMMANDS.containsKey(id)) { @@ -109,6 +126,7 @@ static synchronized void resetForTests() { commandRegistration = null; commandRegistry = null; ROOT_COMMANDS.clear(); + DEBUG_COMMANDS.clear(); SETTINGS_COMMANDS.clear(); } @@ -148,11 +166,15 @@ private static void rebuildRegisteredRoot() { @Nonnull private static ImpulseCommand createRootCommand() { + List debugCommands = new ArrayList<>(DEBUG_COMMANDS.size()); + for (Supplier supplier : DEBUG_COMMANDS.values()) { + debugCommands.add(supplier.get()); + } List settingsCommands = new ArrayList<>(SETTINGS_COMMANDS.size()); for (Supplier supplier : SETTINGS_COMMANDS.values()) { settingsCommands.add(supplier.get()); } - ImpulseCommand command = new ImpulseCommand(settingsCommands); + ImpulseCommand command = new ImpulseCommand(debugCommands, settingsCommands); for (Supplier supplier : ROOT_COMMANDS.values()) { command.registerRootCommand(supplier.get()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java deleted file mode 100644 index 2ec959c4..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommand.java +++ /dev/null @@ -1,296 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.commands; - -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.command.system.CommandContext; -import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; -import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; -import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.commands.SpaceDeleteSupport.DeleteResult; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class SpaceCommand extends AbstractCommandCollection { - - public SpaceCommand() { - super("space", "Explicit physics space lifecycle commands"); - addSubCommand(new CreateCommand()); - addSubCommand(new ListCommand()); - addSubCommand(new DeleteCommand()); - } - - private static final class CreateCommand extends AbstractWorldCommand { - - private final OptionalArg backendArg = withOptionalArg( - "backend", - "Backend id, for example impulse:rapier", - ArgTypes.STRING); - private final OptionalArg physicsChunkArg = withOptionalArg( - "physicsChunk", - "PhysicsChunk collision mode: none, manual, or streaming", - ArgTypes.STRING); - private CreateCommand() { - super("create", "Create an explicit physics space", false); - } - - @Override - protected void execute(@Nonnull CommandContext context, - @Nonnull World world, - @Nonnull Store store) { - BackendId backendId = parseBackendId(context, backendArg, true); - if (backendId == null) { - return; - } - - PhysicsChunkCollisionMode physicsChunkMode = physicsChunkArg.provided(context) - ? parsePhysicsChunkMode(physicsChunkArg.get(context)) - : PhysicsChunkCollisionMode.STREAMING; - if (physicsChunkMode == null) { - context.sendMessage(Message.raw("physicsChunk must be none, manual, or streaming.")); - return; - } - - Store physicsStore = PhysicsThreading.store(world); - try { - Impulse.getRuntimeProvider(backendId); - SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId); - PhysicsChunkCollisionSettings chunkCollisionSettings = - new PhysicsChunkCollisionSettings(); - chunkCollisionSettings.setMode(physicsChunkMode); - PhysicsSpaces.putChunkCollisionSettings(physicsStore, - spaceId, - chunkCollisionSettings); - context.sendMessage(Message.raw("Created physics space id=" - + spaceId.value() - + " backend=" + backendId.value() - + " physicsChunk=" + physicsChunkMode.name().toLowerCase(Locale.ROOT) - + ".")); - } catch (RuntimeException exception) { - context.sendMessage(Message.raw("Failed to create physics space: " - + exception.getMessage())); - } - } - } - - private static final class ListCommand extends AbstractAsyncWorldCommand { - - private ListCommand() { - super("list", "List physics spaces in the target world", false); - } - - @Nonnull - @Override - protected CompletableFuture executeAsync(@Nonnull CommandContext context, - @Nonnull World world) { - Store physicsStore = PhysicsThreading.store(world); - return PhysicsAsync.acceptOnWorldThread(world, - PhysicsDiagnostics.spaceSummariesAsync(world), - summaries -> sendSpaces(context, world, physicsStore, summaries)); - } - - private static void sendSpaces(@Nonnull CommandContext context, - @Nonnull World world, - @Nonnull Store physicsStore, - @Nonnull List summaries) { - List spaces = summaries.stream() - .map(summary -> { - PhysicsChunkCollisionSettings settings = PhysicsSpaces.chunkCollisionSettings( - physicsStore, - summary.spaceId()); - PhysicsChunkCollisionMode physicsChunkMode = settings != null - ? settings.getMode() - : PhysicsChunkCollisionMode.NONE; - return new SpaceListEntry(summary.spaceId(), - summary.backendId().value(), - summary.bodyCount(), - summary.jointCount(), - physicsChunkMode); - }) - .sorted(Comparator.comparingInt(entry -> entry.spaceId().value())) - .toList(); - - context.sendMessage(Message.raw("Physics spaces in world " + world.getName() + ":")); - if (spaces.isEmpty()) { - context.sendMessage(Message.raw("- ")); - return; - } - - for (SpaceListEntry space : spaces) { - context.sendMessage(Message.raw("- id=" + space.spaceId().value() - + " backend=" + space.backendId() - + " bodies=" + space.bodies() - + " joints=" + space.joints() - + " physicsChunk=" - + space.physicsChunkMode().name().toLowerCase(Locale.ROOT))); - } - } - } - - private static final class DeleteCommand extends AbstractAsyncWorldCommand { - - private final OptionalArg spaceArg = withOptionalArg( - "space", - "Space id to delete", - ArgTypes.INTEGER); - - private DeleteCommand() { - super("delete", "Delete a physics space and its runtime backend state", true); - } - - @Nonnull - @Override - protected CompletableFuture executeAsync(@Nonnull CommandContext context, - @Nonnull World world) { - if (!spaceArg.provided(context)) { - context.sendMessage(Message.raw("Missing space id. Example:" - + " /impulse space delete --space=1 --confirm")); - return CompletableFuture.completedFuture(null); - } - - int rawSpaceId = spaceArg.get(context); - return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, - "delete PhysicsStore space", - physicsStore -> SpaceDeleteSupport.deleteOnWorldThread(world, - physicsStore, - rawSpaceId)) - .handle((result, failure) -> { - sendDeleteResult(context, world, result, failure); - return (Void) null; - }) - .toCompletableFuture(); - } - - private static void sendDeleteResult(@Nonnull CommandContext context, - @Nonnull World world, - @Nullable DeleteResult result, - @Nullable Throwable failure) { - Runnable sender = () -> { - if (failure != null) { - Throwable cause = unwrap(failure); - String message = cause.getMessage() != null - ? cause.getMessage() - : cause.toString(); - context.sendMessage(Message.raw("Failed to delete physics space: " - + message)); - return; - } - if (result == null) { - context.sendMessage(Message.raw("Failed to delete physics space.")); - return; - } - switch (result.outcome()) { - case INVALID -> context.sendMessage(Message.raw( - "Space id must be a positive integer.")); - case MISSING -> context.sendMessage(Message.raw("No physics space id=" - + result.rawSpaceId() - + " exists in world " + world.getName() + ".")); - case UNBOUND -> context.sendMessage(Message.raw("PhysicsStore space id=" - + result.rawSpaceId() - + " is not bound in world " + world.getName() + ".")); - case NOT_EMPTY -> context.sendMessage(Message.raw("Physics space id=" - + result.rawSpaceId() - + " is not empty (" + result.registeredBodies() - + " registered bodies, " + result.backendBodies() - + " backend bodies, " + result.joints() + " joints)." - + " Use /impulse clean for populated worlds, then delete empty spaces.")); - case DELETED -> context.sendMessage(Message.raw("Deleted physics space id=" - + result.rawSpaceId() - + " with " + result.backendBodies() - + " backend bodies and " + result.joints() + " joints.")); - } - }; - if (world.isInThread()) { - sender.run(); - return; - } - world.execute(sender); - } - } - - @Nullable - private static BackendId parseBackendId(@Nonnull CommandContext context, - @Nonnull OptionalArg backendArg, - boolean useDefaultWhenMissing) { - if (!backendArg.provided(context)) { - if (!useDefaultWhenMissing) { - return null; - } - - BackendId defaultBackendId = ImpulsePlugin.get().getDefaultBackendId(); - if (defaultBackendId != null) { - return defaultBackendId; - } - - context.sendMessage(Message.raw("Missing backend id. Multiple backends are installed; " - + "use --backend=. Available backends: " + availableBackendIds())); - return null; - } - - String rawBackendId = backendArg.get(context).trim(); - try { - return new BackendId(rawBackendId); - } catch (RuntimeException exception) { - context.sendMessage(Message.raw("Invalid backend id: " + rawBackendId)); - return null; - } - } - - @Nullable - private static PhysicsChunkCollisionMode parsePhysicsChunkMode(@Nonnull String value) { - return switch (value.toLowerCase(Locale.ROOT)) { - case "none", "off", "disabled" -> PhysicsChunkCollisionMode.NONE; - case "manual" -> PhysicsChunkCollisionMode.MANUAL; - case "streaming", "stream", "on", "enabled" -> PhysicsChunkCollisionMode.STREAMING; - default -> null; - }; - } - - @Nonnull - private static String availableBackendIds() { - List backendIds = new ArrayList<>(); - for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { - backendIds.add(provider.getId().value()); - } - backendIds.sort(String::compareTo); - return backendIds.isEmpty() ? "" : String.join(", ", backendIds); - } - - private record SpaceListEntry(@Nonnull SpaceId spaceId, - @Nonnull String backendId, - int bodies, - int joints, - @Nonnull PhysicsChunkCollisionMode physicsChunkMode) { - } - - @Nonnull - private static Throwable unwrap(@Nonnull Throwable failure) { - if (failure instanceof CompletionException completionException - && completionException.getCause() != null) { - return completionException.getCause(); - } - return failure; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java index b498ffa8..5e2ebfd4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java @@ -1,11 +1,15 @@ package dev.hytalemodding.impulse.core.internal.commands.debug; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nonnull; public class DebugCommand extends AbstractCommandCollection { - public DebugCommand() { + public DebugCommand(@Nonnull Collection moduleCommands) { super("debug", "Impulse debug rendering commands"); addSubCommand(new DebugToggleCommand()); addSubCommand(new DebugFlagCommand("shapes", "shape", @@ -20,8 +24,8 @@ public DebugCommand() { addSubCommand(new DebugFlagCommand("joints", "joint", PhysicsDebugResource::isDebugJointsEnabled, PhysicsDebugResource::setDebugJointsEnabled)); - addSubCommand(new DebugFlagCommand("physicschunk", "PhysicsChunk collision", - PhysicsDebugResource::isDebugPhysicsChunkCollisionEnabled, - PhysicsDebugResource::setDebugPhysicsChunkCollisionEnabled)); + for (AbstractCommand command : moduleCommands) { + addSubCommand(command); + } } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java index 75cb8668..ab092da7 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java @@ -8,19 +8,21 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Function; import javax.annotation.Nonnull; -final class DebugFlagCommand extends AbstractAsyncPlayerCommand { +public final class DebugFlagCommand extends AbstractAsyncPlayerCommand { private final String label; private final Function getter; private final BiConsumer setter; - DebugFlagCommand(@Nonnull String name, + public DebugFlagCommand(@Nonnull String name, @Nonnull String label, @Nonnull Function getter, @Nonnull BiConsumer setter) { @@ -37,8 +39,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - assert PhysicsDebugResource.getResourceType() != null; - PhysicsDebugResource resource = store.getResource(PhysicsDebugResource.getResourceType()); + Store physicsStore = PhysicsThreading.store(world); + PhysicsDebugResource resource = + physicsStore.getResource(PhysicsDebugResource.getResourceType()); boolean enabled = !getter.apply(resource); setter.accept(resource, enabled); ctx.sender().sendMessage(Message.raw("Impulse " + label + " debug " diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java index 3a76670b..79467cd2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugToggleCommand.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsDebugOverlayResource; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; @@ -26,8 +26,9 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world) { - assert PhysicsDebugResource.getResourceType() != null; - PhysicsDebugResource debug = store.getResource(PhysicsDebugResource.getResourceType()); + assert PhysicsDebugOverlayResource.getResourceType() != null; + PhysicsDebugOverlayResource debug = + store.getResource(PhysicsDebugOverlayResource.getResourceType()); boolean enabled; if (debug.removeSubscriber(playerRef.getUuid())) { enabled = false; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java index 5df2b62c..e4315aff 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/MaxStepDtSettingCommand.java @@ -39,8 +39,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, Store physicsStore = PhysicsThreading.store(world); if (!dtArg.provided(ctx)) { ctx.sender().sendMessage(Message.raw("Impulse max step dt: " - + PhysicsWorlds.settings(physicsStore).getMaxStepDt() - + " (used by adaptive step modes)")); + + PhysicsWorlds.settings(physicsStore).getMaxStepDt())); return CompletableFuture.completedFuture(null); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java index 68b3e501..ebaf2714 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/settings/SolverSettingsCommand.java @@ -10,7 +10,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; +import dev.hytalemodding.impulse.core.internal.commands.space.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java new file mode 100644 index 00000000..18cf5183 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java @@ -0,0 +1,85 @@ +package dev.hytalemodding.impulse.core.internal.commands.space; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.ImpulsePlugin; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletionException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpaceCommand extends AbstractCommandCollection { + + public SpaceCommand() { + super("space", "Explicit physics space lifecycle commands"); + addSubCommand(new SpaceCreateCommand()); + addSubCommand(new SpaceListCommand()); + addSubCommand(new SpaceDeleteCommand()); + } + + @Nullable + static BackendId parseBackendId(@Nonnull CommandContext context, + @Nonnull OptionalArg backendArg, + boolean useDefaultWhenMissing) { + if (!backendArg.provided(context)) { + if (!useDefaultWhenMissing) { + return null; + } + + BackendId defaultBackendId = ImpulsePlugin.get().getDefaultBackendId(); + if (defaultBackendId != null) { + return defaultBackendId; + } + + context.sendMessage(Message.raw("Missing backend id. Multiple backends are installed; " + + "use --backend=. Available backends: " + availableBackendIds())); + return null; + } + + String rawBackendId = backendArg.get(context).trim(); + try { + return new BackendId(rawBackendId); + } catch (RuntimeException exception) { + context.sendMessage(Message.raw("Invalid backend id: " + rawBackendId)); + return null; + } + } + + @Nullable + static PhysicsChunkCollisionMode parsePhysicsChunkMode(@Nonnull String value) { + return switch (value.toLowerCase(Locale.ROOT)) { + case "none", "off", "disabled" -> PhysicsChunkCollisionMode.NONE; + case "manual" -> PhysicsChunkCollisionMode.MANUAL; + case "streaming", "stream", "on", "enabled" -> PhysicsChunkCollisionMode.STREAMING; + default -> null; + }; + } + + @Nonnull + private static String availableBackendIds() { + List backendIds = new ArrayList<>(); + for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { + backendIds.add(provider.getId().value()); + } + backendIds.sort(String::compareTo); + return backendIds.isEmpty() ? "" : String.join(", ", backendIds); + } + + + @Nonnull + static Throwable unwrap(@Nonnull Throwable failure) { + if (failure instanceof CompletionException completionException + && completionException.getCause() != null) { + return completionException.getCause(); + } + return failure; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java new file mode 100644 index 00000000..3076fd86 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java @@ -0,0 +1,75 @@ +package dev.hytalemodding.impulse.core.internal.commands.space; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import javax.annotation.Nonnull; +import java.util.Locale; + +public class SpaceCreateCommand extends AbstractWorldCommand { + + private final OptionalArg backendArg = withOptionalArg( + "backend", + "Backend id, for example impulse:rapier", + ArgTypes.STRING); + private final OptionalArg physicsChunkArg = withOptionalArg( + "physicsChunk", + "PhysicsChunk collision mode: none, manual, or streaming", + ArgTypes.STRING); + + SpaceCreateCommand() { + super("create", "Create an explicit physics space", false); + } + + @Override + protected void execute(@Nonnull CommandContext context, + @Nonnull World world, + @Nonnull Store store) { + BackendId backendId = SpaceCommand.parseBackendId(context, backendArg, true); + if (backendId == null) { + return; + } + + PhysicsChunkCollisionMode physicsChunkMode = physicsChunkArg.provided(context) + ? SpaceCommand.parsePhysicsChunkMode(physicsChunkArg.get(context)) + : PhysicsChunkCollisionMode.STREAMING; + if (physicsChunkMode == null) { + context.sendMessage( + Message.raw("physicsChunk must be none, manual, or streaming.")); + return; + } + + Store physicsStore = PhysicsThreading.store(world); + try { + Impulse.getRuntimeProvider(backendId); + SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId); + PhysicsChunkCollisionSettings chunkCollisionSettings = + new PhysicsChunkCollisionSettings(); + chunkCollisionSettings.setMode(physicsChunkMode); + PhysicsSpaces.putChunkCollisionSettings(physicsStore, + spaceId, + chunkCollisionSettings); + context.sendMessage(Message.raw("Created physics space id=" + + spaceId.value() + + " backend=" + backendId.value() + + " physicsChunk=" + physicsChunkMode.name().toLowerCase(Locale.ROOT) + + ".")); + } catch (RuntimeException exception) { + context.sendMessage(Message.raw("Failed to create physics space: " + + exception.getMessage())); + } + } +} \ No newline at end of file diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceDeleteCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceDeleteCommand.java new file mode 100644 index 00000000..22feebbf --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceDeleteCommand.java @@ -0,0 +1,94 @@ +package dev.hytalemodding.impulse.core.internal.commands.space; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import dev.hytalemodding.impulse.core.internal.commands.space.SpaceDeleteSupport.DeleteResult; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.concurrent.CompletableFuture; + +public class SpaceDeleteCommand extends AbstractAsyncWorldCommand { + + private final OptionalArg spaceArg = withOptionalArg( + "space", + "Space id to delete", + ArgTypes.INTEGER); + + SpaceDeleteCommand() { + super("delete", "Delete a physics space and its runtime backend state", true); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context, + @Nonnull World world) { + if (!spaceArg.provided(context)) { + context.sendMessage(Message.raw("Missing space id. Example:" + + " /impulse space delete --space=1 --confirm")); + return CompletableFuture.completedFuture(null); + } + + int rawSpaceId = spaceArg.get(context); + return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, + "delete PhysicsStore space", + physicsStore -> SpaceDeleteSupport.deleteOnWorldThread(world, + physicsStore, + rawSpaceId)) + .handle((result, failure) -> { + sendDeleteResult(context, world, result, failure); + return (Void) null; + }) + .toCompletableFuture(); + } + + private static void sendDeleteResult(@Nonnull CommandContext context, + @Nonnull World world, + @Nullable DeleteResult result, + @Nullable Throwable failure) { + Runnable sender = () -> { + if (failure != null) { + Throwable cause = SpaceCommand.unwrap(failure); + String message = cause.getMessage() != null + ? cause.getMessage() + : cause.toString(); + context.sendMessage(Message.raw("Failed to delete physics space: " + + message)); + return; + } + if (result == null) { + context.sendMessage(Message.raw("Failed to delete physics space.")); + return; + } + switch (result.outcome()) { + case INVALID -> context.sendMessage(Message.raw( + "Space id must be a positive integer.")); + case MISSING -> context.sendMessage(Message.raw("No physics space id=" + + result.rawSpaceId() + + " exists in world " + world.getName() + ".")); + case UNBOUND -> context.sendMessage(Message.raw("PhysicsStore space id=" + + result.rawSpaceId() + + " is not bound in world " + world.getName() + ".")); + case NOT_EMPTY -> context.sendMessage(Message.raw("Physics space id=" + + result.rawSpaceId() + + " is not empty (" + result.registeredBodies() + + " registered bodies, " + result.backendBodies() + + " backend bodies, " + result.joints() + " joints)." + + " Use /impulse clean for populated worlds, then delete empty spaces.")); + case DELETED -> context.sendMessage(Message.raw("Deleted physics space id=" + + result.rawSpaceId() + + " with " + result.backendBodies() + + " backend bodies and " + result.joints() + " joints.")); + } + }; + if (world.isInThread()) { + sender.run(); + return; + } + world.execute(sender); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceDeleteSupport.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceDeleteSupport.java index c1d8e2bb..e24dc750 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceDeleteSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceDeleteSupport.java @@ -1,11 +1,10 @@ -package dev.hytalemodding.impulse.core.internal.commands; +package dev.hytalemodding.impulse.core.internal.commands.space; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; @@ -42,8 +41,7 @@ static DeleteResult deleteOnWorldThread( UUID spaceUuid = compatibility.getSpaceUuid(spaceId); Ref spaceRef = spaceUuid != null - ? physicsStore.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid) + ? physicsStore.getExternalData().getRefFromUUID(spaceUuid) : null; if (spaceRef == null || spaceRef.getStore() != physicsStore || !spaceRef.isValid()) { return DeleteResult.unbound(rawSpaceId); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceListCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceListCommand.java new file mode 100644 index 00000000..4c6ab34d --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceListCommand.java @@ -0,0 +1,82 @@ +package dev.hytalemodding.impulse.core.internal.commands.space; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsAsync; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.SpaceSummary; +import javax.annotation.Nonnull; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; + +public class SpaceListCommand extends AbstractAsyncWorldCommand { + + SpaceListCommand() { + super("list", "List physics spaces in the target world", false); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context, + @Nonnull World world) { + Store physicsStore = PhysicsThreading.store(world); + return PhysicsAsync.acceptOnWorldThread(world, + PhysicsDiagnostics.spaceSummariesAsync(world), + summaries -> sendSpaces(context, world, physicsStore, summaries)); + } + + private static void sendSpaces(@Nonnull CommandContext context, + @Nonnull World world, + @Nonnull Store physicsStore, + @Nonnull List summaries) { + List spaces = summaries.stream() + .map(summary -> { + PhysicsChunkCollisionSettings settings = PhysicsSpaces.chunkCollisionSettings( + physicsStore, + summary.spaceId()); + PhysicsChunkCollisionMode physicsChunkMode = settings != null + ? settings.getMode() + : PhysicsChunkCollisionMode.NONE; + return new SpaceListEntry(summary.spaceId(), + summary.backendId().value(), + summary.bodyCount(), + summary.jointCount(), + physicsChunkMode); + }) + .sorted(Comparator.comparingInt(entry -> entry.spaceId().value())) + .toList(); + + context.sendMessage(Message.raw("Physics spaces in world " + world.getName() + ":")); + if (spaces.isEmpty()) { + context.sendMessage(Message.raw("- ")); + return; + } + + for (SpaceListEntry space : spaces) { + context.sendMessage(Message.raw("- id=" + space.spaceId().value() + + " backend=" + space.backendId() + + " bodies=" + space.bodies() + + " joints=" + space.joints() + + " physicsChunk=" + + space.physicsChunkMode().name().toLowerCase(Locale.ROOT))); + } + } + + private record SpaceListEntry(@Nonnull SpaceId spaceId, + @Nonnull String backendId, + int bodies, + int joints, + @Nonnull PhysicsChunkCollisionMode physicsChunkMode) { + } +} \ No newline at end of file diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceSelection.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceSelection.java index 648c5a44..e6b3e009 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/SpaceSelection.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceSelection.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.commands; +package dev.hytalemodding.impulse.core.internal.commands.space; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -8,7 +8,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.Comparator; @@ -43,7 +42,7 @@ public static SelectedSpace resolveStoreSpace(@Nonnull CommandContext context, UUID spaceUuid = compatibility.getSpaceUuid(spaceId); Ref spaceRef = spaceUuid != null - ? store.getResource(PhysicsIdentityIndexResource.getResourceType()).getByUuid(spaceUuid) + ? store.getExternalData().getRefFromUUID(spaceUuid) : null; if (spaceRef == null || spaceRef.getStore() != store || !spaceRef.isValid()) { context.sendMessage(Message.raw("PhysicsStore space id=" + spaceId.value() diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java index 05c5b391..54d5ef75 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java @@ -1,6 +1,8 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandTreeRegistry; +import dev.hytalemodding.impulse.core.internal.commands.debug.DebugFlagCommand; +import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; /** * Command set owned by the PhysicsChunk subplugin. @@ -8,6 +10,7 @@ public final class PhysicsChunkCommandSet { private static final String PHYSICS_CHUNK_ROOT_COMMAND_ID = "physicschunk.root"; + private static final String PHYSICS_CHUNK_DEBUG_COMMAND_ID = "physicschunk.debug"; private static final String COLLISION_LOD_SETTINGS_COMMAND_ID = "physicschunk.settings.collision-lod"; @@ -20,11 +23,21 @@ public static void register() { PhysicsChunkCommand::new, COLLISION_LOD_SETTINGS_COMMAND_ID, CollisionLodSettingsCommand::new); + ImpulseCommandTreeRegistry.registerDebugSubCommand( + PHYSICS_CHUNK_DEBUG_COMMAND_ID, + PhysicsChunkCommandSet::debugCommand); } public static void unregister() { ImpulseCommandTreeRegistry.unregisterRootAndSettingsSubCommands( PHYSICS_CHUNK_ROOT_COMMAND_ID, COLLISION_LOD_SETTINGS_COMMAND_ID); + ImpulseCommandTreeRegistry.unregisterDebugSubCommand(PHYSICS_CHUNK_DEBUG_COMMAND_ID); + } + + private static DebugFlagCommand debugCommand() { + return new DebugFlagCommand("physicschunk", "PhysicsChunk collision", + PhysicsDebugResource::isDebugPhysicsChunkCollisionEnabled, + PhysicsDebugResource::setDebugPhysicsChunkCollisionEnabled); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java index 31a51832..20fb227d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -82,7 +81,7 @@ private static Ref addBodyIdentityRow(@Nonnull Store Ref ref = store.addEntity(PhysicsEntities.entityHolder(store, bodyUuid), AddReason.SPAWN); - store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(bodyUuid, + store.getExternalData().putRefForUUID(bodyUuid, ref); if (generatedChunkCollisionBody) { store.putComponent(ref, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java index 66559e37..93637bd1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java @@ -18,10 +18,15 @@ void coreRootDoesNotOwnPhysicsChunkCommandsByDefault() { ImpulseCommand root = ImpulseCommandTreeRegistry.createRootCommandForTests(); assertFalse(root.getSubCommands().containsKey("physicschunk")); + assertFalse(debug(root).getSubCommands().containsKey("physicschunk")); assertFalse(settings(root).getSubCommands().containsKey("collision-lod")); assertFalse(settings(root).getSubCommands().containsKey("visual")); } + private static AbstractCommand debug(ImpulseCommand root) { + return root.getSubCommands().get("debug"); + } + private static AbstractCommand settings(ImpulseCommand root) { return root.getSubCommands().get("settings"); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandSetTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandSetTest.java index b0a9c637..d7be4b24 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandSetTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/PhysicsChunkCommandSetTest.java @@ -25,6 +25,7 @@ void physicsChunkRegistersCommandSetUnderImpulseRoot() { assertTrue(root.getSubCommands().containsKey("physicschunk")); assertTrue(physicsChunk.getSubCommands().containsKey("settings")); assertTrue(physicsChunk.getSubCommands().containsKey("perf")); + assertTrue(debug(root).getSubCommands().containsKey("physicschunk")); assertTrue(settings(root).getSubCommands().containsKey("collision-lod")); } @@ -35,15 +36,21 @@ void physicsChunkCommandSetIsIdempotentAndRemovable() { ImpulseCommand registered = ImpulseCommandTreeRegistry.createRootCommandForTests(); assertTrue(registered.getSubCommands().containsKey("physicschunk")); + assertTrue(debug(registered).getSubCommands().containsKey("physicschunk")); assertTrue(settings(registered).getSubCommands().containsKey("collision-lod")); PhysicsChunkCommandSet.unregister(); ImpulseCommand removed = ImpulseCommandTreeRegistry.createRootCommandForTests(); assertFalse(removed.getSubCommands().containsKey("physicschunk")); + assertFalse(debug(removed).getSubCommands().containsKey("physicschunk")); assertFalse(settings(removed).getSubCommands().containsKey("collision-lod")); } + private static AbstractCommand debug(ImpulseCommand root) { + return root.getSubCommands().get("debug"); + } + private static AbstractCommand settings(ImpulseCommand root) { return root.getSubCommands().get("settings"); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommandDeleteTest.java similarity index 96% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommandDeleteTest.java index 9c524bce..5f04d1af 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/SpaceCommandDeleteTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommandDeleteTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.commands; +package dev.hytalemodding.impulse.core.internal.commands.space; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -20,7 +20,6 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -168,8 +167,7 @@ private static void bindSpaceId(@Nonnull Store store, @Nonnull Ref spaceRef) { store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .putSpace(spaceId, spaceUuid); - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); } @Nonnull @@ -227,8 +225,7 @@ private static Ref addChunkCollisionBody(@Nonnull Store Date: Mon, 22 Jun 2026 14:36:52 +0200 Subject: [PATCH 514/534] fix(control): prevent stealing actively controlled bodies Signed-off-by: Blovien --- .../PhysicsStoreControlSessionMutations.java | 25 +-- .../control/PhysicsControlSessions.java | 40 +++++ .../modules/control/ControlLifecycleTest.java | 126 ++++++++++++++- ...ysicsStoreControlSessionMutationsTest.java | 144 +++++++++++++++++- .../examples/commands/GrabCommand.java | 8 + 5 files changed, 323 insertions(+), 20 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java index 116f1c9f..10c4c7f2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java @@ -8,10 +8,9 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; @@ -38,7 +37,7 @@ public static void applyRelease(@Nonnull Store store, Ref controlJointRef = session.getControlJointRef(); if (controlJointRef != null) { - disableJoint(physicsStore, controlJointRef); + removeJointRow(physicsStore, controlJointRef); } Ref bodyRef = session.getBodyRef(); @@ -71,18 +70,19 @@ private static void restoreControlledBody(@Nonnull Store store, BodyCommandComponent.setVelocity(releaseVelocity, ZERO, true)); } - private static void disableJoint(@Nonnull Store store, + private static void removeJointRow(@Nonnull Store store, @Nonnull Ref ref) { if (!isValidStoreRef(store, ref)) { return; } - JointComponent joint = store.getComponent(ref, JointComponent.getComponentType()); - if (joint == null) { + UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); + if (uuid == null) { + removeRow(store, ref); return; } - JointComponent disabled = joint.clone(); - disabled.setEnabled(false); - store.putComponent(ref, JointComponent.getComponentType(), disabled); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsStoreRowCleanup.removeRuntimeJoint(store, runtime, uuid.getUuid(), ref); + PhysicsStoreRowCleanup.removeJointEntity(store, uuid.getUuid(), ref); } private static void removeBodyRow(@Nonnull Store store, @@ -95,7 +95,9 @@ private static void removeBodyRow(@Nonnull Store store, removeRow(store, ref); return; } - PhysicsStoreRowCleanup.removeBodyEntity(store, uuid.getUuid(), ref, null); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + PhysicsStoreRowCleanup.removeRuntimeBody(store, runtime, uuid.getUuid(), ref, null); + PhysicsStoreRowCleanup.removeBodyEntity(store, uuid.getUuid(), ref); } private static void removeRow(@Nonnull Store store, @@ -105,8 +107,7 @@ private static void removeRow(@Nonnull Store store, } UuidComponent uuid = store.getComponent(ref, UuidComponent.getComponentType()); if (uuid != null) { - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .removeUuid(uuid.getUuid(), ref); + store.getExternalData().removeRefForUUID(uuid.getUuid(), ref); } store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java index af3c1b57..a8237413 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java @@ -48,6 +48,33 @@ public static boolean hasSession(@Nonnull Store store, return session != null && session.isActive(); } + /** + * Returns whether the PhysicsStore body is currently driven by any active Impulse control + * session. + */ + public static boolean isBodyControlled(@Nullable Ref bodyRef) { + if (!ControlLifecycle.isEnabled()) { + return false; + } + return PhysicsControlRuntimeStates.isControlled(bodyRef); + } + + /** + * Returns whether the controller's active session targets the supplied PhysicsStore body. + */ + public static boolean hasSessionForBody(@Nonnull Store store, + @Nonnull Ref controllerRef, + @Nullable Ref bodyRef) { + if (!isAvailable()) { + return false; + } + PhysicsControlSessionComponent session = + store.getComponent(controllerRef, PhysicsControlSessionComponent.getComponentType()); + return session != null + && session.isActive() + && samePhysicsStoreRef(session.getBodyRef(), bodyRef); + } + /** * Starts or replaces the controller entity's Impulse control session from durable entity UUIDs. * Prefer the ref overload when the caller already has live PhysicsStore entity refs. @@ -102,6 +129,9 @@ public static void startSession(@Nonnull Store store, ComponentType sessionType = PhysicsControlSessionComponent.getComponentType(); releaseSession(store, controllerRef, sessionType); + if (PhysicsControlRuntimeStates.isControlled(bodyRef)) { + throw new IllegalStateException("PhysicsStore body is already controlled"); + } store.putComponent(controllerRef, sessionType, new PhysicsControlSessionComponent(bodyRef, @@ -201,6 +231,16 @@ private static void requireValidRef(@Nonnull Ref ref, } } + private static boolean samePhysicsStoreRef(@Nullable Ref first, + @Nullable Ref second) { + return first != null + && second != null + && first.getStore() == second.getStore() + && first.isValid() + && second.isValid() + && first.getIndex() == second.getIndex(); + } + private static void requireAvailable() { ControlLifecycle.requireEnabled(); if (!ImpulseControllableComponent.isComponentTypeRegistered() diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java index d0608ece..c620443b 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java @@ -2,16 +2,20 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; @@ -19,6 +23,7 @@ import java.lang.reflect.Method; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.joml.Vector3f; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -68,6 +73,102 @@ void disablingLifecycleClearsRegisteredControlledBodies() { registry.shutdown(); } + @Test + void publicFacadeReadsPhysicsStoreBodyControlState() { + ControlLifecycle.enable(); + World world = TestInstanceFactory.world("control-body-state-world"); + ComponentRegistry registry = new ComponentRegistry<>(); + Store store = registry.addStore( + new PhysicsStore(world), + EmptyResourceStorage.get()); + Ref bodyRef = new TestPhysicsRef(store, 7); + try { + runOnWorldThread(world, () -> { + assertFalse(PhysicsControlSessions.isBodyControlled(bodyRef)); + + PhysicsControlRuntimeStates.markControlled(bodyRef); + + assertTrue(PhysicsControlSessions.isBodyControlled(bodyRef)); + }); + } finally { + registry.shutdown(); + } + } + + @Test + void publicFacadeMatchesControllerSessionBody() { + ControlLifecycle.enable(); + ComponentRegistry entityRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(entityRegistry); + World world = TestInstanceFactory.world("control-session-body-world"); + ComponentRegistry physicsRegistry = new ComponentRegistry<>(); + Store physicsStore = physicsRegistry.addStore( + new PhysicsStore(world), + EmptyResourceStorage.get()); + Store entityStore = entityRegistry.addStore(new EntityStore(world), + EmptyResourceStorage.get()); + Ref controllerRef = addEmptyEntity(entityStore); + Ref otherControllerRef = addEmptyEntity(entityStore); + Ref bodyRef = new TestPhysicsRef(physicsStore, 7); + Ref otherBodyRef = new TestPhysicsRef(physicsStore, 8); + Ref anchorRef = new TestPhysicsRef(physicsStore, 9); + try { + entityStore.putComponent(controllerRef, + PhysicsControlSessionComponent.getComponentType(), + session(bodyRef, anchorRef)); + + assertTrue(PhysicsControlSessions.hasSessionForBody(entityStore, + controllerRef, + bodyRef)); + assertFalse(PhysicsControlSessions.hasSessionForBody(entityStore, + controllerRef, + otherBodyRef)); + assertFalse(PhysicsControlSessions.hasSessionForBody(entityStore, + otherControllerRef, + bodyRef)); + } finally { + entityRegistry.shutdown(); + physicsRegistry.shutdown(); + } + } + + @Test + void startSessionRejectsBodyControlledByAnotherController() { + ControlLifecycle.enable(); + ComponentRegistry entityRegistry = new ComponentRegistry<>(); + ControlTypeRegistry.registerComponentTypes(entityRegistry); + World world = TestInstanceFactory.world("control-session-reject-world"); + ComponentRegistry physicsRegistry = new ComponentRegistry<>(); + Store physicsStore = physicsRegistry.addStore( + new PhysicsStore(world), + EmptyResourceStorage.get()); + Store entityStore = entityRegistry.addStore(new EntityStore(world), + EmptyResourceStorage.get()); + Ref controllerRef = addEmptyEntity(entityStore); + Ref bodyRef = new TestPhysicsRef(physicsStore, 7); + Ref anchorRef = new TestPhysicsRef(physicsStore, 8); + try { + runOnWorldThread(world, () -> { + PhysicsControlRuntimeStates.markControlled(bodyRef); + + assertThrows(IllegalStateException.class, + () -> PhysicsControlSessions.startSession(entityStore, + controllerRef, + bodyRef, + anchorRef, + null, + null, + PhysicsBodyType.DYNAMIC, + 4.0f, + new Vector3f(), + new Vector3f())); + }); + } finally { + entityRegistry.shutdown(); + physicsRegistry.shutdown(); + } + } + @Test void controlSessionsAreAvailableOnlyWhenLifecycleAndComponentTypesAreRegistered() { assertFalse(PhysicsControlSessions.isAvailable()); @@ -101,6 +202,29 @@ void disablingLifecycleSkipsStoresWhoseWorldThreadHasStopped() { registry.shutdown(); } + @Nonnull + private static Ref addEmptyEntity(@Nonnull Store store) { + Holder holder = store.getRegistry().newHolder(); + Ref ref = store.addEntity(holder, AddReason.SPAWN); + if (ref == null) { + throw new AssertionError("Failed to add test entity"); + } + return ref; + } + + @Nonnull + private static PhysicsControlSessionComponent session(@Nonnull Ref bodyRef, + @Nonnull Ref anchorRef) { + return new PhysicsControlSessionComponent(bodyRef, + anchorRef, + null, + null, + PhysicsBodyType.DYNAMIC, + 4.0f, + new Vector3f(), + new Vector3f()); + } + private static final class TestPhysicsRef extends Ref { private TestPhysicsRef(Store store, int index) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java index 45a4ffbf..6a25def6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java @@ -1,5 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.control.systems; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -20,10 +21,17 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendJointType; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -62,7 +70,7 @@ class PhysicsStoreControlSessionMutationsTest { private static final SpaceId SPACE_ID = new SpaceId(42); @Test - void releaseClearsTemporaryAnchorBodyCopiedState() { + void releaseClearsTemporarySessionRuntimeAndCopiedState() { StoreFixture fixture = store("control-release-anchor-cleanup"); Store physicsStore = fixture.physicsStore(); Store entityStore = fixture.entityStore(); @@ -73,6 +81,7 @@ void releaseClearsTemporaryAnchorBodyCopiedState() { UUID anchorBodyUuid = uuid(3); UUID controlJointUuid = uuid(4); Ref spaceRef = addSpace(physicsStore, spaceUuid); + BoundSpace space = bindSpace(physicsStore, spaceUuid, spaceRef); Ref controlledBodyRef = addBody(physicsStore, spaceUuid, spaceRef, @@ -94,6 +103,24 @@ void releaseClearsTemporaryAnchorBodyCopiedState() { controlledBodyRef, anchorBodyUuid, anchorBodyRef); + BackendBodyHandle controlledBodyHandle = bindBody(physicsStore, + space, + controlledBodyUuid, + controlledBodyRef, + 0.0f); + BackendBodyHandle anchorBodyHandle = bindBody(physicsStore, + space, + anchorBodyUuid, + anchorBodyRef, + 1.0f); + bindJoint(physicsStore, + space, + controlJointUuid, + controlJointRef, + anchorBodyHandle, + controlledBodyHandle); + assertEquals(2, space.runtime().bodyCount(space.handle().value())); + assertEquals(1, space.runtime().jointCount(space.handle().value())); PhysicsControlSessionComponent session = new PhysicsControlSessionComponent( controlledBodyRef, @@ -107,9 +134,17 @@ void releaseClearsTemporaryAnchorBodyCopiedState() { PhysicsStoreControlSessionMutations.applyRelease(entityStore, session); + PhysicsRuntimeResource runtime = + physicsStore.getResource(PhysicsRuntimeResource.getResourceType()); assertFalse(anchorBodyRef.isValid()); - assertNull(physicsStore.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(anchorBodyUuid)); + assertFalse(controlJointRef.isValid()); + assertNull(physicsStore.getExternalData().getRefFromUUID(anchorBodyUuid)); + assertNull(physicsStore.getExternalData().getRefFromUUID(controlJointUuid)); + assertNull(runtime.getBodyHandle(anchorBodyRef)); + assertNull(runtime.getJointHandle(controlJointRef)); + assertNotNull(runtime.getBodyHandle(controlledBodyRef)); + assertEquals(1, space.runtime().bodyCount(space.handle().value())); + assertEquals(0, space.runtime().jointCount(space.handle().value())); assertNull(physicsStore.getResource(PhysicsSnapshotResource.getResourceType()) .getBody(anchorBodyUuid)); assertFalse(PhysicsBodies.isRegistered(physicsStore, anchorBodyUuid)); @@ -148,13 +183,27 @@ private static Ref addSpace(@Nonnull Store store, new SpaceComponent(BACKEND_ID, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(ref); - store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(spaceUuid, ref); store.getExternalData().putRefForUUID(spaceUuid, ref); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .putSpace(SPACE_ID, spaceUuid); return ref; } + @Nonnull + private static BoundSpace bindSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull Ref spaceRef) { + PhysicsBackendRuntime backendRuntime = + new FakePhysicsBackendRuntimeProvider(BACKEND_ID, false, false).createRuntime(); + BackendSpaceHandle spaceHandle = + new BackendSpaceHandle(backendRuntime.createSpace(SPACE_ID)); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putRuntime(BACKEND_ID, backendRuntime); + runtime.putSpaceHandle(spaceRef, BACKEND_ID, spaceHandle); + runtime.putSpaceMetadata(BACKEND_ID, spaceHandle, spaceUuid, spaceRef); + return new BoundSpace(spaceUuid, spaceRef, backendRuntime, spaceHandle); + } + @Nonnull private static Ref addBody(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -182,7 +231,6 @@ private static Ref addBody(@Nonnull Store store, new CollisionFilterComponent(0x01, 0x02)), AddReason.SPAWN); assertNotNull(ref); - store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(bodyUuid, ref); store.getExternalData().putRefForUUID(bodyUuid, ref); return ref; } @@ -205,11 +253,86 @@ private static Ref addJoint(@Nonnull Store store, joint), AddReason.SPAWN); assertNotNull(ref); - store.getResource(PhysicsIdentityIndexResource.getResourceType()).putUuid(jointUuid, ref); store.getExternalData().putRefForUUID(jointUuid, ref); return ref; } + @Nonnull + private static BackendBodyHandle bindBody(@Nonnull Store store, + @Nonnull BoundSpace space, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef, + float positionX) { + long bodyId = space.runtime().createBody(space.handle().value(), + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + BackendRuntimeCodes.axisCode(PhysicsAxis.Y), + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + positionX, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + BackendBodyHandle handle = new BackendBodyHandle(bodyId); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putBodyHandle(bodyRef, space.ref(), space.handle(), handle); + runtime.putBodySnapshotMetadata(BACKEND_ID, + space.handle(), + handle, + bodyUuid, + bodyRef, + space.uuid()); + runtime.putBodyHitMetadata(BACKEND_ID, + space.handle(), + handle, + bodyUuid, + bodyRef, + PhysicsBodyType.DYNAMIC, + ShapeType.BOX); + return handle; + } + + private static void bindJoint(@Nonnull Store store, + @Nonnull BoundSpace space, + @Nonnull UUID jointUuid, + @Nonnull Ref jointRef, + @Nonnull BackendBodyHandle bodyAHandle, + @Nonnull BackendBodyHandle bodyBHandle) { + long jointId = space.runtime().createJoint(space.handle().value(), + BackendRuntimeCodes.jointTypeCode(BackendJointType.POINT), + bodyAHandle.value(), + bodyBHandle.value(), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + false, + 0.0f, + 0.0f); + BackendJointHandle handle = new BackendJointHandle(jointId); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + runtime.putJointHandle(jointRef, space.ref(), space.handle(), handle); + runtime.putJointMetadata(BACKEND_ID, space.handle(), handle, jointUuid, jointRef); + } + private static void publishCopiedState(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull UUID controlledBodyUuid, @@ -296,6 +419,13 @@ public PhysicsStore getPhysicsStore() { } } + private record BoundSpace( + @Nonnull UUID uuid, + @Nonnull Ref ref, + @Nonnull PhysicsBackendRuntime runtime, + @Nonnull BackendSpaceHandle handle) { + } + private record StoreFixture( @Nonnull ComponentRegistry physicsRegistry, @Nonnull ComponentRegistry entityRegistry, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 8168d7b3..8384f82e 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -131,6 +131,7 @@ private static void finishGrab(@Nonnull CommandContext ctx, } HitSelection selection = selectControllableHit(physicsStore, store, + ref, controllableType, hits); if (selection == null) { @@ -249,6 +250,7 @@ private static JointComponent controlJoint(@Nonnull Ref spaceRef, @Nullable private static HitSelection selectControllableHit(@Nonnull Store physicsStore, @Nonnull Store store, + @Nonnull Ref controllerRef, @Nonnull ComponentType controllableType, @Nonnull List hits) { List candidates = new ArrayList<>(hits.size()); @@ -271,6 +273,12 @@ private static HitSelection selectControllableHit(@Nonnull Store p } HitSelection best = null; for (HitCandidate candidate : candidates) { + if (PhysicsControlSessions.isBodyControlled(candidate.bodyRef()) + && !PhysicsControlSessions.hasSessionForBody(store, + controllerRef, + candidate.bodyRef())) { + continue; + } AttachmentSelection attachments = inspectGameplayAttachments(store, controllableType, candidate.bodyRef()); if (attachments.controllableAttachment() == null && attachments.hasGameplayAttachment()) { From e422ed7a30fb61d6051bc00e17f3a9e0a946483a Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:37:42 +0200 Subject: [PATCH 515/534] refactor(core): move module-owned runtime types into owning packages Signed-off-by: Blovien --- ...csChunkCollisionMutationQueueResource.java | 2 +- .../PhysicsChunkCollisionPayloadResource.java | 2 +- .../PhysicsChunkComponentSyncResource.java | 2 +- .../PhysicsChunkSettingsIndexResource.java | 2 +- .../ChunkCollisionComponentSyncSystem.java | 8 +- .../ChunkCollisionMutationDrainSystem.java | 70 +++++------- ...java => ChunkCollisionProducerSystem.java} | 15 ++- .../ChunkCollisionVoxelStitchingSystem.java | 29 +++-- .../PhysicsChunkSettingsIndexSystem.java | 10 +- .../PhysicsEntityTypeRegistry.java | 15 +-- .../VisualMaterializationSettingsCommand.java | 2 +- .../commands/VisualSyncSettingsCommand.java | 2 +- .../PhysicsBodySyncStateResource.java | 2 +- .../PhysicsDebugOverlayResource.java} | 38 ++----- .../visual/GeneratedProxyLifecycle.java | 5 +- .../PhysicsProjectionCleanupSystem.java | 2 +- .../visual/VisualInterestCollector.java | 2 +- .../CompletedStepPublicationSystem.java | 3 +- .../{ => step}/StepSubmissionSystem.java | 5 +- .../components/package-info.java | 4 - .../modules/physicsentity/package-info.java | 8 +- .../physicsentity/settings/package-info.java | 4 - ...ChunkCollisionComponentSyncSystemTest.java | 12 +-- ...ChunkCollisionMutationDrainSystemTest.java | 101 +++++++++--------- ...hunkCollisionVoxelStitchingSystemTest.java | 17 +-- .../resources/PhysicsDebugResourceTest.java | 9 +- .../PhysicsDebugOverlayResourceTest.java | 39 +++++++ .../PhysicsEntityProjectionResourcesTest.java | 1 + .../PhysicsStoreResourceIndexTest.java | 14 +-- .../CompletedStepPublicationSystemTest.java | 1 + .../systems/StepSubmissionSystemTest.java | 1 + .../PhysicsProjectionCleanupSystemTest.java | 1 + 32 files changed, 215 insertions(+), 213 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/resources/PhysicsChunkCollisionMutationQueueResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/resources/PhysicsChunkCollisionPayloadResource.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/resources/PhysicsChunkComponentSyncResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/resources/PhysicsChunkSettingsIndexResource.java (97%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/systems/ChunkCollisionComponentSyncSystem.java (94%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/systems/ChunkCollisionMutationDrainSystem.java (89%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/{PhysicsChunkCollisionProducerSystem.java => ChunkCollisionProducerSystem.java} (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/systems/ChunkCollisionVoxelStitchingSystem.java (88%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/systems/PhysicsChunkSettingsIndexSystem.java (86%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/resources/PhysicsBodySyncStateResource.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{resources/PhysicsDebugResource.java => modules/physicsentity/resources/PhysicsDebugOverlayResource.java} (78%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/visual/GeneratedProxyLifecycle.java (93%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/visual/PhysicsProjectionCleanupSystem.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicsentity}/systems/visual/VisualInterestCollector.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{ => publication}/CompletedStepPublicationSystem.java (96%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/{ => step}/StepSubmissionSystem.java (99%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/systems/ChunkCollisionComponentSyncSystemTest.java (96%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/systems/ChunkCollisionMutationDrainSystemTest.java (93%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{ => modules/physicschunk}/systems/ChunkCollisionVoxelStitchingSystemTest.java (95%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/{ => physics}/resources/PhysicsDebugResourceTest.java (66%) create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugOverlayResourceTest.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkCollisionMutationQueueResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkCollisionMutationQueueResource.java index 981482a9..e1114793 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionMutationQueueResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkCollisionMutationQueueResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkCollisionPayloadResource.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkCollisionPayloadResource.java index 65c41570..67fb6847 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkCollisionPayloadResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkCollisionPayloadResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkComponentSyncResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkComponentSyncResource.java index 1c3a97f7..8306e3e9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkComponentSyncResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkComponentSyncResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkSettingsIndexResource.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkSettingsIndexResource.java index eb0fdd2c..aed4f9d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsChunkSettingsIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/resources/PhysicsChunkSettingsIndexResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystem.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystem.java index cb860572..7eebfc5d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -15,9 +15,11 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource.ChunkCollisionSurfaceComponents; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkComponentSyncResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkComponentSyncResource.ChunkCollisionSurfaceComponents; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystem.java similarity index 89% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystem.java index 6881fe35..0b40edb1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Ref; @@ -14,18 +14,19 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionMutation; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.BoxPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; +import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -81,25 +82,20 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = store.getResource( PhysicsChunkCollisionPayloadResource.getResourceType()); PhysicsChunkSettingsIndexResource settingsIndex = store.getResource( PhysicsChunkSettingsIndexResource.getResourceType()); List upserts = prepareUpserts(store, runtime, - identity, settingsIndex, restore, mutations); removeGeneratedRows(store, runtime, - identity, removalKeys(mutations, upserts)); applyPreparedUpserts(store, - identity, chunkCollisionPayloads, upserts); } @@ -141,14 +137,13 @@ private static void addRemovalKey(@Nonnull Map> @Nonnull private static List prepareUpserts(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsChunkSettingsIndexResource settingsIndex, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List mutations) { List upserts = new ArrayList<>(); for (ChunkCollisionMutation mutation : mutations) { if (!mutation.remove() && isFreshUpsert(settingsIndex, mutation)) { - PreparedUpsert upsert = prepareUpsert(store, runtime, identity, restore, mutation); + PreparedUpsert upsert = prepareUpsert(store, runtime, restore, mutation); if (upsert != null) { upserts.add(upsert); } @@ -167,7 +162,6 @@ private static boolean isFreshUpsert(@Nonnull PhysicsChunkSettingsIndexResource @Nullable private static PreparedUpsert prepareUpsert(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ChunkCollisionMutation mutation) { ChunkCollisionPayload payload = mutation.payload(); @@ -176,36 +170,38 @@ private static PreparedUpsert prepareUpsert(@Nonnull Store store, + mutation.sourceKey()); return null; } - Ref spaceRef = PhysicsStoreSystemSupport.refForUuid(identity, - mutation.spaceUuid()); + Ref spaceRef = store.getExternalData().getRefFromUUID(mutation.spaceUuid()); + if (spaceRef != null && (spaceRef.getStore() != store || !spaceRef.isValid())) { + spaceRef = null; + } BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; - PhysicsBackendRuntime backendRuntime = spaceRef != null + boolean nativeVoxel; + try (PhysicsBackendRuntime backendRuntime = spaceRef != null ? runtime.runtimeForSpaceRef(spaceRef) - : null; - if (spaceRef == null || spaceHandle == null || backendRuntime == null) { - restore.recordSoftSkip("Chunk collision references unbound space: " - + mutation.sourceKey()); - return null; + : null) { + if (spaceRef == null || spaceHandle == null || backendRuntime == null) { + restore.recordSoftSkip("Chunk collision references unbound space: " + + mutation.sourceKey()); + return null; + } + nativeVoxel = payload.nativeVoxelCollisionEnabled() + && payload.hasFullCubeVoxels() + && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); } - boolean nativeVoxel = payload.nativeVoxelCollisionEnabled() - && payload.hasFullCubeVoxels() - && backendRuntime.supportsVoxelTerrain(spaceHandle.value()); MaterialComponent material = material(store, spaceRef); CollisionFilterComponent filter = filter(store, spaceRef); return new PreparedUpsert(mutation, payload, spaceRef, material, filter, nativeVoxel); } private static void applyPreparedUpserts(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull List upserts) { for (PreparedUpsert upsert : upserts) { - applyPreparedUpsert(store, identity, chunkCollisionPayloads, upsert); + applyPreparedUpsert(store, chunkCollisionPayloads, upsert); } } private static void applyPreparedUpsert(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, @Nonnull PreparedUpsert upsert) { ChunkCollisionMutation mutation = upsert.mutation(); @@ -214,14 +210,12 @@ private static void applyPreparedUpsert(@Nonnull Store store, if (upsert.nativeVoxel()) { chunkCollisionPayloads.put(mutation.payloadResourceKey(), voxelPayload(payload)); addNativeVoxelBody(store, - identity, upsert.spaceRef(), mutation, upsert.material(), upsert.filter()); } else { addBoxBodies(store, - identity, upsert.spaceRef(), mutation, upsert.material(), @@ -230,7 +224,6 @@ private static void applyPreparedUpsert(@Nonnull Store store, PartKind.BOX); } addBoxBodies(store, - identity, upsert.spaceRef(), mutation, upsert.material(), @@ -252,7 +245,6 @@ private static ChunkCollisionPayload voxelPayload(@Nonnull ChunkCollisionPayload } private static void addNativeVoxelBody(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @Nonnull ChunkCollisionMutation mutation, @Nonnull MaterialComponent material, @@ -262,7 +254,6 @@ private static void addNativeVoxelBody(@Nonnull Store store, mutation.sectionY() << ChunkUtil.BITS, mutation.chunkZ() << ChunkUtil.BITS)); addChunkCollisionBody(store, - identity, spaceRef, mutation, target, @@ -283,7 +274,6 @@ private static void addNativeVoxelBody(@Nonnull Store store, } private static void addBoxBodies(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @Nonnull ChunkCollisionMutation mutation, @Nonnull MaterialComponent material, @@ -300,7 +290,6 @@ private static void addBoxBodies(@Nonnull Store store, (float) box.centerY(), (float) box.centerZ())); addChunkCollisionBody(store, - identity, spaceRef, mutation, target, @@ -322,7 +311,6 @@ private static void addBoxBodies(@Nonnull Store store, } private static void addChunkCollisionBody(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Ref spaceRef, @Nonnull ChunkCollisionMutation mutation, @Nonnull TargetComponent target, @@ -357,7 +345,6 @@ private static void addChunkCollisionBody(@Nonnull Store store, partIndex)); Ref ref = store.addEntity(holder, AddReason.SPAWN); assert ref != null; - identity.putUuid(bodyUuid, ref); store.getExternalData().putRefForUUID(bodyUuid, ref); } @@ -385,7 +372,6 @@ private static CollisionFilterComponent filter(@Nonnull Store stor private static void removeGeneratedRows(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Map> keys) { if (keys.isEmpty()) { return; @@ -393,13 +379,15 @@ private static void removeGeneratedRows(@Nonnull Store store, List rows = collectGeneratedRows(store, keys); rows.sort((first, second) -> Integer.compare(second.ref().getIndex(), first.ref().getIndex())); + PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = store.getResource( + PhysicsChunkCollisionPayloadResource.getResourceType()); List bodyEntityRemovals = new ArrayList<>(rows.size()); for (GeneratedRow row : rows) { - PhysicsStoreRowCleanup.removeRuntimeBody(runtime, identity, row.uuid(), row.ref()); + PhysicsStoreRowCleanup.removeRuntimeBody(store, runtime, row.uuid(), row.ref()); + removePayload(chunkCollisionPayloads, row.payloadResourceKey()); bodyEntityRemovals.add(new PhysicsStoreRowCleanup.BodyEntityRemoval(row.uuid(), - row.ref(), - row.payloadResourceKey())); + row.ref())); } if (!bodyEntityRemovals.isEmpty()) { PhysicsStoreRowCleanup.removeBodyEntities(store, bodyEntityRemovals); @@ -446,7 +434,7 @@ private static void removePayload(@Nonnull PhysicsChunkCollisionPayloadResource } @Nonnull - static UUID chunkCollisionBodyUuid(@Nonnull UUID spaceUuid, + public static UUID chunkCollisionBodyUuid(@Nonnull UUID spaceUuid, @Nonnull String sourceKey, @Nonnull PartKind partKind, int partIndex) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionProducerSystem.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionProducerSystem.java index 77f1597f..447aa3a1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkCollisionProducerSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionProducerSystem.java @@ -25,11 +25,11 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource.BodyCursor; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -45,12 +45,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3d; -import org.joml.Vector3f; /** * Produces copied PhysicsStore chunk collision mutations from EntityStore and ChunkStore state. */ -public final class PhysicsChunkCollisionProducerSystem extends TickingSystem +public final class ChunkCollisionProducerSystem extends TickingSystem implements QuerySystem { @Nullable @@ -318,7 +317,7 @@ private static Query query() { if (resolved != null) { return resolved; } - synchronized (PhysicsChunkCollisionProducerSystem.class) { + synchronized (ChunkCollisionProducerSystem.class) { resolved = query; if (resolved == null) { resolved = Query.and(playerType(), transformType()); @@ -334,7 +333,7 @@ private static ComponentType playerType() { if (resolved != null) { return resolved; } - synchronized (PhysicsChunkCollisionProducerSystem.class) { + synchronized (ChunkCollisionProducerSystem.class) { resolved = playerType; if (resolved == null) { resolved = Player.getComponentType(); @@ -350,7 +349,7 @@ private static ComponentType transformType() { if (resolved != null) { return resolved; } - synchronized (PhysicsChunkCollisionProducerSystem.class) { + synchronized (ChunkCollisionProducerSystem.class) { resolved = transformType; if (resolved == null) { resolved = TransformComponent.getComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystem.java similarity index 88% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystem.java index 3c55c4cb..c37fe706 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -15,11 +15,11 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -51,18 +51,16 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); PhysicsChunkCollisionPayloadResource payloads = store.getResource( PhysicsChunkCollisionPayloadResource.getResourceType()); Set stitchedPairs = new ObjectOpenHashSet<>(); BiConsumer, CommandBuffer> collector = - (chunk, _) -> stitchChunk(runtime, identity, payloads, restore, stitchedPairs, chunk); + (chunk, _) -> stitchChunk(store, runtime, payloads, restore, stitchedPairs, chunk); store.forEachChunk(systemIndex, collector); } - private static void stitchChunk(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + private static void stitchChunk(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsChunkCollisionPayloadResource payloads, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Set stitchedPairs, @@ -82,7 +80,7 @@ private static void stitchChunk(@Nonnull PhysicsRuntimeResource runtime, continue; } stitchBody(runtime, - identity, + store, payloads, restore, stitchedPairs, @@ -93,7 +91,7 @@ private static void stitchChunk(@Nonnull PhysicsRuntimeResource runtime, } private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Store store, @Nonnull PhysicsChunkCollisionPayloadResource payloads, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Set stitchedPairs, @@ -119,7 +117,7 @@ private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, } for (ChunkCollisionPayload.Neighbor neighbor : payload.neighbors()) { stitchNeighbor(runtime, - identity, + store, backendRuntime, backendId, spaceHandle, @@ -132,7 +130,7 @@ private static void stitchBody(@Nonnull PhysicsRuntimeResource runtime, } private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + @Nonnull Store store, @Nonnull PhysicsBackendRuntime backendRuntime, @Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle, @@ -140,7 +138,7 @@ private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, @Nonnull BackendBodyHandle bodyHandle, @Nonnull ChunkCollisionPayload.Neighbor neighbor, @Nonnull Set stitchedPairs) { - Ref neighborRef = neighborRef(identity, spaceUuid, neighbor.sourceKey()); + Ref neighborRef = neighborRef(store, spaceUuid, neighbor.sourceKey()); if (neighborRef == null) { return; } @@ -167,14 +165,15 @@ private static void stitchNeighbor(@Nonnull PhysicsRuntimeResource runtime, } @Nullable - private static Ref neighborRef(@Nonnull PhysicsIdentityIndexResource identity, + private static Ref neighborRef(@Nonnull Store store, @Nonnull UUID spaceUuid, @Nonnull String sourceKey) { UUID neighborUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, PartKind.NATIVE_VOXELS, 0); - return PhysicsStoreSystemSupport.refForUuid(identity, neighborUuid); + Ref ref = store.getExternalData().getRefFromUUID(neighborUuid); + return ref != null && ref.getStore() == store && ref.isValid() ? ref : null; } @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkSettingsIndexSystem.java similarity index 86% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkSettingsIndexSystem.java index 60707d13..63aa49dc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsChunkSettingsIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/PhysicsChunkSettingsIndexSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -11,9 +11,11 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java index bf4f9ceb..d16fd5e6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityTypeRegistry.java @@ -5,8 +5,8 @@ import com.hypixel.hytale.component.SystemGroup; import com.hypixel.hytale.component.event.WorldEventType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodySyncStateResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodySyncStateResource; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsDebugOverlayResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualInterestResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.internal.systems.publication.PhysicsStoreEventPublicationSystem; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsBodyAttachmentIndexSystem; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; @@ -53,8 +53,9 @@ public static void registerComponentTypes(@Nonnull ComponentRegistryProxy registry) { - PhysicsDebugResource.setResourceType(registry.registerResource(PhysicsDebugResource.class, - PhysicsDebugResource::new)); + PhysicsDebugOverlayResource.setResourceType(registry.registerResource( + PhysicsDebugOverlayResource.class, + PhysicsDebugOverlayResource::new)); PhysicsRuntimeProfilingResource.setResourceType(registry.registerResource( PhysicsRuntimeProfilingResource.class, PhysicsRuntimeProfilingResource::new)); @@ -91,7 +92,7 @@ public static void clearEntityStoreTypes() { generatedVisualProxyComponentType = null; physicsEventFramePublishedEventType = null; persistenceRestoreGroup = null; - PhysicsDebugResource.clearResourceType(); + PhysicsDebugOverlayResource.clearResourceType(); PhysicsRuntimeProfilingResource.clearResourceType(); PhysicsProjectionIndexResource.clearResourceType(); PhysicsBodySyncStateResource.clearResourceType(); @@ -103,7 +104,7 @@ public static boolean areEntityStoreTypesRegistered() { && generatedVisualProxyComponentType != null && physicsEventFramePublishedEventType != null && persistenceRestoreGroup != null - && PhysicsDebugResource.getResourceType() != null + && PhysicsDebugOverlayResource.getResourceType() != null && PhysicsRuntimeProfilingResource.getResourceType() != null && PhysicsProjectionIndexResource.getResourceType() != null && PhysicsBodySyncStateResource.getResourceType() != null diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java index fc18647c..9d8c7ead 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java @@ -12,7 +12,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; +import dev.hytalemodding.impulse.core.internal.commands.space.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java index 7d53c040..c3267913 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualSyncSettingsCommand.java @@ -12,7 +12,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.commands.SpaceSelection; +import dev.hytalemodding.impulse.core.internal.commands.space.SpaceSelection; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodySyncStateResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodySyncStateResource.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodySyncStateResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodySyncStateResource.java index 7fac0e3a..da86b0f6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsBodySyncStateResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodySyncStateResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsDebugOverlayResource.java similarity index 78% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsDebugOverlayResource.java index 99c98f9b..1385f865 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsDebugOverlayResource.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; @@ -9,21 +9,19 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; -import lombok.Setter; /** - * Runtime-only debug overlay state for one world EntityStore. + * Runtime-only debug overlay projection state for one world EntityStore. * - *

        This resource intentionally keeps transient debug session state separate from - * PhysicsStore authority. Physics world state is persisted and shared by gameplay systems, - * while debug subscriptions, cadence, and packet budgets are temporary operational concerns.

        + *

        Physics debug flags live on PhysicsStore. This resource owns only viewer subscription, + * projection cadence, and packet-budget state for rendering copied physics debug views.

        */ @Getter -public class PhysicsDebugResource implements Resource { +public class PhysicsDebugOverlayResource implements Resource { @Getter @Nullable - private static ResourceType resourceType; + private static ResourceType resourceType; public static final float MIN_REFRESH_SECONDS = 0.05f; public static final float MAX_REFRESH_SECONDS = 2.0f; @@ -39,17 +37,6 @@ public class PhysicsDebugResource implements Resource { private final Set subscriberUuids = new ObjectOpenHashSet<>(); - @Setter - private boolean debugShapesEnabled = true; - @Setter - private boolean debugMotionEnabled = true; - @Setter - private boolean debugContactsEnabled; - @Setter - private boolean debugJointsEnabled = true; - @Setter - private boolean debugPhysicsChunkCollisionEnabled; - private float overlayRefreshSeconds = DEFAULT_OVERLAY_REFRESH_SECONDS; private float physicsChunkRefreshSeconds = DEFAULT_PHYSICS_CHUNK_REFRESH_SECONDS; private float overlayTimeUntilRefresh; @@ -62,7 +49,7 @@ public class PhysicsDebugResource implements Resource { private int maxPhysicsChunkSections = DEFAULT_MAX_PHYSICS_CHUNK_SECTIONS; private int maxPhysicsChunkBoxes = DEFAULT_MAX_PHYSICS_CHUNK_BOXES; - public PhysicsDebugResource() { + public PhysicsDebugOverlayResource() { } public boolean addSubscriber(@Nonnull UUID uuid) { @@ -146,14 +133,9 @@ public boolean tickPhysicsChunkBudget(float dt) { @Nonnull @Override - public PhysicsDebugResource clone() { - PhysicsDebugResource copy = new PhysicsDebugResource(); + public PhysicsDebugOverlayResource clone() { + PhysicsDebugOverlayResource copy = new PhysicsDebugOverlayResource(); copy.subscriberUuids.addAll(subscriberUuids); - copy.debugShapesEnabled = debugShapesEnabled; - copy.debugMotionEnabled = debugMotionEnabled; - copy.debugContactsEnabled = debugContactsEnabled; - copy.debugJointsEnabled = debugJointsEnabled; - copy.debugPhysicsChunkCollisionEnabled = debugPhysicsChunkCollisionEnabled; copy.overlayRefreshSeconds = overlayRefreshSeconds; copy.physicsChunkRefreshSeconds = physicsChunkRefreshSeconds; copy.overlayTimeUntilRefresh = overlayTimeUntilRefresh; @@ -168,7 +150,7 @@ public PhysicsDebugResource clone() { } public static void setResourceType( - @Nonnull ResourceType type) { + @Nonnull ResourceType type) { resourceType = type; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/GeneratedProxyLifecycle.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/GeneratedProxyLifecycle.java index f2a01694..53f951b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/GeneratedProxyLifecycle.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/GeneratedProxyLifecycle.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentAccessor; @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodySyncStateResource; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodySyncStateResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; @@ -41,6 +41,7 @@ public static void clearMissingAttachment(@Nonnull Ref entityRef, @Nonnull BodyAttachmentComponent attachment, @Nonnull CommandBuffer commandBuffer) { UUID bodyUuid = attachment.getBodyUuid(); + assert PhysicsProjectionIndexResource.getResourceType() != null; PhysicsProjectionIndexResource projection = commandBuffer.getResource( PhysicsProjectionIndexResource.getResourceType()); projection.unregisterAttachment(bodyUuid, attachment.getBodyRef(), entityRef); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/PhysicsProjectionCleanupSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/PhysicsProjectionCleanupSystem.java index 55edb48c..ebd53973 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/PhysicsProjectionCleanupSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/VisualInterestCollector.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/VisualInterestCollector.java index 3e04c3ed..2aaf50ee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/visual/VisualInterestCollector.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/visual/VisualInterestCollector.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems.visual; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/CompletedStepPublicationSystem.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/CompletedStepPublicationSystem.java index fb90c8eb..02514b9c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/CompletedStepPublicationSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.systems.publication; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; @@ -15,6 +15,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.CompletedStep; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; import java.util.Set; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/StepSubmissionSystem.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/StepSubmissionSystem.java index f8fd4cec..18c04fba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/StepSubmissionSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.systems.step; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -29,7 +29,8 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource.StepInput; import dev.hytalemodding.impulse.core.internal.resources.PhysicsWorldSettingsResource; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.systems.step.PhysicsStepCountPolicy; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreQueuedReadSystem; +import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsContactEvent; import dev.hytalemodding.impulse.core.plugin.events.PhysicsFrameEvent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java deleted file mode 100644 index 81674340..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * PhysicsEntity components for EntityStore projections and PhysicsStore visual policy settings. - */ -package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java index 12c7a60f..7667ed12 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/package-info.java @@ -1,7 +1,7 @@ /** - * Public API for the bundled PhysicsEntity subplugin. - * - *

        Visual sync and generated-proxy settings live under - * {@code dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings}.

        + * Public API for the bundled PhysicsEntity module. + *

        + * PhysicsEntity module handles the EntityStore projection of the PhysicsStore ecs entities. + *

        */ package dev.hytalemodding.impulse.core.plugin.modules.physicsentity; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java deleted file mode 100644 index 7f3e3225..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/settings/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Public settings owned by the bundled PhysicsEntity subplugin. - */ -package dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystemTest.java similarity index 96% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystemTest.java index 6c0c9d54..4a963efb 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionComponentSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystemTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -22,10 +22,12 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; +import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -139,9 +141,6 @@ private static RuntimeFixture addBoundSpace(@Nonnull Store store, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(spaceRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(spaceUuid, spaceRef); store.getExternalData().putRefForUUID(spaceUuid, spaceRef); FakePhysicsBackendRuntime runtime = (FakePhysicsBackendRuntime) @@ -197,9 +196,6 @@ private static GeneratedRow addGeneratedBoxRow(@Nonnull Store stor Ref bodyRef = store.addEntity(holder, AddReason.SPAWN); assertNotNull(bodyRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(bodyUuid, bodyRef); store.getExternalData().putRefForUUID(bodyUuid, bodyRef); if (!bindRuntime) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystemTest.java similarity index 93% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystemTest.java index cbdc4411..6459204a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystemTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -32,11 +32,10 @@ import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -232,8 +231,7 @@ void destroyingDetailRowDoesNotRemoveNativeVoxelPayloadForSiblingRow() { sourceKey, PartKind.DETAIL_BOX, 0); - assertNotNull(store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(detailUuid)); + assertNotNull(store.getExternalData().getRefFromUUID(detailUuid)); ChunkCollisionPayload retainedPayload = store.getResource( PhysicsChunkCollisionPayloadResource.getResourceType()) .get(payloadKey); @@ -294,8 +292,6 @@ void removeDeletesGeneratedRowsAndPayloadResource() { payload)); new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); UUID boxUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, sourceKey, PartKind.BOX, @@ -304,8 +300,8 @@ void removeDeletesGeneratedRowsAndPayloadResource() { sourceKey, PartKind.DETAIL_BOX, 0); - Ref boxRef = identity.getByUuid(boxUuid); - Ref detailRef = identity.getByUuid(detailUuid); + Ref boxRef = store.getExternalData().getRefFromUUID(boxUuid); + Ref detailRef = store.getExternalData().getRefFromUUID(detailUuid); assertNotNull(boxRef); assertNotNull(detailRef); publishCopiedState(store, spaceUuid, boxUuid, boxRef, detailUuid, detailRef); @@ -320,8 +316,8 @@ void removeDeletesGeneratedRowsAndPayloadResource() { new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); assertEquals(0, queue.size()); - assertNull(identity.getByUuid(boxUuid)); - assertNull(identity.getByUuid(detailUuid)); + assertNull(store.getExternalData().getRefFromUUID(boxUuid)); + assertNull(store.getExternalData().getRefFromUUID(detailUuid)); assertNull(snapshots.getBody(boxUuid)); assertNull(snapshots.getBody(detailUuid)); assertFalse(PhysicsBodies.isRegistered(store, boxUuid)); @@ -364,12 +360,16 @@ void removeDeletesManyGeneratedSourcesWithoutPerSourceFullStoreScans() { boxPayload(index, 2.0, 3.0))); } new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); - assertNotNull(generatedBodyRef(store, spaceUuid, "scale:0", PartKind.BOX, 0)); - assertNotNull(generatedBodyRef(store, - spaceUuid, - "scale:" + (sources - 1), - PartKind.BOX, - 0)); + assertNotNull(store.getExternalData() + .getRefFromUUID(ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + "scale:0", + PartKind.BOX, + 0))); + assertNotNull(store.getExternalData() + .getRefFromUUID(ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + "scale:" + (sources - 1), + PartKind.BOX, + 0))); for (int index = 0; index < sources; index++) { queue.enqueue(ChunkCollisionMutation.remove(spaceUuid, @@ -382,7 +382,12 @@ void removeDeletesManyGeneratedSourcesWithoutPerSourceFullStoreScans() { assertEquals(0, queue.size()); for (int index = 0; index < sources; index++) { - assertNull(generatedBodyRef(store, spaceUuid, "scale:" + index, PartKind.BOX, 0)); + assertNull(store.getExternalData() + .getRefFromUUID(ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid( + spaceUuid, + "scale:" + index, + PartKind.BOX, + 0))); } assertSoftSkipsEmpty(store); } finally { @@ -559,7 +564,11 @@ void staleLifecycleUpsertDoesNotCreateGeneratedRows() { new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); assertEquals(0, queue.size()); - assertNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + assertNull(store.getExternalData() + .getRefFromUUID(ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0))); assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) .get(payloadKey)); assertSoftSkipsEmpty(store); @@ -598,7 +607,11 @@ void staleSettingsUpsertDoesNotCreateGeneratedRows() { new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); assertEquals(0, queue.size()); - assertNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + assertNull(store.getExternalData() + .getRefFromUUID(ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0))); assertNull(store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) .get(payloadKey)); assertSoftSkipsEmpty(store); @@ -633,7 +646,11 @@ void staleRemoveStillDeletesGeneratedRows() { payloadKey, boxPayload(7.0, 8.0, 9.0))); new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); - assertNotNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + assertNotNull(store.getExternalData() + .getRefFromUUID(ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0))); queue.updateStamp(previousGeneration(PhysicsChunkLifecycle.generation()), previousGeneration(settingsGeneration(store))); @@ -641,7 +658,11 @@ void staleRemoveStillDeletesGeneratedRows() { new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); assertEquals(0, queue.size()); - assertNull(generatedBodyRef(store, spaceUuid, sourceKey, PartKind.BOX, 0)); + assertNull(store.getExternalData() + .getRefFromUUID(ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0))); assertSoftSkipsEmpty(store); } finally { registry.removeStore(store); @@ -681,9 +702,7 @@ void sameDrainUpsertThenRemoveKeepsRemoveIntent() { sourceKey, PartKind.BOX, 0); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); - assertNotNull(identity.getByUuid(boxUuid)); + assertNotNull(store.getExternalData().getRefFromUUID(boxUuid)); queue.enqueue(ChunkCollisionMutation.upsert(spaceUuid, sourceKey, @@ -696,7 +715,7 @@ void sameDrainUpsertThenRemoveKeepsRemoveIntent() { new ChunkCollisionMutationDrainSystem().tick(0.0f, 0, store); assertEquals(0, queue.size()); - assertNull(identity.getByUuid(boxUuid)); + assertNull(store.getExternalData().getRefFromUUID(boxUuid)); assertSoftSkipsEmpty(store); } finally { registry.removeStore(store); @@ -791,8 +810,6 @@ private static Ref addBoundSpace(@Nonnull Store stor new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(spaceRef); - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .putUuid(spaceUuid, spaceRef); store.getExternalData().putRefForUUID(spaceUuid, spaceRef); PhysicsBackendRuntime backendRuntime = @@ -908,9 +925,7 @@ private static void assertGeneratedVoxel(@Nonnull Store store, sourceKey, PartKind.NATIVE_VOXELS, 0); - Ref bodyRef = store - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(bodyUuid); + Ref bodyRef = store.getExternalData().getRefFromUUID(bodyUuid); assertNotNull(bodyRef); BodyComponent body = store.getComponent(bodyRef, BodyComponent.getComponentType()); @@ -962,9 +977,7 @@ private static void assertGeneratedBox(@Nonnull Store store, sourceKey, partKind, partIndex); - Ref bodyRef = store - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(bodyUuid); + Ref bodyRef = store.getExternalData().getRefFromUUID(bodyUuid); assertNotNull(bodyRef); assertEquals(bodyUuid, store.getComponent(bodyRef, UuidComponent.getComponentType()).getUuid()); @@ -1019,20 +1032,6 @@ private static void assertGeneratedBox(@Nonnull Store store, assertEquals(partIndex, source.getPartIndex()); } - @Nullable - private static Ref generatedBodyRef(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nonnull String sourceKey, - @Nonnull PartKind partKind, - int partIndex) { - UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, - sourceKey, - partKind, - partIndex); - return store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(bodyUuid); - } - private static void assertMaterialMatchesSpace(@Nonnull Store store, @Nonnull Ref spaceRef, @Nonnull MaterialComponent material) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystemTest.java similarity index 95% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystemTest.java index af0b4f56..a55b7f29 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.systems; +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -28,8 +28,7 @@ import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -194,9 +193,6 @@ private static RuntimeFixture addBoundSpace(@Nonnull Store store, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(spaceRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(spaceUuid, spaceRef); store.getExternalData().putRefForUUID(spaceUuid, spaceRef); FakePhysicsBackendRuntime runtime = (FakePhysicsBackendRuntime) @@ -253,9 +249,6 @@ private static Ref addVoxelRow(@Nonnull Store store, 0)); Ref bodyRef = store.addEntity(holder, AddReason.SPAWN); assertNotNull(bodyRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(bodyUuid, bodyRef); store.getExternalData().putRefForUUID(bodyUuid, bodyRef); long bodyHandle = runtime.backendRuntime() @@ -319,8 +312,8 @@ private static void runStitchingSystem(@Nonnull Store store) { ChunkCollisionVoxelStitchingSystem system = new ChunkCollisionVoxelStitchingSystem(); Method stitchChunk = ChunkCollisionVoxelStitchingSystem.class.getDeclaredMethod( "stitchChunk", + Store.class, PhysicsRuntimeResource.class, - PhysicsIdentityIndexResource.class, PhysicsChunkCollisionPayloadResource.class, PhysicsRestoreStatusResource.class, Set.class, @@ -329,16 +322,14 @@ private static void runStitchingSystem(@Nonnull Store store) { Set stitchedPairs = new HashSet<>(); PhysicsRuntimeResource runtime = store.getResource( PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsChunkCollisionPayloadResource payloads = store.getResource( PhysicsChunkCollisionPayloadResource.getResourceType()); PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); BiConsumer, CommandBuffer> collector = (chunk, _) -> invoke(stitchChunk, + store, runtime, - identity, payloads, restore, stitchedPairs, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResourceTest.java similarity index 66% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResourceTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResourceTest.java index a7c88bc4..b4450cd9 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResourceTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources; +package dev.hytalemodding.impulse.core.internal.physics.resources; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -8,22 +8,25 @@ class PhysicsDebugResourceTest { @Test - void contactsDefaultDisabledWhileOtherOverlayFlagsRemainEnabled() { + void contactsDefaultDisabledWhileOtherPhysicsFlagsRemainEnabled() { PhysicsDebugResource resource = new PhysicsDebugResource(); assertTrue(resource.isDebugShapesEnabled()); assertTrue(resource.isDebugMotionEnabled()); assertFalse(resource.isDebugContactsEnabled()); assertTrue(resource.isDebugJointsEnabled()); + assertFalse(resource.isDebugPhysicsChunkCollisionEnabled()); } @Test - void clonePreservesExplicitContactFlag() { + void clonePreservesExplicitPhysicsFlags() { PhysicsDebugResource resource = new PhysicsDebugResource(); resource.setDebugContactsEnabled(true); + resource.setDebugPhysicsChunkCollisionEnabled(true); PhysicsDebugResource copy = resource.clone(); assertTrue(copy.isDebugContactsEnabled()); + assertTrue(copy.isDebugPhysicsChunkCollisionEnabled()); } } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugOverlayResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugOverlayResourceTest.java new file mode 100644 index 00000000..28c5c8b5 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugOverlayResourceTest.java @@ -0,0 +1,39 @@ +package dev.hytalemodding.impulse.core.internal.resources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; + +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsDebugOverlayResource; +import org.junit.jupiter.api.Test; + +class PhysicsDebugOverlayResourceTest { + + @Test + void clonePreservesSubscribersCadenceAndBudgets() { + UUID subscriberUuid = UUID.randomUUID(); + PhysicsDebugOverlayResource resource = new PhysicsDebugOverlayResource(); + resource.addSubscriber(subscriberUuid); + resource.setOverlayRefreshSeconds(0.25f); + resource.setPhysicsChunkRefreshSeconds(0.5f); + resource.setViewRadius(48.0); + resource.setMaxBodies(32); + resource.setMaxContacts(16); + resource.setMaxJoints(12); + resource.setMaxPhysicsChunkSections(8); + resource.setMaxPhysicsChunkBoxes(64); + + PhysicsDebugOverlayResource copy = resource.clone(); + + assertTrue(copy.getSubscriberUuids().contains(subscriberUuid)); + assertEquals(0.25f, copy.getOverlayRefreshSeconds()); + assertEquals(0.5f, copy.getPhysicsChunkRefreshSeconds()); + assertEquals(48.0, copy.getViewRadius()); + assertEquals(32, copy.getMaxBodies()); + assertEquals(16, copy.getMaxContacts()); + assertEquals(12, copy.getMaxJoints()); + assertEquals(8, copy.getMaxPhysicsChunkSections()); + assertEquals(64, copy.getMaxPhysicsChunkBoxes()); + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java index 67e48d28..6ed6e828 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsEntityProjectionResourcesTest.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodySyncStateResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import java.util.ArrayList; import javax.annotation.Nonnull; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java index 09d13a9c..2bf6cc3c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsStoreResourceIndexTest.java @@ -20,10 +20,11 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; +import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; import java.util.ArrayList; @@ -267,7 +268,8 @@ void runtimeBindsLargeDistinctBodyMetadataSetWithoutQuadraticDuplicateScan() { @Test void runtimeRefreshRebuildsRefIndexesFromScopedBackendMetadata() { PhysicsRuntimeResource runtime = new PhysicsRuntimeResource(); - PhysicsIdentityIndexResource identity = new PhysicsIdentityIndexResource(); + PhysicsStore physicsStore = + new PhysicsStore(TestInstanceFactory.world("runtime-refresh-index-test")); BackendId backendId = new BackendId("test:runtime-refresh"); PhysicsBackendRuntime backendRuntime = new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); @@ -296,11 +298,11 @@ void runtimeRefreshRebuildsRefIndexesFromScopedBackendMetadata() { spaceUuid); runtime.putJointHandle(oldJointRef, oldSpaceRef, spaceHandle, jointHandle); runtime.putJointMetadata(backendId, spaceHandle, jointHandle, jointUuid, oldJointRef); - identity.putUuid(spaceUuid, newSpaceRef); - identity.putUuid(bodyUuid, newBodyRef); - identity.putUuid(jointUuid, newJointRef); + physicsStore.putRefForUUID(spaceUuid, newSpaceRef); + physicsStore.putRefForUUID(bodyUuid, newBodyRef); + physicsStore.putRefForUUID(jointUuid, newJointRef); - runtime.refreshRowRefs(identity); + runtime.refreshRowRefs(physicsStore); assertNull(runtime.getSpaceHandle(oldSpaceRef)); assertNull(runtime.getBodyHandle(oldBodyRef)); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java index 2c66a251..b71b7ac5 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/CompletedStepPublicationSystemTest.java @@ -16,6 +16,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.systems.publication.CompletedStepPublicationSystem; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsStepEvent; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java index 4922020f..5f6c1594 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StepSubmissionSystemTest.java @@ -10,6 +10,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; +import dev.hytalemodding.impulse.core.internal.systems.step.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import java.util.ArrayList; import java.util.concurrent.TimeUnit; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java index add406f5..c225c624 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/visual/PhysicsProjectionCleanupSystemTest.java @@ -15,6 +15,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityTypeRegistry; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; From e8b338a9c1a6efbb895b1061cb88b93e95c936b7 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:38:15 +0200 Subject: [PATCH 516/534] fix(core): resolve physics rows through store uuid data Signed-off-by: Blovien --- .../physics/PhysicsSpaceMutations.java | 17 +-- .../physics/PhysicsStoreRowCleanup.java | 65 ++++------ .../physics/PhysicsTopologyMutations.java | 115 ++++-------------- .../PhysicsIdentityIndexResource.java | 57 --------- .../resources/PhysicsResourceTypes.java | 10 -- .../resources/PhysicsRuntimeResource.java | 24 ++-- .../systems/BodyCommandApplicationSystem.java | 4 +- .../internal/systems/IdentityIndexSystem.java | 8 +- .../systems/PersistenceHydrationSystem.java | 2 - .../systems/PhysicsStoreSystemSupport.java | 24 +--- .../systems/StaleBodyRemovalSystem.java | 63 +++++----- .../systems/binding/JointBindingSystem.java | 78 ++++++------ .../core/plugin/physics/PhysicsEntities.java | 6 +- .../core/plugin/physics/PhysicsSpaces.java | 5 +- .../physics/PhysicsStoreRowCleanupTest.java | 17 ++- .../PhysicsStoreTopologyMutationsTest.java | 98 ++++++++++++--- .../systems/JointBindingSystemTest.java | 10 -- .../systems/StaleBodyRemovalSystemTest.java | 15 +-- .../binding/SpaceBindingSystemTest.java | 3 - 19 files changed, 243 insertions(+), 378 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java index e5945ced..2838c018 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsSpaceMutations.java @@ -12,7 +12,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; @@ -60,9 +59,7 @@ public static Ref addSpace(@Nonnull Store store, throw new IllegalArgumentException("PhysicsStore space id=" + compatibilitySpaceId.value() + " is already registered"); } - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); - Ref existing = identity.getByUuid(spaceUuid); + Ref existing = store.getExternalData().getRefFromUUID(spaceUuid); if (existing != null && existing.isValid()) { throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid + " is already registered"); @@ -72,7 +69,7 @@ public static Ref addSpace(@Nonnull Store store, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))); Ref ref = store.addEntity(holder, AddReason.SPAWN); assert ref != null; - identity.putUuid(spaceUuid, ref); + store.getExternalData().putRefForUUID(spaceUuid, ref); compatibility.putSpace(compatibilitySpaceId, spaceUuid); SpaceId.reserveAtLeast(compatibilitySpaceId.value()); store.getResource(PhysicsRuntimeResource.getResourceType()) @@ -277,11 +274,9 @@ public static void removeEmptySpace(@Nonnull Store store, Objects.requireNonNull(spaceUuid, "spaceUuid"); PhysicsThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); PhysicsSpaceCompatibilityIndexResource compatibility = store.getResource( PhysicsSpaceCompatibilityIndexResource.getResourceType()); - Ref ref = identity.getByUuid(spaceUuid); + Ref ref = store.getExternalData().getRefFromUUID(spaceUuid); BackendSpaceHandle handle = ref != null && ref.isValid() ? runtime.getSpaceHandle(ref) : null; @@ -302,7 +297,7 @@ public static void removeEmptySpace(@Nonnull Store store, } compatibility.removeBySpaceUuid(spaceUuid); if (ref != null && ref.isValid()) { - identity.removeUuid(spaceUuid, ref); + store.getExternalData().removeRefForUUID(spaceUuid, ref); store.removeEntity(ref, store.getRegistry().newHolder(), RemoveReason.REMOVE); } } @@ -324,8 +319,8 @@ public static UUID requireSpaceUuid(@Nonnull Store store, private static Ref requireSpaceRef(@Nonnull Store store, @Nonnull UUID spaceUuid) { PhysicsThreading.requireWorldThread(store, "resolve a PhysicsStore space entity"); - Ref ref = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(Objects.requireNonNull(spaceUuid, "spaceUuid")); + Ref ref = store.getExternalData() + .getRefFromUUID(Objects.requireNonNull(spaceUuid, "spaceUuid")); if (ref == null || !ref.isValid()) { throw new IllegalArgumentException("PhysicsStore space uuid=" + spaceUuid + " row is not registered"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index 5f669124..cc9db8ca 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -10,8 +10,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; @@ -33,11 +31,11 @@ public final class PhysicsStoreRowCleanup { private PhysicsStoreRowCleanup() { } - public static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + public static boolean removeRuntimeJoint(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID jointUuid, @Nonnull Ref jointRef) { - Ref resolvedJointRef = cleanupRefForUuid(identity, jointUuid, jointRef); + Ref resolvedJointRef = cleanupRefForUuid(store, jointUuid, jointRef); BackendJointHandle jointHandle = runtime.getJointHandle(resolvedJointRef); BackendSpaceHandle spaceHandle = runtime.getJointSpaceHandle(resolvedJointRef); if (jointHandle == null) { @@ -58,19 +56,19 @@ public static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime return true; } - public static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + public static boolean removeRuntimeBody(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { - return removeRuntimeBody(runtime, identity, bodyUuid, bodyRef, null); + return removeRuntimeBody(store, runtime, bodyUuid, bodyRef, null); } - public static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + public static boolean removeRuntimeBody(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef, @Nullable PhysicsBackendRuntime fallbackRuntime) { - Ref resolvedBodyRef = cleanupRefForUuid(identity, bodyUuid, bodyRef); + Ref resolvedBodyRef = cleanupRefForUuid(store, bodyUuid, bodyRef); BackendBodyHandle bodyHandle = runtime.getBodyHandle(resolvedBodyRef); BackendSpaceHandle spaceHandle = runtime.getBodySpaceHandle(resolvedBodyRef); if (bodyHandle == null) { @@ -95,10 +93,10 @@ public static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, } @Nonnull - private static Ref cleanupRefForUuid(@Nonnull PhysicsIdentityIndexResource identity, + private static Ref cleanupRefForUuid(@Nonnull Store store, @Nonnull UUID rowUuid, @Nonnull Ref suppliedRef) { - Ref indexedRef = identity.getByUuid(rowUuid); + Ref indexedRef = store.getExternalData().getRefFromUUID(rowUuid); if (refMatchesUuid(indexedRef, rowUuid)) { return indexedRef; } @@ -146,7 +144,7 @@ private static boolean refMatchesUuid(@Nullable Ref ref, public static void clearBodyCopiedState(@Nonnull Store store, @Nonnull UUID bodyUuid, @Nonnull Ref bodyRef) { - clearBodyCopiedState(store, List.of(new BodyEntityRemoval(bodyUuid, bodyRef, null))); + clearBodyCopiedState(store, List.of(new BodyEntityRemoval(bodyUuid, bodyRef))); } public static void clearBodyCopiedState(@Nonnull Store store, @@ -166,10 +164,9 @@ public static void clearBodyCopiedState(@Nonnull Store store, public static void removeBodyEntity(@Nonnull Store store, @Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nullable String payloadResourceKey) { + @Nonnull Ref bodyRef) { clearBodyCopiedState(store, bodyUuid, bodyRef); - removeBodyEntityRow(store, bodyUuid, bodyRef, payloadResourceKey); + removeBodyEntityRow(store, bodyUuid, bodyRef); } public static void removeBodyEntities(@Nonnull Store store, @@ -182,32 +179,26 @@ public static void removeBodyEntities(@Nonnull Store store, for (BodyEntityRemoval removal : removals) { removeBodyEntityRow(store, removal.bodyUuid(), - removal.bodyRef(), - removal.payloadResourceKey()); + removal.bodyRef()); } } static void removeBodyEntityRow(@Nonnull Store store, @Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nullable String payloadResourceKey) { - store.getResource(PhysicsIdentityIndexResource.getResourceType()).removeUuid(bodyUuid, - bodyRef); - removePayload(store, payloadResourceKey); + @Nonnull Ref bodyRef) { + PhysicsStoreCleanupHooks.cleanupBodyRowRuntimeResources(store, bodyUuid, bodyRef); + store.getExternalData().removeRefForUUID(bodyUuid, bodyRef); removeEntityIfValid(store, bodyRef); } public static void removeJointEntity(@Nonnull Store store, @Nonnull UUID jointUuid, @Nonnull Ref jointRef) { - store.getResource(PhysicsIdentityIndexResource.getResourceType()).removeUuid(jointUuid, - jointRef); + store.getExternalData().removeRefForUUID(jointUuid, jointRef); removeEntityIfValid(store, jointRef); } public static void refreshIdentityAndRuntimeRefs(@Nonnull Store store) { - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); ConcurrentLinkedQueue uuidRefs = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { @@ -216,23 +207,12 @@ public static void refreshIdentityAndRuntimeRefs(@Nonnull Store st uuidRefs.add(new UuidRef(uuid.getUuid(), chunk.getReferenceTo(index))); } }); - // The identity maps are fastutil/Hytale mutable maps; rebuild them on one thread. - identity.clearUuidRefs(); + // The UUID map is mutable store state; rebuild it on one thread. store.getExternalData().clearUuidIndex(); for (UuidRef uuidRef : uuidRefs) { - identity.putUuid(uuidRef.uuid(), uuidRef.ref()); store.getExternalData().putRefForUUID(uuidRef.uuid(), uuidRef.ref()); } - runtime.refreshRowRefs(identity); - } - - private static void removePayload(@Nonnull Store store, - @Nullable String payloadResourceKey) { - if (payloadResourceKey == null || payloadResourceKey.isBlank()) { - return; - } - store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()) - .remove(payloadResourceKey); + runtime.refreshRowRefs(store.getExternalData()); } private static void removeEntityIfValid(@Nonnull Store store, @@ -245,8 +225,7 @@ private static void removeEntityIfValid(@Nonnull Store store, public record BodyEntityRemoval( @Nonnull UUID bodyUuid, - @Nonnull Ref bodyRef, - @Nullable String payloadResourceKey) { + @Nonnull Ref bodyRef) { public BodyEntityRemoval { Objects.requireNonNull(bodyUuid, "bodyUuid"); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java index 88013fd3..589a4cfe 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java @@ -5,13 +5,10 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; @@ -19,7 +16,6 @@ import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -41,11 +37,9 @@ public static void destroyBody(@Nonnull Store store, UUID checkedBodyUuid = Objects.requireNonNull(bodyUuid, "bodyUuid"); PhysicsThreading.requireBackendIdle(store, "destroy a PhysicsStore body entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); - Ref bodyRef = identity.getByUuid(checkedBodyUuid); + Ref bodyRef = store.getExternalData().getRefFromUUID(checkedBodyUuid); List removals = collectRows(store, null, null, checkedBodyUuid, bodyRef); - removeRuntimeRows(runtime, identity, removals); + removeRuntimeRows(store, runtime, removals); removeRows(store, removals); } @@ -54,17 +48,13 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( @Nonnull Store store) { PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore body entities"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); PhysicsControlRuntimeStates.clear(store); TopologyCounts removed = countBackendTopology(runtime); List removals = collectRows(store, null, null, null, null); - removeRuntimeRows(runtime, identity, removals); + runtime.destroyBackendBindings(); removeRows(store, removals, false); clearCopiedBodyState(store); - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()).clear(); - store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); - runtime.clearTransientBodyOperations(); + PhysicsStoreCleanupHooks.clearBodyRuntimeResources(store); int keptSpaces = store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .size(); store.getResource(PhysicsRestoreStatusResource.getResourceType()) @@ -78,66 +68,43 @@ public static void removeSpaceWithContents(@Nonnull Store store, @Nonnull UUID spaceUuid) { PhysicsThreading.requireBackendIdle(store, "remove a PhysicsStore space entity"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); - Ref spaceRef = identity.getByUuid(spaceUuid); + Ref spaceRef = store.getExternalData().getRefFromUUID(spaceUuid); List removals = collectRows(store, spaceUuid, spaceRef, null, null); - removeRuntimeRows(runtime, identity, removals); - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()) - .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); + removeRuntimeRows(store, runtime, removals); + PhysicsStoreCleanupHooks.cleanupSpaceRuntimeResources(store, spaceUuid); removeRows(store, removals); PhysicsSpaceMutations.removeEmptySpace(store, spaceUuid); } - public static int clearChunkCollisionRowsForSpace(@Nonnull Store store, - @Nonnull UUID spaceUuid) { - PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore chunk-collision rows"); - PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); - int removedBodies = 0; - Ref spaceRef = identity.getByUuid(spaceUuid); - List removals = collectChunkCollisionRows(store, spaceUuid, spaceRef); - for (RowRemoval removal : removals) { - if (removeRuntimeBody(runtime, identity, removal)) { - removedBodies++; - } - } - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()) - .removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); - removeRows(store, removals); - return removedBodies; - } - - private static void removeRuntimeRows(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + private static void removeRuntimeRows(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull List removals) { for (RowRemoval removal : removals) { if (removal.kind() == RowKind.JOINT) { - removeRuntimeJoint(runtime, identity, removal); + removeRuntimeJoint(store, runtime, removal); } } for (RowRemoval removal : removals) { if (removal.kind() == RowKind.BODY) { - removeRuntimeBody(runtime, identity, removal); + removeRuntimeBody(store, runtime, removal); } } } - private static boolean removeRuntimeJoint(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + private static boolean removeRuntimeJoint(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull RowRemoval removal) { - return PhysicsStoreRowCleanup.removeRuntimeJoint(runtime, - identity, + return PhysicsStoreRowCleanup.removeRuntimeJoint(store, + runtime, removal.rowUuid(), removal.ref()); } - private static boolean removeRuntimeBody(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + private static boolean removeRuntimeBody(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull RowRemoval removal) { - return PhysicsStoreRowCleanup.removeRuntimeBody(runtime, - identity, + return PhysicsStoreRowCleanup.removeRuntimeBody(store, + runtime, removal.rowUuid(), removal.ref()); } @@ -169,43 +136,12 @@ private static List collectRows(@Nonnull Store store, Ref ref = chunk.getReferenceTo(index); JointComponent joint = chunk.getComponent(index, JointComponent.getComponentType()); if (matchesJoint(joint, spaceUuid, spaceRef, bodyUuid, bodyRef)) { - removals.add(new RowRemoval(ref, rowUuid, RowKind.JOINT, null)); + removals.add(new RowRemoval(ref, rowUuid, RowKind.JOINT)); return; } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); - ChunkCollisionSourceComponent source = chunk.getComponent(index, - ChunkCollisionSourceComponent.getComponentType()); if (matchesBody(body, rowUuid, ref, spaceUuid, spaceRef, bodyUuid, bodyRef)) { - removals.add(new RowRemoval(ref, - rowUuid, - RowKind.BODY, - source != null ? source.getPayloadResourceKey() : null)); - } - }); - return new ArrayList<>(removals); - } - - @Nonnull - private static List collectChunkCollisionRows(@Nonnull Store store, - @Nonnull UUID spaceUuid, - @Nullable Ref spaceRef) { - ComponentType uuidType = UuidComponent.getComponentType(); - ConcurrentLinkedQueue removals = new ConcurrentLinkedQueue<>(); - store.forEachEntityParallel(uuidType, (index, chunk, _) -> { - UuidComponent uuid = chunk.getComponent(index, uuidType); - if (uuid == null) { - return; - } - BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); - ChunkCollisionSourceComponent source = chunk.getComponent(index, - ChunkCollisionSourceComponent.getComponentType()); - if (source != null - && body != null - && matchesSpace(body.getSpaceRef(), body.getSpaceUuid(), spaceRef, spaceUuid)) { - removals.add(new RowRemoval(chunk.getReferenceTo(index), - uuid.getUuid(), - RowKind.BODY, - source.getPayloadResourceKey())); + removals.add(new RowRemoval(ref, rowUuid, RowKind.BODY)); } }); return new ArrayList<>(removals); @@ -296,8 +232,7 @@ private static void removeRows(@Nonnull Store store, if (removal.kind() == RowKind.BODY) { PhysicsStoreRowCleanup.removeBodyEntityRow(store, removal.rowUuid(), - removal.ref(), - removal.payloadResourceKey()); + removal.ref()); } else { PhysicsStoreRowCleanup.removeJointEntity(store, removal.rowUuid(), @@ -317,8 +252,7 @@ private static List bodyEntityRemovals( for (RowRemoval removal : removals) { if (removal.kind() == RowKind.BODY && removal.ref().isValid()) { bodyRemovals.add(new BodyEntityRemoval(removal.rowUuid(), - removal.ref(), - removal.payloadResourceKey())); + removal.ref())); } } return bodyRemovals; @@ -336,8 +270,7 @@ private enum RowKind { private record RowRemoval(@Nonnull Ref ref, @Nonnull UUID rowUuid, - @Nonnull RowKind kind, - @Nullable String payloadResourceKey) { + @Nonnull RowKind kind) { } private static final class TopologyCounts { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java deleted file mode 100644 index ce2926b2..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsIdentityIndexResource.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.Map; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Runtime identity index for durable UUID boundaries. - */ -public final class PhysicsIdentityIndexResource implements Resource { - - @Nonnull - private final Map> refsByUuid = new Object2ObjectOpenHashMap<>(); - - public PhysicsIdentityIndexResource() { - } - - public void putUuid(@Nonnull UUID uuid, @Nonnull Ref ref) { - refsByUuid.put(uuid, ref); - } - - @Nullable - public Ref getByUuid(@Nonnull UUID uuid) { - return refsByUuid.get(uuid); - } - - public void removeUuid(@Nonnull UUID uuid, @Nonnull Ref ref) { - refsByUuid.remove(uuid, ref); - } - - public void clearUuidRefs() { - refsByUuid.clear(); - } - - public void clear() { - refsByUuid.clear(); - } - - @Nonnull - @Override - public PhysicsIdentityIndexResource clone() { - PhysicsIdentityIndexResource copy = new PhysicsIdentityIndexResource(); - copy.refsByUuid.putAll(refsByUuid); - return copy; - } - - @Nonnull - public static ResourceType getResourceType() { - return PhysicsResourceTypes.identityIndexResourceType(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index 596260f2..cb5b4977 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -23,8 +23,6 @@ public final class PhysicsResourceTypes { private static ResourceType spaceCompatibilityIndexResourceType; @Nullable - private static ResourceType identityIndexResourceType; - @Nullable private static ResourceType snapshotResourceType; @Nullable private static ResourceType eventResourceType; @@ -55,9 +53,6 @@ public static void registerResourceTypes( spaceCompatibilityIndexResourceType = registry.registerResource( PhysicsSpaceCompatibilityIndexResource.class, PhysicsSpaceCompatibilityIndexResource::new); - identityIndexResourceType = registry.registerResource( - PhysicsIdentityIndexResource.class, - PhysicsIdentityIndexResource::new); snapshotResourceType = registry.registerResource( PhysicsSnapshotResource.class, PhysicsSnapshotResource::new); @@ -99,11 +94,6 @@ public static ResourceType stepSched return spaceCompatibilityIndexResourceType; } - @Nonnull - public static ResourceType identityIndexResourceType() { - return identityIndexResourceType; - } - @Nonnull public static ResourceType snapshotResourceType() { return snapshotResourceType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index 18cc97f4..daf95fd4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -552,19 +552,19 @@ private void markRegistrationTopologyChanged() { registrationTopologyGeneration++; } - public void refreshRowRefs(@Nonnull PhysicsIdentityIndexResource identity) { - PhysicsIdentityIndexResource checkedIdentity = Objects.requireNonNull(identity, "identity"); - refreshSpaceRefs(checkedIdentity); - refreshBodyRefs(checkedIdentity); - refreshJointRefs(checkedIdentity); + public void refreshRowRefs(@Nonnull PhysicsStore physicsStore) { + PhysicsStore checkedPhysicsStore = Objects.requireNonNull(physicsStore, "physicsStore"); + refreshSpaceRefs(checkedPhysicsStore); + refreshBodyRefs(checkedPhysicsStore); + refreshJointRefs(checkedPhysicsStore); } - private void refreshSpaceRefs(@Nonnull PhysicsIdentityIndexResource identity) { + private void refreshSpaceRefs(@Nonnull PhysicsStore physicsStore) { spaceRefsByRowIndex.clear(); spaceHandlesByRowIndex.clear(); backendIdsBySpaceRowIndex.clear(); spaceMetadataByKey.replaceAll((key, metadata) -> { - Ref spaceRef = identity.getByUuid(metadata.spaceUuid()); + Ref spaceRef = physicsStore.getRefFromUUID(metadata.spaceUuid()); if (spaceRef == null) { return metadata; } @@ -576,13 +576,13 @@ private void refreshSpaceRefs(@Nonnull PhysicsIdentityIndexResource identity) { }); } - private void refreshBodyRefs(@Nonnull PhysicsIdentityIndexResource identity) { + private void refreshBodyRefs(@Nonnull PhysicsStore physicsStore) { bodyRefsByRowIndex.clear(); bodyHandlesByRowIndex.clear(); bodySpaceHandlesByRowIndex.clear(); backendIdsByBodyRowIndex.clear(); bodySnapshotMetadataByKey.replaceAll((key, metadata) -> { - Ref bodyRef = identity.getByUuid(metadata.bodyUuid()); + Ref bodyRef = physicsStore.getRefFromUUID(metadata.bodyUuid()); if (bodyRef == null) { return metadata; } @@ -594,7 +594,7 @@ private void refreshBodyRefs(@Nonnull PhysicsIdentityIndexResource identity) { return new BodySnapshotMetadata(metadata.bodyUuid(), bodyRef, metadata.spaceUuid()); }); bodyHitMetadataByKey.replaceAll((_, metadata) -> { - Ref bodyRef = identity.getByUuid(metadata.bodyUuid()); + Ref bodyRef = physicsStore.getRefFromUUID(metadata.bodyUuid()); if (bodyRef == null) { return metadata; } @@ -605,13 +605,13 @@ private void refreshBodyRefs(@Nonnull PhysicsIdentityIndexResource identity) { }); } - private void refreshJointRefs(@Nonnull PhysicsIdentityIndexResource identity) { + private void refreshJointRefs(@Nonnull PhysicsStore physicsStore) { jointHandlesByRowIndex.clear(); jointSpaceHandlesByRowIndex.clear(); jointRefsByRowIndex.clear(); backendIdsByJointRowIndex.clear(); jointMetadataByKey.replaceAll((key, metadata) -> { - Ref jointRef = identity.getByUuid(metadata.jointUuid()); + Ref jointRef = physicsStore.getRefFromUUID(metadata.jointUuid()); if (jointRef == null) { return metadata; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java index bf86bec1..ea81c322 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystem.java @@ -47,7 +47,9 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRestoreStatusResource restore = store.getResource( PhysicsRestoreStatusResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, commandBuffer) -> applyCommands(store, runtime, restore, chunk, commandBuffer); + (chunk, commandBuffer) -> { + applyCommands(store, runtime, restore, chunk, commandBuffer); + }; store.forEachChunk(systemIndex, collector); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java index 94f86d53..10b106e3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/IdentityIndexSystem.java @@ -11,7 +11,6 @@ import com.hypixel.hytale.component.system.QuerySystem; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -29,17 +28,13 @@ public final class IdentityIndexSystem extends TickingSystem @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.clearUuidRefs(); store.getExternalData().clearUuidIndex(); BiConsumer, CommandBuffer> collector = - (chunk, _) -> indexChunk(store, identity, chunk); + (chunk, _) -> indexChunk(store, chunk); store.forEachChunk(systemIndex, collector); } private static void indexChunk(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { UUID uuid = PhysicsStoreSystemSupport.rowUuid(chunk, index); @@ -47,7 +42,6 @@ private static void indexChunk(@Nonnull Store store, continue; } Ref ref = chunk.getReferenceTo(index); - identity.putUuid(uuid, ref); store.getExternalData().putRefForUUID(uuid, ref); } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java index 54312eee..60b47bd6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PersistenceHydrationSystem.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; @@ -39,7 +38,6 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) private static void prepareTransientRestoreState(@Nonnull Store store) { store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings(); - store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); store.getExternalData().clearUuidIndex(); store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); store.getResource(PhysicsEventResource.getResourceType()).clear(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java index bcaf9208..fbc946b4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreSystemSupport.java @@ -7,7 +7,6 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.UUID; import javax.annotation.Nonnull; @@ -46,7 +45,8 @@ public static boolean isNil(@Nonnull UUID uuid) { } @Nullable - static > C component(@Nonnull Store store, + public static > C component( + @Nonnull Store store, @Nullable Ref ref, @Nonnull ComponentType type) { if (ref == null || !ref.isValid()) { @@ -54,24 +54,4 @@ static > C component(@Nonnull Store refForUuid(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID uuid) { - Ref ref = identity.getByUuid(uuid); - return ref != null && ref.isValid() ? ref : null; - } - - @Nullable - public static Ref resolvedRef(@Nonnull PhysicsIdentityIndexResource identity, - @Nonnull UUID uuid, - @Nullable Ref current) { - if (isNil(uuid)) { - return null; - } - if (current != null && current.isValid() && uuid.equals(rowUuid(current))) { - return current; - } - return refForUuid(identity, uuid); - } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java index e2c47edb..5df2edcb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystem.java @@ -12,7 +12,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource.BodySnapshotMetadata; @@ -45,21 +44,17 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); - removeStaleBodies(store, runtime, identity, restore); + removeStaleBodies(store, runtime, restore); } private static void removeStaleBodies(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore) { List staleBodies = new ArrayList<>(); runtime.forEachRuntimeSpaceBinding((_, backendId, spaceHandle, backendRuntime) -> runtime.forEachBodyHandle(backendId, spaceHandle, bodyId -> collectStaleBody(store, - identity, runtime, restore, staleBodies, @@ -72,15 +67,15 @@ private static void removeStaleBodies(@Nonnull Store store, } Set staleBodyUuids = new ObjectOpenHashSet<>(); staleBodies.forEach(body -> staleBodyUuids.add(body.bodyUuid())); - if (!removeDependentJoints(store, runtime, identity, restore, staleBodyUuids)) { + if (!removeDependentJoints(store, runtime, restore, staleBodyUuids)) { return; } - List orderedBodies = currentBodyRefs(identity, staleBodies); + List orderedBodies = currentBodyRefs(store, staleBodies); List bodyEntityRemovals = new ArrayList<>(orderedBodies.size()); for (BoundBody body : orderedBodies) { try { - PhysicsStoreRowCleanup.removeRuntimeBody(runtime, - identity, + PhysicsStoreRowCleanup.removeRuntimeBody(store, + runtime, body.bodyUuid(), body.bodyRef(), body.backendRuntime()); @@ -90,27 +85,26 @@ private static void removeStaleBodies(@Nonnull Store store, + " failed backend removal: " + exception.getMessage()); return; } - bodyEntityRemovals.add(new BodyEntityRemoval(body.bodyUuid(), body.bodyRef(), null)); + bodyEntityRemovals.add(new BodyEntityRemoval(body.bodyUuid(), body.bodyRef())); } removeBodyEntities(store, bodyEntityRemovals); } private static boolean removeDependentJoints(@Nonnull Store store, @Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Set staleBodyUuids) { if (staleBodyUuids.isEmpty()) { return true; } - List joints = collectDependentJoints(store, identity, staleBodyUuids); + List joints = collectDependentJoints(store, staleBodyUuids); joints.sort((first, second) -> Integer.compare(second.ref().getIndex(), first.ref().getIndex())); boolean removedAny = false; for (BoundJoint joint : joints) { try { - PhysicsStoreRowCleanup.removeRuntimeJoint(runtime, - identity, + PhysicsStoreRowCleanup.removeRuntimeJoint(store, + runtime, joint.jointUuid(), joint.ref()); } catch (RuntimeException exception) { @@ -139,12 +133,14 @@ private static void removeBodyEntities(@Nonnull Store store, } @Nonnull - private static List currentBodyRefs(@Nonnull PhysicsIdentityIndexResource identity, + private static List currentBodyRefs(@Nonnull Store store, @Nonnull List staleBodies) { List currentBodies = new ArrayList<>(staleBodies.size()); for (BoundBody body : staleBodies) { - Ref bodyRef = PhysicsStoreSystemSupport.refForUuid(identity, - body.bodyUuid()); + Ref bodyRef = store.getExternalData().getRefFromUUID(body.bodyUuid()); + if (bodyRef != null && (bodyRef.getStore() != store || !bodyRef.isValid())) { + bodyRef = null; + } currentBodies.add(new BoundBody(body.bodyUuid(), bodyRef != null ? bodyRef : body.bodyRef(), body.backendRuntime())); @@ -156,7 +152,6 @@ private static List currentBodyRefs(@Nonnull PhysicsIdentityIndexReso @Nonnull private static List collectDependentJoints(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull Set staleBodyUuids) { ConcurrentLinkedQueue joints = new ConcurrentLinkedQueue<>(); store.forEachEntityParallel(UuidComponent.getComponentType(), (index, chunk, _) -> { @@ -170,31 +165,37 @@ private static List collectDependentJoints(@Nonnull Store(joints); } - private static boolean shouldRemoveJointRow(@Nonnull PhysicsIdentityIndexResource identity, + private static boolean shouldRemoveJointRow(@Nonnull Store store, @Nonnull JointComponent joint, @Nonnull Set staleBodyUuids) { - return endpointRemoved(identity, joint.getBodyAUuid(), joint.getBodyARef(), staleBodyUuids) - || endpointRemoved(identity, joint.getBodyBUuid(), joint.getBodyBRef(), staleBodyUuids); + return endpointRemoved(store, joint.getBodyAUuid(), joint.getBodyARef(), staleBodyUuids) + || endpointRemoved(store, joint.getBodyBUuid(), joint.getBodyBRef(), staleBodyUuids); } - private static boolean endpointRemoved(@Nonnull PhysicsIdentityIndexResource identity, + private static boolean endpointRemoved(@Nonnull Store store, @Nonnull UUID bodyUuid, Ref currentRef, @Nonnull Set staleBodyUuids) { if (!staleBodyUuids.contains(bodyUuid)) { return false; } - return PhysicsStoreSystemSupport.resolvedRef(identity, bodyUuid, currentRef) == null; + UuidComponent currentUuid = PhysicsStoreSystemSupport.component(store, + currentRef, + UuidComponent.getComponentType()); + if (currentUuid != null && bodyUuid.equals(currentUuid.getUuid())) { + return false; + } + Ref indexed = store.getExternalData().getRefFromUUID(bodyUuid); + return indexed == null || indexed.getStore() != store || !indexed.isValid(); } private static void collectStaleBody(@Nonnull Store store, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull List staleBodies, @@ -210,9 +211,13 @@ private static void collectStaleBody(@Nonnull Store store, + " has no runtime snapshot metadata"); return; } - Ref bodyRef = PhysicsStoreSystemSupport.resolvedRef(identity, - metadata.bodyUuid(), - metadata.bodyRef()); + Ref bodyRef = metadata.bodyRef(); + UuidComponent currentUuid = PhysicsStoreSystemSupport.component(store, + bodyRef, + UuidComponent.getComponentType()); + if (currentUuid == null || !metadata.bodyUuid().equals(currentUuid.getUuid())) { + bodyRef = store.getExternalData().getRefFromUUID(metadata.bodyUuid()); + } if (bodyRef == null) { bodyRef = metadata.bodyRef(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java index 90814e90..f3d2d2ba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/JointBindingSystem.java @@ -15,7 +15,6 @@ import dev.hytalemodding.impulse.api.runtime.BackendJointType; import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; @@ -23,6 +22,7 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreSystemSupport; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -48,15 +48,13 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) return; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindChunk(runtime, identity, restore, chunk); + (chunk, _) -> bindChunk(store, runtime, restore, chunk); store.forEachChunk(systemIndex, collector); } - private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + private static void bindChunk(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { @@ -70,29 +68,29 @@ private static void bindChunk(@Nonnull PhysicsRuntimeResource runtime, } Ref jointRef = chunk.getReferenceTo(index); if (!joint.isEnabled()) { - removeJoint(runtime, identity, jointRef, jointUuid); + removeJoint(runtime, jointRef); continue; } BackendJointHandle existing = runtime.getJointHandle(jointRef); if (existing != null) { - if (!endpointsBound(runtime, identity, joint)) { - removeJoint(runtime, identity, jointRef, jointUuid); + if (!endpointsBound(store, runtime, joint)) { + removeJoint(runtime, jointRef); } continue; } - bindJoint(runtime, identity, restore, jointRef, jointUuid, joint); + bindJoint(store, runtime, restore, jointRef, jointUuid, joint); } } - private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + private static void bindJoint(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull PhysicsRestoreStatusResource restore, @Nonnull Ref jointRef, @Nonnull UUID jointUuid, @Nonnull JointComponent joint) { - Ref spaceRef = resolveSpaceRef(identity, joint); - Ref bodyARef = resolveBodyARef(identity, joint); - Ref bodyBRef = resolveBodyBRef(identity, joint); + Ref spaceRef = resolveSpaceRef(store, joint); + Ref bodyARef = resolveBodyARef(store, joint); + Ref bodyBRef = resolveBodyBRef(store, joint); BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; BackendBodyHandle bodyA = bodyARef != null ? runtime.getBodyHandle(bodyARef) : null; BackendBodyHandle bodyB = bodyBRef != null ? runtime.getBodyHandle(bodyBRef) : null; @@ -159,12 +157,12 @@ private static void bindJoint(@Nonnull PhysicsRuntimeResource runtime, } } - private static boolean endpointsBound(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, + private static boolean endpointsBound(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nonnull JointComponent joint) { - Ref spaceRef = resolveSpaceRef(identity, joint); - Ref bodyARef = resolveBodyARef(identity, joint); - Ref bodyBRef = resolveBodyBRef(identity, joint); + Ref spaceRef = resolveSpaceRef(store, joint); + Ref bodyARef = resolveBodyARef(store, joint); + Ref bodyBRef = resolveBodyBRef(store, joint); BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; BackendSpaceHandle bodyASpace = bodyARef != null ? runtime.getBodySpaceHandle(bodyARef) : null; BackendSpaceHandle bodyBSpace = bodyBRef != null ? runtime.getBodySpaceHandle(bodyBRef) : null; @@ -186,39 +184,49 @@ private static boolean endpointsBound(@Nonnull PhysicsRuntimeResource runtime, } @Nullable - private static Ref resolveSpaceRef(@Nonnull PhysicsIdentityIndexResource identity, + private static Ref resolveSpaceRef(@Nonnull Store store, @Nonnull JointComponent joint) { - Ref spaceRef = PhysicsStoreSystemSupport.resolvedRef(identity, - joint.getSpaceUuid(), - joint.getSpaceRef()); + Ref spaceRef = resolveRef(store, joint.getSpaceUuid(), joint.getSpaceRef()); joint.setSpaceRef(spaceRef); return spaceRef; } @Nullable - private static Ref resolveBodyARef(@Nonnull PhysicsIdentityIndexResource identity, + private static Ref resolveBodyARef(@Nonnull Store store, @Nonnull JointComponent joint) { - Ref bodyRef = PhysicsStoreSystemSupport.resolvedRef(identity, - joint.getBodyAUuid(), - joint.getBodyARef()); + Ref bodyRef = resolveRef(store, joint.getBodyAUuid(), joint.getBodyARef()); joint.setBodyARef(bodyRef); return bodyRef; } @Nullable - private static Ref resolveBodyBRef(@Nonnull PhysicsIdentityIndexResource identity, + private static Ref resolveBodyBRef(@Nonnull Store store, @Nonnull JointComponent joint) { - Ref bodyRef = PhysicsStoreSystemSupport.resolvedRef(identity, - joint.getBodyBUuid(), - joint.getBodyBRef()); + Ref bodyRef = resolveRef(store, joint.getBodyBUuid(), joint.getBodyBRef()); joint.setBodyBRef(bodyRef); return bodyRef; } + @Nullable + private static Ref resolveRef(@Nonnull Store store, + @Nonnull UUID uuid, + @Nullable Ref current) { + if (PhysicsStoreSystemSupport.isNil(uuid)) { + return null; + } + UuidComponent currentUuid = PhysicsStoreSystemSupport.component(store, + current, + UuidComponent.getComponentType()); + Ref resolved = currentUuid != null && uuid.equals(currentUuid.getUuid()) + ? current + : store.getExternalData().getRefFromUUID(uuid); + return resolved != null && resolved.getStore() == store && resolved.isValid() + ? resolved + : null; + } + private static void removeJoint(@Nonnull PhysicsRuntimeResource runtime, - @Nonnull PhysicsIdentityIndexResource identity, - @Nonnull Ref jointRef, - @Nonnull UUID jointUuid) { + @Nonnull Ref jointRef) { BackendJointHandle handle = runtime.getJointHandle(jointRef); if (handle == null) { return; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntities.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntities.java index dfde45c8..3f8ef3d5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntities.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsEntities.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; @@ -56,9 +55,8 @@ public static Ref resolveRef(@Nonnull Store store, @Nonnull UUID entityUuid) { Store checkedStore = Objects.requireNonNull(store, "store"); PhysicsThreading.requireWorldThread(checkedStore, "resolve a PhysicsStore entity ref"); - Ref ref = checkedStore - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(Objects.requireNonNull(entityUuid, "entityUuid")); + Ref ref = checkedStore.getExternalData() + .getRefFromUUID(Objects.requireNonNull(entityUuid, "entityUuid")); return ref != null && ref.getStore() == checkedStore && ref.isValid() ? ref : null; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpaces.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpaces.java index 114a43b7..d7291987 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpaces.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpaces.java @@ -9,7 +9,6 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; @@ -50,9 +49,7 @@ public static Ref resolveRef(@Nonnull Store store, if (spaceUuid == null) { return null; } - Ref ref = checkedStore - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); + Ref ref = checkedStore.getExternalData().getRefFromUUID(spaceUuid); return ref != null && ref.getStore() == checkedStore && ref.isValid() ? ref : null; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java index b2061801..9fb69c1d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java @@ -23,7 +23,6 @@ import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; @@ -75,8 +74,8 @@ void clearBodyCopiedStateRemovesMultipleBodiesWithoutRemovingRows() { retainedBodyRef); PhysicsStoreRowCleanup.clearBodyCopiedState(store, - List.of(new BodyEntityRemoval(firstBodyUuid, firstBodyRef, null), - new BodyEntityRemoval(secondBodyUuid, secondBodyRef, null))); + List.of(new BodyEntityRemoval(firstBodyUuid, firstBodyRef), + new BodyEntityRemoval(secondBodyUuid, secondBodyRef))); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); @@ -118,10 +117,8 @@ void refreshIdentityAndRuntimeRefsRebuildsLargeUuidIndexWithoutParallelMapWrites PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); } - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); for (UUID bodyUuid : bodyUuids) { - assertNotNull(identity.getByUuid(bodyUuid)); + assertNotNull(store.getExternalData().getRefFromUUID(bodyUuid)); } } finally { registry.removeStore(store); @@ -154,8 +151,8 @@ void removeRuntimeBodyRejectsStaleRefThatNoLongerMatchesUuid() { bindBody(runtime, fixture, firstBodyUuid, firstBodyRef, firstHandle); bindBody(runtime, fixture, secondBodyUuid, secondBodyRef, secondHandle); - boolean removed = PhysicsStoreRowCleanup.removeRuntimeBody(runtime, - store.getResource(PhysicsIdentityIndexResource.getResourceType()), + boolean removed = PhysicsStoreRowCleanup.removeRuntimeBody(store, + runtime, firstBodyUuid, secondBodyRef); @@ -193,8 +190,8 @@ void removeRuntimeJointRejectsStaleRefThatNoLongerMatchesUuid() { bindJoint(runtime, fixture, firstJointUuid, firstJointRef, firstHandle); bindJoint(runtime, fixture, secondJointUuid, secondJointRef, secondHandle); - boolean removed = PhysicsStoreRowCleanup.removeRuntimeJoint(runtime, - store.getResource(PhysicsIdentityIndexResource.getResourceType()), + boolean removed = PhysicsStoreRowCleanup.removeRuntimeJoint(store, + runtime, firstJointUuid, secondJointRef); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java index 77dffcdf..f1722b4f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.AddReason; @@ -23,12 +24,12 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -61,6 +62,31 @@ class PhysicsStoreTopologyMutationsTest { + @Test + void addSpaceIndexesRefInPhysicsStoreUuidMap() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("topology-add-space-uuid-map-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(100); + Ref spaceRef = PhysicsSpaceMutations.addSpace(store, + spaceUuid, + new SpaceId(100), + new BackendId("test:topology-add-space-uuid-map")); + + assertSame(spaceRef, store.getExternalData().getRefFromUUID(spaceUuid)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Test void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { ComponentRegistry registry = new ComponentRegistry<>(); @@ -100,15 +126,14 @@ void destroyBodyRemovesDependentJointRowAndRuntimeHandles() { PhysicsTopologyMutations.destroyBody(store, bodyAUuid); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsRuntimeResource runtime = store.getResource( PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - assertNull(identity.getByUuid(bodyAUuid)); - assertNull(identity.getByUuid(jointUuid)); - Ref remainingBodyRef = identity.getByUuid(bodyBUuid); + assertNull(store.getExternalData().getRefFromUUID(bodyAUuid)); + assertNull(store.getExternalData().getRefFromUUID(jointUuid)); + Ref remainingBodyRef = + store.getExternalData().getRefFromUUID(bodyBUuid); assertNotNull(remainingBodyRef); assertNull(runtime.getBodyHandle(bodyARef)); assertNull(runtime.getJointHandle(jointRef)); @@ -164,6 +189,58 @@ void clearBodiesKeepingSpacesRecoversFailedRestoreStatus() { } } + @Test + void clearBodiesKeepingSpacesClosesUntrackedRuntimeSpacesAfterFailedRestore() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("topology-clean-closes-orphan-runtime")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(1); + BackendId backendId = new BackendId("test:topology-clean-orphan-runtime"); + Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, + spaceUuid, + new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), + AddReason.SPAWN); + assertNotNull(spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); + SpaceId compatibilitySpaceId = new SpaceId(42); + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .putSpace(compatibilitySpaceId, spaceUuid); + FakePhysicsBackendRuntime staleRuntime = (FakePhysicsBackendRuntime) + new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); + staleRuntime.createSpace(compatibilitySpaceId); + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + runtime.putRuntime(backendId, staleRuntime); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markFailed("PhysicsStore space " + spaceUuid + + " failed backend binding: Space already exists: " + compatibilitySpaceId); + + PhysicsTopologyMutations.clearBodiesKeepingSpaces(store); + + assertFalse(staleRuntime.hasSpace(compatibilitySpaceId.value())); + assertNull(runtime.getRuntime(backendId)); + assertFalse(restore.isFailed(), restore.getFailureMessage()); + assertTrue(restore.isHydrated()); + assertEquals(spaceUuid, + store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) + .getSpaceUuid(compatibilitySpaceId)); + assertNotNull(store.getComponent(spaceRef, SpaceComponent.getComponentType())); + } finally { + registry.removeStore(store); + registry.shutdown(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + } + } + @Nonnull private static BoundSpace addBoundSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -173,9 +250,6 @@ private static BoundSpace addBoundSpace(@Nonnull Store store, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(spaceRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(spaceUuid, spaceRef); store.getExternalData().putRefForUUID(spaceUuid, spaceRef); PhysicsBackendRuntime backendRuntime = new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); @@ -216,9 +290,6 @@ private static Ref addBody(@Nonnull Store store, new CollisionFilterComponent(0x01, 0x02)), AddReason.SPAWN); assertNotNull(bodyRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(bodyUuid, bodyRef); store.getExternalData().putRefForUUID(bodyUuid, bodyRef); return bodyRef; } @@ -247,9 +318,6 @@ private static Ref addJoint(@Nonnull Store store, joint), AddReason.SPAWN); assertNotNull(jointRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(jointUuid, jointRef); store.getExternalData().putRefForUUID(jointUuid, jointRef); return jointRef; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java index 64ca83e8..3c24a4e7 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java @@ -24,7 +24,6 @@ import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -115,9 +114,6 @@ private static BoundSpace addBoundSpace(@Nonnull Store store, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(spaceRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(spaceUuid, spaceRef); store.getExternalData().putRefForUUID(spaceUuid, spaceRef); FakePhysicsBackendRuntime runtime = (FakePhysicsBackendRuntime) new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); @@ -156,9 +152,6 @@ private static Ref addBoundBody(@Nonnull Store store new CollisionFilterComponent(0x01, 0x02)), AddReason.SPAWN); assertNotNull(bodyRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(bodyUuid, bodyRef); store.getExternalData().putRefForUUID(bodyUuid, bodyRef); long bodyId = space.runtime().createBody(space.handle().value(), BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), @@ -224,9 +217,6 @@ private static Ref addJoint(@Nonnull Store store, joint), AddReason.SPAWN); assertNotNull(jointRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(jointUuid, jointRef); store.getExternalData().putRefForUUID(jointUuid, jointRef); return jointRef; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java index f9012372..e7fb5ad7 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java @@ -24,7 +24,6 @@ import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -95,17 +94,15 @@ void tickRemovesMultipleStaleBodiesAndTheirCopiedStateTogether() { new StaleBodyRemovalSystem().tick(0.0f, 0, store); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); PhysicsRuntimeResource runtime = store.getResource( PhysicsRuntimeResource.getResourceType()); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); assertFalse(store.getResource(PhysicsRestoreStatusResource.getResourceType()) .isFailed()); - assertNull(identity.getByUuid(firstStaleUuid)); - assertNull(identity.getByUuid(secondStaleUuid)); - assertNotNull(identity.getByUuid(retainedUuid)); + assertNull(store.getExternalData().getRefFromUUID(firstStaleUuid)); + assertNull(store.getExternalData().getRefFromUUID(secondStaleUuid)); + assertNotNull(store.getExternalData().getRefFromUUID(retainedUuid)); assertNull(runtime.getBodyHandle(firstStaleRef)); assertNull(runtime.getBodyHandle(secondStaleRef)); assertNotNull(runtime.getBodyHandle(retainedRef)); @@ -133,9 +130,6 @@ private static BoundSpace addBoundSpace(@Nonnull Store store, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(spaceRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(spaceUuid, spaceRef); store.getExternalData().putRefForUUID(spaceUuid, spaceRef); PhysicsBackendRuntime runtime = new FakePhysicsBackendRuntimeProvider(backendId, false, false).createRuntime(); @@ -177,9 +171,6 @@ private static Ref addBody(@Nonnull Store store, new CollisionFilterComponent(0x01, 0x02)), AddReason.SPAWN); assertNotNull(bodyRef); - PhysicsIdentityIndexResource identity = store.getResource( - PhysicsIdentityIndexResource.getResourceType()); - identity.putUuid(bodyUuid, bodyRef); store.getExternalData().putRefForUUID(bodyUuid, bodyRef); return bodyRef; } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java index df3a0a48..5202f13c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java @@ -19,7 +19,6 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -59,8 +58,6 @@ void boundSpaceBackendIdMutationFailsRestoreInsteadOfSilentlyKeepingOldBinding() new SpaceComponent(originalBackendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); assertNotNull(spaceRef); - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .putUuid(spaceUuid, spaceRef); store.getExternalData().putRefForUUID(spaceUuid, spaceRef); FakePhysicsBackendRuntime backendRuntime = (FakePhysicsBackendRuntime) new FakePhysicsBackendRuntimeProvider(originalBackendId, false, false) From d2137a6b9fc55568121de1231e2e93dbd47fbae8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:38:30 +0200 Subject: [PATCH 517/534] feat(core): add module-owned PhysicsStore cleanup hooks Signed-off-by: Blovien --- .../physicschunk/PhysicsChunkStoreTypes.java | 423 +++++++++++++++++- .../physicschunk/PhysicsChunkSubPlugin.java | 2 + .../physics/PhysicsStoreCleanupHooks.java | 138 ++++++ .../physics/PhysicsStoreRuntimeCleaner.java | 10 +- .../PhysicsStoreRegistration.java | 13 +- .../PhysicsChunkStoreTypesTest.java | 28 +- 6 files changed, 586 insertions(+), 28 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreCleanupHooks.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java index 6af547f1..5e367db4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypes.java @@ -1,27 +1,71 @@ package dev.hytalemodding.impulse.core.internal.modules.physicschunk; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; -import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionComponentSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionMutationDrainSystem; -import dev.hytalemodding.impulse.core.internal.systems.ChunkCollisionVoxelStitchingSystem; -import dev.hytalemodding.impulse.core.internal.systems.PhysicsChunkSettingsIndexSystem; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionRestoreDependencyComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkComponentSyncResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.ChunkCollisionComponentSyncSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.ChunkCollisionMutationDrainSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.ChunkCollisionRestorePrewarmSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.ChunkCollisionVoxelStitchingSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsChunkSettingsIndexSystem; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreCleanupHooks; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; +import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.BiConsumer; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.joml.Vector3d; +import org.joml.Vector3f; /** * PhysicsStore-side type registration owned by the PhysicsChunk module. */ public final class PhysicsChunkStoreTypes { + @Nonnull + private static final Consumer> FULL_STORE_CLEANUP = + PhysicsChunkStoreTypes::clearPhysicsStoreRuntimeResources; + @Nonnull + private static final Consumer> BODY_RUNTIME_CLEANUP = + PhysicsChunkStoreTypes::clearPhysicsStoreBodyRuntimeResources; + @Nonnull + private static final PhysicsStoreCleanupHooks.SpaceCleanup SPACE_CLEANUP = + PhysicsChunkStoreTypes::clearPhysicsStoreSpaceRuntimeResources; + @Nonnull + private static final PhysicsStoreCleanupHooks.BodyRowCleanup BODY_ROW_CLEANUP = + PhysicsChunkStoreTypes::clearPhysicsStoreBodyRowRuntimeResources; + private PhysicsChunkStoreTypes() { } @@ -48,6 +92,20 @@ public static void clearPhysicsStoreResourceTypes() { PhysicsChunkComponentSyncResource.clearResourceType(); } + public static void registerPhysicsStoreCleanupHooks() { + PhysicsStoreCleanupHooks.registerFullStoreCleanup(FULL_STORE_CLEANUP); + PhysicsStoreCleanupHooks.registerBodyRuntimeCleanup(BODY_RUNTIME_CLEANUP); + PhysicsStoreCleanupHooks.registerSpaceCleanup(SPACE_CLEANUP); + PhysicsStoreCleanupHooks.registerBodyRowCleanup(BODY_ROW_CLEANUP); + } + + public static void clearPhysicsStoreCleanupHooks() { + PhysicsStoreCleanupHooks.unregisterFullStoreCleanup(FULL_STORE_CLEANUP); + PhysicsStoreCleanupHooks.unregisterBodyRuntimeCleanup(BODY_RUNTIME_CLEANUP); + PhysicsStoreCleanupHooks.unregisterSpaceCleanup(SPACE_CLEANUP); + PhysicsStoreCleanupHooks.unregisterBodyRowCleanup(BODY_ROW_CLEANUP); + } + public static void registerSpaceBindingSystems( @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new PhysicsChunkSettingsIndexSystem()); @@ -56,6 +114,7 @@ public static void registerSpaceBindingSystems( public static void registerPreBodyBindingSystems( @Nonnull ComponentRegistryProxy registry) { registry.registerSystem(new ChunkCollisionMutationDrainSystem()); + registry.registerSystem(new ChunkCollisionRestorePrewarmSystem()); } public static void registerPostBodyBindingSystems( @@ -79,12 +138,156 @@ public static void clearPhysicsStoreRuntimeResources(@Nonnull Store store) { + clearIfPresent(store, + PhysicsChunkCollisionMutationQueueResource.getResourceType(), + PhysicsChunkCollisionMutationQueueResource::clear); + clearIfPresent(store, + PhysicsChunkCollisionPayloadResource.getResourceType(), + PhysicsChunkCollisionPayloadResource::clear); + } + + private static void clearPhysicsStoreSpaceRuntimeResources( + @Nonnull Store store, + @Nonnull UUID spaceUuid) { + PhysicsChunkCollisionMutationQueueResource queue = + resourceIfPresent(store, PhysicsChunkCollisionMutationQueueResource.getResourceType()); + if (queue != null) { + queue.removeIf(mutation -> spaceUuid.equals(mutation.spaceUuid())); + } + } + + private static void clearPhysicsStoreBodyRowRuntimeResources( + @Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef) { + if (!bodyRef.isValid()) { + return; + } + ChunkCollisionSourceComponent source = store.getComponent(bodyRef, + ChunkCollisionSourceComponent.getComponentType()); + if (source == null) { + return; + } + removePayload(store, source.getPayloadResourceKey()); + } + + public static int clearChunkCollisionRowsForSpace(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore chunk-collision rows"); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + Ref spaceRef = store.getExternalData().getRefFromUUID(spaceUuid); + List rows = collectChunkCollisionRows(store, spaceUuid, spaceRef); + rows.sort((first, second) -> Integer.compare(second.ref().getIndex(), + first.ref().getIndex())); + int removedBodies = 0; + List bodyRemovals = + new ArrayList<>(rows.size()); + for (GeneratedBodyRow row : rows) { + if (PhysicsStoreRowCleanup.removeRuntimeBody(store, runtime, row.uuid(), row.ref())) { + removedBodies++; + } + bodyRemovals.add(new PhysicsStoreRowCleanup.BodyEntityRemoval(row.uuid(), + row.ref())); + } + clearPhysicsStoreSpaceRuntimeResources(store, spaceUuid); + if (!bodyRemovals.isEmpty()) { + PhysicsStoreRowCleanup.removeBodyEntities(store, bodyRemovals); + PhysicsStoreRowCleanup.refreshIdentityAndRuntimeRefs(store); + } + return removedBodies; + } + @Nullable public static PhysicsChunkCollisionPayloadResource collisionPayloadsIfPresent( @Nonnull Store store) { return resourceIfPresent(store, PhysicsChunkCollisionPayloadResource.getResourceType()); } + public static void prewarmRestoreDependencies(@Nonnull Store store) { + if (!PhysicsChunkLifecycle.isEnabled()) { + return; + } + PhysicsChunkSettingsIndexResource settingsIndex = + resourceIfPresent(store, PhysicsChunkSettingsIndexResource.getResourceType()); + PhysicsChunkCollisionMutationQueueResource queue = + resourceIfPresent(store, PhysicsChunkCollisionMutationQueueResource.getResourceType()); + if (settingsIndex == null || queue == null) { + return; + } + World world = PhysicsThreading.world(store); + if (!world.isInThread()) { + return; + } + PhysicsChunkCollisionStreamingResource streaming = streamingIfPresent(world); + if (streaming == null) { + return; + } + + Map> centersByKey = + collectRestorePrewarmCenters(store, settingsIndex); + if (centersByKey.isEmpty()) { + return; + } + queue.updateStamp(PhysicsChunkLifecycle.generation(), settingsIndex.generation()); + long tick = Math.max(0L, world.getTick()); + for (Map.Entry> entry : centersByKey.entrySet()) { + RestorePrewarmKey key = entry.getKey(); + streaming.ensureAround(world, + key.settings().spaceUuid(), + queue, + entry.getValue(), + key.radius(), + tick, + null, + key.settings().buildOptions()); + } + } + + public static boolean shouldDeferChunkCollisionRestore(@Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref bodyRef, + @Nonnull BodyComponent body, + @Nullable DynamicsComponent dynamics, + @Nonnull PhysicsRestoreStatusResource restore) { + ChunkCollisionRestoreDependencyComponent dependency = store.getComponent(bodyRef, + ChunkCollisionRestoreDependencyComponent.getComponentType()); + if (dependency == null) { + return false; + } + if (!dependency.getSpaceUuid().equals(body.getSpaceUuid())) { + clearRestoreDependency(commandBuffer, bodyRef); + return false; + } + if (dynamics == null || dynamics.getBodyType() != PhysicsBodyType.DYNAMIC) { + clearRestoreDependency(commandBuffer, bodyRef); + return false; + } + if (!PhysicsChunkLifecycle.isEnabled()) { + clearRestoreDependency(commandBuffer, bodyRef); + return false; + } + PhysicsChunkSettingsIndexResource settingsIndex = + resourceIfPresent(store, PhysicsChunkSettingsIndexResource.getResourceType()); + if (settingsIndex == null) { + clearRestoreDependency(commandBuffer, bodyRef); + return false; + } + PhysicsChunkSpaceSettings settings = settingsIndex.settings(body.getSpaceUuid()); + if (settings == null || settings.mode() == PhysicsChunkCollisionMode.NONE) { + clearRestoreDependency(commandBuffer, bodyRef); + return false; + } + prewarmRestoreDependency(store, settings, dependency); + if (hasGeneratedSupportRow(store, dependency)) { + clearRestoreDependency(commandBuffer, bodyRef); + return false; + } + restore.recordSoftSkip("Body restore dependency pending chunk collision"); + return true; + } + private static > void clearIfPresent( @Nonnull Store store, @Nullable ResourceType type, @@ -110,4 +313,208 @@ private static > T resourceIfPresent( return null; } } + + @Nonnull + private static List collectChunkCollisionRows( + @Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nullable Ref spaceRef) { + ComponentType uuidType = UuidComponent.getComponentType(); + ConcurrentLinkedQueue rows = new ConcurrentLinkedQueue<>(); + store.forEachEntityParallel(uuidType, (index, chunk, _) -> { + UuidComponent uuid = chunk.getComponent(index, uuidType); + if (uuid == null) { + return; + } + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + ChunkCollisionSourceComponent source = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()); + if (source != null + && body != null + && matchesSpace(body.getSpaceRef(), body.getSpaceUuid(), spaceRef, spaceUuid)) { + rows.add(new GeneratedBodyRow(chunk.getReferenceTo(index), uuid.getUuid())); + } + }); + return new ArrayList<>(rows); + } + + private static boolean matchesSpace(@Nullable Ref rowSpaceRef, + @Nonnull UUID rowSpaceUuid, + @Nullable Ref spaceRef, + @Nonnull UUID spaceUuid) { + if (spaceRef != null && rowSpaceRef != null) { + return sameRef(rowSpaceRef, spaceRef); + } + return spaceUuid.equals(rowSpaceUuid); + } + + private static boolean sameRef(@Nonnull Ref first, + @Nonnull Ref second) { + return first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); + } + + private static void removePayload(@Nonnull Store store, + @Nullable String payloadResourceKey) { + if (payloadResourceKey == null || payloadResourceKey.isBlank()) { + return; + } + PhysicsChunkCollisionPayloadResource payloads = + resourceIfPresent(store, PhysicsChunkCollisionPayloadResource.getResourceType()); + if (payloads != null) { + payloads.remove(payloadResourceKey); + } + } + + @Nonnull + private static Map> collectRestorePrewarmCenters( + @Nonnull Store store, + @Nonnull PhysicsChunkSettingsIndexResource settingsIndex) { + Map> centersByKey = new Object2ObjectOpenHashMap<>(); + BiConsumer, CommandBuffer> collector = + (chunk, _) -> collectRestorePrewarmCenters(chunk, settingsIndex, centersByKey); + store.forEachChunk(UuidComponent.getComponentType(), collector); + return centersByKey; + } + + private static void collectRestorePrewarmCenters( + @Nonnull ArchetypeChunk chunk, + @Nonnull PhysicsChunkSettingsIndexResource settingsIndex, + @Nonnull Map> centersByKey) { + for (int index = 0; index < chunk.size(); index++) { + BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + if (body == null) { + continue; + } + DynamicsComponent dynamics = chunk.getComponent(index, + DynamicsComponent.getComponentType()); + if (dynamics == null || dynamics.getBodyType() != PhysicsBodyType.DYNAMIC) { + continue; + } + ChunkCollisionRestoreDependencyComponent dependency = chunk.getComponent(index, + ChunkCollisionRestoreDependencyComponent.getComponentType()); + if (dependency == null) { + continue; + } + PhysicsChunkSpaceSettings settings = settingsIndex.settings(body.getSpaceUuid()); + if (settings == null || settings.mode() == PhysicsChunkCollisionMode.NONE) { + continue; + } + RestorePrewarmKey key = new RestorePrewarmKey(settings, dependency.getRadius()); + centersByKey.computeIfAbsent(key, _ -> new ArrayList<>()) + .add(vector3d(dependency.getCenter())); + } + } + + private static void prewarmRestoreDependency(@Nonnull Store store, + @Nonnull PhysicsChunkSpaceSettings settings, + @Nonnull ChunkCollisionRestoreDependencyComponent dependency) { + if (!PhysicsChunkLifecycle.isEnabled()) { + return; + } + World world = PhysicsThreading.world(store); + if (!world.isInThread()) { + return; + } + PhysicsChunkCollisionStreamingResource streaming = streamingIfPresent(world); + PhysicsChunkCollisionMutationQueueResource queue = + resourceIfPresent(store, PhysicsChunkCollisionMutationQueueResource.getResourceType()); + PhysicsChunkSettingsIndexResource settingsIndex = + resourceIfPresent(store, PhysicsChunkSettingsIndexResource.getResourceType()); + if (streaming == null || queue == null || settingsIndex == null) { + return; + } + queue.updateStamp(PhysicsChunkLifecycle.generation(), settingsIndex.generation()); + streaming.ensureAround(world, + settings.spaceUuid(), + queue, + List.of(vector3d(dependency.getCenter())), + dependency.getRadius(), + Math.max(0L, world.getTick()), + null, + settings.buildOptions()); + } + + @Nullable + private static PhysicsChunkCollisionStreamingResource streamingIfPresent(@Nonnull World world) { + try { + ResourceType type = + PhysicsChunkCollisionStreamingResource.getResourceType(); + return world.getEntityStore().getStore().getResource(type); + } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException | IllegalStateException _) { + return null; + } + } + + private static boolean hasGeneratedSupportRow(@Nonnull Store store, + @Nonnull ChunkCollisionRestoreDependencyComponent dependency) { + Vector3f center = dependency.getCenter(); + int radius = dependency.getRadius(); + int minX = (int) Math.floor(center.x) - radius; + int maxX = (int) Math.floor(center.x) + radius; + int minY = Math.max(0, (int) Math.floor(center.y) - radius); + int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, (int) Math.floor(center.y) + radius); + int minZ = (int) Math.floor(center.z) - radius; + int maxZ = (int) Math.floor(center.z) + radius; + + for (int chunkX = ChunkUtil.chunkCoordinate(minX); + chunkX <= ChunkUtil.chunkCoordinate(maxX); + chunkX++) { + for (int sectionY = ChunkUtil.indexSection(minY); + sectionY <= ChunkUtil.indexSection(maxY); + sectionY++) { + for (int chunkZ = ChunkUtil.chunkCoordinate(minZ); + chunkZ <= ChunkUtil.chunkCoordinate(maxZ); + chunkZ++) { + if (hasGeneratedSectionRow(store, dependency.getSpaceUuid(), chunkX, + sectionY, chunkZ)) { + return true; + } + } + } + } + return false; + } + + private static boolean hasGeneratedSectionRow(@Nonnull Store store, + @Nonnull UUID spaceUuid, + int chunkX, + int sectionY, + int chunkZ) { + String sourceKey = PhysicsStoreChunkCollisionMutations.sourceKey(chunkX, + sectionY, + chunkZ); + return hasGeneratedSectionPart(store, spaceUuid, sourceKey, PartKind.BOX) + || hasGeneratedSectionPart(store, spaceUuid, sourceKey, PartKind.DETAIL_BOX) + || hasGeneratedSectionPart(store, spaceUuid, sourceKey, PartKind.NATIVE_VOXELS); + } + + private static boolean hasGeneratedSectionPart(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull String sourceKey, + @Nonnull PartKind partKind) { + Ref ref = store.getExternalData().getRefFromUUID( + ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + partKind, + 0)); + return ref != null && ref.isValid(); + } + + private static void clearRestoreDependency(@Nonnull CommandBuffer commandBuffer, + @Nonnull Ref bodyRef) { + commandBuffer.removeComponent(bodyRef, + ChunkCollisionRestoreDependencyComponent.getComponentType()); + } + + @Nonnull + private static Vector3d vector3d(@Nonnull Vector3f vector) { + return new Vector3d(vector.x, vector.y, vector.z); + } + + private record RestorePrewarmKey(@Nonnull PhysicsChunkSpaceSettings settings, int radius) { + } + + private record GeneratedBodyRow(@Nonnull Ref ref, @Nonnull UUID uuid) { + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java index 5d9f5256..2f383c9f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java @@ -30,6 +30,7 @@ protected void setup() { PhysicsChunkTypes.registerEntityStoreResourceTypes(entityRegistry); PhysicsChunkTypes.registerEntityStoreSystems(entityRegistry); PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(physicsRegistry); + PhysicsChunkStoreTypes.registerPhysicsStoreCleanupHooks(); PhysicsChunkStoreTypes.registerSpaceBindingSystems(physicsRegistry); PhysicsChunkStoreTypes.registerPreBodyBindingSystems(physicsRegistry); PhysicsChunkStoreTypes.registerPostBodyBindingSystems(physicsRegistry); @@ -42,6 +43,7 @@ protected void setup() { protected void shutdown() { PhysicsChunkLifecycle.disable(); PhysicsChunkCommandSet.unregister(); + PhysicsChunkStoreTypes.clearPhysicsStoreCleanupHooks(); PhysicsChunkTypes.clearEntityStoreResourceTypes(); PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreCleanupHooks.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreCleanupHooks.java new file mode 100644 index 00000000..de13c56c --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreCleanupHooks.java @@ -0,0 +1,138 @@ +package dev.hytalemodding.impulse.core.internal.physics; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Module-owned PhysicsStore cleanup callbacks. + */ +public final class PhysicsStoreCleanupHooks { + + @Nonnull + private static final Set>> FULL_STORE_CLEANUPS = + new CopyOnWriteArraySet<>(); + @Nonnull + private static final Set>> BODY_RUNTIME_CLEANUPS = + new CopyOnWriteArraySet<>(); + @Nonnull + private static final Set SPACE_CLEANUPS = new CopyOnWriteArraySet<>(); + @Nonnull + private static final Set BODY_ROW_CLEANUPS = new CopyOnWriteArraySet<>(); + + private PhysicsStoreCleanupHooks() { + } + + public static void registerFullStoreCleanup( + @Nonnull Consumer> cleanup) { + FULL_STORE_CLEANUPS.add(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void unregisterFullStoreCleanup( + @Nonnull Consumer> cleanup) { + FULL_STORE_CLEANUPS.remove(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void registerBodyRuntimeCleanup( + @Nonnull Consumer> cleanup) { + BODY_RUNTIME_CLEANUPS.add(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void unregisterBodyRuntimeCleanup( + @Nonnull Consumer> cleanup) { + BODY_RUNTIME_CLEANUPS.remove(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void registerSpaceCleanup(@Nonnull SpaceCleanup cleanup) { + SPACE_CLEANUPS.add(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void unregisterSpaceCleanup(@Nonnull SpaceCleanup cleanup) { + SPACE_CLEANUPS.remove(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void registerBodyRowCleanup(@Nonnull BodyRowCleanup cleanup) { + BODY_ROW_CLEANUPS.add(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void unregisterBodyRowCleanup(@Nonnull BodyRowCleanup cleanup) { + BODY_ROW_CLEANUPS.remove(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void clearFullStoreRuntimeResources(@Nonnull Store store) { + RuntimeException failure = null; + for (Consumer> cleanup : FULL_STORE_CLEANUPS) { + failure = run(failure, () -> cleanup.accept(store)); + } + throwIfFailed(failure); + } + + static void clearBodyRuntimeResources(@Nonnull Store store) { + RuntimeException failure = null; + for (Consumer> cleanup : BODY_RUNTIME_CLEANUPS) { + failure = run(failure, () -> cleanup.accept(store)); + } + throwIfFailed(failure); + } + + static void cleanupSpaceRuntimeResources(@Nonnull Store store, + @Nonnull UUID spaceUuid) { + RuntimeException failure = null; + for (SpaceCleanup cleanup : SPACE_CLEANUPS) { + failure = run(failure, () -> cleanup.cleanup(store, spaceUuid)); + } + throwIfFailed(failure); + } + + static void cleanupBodyRowRuntimeResources(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef) { + RuntimeException failure = null; + for (BodyRowCleanup cleanup : BODY_ROW_CLEANUPS) { + failure = run(failure, () -> cleanup.cleanup(store, bodyUuid, bodyRef)); + } + throwIfFailed(failure); + } + + @Nullable + private static RuntimeException run(@Nullable RuntimeException failure, + @Nonnull Runnable action) { + try { + action.run(); + return failure; + } catch (RuntimeException exception) { + if (failure == null) { + return exception; + } + failure.addSuppressed(exception); + return failure; + } + } + + private static void throwIfFailed(@Nullable RuntimeException failure) { + if (failure != null) { + throw failure; + } + } + + @FunctionalInterface + public interface SpaceCleanup { + + void cleanup(@Nonnull Store store, @Nonnull UUID spaceUuid); + } + + @FunctionalInterface + public interface BodyRowCleanup { + + void cleanup(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java index f60fe8f5..7c1907ed 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRuntimeCleaner.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; @@ -11,9 +10,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import javax.annotation.Nonnull; @@ -32,16 +28,14 @@ public static void clearAll(@Nonnull Store store) { (index, chunk, commandBuffer) -> commandBuffer.removeEntity( chunk.getReferenceTo(index), RemoveReason.REMOVE)); - store.getResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()).clear(); store.getResource(PhysicsRuntimeResource.getResourceType()).destroyBackendBindings(); - store.getResource(PhysicsIdentityIndexResource.getResourceType()).clear(); + store.getExternalData().clearUuidIndex(); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()).clear(); store.getResource(PhysicsSnapshotResource.getResourceType()).clear(); store.getResource(PhysicsEventResource.getResourceType()).clear(); store.getResource(PhysicsProfilingResource.getResourceType()).reset(); store.getResource(PhysicsStoreReadQueueResource.getResourceType()).clear(); - store.getResource(PhysicsChunkCollisionPayloadResource.getResourceType()).clear(); - store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()).clear(); + PhysicsStoreCleanupHooks.clearFullStoreRuntimeResources(store); store.getResource(PhysicsRestoreStatusResource.getResourceType()) .markRecoveredFromCleanup(); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java index 5a29929a..9122df31 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java @@ -8,10 +8,9 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.early.PhysicsStoreHooks; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.persistence.PhysicsStoreHolderStorage; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreCleanupHooks; import dev.hytalemodding.impulse.core.internal.resources.PhysicsEventResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -24,14 +23,14 @@ import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.BodyCommandApplicationSystem; import dev.hytalemodding.impulse.core.internal.systems.binding.ColliderBindingSystem; -import dev.hytalemodding.impulse.core.internal.systems.CompletedStepPublicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.publication.CompletedStepPublicationSystem; import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.binding.JointBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.systems.PhysicsStoreQueuedReadSystem; import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; -import dev.hytalemodding.impulse.core.internal.systems.StepSubmissionSystem; +import dev.hytalemodding.impulse.core.internal.systems.step.StepSubmissionSystem; import dev.hytalemodding.impulse.core.internal.systems.StaleBodyRemovalSystem; import dev.hytalemodding.impulse.core.internal.systems.binding.TargetBindingSystem; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; @@ -102,11 +101,9 @@ private static void clearRuntimeStateBeforeShutdown(@Nonnull PhysicsStore physic PhysicsRuntimeResource.getResourceType(), PhysicsRuntimeResource::destroyBackendBindings)); failure = runShutdownCleanup(failure, - () -> PhysicsChunkStoreTypes.clearPhysicsStoreRuntimeResources(store)); + () -> PhysicsStoreCleanupHooks.clearFullStoreRuntimeResources(store)); failure = runShutdownCleanup(failure, - () -> cleanupResource(store, - PhysicsIdentityIndexResource.getResourceType(), - PhysicsIdentityIndexResource::clear)); + () -> store.getExternalData().clearUuidIndex()); failure = runShutdownCleanup(failure, () -> cleanupResource(store, PhysicsSpaceCompatibilityIndexResource.getResourceType(), diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java index b2e9f75d..eb5281ca 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java @@ -8,10 +8,11 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkComponentSyncResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkComponentSyncResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import java.util.ArrayList; @@ -41,6 +42,25 @@ void runtimeResourceCleanupIgnoresUnregisteredPhysicsChunkResources() { } } + @Test + void physicsChunkSystemsRegisterInPluginSetupOrder() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + try { + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + PhysicsStoreRegistration.register(proxy); + PhysicsChunkStoreTypes.registerSpaceBindingSystems(proxy); + + assertDoesNotThrow(() -> PhysicsChunkStoreTypes.registerPreBodyBindingSystems(proxy)); + assertDoesNotThrow(() -> PhysicsChunkStoreTypes.registerPostBodyBindingSystems(proxy)); + } finally { + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + registry.shutdown(); + } + } + private static void unregisterPhysicsChunkResources(@Nonnull ComponentRegistry registry) { registry.unregisterResource(PhysicsChunkCollisionMutationQueueResource.getResourceType()); registry.unregisterResource(PhysicsChunkCollisionPayloadResource.getResourceType()); From 49b59d3e1a8c3ae98624f55a3392acea0e800b81 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:38:58 +0200 Subject: [PATCH 518/534] fix(physicsentity): preserve projection cleanup backend state Signed-off-by: Blovien --- .../systems/sync/PhysicsSyncSystem.java | 23 ++++---- .../resources/PhysicsDebugResource.java | 12 +++-- .../body/PhysicsBodyRuntimeState.java | 43 +++++++++++++++ .../systems/debug/PhysicsDebugSystem.java | 52 +++++++++--------- .../debug/PhysicsStoreDebugQueries.java | 14 ++--- .../systems/sync/PhysicsSyncSystemTest.java | 54 +++++++++++++++++-- .../debug/PhysicsStoreDebugQueriesTest.java | 4 +- 7 files changed, 148 insertions(+), 54 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java index 12269c47..40bd7098 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java @@ -18,14 +18,13 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsBodySyncStateResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodySyncStateResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.VisualInterestCollector; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual.PhysicsProjectionCleanupSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; @@ -58,6 +57,9 @@ public class PhysicsSyncSystem extends EntityTickingSystem { private static final float TRANSFORM_POSITION_EPSILON = 0.000001f; private static final float TRANSFORM_ROTATION_EPSILON = 0.000001f; + private static final float LOW_SPEED_POSITION_MOTION_THRESHOLD = 0.125f; + private static final float LOW_SPEED_ROTATION_MOTION_THRESHOLD = + (float) Math.toRadians(1.0); @Nonnull private final ComponentType attachmentType; @@ -269,8 +271,11 @@ private static SyncResult applyComputedPhysicsStoreSnapshot( if (!syncState.isInitializedFor(snapshot.bodyUuid())) { syncState.clear(); } - float snapshotMotion = syncState.recordSnapshotObservation(scratch.position); - boolean lowSpeed = !Float.isNaN(snapshotMotion) && snapshotMotion < 0.125f; + PhysicsBodyRuntimeState.BodySyncState.SnapshotMotion snapshotMotion = + syncState.recordSnapshotObservation(scratch.position, scratch.visualRotation); + boolean lowSpeed = snapshotMotion.observed() + && snapshotMotion.positionDistance() < LOW_SPEED_POSITION_MOTION_THRESHOLD + && snapshotMotion.rotationRadians() < LOW_SPEED_ROTATION_MOTION_THRESHOLD; PhysicsSyncPolicy.SyncDecision decision = PhysicsSyncPolicy.resolveSyncDecision(syncState, settings, scratch.visualPosition, @@ -422,11 +427,9 @@ private PhysicsVisualSyncSettings visualSyncSettings(@Nonnull UUID spaceUuid) { if (settingsBySpace.containsKey(spaceUuid)) { return settingsBySpace.get(spaceUuid); } - Ref spaceRef = physicsStore.getResource( - PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); + Ref spaceRef = physicsStore.getExternalData().getRefFromUUID(spaceUuid); PhysicsVisualSyncSettings settings = null; - if (spaceRef != null && spaceRef.isValid()) { + if (spaceRef != null && spaceRef.getStore() == physicsStore && spaceRef.isValid()) { VisualSyncSettingsComponent component = physicsStore.getComponent(spaceRef, VisualSyncSettingsComponent.getComponentType()); settings = new PhysicsVisualSyncSettings(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java index 62ca06c9..9570b584 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java @@ -9,15 +9,17 @@ import javax.annotation.Nonnull; /** - * Runtime-only debug toggles owned by PhysicsStore. + * Runtime-only physics debug toggles owned by PhysicsStore. */ @Setter @Getter public final class PhysicsDebugResource implements Resource { - private boolean debugBodiesEnabled; + private boolean debugShapesEnabled = true; + private boolean debugMotionEnabled = true; private boolean debugContactsEnabled; - private boolean debugJointsEnabled; + private boolean debugJointsEnabled = true; + private boolean debugPhysicsChunkCollisionEnabled; public PhysicsDebugResource() { } @@ -26,9 +28,11 @@ public PhysicsDebugResource() { @Override public PhysicsDebugResource clone() { PhysicsDebugResource copy = new PhysicsDebugResource(); - copy.debugBodiesEnabled = debugBodiesEnabled; + copy.debugShapesEnabled = debugShapesEnabled; + copy.debugMotionEnabled = debugMotionEnabled; copy.debugContactsEnabled = debugContactsEnabled; copy.debugJointsEnabled = debugJointsEnabled; + copy.debugPhysicsChunkCollisionEnabled = debugPhysicsChunkCollisionEnabled; return copy; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java index 20e293b5..79df8038 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java @@ -45,6 +45,8 @@ public static final class BodySyncState { private final Quaternionf lastSyncedRotation = new Quaternionf(); @Nonnull private final Vector3f lastObservedSnapshotPosition = new Vector3f(); + @Nonnull + private final Quaternionf lastObservedSnapshotRotation = new Quaternionf(); @Getter private boolean initialized; @Getter @@ -88,6 +90,7 @@ public void clear() { lastSyncedPosition.zero(); lastSyncedRotation.identity(); lastObservedSnapshotPosition.zero(); + lastObservedSnapshotRotation.identity(); } public float recordSnapshotObservation(@Nonnull Vector3f position) { @@ -101,6 +104,23 @@ public float recordSnapshotObservation(@Nonnull Vector3f position) { return distance; } + @Nonnull + public SnapshotMotion recordSnapshotObservation(@Nonnull Vector3f position, + @Nonnull Quaternionf rotation) { + if (!snapshotObserved) { + lastObservedSnapshotPosition.set(position); + lastObservedSnapshotRotation.set(rotation); + snapshotObserved = true; + return SnapshotMotion.unobserved(); + } + float distance = lastObservedSnapshotPosition.distance(position); + float rotationRadians = rotationDistanceRadians(lastObservedSnapshotRotation, + rotation); + lastObservedSnapshotPosition.set(position); + lastObservedSnapshotRotation.set(rotation); + return new SnapshotMotion(distance, rotationRadians); + } + public void recordSkip(float dt) { secondsSinceSync += Math.max(dt, 0.0f); } @@ -120,5 +140,28 @@ public Vector3f getLastObservedSnapshotPosition() { return lastObservedSnapshotPosition; } + private static float rotationDistanceRadians(@Nonnull Quaternionf first, + @Nonnull Quaternionf second) { + float dot = Math.abs(first.x * second.x + + first.y * second.y + + first.z * second.z + + first.w * second.w); + return 2.0f * (float) Math.acos(Math.min(dot, 1.0f)); + } + + public record SnapshotMotion(float positionDistance, + float rotationRadians) { + + @Nonnull + private static SnapshotMotion unobserved() { + return new SnapshotMotion(Float.NaN, Float.NaN); + } + + public boolean observed() { + return !Float.isNaN(positionDistance) + && !Float.isNaN(rotationRadians); + } + } + } } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index 4e4cbd1a..c303e33b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -24,7 +24,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsDebugOverlayResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; @@ -98,24 +99,28 @@ public Set> getDependencies() { @Override public void tick(float dt, int index, @Nonnull Store store) { World world = store.getExternalData().getWorld(); - assert PhysicsDebugResource.getResourceType() != null; - PhysicsDebugResource debug = store.getResource(PhysicsDebugResource.getResourceType()); + assert PhysicsDebugOverlayResource.getResourceType() != null; + PhysicsDebugOverlayResource overlay = + store.getResource(PhysicsDebugOverlayResource.getResourceType()); - if (!debug.hasSubscribers()) { + if (!overlay.hasSubscribers()) { return; } - List viewers = resolveSubscribers(world, debug); + List viewers = resolveSubscribers(world, overlay); if (viewers.isEmpty()) { return; } - boolean overlayDue = debug.tickOverlayBudget(dt); - boolean terrainDue = debug.tickPhysicsChunkBudget(dt); + boolean overlayDue = overlay.tickOverlayBudget(dt); + boolean terrainDue = overlay.tickPhysicsChunkBudget(dt); if (!overlayDue && !terrainDue) { return; } + Store physicsStore = PhysicsThreading.store(world); + PhysicsDebugResource debug = + physicsStore.getResource(PhysicsDebugResource.getResourceType()); boolean debugShapes = debug.isDebugShapesEnabled(); boolean debugMotion = debug.isDebugMotionEnabled(); boolean debugContacts = debug.isDebugContactsEnabled(); @@ -126,11 +131,10 @@ public void tick(float dt, int index, @Nonnull Store store) { return; } - Store physicsStore = PhysicsThreading.store(world); float overlayLifetime = PhysicsDebugRenderer.lifetimeForRefresh( - debug.getOverlayRefreshSeconds(), dt); + overlay.getOverlayRefreshSeconds(), dt); float terrainLifetime = PhysicsDebugRenderer.lifetimeForRefresh( - debug.getPhysicsChunkRefreshSeconds(), dt); + overlay.getPhysicsChunkRefreshSeconds(), dt); DebugQueryCache queryCache = queryCacheFor(store); for (PlayerRef viewer : viewers) { @@ -143,19 +147,19 @@ public void tick(float dt, int index, @Nonnull Store store) { store, physicsStore, viewerPosition, - debug.getViewRadius(), + overlay.getViewRadius(), debugShapes, debugMotion, - debug.getMaxBodies(), + overlay.getMaxBodies(), overlayLifetime); renderDetachedBodies(target, store, physicsStore, viewerPosition, - debug.getViewRadius(), + overlay.getViewRadius(), debugShapes, debugMotion, - Math.max(0, debug.getMaxBodies() - renderedBodies), + Math.max(0, overlay.getMaxBodies() - renderedBodies), overlayLifetime); } @@ -170,8 +174,8 @@ public void tick(float dt, int index, @Nonnull Store store) { viewerUuid, queryCache, viewerPosition, - debug.getViewRadius(), - debug.getMaxContacts(), + overlay.getViewRadius(), + overlay.getMaxContacts(), overlayLifetime); } if (overlayDue && debugJoints) { @@ -181,8 +185,8 @@ public void tick(float dt, int index, @Nonnull Store store) { viewerUuid, queryCache, viewerPosition, - debug.getViewRadius(), - debug.getMaxJoints(), + overlay.getViewRadius(), + overlay.getMaxJoints(), overlayLifetime); } if (terrainDue && debugCollision) { @@ -192,9 +196,9 @@ public void tick(float dt, int index, @Nonnull Store store) { viewerUuid, queryCache, viewerPosition, - debug.getViewRadius(), - debug.getMaxPhysicsChunkSections(), - debug.getMaxPhysicsChunkBoxes(), + overlay.getViewRadius(), + overlay.getMaxPhysicsChunkSections(), + overlay.getMaxPhysicsChunkBoxes(), terrainLifetime); } } @@ -210,8 +214,8 @@ private DebugQueryCache queryCacheFor(@Nonnull Store store) { @Nonnull private static List resolveSubscribers(@Nonnull World world, - @Nonnull PhysicsDebugResource debug) { - Set active = new ObjectOpenHashSet<>(debug.getSubscriberUuids()); + @Nonnull PhysicsDebugOverlayResource overlay) { + Set active = new ObjectOpenHashSet<>(overlay.getSubscriberUuids()); List viewers = new ArrayList<>(); for (PlayerRef player : world.getPlayerRefs()) { if (active.remove(player.getUuid())) { @@ -220,7 +224,7 @@ private static List resolveSubscribers(@Nonnull World world, } for (UUID stale : active) { - debug.removeSubscriber(stale); + overlay.removeSubscriber(stale); } return viewers; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java index 0ed1435f..cbf2e0ea 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueries.java @@ -11,11 +11,10 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.SectionCollisionGeometry.BoxCollider; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; @@ -182,8 +181,7 @@ private static List joints(@Nonnull Store s if (spaceUuid == null) { return List.of(); } - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); + Ref spaceRef = store.getExternalData().getRefFromUUID(spaceUuid); PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); double maxDistanceSquared = viewRadius * viewRadius; @@ -223,8 +221,7 @@ private static List physicsChunkSections( if (spaceUuid == null) { return List.of(); } - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); + Ref spaceRef = store.getExternalData().getRefFromUUID(spaceUuid); PhysicsChunkCollisionPayloadResource payloads = store.getResource( PhysicsChunkCollisionPayloadResource.getResourceType()); @@ -494,9 +491,8 @@ private static SpaceContext space(@Nonnull Store store, if (spaceUuid == null) { return null; } - Ref spaceRef = store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(spaceUuid); - if (spaceRef == null || !spaceRef.isValid()) { + Ref spaceRef = store.getExternalData().getRefFromUUID(spaceUuid); + if (spaceRef == null || spaceRef.getStore() != store || !spaceRef.isValid()) { return null; } PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java index 3fd061b5..c6b5bf1f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java @@ -182,6 +182,44 @@ void policyBackedSyncTreatsRetargetedBodyAsInitial() { assertEquals(10.01, transform.getPosition().x, 0.0001); } + @Test + void rotatingNearDynamicBodiesDoNotUseLowSpeedDeadzone() { + UUID bodyUuid = UUID.randomUUID(); + UUID spaceUuid = UUID.randomUUID(); + TransformComponent transform = new TransformComponent(); + BodyAttachmentComponent attachment = new BodyAttachmentComponent(bodyUuid, + TransformAuthority.BODY, + AttachmentLifecycle.EXTERNAL_ENTITY); + PhysicsSyncSystem.Scratch scratch = new PhysicsSyncSystem.Scratch(); + PhysicsBodyRuntimeState.BodySyncState syncState = + new PhysicsBodyRuntimeState.BodySyncState(); + + PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot(bodyUuid, spaceUuid, 10.0f, new Quaternionf(), false), + scratch, + syncState, + new PhysicsVisualSyncSettings(), + PhysicsSyncPolicy.SyncRangeTier.NEAR, + 0.05f); + + PhysicsSyncSystem.SyncResult rotated = PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot(bodyUuid, + spaceUuid, + 10.0f, + new Quaternionf().rotateY((float) Math.toRadians(4.0)), + false), + scratch, + syncState, + new PhysicsVisualSyncSettings(), + PhysicsSyncPolicy.SyncRangeTier.NEAR, + 0.05f); + + assertEquals(PhysicsSyncPolicy.SyncDecision.THRESHOLD, rotated.decision()); + assertTrue(rotated.transformChanged()); + } + @Test void visualPositionKeepsCenterOfMassOffsetWorldUp() { Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, @@ -261,16 +299,24 @@ private static PhysicsBodySnapshot snapshot(@Nonnull UUID bodyUuid, @Nonnull UUID spaceUuid, float positionX, boolean sleeping) { + return snapshot(bodyUuid, spaceUuid, positionX, new Quaternionf(), sleeping); + } + + private static PhysicsBodySnapshot snapshot(@Nonnull UUID bodyUuid, + @Nonnull UUID spaceUuid, + float positionX, + @Nonnull Quaternionf rotation, + boolean sleeping) { return PhysicsBodySnapshot.of(bodyUuid, spaceUuid, PhysicsBodyType.DYNAMIC, positionX, 2.0f, 3.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f, + rotation.x, + rotation.y, + rotation.z, + rotation.w, 0.0f, 0.0f, 0.0f, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java index 122936e5..0131a321 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java @@ -16,7 +16,6 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; @@ -77,8 +76,7 @@ private static void bindSpace(@Nonnull Store store, spaceUuid, new SpaceComponent(backendId, new Vector3f(0.0f, -9.81f, 0.0f))), AddReason.SPAWN); - store.getResource(PhysicsIdentityIndexResource.getResourceType()) - .putUuid(spaceUuid, spaceRef); + store.getExternalData().putRefForUUID(spaceUuid, spaceRef); store.getResource(PhysicsSpaceCompatibilityIndexResource.getResourceType()) .putSpace(new SpaceId(77), spaceUuid); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); From 36ed2c022fb203e281fa0b038327a8a1855113d2 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:39:18 +0200 Subject: [PATCH 519/534] feat(physicschunk): prewarm collision rows before restored body binding Signed-off-by: Blovien --- ...tachedStreamingBenchmarkCrucibleTests.java | 2 +- ...hysicsChunkCollisionStreamingResource.java | 2 +- .../PhysicsChunkMutationCache.java | 6 +- .../physicschunk/PhysicsChunkTypes.java | 5 +- ...nkCollisionRestoreDependencyComponent.java | 102 ++++++++++ .../ChunkCollisionRestorePrewarmSystem.java | 39 ++++ .../PhysicsStoreHolderPersistence.java | 94 ++++++++- .../PhysicsComponentTypeRegistry.java | 14 ++ .../systems/PhysicsStoreQueuedReadSystem.java | 2 + .../systems/binding/BodyBindingSystem.java | 49 +++-- .../PhysicsStoreEventPublicationSystem.java | 2 +- .../physicschunk/PhysicsChunkCollision.java | 10 +- .../plugin/physics/PhysicsDiagnostics.java | 8 - .../PhysicsStoreHolderPersistenceTest.java | 142 ++++--------- .../systems/BodyBindingSystemTest.java | 189 ++++++++++++++++++ 15 files changed, 530 insertions(+), 136 deletions(-) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionRestorePrewarmSystem.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java index 791bb53d..d8c459ee 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java index 83152c73..58bbe182 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkCollisionStreamingResource.java @@ -9,7 +9,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkMutationCache.TargetRefreshDecision; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionBuildStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionStats; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java index 7f7819ad..7e51e94d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkMutationCache.java @@ -11,7 +11,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.MissingSectionReason; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.StreamingTargetDiagnostic; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2LongMap; @@ -733,8 +733,8 @@ private static void pruneExpiredMissingBackoffs(@Nonnull SpaceCollisionCache cac private static int sleepingBodyStreamingInterval(int ttlTicks) { int ttlBound = Math.max(1, ttlTicks / 4); - return Math.max(ACTIVE_BODY_STREAMING_INTERVAL_TICKS, - Math.min(SLEEPING_BODY_STREAMING_INTERVAL_TICKS, ttlBound)); + return Math.clamp(ttlBound, ACTIVE_BODY_STREAMING_INTERVAL_TICKS, + SLEEPING_BODY_STREAMING_INTERVAL_TICKS); } @Nullable diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java index c0ec1939..2af3c0b2 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkTypes.java @@ -2,9 +2,8 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.PhysicsChunkCollisionProducerSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.ChunkCollisionProducerSystem; import javax.annotation.Nonnull; /** @@ -27,7 +26,7 @@ public static void registerEntityStoreResourceTypes( public static void registerEntityStoreSystems( @Nonnull ComponentRegistryProxy registry) { - registry.registerSystem(new PhysicsChunkCollisionProducerSystem()); + registry.registerSystem(new ChunkCollisionProducerSystem()); } public static void clearEntityStoreResourceTypes() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java new file mode 100644 index 00000000..af56e471 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java @@ -0,0 +1,102 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3fUtil; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import lombok.Getter; +import org.joml.Vector3f; + +/** + * Utility component to recover physicschunk bodies when loading bodies on top + */ +public final class ChunkCollisionRestoreDependencyComponent implements Component { + + @Nonnull + public static final BuilderCodec CODEC = + BuilderCodec.builder(ChunkCollisionRestoreDependencyComponent.class, + ChunkCollisionRestoreDependencyComponent::new) + .append(new KeyedCodec<>("SpaceUuid", Codec.UUID_BINARY, false), + (component, value) -> component.spaceUuid = value != null + ? value + : new UUID(0L, 0L), + ChunkCollisionRestoreDependencyComponent::getSpaceUuid) + .add() + .append(new KeyedCodec<>("Center", Vector3fUtil.CODEC, false), + (component, value) -> component.center.set(value != null ? value : new Vector3f()), + ChunkCollisionRestoreDependencyComponent::getCenter) + .add() + .append(new KeyedCodec<>("Radius", Codec.INTEGER, false), + (component, value) -> component.radius = Math.max(0, value != null ? value : 0), + ChunkCollisionRestoreDependencyComponent::getRadius) + .add() + .append(new KeyedCodec<>("ModeAtSave", + new EnumCodec<>(PhysicsChunkCollisionMode.class), + false), + (component, value) -> component.modeAtSave = value != null + ? value + : PhysicsChunkCollisionMode.NONE, + ChunkCollisionRestoreDependencyComponent::getModeAtSave) + .add() + .build(); + + @Nonnull + private UUID spaceUuid = new UUID(0L, 0L); + @Nonnull + private final Vector3f center = new Vector3f(); + @Getter + private int radius; + @Nonnull + private PhysicsChunkCollisionMode modeAtSave = PhysicsChunkCollisionMode.NONE; + + public ChunkCollisionRestoreDependencyComponent() { + } + + public ChunkCollisionRestoreDependencyComponent(@Nonnull UUID spaceUuid, + @Nonnull Vector3f center, + int radius, + @Nonnull PhysicsChunkCollisionMode modeAtSave) { + this.spaceUuid = Objects.requireNonNull(spaceUuid, "spaceUuid"); + this.center.set(Objects.requireNonNull(center, "center")); + this.radius = Math.max(0, radius); + this.modeAtSave = Objects.requireNonNull(modeAtSave, "modeAtSave"); + } + + @Nonnull + public UUID getSpaceUuid() { + return spaceUuid; + } + + @Nonnull + public Vector3f getCenter() { + return new Vector3f(center); + } + + @Nonnull + public PhysicsChunkCollisionMode getModeAtSave() { + return modeAtSave; + } + + @Nonnull + public static ComponentType getComponentType() { + return PhysicsComponentTypeRegistry.chunkCollisionRestoreDependencyComponentType(); + } + + @Nonnull + @Override + public ChunkCollisionRestoreDependencyComponent clone() { + return new ChunkCollisionRestoreDependencyComponent(spaceUuid, + center, + radius, + modeAtSave); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionRestorePrewarmSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionRestorePrewarmSystem.java new file mode 100644 index 00000000..b530a0f3 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionRestorePrewarmSystem.java @@ -0,0 +1,39 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * Requests PhysicsChunk collision support for unbound restored bodies before body binding. + */ +public final class ChunkCollisionRestorePrewarmSystem extends TickingSystem { + + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, SpaceBindingSystem.class), + new SystemDependency<>(Order.AFTER, SpaceSettingsApplicationSystem.class), + new SystemDependency<>(Order.AFTER, PhysicsChunkSettingsIndexSystem.class), + new SystemDependency<>(Order.BEFORE, ChunkCollisionMutationDrainSystem.class), + new SystemDependency<>(Order.BEFORE, BodyBindingSystem.class) + ); + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + PhysicsChunkStoreTypes.prewarmRestoreDependencies(store); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java index 58ee4cad..1959f4fd 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/persistence/PhysicsStoreHolderPersistence.java @@ -6,20 +6,26 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.BsonUtil; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionRestoreDependencyComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; import dev.hytalemodding.impulse.core.plugin.components.ColliderComponent; import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -33,6 +39,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.bson.BsonDocument; +import org.joml.Vector3f; /** * ChunkStore-shaped holder serialization boundary for future PhysicsStore row storage. @@ -107,6 +114,12 @@ private static final class Capture { private final List joints = new ArrayList<>(); @Nonnull private final ObjectOpenHashSet persistentBodyUuids = new ObjectOpenHashSet<>(); + @Nonnull + private final Map> generatedSourcesBySpace = + new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map chunkSettingsBySpace = + new Object2ObjectOpenHashMap<>(); private Capture(@Nonnull Store store, @Nonnull Map snapshotsByBodyUuid) { @@ -125,9 +138,21 @@ private void collectChunk(@Nonnull ArchetypeChunk chunk) { Ref ref = chunk.getReferenceTo(index); if (chunk.getComponent(index, SpaceComponent.getComponentType()) != null) { spaces.add(new Row(uuid, ref)); + ChunkCollisionSettingsComponent settings = chunk.getComponent(index, + ChunkCollisionSettingsComponent.getComponentType()); + if (settings != null) { + chunkSettingsBySpace.put(uuid, settings); + } } BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); + ChunkCollisionSourceComponent source = chunk.getComponent(index, + ChunkCollisionSourceComponent.getComponentType()); + if (body != null && source != null) { + generatedSourcesBySpace + .computeIfAbsent(body.getSpaceUuid(), _ -> new ArrayList<>()) + .add(source); + } if (body != null && shouldPersistBody(chunk, index, body)) { bodies.add(new Row(uuid, ref)); persistentBodyUuids.add(uuid); @@ -158,6 +183,7 @@ private List> toHolders() { Holder holder = store.copySerializableEntity(row.ref()); sanitize(holder); patchBodyTarget(row.uuid(), holder); + attachChunkCollisionRestoreDependency(holder); holders.add(holder); } return List.copyOf(holders); @@ -194,10 +220,76 @@ private void patchBodyTarget(@Nonnull UUID rowUuid, holder.putComponent(TargetComponent.getComponentType(), target); } + private void attachChunkCollisionRestoreDependency( + @Nonnull Holder holder) { + BodyComponent body = holder.getComponent(BodyComponent.getComponentType()); + if (body == null) { + return; + } + DynamicsComponent dynamics = holder.getComponent(DynamicsComponent.getComponentType()); + if (dynamics == null || dynamics.getBodyType() != PhysicsBodyType.DYNAMIC) { + return; + } + ChunkCollisionSettingsComponent settings = chunkSettingsBySpace.get(body.getSpaceUuid()); + if (settings == null || settings.getMode() == PhysicsChunkCollisionMode.NONE) { + return; + } + TargetComponent target = holder.getComponent(TargetComponent.getComponentType()); + if (target == null) { + return; + } + int radius = Math.max(0, settings.getBodyRadius()); + if (!intersectsGeneratedChunkCollision(body.getSpaceUuid(), + target.getPosition(), + radius)) { + return; + } + holder.putComponent(ChunkCollisionRestoreDependencyComponent.getComponentType(), + new ChunkCollisionRestoreDependencyComponent(body.getSpaceUuid(), + target.getPosition(), + radius, + settings.getMode())); + } + + private boolean intersectsGeneratedChunkCollision(@Nonnull UUID spaceUuid, + @Nonnull Vector3f center, + int radius) { + List sources = generatedSourcesBySpace.get(spaceUuid); + if (sources == null || sources.isEmpty()) { + return false; + } + for (ChunkCollisionSourceComponent source : sources) { + if (intersectsSource(source, center, radius)) { + return true; + } + } + return false; + } + + private static boolean intersectsSource(@Nonnull ChunkCollisionSourceComponent source, + @Nonnull Vector3f center, + int radius) { + int minX = (int) Math.floor(center.x) - radius; + int maxX = (int) Math.floor(center.x) + radius; + int minY = Math.max(0, (int) Math.floor(center.y) - radius); + int maxY = Math.min(ChunkUtil.HEIGHT_MINUS_1, + (int) Math.floor(center.y) + radius); + int minZ = (int) Math.floor(center.z) - radius; + int maxZ = (int) Math.floor(center.z) + radius; + + return source.getChunkX() >= ChunkUtil.chunkCoordinate(minX) + && source.getChunkX() <= ChunkUtil.chunkCoordinate(maxX) + && source.getSectionY() >= ChunkUtil.indexSection(minY) + && source.getSectionY() <= ChunkUtil.indexSection(maxY) + && source.getChunkZ() >= ChunkUtil.chunkCoordinate(minZ) + && source.getChunkZ() <= ChunkUtil.chunkCoordinate(maxZ); + } + private static boolean shouldPersistBody(@Nonnull ArchetypeChunk chunk, int index, @Nonnull BodyComponent body) { - return chunk.getComponent(index, ColliderComponent.getComponentType()) != null + return chunk.getComponent(index, ChunkCollisionSourceComponent.getComponentType()) == null + && chunk.getComponent(index, ColliderComponent.getComponentType()) != null && chunk.getComponent(index, ShapeComponent.getComponentType()) != null && chunk.getComponent(index, MaterialComponent.getComponentType()) != null && chunk.getComponent(index, CollisionFilterComponent.getComponentType()) != null; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java index b9120d12..0dc7b401 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java @@ -18,6 +18,7 @@ import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionRestoreDependencyComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; @@ -40,6 +41,9 @@ public final class PhysicsComponentTypeRegistry { @Nullable private static ComponentType chunkCollisionSourceComponentType; @Nullable + private static ComponentType + chunkCollisionRestoreDependencyComponentType; + @Nullable private static ComponentType chunkCollisionSettingsComponentType; @Nullable private static ComponentType dynamicsComponentType; @@ -92,6 +96,10 @@ public static void registerComponentTypes( ChunkCollisionSourceComponent.class, "ChunkCollisionSource", ChunkCollisionSourceComponent.CODEC); + chunkCollisionRestoreDependencyComponentType = registry.registerComponent( + ChunkCollisionRestoreDependencyComponent.class, + "ChunkCollisionRestoreDependency", + ChunkCollisionRestoreDependencyComponent.CODEC); chunkCollisionSettingsComponentType = registry.registerComponent( ChunkCollisionSettingsComponent.class, "ChunkCollisionSettings", @@ -172,6 +180,12 @@ public static ComponentType bodyCommandCompo return chunkCollisionSourceComponentType; } + @Nonnull + public static ComponentType + chunkCollisionRestoreDependencyComponentType() { + return chunkCollisionRestoreDependencyComponentType; + } + @Nonnull public static ComponentType chunkCollisionSettingsComponentType() { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreQueuedReadSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreQueuedReadSystem.java index 9bd117be..457acee1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreQueuedReadSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/PhysicsStoreQueuedReadSystem.java @@ -8,6 +8,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsStoreReadQueueResource; +import dev.hytalemodding.impulse.core.internal.systems.publication.CompletedStepPublicationSystem; + import java.util.List; import java.util.Set; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java index a75c73fe..06315240 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java @@ -17,10 +17,9 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionPayloadResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; @@ -33,6 +32,7 @@ import dev.hytalemodding.impulse.core.plugin.components.MaterialComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -62,17 +62,21 @@ public void tick(float dt, int systemIndex, @Nonnull Store store) PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); PhysicsChunkCollisionPayloadResource chunkCollisionPayloads = PhysicsChunkStoreTypes.collisionPayloadsIfPresent(store); - PhysicsIdentityIndexResource identity = - store.getResource(PhysicsIdentityIndexResource.getResourceType()); BiConsumer, CommandBuffer> collector = - (chunk, _) -> bindBodies(runtime, chunkCollisionPayloads, identity, restore, chunk); + (chunk, commandBuffer) -> bindBodies(store, + runtime, + chunkCollisionPayloads, + restore, + commandBuffer, + chunk); store.forEachChunk(systemIndex, collector); } - private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, + private static void bindBodies(@Nonnull Store store, + @Nonnull PhysicsRuntimeResource runtime, @Nullable PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull CommandBuffer commandBuffer, @Nonnull ArchetypeChunk chunk) { for (int index = 0; index < chunk.size(); index++) { BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); @@ -88,9 +92,10 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, continue; } bindBody(runtime, + store, chunkCollisionPayloads, - identity, restore, + commandBuffer, bodyRef, bodyUuid, body, @@ -104,9 +109,10 @@ private static void bindBodies(@Nonnull PhysicsRuntimeResource runtime, } private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, + @Nonnull Store store, @Nullable PhysicsChunkCollisionPayloadResource chunkCollisionPayloads, - @Nonnull PhysicsIdentityIndexResource identity, @Nonnull PhysicsRestoreStatusResource restore, + @Nonnull CommandBuffer commandBuffer, @Nonnull Ref bodyRef, @Nonnull UUID bodyUuid, @Nonnull BodyComponent body, @@ -116,7 +122,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, @Nullable ShapeComponent shape, @Nullable MaterialComponent material, @Nullable CollisionFilterComponent filter) { - Ref spaceRef = resolveSpaceRef(identity, body); + Ref spaceRef = resolveSpaceRef(store, body); BackendSpaceHandle spaceHandle = spaceRef != null ? runtime.getSpaceHandle(spaceRef) : null; if (spaceHandle == null) { restore.recordSoftSkip("Body references unbound space: " + bodyUuid); @@ -136,6 +142,14 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, restore.recordSoftSkip("Body aggregate is missing collider data: " + bodyUuid); return; } + if (PhysicsChunkStoreTypes.shouldDeferChunkCollisionRestore(store, + commandBuffer, + bodyRef, + body, + dynamics, + restore)) { + return; + } DynamicsComponent bodyDynamics = dynamics != null ? dynamics : new DynamicsComponent(); TargetComponent initialTarget = target != null ? target : new TargetComponent(); Vector3f position = target != null ? initialTarget.getPosition() : new Vector3f(); @@ -291,11 +305,18 @@ private static void applyInitialTargetState(@Nonnull PhysicsBackendRuntime backe } @Nullable - private static Ref resolveSpaceRef(@Nonnull PhysicsIdentityIndexResource identity, + private static Ref resolveSpaceRef(@Nonnull Store store, @Nonnull BodyComponent body) { - Ref spaceRef = PhysicsStoreSystemSupport.resolvedRef(identity, - body.getSpaceUuid(), - body.getSpaceRef()); + Ref spaceRef = body.getSpaceRef(); + UuidComponent uuid = PhysicsStoreSystemSupport.component(store, + spaceRef, + UuidComponent.getComponentType()); + if (uuid == null || !body.getSpaceUuid().equals(uuid.getUuid())) { + spaceRef = store.getExternalData().getRefFromUUID(body.getSpaceUuid()); + } + if (spaceRef != null && (spaceRef.getStore() != store || !spaceRef.isValid())) { + spaceRef = null; + } body.setSpaceRef(spaceRef); return spaceRef; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java index 5bd2c683..db327592 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/publication/PhysicsStoreEventPublicationSystem.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource.StepSample; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; -import dev.hytalemodding.impulse.core.internal.systems.visual.PhysicsProjectionCleanupSystem; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFrame; import dev.hytalemodding.impulse.core.plugin.events.PhysicsEventFramePublishedEvent; import java.util.Collections; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java index c2e7b1a0..d1c798b0 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicschunk/PhysicsChunkCollision.java @@ -7,11 +7,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; @@ -265,7 +265,7 @@ private static int clearSpaceChunkCollisionRows(@Nonnull World world, removed = streaming(world).clearSpace(spaceUuid, stampedQueue(store)); } int directlyRemoved = - PhysicsTopologyMutations.clearChunkCollisionRowsForSpace(store, spaceUuid); + PhysicsChunkStoreTypes.clearChunkCollisionRowsForSpace(store, spaceUuid); return removed != 0 ? removed : directlyRemoved; } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java index ec9c81fa..91234586 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsDiagnostics.java @@ -159,14 +159,6 @@ public static CompletionStage> spaceSummariesAsync(@Nonnull W PhysicsDiagnostics::spaceSummaries); } - @Nonnull - public static CompletionStage> spaceSummariesAsync( - @Nonnull Store store) { - return PhysicsThreading.enqueueReadOnWorldThread(store, - "queue PhysicsStore space summaries read", - PhysicsDiagnostics::spaceSummaries); - } - @Nonnull public static List spaceSummaries(@Nonnull Store store, @Nonnull Ref spaceRef) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java index 6a1480ff..d097be73 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java @@ -28,12 +28,12 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionRestoreDependencyComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsIdentityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -54,6 +54,8 @@ import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsSnapshotFrame; @@ -68,11 +70,8 @@ import javax.annotation.Nonnull; import org.bson.BsonArray; import org.bson.BsonBinary; -import org.bson.BsonBinarySubType; import org.bson.BsonDocument; -import org.bson.BsonDouble; import org.bson.BsonInt32; -import org.bson.BsonString; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.jupiter.api.Test; @@ -85,7 +84,6 @@ class PhysicsStoreHolderPersistenceTest { private static final UUID BODY_B_UUID = uuid(3); private static final UUID GENERATED_BODY_UUID = uuid(4); private static final UUID JOINT_UUID = uuid(5); - private static final UUID LEGACY_SPACE_UUID = uuid(6); private static final BackendId HOLDER_BACKEND_ID = new BackendId("test:holder-persistence"); @TempDir @@ -133,9 +131,7 @@ void holderBlobsPersistUuidBodyRowsWithSnapshotTargetsOnly() { assertNotNull(bodyA); assertNotNull(holder(decoded, BODY_B_UUID)); assertNotNull(holder(decoded, JOINT_UUID)); - Holder generatedBody = holder(decoded, GENERATED_BODY_UUID); - assertNotNull(generatedBody); - assertNull(generatedBody.getComponent(ChunkCollisionSourceComponent.getComponentType())); + assertNull(holder(decoded, GENERATED_BODY_UUID)); assertNull(bodyA.getComponent(BodyCommandComponent.getComponentType())); BodyComponent body = bodyA.getComponent(BodyComponent.getComponentType()); @@ -154,35 +150,48 @@ void holderBlobsPersistUuidBodyRowsWithSnapshotTargetsOnly() { } @Test - void holderHydrationIgnoresLegacyDtoFileWhenHolderStorageExists() { - StoreFixture source = store("holder-save-source", tempDir.resolve("save")); + void holderCaptureAddsChunkCollisionRestoreDependencyToDynamicBodiesNearGeneratedRows() { + StoreFixture fixture = store("holder-capture-restore-context", + tempDir.resolve("capture-restore-context")); try { - Ref spaceRef = addSpace(source.store(), SPACE_UUID); - addBody(source.store(), - BODY_A_UUID, + Ref spaceRef = addSpace(fixture.store(), SPACE_UUID); + fixture.store().putComponent(spaceRef, + ChunkCollisionSettingsComponent.getComponentType(), + new ChunkCollisionSettingsComponent(PhysicsChunkCollisionMode.STREAMING, + false, + 8, + 4, + 100)); + addBody(fixture.store(), BODY_A_UUID, spaceRef, null); + addBody(fixture.store(), + GENERATED_BODY_UUID, spaceRef, - null); - PhysicsStoreHolderStorage.save(source.store()).join(); - } finally { - source.close(); - } - - StoreFixture target = store("holder-save-target", tempDir.resolve("save")); - try { - writeLegacyDtoFile(target.store()); + new ChunkCollisionSourceComponent("chunk:0:0:0", + 0, + 0, + 0, + "chunk-collision/0/0/0", + PartKind.BOX, + 0)); - new PersistenceHydrationSystem().tick(0.0f, 0, target.store()); + List> decoded = PhysicsStoreHolderPersistence + .capturePersistentHolderBlobs(fixture.store()) + .stream() + .map(blob -> PhysicsStoreHolderPersistence.decodeHolder( + fixture.store().getRegistry(), + blob)) + .toList(); - PhysicsRestoreStatusResource restore = target.store().getResource( - PhysicsRestoreStatusResource.getResourceType()); - assertTrue(restore.isHydrated()); - assertFalse(restore.isFailed()); - List rowUuids = rowUuids(target.store()); - assertTrue(rowUuids.contains(SPACE_UUID)); - assertTrue(rowUuids.contains(BODY_A_UUID)); - assertFalse(rowUuids.contains(LEGACY_SPACE_UUID)); + assertNull(holder(decoded, GENERATED_BODY_UUID)); + ChunkCollisionRestoreDependencyComponent dependency = holder(decoded, BODY_A_UUID) + .getComponent(ChunkCollisionRestoreDependencyComponent.getComponentType()); + assertNotNull(dependency); + assertEquals(SPACE_UUID, dependency.getSpaceUuid()); + assertEquals(new Vector3f(1.0f, 2.0f, 3.0f), dependency.getCenter()); + assertEquals(4, dependency.getRadius()); + assertEquals(PhysicsChunkCollisionMode.STREAMING, dependency.getModeAtSave()); } finally { - target.close(); + fixture.close(); } } @@ -223,26 +232,6 @@ void holderSaveCreatesWorldOwnedPhysicsStoreDirectory() { } } - @Test - void hydrationIgnoresLegacyDtoWhenHolderStorageIsMissing() { - StoreFixture fixture = store("legacy-fallback", tempDir.resolve("legacy")); - try { - writeLegacyDtoFile(fixture.store()); - - new PersistenceHydrationSystem().tick(0.0f, 0, fixture.store()); - - PhysicsRestoreStatusResource restore = fixture.store().getResource( - PhysicsRestoreStatusResource.getResourceType()); - assertTrue(restore.isHydrated()); - assertFalse(restore.isFailed()); - List rowUuids = rowUuids(fixture.store()); - assertFalse(rowUuids.contains(LEGACY_SPACE_UUID)); - assertFalse(rowUuids.contains(SPACE_UUID)); - } finally { - fixture.close(); - } - } - @Test void holderHydrationRejectsDuplicateUuidWithoutAddingPartialRows() { StoreFixture fixture = store("holder-duplicate-uuid", @@ -335,9 +324,8 @@ void registeredStoreReloadBindsSavedBodiesOnce() { assertTrue(rowUuids.contains(BODY_A_UUID)); assertTrue(rowUuids.contains(BODY_B_UUID)); - Ref spaceRef = target.store() - .getResource(PhysicsIdentityIndexResource.getResourceType()) - .getByUuid(SPACE_UUID); + Ref spaceRef = target.store().getExternalData().getRefFromUUID( + SPACE_UUID); assertNotNull(spaceRef); PhysicsRuntimeResource runtime = target.store().getResource( PhysicsRuntimeResource.getResourceType()); @@ -534,50 +522,6 @@ private static void publishSnapshot(@Nonnull Store store, true)))); } - private static void writeLegacyDtoFile(@Nonnull Store store) { - Path file = store.getExternalData() - .getWorld() - .getSavePath() - .resolve("resources") - .resolve("PersistentPhysicsStore.json"); - BsonDocument space = new BsonDocument() - .append("SpaceUuid", uuidBinary(LEGACY_SPACE_UUID)) - .append("BackendId", new BsonString("test:legacy-only-fallback")) - .append("Gravity", vector(0.0f, -9.81f, 0.0f)); - BsonDocument document = new BsonDocument() - .append("SchemaVersion", new BsonInt32(2)) - .append("Spaces", new BsonArray(List.of(space))); - try { - Files.createDirectories(file.getParent()); - Files.write(file, BsonUtil.writeToBytes(document)); - } catch (IOException exception) { - throw new AssertionError("Failed to write legacy DTO fixture", exception); - } - } - - @Nonnull - private static BsonBinary uuidBinary(@Nonnull UUID uuid) { - byte[] bytes = new byte[16]; - writeLongBigEndian(bytes, 0, uuid.getMostSignificantBits()); - writeLongBigEndian(bytes, 8, uuid.getLeastSignificantBits()); - return new BsonBinary(BsonBinarySubType.UUID_STANDARD, bytes); - } - - private static void writeLongBigEndian(@Nonnull byte[] bytes, int offset, long value) { - for (int index = 7; index >= 0; index--) { - bytes[offset + index] = (byte) value; - value >>>= 8; - } - } - - @Nonnull - private static BsonDocument vector(float x, float y, float z) { - return new BsonDocument() - .append("X", new BsonDouble(x)) - .append("Y", new BsonDouble(y)) - .append("Z", new BsonDouble(z)); - } - private static void writeHolderStorage(@Nonnull Store store, @Nonnull List> holders) { writeHolderStorageFile(PhysicsStoreHolderStorage.file(store.getExternalData()), diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java index 3b28d82e..ded89df3 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; @@ -20,10 +21,17 @@ import dev.hytalemodding.impulse.api.ShapeType; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionRestoreDependencyComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.ChunkCollisionMutationDrainSystem; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; +import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource.PhysicsChunkSpaceSettings; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; @@ -38,8 +46,11 @@ import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.EntityChunkBoundaryMode; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import java.util.ArrayList; +import java.util.Map; import java.util.UUID; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -96,6 +107,140 @@ void nonVoxelBodyBindingDoesNotRequirePhysicsChunkPayloadResource() { } } + @Test + void chunkRestoreDependencyWaitsWithoutBlockingUnrelatedBodies() { + PhysicsChunkLifecycle.enable(); + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider(BACKEND_ID, false, false); + Impulse.registerRuntimeProvider(provider); + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + proxy.registerSystem(new BodyBindingSystem()); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("body-binding-restore-context-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(1); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + Ref spaceRef = addSpace(store, spaceUuid); + store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()) + .replaceAll(Map.of(spaceUuid, + new PhysicsChunkSpaceSettings(spaceUuid, + PhysicsChunkCollisionMode.STREAMING, + EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED, + false, + 8, + 4, + 100))); + Ref waitingBodyRef = addBody(store, uuid(2), spaceUuid, spaceRef); + store.putComponent(waitingBodyRef, + ChunkCollisionRestoreDependencyComponent.getComponentType(), + new ChunkCollisionRestoreDependencyComponent(spaceUuid, + new Vector3f(1.0f, 2.0f, 3.0f), + 4, + PhysicsChunkCollisionMode.STREAMING)); + Ref ordinaryBodyRef = addBody(store, uuid(3), spaceUuid, spaceRef); + + store.tick(0.0f); + + assertFalse(restore.isFailed(), restore.getFailureMessage()); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + assertNotNull(runtime.getBodyHandle(ordinaryBodyRef)); + assertNull(runtime.getBodyHandle(waitingBodyRef)); + assertEquals(1, + restore.getSoftSkipsByReason() + .getInt("Body restore dependency pending chunk collision")); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceRef); + assertNotNull(spaceHandle); + assertEquals(1, provider.createdRuntimes().get(0).bodyCount(spaceHandle.value())); + } finally { + if (!store.isShutdown()) { + registry.removeStore(store); + } + registry.shutdown(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + PhysicsChunkLifecycle.disable(); + } + } + + @Test + void chunkRestoreDependencyBindsWhenGeneratedSupportRowExists() { + PhysicsChunkLifecycle.enable(); + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider(BACKEND_ID, false, false); + Impulse.registerRuntimeProvider(provider); + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + PhysicsChunkStoreTypes.registerPhysicsStoreResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + proxy.registerSystem(new BodyBindingSystem()); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("body-binding-restore-support-test")), + EmptyResourceStorage.get()); + try { + UUID spaceUuid = uuid(1); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + Ref spaceRef = addSpace(store, spaceUuid); + store.getResource(PhysicsChunkSettingsIndexResource.getResourceType()) + .replaceAll(Map.of(spaceUuid, + new PhysicsChunkSpaceSettings(spaceUuid, + PhysicsChunkCollisionMode.STREAMING, + EntityChunkBoundaryMode.PAUSE_UNTIL_LOADED, + false, + 8, + 4, + 100))); + Ref waitingBodyRef = addBody(store, uuid(2), spaceUuid, spaceRef); + store.putComponent(waitingBodyRef, + ChunkCollisionRestoreDependencyComponent.getComponentType(), + new ChunkCollisionRestoreDependencyComponent(spaceUuid, + new Vector3f(1.0f, 2.0f, 3.0f), + 4, + PhysicsChunkCollisionMode.STREAMING)); + Ref generatedBodyRef = addGeneratedChunkBody(store, + spaceUuid, + spaceRef); + + store.tick(0.0f); + + assertFalse(restore.isFailed(), restore.getFailureMessage()); + assertEquals(0, restore.getSoftSkipsByReason().size()); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + assertNotNull(runtime.getBodyHandle(waitingBodyRef)); + assertNotNull(runtime.getBodyHandle(generatedBodyRef)); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceRef); + assertNotNull(spaceHandle); + assertEquals(2, provider.createdRuntimes().get(0).bodyCount(spaceHandle.value())); + } finally { + if (!store.isShutdown()) { + registry.removeStore(store); + } + registry.shutdown(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + PhysicsChunkLifecycle.disable(); + } + } + private static Ref addSpace(Store store, UUID spaceUuid) { Ref spaceRef = store.addEntity(PhysicsEntities.spaceHolder(store, spaceUuid, @@ -136,6 +281,50 @@ private static Ref addBody(Store store, return bodyRef; } + private static Ref addGeneratedChunkBody(Store store, + UUID spaceUuid, + Ref spaceRef) { + String sourceKey = "chunk:0:0:0"; + UUID bodyUuid = ChunkCollisionMutationDrainSystem.chunkCollisionBodyUuid(spaceUuid, + sourceKey, + PartKind.BOX, + 0); + BodyComponent body = new BodyComponent(spaceUuid); + body.setSpaceRef(spaceRef); + TargetComponent target = new TargetComponent(); + target.setActive(true); + Ref bodyRef = store.addEntity(PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.STATIC, 0.0f, 0.0f, 0.0f, false), + target, + new ColliderComponent(new Vector3f(), new Quaternionf(), false), + new ShapeComponent(ShapeType.BOX, + 8.0f, + 8.0f, + 8.0f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.6f, 0.1f), + new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.ALL)), + AddReason.SPAWN); + assertNotNull(bodyRef); + store.putComponent(bodyRef, + ChunkCollisionSourceComponent.getComponentType(), + new ChunkCollisionSourceComponent(sourceKey, + 0, + 0, + 0, + "", + PartKind.BOX, + 0)); + return bodyRef; + } + private static UUID uuid(long leastSignificantBits) { return new UUID(0L, leastSignificantBits); } From 73a8abbed50ed2759304922311212857641ea8b1 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:39:36 +0200 Subject: [PATCH 520/534] feat(examples): add directional pendulums demo Signed-off-by: Blovien --- impulse-examples/build.gradle.kts | 3 + .../commands/DirectionalPendulumsCommand.java | 701 ++++++++++++++++++ .../examples/commands/ImpulseCommand.java | 1 + .../commands/PhysicsStoreExampleCommands.java | 6 +- .../examples/utils/ExamplePhysicsUtils.java | 26 +- .../DirectionalPendulumsCommandTest.java | 586 +++++++++++++++ 6 files changed, 1318 insertions(+), 5 deletions(-) create mode 100644 impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java create mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java diff --git a/impulse-examples/build.gradle.kts b/impulse-examples/build.gradle.kts index c0134068..63c0b416 100644 --- a/impulse-examples/build.gradle.kts +++ b/impulse-examples/build.gradle.kts @@ -11,6 +11,9 @@ dependencies { compileOnly(project(":impulse-core")) compileOnly(project(":impulse-early-plugin")) testImplementation(project(":impulse-core")) + testImplementation(project(":impulse-early-plugin")) + testImplementation(testFixtures(project(":impulse-backend-api"))) + testImplementation(libs.objenesis) testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") compileOnly(libs.lombok) diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java new file mode 100644 index 00000000..3cd0a32e --- /dev/null +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java @@ -0,0 +1,701 @@ +package dev.hytalemodding.impulse.examples.commands; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.EventTitleUtil; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointType; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; +import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3d; +import org.joml.Vector3f; + +public final class DirectionalPendulumsCommand extends AbstractAsyncPlayerCommand { + + private static final float GRAVITY = 9.81f; + private static final float PIVOT_HALF_SIZE = 0.25f; + private static final float LINK_HALF_LENGTH = 1.0f; + private static final float LINK_THICKNESS = 0.18f; + private static final float UPPER_SWING_SPEED = 0.9f; + private static final float LOWER_RELATIVE_SWING_SPEED = -0.55f; + private static final float CINEMATIC_UPPER_SWING_SPEED = 2.1f; + private static final float CINEMATIC_LOWER_RELATIVE_SWING_SPEED = -1.2f; + static final String PENDULUM_BLOCK_TYPE = "Rock_Marble"; + static final String CINEMATIC_JOLT_BLOCK_TYPE = "Rock_Aqua"; + static final String CINEMATIC_RAPIER_BLOCK_TYPE = "Rock_Basalt"; + static final float PENDULUM_VISUAL_ORIGIN_OFFSET_Y = 0.5f; + static final float PENDULUM_SLEEP_LINEAR_THRESHOLD = 0.0f; + static final float PENDULUM_SLEEP_ANGULAR_THRESHOLD = 0.0f; + static final float PENDULUM_TIME_UNTIL_SLEEP = Float.MAX_VALUE; + private static final float CINEMATIC_GRAVITY_ARROW_DURATION_SECONDS = 12.0f; + private static final BackendId JOLT_BACKEND_ID = new BackendId("impulse:jolt"); + private static final BackendId RAPIER_BACKEND_ID = new BackendId("impulse:rapier"); + private static final RigidBodySpawnSettings PENDULUM_BODY_SETTINGS = + RigidBodySpawnSettings.material(0.6f, 0.05f) + .withCollisionFilter(PhysicsCollisionFilters.DYNAMIC_BODY, + PhysicsCollisionFilters.TERRAIN); + + private static final DirectionSpec[] DIRECTIONS = { + new DirectionSpec(new Vector3f(0.0f, -GRAVITY, 0.0f), + new Vector3d(-8.0, 0.0, -8.0), + new Vector3d(1.0, 0.0, 0.0), + BackendSlot.JOLT), + new DirectionSpec(new Vector3f(0.0f, GRAVITY, 0.0f), + new Vector3d(8.0, 0.0, -8.0), + new Vector3d(1.0, 0.0, 0.0), + BackendSlot.JOLT), + new DirectionSpec(new Vector3f(GRAVITY, 0.0f, 0.0f), + new Vector3d(-8.0, 0.0, 8.0), + new Vector3d(0.0, 0.0, 1.0), + BackendSlot.RAPIER), + new DirectionSpec(new Vector3f(-GRAVITY, 0.0f, 0.0f), + new Vector3d(8.0, 0.0, 8.0), + new Vector3d(0.0, 0.0, 1.0), + BackendSlot.RAPIER) + }; + + private static final DirectionSpec[] CINEMATIC_DIRECTIONS = { + new DirectionSpec(new Vector3f(0.0f, -GRAVITY, 0.0f), + new Vector3d(-4.0, 0.0, -4.0), + new Vector3d(1.0, 0.0, 0.0), + BackendSlot.JOLT), + new DirectionSpec(new Vector3f(0.0f, GRAVITY, 0.0f), + new Vector3d(4.0, 0.0, -4.0), + new Vector3d(1.0, 0.0, 0.0), + BackendSlot.JOLT), + new DirectionSpec(new Vector3f(GRAVITY, 0.0f, 0.0f), + new Vector3d(-4.0, 0.0, 4.0), + new Vector3d(0.0, 0.0, 1.0), + BackendSlot.RAPIER), + new DirectionSpec(new Vector3f(-GRAVITY, 0.0f, 0.0f), + new Vector3d(4.0, 0.0, 4.0), + new Vector3d(0.0, 0.0, 1.0), + BackendSlot.RAPIER) + }; + + private final OptionalArg presetArg = this.withOptionalArg( + "preset", + "Preset: default, clip, cinematic, or gravity-compass", + ArgTypes.STRING); + + public DirectionalPendulumsCommand() { + super("directional-pendulums", + "Spawn four non-streaming spaces with directional double pendulums"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world) { + Preset preset = parsePreset(ctx); + if (preset == null) { + return CompletableFuture.completedFuture(null); + } + + BackendSelection backendSelection = resolveBackendSelection(ctx); + if (backendSelection == null) { + return CompletableFuture.completedFuture(null); + } + + Store physicsStore = PhysicsThreading.store(world); + Vector3d playerPosition = new Vector3d(playerRef.getTransform().getPosition()); + Vector3d center = playerPosition.add(0.0, 6.0, 8.0); + try { + SpawnResult result = spawnDemo(physicsStore, backendSelection, center, preset); + TimeResource time = store.getResource(TimeResource.getResourceType()); + for (CreatedBlockBody createdBody : result.createdBodies()) { + ExamplePhysicsUtils.attachBlockBody(store, time, createdBody); + } + if (preset == Preset.CINEMATIC) { + try { + showGravityCompassPresentation(world, playerRef, result); + } catch (RuntimeException presentationException) { + ctx.sender().sendMessage(Message.raw("Spawned Gravity Compass, but cinematic " + + "presentation failed: " + presentationException.getMessage())); + } + } + ctx.sender().sendMessage(Message.raw("Spawned four directional double pendulum spaces " + + "with physicsChunk=none, backends=2x " + backendSelection.joltBackendId().value() + + "/2x " + backendSelection.rapierBackendId().value() + + ", preset=" + preset.label() + + ", blocks=" + preset.blockSummary() + + ": " + spaceIds(result) + ".")); + } catch (RuntimeException exception) { + ctx.sender().sendMessage(Message.raw("Failed to spawn directional pendulums: " + + exception.getMessage())); + } + return CompletableFuture.completedFuture(null); + } + + @Nonnull + static SpawnResult spawnDemo(@Nonnull Store physicsStore, + @Nonnull BackendId backendId, + @Nonnull Vector3d center) { + return spawnDemo(physicsStore, backendId, center, Preset.DEFAULT); + } + + @Nonnull + static SpawnResult spawnDemo(@Nonnull Store physicsStore, + @Nonnull BackendId backendId, + @Nonnull Vector3d center, + @Nonnull Preset preset) { + return spawnDemo(physicsStore, + new BackendSelection(backendId, backendId), + center, + preset); + } + + @Nonnull + static SpawnResult spawnDemo(@Nonnull Store physicsStore, + @Nonnull BackendId joltBackendId, + @Nonnull BackendId rapierBackendId, + @Nonnull Vector3d center) { + return spawnDemo(physicsStore, joltBackendId, rapierBackendId, center, Preset.DEFAULT); + } + + @Nonnull + static SpawnResult spawnDemo(@Nonnull Store physicsStore, + @Nonnull BackendId joltBackendId, + @Nonnull BackendId rapierBackendId, + @Nonnull Vector3d center, + @Nonnull Preset preset) { + return spawnDemo(physicsStore, + new BackendSelection(joltBackendId, rapierBackendId), + center, + preset); + } + + @Nonnull + private static SpawnResult spawnDemo(@Nonnull Store physicsStore, + @Nonnull BackendSelection backendSelection, + @Nonnull Vector3d center, + @Nonnull Preset preset) { + PhysicsThreading.requireWorldThread(physicsStore, + "spawn directional double pendulum spaces"); + DirectionSpec[] directions = preset.directions(); + List pendulums = new ArrayList<>(directions.length); + List createdBodies = new ArrayList<>(directions.length * 3); + for (DirectionSpec direction : directions) { + BackendId backendId = backendSelection.backendId(direction.backendSlot()); + SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId); + Ref spaceRef = PhysicsSpaces.resolveRef(physicsStore, spaceId); + if (spaceRef == null) { + throw new IllegalStateException("Created physics space id=" + spaceId.value() + + " is not bound."); + } + putChunkCollisionNone(physicsStore, spaceRef); + putGravity(physicsStore, spaceRef, direction.gravity()); + putNoSleepSolverSettings(physicsStore, spaceRef); + CreatedPendulum pendulum = spawnPendulum(physicsStore, + spaceId, + spaceRef, + backendId, + direction.gravity(), + new Vector3d(center).add(direction.originOffset()), + direction.tangent(), + preset.blockType(direction.backendSlot()), + preset.upperSwingSpeed(), + preset.lowerRelativeSwingSpeed()); + pendulums.add(pendulum); + createdBodies.add(pendulum.anchor()); + createdBodies.add(pendulum.upper()); + createdBodies.add(pendulum.lower()); + } + return new SpawnResult(pendulums, createdBodies); + } + + @Nonnull + private static CreatedPendulum spawnPendulum(@Nonnull Store physicsStore, + @Nonnull SpaceId spaceId, + @Nonnull Ref spaceRef, + @Nonnull BackendId backendId, + @Nonnull Vector3f gravity, + @Nonnull Vector3d anchorPosition, + @Nonnull Vector3d tangent, + @Nonnull String blockType, + float upperSwingSpeed, + float lowerRelativeSwingSpeed) { + Vector3d down = new Vector3d(gravity.x, gravity.y, gravity.z).normalize(); + Vector3d upperPosition = new Vector3d(anchorPosition) + .add(new Vector3d(down).mul(LINK_HALF_LENGTH)); + Vector3d lowerPosition = new Vector3d(anchorPosition) + .add(new Vector3d(down).mul(LINK_HALF_LENGTH * 3.0)); + InitialVelocity upperVelocity = initialVelocity(down, + tangent, + upperSwingSpeed, + upperSwingSpeed); + InitialVelocity lowerVelocity = initialVelocity(down, + tangent, + upperSwingSpeed * 2.0f + lowerRelativeSwingSpeed, + lowerRelativeSwingSpeed); + + CreatedBlockBody anchor = spawnBody(physicsStore, + spaceRef, + spaceId, + anchorPosition, + PhysicsShapeSpec.box(PIVOT_HALF_SIZE, PIVOT_HALF_SIZE, PIVOT_HALF_SIZE), + PhysicsBodyType.STATIC, + 0.0f, + null, + blockType); + CreatedBlockBody upper = spawnBody(physicsStore, + spaceRef, + spaceId, + upperPosition, + linkShape(down), + PhysicsBodyType.DYNAMIC, + 1.0f, + upperVelocity, + blockType); + CreatedBlockBody lower = spawnBody(physicsStore, + spaceRef, + spaceId, + lowerPosition, + linkShape(down), + PhysicsBodyType.DYNAMIC, + 1.0f, + lowerVelocity, + blockType); + Ref topJointRef = addPointJoint(physicsStore, + spaceRef, + anchor, + upper, + new Vector3f(), + anchor(down, -LINK_HALF_LENGTH)); + Ref middleJointRef = addPointJoint(physicsStore, + spaceRef, + upper, + lower, + anchor(down, LINK_HALF_LENGTH), + anchor(down, -LINK_HALF_LENGTH)); + return new CreatedPendulum(spaceId, + spaceRef, + backendId, + gravity, + anchor, + upper, + lower, + topJointRef, + middleJointRef); + } + + @Nonnull + private static CreatedBlockBody spawnBody(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef, + @Nonnull SpaceId spaceId, + @Nonnull Vector3d position, + @Nonnull PhysicsShapeSpec shape, + @Nonnull PhysicsBodyType bodyType, + float mass, + @Nullable InitialVelocity initialVelocity, + @Nonnull String blockType) { + UUID bodyUuid = UUID.randomUUID(); + Ref bodyRef = physicsStore.addEntity(PhysicsBodyEntities.bodyHolder( + spaceRef, + bodyUuid, + ExamplePhysicsUtils.toVector3f(position), + shape, + bodyType, + mass, + PENDULUM_BODY_SETTINGS, + initialVelocity != null ? initialVelocity.linear() : null), + AddReason.SPAWN); + assert bodyRef != null; + if (initialVelocity != null) { + TargetComponent target = physicsStore.getComponent(bodyRef, + TargetComponent.getComponentType()); + if (target == null) { + throw new IllegalStateException("PhysicsStore body is missing target component: " + + bodyRef); + } + TargetComponent updated = target.clone(); + updated.setAngularVelocity(initialVelocity.angular()); + physicsStore.putComponent(bodyRef, TargetComponent.getComponentType(), updated); + } + return new CreatedBlockBody(bodyUuid, + bodyRef, + spaceId, + blockType, + (float) position.x, + (float) position.y, + (float) position.z, + mass > 0.0f, + PENDULUM_VISUAL_ORIGIN_OFFSET_Y); + } + + @Nonnull + private static InitialVelocity initialVelocity(@Nonnull Vector3d down, + @Nonnull Vector3d tangent, + float centerSpeed, + float angularEndpointSpeed) { + Vector3d angular = new Vector3d(down) + .cross(tangent) + .mul(angularEndpointSpeed / LINK_HALF_LENGTH); + return new InitialVelocity(ExamplePhysicsUtils.toVector3f( + new Vector3d(tangent).mul(centerSpeed)), + ExamplePhysicsUtils.toVector3f(angular)); + } + + @Nonnull + private static Ref addPointJoint(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef, + @Nonnull CreatedBlockBody bodyA, + @Nonnull CreatedBlockBody bodyB, + @Nonnull Vector3f anchorA, + @Nonnull Vector3f anchorB) { + JointComponent joint = PhysicsJointEntities.joint(spaceRef, + bodyA.bodyRef(), + bodyB.bodyRef(), + JointType.POINT, + anchorA, + anchorB, + new Vector3f()); + Ref jointRef = physicsStore.addEntity(PhysicsEntities.jointHolder( + physicsStore, + UUID.randomUUID(), + joint), + AddReason.SPAWN); + assert jointRef != null; + return jointRef; + } + + @Nonnull + private static Vector3f anchor(@Nonnull Vector3d down, float scale) { + return new Vector3f((float) down.x, (float) down.y, (float) down.z).mul(scale); + } + + @Nonnull + private static PhysicsShapeSpec linkShape(@Nonnull Vector3d down) { + float x = Math.abs(down.x) > 0.5 ? LINK_HALF_LENGTH : LINK_THICKNESS; + float y = Math.abs(down.y) > 0.5 ? LINK_HALF_LENGTH : LINK_THICKNESS; + float z = Math.abs(down.z) > 0.5 ? LINK_HALF_LENGTH : LINK_THICKNESS; + return PhysicsShapeSpec.box(x, y, z); + } + + private static void putChunkCollisionNone(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef) { + PhysicsChunkCollisionSettings settings = new PhysicsChunkCollisionSettings(); + settings.setMode(PhysicsChunkCollisionMode.NONE); + PhysicsSpaces.putChunkCollisionSettings(physicsStore, spaceRef, settings); + } + + private static void putGravity(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef, + @Nonnull Vector3f gravity) { + SpaceComponent space = PhysicsSpaces.getSpaceComponent(physicsStore, + spaceRef, + SpaceComponent.getComponentType()); + if (space == null) { + throw new IllegalStateException("PhysicsStore entity is not a space entity: " + + spaceRef); + } + SpaceComponent updated = space.clone(); + updated.setGravity(gravity); + PhysicsSpaces.putSpaceComponent(physicsStore, + spaceRef, + SpaceComponent.getComponentType(), + updated); + } + + private static void putNoSleepSolverSettings(@Nonnull Store physicsStore, + @Nonnull Ref spaceRef) { + PhysicsSolverSettings settings = PhysicsSpaces.solverSettings(physicsStore, spaceRef); + if (settings == null) { + throw new IllegalStateException("PhysicsStore entity is not a space entity: " + + spaceRef); + } + settings.setDynamicSleepTuning(PENDULUM_SLEEP_LINEAR_THRESHOLD, + PENDULUM_SLEEP_ANGULAR_THRESHOLD, + PENDULUM_TIME_UNTIL_SLEEP); + PhysicsSpaces.putSolverSettings(physicsStore, spaceRef, settings); + } + + @Nullable + private Preset parsePreset(@Nonnull CommandContext ctx) { + if (!presetArg.provided(ctx)) { + return Preset.DEFAULT; + } + String rawPreset = presetArg.get(ctx); + Preset preset = Preset.from(rawPreset); + if (preset != null) { + return preset; + } + ctx.sender().sendMessage(Message.raw("Unknown directional pendulums preset '" + + rawPreset + "'. Expected default, clip, cinematic, or gravity-compass.")); + return null; + } + + private static void showGravityCompassPresentation(@Nonnull World world, + @Nonnull PlayerRef playerRef, + @Nonnull SpawnResult result) { + EventTitleUtil.showEventTitleToPlayer(playerRef, + Message.raw("Gravity Compass"), + Message.raw("Jolt cyan x2 / Rapier basalt x2, four gravity vectors"), + true); + for (CreatedPendulum pendulum : result.pendulums()) { + drawGravityArrow(world, pendulum); + } + } + + private static void drawGravityArrow(@Nonnull World world, + @Nonnull CreatedPendulum pendulum) { + Vector3d direction = new Vector3d(pendulum.gravity().x, + pendulum.gravity().y, + pendulum.gravity().z).normalize().mul(2.5); + Vector3d origin = bodyPosition(pendulum.anchor()) + .sub(new Vector3d(direction).mul(0.5)); + DebugUtils.addArrow(world, + origin, + direction, + gravityArrowColor(pendulum), + 1.0f, + CINEMATIC_GRAVITY_ARROW_DURATION_SECONDS, + DebugUtils.FLAG_FADE); + } + + @Nonnull + private static Vector3f gravityArrowColor(@Nonnull CreatedPendulum pendulum) { + return CINEMATIC_JOLT_BLOCK_TYPE.equals(pendulum.anchor().blockType()) + ? DebugUtils.COLOR_CYAN + : DebugUtils.COLOR_MAGENTA; + } + + @Nonnull + private static Vector3d bodyPosition(@Nonnull CreatedBlockBody body) { + return new Vector3d(body.positionX(), body.positionY(), body.positionZ()); + } + + @Nullable + private BackendSelection resolveBackendSelection(@Nonnull CommandContext ctx) { + List missing = new ArrayList<>(2); + if (!backendRegistered(JOLT_BACKEND_ID)) { + missing.add(JOLT_BACKEND_ID); + } + if (!backendRegistered(RAPIER_BACKEND_ID)) { + missing.add(RAPIER_BACKEND_ID); + } + if (missing.isEmpty()) { + return new BackendSelection(JOLT_BACKEND_ID, RAPIER_BACKEND_ID); + } + String missingIds = String.join(", ", + missing.stream().map(BackendId::value).toList()); + ctx.sender().sendMessage(Message.raw("Directional pendulums require registered backends " + + missingIds + ". Available backends: " + availableBackendIds())); + return null; + } + + private static boolean backendRegistered(@Nonnull BackendId backendId) { + try { + Impulse.getRuntimeProvider(backendId); + return true; + } catch (RuntimeException exception) { + return false; + } + } + + @Nonnull + private static String availableBackendIds() { + List backendIds = new ArrayList<>(); + for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { + backendIds.add(provider.getId().value()); + } + backendIds.sort(String::compareTo); + return backendIds.isEmpty() ? "" : String.join(", ", backendIds); + } + + @Nonnull + private static String spaceIds(@Nonnull SpawnResult result) { + List ids = result.pendulums() + .stream() + .map(pendulum -> Integer.toString(pendulum.spaceId().value())) + .toList(); + return String.join(", ", ids); + } + + record SpawnResult(@Nonnull List pendulums, + @Nonnull List createdBodies) { + + SpawnResult { + pendulums = List.copyOf(Objects.requireNonNull(pendulums, "pendulums")); + createdBodies = List.copyOf(Objects.requireNonNull(createdBodies, "createdBodies")); + } + } + + record CreatedPendulum(@Nonnull SpaceId spaceId, + @Nonnull Ref spaceRef, + @Nonnull BackendId backendId, + @Nonnull Vector3f gravity, + @Nonnull CreatedBlockBody anchor, + @Nonnull CreatedBlockBody upper, + @Nonnull CreatedBlockBody lower, + @Nonnull Ref topJointRef, + @Nonnull Ref middleJointRef) { + + CreatedPendulum { + Objects.requireNonNull(spaceId, "spaceId"); + Objects.requireNonNull(spaceRef, "spaceRef"); + Objects.requireNonNull(backendId, "backendId"); + gravity = new Vector3f(Objects.requireNonNull(gravity, "gravity")); + Objects.requireNonNull(anchor, "anchor"); + Objects.requireNonNull(upper, "upper"); + Objects.requireNonNull(lower, "lower"); + Objects.requireNonNull(topJointRef, "topJointRef"); + Objects.requireNonNull(middleJointRef, "middleJointRef"); + } + } + + private record DirectionSpec(@Nonnull Vector3f gravity, + @Nonnull Vector3d originOffset, + @Nonnull Vector3d tangent, + @Nonnull BackendSlot backendSlot) { + + private DirectionSpec { + gravity = new Vector3f(Objects.requireNonNull(gravity, "gravity")); + originOffset = new Vector3d(Objects.requireNonNull(originOffset, "originOffset")); + tangent = new Vector3d(Objects.requireNonNull(tangent, "tangent")).normalize(); + Objects.requireNonNull(backendSlot, "backendSlot"); + } + } + + private record BackendSelection(@Nonnull BackendId joltBackendId, + @Nonnull BackendId rapierBackendId) { + + private BackendSelection { + Objects.requireNonNull(joltBackendId, "joltBackendId"); + Objects.requireNonNull(rapierBackendId, "rapierBackendId"); + } + + @Nonnull + private BackendId backendId(@Nonnull BackendSlot backendSlot) { + return switch (backendSlot) { + case JOLT -> joltBackendId; + case RAPIER -> rapierBackendId; + }; + } + } + + enum Preset { + DEFAULT("default"), + CINEMATIC("clip"); + + private final String label; + + Preset(@Nonnull String label) { + this.label = label; + } + + @Nullable + private static Preset from(@Nullable String rawPreset) { + if (rawPreset == null || rawPreset.isBlank()) { + return DEFAULT; + } + return switch (rawPreset.trim().toLowerCase(Locale.ROOT)) { + case "default", "classic" -> DEFAULT; + case "clip", "cinematic", "gravity-compass", "gravity_compass" -> CINEMATIC; + default -> null; + }; + } + + @Nonnull + private String label() { + return label; + } + + @Nonnull + private DirectionSpec[] directions() { + return switch (this) { + case DEFAULT -> DIRECTIONS; + case CINEMATIC -> CINEMATIC_DIRECTIONS; + }; + } + + @Nonnull + private String blockType(@Nonnull BackendSlot backendSlot) { + return switch (this) { + case DEFAULT -> PENDULUM_BLOCK_TYPE; + case CINEMATIC -> switch (backendSlot) { + case JOLT -> CINEMATIC_JOLT_BLOCK_TYPE; + case RAPIER -> CINEMATIC_RAPIER_BLOCK_TYPE; + }; + }; + } + + private float upperSwingSpeed() { + return switch (this) { + case DEFAULT -> UPPER_SWING_SPEED; + case CINEMATIC -> CINEMATIC_UPPER_SWING_SPEED; + }; + } + + private float lowerRelativeSwingSpeed() { + return switch (this) { + case DEFAULT -> LOWER_RELATIVE_SWING_SPEED; + case CINEMATIC -> CINEMATIC_LOWER_RELATIVE_SWING_SPEED; + }; + } + + @Nonnull + private String blockSummary() { + return switch (this) { + case DEFAULT -> PENDULUM_BLOCK_TYPE; + case CINEMATIC -> "Jolt=" + CINEMATIC_JOLT_BLOCK_TYPE + + ", Rapier=" + CINEMATIC_RAPIER_BLOCK_TYPE; + }; + } + } + + private enum BackendSlot { + JOLT, + RAPIER + } + + private record InitialVelocity(@Nonnull Vector3f linear, + @Nonnull Vector3f angular) { + + private InitialVelocity { + linear = new Vector3f(Objects.requireNonNull(linear, "linear")); + angular = new Vector3f(Objects.requireNonNull(angular, "angular")); + } + } +} diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java index 60cd6185..63cb776f 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ImpulseCommand.java @@ -12,6 +12,7 @@ public ImpulseCommand() { addSubCommand(new MaterialsCommand()); addSubCommand(new ForcesCommand()); addSubCommand(new JointsCommand()); + addSubCommand(new DirectionalPendulumsCommand()); addSubCommand(new RaycastCommand()); addSubCommand(new PhysicsStoreExampleCommands.BumperCommand()); addSubCommand(new PhysicsStoreExampleCommands.PlatformCommand()); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java index 9abf4d2c..100291db 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/PhysicsStoreExampleCommands.java @@ -106,13 +106,12 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, } return PhysicsAsync.acceptOnWorldThread(world, raycastAsync(store, ref, spaceRef), - hit -> applyImpulse(ctx, store, ref, world, hit)); + hit -> applyImpulse(ctx, store, ref, hit)); } private void applyImpulse(@Nonnull CommandContext ctx, @Nonnull Store store, @Nonnull Ref ref, - @Nonnull World world, @Nullable RaycastHitView hit) { if (hit == null || hit.bodyRef() == null || !hit.bodyRef().isValid()) { ctx.sender().sendMessage(Message.raw("No rigid body in view.")); @@ -121,7 +120,8 @@ private void applyImpulse(@Nonnull CommandContext ctx, int strength = ExamplePhysicsUtils.optionalInt(ctx, strengthArg, 8, 1, 64); Vector3d impulse = new Vector3d(TargetUtil.getLook(ref, store) - .getDirection()).mul(strength); + .getDirection()) + .mul(strength); Ref bodyRef = hit.bodyRef(); Store physicsStore = bodyRef.getStore(); PhysicsBodies.appendCommand(physicsStore, diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index 0ad12d07..e683e876 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -193,6 +193,7 @@ public static void attachBlockBody(@Nonnull Store store, created.bodyUuid(), created.blockType(), new Vector3d(created.positionX(), created.positionY(), created.positionZ()), + created.visualOriginOffsetY(), created.controllable()); } @@ -220,6 +221,7 @@ private static Ref spawnAttachedBlockEntity(@Nonnull Store holder = attachedPhysicsBlockEntityHolder(time, bodyRef, @@ -228,7 +230,7 @@ private static Ref spawnAttachedBlockEntity(@Nonnull Store bodyRef, + @Nonnull SpaceId spaceId, + @Nullable String blockType, + float positionX, + float positionY, + float positionZ, + boolean controllable) { + this(bodyUuid, + bodyRef, + spaceId, + blockType, + positionX, + positionY, + positionZ, + controllable, + Float.NaN); + } public CreatedBlockBody { Objects.requireNonNull(bodyUuid, "bodyUuid"); diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java new file mode 100644 index 00000000..9797d080 --- /dev/null +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java @@ -0,0 +1,586 @@ +package dev.hytalemodding.impulse.examples.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import dev.hytalemodding.impulse.api.BackendId; +import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; +import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; +import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; +import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; +import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; +import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; +import dev.hytalemodding.impulse.core.internal.systems.binding.SpaceBindingSystem; +import dev.hytalemodding.impulse.core.plugin.components.CollisionFilterComponent; +import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointComponent; +import dev.hytalemodding.impulse.core.plugin.components.JointType; +import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; +import dev.hytalemodding.impulse.core.plugin.components.TargetComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; +import dev.hytalemodding.impulse.examples.utils.ExamplePhysicsUtils.CreatedBlockBody; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import javax.annotation.Nonnull; +import org.joml.Vector3d; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; + +class DirectionalPendulumsCommandTest { + + private static final ObjenesisStd OBJENESIS = new ObjenesisStd(); + private static final BackendId JOLT_BACKEND_ID = + new BackendId("test:directional-pendulums-jolt"); + private static final BackendId RAPIER_BACKEND_ID = + new BackendId("test:directional-pendulums-rapier"); + private static final BackendId BINDING_BACKEND_ID = + new BackendId("test:directional-pendulums-binding"); + private static final String CINEMATIC_JOLT_BLOCK_TYPE = "Rock_Aqua"; + private static final String CINEMATIC_RAPIER_BLOCK_TYPE = "Rock_Basalt"; + private static final float CINEMATIC_MIN_LINEAR_SPEED = 2.0f; + private static final float CINEMATIC_MIN_ANGULAR_SPEED = 1.0f; + + @Test + void spawnDemoCreatesFourNonStreamingSpacesWithDirectionalDoublePendulums() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(world("directional-pendulums-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + + DirectionalPendulumsCommand.SpawnResult result = + DirectionalPendulumsCommand.spawnDemo(store, + JOLT_BACKEND_ID, + RAPIER_BACKEND_ID, + new Vector3d(10.0, 20.0, 30.0)); + + assertEquals(4, result.pendulums().size()); + assertEquals(12, result.createdBodies().size()); + + Vector3f[] expectedGravities = { + new Vector3f(0.0f, -9.81f, 0.0f), + new Vector3f(0.0f, 9.81f, 0.0f), + new Vector3f(9.81f, 0.0f, 0.0f), + new Vector3f(-9.81f, 0.0f, 0.0f) + }; + Vector3f[] expectedAnchors = { + new Vector3f(2.0f, 20.0f, 22.0f), + new Vector3f(18.0f, 20.0f, 22.0f), + new Vector3f(2.0f, 20.0f, 38.0f), + new Vector3f(18.0f, 20.0f, 38.0f) + }; + BackendId[] expectedBackends = { + JOLT_BACKEND_ID, + JOLT_BACKEND_ID, + RAPIER_BACKEND_ID, + RAPIER_BACKEND_ID + }; + for (int index = 0; index < expectedGravities.length; index++) { + DirectionalPendulumsCommand.CreatedPendulum pendulum = + result.pendulums().get(index); + assertEquals(expectedBackends[index], pendulum.backendId()); + assertVectorEquals(expectedGravities[index], pendulum.gravity()); + assertBodyPosition(expectedAnchors[index], pendulum.anchor()); + assertPendulumVisual(pendulum.anchor()); + assertPendulumVisual(pendulum.upper()); + assertPendulumVisual(pendulum.lower()); + assertEquals(PhysicsChunkCollisionMode.NONE, + PhysicsSpaces.chunkCollisionSettings(store, pendulum.spaceRef()).getMode()); + assertNoSleepSolverSettings(store, pendulum.spaceRef()); + assertVectorEquals(expectedGravities[index], + store.getComponent(pendulum.spaceRef(), SpaceComponent.getComponentType()) + .getGravity()); + assertBodyMass(store, pendulum.anchor().bodyRef(), 0.0f); + assertBodyMass(store, pendulum.upper().bodyRef(), 1.0f); + assertBodyMass(store, pendulum.lower().bodyRef(), 1.0f); + assertPendulumCollisionFilter(store, pendulum.anchor().bodyRef()); + assertPendulumCollisionFilter(store, pendulum.upper().bodyRef()); + assertPendulumCollisionFilter(store, pendulum.lower().bodyRef()); + assertTangentialVelocity(store, + pendulum.upper().bodyRef(), + expectedGravities[index]); + assertTangentialVelocity(store, + pendulum.lower().bodyRef(), + expectedGravities[index]); + assertPointJoint(store, + pendulum.topJointRef(), + pendulum.anchor().bodyRef(), + pendulum.upper().bodyRef(), + new Vector3f(), + normalized(expectedGravities[index], -1.0f)); + assertEndpointVelocityMatch(store, + pendulum.anchor().bodyRef(), + new Vector3f(), + pendulum.upper().bodyRef(), + normalized(expectedGravities[index], -1.0f)); + assertPointJoint(store, + pendulum.middleJointRef(), + pendulum.upper().bodyRef(), + pendulum.lower().bodyRef(), + normalized(expectedGravities[index], 1.0f), + normalized(expectedGravities[index], -1.0f)); + assertEndpointVelocityMatch(store, + pendulum.upper().bodyRef(), + normalized(expectedGravities[index], 1.0f), + pendulum.lower().bodyRef(), + normalized(expectedGravities[index], -1.0f)); + } + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void spawnCinematicPresetKeepsFourNonStreamingBackendSplitSpaces() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(world("directional-pendulums-cinematic-spaces-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + + DirectionalPendulumsCommand.SpawnResult result = + spawnCinematicDemo(store, + JOLT_BACKEND_ID, + RAPIER_BACKEND_ID, + new Vector3d(10.0, 20.0, 30.0)); + + assertEquals(4, result.pendulums().size()); + assertEquals(12, result.createdBodies().size()); + BackendId[] expectedBackends = { + JOLT_BACKEND_ID, + JOLT_BACKEND_ID, + RAPIER_BACKEND_ID, + RAPIER_BACKEND_ID + }; + Vector3f[] expectedGravities = directionalGravities(); + for (int index = 0; index < expectedBackends.length; index++) { + DirectionalPendulumsCommand.CreatedPendulum pendulum = + result.pendulums().get(index); + assertEquals(expectedBackends[index], pendulum.backendId()); + assertVectorEquals(expectedGravities[index], pendulum.gravity()); + assertEquals(PhysicsChunkCollisionMode.NONE, + PhysicsSpaces.chunkCollisionSettings(store, pendulum.spaceRef()).getMode()); + assertNoSleepSolverSettings(store, pendulum.spaceRef()); + assertVectorEquals(expectedGravities[index], + store.getComponent(pendulum.spaceRef(), SpaceComponent.getComponentType()) + .getGravity()); + } + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void spawnCinematicPresetUsesTighterBackendDistinctVisualLayout() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(world("directional-pendulums-cinematic-layout-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + + DirectionalPendulumsCommand.SpawnResult result = + spawnCinematicDemo(store, + JOLT_BACKEND_ID, + RAPIER_BACKEND_ID, + new Vector3d(10.0, 20.0, 30.0)); + + Vector3f[] expectedAnchors = { + new Vector3f(6.0f, 20.0f, 26.0f), + new Vector3f(14.0f, 20.0f, 26.0f), + new Vector3f(6.0f, 20.0f, 34.0f), + new Vector3f(14.0f, 20.0f, 34.0f) + }; + String[] expectedBlockTypes = { + CINEMATIC_JOLT_BLOCK_TYPE, + CINEMATIC_JOLT_BLOCK_TYPE, + CINEMATIC_RAPIER_BLOCK_TYPE, + CINEMATIC_RAPIER_BLOCK_TYPE + }; + for (int index = 0; index < expectedAnchors.length; index++) { + DirectionalPendulumsCommand.CreatedPendulum pendulum = + result.pendulums().get(index); + assertBodyPosition(expectedAnchors[index], pendulum.anchor()); + assertCinematicVisual(pendulum.anchor(), expectedBlockTypes[index]); + assertCinematicVisual(pendulum.upper(), expectedBlockTypes[index]); + assertCinematicVisual(pendulum.lower(), expectedBlockTypes[index]); + } + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void spawnCinematicPresetUsesStrongerLateralInitialMotion() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(world("directional-pendulums-cinematic-motion-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + + DirectionalPendulumsCommand.SpawnResult result = + spawnCinematicDemo(store, + JOLT_BACKEND_ID, + RAPIER_BACKEND_ID, + new Vector3d(10.0, 20.0, 30.0)); + + Vector3f[] expectedGravities = directionalGravities(); + for (int index = 0; index < expectedGravities.length; index++) { + DirectionalPendulumsCommand.CreatedPendulum pendulum = + result.pendulums().get(index); + assertStrongLateralVelocity(store, + pendulum.upper().bodyRef(), + expectedGravities[index]); + assertStrongLateralVelocity(store, + pendulum.lower().bodyRef(), + expectedGravities[index]); + assertEndpointVelocityMatch(store, + pendulum.anchor().bodyRef(), + new Vector3f(), + pendulum.upper().bodyRef(), + normalized(expectedGravities[index], -1.0f)); + assertEndpointVelocityMatch(store, + pendulum.upper().bodyRef(), + normalized(expectedGravities[index], 1.0f), + pendulum.lower().bodyRef(), + normalized(expectedGravities[index], -1.0f)); + } + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void storeTickAppliesDirectionalSpaceGravitiesWithoutChangingThem() { + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider(BINDING_BACKEND_ID, false, false); + Impulse.registerRuntimeProvider(provider); + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + Store store = registry.addStore( + new PhysicsStore(world("directional-pendulums-binding-test")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + + DirectionalPendulumsCommand.SpawnResult result = + DirectionalPendulumsCommand.spawnDemo(store, + BINDING_BACKEND_ID, + new Vector3d(10.0, 20.0, 30.0)); + + store.tick(0.0f); + assertFalse(restore.isFailed(), restore.getFailureMessage()); + assertEquals(1, provider.createdRuntimes().size()); + FakePhysicsBackendRuntime backendRuntime = provider.createdRuntimes().get(0); + assertBackendGravities(store, backendRuntime, result); + + store.tick(0.0f); + assertFalse(restore.isFailed(), restore.getFailureMessage()); + assertBackendGravities(store, backendRuntime, result); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + private static void assertBackendGravities(@Nonnull Store store, + @Nonnull FakePhysicsBackendRuntime backendRuntime, + @Nonnull DirectionalPendulumsCommand.SpawnResult result) { + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + for (DirectionalPendulumsCommand.CreatedPendulum pendulum : result.pendulums()) { + BackendSpaceHandle handle = runtime.getSpaceHandle(pendulum.spaceRef()); + assertNotNull(handle); + assertVectorEquals(pendulum.gravity(), + backendGravity(backendRuntime, handle.value())); + } + } + + @Nonnull + @SuppressWarnings({"unchecked", "rawtypes"}) + private static DirectionalPendulumsCommand.SpawnResult spawnCinematicDemo( + @Nonnull Store store, + @Nonnull BackendId joltBackendId, + @Nonnull BackendId rapierBackendId, + @Nonnull Vector3d center) { + try { + Class presetType = Class.forName( + DirectionalPendulumsCommand.class.getName() + "$Preset"); + Object cinematicPreset = Enum.valueOf((Class) presetType.asSubclass(Enum.class), + "CINEMATIC"); + Method spawnDemo = DirectionalPendulumsCommand.class.getDeclaredMethod("spawnDemo", + Store.class, + BackendId.class, + BackendId.class, + Vector3d.class, + presetType); + spawnDemo.setAccessible(true); + return (DirectionalPendulumsCommand.SpawnResult) spawnDemo.invoke(null, + store, + joltBackendId, + rapierBackendId, + center, + cinematicPreset); + } catch (ClassNotFoundException exception) { + throw new AssertionError("Expected DirectionalPendulumsCommand.Preset.CINEMATIC", + exception); + } catch (NoSuchMethodException exception) { + throw new AssertionError("Expected cinematic spawnDemo overload", exception); + } catch (IllegalAccessException exception) { + throw new AssertionError("Could not invoke cinematic spawnDemo overload", exception); + } catch (InvocationTargetException exception) { + Throwable cause = exception.getTargetException(); + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + if (cause instanceof Error error) { + throw error; + } + throw new AssertionError("Cinematic spawnDemo overload failed", cause); + } + } + + @Nonnull + private static Vector3f backendGravity(@Nonnull FakePhysicsBackendRuntime runtime, + int spaceId) { + Vector3f gravity = new Vector3f(); + runtime.getGravity(spaceId, gravity::set); + return gravity; + } + + private static void assertBodyMass(@Nonnull Store store, + @Nonnull Ref bodyRef, + float expectedMass) { + DynamicsComponent dynamics = store.getComponent(bodyRef, + DynamicsComponent.getComponentType()); + assertNotNull(dynamics); + assertEquals(expectedMass, dynamics.getMass()); + } + + private static void assertPendulumCollisionFilter(@Nonnull Store store, + @Nonnull Ref bodyRef) { + CollisionFilterComponent filter = store.getComponent(bodyRef, + CollisionFilterComponent.getComponentType()); + assertNotNull(filter); + assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, filter.getCollisionGroup()); + assertEquals(PhysicsCollisionFilters.TERRAIN, filter.getCollisionMask()); + } + + private static void assertPointJoint(@Nonnull Store store, + @Nonnull Ref jointRef, + @Nonnull Ref expectedBodyARef, + @Nonnull Ref expectedBodyBRef, + @Nonnull Vector3f expectedAnchorA, + @Nonnull Vector3f expectedAnchorB) { + JointComponent joint = store.getComponent(jointRef, + JointComponent.getComponentType()); + assertNotNull(joint); + assertEquals(JointType.POINT, joint.getType()); + assertSame(expectedBodyARef, joint.getBodyARef()); + assertSame(expectedBodyBRef, joint.getBodyBRef()); + assertVectorEquals(expectedAnchorA, joint.getAnchorA()); + assertVectorEquals(expectedAnchorB, joint.getAnchorB()); + } + + private static void assertBodyPosition(@Nonnull Vector3f expected, + @Nonnull CreatedBlockBody body) { + assertEquals(expected.x, body.positionX(), 0.0001f); + assertEquals(expected.y, body.positionY(), 0.0001f); + assertEquals(expected.z, body.positionZ(), 0.0001f); + } + + private static void assertPendulumVisual(@Nonnull CreatedBlockBody body) { + assertEquals(DirectionalPendulumsCommand.PENDULUM_BLOCK_TYPE, body.blockType()); + assertEquals(DirectionalPendulumsCommand.PENDULUM_VISUAL_ORIGIN_OFFSET_Y, + body.visualOriginOffsetY(), + 0.0001f); + } + + private static void assertCinematicVisual(@Nonnull CreatedBlockBody body, + @Nonnull String expectedBlockType) { + assertEquals(expectedBlockType, body.blockType()); + assertEquals(DirectionalPendulumsCommand.PENDULUM_VISUAL_ORIGIN_OFFSET_Y, + body.visualOriginOffsetY(), + 0.0001f); + } + + private static void assertNoSleepSolverSettings(@Nonnull Store store, + @Nonnull Ref spaceRef) { + PhysicsSolverSettings settings = PhysicsSpaces.solverSettings(store, spaceRef); + assertNotNull(settings); + assertEquals(DirectionalPendulumsCommand.PENDULUM_SLEEP_LINEAR_THRESHOLD, + settings.getDynamicSleepLinearThreshold(), + 0.0001f); + assertEquals(DirectionalPendulumsCommand.PENDULUM_SLEEP_ANGULAR_THRESHOLD, + settings.getDynamicSleepAngularThreshold(), + 0.0001f); + assertEquals(DirectionalPendulumsCommand.PENDULUM_TIME_UNTIL_SLEEP, + settings.getDynamicSleepTimeUntilSleep(), + 0.0001f); + } + + private static void assertTangentialVelocity(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull Vector3f gravity) { + TargetComponent target = store.getComponent(bodyRef, + TargetComponent.getComponentType()); + assertNotNull(target); + Vector3f velocity = target.getLinearVelocity(); + assertEquals(0.0f, velocity.dot(gravity), 0.0001f); + if (velocity.lengthSquared() <= 0.1f) { + throw new AssertionError("Expected tangential initial velocity but got " + velocity); + } + Vector3f angularVelocity = target.getAngularVelocity(); + assertEquals(0.0f, angularVelocity.dot(gravity), 0.0001f); + if (angularVelocity.lengthSquared() <= 0.1f) { + throw new AssertionError("Expected angular initial velocity but got " + + angularVelocity); + } + } + + private static void assertStrongLateralVelocity(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull Vector3f gravity) { + TargetComponent target = store.getComponent(bodyRef, + TargetComponent.getComponentType()); + assertNotNull(target); + Vector3f velocity = target.getLinearVelocity(); + assertEquals(0.0f, velocity.dot(gravity), 0.0001f); + assertTrue(velocity.length() >= CINEMATIC_MIN_LINEAR_SPEED, + () -> "Expected cinematic linear speed >= " + CINEMATIC_MIN_LINEAR_SPEED + + " but got " + velocity); + Vector3f angularVelocity = target.getAngularVelocity(); + assertEquals(0.0f, angularVelocity.dot(gravity), 0.0001f); + assertTrue(angularVelocity.length() >= CINEMATIC_MIN_ANGULAR_SPEED, + () -> "Expected cinematic angular speed >= " + CINEMATIC_MIN_ANGULAR_SPEED + + " but got " + angularVelocity); + } + + private static void assertEndpointVelocityMatch(@Nonnull Store store, + @Nonnull Ref bodyARef, + @Nonnull Vector3f anchorA, + @Nonnull Ref bodyBRef, + @Nonnull Vector3f anchorB) { + assertVectorEquals(endpointVelocity(store, bodyARef, anchorA), + endpointVelocity(store, bodyBRef, anchorB)); + } + + @Nonnull + private static Vector3f endpointVelocity(@Nonnull Store store, + @Nonnull Ref bodyRef, + @Nonnull Vector3f localAnchor) { + TargetComponent target = store.getComponent(bodyRef, + TargetComponent.getComponentType()); + if (target == null) { + return new Vector3f(); + } + Vector3f angular = target.getAngularVelocity(); + Vector3f anchorVelocity = new Vector3f(); + angular.cross(localAnchor, anchorVelocity); + return target.getLinearVelocity().add(anchorVelocity); + } + + private static void assertVectorEquals(@Nonnull Vector3f expected, + @Nonnull Vector3f actual) { + assertEquals(expected.x, actual.x, 0.0001f); + assertEquals(expected.y, actual.y, 0.0001f); + assertEquals(expected.z, actual.z, 0.0001f); + } + + @Nonnull + private static Vector3f[] directionalGravities() { + return new Vector3f[] { + new Vector3f(0.0f, -9.81f, 0.0f), + new Vector3f(0.0f, 9.81f, 0.0f), + new Vector3f(9.81f, 0.0f, 0.0f), + new Vector3f(-9.81f, 0.0f, 0.0f) + }; + } + + @Nonnull + private static Vector3f normalized(@Nonnull Vector3f vector, float scale) { + return new Vector3f(vector).normalize().mul(scale); + } + + @Nonnull + private static World world(@Nonnull String worldName) { + World world = OBJENESIS.newInstance(World.class); + try { + Field name = World.class.getDeclaredField("name"); + name.setAccessible(true); + name.set(world, worldName); + return world; + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Could not create test world", exception); + } + } + + private static void markCurrentThreadAsWorldThread(@Nonnull Store store) { + try { + Method setThread = TickingThread.class.getDeclaredMethod("setThread", Thread.class); + setThread.setAccessible(true); + setThread.invoke(store.getExternalData().getWorld(), Thread.currentThread()); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError("Could not mark test world thread", exception); + } catch (InvocationTargetException exception) { + throw new AssertionError("Could not mark test world thread", + exception.getTargetException()); + } + } +} From d38d02ec95a3d3d096c778990f26b9ef340891e5 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:39:41 +0200 Subject: [PATCH 521/534] test(rapier): cover directional gravity round trips Signed-off-by: Blovien --- .../RapierBackendRuntimeProviderTest.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java index b4cb206f..090e3dd2 100644 --- a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java @@ -132,6 +132,46 @@ void runtimeSupportsPrimitiveBodySnapshotAndJointLifecycle() { } } + @Test + void gravityRoundTripsEveryAxisDirection() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + float[][] gravities = { + {0.0f, -9.81f, 0.0f}, + {0.0f, 9.81f, 0.0f}, + {9.81f, 0.0f, 0.0f}, + {-9.81f, 0.0f, 0.0f} + }; + for (int index = 0; index < gravities.length; index++) { + int spaceId = runtime.createSpace(new SpaceId(700 + index)); + try { + float[] expected = gravities[index]; + runtime.setGravity(spaceId, expected[0], expected[1], expected[2]); + + assertGravityEquals(expected, runtime, spaceId); + runtime.step(spaceId, 1.0f / 60.0f); + assertGravityEquals(expected, runtime, spaceId); + } finally { + runtime.destroySpace(spaceId); + } + } + } + + private static void assertGravityEquals(float[] expected, + PhysicsBackendRuntime runtime, + int spaceId) { + float[] actual = new float[3]; + runtime.getGravity(spaceId, (x, y, z) -> { + actual[0] = x; + actual[1] = y; + actual[2] = z; + }); + assertEquals(expected[0], actual[0], 0.0001f); + assertEquals(expected[1], actual[1], 0.0001f); + assertEquals(expected[2], actual[2], 0.0001f); + } + @Test void failedNativeBodyRemovalKeepsJavaBodyStateForRetry() { RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); From a187868bffa2de9b1cd95cd298d28623921521fc Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:39:48 +0200 Subject: [PATCH 522/534] refactor(jolt): use Math.clamp for native result bounds Signed-off-by: Blovien --- .../dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java | 2 +- .../hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java index 78733e2d..1b6808db 100644 --- a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java @@ -747,7 +747,7 @@ private int emitContacts(@Nonnull RuntimeSpace space, bodyHandles, contacts); int emitted = 0; - int boundedContacts = Math.min(Math.max(nativeContacts, 0), maxContacts); + int boundedContacts = Math.clamp(nativeContacts, 0, maxContacts); for (int index = 0; index < boundedContacts; index++) { int bodyOffset = index * JoltNativeLibrary.CONTACT_BODY_HANDLE_COUNT; Long bodyAId = space.bodyIdsByHandle.get(bodyHandles[bodyOffset]); diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java index 476ebe51..0ae9ad3e 100644 --- a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java +++ b/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java @@ -669,7 +669,7 @@ public int raycastAll(long spaceHandle, maxHits, nativeBodyHandles, nativeHits); - int boundedCount = Math.min(Math.max(count, 0), maxHits); + int boundedCount = Math.clamp(count, 0, maxHits); readLongs(nativeBodyHandles, bodyHandles, boundedCount); readFloats(nativeHits, hits, @@ -700,7 +700,7 @@ public int contacts(long spaceHandle, int maxContacts, long[] bodyHandles, float maxContacts, nativeBodyHandles, nativeContacts); - int boundedCount = Math.min(Math.max(count, 0), maxContacts); + int boundedCount = Math.clamp(count, 0, maxContacts); readLongs(nativeBodyHandles, bodyHandles, boundedCount * JoltNativeLibrary.CONTACT_BODY_HANDLE_COUNT); From 94cc8a733d5ca8e2ee27e906ba3cc5dabf2286f6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 14:39:53 +0200 Subject: [PATCH 523/534] refactor(jolt): split native ABI from internals Signed-off-by: Blovien --- impulse-jolt/build.gradle.kts | 13 +- impulse-jolt/src/main/cpp/CMakeLists.txt | 19 +- .../main/cpp/abi/impulse_jolt_body_api.cpp | 454 ++++++ .../main/cpp/abi/impulse_jolt_joint_api.cpp | 106 ++ .../src/main/cpp/abi/impulse_jolt_native.h | 44 + .../main/cpp/abi/impulse_jolt_query_api.cpp | 130 ++ .../main/cpp/abi/impulse_jolt_space_api.cpp | 75 + impulse-jolt/src/main/cpp/impulse_jolt.cpp | 1437 ----------------- .../main/cpp/internal/impulse_jolt_joints.cpp | 171 ++ .../main/cpp/internal/impulse_jolt_joints.h | 31 + .../main/cpp/internal/impulse_jolt_query.cpp | 43 + .../main/cpp/internal/impulse_jolt_query.h | 18 + .../cpp/internal/impulse_jolt_registry.cpp | 45 + .../main/cpp/internal/impulse_jolt_registry.h | 23 + .../main/cpp/internal/impulse_jolt_shapes.cpp | 128 ++ .../main/cpp/internal/impulse_jolt_shapes.h | 26 + .../cpp/internal/impulse_jolt_snapshot.cpp | 72 + .../main/cpp/internal/impulse_jolt_snapshot.h | 9 + .../main/cpp/internal/impulse_jolt_space.cpp | 174 ++ .../main/cpp/internal/impulse_jolt_space.h | 122 ++ 20 files changed, 1698 insertions(+), 1442 deletions(-) create mode 100644 impulse-jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp create mode 100644 impulse-jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp create mode 100644 impulse-jolt/src/main/cpp/abi/impulse_jolt_native.h create mode 100644 impulse-jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp create mode 100644 impulse-jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp delete mode 100644 impulse-jolt/src/main/cpp/impulse_jolt.cpp create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.cpp create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.h create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_query.cpp create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_query.h create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.cpp create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.h create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.h create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.h create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_space.cpp create mode 100644 impulse-jolt/src/main/cpp/internal/impulse_jolt_space.h diff --git a/impulse-jolt/build.gradle.kts b/impulse-jolt/build.gradle.kts index 25e792c1..37821c0b 100644 --- a/impulse-jolt/build.gradle.kts +++ b/impulse-jolt/build.gradle.kts @@ -1,6 +1,7 @@ import org.gradle.api.GradleException import org.gradle.api.file.FileCollection import org.gradle.api.file.DuplicatesStrategy +import org.gradle.api.tasks.PathSensitivity import org.gradle.jvm.tasks.Jar plugins { @@ -32,7 +33,9 @@ val nativeResourcePath = "native/$nativeResourceOs/$nativeResourceArch" val nativeLibraryName = nativeLibraryNameFor(nativeResourceOs) val nativeSourceDirectory = layout.projectDirectory.dir("src/main/cpp") val nativeCmakeFile = nativeSourceDirectory.file("CMakeLists.txt") -val nativeSourceFile = nativeSourceDirectory.file("impulse_jolt.cpp") +val nativeSourceFiles = nativeSourceDirectory.asFileTree.matching { + include("**/*.cpp", "**/*.h") +} val cmakeBuildDirectory = layout.buildDirectory.dir("cmake/jolt") val nativeOutputDirectory = layout.buildDirectory.dir("native/jolt") val nativeOutputFile = nativeOutputDirectory.map { directory -> directory.file(nativeLibraryName) } @@ -152,7 +155,9 @@ val configureJoltNative by tasks.registering(Exec::class) { onlyIf { buildNative.get() } inputs.file(nativeCmakeFile) - inputs.file(nativeSourceFile) + inputs.files(nativeSourceFiles) + .withPropertyName("nativeSourceFiles") + .withPathSensitivity(PathSensitivity.RELATIVE) inputs.property("joltPhysicsGitTag", joltPhysicsGitTag) inputs.property("joltCmake", cmakeExecutable) inputs.property("joltCmakeGenerator", cmakeGenerator) @@ -189,7 +194,9 @@ val compileJoltNative by tasks.registering(Exec::class) { onlyIf { buildNative.get() } inputs.file(nativeCmakeFile) - inputs.file(nativeSourceFile) + inputs.files(nativeSourceFiles) + .withPropertyName("nativeSourceFiles") + .withPathSensitivity(PathSensitivity.RELATIVE) inputs.property("joltPhysicsGitTag", joltPhysicsGitTag) inputs.property("joltCmake", cmakeExecutable) inputs.property("joltCmakeGenerator", cmakeGenerator) diff --git a/impulse-jolt/src/main/cpp/CMakeLists.txt b/impulse-jolt/src/main/cpp/CMakeLists.txt index 403005e0..4c0909d3 100644 --- a/impulse-jolt/src/main/cpp/CMakeLists.txt +++ b/impulse-jolt/src/main/cpp/CMakeLists.txt @@ -55,8 +55,23 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(JoltPhysics) -add_library(impulse_jolt SHARED impulse_jolt.cpp) -target_include_directories(impulse_jolt PRIVATE "${JoltPhysics_SOURCE_DIR}/..") +add_library(impulse_jolt SHARED) +target_sources(impulse_jolt PRIVATE + abi/impulse_jolt_body_api.cpp + abi/impulse_jolt_joint_api.cpp + abi/impulse_jolt_query_api.cpp + abi/impulse_jolt_space_api.cpp + internal/impulse_jolt_joints.cpp + internal/impulse_jolt_query.cpp + internal/impulse_jolt_registry.cpp + internal/impulse_jolt_shapes.cpp + internal/impulse_jolt_snapshot.cpp + internal/impulse_jolt_space.cpp +) +target_include_directories(impulse_jolt PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + "${JoltPhysics_SOURCE_DIR}/.." +) target_link_libraries(impulse_jolt PRIVATE Jolt) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(IMPULSE_JOLT_EXPORT_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/impulse_jolt_exports.map") diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp b/impulse-jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp new file mode 100644 index 00000000..113e421c --- /dev/null +++ b/impulse-jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp @@ -0,0 +1,454 @@ +#include "internal/impulse_jolt_registry.h" +#include "internal/impulse_jolt_shapes.h" +#include "internal/impulse_jolt_snapshot.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ImpulseJolt; + +extern "C" { + +IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_body(std::int64_t space_handle, + int shape_type, + float half_extent_x, + float half_extent_y, + float half_extent_z, + float radius, + float half_height, + int axis, + float ground_y, + float mass, + int body_type, + float position_x, + float position_y, + float position_z, + float rotation_x, + float rotation_y, + float rotation_z, + float rotation_w) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + + JPH::ShapeRefC shape = CreateShape(shape_type, + half_extent_x, + half_extent_y, + half_extent_z, + radius, + half_height, + axis, + ground_y); + if (shape == nullptr) { + return 0; + } + + BodyState body; + body.m_ShapeType = shape_type; + body.m_BodyType = body_type; + body.m_Mass = mass; + body.m_CenterOfMassOffsetY = CenterOfMassOffsetY(shape_type, + half_extent_y, + radius, + half_height, + axis); + body.m_HasBoxHalfExtents = shape_type == ShapeBox + && half_extent_x > 0.0F + && half_extent_y > 0.0F + && half_extent_z > 0.0F; + body.m_HalfExtentX = half_extent_x; + body.m_HalfExtentY = half_extent_y; + body.m_HalfExtentZ = half_extent_z; + body.m_Radius = radius; + body.m_HalfHeight = half_height; + body.m_Axis = axis; + + JPH::BodyCreationSettings settings(shape, + JPH::RVec3(position_x, position_y, position_z), + JPH::Quat(rotation_x, rotation_y, rotation_z, rotation_w), + MotionType(body_type), + ObjectLayer(body.m_CollisionGroup, body.m_CollisionMask)); + settings.mFriction = body.m_Friction; + settings.mRestitution = body.m_Restitution; + settings.mLinearDamping = body.m_LinearDamping; + settings.mAngularDamping = body.m_AngularDamping; + settings.mAllowDynamicOrKinematic = true; + settings.mIsSensor = body.m_Sensor; + if (mass > 0.0F && body_type != BodyStatic) { + settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia; + settings.mMassPropertiesOverride.mMass = mass; + } + + JPH::BodyID BodyId = + space->m_PhysicsSystem.GetBodyInterface().CreateAndAddBody(settings, + body_type == BodyStatic + ? JPH::EActivation::DontActivate + : JPH::EActivation::Activate); + if (BodyId.IsInvalid()) { + return 0; + } + body.m_BodyId = BodyId; + + const std::uint64_t body_handle = g_NextBodyHandle++; + space->m_Bodies.emplace(body_handle, body); + space->m_BodyHandlesByJoltId.emplace(BodyId.GetIndexAndSequenceNumber(), body_handle); + return static_cast(body_handle); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_remove_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + auto iterator = space->m_Bodies.find(static_cast(body_handle)); + if (iterator == space->m_Bodies.end()) { + return 1; + } + space->EraseConstraintsForBodyHandle(static_cast(body_handle)); + JPH::BodyInterface& body_interface = space->m_PhysicsSystem.GetBodyInterface(); + if (body_interface.IsAdded(iterator->second.m_BodyId)) { + body_interface.RemoveBody(iterator->second.m_BodyId); + } + body_interface.DestroyBody(iterator->second.m_BodyId); + space->m_BodyHandlesByJoltId.erase(iterator->second.m_BodyId.GetIndexAndSequenceNumber()); + space->EraseContactRecordsForBodyHandle(static_cast(body_handle)); + space->m_Bodies.erase(iterator); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_contains_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + return space != nullptr + && FindBody(*space, static_cast(body_handle)) != nullptr + ? 1 + : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_body_snapshot(std::int64_t space_handle, + std::int64_t body_handle, + float* floats, + int* ints) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr || floats == nullptr || ints == nullptr) { + return 0; + } + BodyState* body = FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + WriteSnapshot(*space, *body, floats, ints); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_transform(std::int64_t space_handle, + std::int64_t body_handle, + float position_x, + float position_y, + float position_z, + float rotation_x, + float rotation_y, + float rotation_z, + float rotation_w) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->m_PhysicsSystem.GetBodyInterface().SetPositionAndRotation(body->m_BodyId, + JPH::RVec3(position_x, position_y, position_z), + JPH::Quat(rotation_x, rotation_y, rotation_z, rotation_w), + JPH::EActivation::Activate); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_position(std::int64_t space_handle, + std::int64_t body_handle, + float x, + float y, + float z) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->m_PhysicsSystem.GetBodyInterface().SetPosition(body->m_BodyId, + JPH::RVec3(x, y, z), + JPH::EActivation::Activate); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_velocity(std::int64_t space_handle, + std::int64_t body_handle, + float linear_x, + float linear_y, + float linear_z, + float angular_x, + float angular_y, + float angular_z) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->m_PhysicsSystem.GetBodyInterface().SetLinearAndAngularVelocity(body->m_BodyId, + JPH::Vec3(linear_x, linear_y, linear_z), + JPH::Vec3(angular_x, angular_y, angular_z)); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_type(std::int64_t space_handle, std::int64_t body_handle, int body_type) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->m_BodyType = body_type; + space->m_PhysicsSystem.GetBodyInterface().SetMotionType(body->m_BodyId, + MotionType(body_type), + body_type == BodyStatic ? JPH::EActivation::DontActivate : JPH::EActivation::Activate); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_damping(std::int64_t space_handle, + std::int64_t body_handle, + float linear, + float angular) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->m_LinearDamping = std::max(0.0F, linear); + body->m_AngularDamping = std::max(0.0F, angular); + JPH::BodyLockWrite body_lock(space->m_PhysicsSystem.GetBodyLockInterface(), body->m_BodyId); + if (body_lock.Succeeded()) { + JPH::MotionProperties* motion_properties = + body_lock.GetBody().GetMotionPropertiesUnchecked(); + if (motion_properties != nullptr) { + motion_properties->SetLinearDamping(body->m_LinearDamping); + motion_properties->SetAngularDamping(body->m_AngularDamping); + } + } + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_friction(std::int64_t space_handle, + std::int64_t body_handle, + float friction) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->m_Friction = friction; + space->m_PhysicsSystem.GetBodyInterface().SetFriction(body->m_BodyId, friction); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_restitution(std::int64_t space_handle, + std::int64_t body_handle, + float restitution) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->m_Restitution = restitution; + space->m_PhysicsSystem.GetBodyInterface().SetRestitution(body->m_BodyId, restitution); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_collision_filter(std::int64_t space_handle, + std::int64_t body_handle, + int group, + int mask) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->m_CollisionGroup = group; + body->m_CollisionMask = mask; + space->m_PhysicsSystem.GetBodyInterface().SetObjectLayer(body->m_BodyId, + ObjectLayer(group, mask)); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_sensor(std::int64_t space_handle, std::int64_t body_handle, int sensor) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->m_Sensor = sensor != 0; + space->m_PhysicsSystem.GetBodyInterface().SetIsSensor(body->m_BodyId, body->m_Sensor); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_continuous_collision(std::int64_t space_handle, + std::int64_t body_handle, + int enabled) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + body->m_ContinuousCollision = enabled != 0; + space->m_PhysicsSystem.GetBodyInterface().SetMotionQuality(body->m_BodyId, + body->m_ContinuousCollision + ? JPH::EMotionQuality::LinearCast + : JPH::EMotionQuality::Discrete); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_is_body_continuous_collision_enabled(std::int64_t space_handle, + std::int64_t body_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + return space->m_PhysicsSystem.GetBodyInterface().GetMotionQuality(body->m_BodyId) + == JPH::EMotionQuality::LinearCast + ? 1 + : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_activate_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->m_PhysicsSystem.GetBodyInterface().ActivateBody(body->m_BodyId); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_sleep_body(std::int64_t space_handle, std::int64_t body_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + space->m_PhysicsSystem.GetBodyInterface().DeactivateBody(body->m_BodyId); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_apply_body_impulse(std::int64_t space_handle, + std::int64_t body_handle, + float x, + float y, + float z, + int has_offset, + float offset_x, + float offset_y, + float offset_z, + int torque) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + JPH::BodyInterface& body_interface = space->m_PhysicsSystem.GetBodyInterface(); + JPH::Vec3 value(x, y, z); + if (torque != 0) { + body_interface.AddAngularImpulse(body->m_BodyId, value); + } else if (has_offset != 0) { + body_interface.AddImpulse(body->m_BodyId, + value, + JPH::RVec3(offset_x, offset_y, offset_z)); + } else { + body_interface.AddImpulse(body->m_BodyId, value); + } + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_apply_body_force(std::int64_t space_handle, + std::int64_t body_handle, + float x, + float y, + float z, + int has_offset, + float offset_x, + float offset_y, + float offset_z, + int torque) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + BodyState* body = space == nullptr + ? nullptr + : FindBody(*space, static_cast(body_handle)); + if (body == nullptr) { + return 0; + } + JPH::BodyInterface& body_interface = space->m_PhysicsSystem.GetBodyInterface(); + JPH::Vec3 value(x, y, z); + if (torque != 0) { + body_interface.AddTorque(body->m_BodyId, value, JPH::EActivation::Activate); + } else if (has_offset != 0) { + body_interface.AddForce(body->m_BodyId, + value, + JPH::RVec3(offset_x, offset_y, offset_z), + JPH::EActivation::Activate); + } else { + body_interface.AddForce(body->m_BodyId, value, JPH::EActivation::Activate); + } + return 1; +} + +} // extern "C" diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp b/impulse-jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp new file mode 100644 index 00000000..9dd75774 --- /dev/null +++ b/impulse-jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp @@ -0,0 +1,106 @@ +#include "internal/impulse_jolt_joints.h" +#include "internal/impulse_jolt_registry.h" + +#include +#include +#include + +#include +#include + +using namespace ImpulseJolt; + +extern "C" { + +IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_joint(std::int64_t space_handle, + int joint_type, + std::int64_t body_a_handle, + std::int64_t body_b_handle, + float anchor_ax, + float anchor_ay, + float anchor_az, + float anchor_bx, + float anchor_by, + float anchor_bz, + float axis_x, + float axis_y, + float axis_z, + float rest_length, + float stiffness, + float damping, + float lower_limit, + float upper_limit, + int motor_enabled, + float motor_target_velocity, + float motor_max_force) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr || body_a_handle == body_b_handle) { + return 0; + } + BodyState* body_a = FindBody(*space, static_cast(body_a_handle)); + BodyState* body_b = FindBody(*space, static_cast(body_b_handle)); + if (body_a == nullptr || body_b == nullptr) { + return 0; + } + + JPH::BodyID body_ids[] = {body_a->m_BodyId, body_b->m_BodyId}; + JPH::BodyLockMultiWrite body_locks(space->m_PhysicsSystem.GetBodyLockInterface(), + body_ids, + 2); + JPH::Body* locked_body_a = body_locks.GetBody(0); + JPH::Body* locked_body_b = body_locks.GetBody(1); + if (locked_body_a == nullptr || locked_body_b == nullptr) { + return 0; + } + + JPH::Ref Constraint = CreateJointConstraint(joint_type, + *locked_body_a, + *locked_body_b, + anchor_ax, + anchor_ay, + anchor_az, + anchor_bx, + anchor_by, + anchor_bz, + axis_x, + axis_y, + axis_z, + rest_length, + stiffness, + damping, + lower_limit, + upper_limit, + motor_enabled, + motor_target_velocity, + motor_max_force); + if (Constraint == nullptr) { + return 0; + } + body_locks.ReleaseLocks(); + + space->m_PhysicsSystem.AddConstraint(Constraint); + space->m_PhysicsSystem.GetBodyInterface().ActivateBody(body_a->m_BodyId); + space->m_PhysicsSystem.GetBodyInterface().ActivateBody(body_b->m_BodyId); + + const std::uint64_t joint_handle = g_NextJointHandle++; + JointState joint; + joint.m_Constraint = Constraint; + joint.m_BodyAHandle = static_cast(body_a_handle); + joint.m_BodyBHandle = static_cast(body_b_handle); + joint.m_JointType = joint_type; + space->m_Joints.emplace(joint_handle, joint); + return static_cast(joint_handle); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_remove_joint(std::int64_t space_handle, std::int64_t joint_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + space->RemoveJointHandle(static_cast(joint_handle)); + return 1; +} + +} // extern "C" diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_native.h b/impulse-jolt/src/main/cpp/abi/impulse_jolt_native.h new file mode 100644 index 00000000..cb45c1b5 --- /dev/null +++ b/impulse-jolt/src/main/cpp/abi/impulse_jolt_native.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#if defined(_WIN32) +#define IMPULSE_JOLT_EXPORT __declspec(dllexport) +#else +#define IMPULSE_JOLT_EXPORT __attribute__((visibility("default"))) +#endif + +namespace ImpulseJolt { + +inline constexpr int ShapeBox = 1; +inline constexpr int ShapeSphere = 2; +inline constexpr int ShapeCapsule = 3; +inline constexpr int ShapeCylinder = 4; +inline constexpr int ShapeCone = 5; +inline constexpr int ShapePlane = 6; + +inline constexpr int BodyStatic = 1; +inline constexpr int BodyDynamic = 2; +inline constexpr int BodyKinematic = 3; + +inline constexpr int JointFixed = 1; +inline constexpr int JointPoint = 2; +inline constexpr int JointHinge = 3; +inline constexpr int JointSlider = 4; +inline constexpr int JointSpring = 5; + +inline constexpr int AxisX = 1; +inline constexpr int AxisY = 2; +inline constexpr int AxisZ = 3; + +inline constexpr float MinShapeSize = 0.001F; +inline constexpr float MinAxisLengthSquared = 1.0e-6F; +inline constexpr std::uint32_t MaxBodies = 131072; +inline constexpr std::uint32_t MaxBodyPairs = 65536; +inline constexpr std::uint32_t MaxContactConstraints = 10240; +inline constexpr std::uint32_t DefaultCollisionGroup = 1; +inline constexpr int RayHitFloatCount = 8; +inline constexpr int ContactBodyHandleCount = 2; +inline constexpr int ContactFloatCount = 11; + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp b/impulse-jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp new file mode 100644 index 00000000..6da7c5ab --- /dev/null +++ b/impulse-jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp @@ -0,0 +1,130 @@ +#include "internal/impulse_jolt_query.h" +#include "internal/impulse_jolt_registry.h" + +#include +#include +#include +#include +#include + +#include +#include + +using namespace ImpulseJolt; + +extern "C" { + +IMPULSE_JOLT_EXPORT int impulse_jolt_raycast_closest(std::int64_t space_handle, + float from_x, + float from_y, + float from_z, + float to_x, + float to_y, + float to_z, + std::int64_t* body_handles, + float* hits) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr || body_handles == nullptr || hits == nullptr) { + return 0; + } + + JPH::Vec3 direction(to_x - from_x, to_y - from_y, to_z - from_z); + if (direction.LengthSq() <= 0.0F) { + return 0; + } + JPH::RRayCast ray(JPH::RVec3(from_x, from_y, from_z), direction); + JPH::RayCastResult hit; + if (!space->m_PhysicsSystem.GetNarrowPhaseQuery().CastRay(ray, hit)) { + return 0; + } + return WriteRayHit(*space, ray, hit, 0, body_handles, hits) ? 1 : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_raycast_all(std::int64_t space_handle, + float from_x, + float from_y, + float from_z, + float to_x, + float to_y, + float to_z, + int max_hits, + std::int64_t* body_handles, + float* hits) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr || max_hits <= 0 || body_handles == nullptr || hits == nullptr) { + return 0; + } + + JPH::Vec3 direction(to_x - from_x, to_y - from_y, to_z - from_z); + if (direction.LengthSq() <= 0.0F) { + return 0; + } + + JPH::RRayCast ray(JPH::RVec3(from_x, from_y, from_z), direction); + JPH::RayCastSettings settings; + JPH::ClosestHitPerBodyCollisionCollector collector; + space->m_PhysicsSystem.GetNarrowPhaseQuery().CastRay(ray, settings, collector); + collector.Sort(); + + int emitted = 0; + for (const JPH::RayCastResult& hit : collector.mHits) { + if (emitted >= max_hits) { + break; + } + if (WriteRayHit(*space, ray, hit, emitted, body_handles, hits)) { + emitted++; + } + } + return emitted; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_contacts(std::int64_t space_handle, + int max_contacts, + std::int64_t* body_handles, + float* contacts) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr || max_contacts <= 0 || body_handles == nullptr || contacts == nullptr) { + return 0; + } + + std::lock_guard contact_lock(space->m_ContactMutex); + int emitted = 0; + for (const ContactRecord& contact : space->m_Contacts) { + if (emitted >= max_contacts) { + break; + } + int body_offset = emitted * ContactBodyHandleCount; + body_handles[body_offset] = static_cast(contact.m_BodyAHandle); + body_handles[body_offset + 1] = static_cast(contact.m_BodyBHandle); + + int contact_offset = emitted * ContactFloatCount; + contacts[contact_offset] = contact.m_PointAX; + contacts[contact_offset + 1] = contact.m_PointAY; + contacts[contact_offset + 2] = contact.m_PointAZ; + contacts[contact_offset + 3] = contact.m_PointBX; + contacts[contact_offset + 4] = contact.m_PointBY; + contacts[contact_offset + 5] = contact.m_PointBZ; + contacts[contact_offset + 6] = contact.m_NormalBX; + contacts[contact_offset + 7] = contact.m_NormalBY; + contacts[contact_offset + 8] = contact.m_NormalBZ; + contacts[contact_offset + 9] = contact.m_Distance; + contacts[contact_offset + 10] = contact.m_Impulse; + emitted++; + } + return emitted; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_contact_count(std::int64_t space_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + std::lock_guard contact_lock(space->m_ContactMutex); + return static_cast(space->m_Contacts.size()); +} + +} // extern "C" diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp b/impulse-jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp new file mode 100644 index 00000000..df897a3e --- /dev/null +++ b/impulse-jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp @@ -0,0 +1,75 @@ +#include "internal/impulse_jolt_registry.h" + +#include +#include +#include +#include +#include + +using namespace ImpulseJolt; + +extern "C" { + +IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_space() { + EnsureJoltInitialized(); + std::lock_guard lock(g_RegistryMutex); + const std::uint64_t handle = g_NextSpaceHandle++; + g_Spaces.emplace(handle, std::make_unique()); + return static_cast(handle); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_destroy_space(std::int64_t space_handle) { + std::lock_guard lock(g_RegistryMutex); + return g_Spaces.erase(static_cast(space_handle)) > 0 ? 1 : 0; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_step(std::int64_t space_handle, float dt) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr || !std::isfinite(dt) || dt <= 0.0F) { + return 0; + } + const int collision_steps = std::max(1, static_cast(std::ceil(dt * 60.0F))); + space->m_PhysicsSystem.Update(dt, + collision_steps, + &space->m_TempAllocator, + &space->m_JobSystem); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_set_gravity(std::int64_t space_handle, float x, float y, float z) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr) { + return 0; + } + space->m_PhysicsSystem.SetGravity(JPH::Vec3(x, y, z)); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_get_gravity(std::int64_t space_handle, float* out) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + if (space == nullptr || out == nullptr) { + return 0; + } + JPH::Vec3 gravity = space->m_PhysicsSystem.GetGravity(); + out[0] = gravity.GetX(); + out[1] = gravity.GetY(); + out[2] = gravity.GetZ(); + return 1; +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_body_count(std::int64_t space_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + return space == nullptr ? 0 : static_cast(space->m_Bodies.size()); +} + +IMPULSE_JOLT_EXPORT int impulse_jolt_joint_count(std::int64_t space_handle) { + std::lock_guard lock(g_RegistryMutex); + Space* space = FindSpace(static_cast(space_handle)); + return space == nullptr ? 0 : static_cast(space->m_Joints.size()); +} + +} // extern "C" diff --git a/impulse-jolt/src/main/cpp/impulse_jolt.cpp b/impulse-jolt/src/main/cpp/impulse_jolt.cpp deleted file mode 100644 index 9f64e3c7..00000000 --- a/impulse-jolt/src/main/cpp/impulse_jolt.cpp +++ /dev/null @@ -1,1437 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_WIN32) -#define IMPULSE_JOLT_EXPORT __declspec(dllexport) -#else -#define IMPULSE_JOLT_EXPORT __attribute__((visibility("default"))) -#endif - -namespace { - -constexpr int SHAPE_BOX = 1; -constexpr int SHAPE_SPHERE = 2; -constexpr int SHAPE_CAPSULE = 3; -constexpr int SHAPE_CYLINDER = 4; -constexpr int SHAPE_CONE = 5; -constexpr int SHAPE_PLANE = 6; - -constexpr int BODY_STATIC = 1; -constexpr int BODY_DYNAMIC = 2; -constexpr int BODY_KINEMATIC = 3; - -constexpr int JOINT_FIXED = 1; -constexpr int JOINT_POINT = 2; -constexpr int JOINT_HINGE = 3; -constexpr int JOINT_SLIDER = 4; -constexpr int JOINT_SPRING = 5; - -constexpr int AXIS_X = 1; -constexpr int AXIS_Y = 2; -constexpr int AXIS_Z = 3; - -constexpr float MIN_SHAPE_SIZE = 0.001F; -constexpr float MIN_AXIS_LENGTH_SQUARED = 1.0e-6F; -constexpr std::uint32_t MAX_BODIES = 131072; -constexpr std::uint32_t MAX_BODY_PAIRS = 65536; -constexpr std::uint32_t MAX_CONTACT_CONSTRAINTS = 10240; -constexpr std::uint32_t DEFAULT_COLLISION_GROUP = 1; -constexpr int RAY_HIT_FLOAT_COUNT = 8; -constexpr int CONTACT_BODY_HANDLE_COUNT = 2; -constexpr int CONTACT_FLOAT_COUNT = 11; - -struct Space; - -struct ContactRecord { - JPH::SubShapeIDPair key; - std::uint64_t body_a_handle = 0; - std::uint64_t body_b_handle = 0; - float point_ax = 0.0F; - float point_ay = 0.0F; - float point_az = 0.0F; - float point_bx = 0.0F; - float point_by = 0.0F; - float point_bz = 0.0F; - float normal_bx = 0.0F; - float normal_by = 0.0F; - float normal_bz = 0.0F; - float distance = 0.0F; - float impulse = 0.0F; -}; - -class ImpulseContactListener final : public JPH::ContactListener { -public: - explicit ImpulseContactListener(Space* owner) - : owner(owner) { - } - - void OnContactAdded(const JPH::Body& body1, - const JPH::Body& body2, - const JPH::ContactManifold& manifold, - JPH::ContactSettings& settings) override; - - void OnContactPersisted(const JPH::Body& body1, - const JPH::Body& body2, - const JPH::ContactManifold& manifold, - JPH::ContactSettings& settings) override; - - void OnContactRemoved(const JPH::SubShapeIDPair& sub_shape_pair) override; - -private: - Space* owner; -}; - -struct BodyState { - JPH::BodyID body_id; - int shape_type = 0; - int body_type = 0; - bool sensor = false; - float mass = 0.0F; - float friction = 0.0F; - float restitution = 0.0F; - float linear_damping = 0.0F; - float angular_damping = 0.0F; - int collision_group = 0; - int collision_mask = 0; - bool continuous_collision = false; - float center_of_mass_offset_y = 0.0F; - bool has_box_half_extents = false; - float half_extent_x = 0.0F; - float half_extent_y = 0.0F; - float half_extent_z = 0.0F; - float radius = 0.0F; - float half_height = 0.0F; - int axis = AXIS_Y; -}; - -struct JointState { - JPH::Ref constraint; - std::uint64_t body_a_handle = 0; - std::uint64_t body_b_handle = 0; - int joint_type = 0; -}; - -struct Space { - JPH::BroadPhaseLayerInterfaceMask broad_phase_layer_interface; - JPH::ObjectVsBroadPhaseLayerFilterMask object_vs_broadphase_layer_filter; - JPH::ObjectLayerPairFilterMask object_layer_filter; - JPH::PhysicsSystem physics_system; - ImpulseContactListener contact_listener; - JPH::TempAllocatorImpl temp_allocator; - JPH::JobSystemThreadPool job_system; - std::unordered_map bodies; - std::unordered_map body_handles_by_jolt_id; - std::unordered_map joints; - std::mutex contact_mutex; - std::vector contacts; - - Space() - : broad_phase_layer_interface(1), - object_vs_broadphase_layer_filter(broad_phase_layer_interface), - contact_listener(this), - temp_allocator(10 * 1024 * 1024), - job_system(JPH::cMaxPhysicsJobs, - JPH::cMaxPhysicsBarriers, - std::max(1U, std::thread::hardware_concurrency())) { - broad_phase_layer_interface.ConfigureLayer( - JPH::BroadPhaseLayer(0), - JPH::ObjectLayerPairFilterMask::cMask, - 0); - physics_system.Init(MAX_BODIES, - 0, - MAX_BODY_PAIRS, - MAX_CONTACT_CONSTRAINTS, - broad_phase_layer_interface, - object_vs_broadphase_layer_filter, - object_layer_filter); - physics_system.SetContactListener(&contact_listener); - physics_system.SetGravity(JPH::Vec3(0.0F, -9.81F, 0.0F)); - } - - ~Space() { - for (auto& [_, joint] : joints) { - if (joint.constraint != nullptr) { - physics_system.RemoveConstraint(joint.constraint); - } - } - joints.clear(); - JPH::BodyInterface& body_interface = physics_system.GetBodyInterface(); - for (auto& [_, body] : bodies) { - if (!body.body_id.IsInvalid()) { - if (body_interface.IsAdded(body.body_id)) { - body_interface.RemoveBody(body.body_id); - } - body_interface.DestroyBody(body.body_id); - } - } - } - - void replace_contact_records(const JPH::Body& body1, - const JPH::Body& body2, - const JPH::ContactManifold& manifold); - - void erase_contact_records(const JPH::SubShapeIDPair& key); - - void erase_contact_records_for_body_handle(std::uint64_t body_handle); - - void erase_constraints_for_body_handle(std::uint64_t body_handle); - - bool remove_joint_handle(std::uint64_t joint_handle); -}; - -std::once_flag jolt_init_once; -std::mutex registry_mutex; -std::uint64_t next_space_handle = 1; -std::uint64_t next_body_handle = 1001; -std::uint64_t next_joint_handle = 2001; -std::unordered_map> spaces; - -void ensure_jolt_initialized() { - std::call_once(jolt_init_once, [] { - JPH::RegisterDefaultAllocator(); - if (JPH::Factory::sInstance == nullptr) { - JPH::Factory::sInstance = new JPH::Factory(); - } - JPH::RegisterTypes(); - }); -} - -float positive(float value) { - return std::max(value, MIN_SHAPE_SIZE); -} - -Space* find_space(std::uint64_t handle) { - auto iterator = spaces.find(handle); - return iterator == spaces.end() ? nullptr : iterator->second.get(); -} - -BodyState* find_body(Space& space, std::uint64_t handle) { - auto iterator = space.bodies.find(handle); - return iterator == space.bodies.end() ? nullptr : &iterator->second; -} - -std::uint64_t native_handle_for_body_id(const Space& space, const JPH::BodyID& body_id) { - auto iterator = space.body_handles_by_jolt_id.find(body_id.GetIndexAndSequenceNumber()); - return iterator == space.body_handles_by_jolt_id.end() ? 0 : iterator->second; -} - -JPH::EMotionType motion_type(int body_type) { - switch (body_type) { - case BODY_STATIC: - return JPH::EMotionType::Static; - case BODY_KINEMATIC: - return JPH::EMotionType::Kinematic; - default: - return JPH::EMotionType::Dynamic; - } -} - -JPH::ObjectLayer object_layer(int collision_group, int collision_mask) { - constexpr std::uint32_t mask_bits = JPH::ObjectLayerPairFilterMask::cMask; - std::uint32_t group = static_cast(collision_group) & mask_bits; - std::uint32_t mask = static_cast(collision_mask) & mask_bits; - if (group == 0) { - group = DEFAULT_COLLISION_GROUP; - } - if (mask == 0) { - mask = mask_bits; - } - return JPH::ObjectLayerPairFilterMask::sGetObjectLayer(group, mask); -} - -float center_of_mass_offset_y(int shape_type, - float half_extent_y, - float radius, - float half_height, - int axis) { - switch (shape_type) { - case SHAPE_BOX: - return half_extent_y; - case SHAPE_SPHERE: - return radius; - case SHAPE_CAPSULE: - return axis == AXIS_Y ? radius + half_height : radius; - case SHAPE_CYLINDER: - return axis == AXIS_Y ? half_height : radius; - case SHAPE_CONE: - return axis == AXIS_Y ? half_height : radius; - default: - return 0.0F; - } -} - -JPH::Quat axis_rotation(int axis) { - switch (axis) { - case AXIS_X: - return JPH::Quat::sRotation(JPH::Vec3::sAxisZ(), -0.5F * JPH::JPH_PI); - case AXIS_Z: - return JPH::Quat::sRotation(JPH::Vec3::sAxisX(), 0.5F * JPH::JPH_PI); - default: - return JPH::Quat::sIdentity(); - } -} - -JPH::ShapeRefC rotated_for_axis(JPH::ShapeRefC shape, int axis) { - if (axis == AXIS_Y || shape == nullptr) { - return shape; - } - return new JPH::RotatedTranslatedShape(JPH::Vec3::sZero(), axis_rotation(axis), shape); -} - -JPH::ShapeRefC create_shape(int shape_type, - float half_extent_x, - float half_extent_y, - float half_extent_z, - float radius, - float half_height, - int axis, - float ground_y) { - switch (shape_type) { - case SHAPE_BOX: - return new JPH::BoxShape(JPH::Vec3(positive(half_extent_x), - positive(half_extent_y), - positive(half_extent_z))); - case SHAPE_SPHERE: - return new JPH::SphereShape(positive(radius)); - case SHAPE_CAPSULE: - return rotated_for_axis( - new JPH::CapsuleShape(positive(half_height), positive(radius)), - axis); - case SHAPE_CYLINDER: - return rotated_for_axis( - new JPH::CylinderShape(positive(half_height), positive(radius)), - axis); - case SHAPE_CONE: { - JPH::TaperedCylinderShapeSettings settings(positive(half_height), - 0.0F, - positive(radius)); - JPH::Shape::ShapeResult result = settings.Create(); - if (result.HasError()) { - return nullptr; - } - return rotated_for_axis(result.Get(), axis); - } - case SHAPE_PLANE: - return new JPH::PlaneShape( - JPH::Plane::sFromPointAndNormal(JPH::Vec3(0.0F, ground_y, 0.0F), - JPH::Vec3::sAxisY())); - default: - return nullptr; - } -} - -float finite_or(float value, float fallback) { - return std::isfinite(value) ? value : fallback; -} - -float non_negative(float value) { - return std::max(0.0F, finite_or(value, 0.0F)); -} - -JPH::RVec3 local_point(float x, float y, float z) { - return JPH::RVec3(finite_or(x, 0.0F), finite_or(y, 0.0F), finite_or(z, 0.0F)); -} - -JPH::Vec3 normalized_axis(float x, float y, float z) { - x = finite_or(x, 0.0F); - y = finite_or(y, 1.0F); - z = finite_or(z, 0.0F); - const float length_squared = x * x + y * y + z * z; - if (!std::isfinite(length_squared) || length_squared <= MIN_AXIS_LENGTH_SQUARED) { - return JPH::Vec3::sAxisY(); - } - const float inverse_length = 1.0F / std::sqrt(length_squared); - return JPH::Vec3(x * inverse_length, y * inverse_length, z * inverse_length); -} - -JPH::Vec3 normal_for_axis(JPH::Vec3Arg axis) { - const JPH::Vec3 reference = std::fabs(axis.GetY()) < 0.9F - ? JPH::Vec3::sAxisY() - : JPH::Vec3::sAxisX(); - JPH::Vec3 normal = axis.Cross(reference); - if (normal.LengthSq() <= MIN_AXIS_LENGTH_SQUARED) { - normal = axis.Cross(JPH::Vec3::sAxisZ()); - } - return normal.LengthSq() <= MIN_AXIS_LENGTH_SQUARED - ? JPH::Vec3::sAxisX() - : normal.Normalized(); -} - -void configure_spring(JPH::SpringSettings& settings, float stiffness, float damping) { - const float clamped_stiffness = non_negative(stiffness); - if (clamped_stiffness <= 0.0F) { - return; - } - settings.mMode = JPH::ESpringMode::StiffnessAndDamping; - settings.mStiffness = clamped_stiffness; - settings.mDamping = non_negative(damping); -} - -JPH::Ref create_joint_constraint(int joint_type, - JPH::Body& body_a, - JPH::Body& body_b, - float anchor_ax, - float anchor_ay, - float anchor_az, - float anchor_bx, - float anchor_by, - float anchor_bz, - float axis_x, - float axis_y, - float axis_z, - float rest_length, - float stiffness, - float damping, - float lower_limit, - float upper_limit, - int motor_enabled, - float motor_target_velocity, - float motor_max_force) { - const JPH::RVec3 anchor_a = local_point(anchor_ax, anchor_ay, anchor_az); - const JPH::RVec3 anchor_b = local_point(anchor_bx, anchor_by, anchor_bz); - const JPH::Vec3 axis = normalized_axis(axis_x, axis_y, axis_z); - const JPH::Vec3 normal = normal_for_axis(axis); - const float ordered_lower = std::min(finite_or(lower_limit, 0.0F), - finite_or(upper_limit, 0.0F)); - const float ordered_upper = std::max(finite_or(lower_limit, 0.0F), - finite_or(upper_limit, 0.0F)); - const bool explicit_limits = ordered_lower < ordered_upper; - - switch (joint_type) { - case JOINT_FIXED: { - JPH::FixedConstraintSettings settings; - settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; - settings.mAutoDetectPoint = false; - settings.mPoint1 = anchor_a; - settings.mPoint2 = anchor_b; - return settings.Create(body_a, body_b); - } - case JOINT_POINT: { - JPH::PointConstraintSettings settings; - settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; - settings.mPoint1 = anchor_a; - settings.mPoint2 = anchor_b; - return settings.Create(body_a, body_b); - } - case JOINT_HINGE: { - JPH::HingeConstraintSettings settings; - settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; - settings.mPoint1 = anchor_a; - settings.mPoint2 = anchor_b; - settings.mHingeAxis1 = settings.mHingeAxis2 = axis; - settings.mNormalAxis1 = settings.mNormalAxis2 = normal; - if (explicit_limits) { - settings.mLimitsMin = std::clamp(ordered_lower, -JPH::JPH_PI, 0.0F); - settings.mLimitsMax = std::clamp(ordered_upper, 0.0F, JPH::JPH_PI); - } - JPH::HingeConstraint* constraint = - static_cast(settings.Create(body_a, body_b)); - if (constraint != nullptr && motor_enabled != 0) { - if (motor_max_force > 0.0F) { - constraint->GetMotorSettings().SetTorqueLimit(motor_max_force); - } - constraint->SetMotorState(JPH::EMotorState::Velocity); - constraint->SetTargetAngularVelocity(finite_or(motor_target_velocity, 0.0F)); - } - return constraint; - } - case JOINT_SLIDER: { - JPH::SliderConstraintSettings settings; - settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; - settings.mAutoDetectPoint = false; - settings.mPoint1 = anchor_a; - settings.mPoint2 = anchor_b; - settings.mSliderAxis1 = settings.mSliderAxis2 = axis; - settings.mNormalAxis1 = settings.mNormalAxis2 = normal; - if (explicit_limits) { - settings.mLimitsMin = ordered_lower; - settings.mLimitsMax = ordered_upper; - } - JPH::SliderConstraint* constraint = - static_cast(settings.Create(body_a, body_b)); - if (constraint != nullptr && motor_enabled != 0) { - if (motor_max_force > 0.0F) { - constraint->GetMotorSettings().SetForceLimit(motor_max_force); - } - constraint->SetMotorState(JPH::EMotorState::Velocity); - constraint->SetTargetVelocity(finite_or(motor_target_velocity, 0.0F)); - } - return constraint; - } - case JOINT_SPRING: { - JPH::DistanceConstraintSettings settings; - settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; - settings.mPoint1 = anchor_a; - settings.mPoint2 = anchor_b; - const float rest = non_negative(rest_length); - if (rest > 0.0F) { - settings.mMinDistance = rest; - settings.mMaxDistance = rest; - } - configure_spring(settings.mLimitsSpringSettings, stiffness, damping); - return settings.Create(body_a, body_b); - } - default: - return nullptr; - } -} - -void write_snapshot(Space& space, const BodyState& body, float* floats, int* ints) { - JPH::BodyInterface& body_interface = space.physics_system.GetBodyInterface(); - JPH::RVec3 position = JPH::RVec3::sZero(); - JPH::Quat rotation = JPH::Quat::sIdentity(); - JPH::Vec3 linear_velocity = JPH::Vec3::sZero(); - JPH::Vec3 angular_velocity = JPH::Vec3::sZero(); - float linear_damping = body.linear_damping; - float angular_damping = body.angular_damping; - - body_interface.GetPositionAndRotation(body.body_id, position, rotation); - body_interface.GetLinearAndAngularVelocity(body.body_id, linear_velocity, angular_velocity); - JPH::BodyLockRead lock(space.physics_system.GetBodyLockInterface(), body.body_id); - if (lock.Succeeded()) { - const JPH::MotionProperties* motion_properties = - lock.GetBody().GetMotionPropertiesUnchecked(); - if (motion_properties != nullptr) { - linear_damping = motion_properties->GetLinearDamping(); - angular_damping = motion_properties->GetAngularDamping(); - } - } - - floats[0] = static_cast(position.GetX()); - floats[1] = static_cast(position.GetY()); - floats[2] = static_cast(position.GetZ()); - floats[3] = rotation.GetX(); - floats[4] = rotation.GetY(); - floats[5] = rotation.GetZ(); - floats[6] = rotation.GetW(); - floats[7] = linear_velocity.GetX(); - floats[8] = linear_velocity.GetY(); - floats[9] = linear_velocity.GetZ(); - floats[10] = angular_velocity.GetX(); - floats[11] = angular_velocity.GetY(); - floats[12] = angular_velocity.GetZ(); - floats[13] = body.mass; - floats[14] = body_interface.GetFriction(body.body_id); - floats[15] = body_interface.GetRestitution(body.body_id); - floats[16] = linear_damping; - floats[17] = angular_damping; - floats[18] = body.center_of_mass_offset_y; - floats[19] = body.half_extent_x; - floats[20] = body.half_extent_y; - floats[21] = body.half_extent_z; - floats[22] = body.radius; - floats[23] = body.half_height; - - ints[0] = body.shape_type; - ints[1] = body.body_type; - ints[2] = body_interface.IsActive(body.body_id) ? 0 : 1; - ints[3] = body_interface.IsSensor(body.body_id) ? 1 : 0; - ints[4] = body.collision_group; - ints[5] = body.collision_mask; - ints[6] = body_interface.GetMotionQuality(body.body_id) - == JPH::EMotionQuality::LinearCast - ? 1 - : 0; - ints[7] = body.has_box_half_extents ? 1 : 0; - ints[8] = body.axis; -} - -void Space::replace_contact_records(const JPH::Body& body1, - const JPH::Body& body2, - const JPH::ContactManifold& manifold) { - std::uint64_t body_a_handle = native_handle_for_body_id(*this, body1.GetID()); - std::uint64_t body_b_handle = native_handle_for_body_id(*this, body2.GetID()); - if (body_a_handle == 0 || body_b_handle == 0) { - return; - } - - JPH::SubShapeIDPair key(body1.GetID(), - manifold.mSubShapeID1, - body2.GetID(), - manifold.mSubShapeID2); - std::lock_guard lock(contact_mutex); - contacts.erase(std::remove_if(contacts.begin(), - contacts.end(), - [&key](const ContactRecord& record) { - return record.key == key; - }), - contacts.end()); - - for (JPH::uint index = 0; index < manifold.mRelativeContactPointsOn1.size(); index++) { - JPH::RVec3 point_a = manifold.GetWorldSpaceContactPointOn1(index); - JPH::RVec3 point_b = manifold.GetWorldSpaceContactPointOn2(index); - ContactRecord record; - record.key = key; - record.body_a_handle = body_a_handle; - record.body_b_handle = body_b_handle; - record.point_ax = static_cast(point_a.GetX()); - record.point_ay = static_cast(point_a.GetY()); - record.point_az = static_cast(point_a.GetZ()); - record.point_bx = static_cast(point_b.GetX()); - record.point_by = static_cast(point_b.GetY()); - record.point_bz = static_cast(point_b.GetZ()); - record.normal_bx = manifold.mWorldSpaceNormal.GetX(); - record.normal_by = manifold.mWorldSpaceNormal.GetY(); - record.normal_bz = manifold.mWorldSpaceNormal.GetZ(); - record.distance = -manifold.mPenetrationDepth; - record.impulse = 0.0F; - contacts.push_back(record); - } -} - -void Space::erase_contact_records(const JPH::SubShapeIDPair& key) { - std::lock_guard lock(contact_mutex); - contacts.erase(std::remove_if(contacts.begin(), - contacts.end(), - [&key](const ContactRecord& record) { - return record.key == key; - }), - contacts.end()); -} - -void Space::erase_contact_records_for_body_handle(std::uint64_t body_handle) { - std::lock_guard lock(contact_mutex); - contacts.erase(std::remove_if(contacts.begin(), - contacts.end(), - [body_handle](const ContactRecord& record) { - return record.body_a_handle == body_handle - || record.body_b_handle == body_handle; - }), - contacts.end()); -} - -void Space::erase_constraints_for_body_handle(std::uint64_t body_handle) { - for (auto iterator = joints.begin(); iterator != joints.end();) { - JointState& joint = iterator->second; - if (joint.body_a_handle != body_handle && joint.body_b_handle != body_handle) { - ++iterator; - continue; - } - if (joint.constraint != nullptr) { - physics_system.RemoveConstraint(joint.constraint); - } - iterator = joints.erase(iterator); - } -} - -bool Space::remove_joint_handle(std::uint64_t joint_handle) { - auto iterator = joints.find(joint_handle); - if (iterator == joints.end()) { - return false; - } - if (iterator->second.constraint != nullptr) { - physics_system.RemoveConstraint(iterator->second.constraint); - } - joints.erase(iterator); - return true; -} - -void ImpulseContactListener::OnContactAdded(const JPH::Body& body1, - const JPH::Body& body2, - const JPH::ContactManifold& manifold, - JPH::ContactSettings& settings) { - (void) settings; - if (owner != nullptr) { - owner->replace_contact_records(body1, body2, manifold); - } -} - -void ImpulseContactListener::OnContactPersisted(const JPH::Body& body1, - const JPH::Body& body2, - const JPH::ContactManifold& manifold, - JPH::ContactSettings& settings) { - (void) settings; - if (owner != nullptr) { - owner->replace_contact_records(body1, body2, manifold); - } -} - -void ImpulseContactListener::OnContactRemoved(const JPH::SubShapeIDPair& sub_shape_pair) { - if (owner != nullptr) { - owner->erase_contact_records(sub_shape_pair); - } -} - -bool write_ray_hit(Space& space, - const JPH::RRayCast& ray, - const JPH::RayCastResult& hit, - int index, - std::int64_t* body_handles, - float* hits) { - std::uint64_t body_handle = native_handle_for_body_id(space, hit.mBodyID); - if (body_handle == 0) { - return false; - } - - JPH::RVec3 point = ray.GetPointOnRay(hit.mFraction); - JPH::Vec3 normal = JPH::Vec3::sZero(); - JPH::BodyLockRead lock(space.physics_system.GetBodyLockInterface(), hit.mBodyID); - if (lock.Succeeded()) { - normal = lock.GetBody().GetWorldSpaceSurfaceNormal(hit.mSubShapeID2, point); - } - - body_handles[index] = static_cast(body_handle); - int offset = index * RAY_HIT_FLOAT_COUNT; - hits[offset] = static_cast(point.GetX()); - hits[offset + 1] = static_cast(point.GetY()); - hits[offset + 2] = static_cast(point.GetZ()); - hits[offset + 3] = normal.GetX(); - hits[offset + 4] = normal.GetY(); - hits[offset + 5] = normal.GetZ(); - hits[offset + 6] = hit.mFraction; - hits[offset + 7] = ray.mDirection.Length() * hit.mFraction; - return true; -} - -} // namespace - -extern "C" { - -IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_space() { - ensure_jolt_initialized(); - std::lock_guard lock(registry_mutex); - const std::uint64_t handle = next_space_handle++; - spaces.emplace(handle, std::make_unique()); - return static_cast(handle); -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_destroy_space(std::int64_t space_handle) { - std::lock_guard lock(registry_mutex); - return spaces.erase(static_cast(space_handle)) > 0 ? 1 : 0; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_step(std::int64_t space_handle, float dt) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr || !std::isfinite(dt) || dt <= 0.0F) { - return 0; - } - const int collision_steps = std::max(1, static_cast(std::ceil(dt * 60.0F))); - space->physics_system.Update(dt, - collision_steps, - &space->temp_allocator, - &space->job_system); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_gravity(std::int64_t space_handle, float x, float y, float z) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr) { - return 0; - } - space->physics_system.SetGravity(JPH::Vec3(x, y, z)); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_get_gravity(std::int64_t space_handle, float* out) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr || out == nullptr) { - return 0; - } - JPH::Vec3 gravity = space->physics_system.GetGravity(); - out[0] = gravity.GetX(); - out[1] = gravity.GetY(); - out[2] = gravity.GetZ(); - return 1; -} - -IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_body(std::int64_t space_handle, - int shape_type, - float half_extent_x, - float half_extent_y, - float half_extent_z, - float radius, - float half_height, - int axis, - float ground_y, - float mass, - int body_type, - float position_x, - float position_y, - float position_z, - float rotation_x, - float rotation_y, - float rotation_z, - float rotation_w) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr) { - return 0; - } - - JPH::ShapeRefC shape = create_shape(shape_type, - half_extent_x, - half_extent_y, - half_extent_z, - radius, - half_height, - axis, - ground_y); - if (shape == nullptr) { - return 0; - } - - BodyState body; - body.shape_type = shape_type; - body.body_type = body_type; - body.mass = mass; - body.center_of_mass_offset_y = center_of_mass_offset_y(shape_type, - half_extent_y, - radius, - half_height, - axis); - body.has_box_half_extents = shape_type == SHAPE_BOX - && half_extent_x > 0.0F - && half_extent_y > 0.0F - && half_extent_z > 0.0F; - body.half_extent_x = half_extent_x; - body.half_extent_y = half_extent_y; - body.half_extent_z = half_extent_z; - body.radius = radius; - body.half_height = half_height; - body.axis = axis; - - JPH::BodyCreationSettings settings(shape, - JPH::RVec3(position_x, position_y, position_z), - JPH::Quat(rotation_x, rotation_y, rotation_z, rotation_w), - motion_type(body_type), - object_layer(body.collision_group, body.collision_mask)); - settings.mFriction = body.friction; - settings.mRestitution = body.restitution; - settings.mLinearDamping = body.linear_damping; - settings.mAngularDamping = body.angular_damping; - settings.mAllowDynamicOrKinematic = true; - settings.mIsSensor = body.sensor; - if (mass > 0.0F && body_type != BODY_STATIC) { - settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia; - settings.mMassPropertiesOverride.mMass = mass; - } - - JPH::BodyID body_id = - space->physics_system.GetBodyInterface().CreateAndAddBody(settings, - body_type == BODY_STATIC - ? JPH::EActivation::DontActivate - : JPH::EActivation::Activate); - if (body_id.IsInvalid()) { - return 0; - } - body.body_id = body_id; - - const std::uint64_t body_handle = next_body_handle++; - space->bodies.emplace(body_handle, body); - space->body_handles_by_jolt_id.emplace(body_id.GetIndexAndSequenceNumber(), body_handle); - return static_cast(body_handle); -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_remove_body(std::int64_t space_handle, std::int64_t body_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr) { - return 0; - } - auto iterator = space->bodies.find(static_cast(body_handle)); - if (iterator == space->bodies.end()) { - return 1; - } - space->erase_constraints_for_body_handle(static_cast(body_handle)); - JPH::BodyInterface& body_interface = space->physics_system.GetBodyInterface(); - if (body_interface.IsAdded(iterator->second.body_id)) { - body_interface.RemoveBody(iterator->second.body_id); - } - body_interface.DestroyBody(iterator->second.body_id); - space->body_handles_by_jolt_id.erase(iterator->second.body_id.GetIndexAndSequenceNumber()); - space->erase_contact_records_for_body_handle(static_cast(body_handle)); - space->bodies.erase(iterator); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_contains_body(std::int64_t space_handle, std::int64_t body_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - return space != nullptr - && find_body(*space, static_cast(body_handle)) != nullptr - ? 1 - : 0; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_body_snapshot(std::int64_t space_handle, - std::int64_t body_handle, - float* floats, - int* ints) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr || floats == nullptr || ints == nullptr) { - return 0; - } - BodyState* body = find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - write_snapshot(*space, *body, floats, ints); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_transform(std::int64_t space_handle, - std::int64_t body_handle, - float position_x, - float position_y, - float position_z, - float rotation_x, - float rotation_y, - float rotation_z, - float rotation_w) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - space->physics_system.GetBodyInterface().SetPositionAndRotation(body->body_id, - JPH::RVec3(position_x, position_y, position_z), - JPH::Quat(rotation_x, rotation_y, rotation_z, rotation_w), - JPH::EActivation::Activate); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_position(std::int64_t space_handle, - std::int64_t body_handle, - float x, - float y, - float z) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - space->physics_system.GetBodyInterface().SetPosition(body->body_id, - JPH::RVec3(x, y, z), - JPH::EActivation::Activate); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_velocity(std::int64_t space_handle, - std::int64_t body_handle, - float linear_x, - float linear_y, - float linear_z, - float angular_x, - float angular_y, - float angular_z) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - space->physics_system.GetBodyInterface().SetLinearAndAngularVelocity(body->body_id, - JPH::Vec3(linear_x, linear_y, linear_z), - JPH::Vec3(angular_x, angular_y, angular_z)); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_type(std::int64_t space_handle, std::int64_t body_handle, int body_type) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - body->body_type = body_type; - space->physics_system.GetBodyInterface().SetMotionType(body->body_id, - motion_type(body_type), - body_type == BODY_STATIC ? JPH::EActivation::DontActivate : JPH::EActivation::Activate); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_damping(std::int64_t space_handle, - std::int64_t body_handle, - float linear, - float angular) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - body->linear_damping = std::max(0.0F, linear); - body->angular_damping = std::max(0.0F, angular); - JPH::BodyLockWrite body_lock(space->physics_system.GetBodyLockInterface(), body->body_id); - if (body_lock.Succeeded()) { - JPH::MotionProperties* motion_properties = - body_lock.GetBody().GetMotionPropertiesUnchecked(); - if (motion_properties != nullptr) { - motion_properties->SetLinearDamping(body->linear_damping); - motion_properties->SetAngularDamping(body->angular_damping); - } - } - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_friction(std::int64_t space_handle, - std::int64_t body_handle, - float friction) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - body->friction = friction; - space->physics_system.GetBodyInterface().SetFriction(body->body_id, friction); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_restitution(std::int64_t space_handle, - std::int64_t body_handle, - float restitution) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - body->restitution = restitution; - space->physics_system.GetBodyInterface().SetRestitution(body->body_id, restitution); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_collision_filter(std::int64_t space_handle, - std::int64_t body_handle, - int group, - int mask) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - body->collision_group = group; - body->collision_mask = mask; - space->physics_system.GetBodyInterface().SetObjectLayer(body->body_id, - object_layer(group, mask)); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_sensor(std::int64_t space_handle, std::int64_t body_handle, int sensor) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - body->sensor = sensor != 0; - space->physics_system.GetBodyInterface().SetIsSensor(body->body_id, body->sensor); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_set_body_continuous_collision(std::int64_t space_handle, - std::int64_t body_handle, - int enabled) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - body->continuous_collision = enabled != 0; - space->physics_system.GetBodyInterface().SetMotionQuality(body->body_id, - body->continuous_collision - ? JPH::EMotionQuality::LinearCast - : JPH::EMotionQuality::Discrete); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_is_body_continuous_collision_enabled(std::int64_t space_handle, - std::int64_t body_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - return space->physics_system.GetBodyInterface().GetMotionQuality(body->body_id) - == JPH::EMotionQuality::LinearCast - ? 1 - : 0; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_activate_body(std::int64_t space_handle, std::int64_t body_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - space->physics_system.GetBodyInterface().ActivateBody(body->body_id); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_sleep_body(std::int64_t space_handle, std::int64_t body_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - space->physics_system.GetBodyInterface().DeactivateBody(body->body_id); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_apply_body_impulse(std::int64_t space_handle, - std::int64_t body_handle, - float x, - float y, - float z, - int has_offset, - float offset_x, - float offset_y, - float offset_z, - int torque) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - JPH::BodyInterface& body_interface = space->physics_system.GetBodyInterface(); - JPH::Vec3 value(x, y, z); - if (torque != 0) { - body_interface.AddAngularImpulse(body->body_id, value); - } else if (has_offset != 0) { - body_interface.AddImpulse(body->body_id, - value, - JPH::RVec3(offset_x, offset_y, offset_z)); - } else { - body_interface.AddImpulse(body->body_id, value); - } - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_apply_body_force(std::int64_t space_handle, - std::int64_t body_handle, - float x, - float y, - float z, - int has_offset, - float offset_x, - float offset_y, - float offset_z, - int torque) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - BodyState* body = space == nullptr - ? nullptr - : find_body(*space, static_cast(body_handle)); - if (body == nullptr) { - return 0; - } - JPH::BodyInterface& body_interface = space->physics_system.GetBodyInterface(); - JPH::Vec3 value(x, y, z); - if (torque != 0) { - body_interface.AddTorque(body->body_id, value, JPH::EActivation::Activate); - } else if (has_offset != 0) { - body_interface.AddForce(body->body_id, - value, - JPH::RVec3(offset_x, offset_y, offset_z), - JPH::EActivation::Activate); - } else { - body_interface.AddForce(body->body_id, value, JPH::EActivation::Activate); - } - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_raycast_closest(std::int64_t space_handle, - float from_x, - float from_y, - float from_z, - float to_x, - float to_y, - float to_z, - std::int64_t* body_handles, - float* hits) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr || body_handles == nullptr || hits == nullptr) { - return 0; - } - - JPH::Vec3 direction(to_x - from_x, to_y - from_y, to_z - from_z); - if (direction.LengthSq() <= 0.0F) { - return 0; - } - JPH::RRayCast ray(JPH::RVec3(from_x, from_y, from_z), direction); - JPH::RayCastResult hit; - if (!space->physics_system.GetNarrowPhaseQuery().CastRay(ray, hit)) { - return 0; - } - return write_ray_hit(*space, ray, hit, 0, body_handles, hits) ? 1 : 0; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_raycast_all(std::int64_t space_handle, - float from_x, - float from_y, - float from_z, - float to_x, - float to_y, - float to_z, - int max_hits, - std::int64_t* body_handles, - float* hits) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr || max_hits <= 0 || body_handles == nullptr || hits == nullptr) { - return 0; - } - - JPH::Vec3 direction(to_x - from_x, to_y - from_y, to_z - from_z); - if (direction.LengthSq() <= 0.0F) { - return 0; - } - - JPH::RRayCast ray(JPH::RVec3(from_x, from_y, from_z), direction); - JPH::RayCastSettings settings; - JPH::ClosestHitPerBodyCollisionCollector collector; - space->physics_system.GetNarrowPhaseQuery().CastRay(ray, settings, collector); - collector.Sort(); - - int emitted = 0; - for (const JPH::RayCastResult& hit : collector.mHits) { - if (emitted >= max_hits) { - break; - } - if (write_ray_hit(*space, ray, hit, emitted, body_handles, hits)) { - emitted++; - } - } - return emitted; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_contacts(std::int64_t space_handle, - int max_contacts, - std::int64_t* body_handles, - float* contacts) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr || max_contacts <= 0 || body_handles == nullptr || contacts == nullptr) { - return 0; - } - - std::lock_guard contact_lock(space->contact_mutex); - int emitted = 0; - for (const ContactRecord& contact : space->contacts) { - if (emitted >= max_contacts) { - break; - } - int body_offset = emitted * CONTACT_BODY_HANDLE_COUNT; - body_handles[body_offset] = static_cast(contact.body_a_handle); - body_handles[body_offset + 1] = static_cast(contact.body_b_handle); - - int contact_offset = emitted * CONTACT_FLOAT_COUNT; - contacts[contact_offset] = contact.point_ax; - contacts[contact_offset + 1] = contact.point_ay; - contacts[contact_offset + 2] = contact.point_az; - contacts[contact_offset + 3] = contact.point_bx; - contacts[contact_offset + 4] = contact.point_by; - contacts[contact_offset + 5] = contact.point_bz; - contacts[contact_offset + 6] = contact.normal_bx; - contacts[contact_offset + 7] = contact.normal_by; - contacts[contact_offset + 8] = contact.normal_bz; - contacts[contact_offset + 9] = contact.distance; - contacts[contact_offset + 10] = contact.impulse; - emitted++; - } - return emitted; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_contact_count(std::int64_t space_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr) { - return 0; - } - std::lock_guard contact_lock(space->contact_mutex); - return static_cast(space->contacts.size()); -} - -IMPULSE_JOLT_EXPORT std::int64_t impulse_jolt_create_joint(std::int64_t space_handle, - int joint_type, - std::int64_t body_a_handle, - std::int64_t body_b_handle, - float anchor_ax, - float anchor_ay, - float anchor_az, - float anchor_bx, - float anchor_by, - float anchor_bz, - float axis_x, - float axis_y, - float axis_z, - float rest_length, - float stiffness, - float damping, - float lower_limit, - float upper_limit, - int motor_enabled, - float motor_target_velocity, - float motor_max_force) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr || body_a_handle == body_b_handle) { - return 0; - } - BodyState* body_a = find_body(*space, static_cast(body_a_handle)); - BodyState* body_b = find_body(*space, static_cast(body_b_handle)); - if (body_a == nullptr || body_b == nullptr) { - return 0; - } - - JPH::BodyID body_ids[] = {body_a->body_id, body_b->body_id}; - JPH::BodyLockMultiWrite body_locks(space->physics_system.GetBodyLockInterface(), - body_ids, - 2); - JPH::Body* locked_body_a = body_locks.GetBody(0); - JPH::Body* locked_body_b = body_locks.GetBody(1); - if (locked_body_a == nullptr || locked_body_b == nullptr) { - return 0; - } - - JPH::Ref constraint = create_joint_constraint(joint_type, - *locked_body_a, - *locked_body_b, - anchor_ax, - anchor_ay, - anchor_az, - anchor_bx, - anchor_by, - anchor_bz, - axis_x, - axis_y, - axis_z, - rest_length, - stiffness, - damping, - lower_limit, - upper_limit, - motor_enabled, - motor_target_velocity, - motor_max_force); - if (constraint == nullptr) { - return 0; - } - body_locks.ReleaseLocks(); - - space->physics_system.AddConstraint(constraint); - space->physics_system.GetBodyInterface().ActivateBody(body_a->body_id); - space->physics_system.GetBodyInterface().ActivateBody(body_b->body_id); - - const std::uint64_t joint_handle = next_joint_handle++; - JointState joint; - joint.constraint = constraint; - joint.body_a_handle = static_cast(body_a_handle); - joint.body_b_handle = static_cast(body_b_handle); - joint.joint_type = joint_type; - space->joints.emplace(joint_handle, joint); - return static_cast(joint_handle); -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_remove_joint(std::int64_t space_handle, std::int64_t joint_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - if (space == nullptr) { - return 0; - } - space->remove_joint_handle(static_cast(joint_handle)); - return 1; -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_body_count(std::int64_t space_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - return space == nullptr ? 0 : static_cast(space->bodies.size()); -} - -IMPULSE_JOLT_EXPORT int impulse_jolt_joint_count(std::int64_t space_handle) { - std::lock_guard lock(registry_mutex); - Space* space = find_space(static_cast(space_handle)); - return space == nullptr ? 0 : static_cast(space->joints.size()); -} - -} // extern "C" diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.cpp b/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.cpp new file mode 100644 index 00000000..a1f2457c --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.cpp @@ -0,0 +1,171 @@ +#include "internal/impulse_jolt_joints.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ImpulseJolt { + +float FiniteOr(float value, float fallback) { + return std::isfinite(value) ? value : fallback; +} + +float NonNegative(float value) { + return std::max(0.0F, FiniteOr(value, 0.0F)); +} + +JPH::RVec3 LocalPoint(float x, float y, float z) { + return JPH::RVec3(FiniteOr(x, 0.0F), FiniteOr(y, 0.0F), FiniteOr(z, 0.0F)); +} + +JPH::Vec3 NormalizedAxis(float x, float y, float z) { + x = FiniteOr(x, 0.0F); + y = FiniteOr(y, 1.0F); + z = FiniteOr(z, 0.0F); + const float lengthSquared = x * x + y * y + z * z; + if (!std::isfinite(lengthSquared) || lengthSquared <= MinAxisLengthSquared) { + return JPH::Vec3::sAxisY(); + } + const float inverseLength = 1.0F / std::sqrt(lengthSquared); + return JPH::Vec3(x * inverseLength, y * inverseLength, z * inverseLength); +} + +JPH::Vec3 NormalForAxis(JPH::Vec3Arg axis) { + const JPH::Vec3 reference = std::fabs(axis.GetY()) < 0.9F + ? JPH::Vec3::sAxisY() + : JPH::Vec3::sAxisX(); + JPH::Vec3 normal = axis.Cross(reference); + if (normal.LengthSq() <= MinAxisLengthSquared) { + normal = axis.Cross(JPH::Vec3::sAxisZ()); + } + return normal.LengthSq() <= MinAxisLengthSquared + ? JPH::Vec3::sAxisX() + : normal.Normalized(); +} + +void ConfigureSpring(JPH::SpringSettings& settings, float stiffness, float damping) { + const float clampedStiffness = NonNegative(stiffness); + if (clampedStiffness <= 0.0F) { + return; + } + settings.mMode = JPH::ESpringMode::StiffnessAndDamping; + settings.mStiffness = clampedStiffness; + settings.mDamping = NonNegative(damping); +} + +JPH::Ref CreateJointConstraint(int jointType, + JPH::Body& bodyA, + JPH::Body& bodyB, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisXValue, + float axisYValue, + float axisZValue, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + int motorEnabled, + float motorTargetVelocity, + float motorMaxForce) { + const JPH::RVec3 anchorA = LocalPoint(anchorAX, anchorAY, anchorAZ); + const JPH::RVec3 anchorB = LocalPoint(anchorBX, anchorBY, anchorBZ); + const JPH::Vec3 axis = NormalizedAxis(axisXValue, axisYValue, axisZValue); + const JPH::Vec3 normal = NormalForAxis(axis); + const float orderedLower = std::min(FiniteOr(lowerLimit, 0.0F), + FiniteOr(upperLimit, 0.0F)); + const float orderedUpper = std::max(FiniteOr(lowerLimit, 0.0F), + FiniteOr(upperLimit, 0.0F)); + const bool explicitLimits = orderedLower < orderedUpper; + + switch (jointType) { + case JointFixed: { + JPH::FixedConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mAutoDetectPoint = false; + settings.mPoint1 = anchorA; + settings.mPoint2 = anchorB; + return settings.Create(bodyA, bodyB); + } + case JointPoint: { + JPH::PointConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = anchorA; + settings.mPoint2 = anchorB; + return settings.Create(bodyA, bodyB); + } + case JointHinge: { + JPH::HingeConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = anchorA; + settings.mPoint2 = anchorB; + settings.mHingeAxis1 = settings.mHingeAxis2 = axis; + settings.mNormalAxis1 = settings.mNormalAxis2 = normal; + if (explicitLimits) { + settings.mLimitsMin = std::clamp(orderedLower, -JPH::JPH_PI, 0.0F); + settings.mLimitsMax = std::clamp(orderedUpper, 0.0F, JPH::JPH_PI); + } + JPH::HingeConstraint* constraint = + static_cast(settings.Create(bodyA, bodyB)); + if (constraint != nullptr && motorEnabled != 0) { + if (motorMaxForce > 0.0F) { + constraint->GetMotorSettings().SetTorqueLimit(motorMaxForce); + } + constraint->SetMotorState(JPH::EMotorState::Velocity); + constraint->SetTargetAngularVelocity(FiniteOr(motorTargetVelocity, 0.0F)); + } + return constraint; + } + case JointSlider: { + JPH::SliderConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mAutoDetectPoint = false; + settings.mPoint1 = anchorA; + settings.mPoint2 = anchorB; + settings.mSliderAxis1 = settings.mSliderAxis2 = axis; + settings.mNormalAxis1 = settings.mNormalAxis2 = normal; + if (explicitLimits) { + settings.mLimitsMin = orderedLower; + settings.mLimitsMax = orderedUpper; + } + JPH::SliderConstraint* constraint = + static_cast(settings.Create(bodyA, bodyB)); + if (constraint != nullptr && motorEnabled != 0) { + if (motorMaxForce > 0.0F) { + constraint->GetMotorSettings().SetForceLimit(motorMaxForce); + } + constraint->SetMotorState(JPH::EMotorState::Velocity); + constraint->SetTargetVelocity(FiniteOr(motorTargetVelocity, 0.0F)); + } + return constraint; + } + case JointSpring: { + JPH::DistanceConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = anchorA; + settings.mPoint2 = anchorB; + const float rest = NonNegative(restLength); + if (rest > 0.0F) { + settings.mMinDistance = rest; + settings.mMaxDistance = rest; + } + ConfigureSpring(settings.mLimitsSpringSettings, stiffness, damping); + return settings.Create(bodyA, bodyB); + } + default: + return nullptr; + } +} + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.h b/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.h new file mode 100644 index 00000000..32509a72 --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.h @@ -0,0 +1,31 @@ +#pragma once + +#include "internal/impulse_jolt_space.h" + +#include +#include + +namespace ImpulseJolt { + +JPH::Ref CreateJointConstraint(int jointType, + JPH::Body& bodyA, + JPH::Body& bodyB, + float anchorAX, + float anchorAY, + float anchorAZ, + float anchorBX, + float anchorBY, + float anchorBZ, + float axisXValue, + float axisYValue, + float axisZValue, + float restLength, + float stiffness, + float damping, + float lowerLimit, + float upperLimit, + int motorEnabled, + float motorTargetVelocity, + float motorMaxForce); + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.cpp b/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.cpp new file mode 100644 index 00000000..fd513194 --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.cpp @@ -0,0 +1,43 @@ +#include "internal/impulse_jolt_query.h" +#include "internal/impulse_jolt_registry.h" + +#include +#include +#include + +#include + +namespace ImpulseJolt { + +bool WriteRayHit(Space& targetSpace, + const JPH::RRayCast& ray, + const JPH::RayCastResult& hit, + int index, + std::int64_t* bodyHandles, + float* hits) { + std::uint64_t bodyHandle = NativeHandleForBodyId(targetSpace, hit.mBodyID); + if (bodyHandle == 0) { + return false; + } + + JPH::RVec3 point = ray.GetPointOnRay(hit.mFraction); + JPH::Vec3 normal = JPH::Vec3::sZero(); + JPH::BodyLockRead lock(targetSpace.m_PhysicsSystem.GetBodyLockInterface(), hit.mBodyID); + if (lock.Succeeded()) { + normal = lock.GetBody().GetWorldSpaceSurfaceNormal(hit.mSubShapeID2, point); + } + + bodyHandles[index] = static_cast(bodyHandle); + int offset = index * RayHitFloatCount; + hits[offset] = static_cast(point.GetX()); + hits[offset + 1] = static_cast(point.GetY()); + hits[offset + 2] = static_cast(point.GetZ()); + hits[offset + 3] = normal.GetX(); + hits[offset + 4] = normal.GetY(); + hits[offset + 5] = normal.GetZ(); + hits[offset + 6] = hit.mFraction; + hits[offset + 7] = ray.mDirection.Length() * hit.mFraction; + return true; +} + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.h b/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.h new file mode 100644 index 00000000..b0d5ffa8 --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.h @@ -0,0 +1,18 @@ +#pragma once + +#include "internal/impulse_jolt_space.h" + +#include + +#include + +namespace ImpulseJolt { + +bool WriteRayHit(Space& targetSpace, + const JPH::RRayCast& ray, + const JPH::RayCastResult& hit, + int index, + std::int64_t* bodyHandles, + float* hits); + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.cpp b/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.cpp new file mode 100644 index 00000000..13601805 --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.cpp @@ -0,0 +1,45 @@ +#include "internal/impulse_jolt_registry.h" + +#include +#include +#include + +#include +#include +#include + +namespace ImpulseJolt { + +std::once_flag s_JoltInitOnce; +std::mutex g_RegistryMutex; +std::uint64_t g_NextSpaceHandle = 1; +std::uint64_t g_NextBodyHandle = 1001; +std::uint64_t g_NextJointHandle = 2001; +std::unordered_map> g_Spaces; + +void EnsureJoltInitialized() { + std::call_once(s_JoltInitOnce, [] { + JPH::RegisterDefaultAllocator(); + if (JPH::Factory::sInstance == nullptr) { + JPH::Factory::sInstance = new JPH::Factory(); + } + JPH::RegisterTypes(); + }); +} + +Space* FindSpace(std::uint64_t handle) { + auto iterator = g_Spaces.find(handle); + return iterator == g_Spaces.end() ? nullptr : iterator->second.get(); +} + +BodyState* FindBody(Space& targetSpace, std::uint64_t handle) { + auto iterator = targetSpace.m_Bodies.find(handle); + return iterator == targetSpace.m_Bodies.end() ? nullptr : &iterator->second; +} + +std::uint64_t NativeHandleForBodyId(const Space& targetSpace, const JPH::BodyID& bodyId) { + auto iterator = targetSpace.m_BodyHandlesByJoltId.find(bodyId.GetIndexAndSequenceNumber()); + return iterator == targetSpace.m_BodyHandlesByJoltId.end() ? 0 : iterator->second; +} + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.h b/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.h new file mode 100644 index 00000000..2dc95137 --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.h @@ -0,0 +1,23 @@ +#pragma once + +#include "internal/impulse_jolt_space.h" + +#include +#include +#include +#include + +namespace ImpulseJolt { + +extern std::mutex g_RegistryMutex; +extern std::uint64_t g_NextSpaceHandle; +extern std::uint64_t g_NextBodyHandle; +extern std::uint64_t g_NextJointHandle; +extern std::unordered_map> g_Spaces; + +void EnsureJoltInitialized(); +Space* FindSpace(std::uint64_t handle); +BodyState* FindBody(Space& targetSpace, std::uint64_t handle); +std::uint64_t NativeHandleForBodyId(const Space& targetSpace, const JPH::BodyID& bodyId); + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp b/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp new file mode 100644 index 00000000..ee1ea80d --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp @@ -0,0 +1,128 @@ +#include "internal/impulse_jolt_shapes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ImpulseJolt { + +float Positive(float value) { + return std::max(value, MinShapeSize); +} + +JPH::EMotionType MotionType(int bodyType) { + switch (bodyType) { + case BodyStatic: + return JPH::EMotionType::Static; + case BodyKinematic: + return JPH::EMotionType::Kinematic; + default: + return JPH::EMotionType::Dynamic; + } +} + +JPH::ObjectLayer ObjectLayer(int collisionGroup, int collisionMask) { + constexpr std::uint32_t maskBits = JPH::ObjectLayerPairFilterMask::cMask; + std::uint32_t group = static_cast(collisionGroup) & maskBits; + std::uint32_t mask = static_cast(collisionMask) & maskBits; + if (group == 0) { + group = DefaultCollisionGroup; + } + if (mask == 0) { + mask = maskBits; + } + return JPH::ObjectLayerPairFilterMask::sGetObjectLayer(group, mask); +} + +float CenterOfMassOffsetY(int shapeType, + float halfExtentY, + float radius, + float halfHeight, + int axis) { + switch (shapeType) { + case ShapeBox: + return halfExtentY; + case ShapeSphere: + return radius; + case ShapeCapsule: + return axis == AxisY ? radius + halfHeight : radius; + case ShapeCylinder: + return axis == AxisY ? halfHeight : radius; + case ShapeCone: + return axis == AxisY ? halfHeight : radius; + default: + return 0.0F; + } +} + +JPH::Quat AxisRotation(int axis) { + switch (axis) { + case AxisX: + return JPH::Quat::sRotation(JPH::Vec3::sAxisZ(), -0.5F * JPH::JPH_PI); + case AxisZ: + return JPH::Quat::sRotation(JPH::Vec3::sAxisX(), 0.5F * JPH::JPH_PI); + default: + return JPH::Quat::sIdentity(); + } +} + +JPH::ShapeRefC RotatedForAxis(JPH::ShapeRefC shape, int axis) { + if (axis == AxisY || shape == nullptr) { + return shape; + } + return new JPH::RotatedTranslatedShape(JPH::Vec3::sZero(), AxisRotation(axis), shape); +} + +JPH::ShapeRefC CreateShape(int shapeType, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axis, + float groundY) { + switch (shapeType) { + case ShapeBox: + return new JPH::BoxShape(JPH::Vec3(Positive(halfExtentX), + Positive(halfExtentY), + Positive(halfExtentZ))); + case ShapeSphere: + return new JPH::SphereShape(Positive(radius)); + case ShapeCapsule: + return RotatedForAxis( + new JPH::CapsuleShape(Positive(halfHeight), Positive(radius)), + axis); + case ShapeCylinder: + return RotatedForAxis( + new JPH::CylinderShape(Positive(halfHeight), Positive(radius)), + axis); + case ShapeCone: { + JPH::TaperedCylinderShapeSettings settings(Positive(halfHeight), + 0.0F, + Positive(radius)); + JPH::Shape::ShapeResult result = settings.Create(); + if (result.HasError()) { + return nullptr; + } + return RotatedForAxis(result.Get(), axis); + } + case ShapePlane: + return new JPH::PlaneShape( + JPH::Plane::sFromPointAndNormal(JPH::Vec3(0.0F, groundY, 0.0F), + JPH::Vec3::sAxisY())); + default: + return nullptr; + } +} + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.h b/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.h new file mode 100644 index 00000000..93399060 --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.h @@ -0,0 +1,26 @@ +#pragma once + +#include "internal/impulse_jolt_space.h" + +#include +#include + +namespace ImpulseJolt { + +JPH::EMotionType MotionType(int bodyType); +JPH::ObjectLayer ObjectLayer(int collisionGroup, int collisionMask); +float CenterOfMassOffsetY(int shapeType, + float halfExtentY, + float radius, + float halfHeight, + int axis); +JPH::ShapeRefC CreateShape(int shapeType, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axis, + float groundY); + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp b/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp new file mode 100644 index 00000000..0ea9befc --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp @@ -0,0 +1,72 @@ +#include "internal/impulse_jolt_snapshot.h" + +#include +#include +#include +#include +#include +#include + +namespace ImpulseJolt { + +void WriteSnapshot(Space& targetSpace, const BodyState& body, float* floats, int* ints) { + JPH::BodyInterface& bodyInterface = targetSpace.m_PhysicsSystem.GetBodyInterface(); + JPH::RVec3 position = JPH::RVec3::sZero(); + JPH::Quat rotation = JPH::Quat::sIdentity(); + JPH::Vec3 linearVelocity = JPH::Vec3::sZero(); + JPH::Vec3 angularVelocity = JPH::Vec3::sZero(); + float linearDamping = body.m_LinearDamping; + float angularDamping = body.m_AngularDamping; + + bodyInterface.GetPositionAndRotation(body.m_BodyId, position, rotation); + bodyInterface.GetLinearAndAngularVelocity(body.m_BodyId, linearVelocity, angularVelocity); + JPH::BodyLockRead lock(targetSpace.m_PhysicsSystem.GetBodyLockInterface(), body.m_BodyId); + if (lock.Succeeded()) { + const JPH::MotionProperties* motionProperties = + lock.GetBody().GetMotionPropertiesUnchecked(); + if (motionProperties != nullptr) { + linearDamping = motionProperties->GetLinearDamping(); + angularDamping = motionProperties->GetAngularDamping(); + } + } + + floats[0] = static_cast(position.GetX()); + floats[1] = static_cast(position.GetY()); + floats[2] = static_cast(position.GetZ()); + floats[3] = rotation.GetX(); + floats[4] = rotation.GetY(); + floats[5] = rotation.GetZ(); + floats[6] = rotation.GetW(); + floats[7] = linearVelocity.GetX(); + floats[8] = linearVelocity.GetY(); + floats[9] = linearVelocity.GetZ(); + floats[10] = angularVelocity.GetX(); + floats[11] = angularVelocity.GetY(); + floats[12] = angularVelocity.GetZ(); + floats[13] = body.m_Mass; + floats[14] = bodyInterface.GetFriction(body.m_BodyId); + floats[15] = bodyInterface.GetRestitution(body.m_BodyId); + floats[16] = linearDamping; + floats[17] = angularDamping; + floats[18] = body.m_CenterOfMassOffsetY; + floats[19] = body.m_HalfExtentX; + floats[20] = body.m_HalfExtentY; + floats[21] = body.m_HalfExtentZ; + floats[22] = body.m_Radius; + floats[23] = body.m_HalfHeight; + + ints[0] = body.m_ShapeType; + ints[1] = body.m_BodyType; + ints[2] = bodyInterface.IsActive(body.m_BodyId) ? 0 : 1; + ints[3] = bodyInterface.IsSensor(body.m_BodyId) ? 1 : 0; + ints[4] = body.m_CollisionGroup; + ints[5] = body.m_CollisionMask; + ints[6] = bodyInterface.GetMotionQuality(body.m_BodyId) + == JPH::EMotionQuality::LinearCast + ? 1 + : 0; + ints[7] = body.m_HasBoxHalfExtents ? 1 : 0; + ints[8] = body.m_Axis; +} + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.h b/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.h new file mode 100644 index 00000000..a509c87a --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.h @@ -0,0 +1,9 @@ +#pragma once + +#include "internal/impulse_jolt_space.h" + +namespace ImpulseJolt { + +void WriteSnapshot(Space& targetSpace, const BodyState& body, float* floats, int* ints); + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.cpp b/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.cpp new file mode 100644 index 00000000..096edc2b --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.cpp @@ -0,0 +1,174 @@ +#include "internal/impulse_jolt_registry.h" +#include "internal/impulse_jolt_space.h" + +#include + +#include +#include + +namespace ImpulseJolt { + +ImpulseContactListener::ImpulseContactListener(Space* owner) + : m_Owner(owner) { +} + +Space::Space() + : m_BroadPhaseLayerInterface(1), + m_ObjectVsBroadphaseLayerFilter(m_BroadPhaseLayerInterface), + m_ContactListener(this), + m_TempAllocator(10 * 1024 * 1024), + m_JobSystem(JPH::cMaxPhysicsJobs, + JPH::cMaxPhysicsBarriers, + std::max(1U, std::thread::hardware_concurrency())) { + m_BroadPhaseLayerInterface.ConfigureLayer( + JPH::BroadPhaseLayer(0), + JPH::ObjectLayerPairFilterMask::cMask, + 0); + m_PhysicsSystem.Init(MaxBodies, + 0, + MaxBodyPairs, + MaxContactConstraints, + m_BroadPhaseLayerInterface, + m_ObjectVsBroadphaseLayerFilter, + m_ObjectLayerFilter); + m_PhysicsSystem.SetContactListener(&m_ContactListener); + m_PhysicsSystem.SetGravity(JPH::Vec3(0.0F, -9.81F, 0.0F)); +} + +Space::~Space() { + for (auto& entry : m_Joints) { + JointState& joint = entry.second; + if (joint.m_Constraint != nullptr) { + m_PhysicsSystem.RemoveConstraint(joint.m_Constraint); + } + } + m_Joints.clear(); + JPH::BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface(); + for (auto& entry : m_Bodies) { + BodyState& body = entry.second; + if (!body.m_BodyId.IsInvalid()) { + if (bodyInterface.IsAdded(body.m_BodyId)) { + bodyInterface.RemoveBody(body.m_BodyId); + } + bodyInterface.DestroyBody(body.m_BodyId); + } + } +} + +void Space::ReplaceContactRecords(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold) { + std::uint64_t bodyAHandle = NativeHandleForBodyId(*this, body1.GetID()); + std::uint64_t bodyBHandle = NativeHandleForBodyId(*this, body2.GetID()); + if (bodyAHandle == 0 || bodyBHandle == 0) { + return; + } + + JPH::SubShapeIDPair key(body1.GetID(), + manifold.mSubShapeID1, + body2.GetID(), + manifold.mSubShapeID2); + std::lock_guard lock(m_ContactMutex); + m_Contacts.erase(std::remove_if(m_Contacts.begin(), + m_Contacts.end(), + [&key](const ContactRecord& record) { + return record.m_Key == key; + }), + m_Contacts.end()); + + for (JPH::uint index = 0; index < manifold.mRelativeContactPointsOn1.size(); index++) { + JPH::RVec3 pointA = manifold.GetWorldSpaceContactPointOn1(index); + JPH::RVec3 pointB = manifold.GetWorldSpaceContactPointOn2(index); + ContactRecord record; + record.m_Key = key; + record.m_BodyAHandle = bodyAHandle; + record.m_BodyBHandle = bodyBHandle; + record.m_PointAX = static_cast(pointA.GetX()); + record.m_PointAY = static_cast(pointA.GetY()); + record.m_PointAZ = static_cast(pointA.GetZ()); + record.m_PointBX = static_cast(pointB.GetX()); + record.m_PointBY = static_cast(pointB.GetY()); + record.m_PointBZ = static_cast(pointB.GetZ()); + record.m_NormalBX = manifold.mWorldSpaceNormal.GetX(); + record.m_NormalBY = manifold.mWorldSpaceNormal.GetY(); + record.m_NormalBZ = manifold.mWorldSpaceNormal.GetZ(); + record.m_Distance = -manifold.mPenetrationDepth; + record.m_Impulse = 0.0F; + m_Contacts.push_back(record); + } +} + +void Space::EraseContactRecords(const JPH::SubShapeIDPair& key) { + std::lock_guard lock(m_ContactMutex); + m_Contacts.erase(std::remove_if(m_Contacts.begin(), + m_Contacts.end(), + [&key](const ContactRecord& record) { + return record.m_Key == key; + }), + m_Contacts.end()); +} + +void Space::EraseContactRecordsForBodyHandle(std::uint64_t bodyHandle) { + std::lock_guard lock(m_ContactMutex); + m_Contacts.erase(std::remove_if(m_Contacts.begin(), + m_Contacts.end(), + [bodyHandle](const ContactRecord& record) { + return record.m_BodyAHandle == bodyHandle + || record.m_BodyBHandle == bodyHandle; + }), + m_Contacts.end()); +} + +void Space::EraseConstraintsForBodyHandle(std::uint64_t bodyHandle) { + for (auto iterator = m_Joints.begin(); iterator != m_Joints.end();) { + JointState& joint = iterator->second; + if (joint.m_BodyAHandle != bodyHandle && joint.m_BodyBHandle != bodyHandle) { + ++iterator; + continue; + } + if (joint.m_Constraint != nullptr) { + m_PhysicsSystem.RemoveConstraint(joint.m_Constraint); + } + iterator = m_Joints.erase(iterator); + } +} + +bool Space::RemoveJointHandle(std::uint64_t jointHandle) { + auto iterator = m_Joints.find(jointHandle); + if (iterator == m_Joints.end()) { + return false; + } + if (iterator->second.m_Constraint != nullptr) { + m_PhysicsSystem.RemoveConstraint(iterator->second.m_Constraint); + } + m_Joints.erase(iterator); + return true; +} + +void ImpulseContactListener::OnContactAdded(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) { + (void) settings; + if (m_Owner != nullptr) { + m_Owner->ReplaceContactRecords(body1, body2, manifold); + } +} + +void ImpulseContactListener::OnContactPersisted(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) { + (void) settings; + if (m_Owner != nullptr) { + m_Owner->ReplaceContactRecords(body1, body2, manifold); + } +} + +void ImpulseContactListener::OnContactRemoved(const JPH::SubShapeIDPair& subShapePair) { + if (m_Owner != nullptr) { + m_Owner->EraseContactRecords(subShapePair); + } +} + +} // namespace ImpulseJolt diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.h b/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.h new file mode 100644 index 00000000..91ab8a6e --- /dev/null +++ b/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.h @@ -0,0 +1,122 @@ +#pragma once + +#include "abi/impulse_jolt_native.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace ImpulseJolt { + +struct Space; + +struct ContactRecord { + JPH::SubShapeIDPair m_Key; + std::uint64_t m_BodyAHandle = 0; + std::uint64_t m_BodyBHandle = 0; + float m_PointAX = 0.0F; + float m_PointAY = 0.0F; + float m_PointAZ = 0.0F; + float m_PointBX = 0.0F; + float m_PointBY = 0.0F; + float m_PointBZ = 0.0F; + float m_NormalBX = 0.0F; + float m_NormalBY = 0.0F; + float m_NormalBZ = 0.0F; + float m_Distance = 0.0F; + float m_Impulse = 0.0F; +}; + +class ImpulseContactListener final : public JPH::ContactListener { +public: + explicit ImpulseContactListener(Space* owner); + + void OnContactAdded(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) override; + + void OnContactPersisted(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold, + JPH::ContactSettings& settings) override; + + void OnContactRemoved(const JPH::SubShapeIDPair& subShapePair) override; + +private: + Space* m_Owner; +}; + +struct BodyState { + JPH::BodyID m_BodyId; + int m_ShapeType = 0; + int m_BodyType = 0; + bool m_Sensor = false; + float m_Mass = 0.0F; + float m_Friction = 0.0F; + float m_Restitution = 0.0F; + float m_LinearDamping = 0.0F; + float m_AngularDamping = 0.0F; + int m_CollisionGroup = 0; + int m_CollisionMask = 0; + bool m_ContinuousCollision = false; + float m_CenterOfMassOffsetY = 0.0F; + bool m_HasBoxHalfExtents = false; + float m_HalfExtentX = 0.0F; + float m_HalfExtentY = 0.0F; + float m_HalfExtentZ = 0.0F; + float m_Radius = 0.0F; + float m_HalfHeight = 0.0F; + int m_Axis = AxisY; +}; + +struct JointState { + JPH::Ref m_Constraint; + std::uint64_t m_BodyAHandle = 0; + std::uint64_t m_BodyBHandle = 0; + int m_JointType = 0; +}; + +struct Space { + JPH::BroadPhaseLayerInterfaceMask m_BroadPhaseLayerInterface; + JPH::ObjectVsBroadPhaseLayerFilterMask m_ObjectVsBroadphaseLayerFilter; + JPH::ObjectLayerPairFilterMask m_ObjectLayerFilter; + JPH::PhysicsSystem m_PhysicsSystem; + ImpulseContactListener m_ContactListener; + JPH::TempAllocatorImpl m_TempAllocator; + JPH::JobSystemThreadPool m_JobSystem; + std::unordered_map m_Bodies; + std::unordered_map m_BodyHandlesByJoltId; + std::unordered_map m_Joints; + std::mutex m_ContactMutex; + std::vector m_Contacts; + + Space(); + ~Space(); + + void ReplaceContactRecords(const JPH::Body& body1, + const JPH::Body& body2, + const JPH::ContactManifold& manifold); + + void EraseContactRecords(const JPH::SubShapeIDPair& key); + + void EraseContactRecordsForBodyHandle(std::uint64_t bodyHandle); + + void EraseConstraintsForBodyHandle(std::uint64_t bodyHandle); + + bool RemoveJointHandle(std::uint64_t jointHandle); +}; + +} // namespace ImpulseJolt From a2a00ebc26f2f52cb31cc08ef5a094eb08842a03 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 17:19:18 +0200 Subject: [PATCH 524/534] feat(backend-api): seed body state during creation Signed-off-by: Blovien --- .../api/runtime/PhysicsBackendRuntime.java | 64 +++++++++ .../FakePhysicsBackendRuntimeProvider.java | 111 +++++++++++++++ .../systems/binding/BodyBindingSystem.java | 26 ++-- .../systems/BodyBindingSystemTest.java | 99 ++++++++++++++ .../impulse/rapier/RapierBackendRuntime.java | 126 +++++++++++++++++- .../RapierBackendRuntimeProviderTest.java | 66 +++++++++ 6 files changed, 475 insertions(+), 17 deletions(-) diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java index 3e2cf236..46270a3a 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java @@ -44,6 +44,70 @@ long createBody(int spaceId, float rotationZ, float rotationW); + default long createBodyWithInitialState(int spaceId, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearDamping, + float angularDamping, + float friction, + float restitution, + int collisionGroup, + int collisionMask, + boolean sensor, + boolean continuousCollisionEnabled) { + long bodyId = createBody(spaceId, + shapeTypeCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode, + groundY, + mass, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW); + try { + setBodyDamping(spaceId, bodyId, linearDamping, angularDamping); + setBodyFriction(spaceId, bodyId, friction); + setBodyRestitution(spaceId, bodyId, restitution); + setBodyCollisionFilter(spaceId, bodyId, collisionGroup, collisionMask); + setBodySensor(spaceId, bodyId, sensor); + if (continuousCollisionEnabled && supportsContinuousCollision(spaceId)) { + setBodyContinuousCollision(spaceId, bodyId, true); + } + return bodyId; + } catch (RuntimeException exception) { + try { + removeBody(spaceId, bodyId); + } catch (RuntimeException rollbackFailure) { + exception.addSuppressed(rollbackFailure); + } + throw exception; + } + } + boolean supportsVoxelTerrain(int spaceId); long createVoxelTerrain(int spaceId, diff --git a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java index 2f3b2b3b..416d99ac 100644 --- a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java +++ b/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java @@ -102,6 +102,13 @@ public static final class FakePhysicsBackendRuntime implements PhysicsBackendRun private final FailureController failures; private long nextBodyId = 1L; private long nextJointId = 1L; + private int createBodyWithInitialStateCalls; + private int setBodyDampingCalls; + private int setBodyFrictionCalls; + private int setBodyRestitutionCalls; + private int setBodyCollisionFilterCalls; + private int setBodySensorCalls; + private int setBodyContinuousCollisionCalls; private FakePhysicsBackendRuntime(boolean continuousCollision, boolean voxelTerrain, @@ -192,6 +199,64 @@ public long createBody(int spaceId, return bodyId; } + @Override + public long createBodyWithInitialState(int spaceId, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearDamping, + float angularDamping, + float friction, + float restitution, + int collisionGroup, + int collisionMask, + boolean sensor, + boolean continuousCollisionEnabled) { + createBodyWithInitialStateCalls++; + long bodyId = createBody(spaceId, + shapeTypeCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode, + groundY, + mass, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW); + BodyState body = requireBody(requireSpace(spaceId), bodyId); + body.linearDamping = linearDamping; + body.angularDamping = angularDamping; + body.friction = friction; + body.restitution = restitution; + body.collisionGroup = collisionGroup; + body.collisionMask = collisionMask; + body.sensor = sensor; + body.continuousCollision = continuousCollisionEnabled && supportsContinuousCollision(spaceId); + return bodyId; + } + @Override public boolean supportsVoxelTerrain(int spaceId) { requireSpace(spaceId); @@ -350,6 +415,7 @@ public int bodyTypeCode(int spaceId, long bodyId) { @Override public void setBodyDamping(int spaceId, long bodyId, float linearDamping, float angularDamping) { + setBodyDampingCalls++; BodyState body = requireBody(requireSpace(spaceId), bodyId); body.linearDamping = linearDamping; body.angularDamping = angularDamping; @@ -357,6 +423,7 @@ public void setBodyDamping(int spaceId, long bodyId, float linearDamping, float @Override public void setBodyFriction(int spaceId, long bodyId, float friction) { + setBodyFrictionCalls++; failures.maybeFailBodyFriction(); requireBody(requireSpace(spaceId), bodyId).friction = friction; } @@ -365,8 +432,17 @@ public float bodyFriction(int spaceId, long bodyId) { return requireBody(requireSpace(spaceId), bodyId).friction; } + public float bodyLinearDamping(int spaceId, long bodyId) { + return requireBody(requireSpace(spaceId), bodyId).linearDamping; + } + + public float bodyAngularDamping(int spaceId, long bodyId) { + return requireBody(requireSpace(spaceId), bodyId).angularDamping; + } + @Override public void setBodyRestitution(int spaceId, long bodyId, float restitution) { + setBodyRestitutionCalls++; requireBody(requireSpace(spaceId), bodyId).restitution = restitution; } @@ -376,6 +452,7 @@ public float bodyRestitution(int spaceId, long bodyId) { @Override public void setBodyCollisionFilter(int spaceId, long bodyId, int group, int mask) { + setBodyCollisionFilterCalls++; BodyState body = requireBody(requireSpace(spaceId), bodyId); body.collisionGroup = group; body.collisionMask = mask; @@ -391,11 +468,17 @@ public int bodyCollisionMask(int spaceId, long bodyId) { @Override public void setBodySensor(int spaceId, long bodyId, boolean sensor) { + setBodySensorCalls++; requireBody(requireSpace(spaceId), bodyId).sensor = sensor; } + public boolean bodySensor(int spaceId, long bodyId) { + return requireBody(requireSpace(spaceId), bodyId).sensor; + } + @Override public void setBodyContinuousCollision(int spaceId, long bodyId, boolean enabled) { + setBodyContinuousCollisionCalls++; requireBody(requireSpace(spaceId), bodyId).continuousCollision = enabled; } @@ -602,6 +685,34 @@ public boolean hasSpace(int spaceId) { return spaces.containsKey(spaceId); } + public int createBodyWithInitialStateCalls() { + return createBodyWithInitialStateCalls; + } + + public int setBodyDampingCalls() { + return setBodyDampingCalls; + } + + public int setBodyFrictionCalls() { + return setBodyFrictionCalls; + } + + public int setBodyRestitutionCalls() { + return setBodyRestitutionCalls; + } + + public int setBodyCollisionFilterCalls() { + return setBodyCollisionFilterCalls; + } + + public int setBodySensorCalls() { + return setBodySensorCalls; + } + + public int setBodyContinuousCollisionCalls() { + return setBodyContinuousCollisionCalls; + } + private static void emitSnapshot(long bodyId, @Nonnull BodyState body, @Nonnull BackendBodySnapshotSink sink) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java index 06315240..646a7164 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/BodyBindingSystem.java @@ -157,6 +157,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, PhysicsBodyType bodyType = bodyDynamics.getBodyType(); float mass = bodyType == PhysicsBodyType.DYNAMIC ? bodyDynamics.getMass() : 0.0f; long bodyId = Long.MIN_VALUE; + boolean bodyCreatedWithInitialState = false; try { if (shape.getShapeType() == ShapeType.VOXELS) { if (bodyType != PhysicsBodyType.STATIC) { @@ -176,7 +177,7 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, return; } } else { - bodyId = backendRuntime.createBody(spaceHandle.value(), + bodyId = backendRuntime.createBodyWithInitialState(spaceHandle.value(), BackendRuntimeCodes.shapeTypeCode(shape.getShapeType()), shape.getHalfExtentX(), shape.getHalfExtentY(), @@ -193,23 +194,20 @@ private static void bindBody(@Nonnull PhysicsRuntimeResource runtime, rotation.x, rotation.y, rotation.z, - rotation.w); - backendRuntime.setBodyDamping(spaceHandle.value(), - bodyId, + rotation.w, bodyDynamics.getLinearDamping(), - bodyDynamics.getAngularDamping()); - backendRuntime.setBodyFriction(spaceHandle.value(), bodyId, material.getFriction()); - backendRuntime.setBodyRestitution(spaceHandle.value(), - bodyId, - material.getRestitution()); - backendRuntime.setBodyCollisionFilter(spaceHandle.value(), - bodyId, + bodyDynamics.getAngularDamping(), + material.getFriction(), + material.getRestitution(), filter.getCollisionGroup(), - filter.getCollisionMask()); - backendRuntime.setBodySensor(spaceHandle.value(), bodyId, collider.isSensor()); + filter.getCollisionMask(), + collider.isSensor(), + bodyDynamics.isContinuousCollisionEnabled()); + bodyCreatedWithInitialState = true; } BackendBodyHandle bodyHandle = new BackendBodyHandle(bodyId); - if (bodyDynamics.isContinuousCollisionEnabled() + if (!bodyCreatedWithInitialState + && bodyDynamics.isContinuousCollisionEnabled() && backendRuntime.supportsContinuousCollision(spaceHandle.value())) { backendRuntime.setBodyContinuousCollision(spaceHandle.value(), bodyId, true); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java index ded89df3..02676a73 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; @@ -107,6 +108,73 @@ void nonVoxelBodyBindingDoesNotRequirePhysicsChunkPayloadResource() { } } + @Test + void nonVoxelBodyBindingSeedsInitialPropertiesWithoutSeparateMutationCalls() { + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + FakePhysicsBackendRuntimeProvider provider = + new FakePhysicsBackendRuntimeProvider(BACKEND_ID, true, false); + Impulse.registerRuntimeProvider(provider); + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + proxy.registerSystem(new PersistenceHydrationSystem()); + proxy.registerSystem(new IdentityIndexSystem()); + proxy.registerSystem(new SpaceBindingSystem()); + proxy.registerSystem(new SpaceSettingsApplicationSystem()); + proxy.registerSystem(new BodyBindingSystem()); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("body-binding-configured-create-test")), + EmptyResourceStorage.get()); + try { + PhysicsRestoreStatusResource restore = store.getResource( + PhysicsRestoreStatusResource.getResourceType()); + restore.markComplete(); + restore.markHydrated(); + UUID spaceUuid = uuid(1); + Ref spaceRef = addSpace(store, spaceUuid); + Ref bodyRef = addConfiguredBody(store, uuid(2), spaceUuid, spaceRef); + + store.tick(0.0f); + + assertFalse(restore.isFailed(), restore.getFailureMessage()); + PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); + BackendSpaceHandle spaceHandle = runtime.getSpaceHandle(spaceRef); + BackendBodyHandle bodyHandle = runtime.getBodyHandle(bodyRef); + assertNotNull(spaceHandle); + assertNotNull(bodyHandle); + FakePhysicsBackendRuntime backendRuntime = provider.createdRuntimes().get(0); + assertEquals(1, backendRuntime.createBodyWithInitialStateCalls()); + assertEquals(0, backendRuntime.setBodyDampingCalls()); + assertEquals(0, backendRuntime.setBodyFrictionCalls()); + assertEquals(0, backendRuntime.setBodyRestitutionCalls()); + assertEquals(0, backendRuntime.setBodyCollisionFilterCalls()); + assertEquals(0, backendRuntime.setBodySensorCalls()); + assertEquals(0, backendRuntime.setBodyContinuousCollisionCalls()); + assertEquals(0.2f, + backendRuntime.bodyLinearDamping(spaceHandle.value(), bodyHandle.value())); + assertEquals(0.3f, + backendRuntime.bodyAngularDamping(spaceHandle.value(), bodyHandle.value())); + assertEquals(0.72f, backendRuntime.bodyFriction(spaceHandle.value(), bodyHandle.value())); + assertEquals(0.18f, + backendRuntime.bodyRestitution(spaceHandle.value(), bodyHandle.value())); + assertEquals(PhysicsCollisionFilters.TERRAIN, + backendRuntime.bodyCollisionGroup(spaceHandle.value(), bodyHandle.value())); + assertEquals(PhysicsCollisionFilters.DYNAMIC_BODY, + backendRuntime.bodyCollisionMask(spaceHandle.value(), bodyHandle.value())); + assertTrue(backendRuntime.bodySensor(spaceHandle.value(), bodyHandle.value())); + assertTrue(backendRuntime.isBodyContinuousCollisionEnabled(spaceHandle.value(), + bodyHandle.value())); + } finally { + if (!store.isShutdown()) { + registry.removeStore(store); + } + registry.shutdown(); + PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); + } + } + @Test void chunkRestoreDependencyWaitsWithoutBlockingUnrelatedBodies() { PhysicsChunkLifecycle.enable(); @@ -281,6 +349,37 @@ private static Ref addBody(Store store, return bodyRef; } + private static Ref addConfiguredBody(Store store, + UUID bodyUuid, + UUID spaceUuid, + Ref spaceRef) { + BodyComponent body = new BodyComponent(spaceUuid); + body.setSpaceRef(spaceRef); + TargetComponent target = new TargetComponent(); + target.setActive(true); + Ref bodyRef = store.addEntity(PhysicsEntities.bodyHolder(store, + bodyUuid, + body, + new DynamicsComponent(PhysicsBodyType.DYNAMIC, 2.0f, 0.2f, 0.3f, true), + target, + new ColliderComponent(new Vector3f(), new Quaternionf(), true), + new ShapeComponent(ShapeType.BOX, + 0.5f, + 0.5f, + 0.5f, + 0.0f, + 0.0f, + PhysicsAxis.Y, + 0.0f, + ""), + new MaterialComponent(0.72f, 0.18f), + new CollisionFilterComponent(PhysicsCollisionFilters.TERRAIN, + PhysicsCollisionFilters.DYNAMIC_BODY)), + AddReason.SPAWN); + assertNotNull(bodyRef); + return bodyRef; + } + private static Ref addGeneratedChunkBody(Store store, UUID spaceUuid, Ref spaceRef) { diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java index 1381ed47..64e12956 100644 --- a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java +++ b/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java @@ -31,6 +31,9 @@ final class RapierBackendRuntime implements PhysicsBackendRuntime { private static final Cleaner CLEANER = Cleaner.create(); private static final float DEFAULT_DYNAMIC_MASS = 1.0f; + private static final float DEFAULT_BODY_FRICTION = 0.5f; + private static final int DEFAULT_BODY_COLLISION_GROUP = 1; + private static final int DEFAULT_BODY_COLLISION_MASK = 1; private static final int DEFAULT_SOLVER_ITERATIONS = 4; private static final int DEFAULT_INTERNAL_PGS_ITERATIONS = 1; private static final int DEFAULT_STABILIZATION_ITERATIONS = 1; @@ -137,6 +140,115 @@ public long createBody(int spaceId, float rotationY, float rotationZ, float rotationW) { + return createBodyInternal(spaceId, + shapeTypeCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode, + groundY, + mass, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + 0.0f, + 0.0f, + DEFAULT_BODY_FRICTION, + 0.0f, + DEFAULT_BODY_COLLISION_GROUP, + DEFAULT_BODY_COLLISION_MASK, + false, + false); + } + + @Override + public long createBodyWithInitialState(int spaceId, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearDamping, + float angularDamping, + float friction, + float restitution, + int collisionGroup, + int collisionMask, + boolean sensor, + boolean continuousCollisionEnabled) { + return createBodyInternal(spaceId, + shapeTypeCode, + halfExtentX, + halfExtentY, + halfExtentZ, + radius, + halfHeight, + axisCode, + groundY, + mass, + bodyTypeCode, + positionX, + positionY, + positionZ, + rotationX, + rotationY, + rotationZ, + rotationW, + linearDamping, + angularDamping, + friction, + restitution, + collisionGroup, + collisionMask, + sensor, + continuousCollisionEnabled); + } + + private long createBodyInternal(int spaceId, + int shapeTypeCode, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode, + float groundY, + float mass, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearDamping, + float angularDamping, + float friction, + float restitution, + int collisionGroup, + int collisionMask, + boolean sensor, + boolean continuousCollisionEnabled) { SpaceState state = requireSpace(spaceId); ShapeType shapeType = BackendRuntimeCodes.shapeType(shapeTypeCode); if (shapeType == ShapeType.VOXELS || shapeType == ShapeType.UNKNOWN) { @@ -164,6 +276,14 @@ public long createBody(int spaceId, rotationY, rotationZ, rotationW); + body.linearDamping = linearDamping; + body.angularDamping = angularDamping; + body.friction = friction; + body.restitution = restitution; + body.collisionGroup = collisionGroup; + body.collisionMask = collisionMask; + body.sensor = sensor; + body.continuousCollisionEnabled = continuousCollisionEnabled; long handle = RapierNative.addBodyNative(state.nativeSpaceHandle, shapeType.ordinal(), body.halfExtentX, @@ -1177,12 +1297,12 @@ private static final class BodyState { private boolean sleeping; private boolean sensor; private float mass; - private float friction = 0.5f; + private float friction = DEFAULT_BODY_FRICTION; private float restitution; private float linearDamping; private float angularDamping; - private int collisionGroup = 1; - private int collisionMask = 1; + private int collisionGroup = DEFAULT_BODY_COLLISION_GROUP; + private int collisionMask = DEFAULT_BODY_COLLISION_MASK; private boolean continuousCollisionEnabled; private BodyState(long bodyId, diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java index 090e3dd2..ee75685a 100644 --- a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java @@ -158,6 +158,56 @@ void gravityRoundTripsEveryAxisDirection() { } } + @Test + void configuredBodyCreationSeedsInitialBodyState() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + PhysicsBackendRuntime runtime = provider.createRuntime(); + int spaceId = runtime.createSpace(new SpaceId(76)); + try { + long bodyId = runtime.createBodyWithInitialState(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + 1.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), + 1.0f, + 2.0f, + 3.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.2f, + 0.3f, + 0.65f, + 0.15f, + 2, + 3, + true, + true); + CapturedSnapshot snapshot = new CapturedSnapshot(); + + assertTrue(runtime.bodySnapshot(spaceId, bodyId, snapshot)); + + assertEquals(0.2f, snapshot.linearDamping); + assertEquals(0.3f, snapshot.angularDamping); + assertEquals(0.65f, snapshot.friction); + assertEquals(0.15f, snapshot.restitution); + assertEquals(2, snapshot.collisionGroup); + assertEquals(3, snapshot.collisionMask); + assertTrue(snapshot.sensor); + assertTrue(snapshot.continuousCollisionEnabled); + } finally { + runtime.destroySpace(spaceId); + } + } + private static void assertGravityEquals(float[] expected, PhysicsBackendRuntime runtime, int spaceId) { @@ -381,6 +431,14 @@ private static final class CapturedSnapshot implements BackendBodySnapshotSink { private float positionX; private float positionY; private float positionZ; + private boolean sensor; + private float friction; + private float restitution; + private float linearDamping; + private float angularDamping; + private int collisionGroup; + private int collisionMask; + private boolean continuousCollisionEnabled; @Override public void accept(long bodyId, @@ -423,6 +481,14 @@ public void accept(long bodyId, this.positionX = positionX; this.positionY = positionY; this.positionZ = positionZ; + this.sensor = sensor; + this.friction = friction; + this.restitution = restitution; + this.linearDamping = linearDamping; + this.angularDamping = angularDamping; + this.collisionGroup = collisionGroup; + this.collisionMask = collisionMask; + this.continuousCollisionEnabled = continuousCollisionEnabled; } } } From e9e37eba96e71d14501266b744b3b513f7a9ed87 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 17:19:34 +0200 Subject: [PATCH 525/534] fix(core): sync near awake visuals every tick Signed-off-by: Blovien --- .../systems/sync/PhysicsSyncPolicy.java | 28 +++++++------------ .../systems/sync/PhysicsSyncSystem.java | 9 ------ .../systems/sync/PhysicsSyncPolicyTest.java | 25 ++++------------- 3 files changed, 15 insertions(+), 47 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java index 0753661a..463f9ae3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java @@ -15,16 +15,11 @@ */ public final class PhysicsSyncPolicy { - // Near visuals sync after roughly one thirty-second of a block of movement. + // Near sleeping visuals still sync if the backend pose changes while asleep. private static final float POSITION_SYNC_THRESHOLD = 1.0f / 32.0f; private static final float POSITION_SYNC_THRESHOLD_SQUARED = POSITION_SYNC_THRESHOLD * POSITION_SYNC_THRESHOLD; - // Low-speed visuals use a wider deadzone for tiny awake-body solver jitter. - private static final float LOW_SPEED_POSITION_SYNC_THRESHOLD = 1.0f / 8.0f; - private static final float LOW_SPEED_POSITION_SYNC_THRESHOLD_SQUARED = - LOW_SPEED_POSITION_SYNC_THRESHOLD * LOW_SPEED_POSITION_SYNC_THRESHOLD; - // Mid-range visuals are outside the full-sync radius but still within visual range. private static final float MID_RANGE_POSITION_SYNC_THRESHOLD = 0.5f; private static final float MID_RANGE_POSITION_SYNC_THRESHOLD_SQUARED = @@ -37,8 +32,6 @@ public final class PhysicsSyncPolicy { */ private static final float ROTATION_SYNC_DOT_THRESHOLD = (float) Math.cos(Math.toRadians(1.0)); - private static final float LOW_SPEED_ROTATION_SYNC_DOT_THRESHOLD = - (float) Math.cos(Math.toRadians(3.0)); private static final float MID_RANGE_ROTATION_SYNC_DOT_THRESHOLD = (float) Math.cos(Math.toRadians(8.0)); @@ -52,7 +45,6 @@ public final class PhysicsSyncPolicy { // Keepalive updates bound how long an awake visual can stay below sync thresholds. private static final float ACTIVE_KEEPALIVE_SECONDS = 0.25f; - private static final float LOW_SPEED_KEEPALIVE_SECONDS = 1.25f; private static final float MID_RANGE_KEEPALIVE_SECONDS = 2.5f; private static final float SECONDS_PER_TICK = 0.05f; @@ -106,7 +98,6 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn @Nonnull Vector3f position, @Nonnull Quaternionf rotation, boolean sleeping, - boolean lowSpeed, boolean kinematic, @Nonnull SyncRangeTier rangeTier) { if (!syncState.isInitialized()) { @@ -139,12 +130,9 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn keepaliveSeconds = MID_RANGE_KEEPALIVE_SECONDS; minimumIntervalTicks = visualSyncSettings.getVisualMidSyncIntervalTicks(); } else { - positionThresholdSquared = lowSpeed && !kinematic - ? LOW_SPEED_POSITION_SYNC_THRESHOLD_SQUARED : POSITION_SYNC_THRESHOLD_SQUARED; - rotationDotThreshold = lowSpeed && !kinematic - ? LOW_SPEED_ROTATION_SYNC_DOT_THRESHOLD : ROTATION_SYNC_DOT_THRESHOLD; - keepaliveSeconds = lowSpeed && !kinematic - ? LOW_SPEED_KEEPALIVE_SECONDS : ACTIVE_KEEPALIVE_SECONDS; + positionThresholdSquared = POSITION_SYNC_THRESHOLD_SQUARED; + rotationDotThreshold = ROTATION_SYNC_DOT_THRESHOLD; + keepaliveSeconds = ACTIVE_KEEPALIVE_SECONDS; } if (minimumIntervalTicks > 1 @@ -152,6 +140,11 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn return SyncDecision.SKIP_VISUAL_RANGE; } + if (!sleeping && rangeTier == SyncRangeTier.NEAR) { + // Smooth near awake visuals prefer cadence; sleep and range tiers own throttling. + return SyncDecision.THRESHOLD; + } + if (position.distanceSquared(syncState.getLastSyncedPosition()) >= positionThresholdSquared || rotationChangedEnough(rotation, syncState.getLastSyncedRotation(), rotationDotThreshold)) { return SyncDecision.THRESHOLD; @@ -165,8 +158,7 @@ static SyncDecision resolveSyncDecision(@Nonnull PhysicsBodyRuntimeState.BodySyn if (rangeTier == SyncRangeTier.MID) { return SyncDecision.SKIP_VISUAL_RANGE; } - return lowSpeed && !kinematic ? SyncDecision.SKIP_VISUAL_DEADZONE - : SyncDecision.SKIP_THRESHOLD; + return SyncDecision.SKIP_THRESHOLD; } private static boolean rotationChangedEnough(@Nonnull Quaternionf current, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java index 40bd7098..41f440bb 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java @@ -57,9 +57,6 @@ public class PhysicsSyncSystem extends EntityTickingSystem { private static final float TRANSFORM_POSITION_EPSILON = 0.000001f; private static final float TRANSFORM_ROTATION_EPSILON = 0.000001f; - private static final float LOW_SPEED_POSITION_MOTION_THRESHOLD = 0.125f; - private static final float LOW_SPEED_ROTATION_MOTION_THRESHOLD = - (float) Math.toRadians(1.0); @Nonnull private final ComponentType attachmentType; @@ -271,17 +268,11 @@ private static SyncResult applyComputedPhysicsStoreSnapshot( if (!syncState.isInitializedFor(snapshot.bodyUuid())) { syncState.clear(); } - PhysicsBodyRuntimeState.BodySyncState.SnapshotMotion snapshotMotion = - syncState.recordSnapshotObservation(scratch.position, scratch.visualRotation); - boolean lowSpeed = snapshotMotion.observed() - && snapshotMotion.positionDistance() < LOW_SPEED_POSITION_MOTION_THRESHOLD - && snapshotMotion.rotationRadians() < LOW_SPEED_ROTATION_MOTION_THRESHOLD; PhysicsSyncPolicy.SyncDecision decision = PhysicsSyncPolicy.resolveSyncDecision(syncState, settings, scratch.visualPosition, scratch.visualRotation, snapshot.sleeping(), - lowSpeed, kinematic, rangeTier); if (!shouldWriteTransform(decision)) { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java index ab1a2298..21e419a1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java @@ -24,7 +24,6 @@ void returnsInitialForUninitializedSyncState() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.NEAR)); } @@ -39,7 +38,6 @@ void returnsTransitionWhenSleepingStateChanges() { new Quaternionf(), true, false, - false, PhysicsSyncPolicy.SyncRangeTier.NEAR)); } @@ -54,38 +52,34 @@ void farRangeFollowersSkipVisualSync() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.FAR)); } @Test - void lowSpeedNearBodiesUseVisualDeadzoneBeforeKeepalive() { + void lowSpeedNearBodiesUseNormalNearThreshold() { PhysicsBodyRuntimeState.BodySyncState syncState = initializedState(false); syncState.recordSkip(1.0f); - assertEquals(PhysicsSyncPolicy.SyncDecision.SKIP_VISUAL_DEADZONE, + assertEquals(PhysicsSyncPolicy.SyncDecision.THRESHOLD, PhysicsSyncPolicy.resolveSyncDecision(syncState, new PhysicsVisualSyncSettings(), new Vector3f(0.05f, 0.0f, 0.0f), new Quaternionf(), false, true, - false, PhysicsSyncPolicy.SyncRangeTier.NEAR)); } @Test - void lowSpeedNearBodiesTriggerKeepaliveAtLongerInterval() { + void activeNearBodiesSyncEveryTickBelowThreshold() { PhysicsBodyRuntimeState.BodySyncState syncState = initializedState(false); - syncState.recordSkip(1.25f); - assertEquals(PhysicsSyncPolicy.SyncDecision.KEEPALIVE, + assertEquals(PhysicsSyncPolicy.SyncDecision.THRESHOLD, PhysicsSyncPolicy.resolveSyncDecision(syncState, new PhysicsVisualSyncSettings(), - new Vector3f(0.05f, 0.0f, 0.0f), + new Vector3f(0.001f, 0.0f, 0.0f), new Quaternionf(), false, - true, false, PhysicsSyncPolicy.SyncRangeTier.NEAR)); } @@ -102,7 +96,6 @@ void midRangeFollowersUseCoarseThresholdAndKeepalive() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.MID)); syncState.recordSkip(0.1f); @@ -113,7 +106,6 @@ void midRangeFollowersUseCoarseThresholdAndKeepalive() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.MID)); } @@ -131,7 +123,6 @@ void midRangeFollowersRespectConfiguredMinimumInterval() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.MID)); syncState.recordSkip(0.05f); @@ -142,7 +133,6 @@ void midRangeFollowersRespectConfiguredMinimumInterval() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.MID)); } @@ -161,7 +151,6 @@ void farRangeLodUsesConfiguredIntervalWhenCutoffIsDisabled() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.FAR)); syncState.recordSkip(0.05f); @@ -172,7 +161,6 @@ void farRangeLodUsesConfiguredIntervalWhenCutoffIsDisabled() { new Quaternionf(), false, false, - false, PhysicsSyncPolicy.SyncRangeTier.FAR)); } @@ -187,7 +175,6 @@ void kinematicBodiesBypassLowSpeedDeadzoneThresholds() { new Quaternionf(), false, true, - true, PhysicsSyncPolicy.SyncRangeTier.NEAR)); } @@ -203,7 +190,6 @@ void activeBodiesTriggerThresholdOnRotationChange() { rotated, false, false, - false, PhysicsSyncPolicy.SyncRangeTier.NEAR)); } @@ -233,7 +219,6 @@ void sleepingBodiesSkipAfterThresholdAndKeepaliveChecks() { new Quaternionf(), true, false, - false, PhysicsSyncPolicy.SyncRangeTier.NEAR)); } From f0509c238b74dc9287afc4fc56d0097bf60cfc23 Mon Sep 17 00:00:00 2001 From: Blovien Date: Mon, 22 Jun 2026 17:27:00 +0200 Subject: [PATCH 526/534] fix(core): sanitize visual sync rotations Signed-off-by: Blovien --- .../systems/sync/PhysicsSyncSystem.java | 50 ++++++++++++++++++- .../systems/sync/PhysicsSyncSystemTest.java | 22 ++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java index 41f440bb..fce85fad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java @@ -291,7 +291,8 @@ private static void computeVisualPose(@Nonnull BodyAttachmentComponent attachmen @Nonnull PhysicsBodySnapshot snapshot, @Nonnull Scratch scratch) { scratch.position.set(snapshot.positionX(), snapshot.positionY(), snapshot.positionZ()); - scratch.rotation.set(snapshot.rotationX(), + setFiniteUnitQuaternionOrIdentity(scratch.rotation, + snapshot.rotationX(), snapshot.rotationY(), snapshot.rotationZ(), snapshot.rotationW()); @@ -302,8 +303,14 @@ private static void computeVisualPose(@Nonnull BodyAttachmentComponent attachmen scratch.visualPosition, scratch.worldOffset); scratch.visualRotation.set(scratch.rotation); - scratch.visualRotation.mul(attachment.getLocalRotationOffset()); + setFiniteUnitQuaternionOrIdentity(scratch.localRotationOffset, + attachment.getLocalRotationOffset()); + scratch.visualRotation.mul(scratch.localRotationOffset); + setFiniteUnitQuaternionOrIdentity(scratch.visualRotation, scratch.visualRotation); scratch.visualRotation.getEulerAnglesYXZ(scratch.euler); + if (!isFinite(scratch.euler)) { + scratch.euler.zero(); + } } private static boolean writeTransformIfChanged(@Nonnull TransformComponent transform, @@ -373,10 +380,49 @@ private static boolean matchesTransform(@Nonnull TransformComponent transform, && Math.abs(transform.getRotation().z() - rotation.z) <= TRANSFORM_ROTATION_EPSILON; } + private static void setFiniteUnitQuaternionOrIdentity(@Nonnull Quaternionf target, + float x, + float y, + float z, + float w) { + target.set(x, y, z, w); + setFiniteUnitQuaternionOrIdentity(target, target); + } + + private static void setFiniteUnitQuaternionOrIdentity(@Nonnull Quaternionf target, + @Nonnull Quaternionf source) { + if (target != source) { + target.set(source); + } + if (!isFiniteAndNonZero(target)) { + target.identity(); + return; + } + target.normalize(); + if (!isFiniteAndNonZero(target)) { + target.identity(); + } + } + + private static boolean isFiniteAndNonZero(@Nonnull Quaternionf quaternion) { + float lengthSquared = quaternion.x * quaternion.x + + quaternion.y * quaternion.y + + quaternion.z * quaternion.z + + quaternion.w * quaternion.w; + return Float.isFinite(lengthSquared) && lengthSquared > 0.0f; + } + + private static boolean isFinite(@Nonnull Vector3f vector) { + return Float.isFinite(vector.x) + && Float.isFinite(vector.y) + && Float.isFinite(vector.z); + } + static final class Scratch { private final Vector3f position = new Vector3f(); private final Quaternionf rotation = new Quaternionf(); + private final Quaternionf localRotationOffset = new Quaternionf(); private final Vector3f visualPosition = new Vector3f(); private final Quaternionf visualRotation = new Quaternionf(); private final Vector3f worldOffset = new Vector3f(); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java index c6b5bf1f..8344be42 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java @@ -220,6 +220,28 @@ void rotatingNearDynamicBodiesDoNotUseLowSpeedDeadzone() { assertTrue(rotated.transformChanged()); } + @Test + void nonFiniteSnapshotRotationWritesFiniteFallbackRotation() { + UUID bodyUuid = UUID.randomUUID(); + UUID spaceUuid = UUID.randomUUID(); + TransformComponent transform = new TransformComponent(); + transform.getRotation().set(0.1815971f, 1.2601684f, Float.NaN); + BodyAttachmentComponent attachment = new BodyAttachmentComponent(bodyUuid, + TransformAuthority.BODY, + AttachmentLifecycle.EXTERNAL_ENTITY); + PhysicsSyncSystem.Scratch scratch = new PhysicsSyncSystem.Scratch(); + Quaternionf invalidRotation = new Quaternionf(Float.NaN, 0.0f, 0.0f, 1.0f); + + assertTrue(PhysicsSyncSystem.applyPhysicsStoreSnapshot(transform, + attachment, + snapshot(bodyUuid, spaceUuid, 10.0f, invalidRotation, false), + scratch)); + + assertTrue(Float.isFinite(transform.getRotation().x())); + assertTrue(Float.isFinite(transform.getRotation().y())); + assertTrue(Float.isFinite(transform.getRotation().z())); + } + @Test void visualPositionKeepsCenterOfMassOffsetWorldUp() { Vector3f visualPosition = PhysicsVisualPoseMath.visualPositionFromBodyPose(new Vector3f(10.0f, From 9f008f5215aeda58147b549ec583eb7a61a62438 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 23 Jun 2026 22:01:49 +0200 Subject: [PATCH 527/534] refactor(api): rename backend registry facade Signed-off-by: Blovien --- ...pulse.java => ImpulseBackendRegistry.java} | 4 +- ...> ImpulseBackendRegistryRegistryTest.java} | 28 +++++------ .../impulse/core/ImpulsePlugin.java | 48 +++---------------- .../commands/backend/BackendListCommand.java | 4 +- .../internal/commands/space/SpaceCommand.java | 4 +- .../commands/space/SpaceCreateCommand.java | 4 +- .../systems/binding/SpaceBindingSystem.java | 4 +- ...ndRegistryPluginBackendSelectionTest.java} | 2 +- ...ckendRegistryCommandTreeRegistryTest.java} | 2 +- ...endRegistrySubPluginRegistrationTest.java} | 4 +- ...ontrollableComponentRegistrationTest.java} | 2 +- .../PhysicsStoreHolderPersistenceTest.java | 6 +-- .../systems/BodyBindingSystemTest.java | 15 ++---- .../commands/DirectionalPendulumsCommand.java | 10 ++-- .../DirectionalPendulumsCommandTest.java | 4 +- 15 files changed, 49 insertions(+), 92 deletions(-) rename impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/{Impulse.java => ImpulseBackendRegistry.java} (97%) rename impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/{ImpulseRegistryTest.java => ImpulseBackendRegistryRegistryTest.java} (82%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/{ImpulsePluginBackendSelectionTest.java => ImpulseBackendRegistryPluginBackendSelectionTest.java} (99%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/{ImpulseCommandTreeRegistryTest.java => ImpulseBackendRegistryCommandTreeRegistryTest.java} (95%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/{ImpulseSubPluginRegistrationTest.java => ImpulseBackendRegistrySubPluginRegistrationTest.java} (97%) rename impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/{ImpulseControllableComponentRegistrationTest.java => ImpulseBackendRegistryControllableComponentRegistrationTest.java} (95%) diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java similarity index 97% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java rename to impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java index 2777ba40..c3bde275 100644 --- a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/Impulse.java +++ b/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java @@ -16,7 +16,7 @@ /** * Entry point and backend registry for Impulse. */ -public final class Impulse { +public final class ImpulseBackendRegistry { private static final Logger LOGGER = Logger.getLogger("Impulse"); @@ -29,7 +29,7 @@ public final class Impulse { private static final Set INITIALIZED_BACKENDS = new HashSet<>(); - private Impulse() { + private ImpulseBackendRegistry() { } /** diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java similarity index 82% rename from impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java rename to impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java index 0059c556..5b10f2fe 100644 --- a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseRegistryTest.java +++ b/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java @@ -21,7 +21,7 @@ import javax.annotation.Nonnull; import org.junit.jupiter.api.Test; -class ImpulseRegistryTest { +class ImpulseBackendRegistryRegistryTest { private static final AtomicInteger ID_COUNTER = new AtomicInteger(); private static final int CONCURRENT_RUNTIME_CREATIONS = 4; @@ -29,7 +29,7 @@ class ImpulseRegistryTest { @Test void throwsWhenRequestingUnknownRuntimeProvider() { IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> Impulse.getRuntimeProvider(new BackendId(uniqueId()))); + () -> ImpulseBackendRegistry.getRuntimeProvider(new BackendId(uniqueId()))); assertTrue(exception.getMessage().startsWith("No backend runtime provider registered with id:")); } @@ -37,15 +37,15 @@ void throwsWhenRequestingUnknownRuntimeProvider() { @Test void registersRuntimeProvidersInitializesOnceAndCreatesRuntime() { CountingRuntimeProvider provider = new CountingRuntimeProvider(new BackendId(uniqueId())); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); - PhysicsBackendRuntime firstRuntime = Impulse.createRuntime(provider.getId()); - PhysicsBackendRuntime secondRuntime = Impulse.createRuntime(provider.getId()); + PhysicsBackendRuntime firstRuntime = ImpulseBackendRegistry.createRuntime(provider.getId()); + PhysicsBackendRuntime secondRuntime = ImpulseBackendRegistry.createRuntime(provider.getId()); - assertSame(provider, Impulse.getRuntimeProvider(provider.getId())); + assertSame(provider, ImpulseBackendRegistry.getRuntimeProvider(provider.getId())); assertEquals(1, provider.initCount()); assertEquals(2, provider.createRuntimeCount()); - assertTrue(Impulse.getRuntimeProviders().contains(provider)); + assertTrue(ImpulseBackendRegistry.getRuntimeProviders().contains(provider)); assertTrue(provider.createdRuntimes().contains(firstRuntime)); assertTrue(provider.createdRuntimes().contains(secondRuntime)); } @@ -56,21 +56,21 @@ void replacingRuntimeProviderResetsItsInitializationState() { CountingRuntimeProvider first = new CountingRuntimeProvider(backendId); CountingRuntimeProvider second = new CountingRuntimeProvider(backendId); - Impulse.registerRuntimeProvider(first); - Impulse.createRuntime(backendId); - Impulse.registerRuntimeProvider(second); - Impulse.createRuntime(backendId); + ImpulseBackendRegistry.registerRuntimeProvider(first); + ImpulseBackendRegistry.createRuntime(backendId); + ImpulseBackendRegistry.registerRuntimeProvider(second); + ImpulseBackendRegistry.createRuntime(backendId); assertEquals(1, first.initCount()); assertEquals(1, second.initCount()); assertEquals(1, second.createRuntimeCount()); - assertSame(second, Impulse.getRuntimeProvider(backendId)); + assertSame(second, ImpulseBackendRegistry.getRuntimeProvider(backendId)); } @Test void createsRuntimesConcurrentlyThroughRegistry() throws Exception { CountingRuntimeProvider provider = new CountingRuntimeProvider(new BackendId(uniqueId())); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_RUNTIME_CREATIONS); CountDownLatch ready = new CountDownLatch(CONCURRENT_RUNTIME_CREATIONS); @@ -82,7 +82,7 @@ void createsRuntimesConcurrentlyThroughRegistry() throws Exception { .mapToObj(ignored -> executor.submit(() -> { ready.countDown(); assertTrue(start.await(5, TimeUnit.SECONDS)); - return Impulse.createRuntime(provider.getId()); + return ImpulseBackendRegistry.createRuntime(provider.getId()); })) .toList(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index 928c5a07..bcdabbbc 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -1,18 +1,15 @@ package dev.hytalemodding.impulse.core; import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.common.plugin.PluginIdentifier; import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.HytaleServer; import com.hypixel.hytale.server.core.Options; import com.hypixel.hytale.server.core.command.system.CommandRegistry; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; -import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.plugin.PluginManager; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.ImpulseBackendRegistry; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandTreeRegistry; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; @@ -52,6 +49,7 @@ public BackendId getDefaultBackendId() { @Override protected void setup() { PhysicsStoreEarlyPluginProbe.requireAvailable(); + ComponentRegistryProxy physicsStoreRegistry = PhysicsStoreRegistration.physicsStoreRegistry(this); PhysicsComponentTypeRegistry.registerComponentTypes(physicsStoreRegistry); @@ -62,59 +60,27 @@ protected void setup() { registerCommands(); } - @Override - protected void start() { - registerCrucibleSuites(); - } - @Override protected void shutdown() { ImpulseCommandTreeRegistry.unregister(); } - /** - * Registers optional Crucible suites after Crucible has loaded. - * Core owns these suites because they validate Impulse API and ECS behavior, - * not example command behavior. - */ - private void registerCrucibleSuites() { - try { - PluginManager pluginManager = HytaleServer.get().getPluginManager(); - PluginBase cruciblePlugin = pluginManager.getPlugin( - new PluginIdentifier("com.ionforgelabs", "crucible")); - if (cruciblePlugin == null) { - return; - } - ClassLoader crucibleLoader = ((JavaPlugin) cruciblePlugin).getClassLoader(); - Class suitesClass = Class.forName( - "dev.hytalemodding.impulse.core.internal.crucible.ImpulseCrucibleSuites", - true, - crucibleLoader); - suitesClass.getMethod("register", ClassLoader.class).invoke(null, crucibleLoader); - } catch (ClassNotFoundException e) { - // Crucible is not installed. - } catch (ReflectiveOperationException e) { - LOGGER.at(Level.WARNING) - .log("Failed to register Impulse Crucible suites: %s", e.getMessage()); - } - } - private void discoverBackends() { for (PhysicsBackendRuntimeProvider provider : BackendDiscovery.discoverRuntimeProviders( backendSearchRoots(), getClassLoader())) { - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); } - for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { + for (PhysicsBackendRuntimeProvider provider : ImpulseBackendRegistry.getRuntimeProviders()) { LOGGER.at(Level.INFO).log("Registered physics backend runtime %s", provider.getId()); } - if (Impulse.getRuntimeProviders().isEmpty()) { + if (ImpulseBackendRegistry.getRuntimeProviders().isEmpty()) { throw new IllegalStateException("No physics backends discovered"); } - defaultBackendId = selectDefaultRuntimeProviderId(Impulse.getRuntimeProviders()); + defaultBackendId = selectDefaultRuntimeProviderId(ImpulseBackendRegistry.getRuntimeProviders()); if (defaultBackendId != null) { LOGGER.at(Level.INFO).log("Using default physics backend %s", defaultBackendId); return; @@ -146,7 +112,7 @@ static BackendId selectDefaultRuntimeProviderId( @Nonnull private String getAvailableBackendIds() { StringBuilder ids = new StringBuilder(); - for (PhysicsBackendRuntimeProvider backend : Impulse.getRuntimeProviders()) { + for (PhysicsBackendRuntimeProvider backend : ImpulseBackendRegistry.getRuntimeProviders()) { if (!ids.isEmpty()) { ids.append(", "); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/backend/BackendListCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/backend/BackendListCommand.java index 3fa4bd1f..6786be59 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/backend/BackendListCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/backend/BackendListCommand.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.ImpulseBackendRegistry; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import java.util.ArrayList; import java.util.List; @@ -29,7 +29,7 @@ protected CompletableFuture executeAsync(@Nonnull CommandContext ctx, @Nonnull PlayerRef playerRef, @Nonnull World world) { List backendIds = new ArrayList<>(); - for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { + for (PhysicsBackendRuntimeProvider provider : ImpulseBackendRegistry.getRuntimeProviders()) { backendIds.add(provider.getId().value()); } backendIds.sort(String::compareTo); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java index 18cf5183..acedfd9d 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommand.java @@ -5,7 +5,7 @@ import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.ImpulseBackendRegistry; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.ImpulsePlugin; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; @@ -66,7 +66,7 @@ static PhysicsChunkCollisionMode parsePhysicsChunkMode(@Nonnull String value) { @Nonnull private static String availableBackendIds() { List backendIds = new ArrayList<>(); - for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { + for (PhysicsBackendRuntimeProvider provider : ImpulseBackendRegistry.getRuntimeProviders()) { backendIds.add(provider.getId().value()); } backendIds.sort(String::compareTo); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java index 3076fd86..c680ff70 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCreateCommand.java @@ -10,7 +10,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.ImpulseBackendRegistry; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; @@ -54,7 +54,7 @@ protected void execute(@Nonnull CommandContext context, Store physicsStore = PhysicsThreading.store(world); try { - Impulse.getRuntimeProvider(backendId); + ImpulseBackendRegistry.getRuntimeProvider(backendId); SpaceId spaceId = PhysicsSpaces.create(physicsStore, backendId); PhysicsChunkCollisionSettings chunkCollisionSettings = new PhysicsChunkCollisionSettings(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java index 310a0f4d..a152197f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystem.java @@ -12,7 +12,7 @@ import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.ImpulseBackendRegistry; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; @@ -129,7 +129,7 @@ private static void bindSpace(@Nonnull PhysicsRuntimeResource runtime, PhysicsBackendRuntime backendRuntime = runtime.getRuntime(backendId); if (backendRuntime == null) { try { - backendRuntime = Impulse.createRuntime(backendId); + backendRuntime = ImpulseBackendRegistry.createRuntime(backendId); } catch (RuntimeException exception) { restore.markFailed("PhysicsStore space " + spaceUuid + " references unavailable backend id " + backendId.value()); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulseBackendRegistryPluginBackendSelectionTest.java similarity index 99% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulseBackendRegistryPluginBackendSelectionTest.java index 8b75602a..ad6a4d11 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulsePluginBackendSelectionTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulseBackendRegistryPluginBackendSelectionTest.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -class ImpulsePluginBackendSelectionTest { +class ImpulseBackendRegistryPluginBackendSelectionTest { private static final String RUNTIME_PROVIDER_SERVICE = "META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider"; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseBackendRegistryCommandTreeRegistryTest.java similarity index 95% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseBackendRegistryCommandTreeRegistryTest.java index 93637bd1..82a07f65 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseCommandTreeRegistryTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/ImpulseBackendRegistryCommandTreeRegistryTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -class ImpulseCommandTreeRegistryTest { +class ImpulseBackendRegistryCommandTreeRegistryTest { @AfterEach void resetRegistry() { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java similarity index 97% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java index 649bc752..ee0fee39 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseSubPluginRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java @@ -20,7 +20,7 @@ import javax.annotation.Nonnull; import org.junit.jupiter.api.Test; -class ImpulseSubPluginRegistrationTest { +class ImpulseBackendRegistrySubPluginRegistrationTest { @Test void generatedManifestSubPluginsSupportHytalePendingLoadInheritance() throws IOException { @@ -119,7 +119,7 @@ private static PluginManifest manifest(String group, } private static PluginManifest decodeGeneratedManifest() throws IOException { - InputStream stream = ImpulseSubPluginRegistrationTest.class + InputStream stream = ImpulseBackendRegistrySubPluginRegistrationTest.class .getClassLoader() .getResourceAsStream("manifest.json"); assertNotNull(stream); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseControllableComponentRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseBackendRegistryControllableComponentRegistrationTest.java similarity index 95% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseControllableComponentRegistrationTest.java rename to impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseBackendRegistryControllableComponentRegistrationTest.java index 64deed29..45fea058 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseControllableComponentRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseBackendRegistryControllableComponentRegistrationTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -class ImpulseControllableComponentRegistrationTest { +class ImpulseBackendRegistryControllableComponentRegistrationTest { @AfterEach void clearRegistration() { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java index d097be73..f13cb132 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java @@ -19,7 +19,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.BsonUtil; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.ImpulseBackendRegistry; import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; @@ -286,7 +286,7 @@ void holderHydrationRejectsBodyWithoutSavedSpaceWithoutAddingPartialRows() { void registeredStoreReloadBindsSavedBodiesOnce() { FakePhysicsBackendRuntimeProvider provider = new FakePhysicsBackendRuntimeProvider(HOLDER_BACKEND_ID, false, false); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); Path savePath = tempDir.resolve("registered-reload"); StoreFixture source = registeredStore("registered-reload-source", savePath); @@ -358,7 +358,7 @@ void registeredStoreReloadBindsSavedBodiesOnce() { void holderHydrationClosesStaleUntrackedBackendRuntimeBeforeRebinding() { FakePhysicsBackendRuntimeProvider provider = new FakePhysicsBackendRuntimeProvider(HOLDER_BACKEND_ID, false, false); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); Path savePath = tempDir.resolve("stale-backend-runtime"); SpaceId compatibilitySpaceId = new SpaceId(2); diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java index 02676a73..4b2a1f91 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java @@ -14,12 +14,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsAxis; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.*; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; @@ -66,7 +61,7 @@ void nonVoxelBodyBindingDoesNotRequirePhysicsChunkPayloadResource() { PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); FakePhysicsBackendRuntimeProvider provider = new FakePhysicsBackendRuntimeProvider(BACKEND_ID, false, false); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); @@ -113,7 +108,7 @@ void nonVoxelBodyBindingSeedsInitialPropertiesWithoutSeparateMutationCalls() { PhysicsChunkStoreTypes.clearPhysicsStoreResourceTypes(); FakePhysicsBackendRuntimeProvider provider = new FakePhysicsBackendRuntimeProvider(BACKEND_ID, true, false); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); @@ -180,7 +175,7 @@ void chunkRestoreDependencyWaitsWithoutBlockingUnrelatedBodies() { PhysicsChunkLifecycle.enable(); FakePhysicsBackendRuntimeProvider provider = new FakePhysicsBackendRuntimeProvider(BACKEND_ID, false, false); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); @@ -247,7 +242,7 @@ void chunkRestoreDependencyBindsWhenGeneratedSupportRowExists() { PhysicsChunkLifecycle.enable(); FakePhysicsBackendRuntimeProvider provider = new FakePhysicsBackendRuntimeProvider(BACKEND_ID, false, false); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java index 3cd0a32e..df4765f8 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommand.java @@ -15,11 +15,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.EventTitleUtil; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.*; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; import dev.hytalemodding.impulse.core.plugin.components.JointType; @@ -527,7 +523,7 @@ private BackendSelection resolveBackendSelection(@Nonnull CommandContext ctx) { private static boolean backendRegistered(@Nonnull BackendId backendId) { try { - Impulse.getRuntimeProvider(backendId); + ImpulseBackendRegistry.getRuntimeProvider(backendId); return true; } catch (RuntimeException exception) { return false; @@ -537,7 +533,7 @@ private static boolean backendRegistered(@Nonnull BackendId backendId) { @Nonnull private static String availableBackendIds() { List backendIds = new ArrayList<>(); - for (PhysicsBackendRuntimeProvider provider : Impulse.getRuntimeProviders()) { + for (PhysicsBackendRuntimeProvider provider : ImpulseBackendRegistry.getRuntimeProviders()) { backendIds.add(provider.getId().value()); } backendIds.sort(String::compareTo); diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java index 9797d080..9233a522 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java @@ -15,7 +15,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.thread.TickingThread; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; +import dev.hytalemodding.impulse.api.ImpulseBackendRegistry; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; @@ -299,7 +299,7 @@ void spawnCinematicPresetUsesStrongerLateralInitialMotion() { void storeTickAppliesDirectionalSpaceGravitiesWithoutChangingThem() { FakePhysicsBackendRuntimeProvider provider = new FakePhysicsBackendRuntimeProvider(BINDING_BACKEND_ID, false, false); - Impulse.registerRuntimeProvider(provider); + ImpulseBackendRegistry.registerRuntimeProvider(provider); ComponentRegistry registry = new ComponentRegistry<>(); ComponentRegistryProxy proxy = new ComponentRegistryProxy<>(new ArrayList<>(), registry); From 71728f22ffa0d1e3ba51c3a5321050b5d93b9e89 Mon Sep 17 00:00:00 2001 From: Blovien Date: Tue, 23 Jun 2026 22:02:20 +0200 Subject: [PATCH 528/534] test(core): replace crucible with junit coverage Signed-off-by: Blovien --- .github/ISSUE_TEMPLATE/bug_report.yml | 8 +- CONTRIBUTING.md | 10 +- README.md | 9 +- gradle/libs.versions.toml | 2 - impulse-core/README.md | 34 +- impulse-core/build.gradle.kts | 9 - .../crucible/BenchmarkSpaceStatsView.java | 22 - .../ControlSubPluginCrucibleSupport.java | 165 --- .../internal/crucible/CrucibleBackends.java | 71 -- .../internal/crucible/CrucibleBridge.java | 197 ---- .../internal/crucible/CrucibleContext.java | 77 -- .../core/internal/crucible/CrucibleSuite.java | 15 - .../internal/crucible/CrucibleTestCase.java | 65 -- .../crucible/ImpulseApiCrucibleTests.java | 621 ----------- .../crucible/ImpulseCrucibleSuites.java | 26 - ...tachedStreamingBenchmarkCrucibleTests.java | 992 ------------------ .../crucible/ImpulseLiveCrucibleTests.java | 176 ---- ...pulseRapierBodyBenchmarkCrucibleTests.java | 830 --------------- .../PhysicsChunkSubPluginCrucibleSupport.java | 88 -- ...PhysicsEntitySubPluginCrucibleSupport.java | 112 -- .../PhysicsStoreBenchmarkQueries.java | 161 --- .../crucible/PhysicsStoreCrucibleSupport.java | 69 -- .../physics/PhysicsStoreRowCleanup.java | 6 +- .../resources/PhysicsRuntimeResource.java | 25 + impulse-core/src/module-info/module-info.java | 1 - .../crucible/CrucibleBackendsTest.java | 57 - .../PhysicsEntityLifecycleTest.java | 60 ++ .../systems/StaleBodyRemovalSystemTest.java | 53 + .../PhysicsSpacesSettingsComponentTest.java | 186 +++- .../rapier/RapierBodyDynamicsTest.java | 250 +++++ scripts/ci/install-crucible-runtime.sh | 50 - settings.gradle.kts | 4 - 32 files changed, 596 insertions(+), 3855 deletions(-) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ControlSubPluginCrucibleSupport.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackends.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBridge.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleContext.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleSuite.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleTestCase.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseCrucibleSuites.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsChunkSubPluginCrucibleSupport.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java delete mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java create mode 100644 impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycleTest.java create mode 100644 impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java delete mode 100755 scripts/ci/install-crucible-runtime.sh diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 67ef3d7c..29d238d4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -34,11 +34,11 @@ body: label: Validation already run placeholder: "./gradlew headlessTest" - type: textarea - id: crucible-test + id: runtime-reproduction attributes: - label: Crucible reproduction - description: If this is a runtime/server bug, include the failing Crucible test or describe the test case that should be added. - placeholder: "Example: add a focused Crucible smoke that unloads/reloads the affected plugin after spawning the failing entity state." + label: Runtime reproduction + description: If this is a runtime/server bug, include the focused regression test or describe the manual runAllMods scenario. + placeholder: "Example: runAllMods, create space --backend=impulse:rapier, spawn the failing entity state, then unload/reload the affected plugin." - type: textarea id: logs attributes: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61b1d8f4..3630226c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,8 @@ Open an issue or design discussion before changing architecture, persistence, li backend runtime contracts, public API packages or native artifact packaging. For bug reports, include a clear reproduction path and logs. When the bug depends on Hytale server -runtime behavior, also try to add or propose a failing Crucible test so that it can be reproduced in-game. +runtime behavior, also try to add or propose a focused JUnit/headless regression test, or document +the `runAllMods` world setup and commands needed to reproduce it. ## AI-Assisted Contributions @@ -67,8 +68,9 @@ Runtime/server behavior should be checked with: ./gradlew runAllMods ``` -When a runtime bug is fixed, prefer adding a focused Crucible test that reproduces the failing -server behavior. +When a runtime bug is fixed, prefer adding a focused module test that reproduces the failing +behavior. If the bug only exists in a live Hytale server loop, document the manual `runAllMods` +scenario used for validation. Backend-specific changes should name the backend used for validation, such as `impulse:bullet` or -`impulse:rapier`. \ No newline at end of file +`impulse:rapier`. diff --git a/README.md b/README.md index b8cc2148..06c4e3f1 100644 --- a/README.md +++ b/README.md @@ -194,10 +194,13 @@ Impulse has a dedicated headless/serverless test lane that does not boot the Hyt ./gradlew headlessTest ``` -Crucible in-game tests are also provided. Run them in game with: +For runtime/server behavior, use focused module tests first and then reproduce manually with the +Hytale runtime when the bug depends on plugin loading, live worlds, or command behavior: -``` -/crucible run +```bash +./gradlew :impulse-core:test +./gradlew :impulse-rapier:test +./gradlew runAllMods ``` ## Native Binary Notice diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b461370e..4d334f93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] libbulletjme = "23.0.0" -crucible = "1.0.0" hytale-gradle = "1.0.41" lombok = "1.18.38" joml = "1.10.5" @@ -12,7 +11,6 @@ objenesis = "3.4" libbulletjme = { module = "com.github.stephengold:Libbulletjme-Linux64", version.ref = "libbulletjme" } # TODO: Linux and others... libbulletjme-native = { module = "com.github.stephengold:Libbulletjme-Linux64", version.ref = "libbulletjme" } -crucible = { module = "com.ionforgelabs:crucible", version.ref = "crucible" } lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } joml = { module = "org.joml:joml", version.ref = "joml" } jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" } diff --git a/impulse-core/README.md b/impulse-core/README.md index e09f4bbe..4c3c1d5e 100644 --- a/impulse-core/README.md +++ b/impulse-core/README.md @@ -46,34 +46,8 @@ the exported profile includes Hytale world/store tick threads and PhysicsStore c Avoid contact debug rendering during benchmark captures; it calls backend contact enumeration and will distort the hot path. -## Crucible tests +## Runtime validation -Install the patched Crucible runtime jar: - -```bash -./scripts/ci/install-crucible-runtime.sh -``` - -Run the smoke-tagged runtime suite: - -```bash -JAVA_TOOL_OPTIONS="-Dcrucible.autorun=true -Dcrucible.tags=smoke" \ - ./gradlew runAllMods -``` - -Run the live-tagged runtime suite: - -```bash -JAVA_TOOL_OPTIONS="-Dcrucible.autorun=true -Dcrucible.tags=live" \ - ./gradlew runAllMods -``` - -Run the detached full-collision streaming benchmark scenario: - -```bash -JAVA_TOOL_OPTIONS="-Dcrucible.autorun=true -Dcrucible.tags=benchmark" \ - ./gradlew runAllMods -``` - -Crucible selects `impulse:rapier` when it is installed. Override that only for backend-specific -debugging with `-Dimpulse.crucible.backend=`. +Use ordinary Gradle tests for backend physics, PhysicsStore topology, settings round trips, and +module lifecycle predicates. When a failure depends on live Hytale server behavior, reproduce it +with `./gradlew runAllMods` and document the world setup and commands used. diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index c15aa59c..c127bfa2 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -4,12 +4,6 @@ plugins { version = rootProject.version -repositories { - maven { - url = uri("https://gitlab.com/api/v4/projects/82033924/packages/maven") - } -} - val coreModuleName = "dev.hytalemodding.impulse.core" // These parent dependencies are shared by the core plugin and inherited by bundled subplugins. val impulseManifestDependencies = listOf( @@ -27,12 +21,10 @@ val moduleInfoModulePath by configurations.creating { dependencies { implementation(project(":impulse-backend-api")) compileOnly(project(":impulse-early-plugin")) - compileOnly(libs.crucible) compileOnly(libs.lombok) moduleInfoModulePath(libs.joml) moduleInfoModulePath(libs.jsr305) - moduleInfoModulePath(libs.crucible) annotationProcessor(libs.lombok) @@ -102,7 +94,6 @@ hytaleTools { modDescription = property("mod_description") as String manifestServerVersion = property("hytale_version") as String manifestDependencies = impulseManifestDependencies - manifestOptionalDependencies = "com.ionforgelabs:crucible=*" subPlugin ( "ImpulseControl", diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java deleted file mode 100644 index 8b3076e1..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/BenchmarkSpaceStatsView.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -/** - * Copied store tick lane counters used by stress and fall-envelope diagnostics. - */ -public record BenchmarkSpaceStatsView(int bodies, - int dynamicBodies, - int awakeDynamicBodies, - int sleepingDynamicBodies, - int detachedBodies, - int rawBodies, - int terrainBodies, - int belowPlaneBodies, - int belowTerrainBodies, - int belowWorldMinBodies, - int belowVoidBodies, - int terrainBaselineBodies, - int missingTerrainBaselineBodies, - float minTerrainBottomClearance, - float minDynamicBodyY, - float maxDynamicBodyY) { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ControlSubPluginCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ControlSubPluginCrucibleSupport.java deleted file mode 100644 index 3ea76160..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ControlSubPluginCrucibleSupport.java +++ /dev/null @@ -1,165 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.common.plugin.PluginIdentifier; -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.entity.entities.BlockEntity; -import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; -import com.hypixel.hytale.server.core.modules.time.TimeResource; -import com.hypixel.hytale.server.core.plugin.PluginBase; -import com.hypixel.hytale.server.core.plugin.PluginManager; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; -import org.joml.Vector3d; - -/** - * Runtime-only helpers for exercising the control subplugin through Hytale. - */ -final class ControlSubPluginCrucibleSupport { - - private static final PluginIdentifier PLUGIN_ID = - new PluginIdentifier("HytaleModding", "ImpulseControl"); - private static final String DEFAULT_BLOCK_TYPE = "Rock_Stone"; - private static final long SMOKE_TIMEOUT_SECONDS = 30L; - private static final ComponentType DESPAWN_TYPE = - DespawnComponent.getComponentType(); - - private ControlSubPluginCrucibleSupport() { - } - - @Nonnull - static CompletionStage loadUnloadReloadSmokeAsync( - @Nonnull CrucibleContext context) { - return CompletableFuture.supplyAsync(() -> loadUnloadReloadSmoke(context)) - .orTimeout(SMOKE_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .exceptionally(failure -> CrucibleTestCase.TestOutcome.fail( - "Control subplugin lifecycle smoke failed: " + failure.getMessage())); - } - - private static CrucibleTestCase.TestOutcome loadUnloadReloadSmoke( - @Nonnull CrucibleContext context) { - try { - PluginManager pluginManager = PluginManager.get(); - if (!pluginManager.getAvailablePlugins().containsKey(PLUGIN_ID) - && pluginManager.getPlugin(PLUGIN_ID) == null) { - return CrucibleTestCase.TestOutcome.fail( - "Control subplugin is not available: " + PLUGIN_ID); - } - - if (!ensureLoaded(pluginManager)) { - return CrucibleTestCase.TestOutcome.fail( - "Control subplugin load did not enable the lifecycle"); - } - Ref controllableRef = addControllableEntity(context); - if (!controllableRef.isValid()) { - return CrucibleTestCase.TestOutcome.fail("Failed to add controllable test entity"); - } - if (!pluginManager.unload(PLUGIN_ID)) { - return CrucibleTestCase.TestOutcome.fail( - "Control subplugin unload returned false"); - } - if (pluginManager.getPlugin(PLUGIN_ID) != null) { - return CrucibleTestCase.TestOutcome.fail( - "Control subplugin remained loaded after unload"); - } - if (ControlLifecycle.isEnabled()) { - return CrucibleTestCase.TestOutcome.fail( - "Control subplugin unload did not disable the lifecycle"); - } - boolean loadResult = pluginManager.load(PLUGIN_ID); - PluginBase loadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!loadResult || !ControlLifecycle.isEnabled()) { - return CrucibleTestCase.TestOutcome.fail( - "Control subplugin reload load did not enable the lifecycle: " - + "loadResult=" + loadResult - + ", pluginState=" + stateOf(loadedPlugin) - + ", lifecycleEnabled=" + ControlLifecycle.isEnabled()); - } - boolean reloadResult = pluginManager.reload(PLUGIN_ID); - PluginBase reloadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!reloadResult || !ControlLifecycle.isEnabled()) { - return CrucibleTestCase.TestOutcome.fail( - "Control subplugin reload did not leave the lifecycle enabled: " - + "reloadResult=" + reloadResult - + ", pluginState=" + stateOf(reloadedPlugin) - + ", lifecycleEnabled=" + ControlLifecycle.isEnabled()); - } - return CrucibleTestCase.TestOutcome.pass(); - } catch (ReflectiveOperationException | RuntimeException exception) { - return CrucibleTestCase.TestOutcome.fail(exception.getMessage()); - } - } - - private static boolean ensureLoaded(@Nonnull PluginManager pluginManager) { - if (pluginManager.getPlugin(PLUGIN_ID) != null && ControlLifecycle.isEnabled()) { - return true; - } - return pluginManager.load(PLUGIN_ID) && ControlLifecycle.isEnabled(); - } - - @Nonnull - private static Ref addControllableEntity(@Nonnull CrucibleContext context) - throws ReflectiveOperationException { - World world = context.world(); - if (world.isInThread()) { - return addControllableEntityOnWorldThread(context, world); - } - - CompletableFuture> entity = new CompletableFuture<>(); - try { - world.execute(() -> { - try { - entity.complete(addControllableEntityOnWorldThread(context, world)); - } catch (Throwable throwable) { - entity.completeExceptionally(throwable); - } - }); - } catch (RuntimeException exception) { - entity.completeExceptionally(exception); - } - - try { - return entity.orTimeout(SMOKE_TIMEOUT_SECONDS, TimeUnit.SECONDS).join(); - } catch (CompletionException exception) { - Throwable cause = exception.getCause(); - if (cause instanceof ReflectiveOperationException reflectiveOperationException) { - throw reflectiveOperationException; - } - throw exception; - } - } - - @Nonnull - private static Ref addControllableEntityOnWorldThread( - @Nonnull CrucibleContext context, - @Nonnull World world) throws ReflectiveOperationException { - Store store = world.getEntityStore().getStore(); - TimeResource time = store.getResource(TimeResource.getResourceType()); - Holder holder = BlockEntity.assembleDefaultBlockEntity( - time, - DEFAULT_BLOCK_TYPE, - new Vector3d(context.wx(0), context.wy(8), context.wz(0))); - holder.removeComponent(DESPAWN_TYPE); - holder.addComponent(ImpulseControllableComponent.getComponentType(), - new ImpulseControllableComponent()); - holder.addComponent(PhysicsControlSessionComponent.getComponentType(), - new PhysicsControlSessionComponent()); - return store.addEntity(holder, AddReason.SPAWN); - } - - @Nonnull - private static String stateOf(PluginBase plugin) { - return plugin == null ? "missing" : plugin.getState().name(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackends.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackends.java deleted file mode 100644 index 313c2953..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackends.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -final class CrucibleBackends { - - private static final String BACKEND_PROPERTY = "impulse.crucible.backend"; - private static final BackendId RAPIER_BACKEND_ID = new BackendId("impulse:rapier"); - - private CrucibleBackends() { - } - - @Nonnull - static BackendId requireBackendId() { - return selectBackendId(Impulse.getRuntimeProviders(), System.getProperty(BACKEND_PROPERTY)); - } - - @Nonnull - static BackendId selectBackendId(@Nonnull Collection providers, - @Nullable String configuredBackendId) { - List backendIds = new ArrayList<>(); - for (PhysicsBackendRuntimeProvider provider : providers) { - backendIds.add(provider.getId()); - } - backendIds.sort(Comparator.comparing(BackendId::value)); - - if (backendIds.isEmpty()) { - throw new IllegalStateException("No physics backend runtimes registered"); - } - - String configured = configuredBackendId == null ? "" : configuredBackendId.trim(); - if (!configured.isEmpty()) { - BackendId requestedBackendId = new BackendId(configured); - if (backendIds.contains(requestedBackendId)) { - return requestedBackendId; - } - throw new IllegalStateException("Configured Crucible physics backend " - + requestedBackendId + " is not registered. Available backend runtimes: " - + formatBackendIds(backendIds)); - } - - if (backendIds.contains(RAPIER_BACKEND_ID)) { - return RAPIER_BACKEND_ID; - } - - if (backendIds.size() == 1) { - return backendIds.getFirst(); - } - - throw new IllegalStateException("Multiple physics backends are registered and Rapier is " - + "not available. Set -D" + BACKEND_PROPERTY + "=. Available backends: " - + formatBackendIds(backendIds)); - } - - @Nonnull - private static String formatBackendIds(@Nonnull List backendIds) { - List values = new ArrayList<>(); - for (BackendId backendId : backendIds) { - values.add(backendId.value()); - } - return String.join(", ", values); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBridge.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBridge.java deleted file mode 100644 index f4380215..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBridge.java +++ /dev/null @@ -1,197 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import dev.hytalemodding.impulse.core.internal.crucible.CrucibleTestCase.TestOutcome; - -/** - * Reflective adapter for Crucible's API. - * - *

        No class in impulse-examples should statically reference Crucible API - * types because Crucible is an optional directory-loaded mod with its own - * plugin classloader.

        - */ -final class CrucibleBridge { - - private final Class asyncTestSuiteClass; - private final Method registerSuite; - private final Method pass; - private final Method fail; - private final Method error; - - private CrucibleBridge( - Class asyncTestSuiteClass, - Method registerSuite, - Method pass, - Method fail, - Method error) { - - this.asyncTestSuiteClass = asyncTestSuiteClass; - this.registerSuite = registerSuite; - this.pass = pass; - this.fail = fail; - this.error = error; - } - - static CrucibleBridge create(ClassLoader loader) throws ReflectiveOperationException { - Class apiClass = Class.forName( - "ionforgelabs.crucible.api.CrucibleAPI", true, loader); - Class testSuiteClass = Class.forName( - "ionforgelabs.crucible.api.TestSuite", true, loader); - Class asyncTestSuiteClass = Class.forName( - "ionforgelabs.crucible.api.AsyncTestSuite", true, loader); - Class resultClass = Class.forName( - "ionforgelabs.crucible.api.TestResult", true, loader); - - return new CrucibleBridge( - asyncTestSuiteClass, - apiClass.getMethod("registerSuite", testSuiteClass), - resultClass.getMethod("pass", String.class, String.class), - resultClass.getMethod("fail", String.class, String.class, String.class), - resultClass.getMethod("error", String.class, String.class, Exception.class)); - } - - void registerSuite(ClassLoader loader, CrucibleSuite suite) - throws ReflectiveOperationException { - - Object proxy = Proxy.newProxyInstance(loader, - new Class[] {asyncTestSuiteClass}, - new SuiteInvocationHandler(this, suite)); - registerSuite.invoke(null, proxy); - } - - private Object pass(String suite, String test) throws ReflectiveOperationException { - return pass.invoke(null, suite, test); - } - - private Object fail(String suite, String test, String message) - throws ReflectiveOperationException { - - return fail.invoke(null, suite, test, message); - } - - private Object error(String suite, String test, Exception exception) - throws ReflectiveOperationException { - - return error.invoke(null, suite, test, exception); - } - - private record SuiteInvocationHandler(CrucibleBridge bridge, CrucibleSuite suite) implements - InvocationHandler { - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return switch (method.getName()) { - case "id", "toString" -> suite.id(); - case "name" -> suite.name(); - case "description" -> suite.description(); - case "tags" -> suite.tags(); - case "run" -> runTests(context(args)); - case "runAsync" -> runTestsAsync(context(args)); - case "hashCode" -> suite.id().hashCode(); - case "equals" -> args != null && args.length > 0 && proxy == args[0]; - default -> throw new UnsupportedOperationException( - "Unsupported Crucible TestSuite method: " + method.getName()); - }; - } - - private CrucibleContext context(Object[] args) { - Object rawContext = args != null && args.length > 0 ? args[0] : null; - return new CrucibleContext(rawContext); - } - - private List runTests(CrucibleContext context) - throws ReflectiveOperationException { - - List results = new ArrayList<>(); - for (CrucibleTestCase test : suite.tests()) { - results.add(runTestSync(context, test)); - } - return results; - } - - private CompletableFuture> runTestsAsync(CrucibleContext context) { - CompletableFuture> results = CompletableFuture.completedFuture( - new ArrayList<>()); - for (CrucibleTestCase test : suite.tests()) { - results = results.thenCompose(current -> runTestAsync(context, test) - .thenApply(result -> { - current.add(result); - return current; - })); - } - return results; - } - - private Object runTestSync(CrucibleContext context, CrucibleTestCase test) - throws ReflectiveOperationException { - - try { - TestOutcome outcome = test.body().run(context).toCompletableFuture().join(); - if (outcome.passed()) { - return bridge.pass(suite.id(), test.name()); - } - return bridge.fail(suite.id(), - test.name(), - failureMessage(test, outcome)); - } catch (CompletionException e) { - return bridge.error(suite.id(), test.name(), asException(e)); - } catch (Exception e) { - return bridge.error(suite.id(), test.name(), e); - } - } - - private CompletableFuture runTestAsync(CrucibleContext context, - CrucibleTestCase test) { - try { - return test.body().run(context).handle((outcome, failure) -> { - try { - if (failure != null) { - return bridge.error(suite.id(), test.name(), asException(failure)); - } - if (outcome != null && outcome.passed()) { - return bridge.pass(suite.id(), test.name()); - } - return bridge.fail(suite.id(), - test.name(), - failureMessage(test, outcome)); - } catch (ReflectiveOperationException e) { - throw new CompletionException(e); - } - }).toCompletableFuture(); - } catch (Exception e) { - try { - return CompletableFuture.completedFuture( - bridge.error(suite.id(), test.name(), e)); - } catch (ReflectiveOperationException reflective) { - return CompletableFuture.failedFuture(reflective); - } - } - } - - private static String failureMessage(CrucibleTestCase test, - TestOutcome outcome) { - if (outcome != null && outcome.failureMessage() != null - && !outcome.failureMessage().isBlank()) { - return outcome.failureMessage(); - } - return test.failureMessage(); - } - - private static Exception asException(Throwable failure) { - Throwable current = failure; - if (current instanceof CompletionException && current.getCause() != null) { - current = current.getCause(); - } - if (current instanceof Exception exception) { - return exception; - } - return new RuntimeException(current); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleContext.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleContext.java deleted file mode 100644 index b547cf93..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleContext.java +++ /dev/null @@ -1,77 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.server.core.universe.world.World; -import java.lang.reflect.Method; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * Reflection wrapper around Crucible's TestContext. - */ -final class CrucibleContext { - - private static final long MILLIS_PER_TICK = 50L; - private static final ScheduledExecutorService DELAYED_WORLD_CALLBACKS = - Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread thread = new Thread(runnable, "Impulse Crucible delayed callbacks"); - thread.setDaemon(true); - return thread; - }); - - private final Object handle; - - CrucibleContext(Object handle) { - this.handle = handle; - } - - World world() throws ReflectiveOperationException { - Method getWorld = handle.getClass().getMethod("getWorld"); - return (World) getWorld.invoke(handle); - } - - int wx(int value) throws ReflectiveOperationException { - return coordinate("wx", value); - } - - int wy(int value) throws ReflectiveOperationException { - return coordinate("wy", value); - } - - int wz(int value) throws ReflectiveOperationException { - return coordinate("wz", value); - } - - @SuppressWarnings("unchecked") - CompletionStage waitTicks(int ticks) throws ReflectiveOperationException { - Method waitTicks = handle.getClass().getMethod("waitTicks", int.class); - return (CompletionStage) waitTicks.invoke(handle, ticks); - } - - /** - * Waits approximately long enough for the test world to tick, then resumes on - * the world thread with one queued task. Crucible's built-in waitTicks helper - * requeues itself through World.execute while the same task queue is draining, - * which prevents the world tick counter from advancing on current Hytale builds. - */ - CompletionStage waitApproxTicksOnWorld(int ticks) throws ReflectiveOperationException { - World world = world(); - CompletableFuture future = new CompletableFuture<>(); - long delayMillis = Math.max(1L, ticks) * MILLIS_PER_TICK; - DELAYED_WORLD_CALLBACKS.schedule(() -> { - try { - world.execute(() -> future.complete(null)); - } catch (Exception e) { - future.completeExceptionally(e); - } - }, delayMillis, TimeUnit.MILLISECONDS); - return future; - } - - private int coordinate(String methodName, int value) throws ReflectiveOperationException { - Method method = handle.getClass().getMethod(methodName, int.class); - return (int) method.invoke(handle, value); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleSuite.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleSuite.java deleted file mode 100644 index 16a98b88..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleSuite.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import java.util.List; -import java.util.Set; - -/** - * Backend-neutral description of one Crucible suite. - */ -record CrucibleSuite( - String id, - String name, - String description, - Set tags, - List tests) { -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleTestCase.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleTestCase.java deleted file mode 100644 index b592d2d0..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleTestCase.java +++ /dev/null @@ -1,65 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -/** - * One proxy-backed Crucible test case. - */ -record CrucibleTestCase( - String name, - ContextualTest body, - String failureMessage) { - - static CrucibleTestCase sync(String name, BooleanTest body, String failureMessage) { - return new CrucibleTestCase(name, - context -> CompletableFuture.completedFuture(TestOutcome.from(body.run())), - failureMessage); - } - - static CrucibleTestCase async(String name, - ContextualBooleanTest body, - String failureMessage) { - - return new CrucibleTestCase(name, - context -> body.run(context).thenApply(TestOutcome::from), - failureMessage); - } - - static CrucibleTestCase asyncResult(String name, - ContextualTest body, - String failureMessage) { - - return new CrucibleTestCase(name, body, failureMessage); - } - - @FunctionalInterface - interface BooleanTest { - boolean run(); - } - - @FunctionalInterface - interface ContextualBooleanTest { - CompletionStage run(CrucibleContext context); - } - - @FunctionalInterface - interface ContextualTest { - CompletionStage run(CrucibleContext context); - } - - record TestOutcome(boolean passed, String failureMessage) { - - static TestOutcome pass() { - return new TestOutcome(true, ""); - } - - static TestOutcome fail(String failureMessage) { - return new TestOutcome(false, failureMessage); - } - - static TestOutcome from(boolean passed) { - return passed ? pass() : fail(""); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java deleted file mode 100644 index ccfbe5c7..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseApiCrucibleTests.java +++ /dev/null @@ -1,621 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.ImpulsePlugin; -import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodySnapshots; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsDiagnostics; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; -import java.util.UUID; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.joml.Vector3f; - -/** - * Crucible suites that exercise Impulse API behavior inside a live Hytale server. - */ -final class ImpulseApiCrucibleTests { - - private static final PhysicsBackendExtensionId RAPIER_SOLVER_EXTENSION_ID = - new PhysicsBackendExtensionId("impulse:rapier_solver"); - private static final String RAPIER_INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; - private static final String RAPIER_MIN_ISLAND_SIZE = "minIslandSize"; - - private ImpulseApiCrucibleTests() { - } - - static void register(CrucibleBridge bridge, ClassLoader loader) - throws ReflectiveOperationException { - - bridge.registerSuite(loader, smokeSuite()); - bridge.registerSuite(loader, runtimeStabilitySuite()); - bridge.registerSuite(loader, transformSyncSuite()); - bridge.registerSuite(loader, terrainCollisionSuite()); - } - - private static CrucibleSuite smokeSuite() { - return new CrucibleSuite( - "impulse:smoke", - "Impulse Smoke", - "Verifies plugin load, backend registration, and basic API operations", - Set.of("smoke"), - List.of( - CrucibleTestCase.sync("plugin loaded", () -> ImpulsePlugin.get() != null, - "ImpulsePlugin singleton is null"), - CrucibleTestCase.sync("backends registered", () -> { - Collection providers = Impulse.getRuntimeProviders(); - return !providers.isEmpty(); - }, "No physics backend runtimes registered"), - CrucibleTestCase.sync("test backend selectable", () -> - { - CrucibleBackends.requireBackendId(); - return true; - }, - "No backend id is available for Crucible tests"), - CrucibleTestCase.asyncResult("PhysicsChunk subplugin load/unload/reload", - ignored -> PhysicsChunkSubPluginCrucibleSupport.loadUnloadReloadSmokeAsync(), - "PhysicsChunk subplugin lifecycle smoke failed"), - CrucibleTestCase.asyncResult("physics entity subplugin load/unload/reload", - ignored -> PhysicsEntitySubPluginCrucibleSupport.loadUnloadReloadSmokeAsync(), - "PhysicsEntity subplugin lifecycle smoke failed"), - CrucibleTestCase.asyncResult("control subplugin load/unload/reload", - ControlSubPluginCrucibleSupport::loadUnloadReloadSmokeAsync, - "Control subplugin lifecycle smoke failed"), - CrucibleTestCase.sync("create space and body", - ImpulseApiCrucibleTests::createSpaceAndBody, - "Expected a body to be added to the physics space"), - CrucibleTestCase.sync("step", - ImpulseApiCrucibleTests::stepSpaceDoesNotThrow, - "Physics space step failed"))); - } - - private static CrucibleSuite runtimeStabilitySuite() { - return new CrucibleSuite( - "impulse:runtime_stability", - "Impulse Runtime Stability", - "Verifies explicit space lifecycle, detached cleanup, and settings retention", - Set.of("smoke", "stability"), - List.of( - CrucibleTestCase.async("space count round trip", - ImpulseApiCrucibleTests::spaceCountRoundTrip, - "PhysicsStore space count did not return to its previous value"), - CrucibleTestCase.async("created explicit space lifecycle works", - ImpulseApiCrucibleTests::createdExplicitSpaceLifecycleWorks, - "Explicit space was not registered correctly"), - CrucibleTestCase.async("clear populated spaces", - ImpulseApiCrucibleTests::clearPopulatedSpaces, - "PhysicsStore entity cleanup did not remove populated runtime spaces"), - CrucibleTestCase.async("detached unregister removes body", - ImpulseApiCrucibleTests::detachedUnregisterRemovesBackendBody, - "Detached unregister did not remove the backend body"), - CrucibleTestCase.async("settings round trip", - ImpulseApiCrucibleTests::settingsRoundTrip, - "Composable space settings did not retain runtime settings"))); - } - - private static CrucibleSuite transformSyncSuite() { - return new CrucibleSuite( - "impulse:transform_sync", - "Impulse Transform Sync", - "Verifies physics bodies move correctly under gravity", - Set.of("integration"), - List.of( - CrucibleTestCase.sync("body fell", ImpulseApiCrucibleTests::dynamicBodyFalls, - "Dynamic body did not fall under gravity"), - CrucibleTestCase.sync("static body stays", - ImpulseApiCrucibleTests::staticBodyDoesNotMove, - "Static body moved while stepping the physics space"))); - } - - private static CrucibleSuite terrainCollisionSuite() { - return new CrucibleSuite( - "impulse:terrain_collision", - "Impulse Terrain Collision", - "Verifies dynamic bodies land on ground plane collision", - Set.of("collision"), - List.of( - CrucibleTestCase.sync("body landed", - ImpulseApiCrucibleTests::bodyLandsOnGroundPlane, - "Body did not land on the ground plane"), - CrucibleTestCase.sync("body settled", - ImpulseApiCrucibleTests::bodySettlesWithinTolerance, - "Body did not settle within the expected speed tolerance"), - CrucibleTestCase.sync("ray hit ground", - ImpulseApiCrucibleTests::raycastHitsGroundPlane, - "Raycast from above did not hit the ground plane"))); - } - - private static boolean createSpaceAndBody() { - PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); - int spaceId = runtime.createSpace(SpaceId.next()); - try { - long bodyId = runtime.createBody(spaceId, - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - 1.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.DYNAMIC), - 0f, - 10f, - 0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - return runtime.bodyCount(spaceId) == 1 && runtime.containsBody(spaceId, bodyId); - } finally { - runtime.destroySpace(spaceId); - } - } - - private static CompletionStage spaceCountRoundTrip(@Nonnull CrucibleContext context) { - return callWhenPhysicsStoreIdle(context, "run Crucible space count round trip", world -> { - Store store = physicsStore(world); - int previousCount = PhysicsSpaces.count(store); - SpaceId spaceId = PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); - PhysicsSpaceMutations.removeEmptySpace(store, spaceId); - return PhysicsSpaces.count(store) == previousCount - && !PhysicsSpaces.hasSpace(store, spaceId); - }); - } - - private static CompletionStage createdExplicitSpaceLifecycleWorks( - @Nonnull CrucibleContext context) { - return callWhenPhysicsStoreIdle(context, "run Crucible explicit space lifecycle", world -> { - Store store = physicsStore(world); - SpaceId spaceId = PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); - PhysicsChunkCollisionSettings chunkCollisionSettings = - new PhysicsChunkCollisionSettings(); - chunkCollisionSettings.setMode(PhysicsChunkCollisionMode.STREAMING); - PhysicsSpaces.putChunkCollisionSettings(store, spaceId, chunkCollisionSettings); - PhysicsChunkCollisionSettings spaceSettings = - PhysicsSpaces.chunkCollisionSettings(store, spaceId); - boolean registered = PhysicsSpaces.hasSpace(store, spaceId) - && spaceSettings != null - && spaceSettings.getMode() == PhysicsChunkCollisionMode.STREAMING; - PhysicsSpaceMutations.removeEmptySpace(store, spaceId); - return registered && !PhysicsSpaces.hasSpace(store, spaceId); - }); - } - - private static CompletionStage clearPopulatedSpaces( - @Nonnull CrucibleContext context) { - return populatedBodyCleanup(context, true); - } - - private static CompletionStage detachedUnregisterRemovesBackendBody( - @Nonnull CrucibleContext context) { - return populatedBodyCleanup(context, false); - } - - private static CompletionStage populatedBodyCleanup( - @Nonnull CrucibleContext context, - boolean checkSpaceRemoval) { - return createPopulatedBodyCleanupState(context) - .thenCompose(state -> waitApproxTicksOnWorld(context, 4) - .thenCompose(_ -> removeBodyEntityAndWait(context, state.store(), state.bodyRef())) - .thenCompose(_ -> PhysicsDiagnostics.bodyCountAsync(state.store(), state.spaceRef())) - .thenCompose(bodyCount -> PhysicsThreading.callWhenBackendIdleOnWorldThread( - state.world(), - "check Crucible body cleanup", - _ -> { - boolean spaceEmpty = bodyCount == 0; - boolean noRegistrations = - PhysicsBodies.bodyUuids(state.store()).isEmpty(); - boolean removedSpace = true; - if (checkSpaceRemoval || spaceEmpty) { - PhysicsSpaceMutations.removeEmptySpace( - state.store(), - state.spaceId()); - removedSpace = !PhysicsSpaces.hasSpace(state.store(), state.spaceId()); - } - return spaceEmpty && noRegistrations && removedSpace; - }))); - } - - private static CompletionStage createPopulatedBodyCleanupState( - @Nonnull CrucibleContext context) { - return callWhenPhysicsStoreIdle(context, "create Crucible body cleanup state", world -> { - Store store = physicsStore(world); - SpaceId spaceId = SpaceId.next(); - Ref spaceRef = PhysicsSpaces.create(store, - UUID.randomUUID(), - spaceId, - CrucibleBackends.requireBackendId()); - Ref bodyRef = addCrucibleBox(store, spaceRef, UUID.randomUUID()); - return new PopulatedBodyCleanupState(world, store, spaceId, spaceRef, bodyRef); - }); - } - - @Nonnull - private static CompletionStage callWhenPhysicsStoreIdle( - @Nonnull CrucibleContext context, - @Nonnull String operation, - @Nonnull Function action) { - try { - World world = context.world(); - return PhysicsThreading.callWhenBackendIdleOnWorldThread(world, - operation, - _ -> action.apply(world)); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - } - - private static CompletionStage removeBodyEntityAndWait(@Nonnull CrucibleContext context, - @Nonnull Store store, - @Nonnull Ref bodyRef) { - return PhysicsThreading.executeOnWorldThread(PhysicsThreading.world(store), - "remove Crucible body entity", - _ -> { - if (bodyRef.isValid()) { - store.removeEntity(bodyRef, store.getRegistry().newHolder(), RemoveReason.REMOVE); - } - }) - .thenCompose(_ -> waitApproxTicksOnWorld(context, 4)); - } - - private static CompletionStage waitApproxTicksOnWorld(@Nonnull CrucibleContext context, - int ticks) { - try { - return context.waitApproxTicksOnWorld(ticks); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - } - - @Nonnull - private static Ref addCrucibleBox(@Nonnull Store store, - @Nonnull Ref spaceRef, - @Nonnull UUID bodyUuid) { - return store.addEntity(PhysicsBodyEntities.dynamicBodyHolder(spaceRef, - bodyUuid, - new Vector3f(0.0f, 5.0f, 0.0f), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - RigidBodySpawnSettings.defaults(), - null), AddReason.SPAWN); - } - - private static CompletionStage settingsRoundTrip(@Nonnull CrucibleContext context) { - PopulatedSettings settings = populatedSettings(); - - return callWhenPhysicsStoreIdle(context, "run Crucible settings round trip", world -> { - Store store = physicsStore(world); - SpaceId spaceId = PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); - try { - applyPopulatedSettings(store, spaceId, settings); - PhysicsChunkCollisionSettings chunkCollision = - PhysicsSpaces.chunkCollisionSettings(store, spaceId); - PhysicsVisualSyncSettings visualSync = - PhysicsSpaces.visualSyncSettings(store, spaceId); - PhysicsSolverSettings solver = PhysicsSpaces.solverSettings(store, spaceId); - PhysicsExtensionSettings extension = PhysicsSpaces.extensionSettings(store, - spaceId); - PhysicsVisualMaterializationSettings visualMaterialization = - PhysicsSpaces.visualMaterializationSettings(store, spaceId); - if (chunkCollision == null - || visualSync == null - || solver == null - || extension == null - || visualMaterialization == null) { - return false; - } - return chunkCollision.getMode() == PhysicsChunkCollisionMode.STREAMING - && chunkCollision.getRadius() == 9 - && chunkCollision.getBodyRadius() == 5 - && chunkCollision.getTtlTicks() == 77 - && visualSync.getVisualFullSyncRadius() == 48 - && visualSync.getVisualMaxSyncRadius() == 96 - && !visualSync.isVisualFarSyncCutoffEnabled() - && visualSync.getVisualMidSyncIntervalTicks() == 3 - && visualSync.getVisualFarSyncIntervalTicks() == 17 - && visualSync.getVisualOcclusionMode() == VisualOcclusionMode.PRIORITY - && visualSync.getVisualOcclusionRaycastsPerTick() == 31 - && visualSync.getVisualOcclusionCacheTicks() == 7 - && solver.getSolverIterations() == 5 - && solver.getStabilizationIterations() == 1 - && extension - .getInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_INTERNAL_PGS_ITERATIONS) - .orElse(-1) == 2 - && extension - .getInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_MIN_ISLAND_SIZE) - .orElse(-1) == 64 - && visualSync.isEntityVisualSyncCullingEnabled() - && visualSync.isVisualVisibilityCullingEnabled() - && visualMaterialization.isDetachedVisualMaterializationEnabled() - && visualMaterialization.getDetachedVisualMaterializationRadius() == 48 - && visualMaterialization.getDetachedVisualDematerializationRadius() == 72 - && visualMaterialization.getDetachedVisualMaxSpawnsPerTick() == 33 - && visualMaterialization.getDetachedVisualMaxMaterialized() == 444 - && "Rock_Stone".equals(visualMaterialization.getDetachedVisualBlockType()); - } finally { - PhysicsSpaceMutations.removeEmptySpace(store, spaceId); - } - }); - } - - @Nonnull - private static PopulatedSettings populatedSettings() { - PhysicsChunkCollisionSettings chunkCollision = new PhysicsChunkCollisionSettings(); - chunkCollision.setMode(PhysicsChunkCollisionMode.STREAMING); - chunkCollision.setRadius(9); - chunkCollision.setBodyRadius(5); - chunkCollision.setTtlTicks(77); - - PhysicsVisualSyncSettings visualSync = new PhysicsVisualSyncSettings(); - visualSync.setVisualMaxSyncRadius(96); - visualSync.setVisualFullSyncRadius(48); - visualSync.setVisualFarSyncCutoffEnabled(false); - visualSync.setVisualMidSyncIntervalTicks(3); - visualSync.setVisualFarSyncIntervalTicks(17); - visualSync.setVisualOcclusionMode(VisualOcclusionMode.PRIORITY); - visualSync.setVisualOcclusionRaycastsPerTick(31); - visualSync.setVisualOcclusionCacheTicks(7); - visualSync.setEntityVisualSyncCullingEnabled(true); - visualSync.setVisualVisibilityCullingEnabled(true); - - PhysicsSolverSettings solver = new PhysicsSolverSettings(); - solver.setSolverIterations(5); - solver.setStabilizationIterations(1); - - PhysicsExtensionSettings extension = new PhysicsExtensionSettings(); - extension.setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_INTERNAL_PGS_ITERATIONS, - 2); - extension.setInt(RAPIER_SOLVER_EXTENSION_ID, - RAPIER_MIN_ISLAND_SIZE, - 64); - - PhysicsVisualMaterializationSettings visualMaterialization = - new PhysicsVisualMaterializationSettings(); - visualMaterialization.setDetachedVisualMaterializationEnabled(true); - visualMaterialization.setDetachedVisualDematerializationRadius(72); - visualMaterialization.setDetachedVisualMaterializationRadius(48); - visualMaterialization.setDetachedVisualMaxSpawnsPerTick(33); - visualMaterialization.setDetachedVisualMaxMaterialized(444); - visualMaterialization.setDetachedVisualBlockType("Rock_Stone"); - return new PopulatedSettings(chunkCollision, - visualSync, - solver, - extension, - visualMaterialization); - } - - private static void applyPopulatedSettings(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull PopulatedSettings settings) { - PhysicsSpaces.putChunkCollisionSettings(store, - spaceId, - settings.chunkCollisionSettings()); - PhysicsSpaces.putVisualSyncSettings(store, spaceId, settings.visualSyncSettings()); - PhysicsSpaces.putSolverSettings(store, spaceId, settings.solverSettings()); - PhysicsSpaces.putExtensionSettings(store, spaceId, settings.extensionSettings()); - PhysicsSpaces.putVisualMaterializationSettings(store, - spaceId, - settings.visualMaterializationSettings()); - } - - private static Store physicsStore(@Nonnull World world) { - return PhysicsStoreCrucibleSupport.physicsStore(world); - } - - private record PopulatedSettings( - @Nonnull PhysicsChunkCollisionSettings chunkCollisionSettings, - @Nonnull PhysicsVisualSyncSettings visualSyncSettings, - @Nonnull PhysicsSolverSettings solverSettings, - @Nonnull PhysicsExtensionSettings extensionSettings, - @Nonnull PhysicsVisualMaterializationSettings visualMaterializationSettings) { - } - - private record PopulatedBodyCleanupState(@Nonnull World world, - @Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull Ref spaceRef, - @Nonnull Ref bodyRef) { - } - - private static boolean stepSpaceDoesNotThrow() { - PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); - int spaceId = runtime.createSpace(SpaceId.next()); - try { - runtime.setGravity(spaceId, 0f, -9.81f, 0f); - createBox(runtime, spaceId, 1.0f, PhysicsBodyType.DYNAMIC, 0f, 10f, 0f); - runtime.step(spaceId, 1f / 60f); - return true; - } finally { - runtime.destroySpace(spaceId); - } - } - - private static boolean dynamicBodyFalls() { - PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); - int spaceId = runtime.createSpace(SpaceId.next()); - try { - runtime.setGravity(spaceId, 0f, -9.81f, 0f); - long bodyId = createBox(runtime, spaceId, 1.0f, PhysicsBodyType.DYNAMIC, 0f, 20f, 0f); - - float dt = 1f / 60f; - for (int i = 0; i < 60; i++) { - runtime.step(spaceId, dt); - } - - PhysicsBodySnapshot snapshot = requireSnapshot(runtime, spaceId, bodyId); - float yAfter = snapshot.positionY(); - float vy = snapshot.linearVelocityY(); - return yAfter < 20f && vy < 0f; - } finally { - runtime.destroySpace(spaceId); - } - } - - private static boolean staticBodyDoesNotMove() { - PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); - int spaceId = runtime.createSpace(SpaceId.next()); - try { - runtime.setGravity(spaceId, 0f, -9.81f, 0f); - long bodyId = createBox(runtime, spaceId, 0.0f, PhysicsBodyType.STATIC, 0f, 10f, 0f); - - float dt = 1f / 60f; - for (int i = 0; i < 60; i++) { - runtime.step(spaceId, dt); - } - - float yAfter = requireSnapshot(runtime, spaceId, bodyId).positionY(); - return Math.abs(yAfter - 10f) < 0.01f; - } finally { - runtime.destroySpace(spaceId); - } - } - - private static boolean bodyLandsOnGroundPlane() { - PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); - int spaceId = runtime.createSpace(SpaceId.next()); - try { - runtime.setGravity(spaceId, 0f, -9.81f, 0f); - createPlane(runtime, spaceId, 0f); - - long bodyId = createBox(runtime, spaceId, 1.0f, PhysicsBodyType.DYNAMIC, 0f, 5f, 0f); - - float dt = 1f / 60f; - for (int i = 0; i < 300; i++) { - runtime.step(spaceId, dt); - } - - float yAfter = requireSnapshot(runtime, spaceId, bodyId).positionY(); - return yAfter < 3f && yAfter > -0.5f; - } finally { - runtime.destroySpace(spaceId); - } - } - - private static boolean bodySettlesWithinTolerance() { - PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); - int spaceId = runtime.createSpace(SpaceId.next()); - try { - runtime.setGravity(spaceId, 0f, -9.81f, 0f); - createPlane(runtime, spaceId, 0f); - - long bodyId = createBox(runtime, spaceId, 1.0f, PhysicsBodyType.DYNAMIC, 0f, 2f, 0f); - - float dt = 1f / 60f; - for (int i = 0; i < 300; i++) { - runtime.step(spaceId, dt); - } - - float speed = requireSnapshot(runtime, spaceId, bodyId).linearVelocity().length(); - return speed < 0.5f; - } finally { - runtime.destroySpace(spaceId); - } - } - - private static boolean raycastHitsGroundPlane() { - PhysicsBackendRuntime runtime = Impulse.createRuntime(CrucibleBackends.requireBackendId()); - int spaceId = runtime.createSpace(SpaceId.next()); - try { - createPlane(runtime, spaceId, 0f); - - return runtime.raycastAll(spaceId, 0f, 10f, 0f, 0f, -10f, 0f, (_, _, _, _, _, _, _, _, _) -> { - }) > 0; - } finally { - runtime.destroySpace(spaceId); - } - } - - @Nonnull - private static PhysicsBodySnapshot requireSnapshot(@Nonnull PhysicsBackendRuntime runtime, - int spaceId, - long bodyId) { - PhysicsBodySnapshot snapshot = PhysicsBodySnapshots.read(runtime, spaceId, bodyId); - if (snapshot == null) { - throw new IllegalStateException("Missing backend snapshot for body " + bodyId); - } - return snapshot; - } - - private static long createBox(@Nonnull PhysicsBackendRuntime runtime, - int spaceId, - float mass, - @Nonnull PhysicsBodyType bodyType, - float positionX, - float positionY, - float positionZ) { - return runtime.createBody(spaceId, - BackendRuntimeCodes.SHAPE_BOX, - 0.5f, - 0.5f, - 0.5f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - 0.0f, - mass, - BackendRuntimeCodes.bodyTypeCode(bodyType), - positionX, - positionY, - positionZ, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - } - - private static long createPlane(@Nonnull PhysicsBackendRuntime runtime, int spaceId, float groundY) { - return runtime.createBody(spaceId, - BackendRuntimeCodes.SHAPE_PLANE, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - BackendRuntimeCodes.AXIS_Y, - groundY, - 0.0f, - BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC), - 0.0f, - groundY, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseCrucibleSuites.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseCrucibleSuites.java deleted file mode 100644 index beb10951..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseCrucibleSuites.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -/** - * Entry point used by the core plugin to register optional Crucible suites. - */ -public final class ImpulseCrucibleSuites { - - private ImpulseCrucibleSuites() { - } - - /** - * Registers all Impulse suites using the classloader that loaded Crucible. - * - * @param crucibleLoader the classloader for the Crucible plugin - * @throws ReflectiveOperationException if Crucible's API shape changed - */ - public static void register(ClassLoader crucibleLoader) - throws ReflectiveOperationException { - - CrucibleBridge bridge = CrucibleBridge.create(crucibleLoader); - ImpulseApiCrucibleTests.register(bridge, crucibleLoader); - ImpulseLiveCrucibleTests.register(bridge, crucibleLoader); - ImpulseDetachedStreamingBenchmarkCrucibleTests.register(bridge, crucibleLoader); - ImpulseRapierBodyBenchmarkCrucibleTests.register(bridge, crucibleLoader); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java deleted file mode 100644 index d8c459ee..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseDetachedStreamingBenchmarkCrucibleTests.java +++ /dev/null @@ -1,992 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.component.Archetype; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; -import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkBuildOptions; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionPrewarmStats; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; -import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3d; -import org.joml.Vector3f; - -/** - * Benchmark-oriented Crucible scenario for detached bodies using streamed PhysicsChunk collision. - */ -@SuppressWarnings("SameParameterValue") -final class ImpulseDetachedStreamingBenchmarkCrucibleTests { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final String COUNTS_PROPERTY = "impulse.crucible.detachedStreaming.counts"; - private static final String WARMUP_TICKS_PROPERTY = - "impulse.crucible.detachedStreaming.warmupTicks"; - private static final String SAMPLE_TICKS_PROPERTY = - "impulse.crucible.detachedStreaming.sampleTicks"; - private static final String MIN_TPS_PROPERTY = - "impulse.crucible.detachedStreaming.minTps"; - private static final String WARN_TPS_PROPERTY = - "impulse.crucible.detachedStreaming.warnTps"; - private static final String STRICT_PLANE_GATE_PROPERTY = - "impulse.crucible.detachedStreaming.strictPlaneGate"; - private static final PhysicsBackendExtensionId RAPIER_SOLVER_EXTENSION_ID = - new PhysicsBackendExtensionId("impulse:rapier_solver"); - private static final String RAPIER_INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; - private static final String RAPIER_MIN_ISLAND_SIZE = "minIslandSize"; - - private static final int DEFAULT_STAGE_COUNT = 500; - private static final int DEFAULT_WARMUP_TICKS = 60; - private static final int DEFAULT_SAMPLE_TICKS = 200; - private static final int MIN_STAGE_COUNT = 1; - private static final int MAX_STAGE_COUNT = 10_000; - private static final int MIN_WARMUP_TICKS = 1; - private static final int MAX_WARMUP_TICKS = 1_200; - private static final int MIN_SAMPLE_TICKS = 20; - private static final int MAX_SAMPLE_TICKS = 7_200; - private static final int BODY_STREAMING_RADIUS = 8; - private static final int TICKING_CHUNK_REQUEST_FLAGS = 4; - private static final int MAX_CHUNK_PREFLIGHT_ATTEMPTS = 100; - private static final int CHUNK_PREFLIGHT_WAIT_TICKS = 2; - private static final float TARGET_MAX_STEP_DT = 1.0f / 30.0f; - private static final float GROUND_Y = 122.0f; - private static final float BELOW_PLANE_TOLERANCE = 1.0f; - private static final float BODY_WORLD_MIN_Y = -32.0f; - private static final float BODY_VOID_Y = -128.0f; - private static final double STREAMING_FALL_ENVELOPE_MIN_Y = 0.0; - private static final double STREAMING_HORIZONTAL_DRIFT_HALO_BLOCKS = 16.0; - private static final double DETACHED_SPACING = 1.5; - private static final Vector3d ORIGIN = new Vector3d(0.0, 128.0, 0.0); - private static final ComponentType WORLD_CHUNK_TYPE = - WorldChunk.getComponentType(); - - private ImpulseDetachedStreamingBenchmarkCrucibleTests() { - } - - static void register(CrucibleBridge bridge, ClassLoader loader) - throws ReflectiveOperationException { - - bridge.registerSuite(loader, benchmarkSuite()); - } - - private static CrucibleSuite benchmarkSuite() { - return new CrucibleSuite( - "impulse:detached_streaming_benchmark", - "Impulse Detached Streaming Benchmark", - "Runs detached full-collision streamed-world benchmark stages with health gates", - Set.of("benchmark", "streaming"), - List.of(CrucibleTestCase.asyncResult("detached full-collision streaming stages", - ImpulseDetachedStreamingBenchmarkCrucibleTests::detachedStreamingStages, - "Detached streaming benchmark breached a health gate"))); - } - - private static CompletionStage detachedStreamingStages( - CrucibleContext context) { - try { - StagePlan plan = StagePlan.fromSystemProperties(); - StageRunner runner = new StageRunner(context, plan); - return runner.run(); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private static final class StageRunner { - - private final CrucibleContext context; - private final StagePlan plan; - private final World world; - private final Store physicsStore; - private final PhysicsProfilingResource physicsStoreProfiling; - private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final PhysicsChunkProfilingResource collisionProfiling; - private final PhysicsChunkCollisionStreamingResource collisionStreaming; - private final PhysicsWorldSettings previousWorldSettings; - private final boolean previousPhysicsStoreProfilingEnabled; - private final List retainedChunks = new ArrayList<>(); - - private StageRunner(@Nonnull CrucibleContext context, @Nonnull StagePlan plan) - throws ReflectiveOperationException { - this.context = context; - this.plan = plan; - this.world = context.world(); - Store store = world.getEntityStore().getStore(); - this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); - this.physicsStoreProfiling = physicsStore.getResource( - PhysicsProfilingResource.getResourceType()); - this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); - this.collisionProfiling = store.getResource( - PhysicsChunkProfilingResource.getResourceType()); - this.collisionStreaming = store.getResource( - PhysicsChunkCollisionStreamingResource.getResourceType()); - this.previousWorldSettings = PhysicsWorlds.settings(physicsStore); - this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); - } - - private CompletionStage run() { - return runStage(0, new ArrayList<>()).handle((outcome, failure) -> { - clearStageState(); - restoreStepSettings(); - if (failure != null) { - if (failure instanceof CompletionException completionException) { - throw completionException; - } - throw new CompletionException(failure); - } - return outcome; - }); - } - - private CompletionStage runStage(int stageIndex, - List reports) { - if (stageIndex >= plan.counts().size()) { - return CompletableFuture.completedFuture(outcome(reports)); - } - - int count = plan.counts().get(stageIndex); - return startStageWhenReady(count, 1) - .thenCompose(started -> contextWait(plan.warmupTicks()).thenCompose(_ -> { - physicsStoreProfiling.reset(); - runtimeProfiling.reset(); - collisionProfiling.reset(); - physicsStoreProfiling.setEnabled(true); - runtimeProfiling.setEnabled(true); - collisionProfiling.setEnabled(true); - long startedNanos = System.nanoTime(); - return contextWait(plan.sampleTicks()).thenApply( - _ -> finishStage(count, started, startedNanos)); - })) - .thenCompose(report -> { - reports.add(report); - LOGGER.at(Level.INFO).log("Crucible detached streaming stage: %s", - report.summary()); - clearStageState(); - if (report.health().status() == StageStatus.STOP) { - return CompletableFuture.completedFuture(outcome(reports)); - } - return runStage(stageIndex + 1, reports); - }); - } - - private CompletionStage startStageWhenReady(int count, int attempt) { - clearStageState(); - if (!PhysicsChunkSubPluginCrucibleSupport.ensureLoaded()) { - return CompletableFuture.completedFuture(StartedStage.failed(count, - "PhysicsChunk subplugin did not load")); - } - - PhysicsWorldSettings worldSettings = PhysicsWorlds.settings(physicsStore); - worldSettings.setStepMode(PhysicsStepMode.PROGRESSIVE_REFINEMENT); - worldSettings.setStepSchedulingMode(PhysicsStepSchedulingMode.DROP_PENDING_DT); - worldSettings.setSimulationSteps(1); - worldSettings.setMaxStepDt(TARGET_MAX_STEP_DT); - PhysicsWorlds.putSettings(physicsStore, worldSettings); - - BenchmarkChunks chunks = benchmarkChunks(count); - if (!areChunksReady(chunks)) { - int requested = requestChunks(chunks); - if (attempt >= MAX_CHUNK_PREFLIGHT_ATTEMPTS) { - String message = "count=" + count - + " chunk preflight failed after " + attempt - + " attempts; refs=" + chunks.size() - + " requested=" + requested; - return CompletableFuture.completedFuture( - StartedStage.failed(count, message)); - } - return contextWait(CHUNK_PREFLIGHT_WAIT_TICKS) - .thenCompose(_ -> startStageWhenReady(count, attempt + 1)); - } - - int retained = retainChunks(chunks); - configureMissingSectionDiagnostics(chunks); - SpaceId spaceId = PhysicsSpaces.create(physicsStore, - CrucibleBackends.requireBackendId()); - PhysicsSpaceMutations.putChunkCollisionSettings(physicsStore, - spaceId, - benchmarkChunkCollisionSettings()); - PhysicsSpaceMutations.putSolverSettings(physicsStore, - spaceId, - benchmarkSolverSettings()); - PhysicsSpaceMutations.putExtensionSettings(physicsStore, - spaceId, - benchmarkExtensionSettings()); - PrewarmStats prewarm = prewarmPhysicsChunkCollision(spaceId, count); - spawnDetachedBodies(spaceId, count); - physicsStoreProfiling.reset(); - runtimeProfiling.reset(); - collisionProfiling.reset(); - physicsStoreProfiling.setEnabled(true); - runtimeProfiling.setEnabled(true); - collisionProfiling.setEnabled(true); - return CompletableFuture.completedFuture( - StartedStage.started(spaceId, chunks, retained, prewarm)); - } - - private StageReport finishStage(int count, - StartedStage started, - long startedNanos) { - if (!started.started()) { - return StageReport.failedPreflight(count, started.failureMessage()); - } - SpaceId spaceId = started.spaceId(); - if (spaceId == null || !PhysicsSpaces.hasSpace(physicsStore, spaceId)) { - return StageReport.failedPreflight(count, "space disappeared during benchmark"); - } - - StepSnapshot step = runtimeProfiling.getCumulativeStep(); - SyncSnapshot sync = runtimeProfiling.getCumulativeSync(); - Snapshot collisionProfilingSnapshot = collisionProfiling.getCumulativeSnapshot(); - double elapsedSeconds = Math.max(0.001, - (System.nanoTime() - startedNanos) / 1_000_000_000.0); - double observedTickRate = step.getTickSamples() / elapsedSeconds; - SpaceStats stats = SpaceStats.collect(physicsStore, collisionStreaming, spaceId); - double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); - double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); - double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); - double avgTerrainMs = averageMillis(collisionProfilingSnapshot.getTickNanos(), - collisionProfilingSnapshot.getTickSamples()); - double totalMs = avgStepMs - + avgSnapshotMs - + avgSyncMs - + avgTerrainMs; - StageHealth health = assessHealth(count, - observedTickRate, - stats, - collisionProfilingSnapshot.getMissingChunks()); - - assert started.chunks() != null; - assert started.prewarm() != null; - return new StageReport(count, - observedTickRate, - avgStepMs, - avgSnapshotMs, - avgSyncMs, - avgTerrainMs, - totalMs, - started.retainedColumns(), - started.chunks().size(), - started.prewarm().sectionTargets(), - started.prewarm().sectionsBuilt(), - stats.bodies, - stats.dynamicBodies, - stats.terrainBodies, - stats.belowPlaneBodies, - stats.belowTerrainBodies, - stats.belowWorldMinBodies, - stats.belowVoidBodies, - stats.terrainBaselineBodies, - stats.missingTerrainBaselineBodies, - stats.minTerrainBottomClearance(), - collisionProfilingSnapshot.getTickSamples(), - collisionProfilingSnapshot.getEnsureCalls(), - collisionProfilingSnapshot.getSectionRequests(), - collisionProfilingSnapshot.getSectionCacheHits(), - collisionProfilingSnapshot.getSectionsBuilt(), - collisionProfilingSnapshot.getMissingChunks(), - collisionProfilingSnapshot.getMissingBlockChunks(), - collisionProfilingSnapshot.getMissingBlockSections(), - collisionProfilingSnapshot.getUniqueMissingSections(), - collisionProfilingSnapshot.getMissingOutsideRetainedEnvelope(), - collisionProfilingSnapshot.getBodyStreamingTargets(), - health); - } - - private CompletionStage contextWait(int ticks) { - try { - return context.waitApproxTicksOnWorld(ticks); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - } - - private void clearStageState() { - releaseRetainedChunks(); - PhysicsStoreCrucibleSupport.clearAll(physicsStore); - physicsStoreProfiling.reset(); - runtimeProfiling.reset(); - collisionProfiling.reset(); - collisionProfiling.clearDiagnosticRetainedSections(); - } - - private void restoreStepSettings() { - PhysicsWorlds.putSettings(physicsStore, previousWorldSettings); - physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); - } - - private PrewarmStats prewarmPhysicsChunkCollision(@Nonnull SpaceId spaceId, int count) { - BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); - UUID spaceUuid = PhysicsSpaceMutations.requireSpaceUuid(physicsStore, spaceId); - PhysicsChunkCollisionMutationQueueResource queue = physicsStore.getResource( - PhysicsChunkCollisionMutationQueueResource.getResourceType()); - PhysicsChunkBuildOptions buildOptions = PhysicsChunkBuildOptions.fromSettings( - benchmarkChunkCollisionSettings()); - PhysicsChunkCollisionPrewarmStats stats = collisionStreaming.ensureAround(world, - spaceUuid, - queue, - prewarmCenters(layout, count), - BODY_STREAMING_RADIUS, - 0L, - null, - buildOptions); - return new PrewarmStats(stats.sectionTargets(), - stats.buildStats().sectionsBuilt(), - stats.buildStats().colliderBodies()); - } - - @Nonnull - private static PhysicsChunkCollisionSettings benchmarkChunkCollisionSettings() { - PhysicsChunkCollisionSettings settings = new PhysicsChunkCollisionSettings(); - settings.setMode(PhysicsChunkCollisionMode.STREAMING); - settings.setBodyRadius(BODY_STREAMING_RADIUS); - return settings; - } - - @Nonnull - private static PhysicsSolverSettings benchmarkSolverSettings() { - PhysicsSolverSettings settings = new PhysicsSolverSettings(); - settings.setSolverIterations(4); - settings.setStabilizationIterations(1); - return settings; - } - - @Nonnull - private static PhysicsExtensionSettings benchmarkExtensionSettings() { - PhysicsExtensionSettings settings = new PhysicsExtensionSettings(); - settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS, 1); - settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_MIN_ISLAND_SIZE, 128); - return settings; - } - - @Nonnull - private static List prewarmCenters(@Nonnull BenchmarkLayout layout, int count) { - List centers = new ArrayList<>(); - for (int index = 0; index < Math.max(0, count); index++) { - double positionX = layout.positionX(index); - double positionY = layout.positionY(); - double positionZ = layout.positionZ(index); - addPrewarmEnvelopeCenters(centers, positionX, positionY, positionZ); - } - return centers; - } - - private static void addPrewarmEnvelopeCenters(@Nonnull List centers, - double positionX, - double positionY, - double positionZ) { - double halo = STREAMING_HORIZONTAL_DRIFT_HALO_BLOCKS; - for (int offsetX = -1; offsetX <= 1; offsetX++) { - for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { - addPrewarmEnvelopeCentersAt(centers, - positionX + offsetX * halo, - positionY, - positionZ + offsetZ * halo); - } - } - } - - private static void addPrewarmEnvelopeCentersAt(@Nonnull List centers, - double positionX, - double positionY, - double positionZ) { - double step = Math.max(1.0, BODY_STREAMING_RADIUS * 2.0); - double minCenterY = Math.min(positionY, - STREAMING_FALL_ENVELOPE_MIN_Y + BODY_STREAMING_RADIUS); - double lastY = Double.NaN; - for (double y = positionY; y >= minCenterY; y -= step) { - centers.add(new Vector3d(positionX, y, positionZ)); - lastY = y; - } - if (Double.isNaN(lastY) || lastY > minCenterY) { - centers.add(new Vector3d(positionX, minCenterY, positionZ)); - } - } - - private void configureMissingSectionDiagnostics(@Nonnull BenchmarkChunks chunks) { - LongSet sectionKeys = new LongOpenHashSet(); - for (ChunkSection section : chunks.sections()) { - sectionKeys.add(PhysicsChunkProfilingResource.packDiagnosticSectionKey( - section.x(), - section.y(), - section.z())); - } - collisionProfiling.setDiagnosticRetainedSections(sectionKeys); - } - - private void spawnDetachedBodies(@Nonnull SpaceId spaceId, int count) { - BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); - PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); - RigidBodySpawnSettings settings = RigidBodySpawnSettings.of(0.45f, - 0.0f, - 0.02f, - 0.25f, - PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - for (int i = 0; i < count; i++) { - PhysicsStoreCrucibleSupport.addBody(physicsStore, - spaceId, - UUID.randomUUID(), - new Vector3f((float) layout.positionX(i), - (float) layout.positionY(), - (float) layout.positionZ(i)), - box, - PhysicsBodyType.DYNAMIC, - 1.0f, - settings, - null); - } - } - - private BenchmarkChunks benchmarkChunks(int count) { - LongSet columns = new LongOpenHashSet(); - Set sections = new ObjectOpenHashSet<>(); - BenchmarkLayout layout = BenchmarkLayout.flatGrid(count); - for (int i = 0; i < count; i++) { - addStreamingCollisionChunks(columns, - sections, - layout.positionX(i), - layout.positionY(), - layout.positionZ(i)); - } - return new BenchmarkChunks(columns, sections); - } - - private void addStreamingCollisionChunks(@Nonnull LongSet columns, - @Nonnull Set sections, - double positionX, - double positionY, - double positionZ) { - int centerBlockX = (int) Math.floor(positionX); - int centerBlockZ = (int) Math.floor(positionZ); - int horizontalRadius = BODY_STREAMING_RADIUS - + (int) Math.ceil(STREAMING_HORIZONTAL_DRIFT_HALO_BLOCKS); - int minChunkX = ChunkUtil.chunkCoordinate(centerBlockX - horizontalRadius); - int maxChunkX = ChunkUtil.chunkCoordinate(centerBlockX + horizontalRadius); - int minBlockY = Math.max(0, (int) Math.floor(Math.min( - positionY - BODY_STREAMING_RADIUS, - STREAMING_FALL_ENVELOPE_MIN_Y))); - int maxBlockY = Math.min(ChunkUtil.HEIGHT_MINUS_1, - (int) Math.floor(positionY + BODY_STREAMING_RADIUS)); - int minChunkY = ChunkUtil.indexSection(minBlockY); - int maxChunkY = ChunkUtil.indexSection(maxBlockY); - int minChunkZ = ChunkUtil.chunkCoordinate(centerBlockZ - horizontalRadius); - int maxChunkZ = ChunkUtil.chunkCoordinate(centerBlockZ + horizontalRadius); - for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { - for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - columns.add(ChunkUtil.indexChunk(chunkX, chunkZ)); - for (int chunkY = minChunkY; chunkY <= maxChunkY; chunkY++) { - sections.add(new ChunkSection(chunkX, chunkY, chunkZ)); - } - } - } - } - - private boolean areChunksReady(@Nonnull BenchmarkChunks chunks) { - ChunkStore chunkStore = world.getChunkStore(); - Store chunkComponentStore = chunkStore.getStore(); - for (long chunkIndex : chunks.columns()) { - if (!isChunkTicking(chunkStore, chunkComponentStore, chunkIndex)) { - return false; - } - } - for (ChunkSection section : chunks.sections()) { - if (!isChunkSectionReady(chunkStore, chunkComponentStore, section)) { - return false; - } - } - return true; - } - - private int requestChunks(@Nonnull BenchmarkChunks chunks) { - int requested = 0; - ChunkStore chunkStore = world.getChunkStore(); - Store chunkComponentStore = chunkStore.getStore(); - for (long chunkIndex : chunks.columns()) { - if (!isChunkTicking(chunkStore, chunkComponentStore, chunkIndex)) { - chunkStore.getChunkReferenceAsync(chunkIndex, TICKING_CHUNK_REQUEST_FLAGS); - requested++; - } - } - for (ChunkSection section : chunks.sections()) { - if (!isChunkSectionReady(chunkStore, chunkComponentStore, section)) { - chunkStore.getChunkSectionReferenceAsync(section.x(), - section.y(), - section.z(), - TICKING_CHUNK_REQUEST_FLAGS); - requested++; - } - } - return requested; - } - - private int retainChunks(@Nonnull BenchmarkChunks chunks) { - int retained = 0; - ChunkStore chunkStore = world.getChunkStore(); - Store chunkComponentStore = chunkStore.getStore(); - for (long chunkIndex : chunks.columns()) { - Ref chunkRef = chunkStore.getChunkReference(chunkIndex); - retained += retainChunkRef(chunkComponentStore, chunkRef); - } - return retained; - } - - private int retainChunkRef(@Nonnull Store chunkComponentStore, - @Nullable Ref chunkRef) { - if (chunkRef == null || !chunkRef.isValid()) { - return 0; - } - WorldChunk worldChunk = chunkComponentStore.getComponent(chunkRef, WORLD_CHUNK_TYPE); - if (worldChunk == null || !worldChunk.is(ChunkFlag.TICKING)) { - return 0; - } - worldChunk.addKeepLoaded(); - worldChunk.resetKeepAlive(); - worldChunk.resetActiveTimer(); - retainedChunks.add(worldChunk); - return 1; - } - - private void releaseRetainedChunks() { - for (WorldChunk worldChunk : retainedChunks) { - worldChunk.removeKeepLoaded(); - } - retainedChunks.clear(); - } - - private boolean isChunkTicking(@Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore, - long chunkIndex) { - Ref chunkRef = chunkStore.getChunkReference(chunkIndex); - if (chunkRef == null || !chunkRef.isValid()) { - return false; - } - WorldChunk worldChunk = chunkComponentStore.getComponent(chunkRef, WORLD_CHUNK_TYPE); - return worldChunk != null && worldChunk.is(ChunkFlag.TICKING); - } - - private boolean isChunkSectionReady(@Nonnull ChunkStore chunkStore, - @Nonnull Store chunkComponentStore, - @Nonnull ChunkSection section) { - Ref chunkRef = chunkStore.getChunkSectionReference(section.x(), - section.y(), - section.z()); - if (chunkRef == null || !chunkRef.isValid()) { - return false; - } - Archetype archetype = chunkComponentStore.getArchetype(chunkRef); - return !archetype.contains(ChunkStore.REGISTRY.getNonTickingComponentType()); - } - } - - private static CrucibleTestCase.TestOutcome outcome(@Nonnull List reports) { - List failed = reports.stream() - .filter(report -> report.health().status() == StageStatus.STOP) - .map(StageReport::summary) - .toList(); - if (!failed.isEmpty()) { - return CrucibleTestCase.TestOutcome.fail(String.join(" | ", failed)); - } - return CrucibleTestCase.TestOutcome.pass(); - } - - private static StageHealth assessHealth(int count, - double observedTickRate, - @Nonnull SpaceStats stats, - int missingChunks) { - List stops = new ArrayList<>(); - List warnings = new ArrayList<>(); - if (observedTickRate < configuredDouble(MIN_TPS_PROPERTY, 8.0)) { - stops.add("observedTPS=" + format(observedTickRate) + "<8"); - } - if (stats.terrainBodies == 0) { - stops.add("terrainBodies=0"); - } - if (stats.belowWorldMinBodies > 0) { - stops.add("belowWorldMinBodies=" + stats.belowWorldMinBodies); - } - if (stats.belowVoidBodies > 0) { - stops.add("belowVoidBodies=" + stats.belowVoidBodies); - } - int maxBelowGround = Math.max(5, (int) Math.ceil(count * 0.01)); - if (stats.belowTerrainBodies > maxBelowGround) { - stops.add("belowTerrainBodies=" + stats.belowTerrainBodies - + ">" + maxBelowGround); - } - if (stats.belowPlaneBodies > maxBelowGround) { - String planeReason = "belowPlaneBodies=" + stats.belowPlaneBodies - + ">" + maxBelowGround; - if (configuredBoolean(STRICT_PLANE_GATE_PROPERTY, false)) { - stops.add(planeReason); - } else { - warnings.add(planeReason + " (strictPlaneGate=false)"); - } - } - if (missingChunks > 0) { - stops.add("missingChunks=" + missingChunks); - } - if (!stops.isEmpty()) { - return new StageHealth(StageStatus.STOP, String.join("; ", stops)); - } - if (observedTickRate < configuredDouble(WARN_TPS_PROPERTY, 15.0)) { - warnings.add("observedTPS=" + format(observedTickRate) + "<15"); - } - if (!warnings.isEmpty()) { - return new StageHealth(StageStatus.WARN, String.join("; ", warnings)); - } - return new StageHealth(StageStatus.PASS, "within gates"); - } - - private static double averageMillis(long nanos, int samples) { - if (samples <= 0) { - return 0.0; - } - return nanos / 1_000_000.0 / samples; - } - - private static String format(double value) { - return String.format(Locale.ROOT, "%.3f", value); - } - - private static String formatOptional(double value) { - return Double.isFinite(value) ? format(value) : "n/a"; - } - - private static int configuredInt(String property, int fallback, int min, int max) { - String raw = System.getProperty(property); - if (raw == null || raw.isBlank()) { - return fallback; - } - try { - return Math.clamp(Integer.parseInt(raw.trim()), min, max); - } catch (NumberFormatException ignored) { - return fallback; - } - } - - private static double configuredDouble(String property, double fallback) { - String raw = System.getProperty(property); - if (raw == null || raw.isBlank()) { - return fallback; - } - try { - return Double.parseDouble(raw.trim()); - } catch (NumberFormatException ignored) { - return fallback; - } - } - - private static boolean configuredBoolean(String property, boolean fallback) { - String raw = System.getProperty(property); - if (raw == null || raw.isBlank()) { - return fallback; - } - return Boolean.parseBoolean(raw.trim()); - } - - private record StagePlan(@Nonnull List counts, - int warmupTicks, - int sampleTicks) { - - private static StagePlan fromSystemProperties() { - return new StagePlan(configuredCounts(), - configuredInt(WARMUP_TICKS_PROPERTY, - DEFAULT_WARMUP_TICKS, - MIN_WARMUP_TICKS, - MAX_WARMUP_TICKS), - configuredInt(SAMPLE_TICKS_PROPERTY, - DEFAULT_SAMPLE_TICKS, - MIN_SAMPLE_TICKS, - MAX_SAMPLE_TICKS)); - } - - private static List configuredCounts() { - String raw = System.getProperty(COUNTS_PROPERTY); - if (raw == null || raw.isBlank()) { - return List.of(DEFAULT_STAGE_COUNT); - } - List counts = new ArrayList<>(); - for (String token : raw.split(",")) { - try { - int count = Math.clamp(Integer.parseInt(token.trim()), - MIN_STAGE_COUNT, - MAX_STAGE_COUNT); - counts.add(count); - } catch (NumberFormatException ignored) { - } - } - return counts.isEmpty() ? List.of(DEFAULT_STAGE_COUNT) : List.copyOf(counts); - } - } - - private record StartedStage(boolean started, - @Nullable SpaceId spaceId, - @Nullable BenchmarkChunks chunks, - int retainedColumns, - @Nullable PrewarmStats prewarm, - @Nonnull String failureMessage) { - - private static StartedStage started(@Nonnull SpaceId spaceId, - @Nonnull BenchmarkChunks chunks, - int retainedColumns, - @Nonnull PrewarmStats prewarm) { - return new StartedStage(true, spaceId, chunks, retainedColumns, prewarm, ""); - } - - private static StartedStage failed(int count, @Nonnull String failureMessage) { - return new StartedStage(false, - null, - new BenchmarkChunks(new LongOpenHashSet(), Set.of()), - 0, - new PrewarmStats(0, 0, 0), - "count=" + count + " " + failureMessage); - } - } - - private record StageReport(int count, - double observedTickRate, - double avgStepMs, - double avgSnapshotMs, - double avgSyncMs, - double avgTerrainMs, - double totalMs, - int retainedColumns, - int chunkRefs, - int prewarmTargets, - int prewarmSectionsBuilt, - int bodies, - int dynamicBodies, - int terrainBodies, - int belowPlaneBodies, - int belowTerrainBodies, - int belowWorldMinBodies, - int belowVoidBodies, - int terrainBaselineBodies, - int missingTerrainBaselineBodies, - double minTerrainBottomClearance, - int terrainSamples, - int ensureCalls, - int sectionRequests, - int sectionCacheHits, - int sectionsBuilt, - int missingChunks, - int missingBlockChunks, - int missingBlockSections, - int uniqueMissingSections, - int missingOutsideRetainedEnvelope, - int bodyTargets, - @Nonnull StageHealth health) { - - private static StageReport failedPreflight(int count, @Nonnull String reason) { - StageHealth health = new StageHealth(StageStatus.STOP, reason); - return new StageReport(count, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - Double.NaN, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - health); - } - - private String summary() { - return "count=" + count - + " health=" + health.status() - + " reason=" + health.reason() - + " tps=" + format(observedTickRate) - + " totalMs=" + format(totalMs) - + " step/snapshot/sync/terrainMs=" + format(avgStepMs) - + "/" + format(avgSnapshotMs) - + "/" + format(avgSyncMs) - + "/" + format(avgTerrainMs) - + " bodies dynamic/physicsChunk=" + dynamicBodies - + "/" + terrainBodies - + " belowPlane/terrain/worldMin/void=" + belowPlaneBodies - + "/" + belowTerrainBodies - + "/" + belowWorldMinBodies - + "/" + belowVoidBodies - + " terrainBaseline samples/missing/minClearance=" + terrainBaselineBodies - + "/" + missingTerrainBaselineBodies - + "/" + formatOptional(minTerrainBottomClearance) - + " chunks retained/refs=" + retainedColumns - + "/" + chunkRefs - + " prewarm targets/built=" + prewarmTargets - + "/" + prewarmSectionsBuilt - + " streamingFallMinY=" + format(STREAMING_FALL_ENVELOPE_MIN_Y) - + " streamingHorizontalHalo=" + format(STREAMING_HORIZONTAL_DRIFT_HALO_BLOCKS) - + " terrain samples/ensure/req/hit/build/miss/bodyTargets=" - + terrainSamples - + "/" + ensureCalls - + "/" + sectionRequests - + "/" + sectionCacheHits - + "/" + sectionsBuilt - + "/" + missingChunks - + "/" + bodyTargets - + " missing blockChunk/blockSection/unique/outsideRetained=" - + missingBlockChunks - + "/" + missingBlockSections - + "/" + uniqueMissingSections - + "/" + missingOutsideRetainedEnvelope - + " totalBodies=" + bodies; - } - } - - private record StageHealth(@Nonnull StageStatus status, @Nonnull String reason) { - } - - private enum StageStatus { - PASS, - WARN, - STOP - } - - private record PrewarmStats(int sectionTargets, int sectionsBuilt, int colliderBodies) { - } - - private record BenchmarkChunks(@Nonnull LongSet columns, @Nonnull Set sections) { - - private int size() { - return columns.size() + sections.size(); - } - } - - private record ChunkSection(int x, int y, int z) { - } - - private record BenchmarkLayout(@Nonnull Vector3d origin, int side, double spacing) { - - private static BenchmarkLayout flatGrid(int count) { - int side = (int) Math.ceil(Math.sqrt(count)); - double half = (side - 1) * DETACHED_SPACING * 0.5; - return new BenchmarkLayout(new Vector3d( - ORIGIN.x - half, - ORIGIN.y, - ORIGIN.z - half), side, DETACHED_SPACING); - } - - private double positionX(int index) { - int x = index % side; - return origin.x + x * spacing; - } - - private double positionY() { - return origin.y; - } - - private double positionZ(int index) { - int z = index / side; - return origin.z + z * spacing; - } - } - - private static final class SpaceStats { - - private int bodies; - private int dynamicBodies; - private int terrainBodies; - private int belowPlaneBodies; - private int belowTerrainBodies; - private int belowWorldMinBodies; - private int belowVoidBodies; - private int terrainBaselineBodies; - private int missingTerrainBaselineBodies; - private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; - - private static SpaceStats collect(@Nonnull Store physicsStore, - @Nonnull PhysicsChunkCollisionStreamingResource collisionStreaming, - @Nonnull SpaceId spaceId) { - BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( - physicsStore, - collisionStreaming, - new PhysicsStoreBenchmarkQueries.BenchmarkSpaceStatsRequest(spaceId, - GROUND_Y, - BELOW_PLANE_TOLERANCE, - BODY_WORLD_MIN_Y, - BODY_VOID_Y, - true)); - SpaceStats stats = new SpaceStats(); - stats.bodies = view.bodies(); - stats.dynamicBodies = view.dynamicBodies(); - stats.terrainBodies = view.terrainBodies(); - stats.belowPlaneBodies = view.belowPlaneBodies(); - stats.belowTerrainBodies = view.belowTerrainBodies(); - stats.belowWorldMinBodies = view.belowWorldMinBodies(); - stats.belowVoidBodies = view.belowVoidBodies(); - stats.terrainBaselineBodies = view.terrainBaselineBodies(); - stats.missingTerrainBaselineBodies = view.missingTerrainBaselineBodies(); - stats.minTerrainBottomClearance = view.minTerrainBottomClearance(); - return stats; - } - - private double minTerrainBottomClearance() { - return terrainBaselineBodies > 0 ? minTerrainBottomClearance : Double.NaN; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java deleted file mode 100644 index 5244ca78..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseLiveCrucibleTests.java +++ /dev/null @@ -1,176 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.entity.entities.BlockEntity; -import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.modules.time.TimeResource; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import org.joml.Vector3d; -import org.joml.Vector3f; - -/** - * Crucible suites that exercise entity-backed bodies through Hytale's live ECS. - */ -final class ImpulseLiveCrucibleTests { - - private static final String DEFAULT_BLOCK_TYPE = "Rock_Stone"; - private static final ComponentType TRANSFORM_TYPE = - TransformComponent.getComponentType(); - private static final ComponentType DESPAWN_TYPE = - DespawnComponent.getComponentType(); - - private ImpulseLiveCrucibleTests() { - } - - static void register(CrucibleBridge bridge, ClassLoader loader) - throws ReflectiveOperationException { - - bridge.registerSuite(loader, ecsLiveSuite()); - } - - private static CrucibleSuite ecsLiveSuite() { - return new CrucibleSuite( - "impulse:ecs_live", - "Impulse ECS Live", - "Verifies entity-backed bodies move through the real Hytale ECS tick path", - Set.of("live", "integration"), - List.of(CrucibleTestCase.async("entity body falls", - ImpulseLiveCrucibleTests::entityBodyFallsThroughEcs, - "Entity-backed body did not fall through the live ECS tick path"))); - } - - private static CompletionStage entityBodyFallsThroughEcs(CrucibleContext context) { - try { - World world = context.world(); - Store store = world.getEntityStore().getStore(); - Store physicsStore = physicsStore(world); - SpaceId spaceId = liveTestSpaceId(physicsStore, world); - - Vector3d visualPosition = new Vector3d( - context.wx(0), - context.wy(20), - context.wz(0)); - PhysicsSpaceMutations.putSpaceGravity(physicsStore, - spaceId, - new Vector3f(0.0f, -9.81f, 0.0f)); - UUID bodyUuid = UUID.randomUUID(); - submitLiveBody(physicsStore, spaceId, bodyUuid, visualPosition); - - Ref ref = spawnLiveBlockBody(store, spaceId, bodyUuid, visualPosition); - double startY = visualPosition.y; - - return context.waitApproxTicksOnWorld(40).thenApply(ignored -> bodyAndEntityMovedDown( - store, - ref, - bodyUuid, - startY)); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - } - - private static boolean bodyAndEntityMovedDown(Store store, - Ref ref, - UUID bodyUuid, - double startY) { - - if (!ref.isValid()) { - return false; - } - TransformComponent transform = store.getComponent(ref, TRANSFORM_TYPE); - if (transform == null) { - return false; - } - double transformY = transform.getPosition().y; - PhysicsBodySnapshot snapshot = physicsStore(store.getExternalData().getWorld()) - .getResource(PhysicsSnapshotResource.getResourceType()) - .getBody(bodyUuid); - if (snapshot == null) { - return false; - } - float bodyY = snapshot.position().y; - return transformY < startY - 0.05 && bodyY < startY - 0.05f; - } - - private static SpaceId liveTestSpaceId(Store store, World world) { - SpaceId existingSpaceId = PhysicsSpaces.spaceIds(store) - .stream() - .min(Comparator.comparingInt(SpaceId::value)) - .orElse(null); - if (existingSpaceId != null) { - return existingSpaceId; - } - return PhysicsSpaces.create(store, CrucibleBackends.requireBackendId()); - } - - private static void submitLiveBody(Store store, - SpaceId spaceId, - UUID bodyUuid, - Vector3d visualPosition) { - PhysicsThreading.requireWorldThread(store, "add Crucible live PhysicsStore body entity"); - Ref spaceRef = PhysicsSpaces.resolveRef(store, spaceId); - if (spaceRef == null) { - throw new IllegalStateException("No PhysicsStore space ref for id=" + spaceId.value()); - } - store.addEntity(PhysicsBodyEntities.dynamicBodyHolder(spaceRef, - bodyUuid, - new Vector3f((float) visualPosition.x, - (float) visualPosition.y, - (float) visualPosition.z), - PhysicsShapeSpec.box(0.5f, 0.5f, 0.5f), - 1.0f, - RigidBodySpawnSettings.defaults(), - null), AddReason.SPAWN); - } - - private static Store physicsStore(World world) { - return PhysicsThreading.store(world); - } - - private static Ref spawnLiveBlockBody(Store store, - SpaceId spaceId, - UUID bodyUuid, - Vector3d visualPosition) { - - TimeResource time = store.getResource(TimeResource.getResourceType()); - Holder holder = BlockEntity.assembleDefaultBlockEntity( - time, - DEFAULT_BLOCK_TYPE, - new Vector3d(visualPosition)); - holder.removeComponent(DESPAWN_TYPE); - holder.addComponent(BodyAttachmentComponent.getComponentType(), - new BodyAttachmentComponent(bodyUuid, - TransformAuthority.BODY, - AttachmentLifecycle.EXTERNAL_ENTITY)); - holder.addComponent(ImpulseControllableComponent.getComponentType(), - new ImpulseControllableComponent()); - - return store.addEntity(holder, AddReason.SPAWN); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java deleted file mode 100644 index 7b0aa8d5..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/ImpulseRapierBodyBenchmarkCrucibleTests.java +++ /dev/null @@ -1,830 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.Impulse; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.StepSnapshot; -import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource.SyncSnapshot; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.profiling.PhysicsChunkProfilingResource.Snapshot; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsStepSchedulerResource; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualInterestResource; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsWorlds; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsStepSchedulingMode; -import dev.hytalemodding.impulse.core.plugin.settings.PhysicsWorldSettings; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3d; -import org.joml.Vector3f; - -/** - * Benchmark-oriented Crucible scenario for Rapier detached body-only fixed substeps. - */ -final class ImpulseRapierBodyBenchmarkCrucibleTests { - - private static final HytaleLogger LOGGER = HytaleLogger.get("Impulse"); - private static final BackendId RAPIER_BACKEND_ID = new BackendId("impulse:rapier"); - private static final PhysicsBackendExtensionId RAPIER_SOLVER_EXTENSION_ID = - new PhysicsBackendExtensionId("impulse:rapier_solver"); - private static final String RAPIER_INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; - private static final String RAPIER_MIN_ISLAND_SIZE = "minIslandSize"; - private static final String COUNT_PROPERTY = "impulse.crucible.rapierBodyMatrix.count"; - private static final String SUBSTEPS_PROPERTY = "impulse.crucible.rapierBodyMatrix.substeps"; - private static final String WARMUP_TICKS_PROPERTY = - "impulse.crucible.rapierBodyMatrix.warmupTicks"; - private static final String SAMPLE_TICKS_PROPERTY = - "impulse.crucible.rapierBodyMatrix.sampleTicks"; - private static final String MIN_TPS_PROPERTY = "impulse.crucible.rapierBodyMatrix.minTps"; - private static final String WARN_TPS_PROPERTY = "impulse.crucible.rapierBodyMatrix.warnTps"; - - private static final int DEFAULT_COUNT = 5_000; - private static final int DEFAULT_WARMUP_TICKS = 60; - private static final int DEFAULT_SAMPLE_TICKS = 200; - private static final int MIN_COUNT = 1; - private static final int MAX_COUNT = 10_000; - private static final int MIN_WARMUP_TICKS = 1; - private static final int MAX_WARMUP_TICKS = 1_200; - private static final int MIN_SAMPLE_TICKS = 20; - private static final int MAX_SAMPLE_TICKS = 7_200; - private static final float TARGET_MAX_STEP_DT = 1.0f / 30.0f; - private static final float GROUND_Y = 122.0f; - private static final float BELOW_PLANE_TOLERANCE = 1.0f; - private static final float BODY_WORLD_MIN_Y = -32.0f; - private static final float BODY_VOID_Y = -128.0f; - private static final double DETACHED_SPACING = 1.5; - private static final Vector3d ORIGIN = new Vector3d(0.0, 128.0, 0.0); - - private ImpulseRapierBodyBenchmarkCrucibleTests() { - } - - static void register(CrucibleBridge bridge, ClassLoader loader) - throws ReflectiveOperationException { - - bridge.registerSuite(loader, benchmarkSuite()); - } - - private static CrucibleSuite benchmarkSuite() { - return new CrucibleSuite( - "impulse:rapier_body_fixed_substep_benchmark", - "Impulse Rapier Body Fixed-Substep Benchmark", - "Runs the 5000 detached body-only Rapier benchmark for fixed substep counts", - Set.of("benchmark", "rapier", "body", "fixed"), - List.of(CrucibleTestCase.asyncResult("5000 body-only fixed substep matrix", - ImpulseRapierBodyBenchmarkCrucibleTests::rapierBodyMatrix, - "Rapier body fixed-substep benchmark breached a health gate"))); - } - - private static CompletionStage rapierBodyMatrix( - CrucibleContext context) { - if (!rapierBackendAvailable()) { - return CompletableFuture.completedFuture(CrucibleTestCase.TestOutcome.fail( - "Rapier backend impulse:rapier is not registered; available=" - + availableBackendIds())); - } - try { - MatrixPlan plan = MatrixPlan.fromSystemProperties(); - MatrixRunner runner = new MatrixRunner(context, plan); - return runner.run(); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - } - - private static boolean rapierBackendAvailable() { - return Impulse.getRuntimeProviders().stream() - .anyMatch(provider -> RAPIER_BACKEND_ID.equals(provider.getId())); - } - - @Nonnull - private static List availableBackendIds() { - return Impulse.getRuntimeProviders().stream() - .map(provider -> provider.getId().value()) - .sorted() - .toList(); - } - - private static final class MatrixRunner { - - private final CrucibleContext context; - private final MatrixPlan plan; - private final World world; - private final Store store; - private final Store physicsStore; - private final PhysicsProfilingResource physicsStoreProfiling; - private final PhysicsRuntimeProfilingResource runtimeProfiling; - private final PhysicsChunkProfilingResource collisionProfiling; - private final PhysicsWorldSettings previousWorldSettings; - private final boolean previousPhysicsStoreProfilingEnabled; - private final boolean previousRuntimeProfilingEnabled; - private final boolean previousTerrainProfilingEnabled; - - private MatrixRunner(@Nonnull CrucibleContext context, @Nonnull MatrixPlan plan) - throws ReflectiveOperationException { - this.context = context; - this.plan = plan; - this.world = context.world(); - this.store = world.getEntityStore().getStore(); - this.physicsStore = PhysicsStoreCrucibleSupport.physicsStore(world); - this.physicsStoreProfiling = physicsStore.getResource( - PhysicsProfilingResource.getResourceType()); - this.runtimeProfiling = store.getResource(PhysicsRuntimeProfilingResource.getResourceType()); - this.collisionProfiling = store.getResource( - PhysicsChunkProfilingResource.getResourceType()); - this.previousWorldSettings = PhysicsWorlds.settings(physicsStore); - this.previousPhysicsStoreProfilingEnabled = physicsStoreProfiling.isEnabled(); - this.previousRuntimeProfilingEnabled = runtimeProfiling.isEnabled(); - this.previousTerrainProfilingEnabled = collisionProfiling.isEnabled(); - } - - private CompletionStage run() { - return runCase(0, new ArrayList<>()).handle((outcome, failure) -> { - clearCaseState(); - restoreSettings(); - if (failure != null) { - if (failure instanceof CompletionException completionException) { - throw completionException; - } - throw new CompletionException(failure); - } - return outcome; - }); - } - - private CompletionStage runCase(int index, - @Nonnull List reports) { - if (index >= plan.substeps().size()) { - logComparison(reports); - return CompletableFuture.completedFuture(outcome(reports)); - } - - MatrixCase matrixCase = new MatrixCase(plan.count(), plan.substeps().get(index)); - return startCase(matrixCase) - .thenCompose(started -> contextWait(plan.warmupTicks()) - .thenCompose(_ -> waitForPhysicsStoreIdle()) - .thenCompose(_ -> contextWait(1)) - .thenCompose(_ -> { - physicsStoreProfiling.reset(); - runtimeProfiling.reset(); - collisionProfiling.reset(); - physicsStoreProfiling.setEnabled(true); - runtimeProfiling.setEnabled(true); - collisionProfiling.setEnabled(true); - long startedNanos = System.nanoTime(); - return contextWait(plan.sampleTicks()).thenApply( - _ -> finishCase(matrixCase, started, startedNanos)); - })) - .thenCompose(report -> { - reports.add(report); - LOGGER.at(Level.INFO).log("Crucible Rapier body matrix case: %s", - report.summary()); - clearCaseState(); - if (report.health().status() == MatrixStatus.STOP) { - return CompletableFuture.completedFuture(outcome(reports)); - } - return runCase(index + 1, reports); - }); - } - - private CompletionStage startCase(@Nonnull MatrixCase matrixCase) { - clearCaseState(); - PhysicsWorldSettings worldSettings = PhysicsWorlds.settings(physicsStore); - worldSettings.setStepMode(PhysicsStepMode.FIXED); - worldSettings.setStepSchedulingMode(PhysicsStepSchedulingMode.DROP_PENDING_DT); - worldSettings.setSimulationSteps(matrixCase.fixedSubsteps()); - worldSettings.setMaxStepDt(TARGET_MAX_STEP_DT); - PhysicsWorlds.putSettings(physicsStore, worldSettings); - visualInterests().clearSyntheticVisualInterests(); - - try { - SpaceId spaceId = PhysicsSpaces.create(physicsStore, RAPIER_BACKEND_ID); - PhysicsSpaceMutations.putSolverSettings(physicsStore, - spaceId, - benchmarkSolverSettings()); - PhysicsSpaceMutations.putExtensionSettings(physicsStore, - spaceId, - benchmarkExtensionSettings()); - return populateBenchmarkSpace(spaceId, matrixCase); - } catch (RuntimeException exception) { - return CompletableFuture.completedFuture( - StartedCase.failed(exception.getMessage())); - } - } - - @Nonnull - private static PhysicsSolverSettings benchmarkSolverSettings() { - PhysicsSolverSettings settings = new PhysicsSolverSettings(); - settings.setSolverIterations(PhysicsSolverSettings.DEFAULT_SOLVER_ITERATIONS); - settings.setStabilizationIterations( - PhysicsSolverSettings.DEFAULT_STABILIZATION_ITERATIONS); - return settings; - } - - @Nonnull - private static PhysicsExtensionSettings benchmarkExtensionSettings() { - PhysicsExtensionSettings settings = new PhysicsExtensionSettings(); - settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS, 1); - settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_MIN_ISLAND_SIZE, 128); - return settings; - } - - private CompletionStage populateBenchmarkSpace(@Nonnull SpaceId spaceId, - @Nonnull MatrixCase matrixCase) { - BenchmarkLayout layout = BenchmarkLayout.flatGrid(matrixCase.count()); - RigidBodySpawnSettings groundSettings = RigidBodySpawnSettings.defaults() - .withCollisionFilter(PhysicsCollisionFilters.TERRAIN, PhysicsCollisionFilters.ALL); - RigidBodySpawnSettings bodySettings = RigidBodySpawnSettings.of(0.45f, - 0.0f, - 0.02f, - 0.25f, - PhysicsCollisionFilters.DYNAMIC_BODY, - PhysicsCollisionFilters.TERRAIN | PhysicsCollisionFilters.DYNAMIC_BODY); - PhysicsStoreCrucibleSupport.addBody(physicsStore, - spaceId, - UUID.randomUUID(), - new Vector3f(0.0f, GROUND_Y, 0.0f), - PhysicsShapeSpec.plane(GROUND_Y), - PhysicsBodyType.STATIC, - 0.0f, - groundSettings, - null); - PhysicsShapeSpec box = PhysicsShapeSpec.box(0.48f, 0.48f, 0.48f); - for (int i = 0; i < matrixCase.count(); i++) { - PhysicsStoreCrucibleSupport.addBody(physicsStore, - spaceId, - UUID.randomUUID(), - new Vector3f((float) layout.positionX(i), - (float) layout.positionY(), - (float) layout.positionZ(i)), - box, - PhysicsBodyType.DYNAMIC, - 1.0f, - bodySettings, - null); - } - return CompletableFuture.completedFuture(StartedCase.started(spaceId)); - } - - private MatrixReport finishCase(@Nonnull MatrixCase matrixCase, - @Nonnull StartedCase started, - long startedNanos) { - if (!started.started()) { - return MatrixReport.failedPreflight(matrixCase, started.failureMessage()); - } - SpaceId spaceId = started.spaceId(); - if (spaceId == null || !PhysicsSpaces.hasSpace(physicsStore, spaceId)) { - return MatrixReport.failedPreflight(matrixCase, - "space disappeared during benchmark"); - } - - StepSnapshot step = runtimeProfiling.getCumulativeStep(); - SyncSnapshot sync = runtimeProfiling.getCumulativeSync(); - Snapshot collisionProfilingSnapshot = collisionProfiling.getCumulativeSnapshot(); - double elapsedSeconds = Math.max(0.001, - (System.nanoTime() - startedNanos) / 1_000_000_000.0); - double observedTickRate = step.getTickSamples() / elapsedSeconds; - SpaceStats stats = SpaceStats.collect(physicsStore, spaceId); - double avgStepMs = averageMillis(step.getTickNanos(), step.getTickSamples()); - double avgSnapshotMs = averageMillis(step.getSnapshotNanos(), step.getTickSamples()); - double avgSyncMs = averageMillis(sync.getTickNanos(), sync.getTickSamples()); - double avgTerrainMs = averageMillis(collisionProfilingSnapshot.getTickNanos(), - collisionProfilingSnapshot.getTickSamples()); - double totalMs = avgStepMs - + avgSnapshotMs - + avgSyncMs - + avgTerrainMs; - MatrixHealth health = assessHealth(matrixCase, - observedTickRate, - step, - collisionProfilingSnapshot, - stats); - - return new MatrixReport(matrixCase, - observedTickRate, - avgStepMs, - avgSnapshotMs, - avgSyncMs, - avgTerrainMs, - totalMs, - step.getTickSamples(), - step.getSubsteps(), - step.getBodySnapshots(), - step.getSpatialIndexCells(), - sync.getTickSamples(), - sync.getBodiesInspected(), - sync.getBodiesSynced(), - collisionProfilingSnapshot.getTickSamples(), - collisionProfilingSnapshot.getStreamingSpaces(), - collisionProfilingSnapshot.getEnsureCalls(), - collisionProfilingSnapshot.getSectionRequests(), - collisionProfilingSnapshot.getSectionsBuilt(), - collisionProfilingSnapshot.getBodyStreamingTargets(), - stats.bodies, - stats.dynamicBodies, - stats.detachedBodies, - stats.rawBodies, - stats.terrainBodies, - stats.awakeDynamicBodies, - stats.sleepingDynamicBodies, - stats.belowPlaneBodies, - stats.belowWorldMinBodies, - stats.belowVoidBodies, - stats.minDynamicBodyY(), - stats.maxDynamicBodyY(), - health); - } - - private CompletionStage contextWait(int ticks) { - try { - return context.waitApproxTicksOnWorld(ticks); - } catch (ReflectiveOperationException e) { - return CompletableFuture.failedFuture(e); - } - } - - private CompletionStage waitForPhysicsStoreIdle() { - return physicsStore.getResource(PhysicsStepSchedulerResource.getResourceType()) - .whenIdle(); - } - - private void clearCaseState() { - removeBenchmarkEntities(); - visualInterests().clearSyntheticVisualInterests(); - PhysicsStoreCrucibleSupport.clearAll(physicsStore); - physicsStoreProfiling.reset(); - runtimeProfiling.reset(); - collisionProfiling.reset(); - collisionProfiling.clearDiagnosticRetainedSections(); - } - - private void restoreSettings() { - PhysicsWorlds.putSettings(physicsStore, previousWorldSettings); - physicsStoreProfiling.setEnabled(previousPhysicsStoreProfilingEnabled); - runtimeProfiling.setEnabled(previousRuntimeProfilingEnabled); - collisionProfiling.setEnabled(previousTerrainProfilingEnabled); - } - - @Nonnull - private PhysicsVisualInterestResource visualInterests() { - return store.getResource(PhysicsVisualInterestResource.getResourceType()); - } - - private void removeBenchmarkEntities() { - ComponentType attachmentType = - BodyAttachmentComponent.getComponentType(); - store.forEachEntityParallel(attachmentType, - (index, archetypeChunk, commandBuffer) -> commandBuffer.removeEntity( - archetypeChunk.getReferenceTo(index), - RemoveReason.REMOVE)); - ComponentType controlSessionType = - PhysicsControlSessionComponent.getComponentType(); - store.forEachEntityParallel(controlSessionType, - (index, archetypeChunk, commandBuffer) -> commandBuffer.removeComponent( - archetypeChunk.getReferenceTo(index), - controlSessionType)); - } - - } - - private static CrucibleTestCase.TestOutcome outcome(@Nonnull List reports) { - List failed = reports.stream() - .filter(report -> report.health().status() == MatrixStatus.STOP) - .map(MatrixReport::summary) - .toList(); - if (!failed.isEmpty()) { - return CrucibleTestCase.TestOutcome.fail(String.join(" | ", failed)); - } - return CrucibleTestCase.TestOutcome.pass(); - } - - private static MatrixHealth assessHealth(@Nonnull MatrixCase matrixCase, - double observedTickRate, - @Nonnull StepSnapshot step, - @Nonnull Snapshot collisionProfilingSnapshot, - @Nonnull SpaceStats stats) { - List stops = new ArrayList<>(); - List warnings = new ArrayList<>(); - if (step.getTickSamples() <= 0) { - stops.add("stepSamples=0"); - } - if (collisionProfilingSnapshot.getTickSamples() <= 0) { - stops.add("terrainSamples=0"); - } - if (stats.dynamicBodies != matrixCase.count()) { - stops.add("dynamicBodies=" + stats.dynamicBodies + "!=" + matrixCase.count()); - } - if (stats.detachedBodies != matrixCase.count()) { - stops.add("detachedBodies=" + stats.detachedBodies + "!=" + matrixCase.count()); - } - int expectedBodies = expectedBenchmarkBodies(matrixCase); - if (stats.bodies != expectedBodies) { - stops.add("bodies=" + stats.bodies + "!=" + expectedBodies); - } - int expectedSubsteps = step.getTickSamples() * matrixCase.fixedSubsteps(); - if (step.getTickSamples() > 0 && step.getSubsteps() != expectedSubsteps) { - stops.add("substeps=" + step.getSubsteps() + "!=" + expectedSubsteps); - } - int expectedSnapshots = step.getTickSamples() * expectedBodies; - if (step.getTickSamples() > 0 && step.getBodySnapshots() != expectedSnapshots) { - stops.add("bodySnapshots=" + step.getBodySnapshots() + "!=" + expectedSnapshots); - } - if (collisionProfilingSnapshot.getStreamingSpaces() > 0) { - stops.add("collisionStreamingSpaces=" + collisionProfilingSnapshot.getStreamingSpaces()); - } - if (collisionProfilingSnapshot.getEnsureCalls() > 0) { - stops.add("terrainEnsureCalls=" + collisionProfilingSnapshot.getEnsureCalls()); - } - if (collisionProfilingSnapshot.getSectionsBuilt() > 0) { - stops.add("terrainSectionsBuilt=" + collisionProfilingSnapshot.getSectionsBuilt()); - } - if (collisionProfilingSnapshot.getBodyStreamingTargets() > 0) { - stops.add("terrainBodyTargets=" + collisionProfilingSnapshot.getBodyStreamingTargets()); - } - if (stats.terrainBodies > 0) { - stops.add("terrainBodies=" + stats.terrainBodies); - } - if (stats.belowWorldMinBodies > 0) { - stops.add("belowWorldMinBodies=" + stats.belowWorldMinBodies); - } - if (stats.belowVoidBodies > 0) { - stops.add("belowVoidBodies=" + stats.belowVoidBodies); - } - int maxBelowGround = Math.max(5, (int) Math.ceil(matrixCase.count() * 0.01)); - if (stats.belowPlaneBodies > maxBelowGround) { - stops.add("belowPlaneBodies=" + stats.belowPlaneBodies + ">" + maxBelowGround); - } - double minTps = configuredDouble(MIN_TPS_PROPERTY, 8.0); - if (observedTickRate < minTps) { - stops.add("observedTPS=" + format(observedTickRate) + "<" + format(minTps)); - } - if (!stops.isEmpty()) { - return new MatrixHealth(MatrixStatus.STOP, String.join("; ", stops)); - } - double warnTps = configuredDouble(WARN_TPS_PROPERTY, 15.0); - if (observedTickRate < warnTps) { - warnings.add("observedTPS=" + format(observedTickRate) + "<" + format(warnTps)); - } - if (!warnings.isEmpty()) { - return new MatrixHealth(MatrixStatus.WARN, String.join("; ", warnings)); - } - return new MatrixHealth(MatrixStatus.PASS, "within gates"); - } - - private static int expectedBenchmarkBodies(@Nonnull MatrixCase matrixCase) { - return matrixCase.count() + 1; - } - - private static void logComparison(@Nonnull List reports) { - if (reports.size() < 2) { - return; - } - MatrixReport first = reports.get(0); - MatrixReport second = reports.get(1); - LOGGER.at(Level.INFO).log("Crucible Rapier body matrix comparison: %sx=%sms " - + "%sx=%sms stepRatio=%s snapshotRatio=%s " - + "totalRatio=%s collisionCounters=%s/%s", - first.matrixCase().fixedSubsteps(), - format(first.avgStepMs()), - second.matrixCase().fixedSubsteps(), - format(second.avgStepMs()), - format(ratio(second.avgStepMs(), first.avgStepMs())), - format(ratio(second.avgSnapshotMs(), first.avgSnapshotMs())), - format(ratio(second.totalMs(), first.totalMs())), - first.terrainCounterSummary(), - second.terrainCounterSummary()); - } - - private static double ratio(double numerator, double denominator) { - return denominator > 0.0 ? numerator / denominator : Double.NaN; - } - - private static double averageMillis(long nanos, int samples) { - if (samples <= 0) { - return 0.0; - } - return nanos / 1_000_000.0 / samples; - } - - private static String format(double value) { - return String.format(Locale.ROOT, "%.3f", value); - } - - private static String formatOptional(double value) { - return Double.isFinite(value) ? format(value) : "n/a"; - } - - private static int configuredInt(String property, int fallback, int min, int max) { - String raw = System.getProperty(property); - if (raw == null || raw.isBlank()) { - return fallback; - } - try { - return Math.clamp(Integer.parseInt(raw.trim()), min, max); - } catch (NumberFormatException ignored) { - return fallback; - } - } - - private static double configuredDouble(String property, double fallback) { - String raw = System.getProperty(property); - if (raw == null || raw.isBlank()) { - return fallback; - } - try { - return Double.parseDouble(raw.trim()); - } catch (NumberFormatException ignored) { - return fallback; - } - } - - private record MatrixPlan(int count, - @Nonnull List substeps, - int warmupTicks, - int sampleTicks) { - - private static MatrixPlan fromSystemProperties() { - return new MatrixPlan(configuredInt(COUNT_PROPERTY, DEFAULT_COUNT, MIN_COUNT, MAX_COUNT), - configuredSubsteps(), - configuredInt(WARMUP_TICKS_PROPERTY, - DEFAULT_WARMUP_TICKS, - MIN_WARMUP_TICKS, - MAX_WARMUP_TICKS), - configuredInt(SAMPLE_TICKS_PROPERTY, - DEFAULT_SAMPLE_TICKS, - MIN_SAMPLE_TICKS, - MAX_SAMPLE_TICKS)); - } - - @Nonnull - private static List configuredSubsteps() { - String raw = System.getProperty(SUBSTEPS_PROPERTY); - if (raw == null || raw.isBlank()) { - return List.of(1, 2); - } - List substeps = new ArrayList<>(); - for (String token : raw.split(",")) { - try { - int value = Math.clamp(Integer.parseInt(token.trim()), - PhysicsWorldSettings.MIN_SIMULATION_STEPS, - PhysicsWorldSettings.MAX_SIMULATION_STEPS); - if (!substeps.contains(value)) { - substeps.add(value); - } - } catch (NumberFormatException ignored) { - } - } - return substeps.isEmpty() ? List.of(1, 2) : List.copyOf(substeps); - } - } - - private record MatrixCase(int count, int fixedSubsteps) { - } - - private record StartedCase(boolean started, - @Nullable SpaceId spaceId, - @Nonnull String failureMessage) { - - private static StartedCase started(@Nonnull SpaceId spaceId) { - return new StartedCase(true, spaceId, ""); - } - - private static StartedCase failed(@Nullable String failureMessage) { - return new StartedCase(false, - null, - failureMessage != null ? failureMessage : "unknown startup failure"); - } - } - - private record MatrixReport(@Nonnull MatrixCase matrixCase, - double observedTickRate, - double avgStepMs, - double avgSnapshotMs, - double avgSyncMs, - double avgTerrainMs, - double totalMs, - int stepSamples, - int substeps, - int bodySnapshots, - int spatialIndexCells, - int syncSamples, - int syncInspected, - int syncSynced, - int terrainSamples, - int collisionStreamingSpaces, - int terrainEnsureCalls, - int terrainSectionRequests, - int terrainSectionsBuilt, - int terrainBodyTargets, - int bodies, - int dynamicBodies, - int detachedBodies, - int rawBodies, - int terrainBodies, - int awakeDynamicBodies, - int sleepingDynamicBodies, - int belowPlaneBodies, - int belowWorldMinBodies, - int belowVoidBodies, - double minDynamicBodyY, - double maxDynamicBodyY, - @Nonnull MatrixHealth health) { - - private static MatrixReport failedPreflight(@Nonnull MatrixCase matrixCase, - @Nonnull String reason) { - MatrixHealth health = new MatrixHealth(MatrixStatus.STOP, reason); - return new MatrixReport(matrixCase, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - Double.NaN, - Double.NaN, - health); - } - - private String summary() { - return "count=" + matrixCase.count() - + " fixedSubsteps=" + matrixCase.fixedSubsteps() - + " health=" + health.status() - + " reason=" + health.reason() - + " tps=" + format(observedTickRate) - + " totalMs=" + format(totalMs) - + " step/snapshot/sync/terrainMs=" + format(avgStepMs) - + "/" + format(avgSnapshotMs) - + "/" + format(avgSyncMs) - + "/" + format(avgTerrainMs) - + " step samples/substeps/bodySnapshots/spatialCells=" + stepSamples - + "/" + substeps - + "/" + bodySnapshots - + "/" + spatialIndexCells - + " sync samples/inspected/synced=" + syncSamples - + "/" + syncInspected - + "/" + syncSynced - + " terrain samples/streaming/ensure/req/build/bodyTargets=" - + terrainSamples - + "/" + collisionStreamingSpaces - + "/" + terrainEnsureCalls - + "/" + terrainSectionRequests - + "/" + terrainSectionsBuilt - + "/" + terrainBodyTargets - + " bodies total/dynamic/detached/raw/physicsChunk=" + bodies - + "/" + dynamicBodies - + "/" + detachedBodies - + "/" + rawBodies - + "/" + terrainBodies - + " awake/sleeping=" + awakeDynamicBodies - + "/" + sleepingDynamicBodies - + " belowPlane/worldMin/void=" + belowPlaneBodies - + "/" + belowWorldMinBodies - + "/" + belowVoidBodies - + " yMin/yMax=" + formatOptional(minDynamicBodyY) - + "/" + formatOptional(maxDynamicBodyY); - } - - private String terrainCounterSummary() { - return terrainSamples - + "/" + collisionStreamingSpaces - + "/" + terrainEnsureCalls - + "/" + terrainSectionsBuilt - + "/" + terrainBodyTargets; - } - } - - private record MatrixHealth(@Nonnull MatrixStatus status, @Nonnull String reason) { - } - - private enum MatrixStatus { - PASS, - WARN, - STOP - } - - private record BenchmarkLayout(@Nonnull Vector3d origin, int side, double spacing) { - - private static BenchmarkLayout flatGrid(int count) { - int side = (int) Math.ceil(Math.sqrt(count)); - double half = (side - 1) * DETACHED_SPACING * 0.5; - return new BenchmarkLayout(new Vector3d( - ORIGIN.x - half, - ORIGIN.y, - ORIGIN.z - half), side, DETACHED_SPACING); - } - - private double positionX(int index) { - return origin.x + (index % side) * spacing; - } - - private double positionY() { - return origin.y; - } - - private double positionZ(int index) { - return origin.z + ((double) index / side) * spacing; - } - } - - private static final class SpaceStats { - - private int bodies; - private int dynamicBodies; - private int awakeDynamicBodies; - private int sleepingDynamicBodies; - private int detachedBodies; - private int rawBodies; - private int terrainBodies; - private int belowPlaneBodies; - private int belowWorldMinBodies; - private int belowVoidBodies; - private double minDynamicBodyY = Double.POSITIVE_INFINITY; - private double maxDynamicBodyY = Double.NEGATIVE_INFINITY; - - private static SpaceStats collect(@Nonnull Store physicsStore, - @Nonnull SpaceId spaceId) { - BenchmarkSpaceStatsView view = PhysicsStoreBenchmarkQueries.benchmarkSpaceStats( - physicsStore, - null, - new PhysicsStoreBenchmarkQueries.BenchmarkSpaceStatsRequest(spaceId, - GROUND_Y, - BELOW_PLANE_TOLERANCE, - BODY_WORLD_MIN_Y, - BODY_VOID_Y, - false)); - SpaceStats stats = new SpaceStats(); - stats.bodies = view.bodies(); - stats.dynamicBodies = view.dynamicBodies(); - stats.awakeDynamicBodies = view.awakeDynamicBodies(); - stats.sleepingDynamicBodies = view.sleepingDynamicBodies(); - stats.detachedBodies = view.detachedBodies(); - stats.rawBodies = view.rawBodies(); - stats.terrainBodies = view.terrainBodies(); - stats.belowPlaneBodies = view.belowPlaneBodies(); - stats.belowWorldMinBodies = view.belowWorldMinBodies(); - stats.belowVoidBodies = view.belowVoidBodies(); - stats.minDynamicBodyY = view.minDynamicBodyY(); - stats.maxDynamicBodyY = view.maxDynamicBodyY(); - return stats; - } - - private double minDynamicBodyY() { - return Double.isFinite(minDynamicBodyY) ? minDynamicBodyY : Double.NaN; - } - - private double maxDynamicBodyY() { - return Double.isFinite(maxDynamicBodyY) ? maxDynamicBodyY : Double.NaN; - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsChunkSubPluginCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsChunkSubPluginCrucibleSupport.java deleted file mode 100644 index e4534599..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsChunkSubPluginCrucibleSupport.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.common.plugin.PluginIdentifier; -import com.hypixel.hytale.server.core.plugin.PluginManager; -import com.hypixel.hytale.server.core.plugin.PluginBase; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkLifecycle; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; - -/** - * Runtime-only helpers for exercising the PhysicsChunk subplugin through Hytale. - */ -final class PhysicsChunkSubPluginCrucibleSupport { - - private static final PluginIdentifier PLUGIN_ID = - new PluginIdentifier("HytaleModding", "ImpulsePhysicsChunk"); - - private PhysicsChunkSubPluginCrucibleSupport() { - } - - static boolean ensureLoaded() { - PluginManager pluginManager = PluginManager.get(); - if (pluginManager.getPlugin(PLUGIN_ID) != null && PhysicsChunkLifecycle.isEnabled()) { - return true; - } - return pluginManager.load(PLUGIN_ID) && PhysicsChunkLifecycle.isEnabled(); - } - - @Nonnull - static CompletionStage loadUnloadReloadSmokeAsync() { - return CompletableFuture.supplyAsync(PhysicsChunkSubPluginCrucibleSupport::loadUnloadReloadSmoke) - .orTimeout(30L, TimeUnit.SECONDS) - .exceptionally(failure -> CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin lifecycle smoke failed: " + failure.getMessage())); - } - - private static CrucibleTestCase.TestOutcome loadUnloadReloadSmoke() { - PluginManager pluginManager = PluginManager.get(); - if (!pluginManager.getAvailablePlugins().containsKey(PLUGIN_ID) - && pluginManager.getPlugin(PLUGIN_ID) == null) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin is not available: " + PLUGIN_ID); - } - - if (!ensureLoaded()) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin load did not enable the lifecycle"); - } - if (!pluginManager.unload(PLUGIN_ID)) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin unload returned false"); - } - if (pluginManager.getPlugin(PLUGIN_ID) != null) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin remained loaded after unload"); - } - if (PhysicsChunkLifecycle.isEnabled()) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin unload did not disable the lifecycle"); - } - boolean loadResult = pluginManager.load(PLUGIN_ID); - PluginBase loadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!loadResult || !PhysicsChunkLifecycle.isEnabled()) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin reload load did not enable the lifecycle: " - + "loadResult=" + loadResult - + ", pluginState=" + stateOf(loadedPlugin) - + ", lifecycleEnabled=" + PhysicsChunkLifecycle.isEnabled()); - } - boolean reloadResult = pluginManager.reload(PLUGIN_ID); - PluginBase reloadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!reloadResult || !PhysicsChunkLifecycle.isEnabled()) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsChunk subplugin reload did not leave the lifecycle enabled: " - + "reloadResult=" + reloadResult - + ", pluginState=" + stateOf(reloadedPlugin) - + ", lifecycleEnabled=" + PhysicsChunkLifecycle.isEnabled()); - } - return CrucibleTestCase.TestOutcome.pass(); - } - - @Nonnull - private static String stateOf(PluginBase plugin) { - return plugin == null ? "missing" : plugin.getState().name(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java deleted file mode 100644 index af2d3c84..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsEntitySubPluginCrucibleSupport.java +++ /dev/null @@ -1,112 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.common.plugin.PluginIdentifier; -import com.hypixel.hytale.server.core.plugin.PluginBase; -import com.hypixel.hytale.server.core.plugin.PluginManager; -import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityLifecycle; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; - -/** - * Runtime-only helpers for exercising the PhysicsEntity subplugin through Hytale. - */ -final class PhysicsEntitySubPluginCrucibleSupport { - - private static final PluginIdentifier PLUGIN_ID = - new PluginIdentifier("HytaleModding", "ImpulsePhysicsEntity"); - private static final long SMOKE_TIMEOUT_SECONDS = 30L; - - private PhysicsEntitySubPluginCrucibleSupport() { - } - - @Nonnull - static CompletionStage loadUnloadReloadSmokeAsync() { - return CompletableFuture.supplyAsync(PhysicsEntitySubPluginCrucibleSupport::loadUnloadReloadSmoke) - .orTimeout(SMOKE_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .exceptionally(failure -> CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin lifecycle smoke failed: " + failure.getMessage())); - } - - private static CrucibleTestCase.TestOutcome loadUnloadReloadSmoke() { - PluginManager pluginManager = PluginManager.get(); - if (!pluginManager.getAvailablePlugins().containsKey(PLUGIN_ID) - && pluginManager.getPlugin(PLUGIN_ID) == null) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin is not available: " + PLUGIN_ID); - } - - if (!ensureLoaded(pluginManager)) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin load did not enable the lifecycle"); - } - if (!pluginManager.unload(PLUGIN_ID)) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin unload returned false"); - } - if (pluginManager.getPlugin(PLUGIN_ID) != null) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin remained loaded after unload"); - } - if (isAvailable()) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin unload did not disable availability: " - + availabilityState()); - } - boolean loadResult = pluginManager.load(PLUGIN_ID); - PluginBase loadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!loadResult || !isAvailable()) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin reload load did not enable availability: " - + "loadResult=" + loadResult - + ", pluginState=" + stateOf(loadedPlugin) - + ", " + availabilityState()); - } - boolean reloadResult = pluginManager.reload(PLUGIN_ID); - PluginBase reloadedPlugin = pluginManager.getPlugin(PLUGIN_ID); - if (!reloadResult || !isAvailable()) { - return CrucibleTestCase.TestOutcome.fail( - "PhysicsEntity subplugin reload did not leave availability enabled: " - + "reloadResult=" + reloadResult - + ", pluginState=" + stateOf(reloadedPlugin) - + ", " + availabilityState()); - } - return CrucibleTestCase.TestOutcome.pass(); - } - - private static boolean ensureLoaded(@Nonnull PluginManager pluginManager) { - if (pluginManager.getPlugin(PLUGIN_ID) != null && isAvailable()) { - return true; - } - return pluginManager.load(PLUGIN_ID) && isAvailable(); - } - - private static boolean isAvailable() { - return PhysicsEntityLifecycle.isEnabled() - && PhysicsEntityTypes.areEntityStoreTypesRegistered() - && BodyAttachmentComponent.isComponentTypeRegistered() - && GeneratedVisualProxyComponent.isComponentTypeRegistered() - && PhysicsEntityAttachments.isAvailable(); - } - - @Nonnull - private static String availabilityState() { - return "lifecycleEnabled=" + PhysicsEntityLifecycle.isEnabled() - + ", typesRegistered=" + PhysicsEntityTypes.areEntityStoreTypesRegistered() - + ", bodyAttachmentRegistered=" - + BodyAttachmentComponent.isComponentTypeRegistered() - + ", generatedVisualProxyRegistered=" - + GeneratedVisualProxyComponent.isComponentTypeRegistered() - + ", attachmentsAvailable=" + PhysicsEntityAttachments.isAvailable(); - } - - @Nonnull - private static String stateOf(PluginBase plugin) { - return plugin == null ? "missing" : plugin.getState().name(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java deleted file mode 100644 index aa765cd9..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreBenchmarkQueries.java +++ /dev/null @@ -1,161 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionStreamingResource; -import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; -import dev.hytalemodding.impulse.core.internal.physics.PhysicsSpaceMutations; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; -import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; -import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; -import java.util.UUID; -import java.util.function.BiConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Crucible-only copied diagnostics sourced from authoritative PhysicsStore entities. - */ -final class PhysicsStoreBenchmarkQueries { - - private PhysicsStoreBenchmarkQueries() { - } - - @Nonnull - static BenchmarkSpaceStatsView benchmarkSpaceStats(@Nonnull Store store, - @Nullable PhysicsChunkCollisionStreamingResource streaming, - @Nonnull BenchmarkSpaceStatsRequest query) { - PhysicsThreading.requireWorldThread(store, "read Crucible PhysicsStore benchmark stats"); - UUID spaceUuid = PhysicsSpaceMutations.requireSpaceUuid(store, query.spaceId()); - PhysicsSnapshotResource snapshots = store.getResource(PhysicsSnapshotResource.getResourceType()); - BenchmarkSpaceStatsAccumulator stats = new BenchmarkSpaceStatsAccumulator(); - BiConsumer, CommandBuffer> collector = - (chunk, _) -> collectBodyRows(chunk, snapshots, spaceUuid, query, stats); - store.forEachChunk(BodyComponent.getComponentType(), collector); - int terrainBodies = streaming != null ? streaming.bodyCount(spaceUuid) : 0; - stats.bodies += terrainBodies; - stats.terrainBodies += terrainBodies; - return stats.toView(); - } - - private static void collectBodyRows(@Nonnull ArchetypeChunk chunk, - @Nonnull PhysicsSnapshotResource snapshots, - @Nonnull UUID spaceUuid, - @Nonnull BenchmarkSpaceStatsRequest query, - @Nonnull BenchmarkSpaceStatsAccumulator stats) { - for (int index = 0; index < chunk.size(); index++) { - BodyComponent body = chunk.getComponent(index, BodyComponent.getComponentType()); - if (body == null || !spaceUuid.equals(body.getSpaceUuid())) { - continue; - } - UuidComponent uuid = chunk.getComponent(index, UuidComponent.getComponentType()); - if (uuid == null) { - continue; - } - PhysicsBodySnapshot snapshot = snapshots.getBody(uuid.getUuid()); - if (snapshot == null) { - continue; - } - ShapeComponent shape = chunk.getComponent(index, ShapeComponent.getComponentType()); - boolean chunkCollisionBody = chunk.getComponent(index, - ChunkCollisionSourceComponent.getComponentType()) != null; - classifyBody(stats, shape, snapshot, query, chunkCollisionBody); - } - } - - private static void classifyBody(@Nonnull BenchmarkSpaceStatsAccumulator stats, - @Nullable ShapeComponent shape, - @Nonnull PhysicsBodySnapshot snapshot, - @Nonnull BenchmarkSpaceStatsRequest query, - boolean chunkCollisionBody) { - stats.bodies++; - if (snapshot.bodyType() == PhysicsBodyType.DYNAMIC) { - stats.dynamicBodies++; - Vector3f position = snapshot.position(); - stats.minDynamicBodyY = Math.min(stats.minDynamicBodyY, position.y); - stats.maxDynamicBodyY = Math.max(stats.maxDynamicBodyY, position.y); - if (position.y < query.groundY() - query.belowPlaneTolerance()) { - stats.belowPlaneBodies++; - } - if (query.includeTerrainProbe()) { - stats.missingTerrainBaselineBodies++; - } - if (position.y < query.bodyWorldMinY()) { - stats.belowWorldMinBodies++; - } - if (position.y < query.bodyVoidY()) { - stats.belowVoidBodies++; - } - if (snapshot.sleeping()) { - stats.sleepingDynamicBodies++; - } else { - stats.awakeDynamicBodies++; - } - } - - if (chunkCollisionBody) { - stats.terrainBodies++; - return; - } - if (shape == null || shape.getShapeType() != ShapeType.PLANE) { - stats.detachedBodies++; - } - } - - private static final class BenchmarkSpaceStatsAccumulator { - - private int bodies; - private int dynamicBodies; - private int awakeDynamicBodies; - private int sleepingDynamicBodies; - private int detachedBodies; - private int rawBodies; - private int terrainBodies; - private int belowPlaneBodies; - private int belowTerrainBodies; - private int belowWorldMinBodies; - private int belowVoidBodies; - private int terrainBaselineBodies; - private int missingTerrainBaselineBodies; - private double minTerrainBottomClearance = Double.POSITIVE_INFINITY; - private double minDynamicBodyY = Double.POSITIVE_INFINITY; - private double maxDynamicBodyY = Double.NEGATIVE_INFINITY; - - @Nonnull - private BenchmarkSpaceStatsView toView() { - return new BenchmarkSpaceStatsView(bodies, - dynamicBodies, - awakeDynamicBodies, - sleepingDynamicBodies, - detachedBodies, - rawBodies, - terrainBodies, - belowPlaneBodies, - belowTerrainBodies, - belowWorldMinBodies, - belowVoidBodies, - terrainBaselineBodies, - missingTerrainBaselineBodies, - Double.isFinite(minTerrainBottomClearance) ? (float) minTerrainBottomClearance : Float.NaN, - Double.isFinite(minDynamicBodyY) ? (float) minDynamicBodyY : Float.NaN, - Double.isFinite(maxDynamicBodyY) ? (float) maxDynamicBodyY : Float.NaN); - } - } - - record BenchmarkSpaceStatsRequest(@Nonnull SpaceId spaceId, - float groundY, - float belowPlaneTolerance, - float bodyWorldMinY, - float bodyVoidY, - boolean includeTerrainProbe) { - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java deleted file mode 100644 index f96e4e50..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/crucible/PhysicsStoreCrucibleSupport.java +++ /dev/null @@ -1,69 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; -import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRuntimeCleaner; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsSpaces; -import dev.hytalemodding.impulse.core.plugin.physics.PhysicsShapeSpec; -import dev.hytalemodding.impulse.core.plugin.physics.RigidBodySpawnSettings; -import java.util.UUID; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.joml.Vector3f; - -/** - * Internal Crucible helpers for authoring and clearing live PhysicsStore entities. - */ -final class PhysicsStoreCrucibleSupport { - - private PhysicsStoreCrucibleSupport() { - } - - @Nonnull - static Store physicsStore(@Nonnull World world) { - return PhysicsThreading.store(world); - } - - static void clearAll(@Nonnull Store store) { - PhysicsStoreRuntimeCleaner.clearAll(store); - } - - @Nonnull - static Ref addBody(@Nonnull Store store, - @Nonnull SpaceId spaceId, - @Nonnull UUID bodyUuid, - @Nonnull Vector3f bodyCenter, - @Nonnull PhysicsShapeSpec shape, - @Nonnull PhysicsBodyType bodyType, - float mass, - @Nonnull RigidBodySpawnSettings settings, - @Nullable Vector3f linearVelocity) { - PhysicsThreading.requireWorldThread(store, "add Crucible PhysicsStore body entity"); - Ref spaceRef = requireSpaceRef(store, spaceId); - return store.addEntity(PhysicsBodyEntities.bodyHolder(spaceRef, - bodyUuid, - bodyCenter, - shape, - bodyType, - mass, - settings, - linearVelocity), AddReason.SPAWN); - } - - @Nonnull - private static Ref requireSpaceRef(@Nonnull Store store, - @Nonnull SpaceId spaceId) { - Ref spaceRef = PhysicsSpaces.resolveRef(store, spaceId); - if (spaceRef == null) { - throw new IllegalStateException("No PhysicsStore space ref for id=" + spaceId.value()); - } - return spaceRef; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index cc9db8ca..657d5f6a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -74,8 +74,12 @@ public static boolean removeRuntimeBody(@Nonnull Store store, if (bodyHandle == null) { if (refMatchesUuid(resolvedBodyRef, bodyUuid)) { runtime.removeBodyHandle(resolvedBodyRef); + return false; } - return false; + if (resolvedBodyRef.getStore() == store && resolvedBodyRef.isValid()) { + return false; + } + return runtime.removeBackendBody(bodyUuid, fallbackRuntime); } BackendId backendId = runtime.getBodyBackendId(resolvedBodyRef); if (!bodyBindingMatchesUuid(runtime, bodyUuid, backendId, spaceHandle, bodyHandle)) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java index daf95fd4..55891152 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsRuntimeResource.java @@ -251,6 +251,31 @@ public void removeBodyHandle(@Nonnull Ref bodyRef) { markRegistrationTopologyChanged(); } + public boolean removeBackendBody(@Nonnull UUID bodyUuid, + @Nullable PhysicsBackendRuntime fallbackRuntime) { + BackendBodyKey bodyKey = bodySnapshotKeysByUuid.get( + Objects.requireNonNull(bodyUuid, "bodyUuid")); + if (bodyKey == null) { + return false; + } + BodySnapshotMetadata metadata = bodySnapshotMetadataByKey.get(bodyKey); + if (metadata == null || !bodyUuid.equals(metadata.bodyUuid())) { + return false; + } + PhysicsBackendRuntime runtime = runtimeForBackendId(bodyKey.backendId()); + if (runtime == null) { + runtime = fallbackRuntime; + } + if (runtime != null) { + runtime.removeBody(bodyKey.spaceHandle(), bodyKey.bodyHandle()); + } + removePendingBodyOperations(metadata.bodyRef()); + removeBodyHandleFromSpaceIndex(bodyKey); + removeBodyMetadata(bodyKey); + markRegistrationTopologyChanged(); + return true; + } + @Nonnull public List> bodyRefsForSpaceHandle(@Nonnull BackendId backendId, @Nonnull BackendSpaceHandle spaceHandle) { diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 78aefd91..2a694769 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -2,7 +2,6 @@ requires transitive impulse.api; requires transitive org.joml; requires static jsr305; - requires static crucible; exports dev.hytalemodding.impulse.core.plugin.codec; exports dev.hytalemodding.impulse.core.plugin.components; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java deleted file mode 100644 index 8ce9b56f..00000000 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/crucible/CrucibleBackendsTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.crucible; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import java.util.List; -import org.junit.jupiter.api.Test; - -class CrucibleBackendsTest { - - @Test - void configuredBackendWinsWhenRegistered() { - BackendId jolt = new BackendId("impulse:jolt"); - BackendId rapier = new BackendId("impulse:rapier"); - - assertEquals(jolt, CrucibleBackends.selectBackendId(List.of( - provider(jolt), - provider(rapier)), "impulse:jolt")); - } - - @Test - void rapierIsPreferredWhenMultipleBackendsAreRegistered() { - BackendId jolt = new BackendId("impulse:jolt"); - BackendId rapier = new BackendId("impulse:rapier"); - - assertEquals(rapier, CrucibleBackends.selectBackendId(List.of( - provider(jolt), - provider(rapier)), null)); - } - - @Test - void singleBackendIsSelectedWhenRapierIsUnavailable() { - BackendId jolt = new BackendId("impulse:jolt"); - - assertEquals(jolt, CrucibleBackends.selectBackendId(List.of( - provider(jolt)), null)); - } - - @Test - void multipleNonRapierBackendsRequireExplicitConfiguration() { - assertThrows(IllegalStateException.class, () -> CrucibleBackends.selectBackendId(List.of( - provider(new BackendId("impulse:alpha")), - provider(new BackendId("impulse:beta"))), null)); - } - - @Test - void configuredBackendMustBeRegistered() { - assertThrows(IllegalStateException.class, () -> CrucibleBackends.selectBackendId(List.of( - provider(new BackendId("impulse:rapier"))), "impulse:missing")); - } - - private static FakePhysicsBackendRuntimeProvider provider(BackendId id) { - return new FakePhysicsBackendRuntimeProvider(id, false, false); - } -} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycleTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycleTest.java new file mode 100644 index 00000000..2b9af863 --- /dev/null +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityLifecycleTest.java @@ -0,0 +1,60 @@ +package dev.hytalemodding.impulse.core.internal.modules.physicsentity; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import java.util.ArrayList; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class PhysicsEntityLifecycleTest { + + @AfterEach + void clearLifecycleAndTypes() { + PhysicsEntityLifecycle.disable(); + PhysicsEntityTypeRegistry.clearEntityStoreTypes(); + } + + @Test + void attachmentsAreUnavailableWithoutLifecycleAndRegisteredTypes() { + assertFalse(PhysicsEntityLifecycle.isEnabled()); + assertFalse(PhysicsEntityTypes.areEntityStoreTypesRegistered()); + assertFalse(BodyAttachmentComponent.isComponentTypeRegistered()); + assertFalse(GeneratedVisualProxyComponent.isComponentTypeRegistered()); + assertFalse(PhysicsEntityAttachments.isAvailable()); + } + + @Test + void attachmentsAreAvailableOnlyWhenLifecycleAndTypesAreRegistered() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + try { + PhysicsEntityLifecycle.enable(); + assertFalse(PhysicsEntityAttachments.isAvailable()); + + PhysicsEntityTypeRegistry.registerComponentTypes(proxy); + PhysicsEntityTypeRegistry.registerResourceTypes(proxy); + PhysicsEntityTypeRegistry.registerEventTypes(proxy); + PhysicsEntityTypeRegistry.registerSystemGroups(proxy); + + assertTrue(PhysicsEntityTypes.areEntityStoreTypesRegistered()); + assertTrue(BodyAttachmentComponent.isComponentTypeRegistered()); + assertTrue(GeneratedVisualProxyComponent.isComponentTypeRegistered()); + assertTrue(PhysicsEntityAttachments.isAvailable()); + + PhysicsEntityLifecycle.disable(); + + assertFalse(PhysicsEntityAttachments.isAvailable()); + } finally { + registry.shutdown(); + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java index e7fb5ad7..7b5160ea 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java @@ -10,6 +10,7 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.EmptyResourceStorage; import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.thread.TickingThread; @@ -121,6 +122,48 @@ void tickRemovesMultipleStaleBodiesAndTheirCopiedStateTogether() { } } + @Test + void tickRemovesBackendBodyAfterAuthoritativeBodyEntityIsRemoved() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("stale-body-removal-entity-row")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + UUID spaceUuid = uuid(11); + UUID bodyUuid = uuid(12); + BoundSpace space = addBoundSpace(store, + spaceUuid, + new BackendId("test:stale-body-row-removal")); + Ref bodyRef = addBody(store, spaceUuid, space.ref(), bodyUuid); + bindBody(store, space, bodyUuid, bodyRef, 0.0f); + publishCopiedState(store, spaceUuid, bodyUuid, bodyRef); + + store.removeEntity(bodyRef, store.getRegistry().newHolder(), RemoveReason.REMOVE); + new StaleBodyRemovalSystem().tick(0.0f, 0, store); + + PhysicsRuntimeResource runtime = store.getResource( + PhysicsRuntimeResource.getResourceType()); + PhysicsSnapshotResource snapshots = + store.getResource(PhysicsSnapshotResource.getResourceType()); + assertFalse(store.getResource(PhysicsRestoreStatusResource.getResourceType()) + .isFailed()); + assertNull(store.getExternalData().getRefFromUUID(bodyUuid)); + assertNull(runtime.getBodyHandle(bodyRef)); + assertEquals(0, space.runtime().bodyCount(space.handle().value())); + assertNull(snapshots.getBody(bodyUuid)); + assertNull(PhysicsBodies.spaceId(store, bodyUuid)); + assertFalse(bodyRef.isValid()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Nonnull private static BoundSpace addBoundSpace(@Nonnull Store store, @Nonnull UUID spaceUuid, @@ -232,6 +275,16 @@ private static void publishCopiedState(@Nonnull Store store, snapshot(retainedBodyRef, retainedBodyUuid, spaceUuid)))); } + private static void publishCopiedState(@Nonnull Store store, + @Nonnull UUID spaceUuid, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef) { + store.getResource(PhysicsSnapshotResource.getResourceType()) + .publish(new PhysicsSnapshotFrame(1L, + 0.05f, + List.of(snapshot(bodyRef, bodyUuid, spaceUuid)))); + } + @Nonnull private static PhysicsBodySnapshot snapshot(@Nonnull Ref bodyRef, @Nonnull UUID bodyUuid, diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java index 568953d1..0c83e755 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java @@ -1,18 +1,20 @@ package dev.hytalemodding.impulse.core.plugin.physics; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.EmptyResourceStorage; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.thread.TickingThread; -import com.hypixel.hytale.component.EmptyResourceStorage; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; @@ -21,13 +23,18 @@ import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SolverSettingsComponent; import dev.hytalemodding.impulse.core.plugin.components.SpaceComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; -import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; +import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsChunkCollisionSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.settings.PhysicsCollisionLodSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualSyncSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualMaterializationSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; +import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsBackendExtensionId; +import dev.hytalemodding.impulse.core.plugin.settings.PhysicsExtensionSettings; import dev.hytalemodding.impulse.core.plugin.settings.PhysicsSolverSettings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -39,6 +46,11 @@ class PhysicsSpacesSettingsComponentTest { + private static final PhysicsBackendExtensionId RAPIER_SOLVER_EXTENSION_ID = + new PhysicsBackendExtensionId("impulse:rapier_solver"); + private static final String RAPIER_INTERNAL_PGS_ITERATIONS = "internalPgsIterations"; + private static final String RAPIER_MIN_ISLAND_SIZE = "minIslandSize"; + @Test void defaultSpaceSettingsHolderDoesNotMaterializeDefaultComponents() { ComponentRegistry registry = new ComponentRegistry<>(); @@ -172,6 +184,119 @@ void domainSettingWritesAddAndRemoveOnlyTheirOwnComponents() { } } + @Test + void explicitSpaceLifecycleCleansCompatibilityIndexAndSettings() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physics-space-lifecycle-round-trip")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + int previousCount = PhysicsSpaces.count(store); + SpaceId spaceId = new SpaceId(2002); + + PhysicsSpaces.create(store, + uuid(3), + spaceId, + new BackendId("test:settings-lifecycle")); + PhysicsSpaces.putChunkCollisionSettings(store, + spaceId, + populatedChunkCollisionSettings()); + + PhysicsChunkCollisionSettings settings = + PhysicsSpaces.chunkCollisionSettings(store, spaceId); + assertNotNull(settings); + assertEquals(PhysicsChunkCollisionMode.STREAMING, settings.getMode()); + assertTrue(PhysicsSpaces.hasSpace(store, spaceId)); + assertNotNull(PhysicsSpaces.resolveRef(store, spaceId)); + + PhysicsSpaces.removeEmpty(store, spaceId); + + assertEquals(previousCount, PhysicsSpaces.count(store)); + assertFalse(PhysicsSpaces.hasSpace(store, spaceId)); + assertNull(PhysicsSpaces.resolveRef(store, spaceId)); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + + @Test + void populatedSpaceSettingsRoundTripThroughPublicFacade() { + ComponentRegistry registry = new ComponentRegistry<>(); + ComponentRegistryProxy proxy = + new ComponentRegistryProxy<>(new ArrayList<>(), registry); + PhysicsComponentTypeRegistry.registerComponentTypes(proxy); + PhysicsResourceTypes.registerResourceTypes(proxy); + Store store = registry.addStore( + new PhysicsStore(TestInstanceFactory.world("physics-space-settings-round-trip")), + EmptyResourceStorage.get()); + try { + markCurrentThreadAsWorldThread(store); + SpaceId spaceId = new SpaceId(2003); + Ref spaceRef = PhysicsSpaces.create(store, + uuid(4), + spaceId, + new BackendId("test:settings-round-trip")); + + PhysicsSpaces.putChunkCollisionSettings(store, + spaceId, + populatedChunkCollisionSettings()); + PhysicsSpaces.putVisualSyncSettings(store, spaceId, populatedVisualSyncSettings()); + PhysicsSpaces.putSolverSettings(store, spaceId, populatedSolverSettings()); + PhysicsSpaces.putExtensionSettings(store, spaceId, populatedExtensionSettings()); + PhysicsSpaces.putVisualMaterializationSettings(store, + spaceId, + populatedVisualMaterializationSettings()); + + PhysicsChunkCollisionSettings chunkCollision = + PhysicsSpaces.chunkCollisionSettings(store, spaceRef); + PhysicsVisualSyncSettings visualSync = PhysicsSpaces.visualSyncSettings(store, spaceRef); + PhysicsSolverSettings solver = PhysicsSpaces.solverSettings(store, spaceRef); + PhysicsExtensionSettings extension = PhysicsSpaces.extensionSettings(store, spaceRef); + PhysicsVisualMaterializationSettings visualMaterialization = + PhysicsSpaces.visualMaterializationSettings(store, spaceRef); + assertNotNull(chunkCollision); + assertNotNull(visualSync); + assertNotNull(solver); + assertNotNull(extension); + assertNotNull(visualMaterialization); + assertEquals(PhysicsChunkCollisionMode.STREAMING, chunkCollision.getMode()); + assertEquals(9, chunkCollision.getRadius()); + assertEquals(5, chunkCollision.getBodyRadius()); + assertEquals(77, chunkCollision.getTtlTicks()); + assertEquals(48, visualSync.getVisualFullSyncRadius()); + assertEquals(96, visualSync.getVisualMaxSyncRadius()); + assertFalse(visualSync.isVisualFarSyncCutoffEnabled()); + assertEquals(3, visualSync.getVisualMidSyncIntervalTicks()); + assertEquals(17, visualSync.getVisualFarSyncIntervalTicks()); + assertEquals(VisualOcclusionMode.PRIORITY, visualSync.getVisualOcclusionMode()); + assertEquals(31, visualSync.getVisualOcclusionRaycastsPerTick()); + assertEquals(7, visualSync.getVisualOcclusionCacheTicks()); + assertTrue(visualSync.isEntityVisualSyncCullingEnabled()); + assertTrue(visualSync.isVisualVisibilityCullingEnabled()); + assertEquals(5, solver.getSolverIterations()); + assertEquals(1, solver.getStabilizationIterations()); + assertEquals(2, extension.getInt(RAPIER_SOLVER_EXTENSION_ID, + RAPIER_INTERNAL_PGS_ITERATIONS).orElseThrow()); + assertEquals(64, extension.getInt(RAPIER_SOLVER_EXTENSION_ID, + RAPIER_MIN_ISLAND_SIZE).orElseThrow()); + assertTrue(visualMaterialization.isDetachedVisualMaterializationEnabled()); + assertEquals(48, visualMaterialization.getDetachedVisualMaterializationRadius()); + assertEquals(72, visualMaterialization.getDetachedVisualDematerializationRadius()); + assertEquals(33, visualMaterialization.getDetachedVisualMaxSpawnsPerTick()); + assertEquals(444, visualMaterialization.getDetachedVisualMaxMaterialized()); + assertEquals("Rock_Stone", visualMaterialization.getDetachedVisualBlockType()); + } finally { + registry.removeStore(store); + registry.shutdown(); + } + } + @Nonnull private static UUID uuid(int lowBits) { return new UUID(0L, lowBits); @@ -189,4 +314,59 @@ private static void markCurrentThreadAsWorldThread(@Nonnull Store exception.getTargetException()); } } + + @Nonnull + private static PhysicsChunkCollisionSettings populatedChunkCollisionSettings() { + PhysicsChunkCollisionSettings settings = new PhysicsChunkCollisionSettings(); + settings.setMode(PhysicsChunkCollisionMode.STREAMING); + settings.setRadius(9); + settings.setBodyRadius(5); + settings.setTtlTicks(77); + return settings; + } + + @Nonnull + private static PhysicsVisualSyncSettings populatedVisualSyncSettings() { + PhysicsVisualSyncSettings settings = new PhysicsVisualSyncSettings(); + settings.setVisualMaxSyncRadius(96); + settings.setVisualFullSyncRadius(48); + settings.setVisualFarSyncCutoffEnabled(false); + settings.setVisualMidSyncIntervalTicks(3); + settings.setVisualFarSyncIntervalTicks(17); + settings.setVisualOcclusionMode(VisualOcclusionMode.PRIORITY); + settings.setVisualOcclusionRaycastsPerTick(31); + settings.setVisualOcclusionCacheTicks(7); + settings.setEntityVisualSyncCullingEnabled(true); + settings.setVisualVisibilityCullingEnabled(true); + return settings; + } + + @Nonnull + private static PhysicsSolverSettings populatedSolverSettings() { + PhysicsSolverSettings settings = new PhysicsSolverSettings(); + settings.setSolverIterations(5); + settings.setStabilizationIterations(1); + return settings; + } + + @Nonnull + private static PhysicsExtensionSettings populatedExtensionSettings() { + PhysicsExtensionSettings settings = new PhysicsExtensionSettings(); + settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_INTERNAL_PGS_ITERATIONS, 2); + settings.setInt(RAPIER_SOLVER_EXTENSION_ID, RAPIER_MIN_ISLAND_SIZE, 64); + return settings; + } + + @Nonnull + private static PhysicsVisualMaterializationSettings populatedVisualMaterializationSettings() { + PhysicsVisualMaterializationSettings settings = + new PhysicsVisualMaterializationSettings(); + settings.setDetachedVisualMaterializationEnabled(true); + settings.setDetachedVisualDematerializationRadius(72); + settings.setDetachedVisualMaterializationRadius(48); + settings.setDetachedVisualMaxSpawnsPerTick(33); + settings.setDetachedVisualMaxMaterialized(444); + settings.setDetachedVisualBlockType("Rock_Stone"); + return settings; + } } diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java new file mode 100644 index 00000000..eeb3f6b8 --- /dev/null +++ b/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java @@ -0,0 +1,250 @@ +package dev.hytalemodding.impulse.rapier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.hytalemodding.impulse.api.PhysicsBodyType; +import dev.hytalemodding.impulse.api.ShapeType; +import dev.hytalemodding.impulse.api.SpaceId; +import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; +import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; +import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +class RapierBodyDynamicsTest { + + @Test + void dynamicBodyFallsUnderGravity() { + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(800)); + try { + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + long bodyId = createBox(runtime, + spaceId, + 1.0f, + PhysicsBodyType.DYNAMIC, + 0.0f, + 20.0f, + 0.0f); + + step(runtime, spaceId, 60); + + CapturedSnapshot snapshot = requireSnapshot(runtime, spaceId, bodyId); + assertTrue(snapshot.position.y < 20.0f); + assertTrue(snapshot.linearVelocity.y < 0.0f); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void staticBodyDoesNotMoveUnderGravity() { + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(801)); + try { + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + long bodyId = createBox(runtime, + spaceId, + 0.0f, + PhysicsBodyType.STATIC, + 0.0f, + 10.0f, + 0.0f); + + step(runtime, spaceId, 60); + + CapturedSnapshot snapshot = requireSnapshot(runtime, spaceId, bodyId); + assertEquals(10.0f, snapshot.position.y, 0.01f); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void dynamicBodyLandsOnGroundPlane() { + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(802)); + try { + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + createPlane(runtime, spaceId, 0.0f); + long bodyId = createBox(runtime, + spaceId, + 1.0f, + PhysicsBodyType.DYNAMIC, + 0.0f, + 5.0f, + 0.0f); + + step(runtime, spaceId, 300); + + CapturedSnapshot snapshot = requireSnapshot(runtime, spaceId, bodyId); + assertTrue(snapshot.position.y < 3.0f); + assertTrue(snapshot.position.y > -0.5f); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void dynamicBodySettlesOnGroundPlane() { + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(803)); + try { + runtime.setGravity(spaceId, 0.0f, -9.81f, 0.0f); + createPlane(runtime, spaceId, 0.0f); + long bodyId = createBox(runtime, + spaceId, + 1.0f, + PhysicsBodyType.DYNAMIC, + 0.0f, + 2.0f, + 0.0f); + + step(runtime, spaceId, 300); + + CapturedSnapshot snapshot = requireSnapshot(runtime, spaceId, bodyId); + assertTrue(snapshot.linearVelocity.length() < 0.5f); + } finally { + runtime.destroySpace(spaceId); + } + } + + @Test + void raycastHitsGroundPlane() { + PhysicsBackendRuntime runtime = runtime(); + int spaceId = runtime.createSpace(new SpaceId(804)); + try { + createPlane(runtime, spaceId, 0.0f); + + int hits = runtime.raycastAll(spaceId, + 0.0f, + 10.0f, + 0.0f, + 0.0f, + -10.0f, + 0.0f, + (_, _, _, _, _, _, _, _, _) -> { + }); + + assertTrue(hits > 0); + } finally { + runtime.destroySpace(spaceId); + } + } + + private static PhysicsBackendRuntime runtime() { + RapierBackendRuntimeProvider provider = new RapierBackendRuntimeProvider(); + provider.init(); + return provider.createRuntime(); + } + + private static void step(PhysicsBackendRuntime runtime, int spaceId, int steps) { + for (int i = 0; i < steps; i++) { + runtime.step(spaceId, 1.0f / 60.0f); + } + } + + private static CapturedSnapshot requireSnapshot(PhysicsBackendRuntime runtime, + int spaceId, + long bodyId) { + CapturedSnapshot snapshot = new CapturedSnapshot(); + if (!runtime.bodySnapshot(spaceId, bodyId, snapshot)) { + throw new AssertionError("Missing backend snapshot for body " + bodyId); + } + return snapshot; + } + + private static long createBox(PhysicsBackendRuntime runtime, + int spaceId, + float mass, + PhysicsBodyType bodyType, + float positionX, + float positionY, + float positionZ) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.BOX), + 0.5f, + 0.5f, + 0.5f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + 0.0f, + mass, + BackendRuntimeCodes.bodyTypeCode(bodyType), + positionX, + positionY, + positionZ, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static long createPlane(PhysicsBackendRuntime runtime, int spaceId, float groundY) { + return runtime.createBody(spaceId, + BackendRuntimeCodes.shapeTypeCode(ShapeType.PLANE), + -1.0f, + -1.0f, + -1.0f, + -1.0f, + -1.0f, + BackendRuntimeCodes.AXIS_Y, + groundY, + 0.0f, + BackendRuntimeCodes.bodyTypeCode(PhysicsBodyType.STATIC), + 0.0f, + groundY, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f); + } + + private static final class CapturedSnapshot implements BackendBodySnapshotSink { + + private final Vector3f position = new Vector3f(); + private final Vector3f linearVelocity = new Vector3f(); + + @Override + public void accept(long bodyId, + int shapeTypeCode, + int bodyTypeCode, + float positionX, + float positionY, + float positionZ, + float rotationX, + float rotationY, + float rotationZ, + float rotationW, + float linearVelocityX, + float linearVelocityY, + float linearVelocityZ, + float angularVelocityX, + float angularVelocityY, + float angularVelocityZ, + boolean sleeping, + boolean sensor, + float mass, + float friction, + float restitution, + float linearDamping, + float angularDamping, + int collisionGroup, + int collisionMask, + boolean continuousCollisionEnabled, + float centerOfMassOffsetY, + boolean hasBoxHalfExtents, + float halfExtentX, + float halfExtentY, + float halfExtentZ, + float radius, + float halfHeight, + int axisCode) { + position.set(positionX, positionY, positionZ); + linearVelocity.set(linearVelocityX, linearVelocityY, linearVelocityZ); + } + } +} diff --git a/scripts/ci/install-crucible-runtime.sh b/scripts/ci/install-crucible-runtime.sh deleted file mode 100755 index f49f1d46..00000000 --- a/scripts/ci/install-crucible-runtime.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -CRUCIBLE_VERSION="${CRUCIBLE_VERSION:-1.0.0}" -SERVER_VERSION="${1:-}" - -if [[ -z "${SERVER_VERSION}" ]]; then - SERVER_VERSION="$(awk -F= '$1 == "hytale_version" { print $2 }' "${ROOT_DIR}/gradle.properties")" -fi - -CRUCIBLE_CACHE_DIR="${HOME}/.gradle/caches/modules-2/files-2.1/com.ionforgelabs/crucible/${CRUCIBLE_VERSION}" -SOURCE_JAR="$(find "${CRUCIBLE_CACHE_DIR}" -name "crucible-${CRUCIBLE_VERSION}.jar" -type f | head -n 1)" - -if [[ -z "${SOURCE_JAR}" ]]; then - echo "Could not find Crucible ${CRUCIBLE_VERSION} in Gradle cache." >&2 - echo "Run ./gradlew :impulse-examples:dependencies --configuration compileClasspath first." >&2 - exit 1 -fi - -mkdir -p "${ROOT_DIR}/run/mods" - -python3 - "${SOURCE_JAR}" "${ROOT_DIR}/run/mods/crucible-${CRUCIBLE_VERSION}.jar" "${SERVER_VERSION}" <<'PY' -import json -import sys -import zipfile -from pathlib import Path - -source = Path(sys.argv[1]) -target = Path(sys.argv[2]) -server_version = sys.argv[3] - -with zipfile.ZipFile(source, "r") as src: - manifest = json.loads(src.read("manifest.json")) - manifest["ServerVersion"] = server_version - - with zipfile.ZipFile(target, "w") as dst: - for info in src.infolist(): - if info.filename == "manifest.json": - continue - dst.writestr(info, src.read(info.filename)) - - dst.writestr( - "manifest.json", - json.dumps(manifest, indent=2).encode("utf-8") + b"\n", - ) -PY - -echo "Installed patched Crucible ${CRUCIBLE_VERSION} for Hytale ${SERVER_VERSION}:" -echo "${ROOT_DIR}/run/mods/crucible-${CRUCIBLE_VERSION}.jar" diff --git a/settings.gradle.kts b/settings.gradle.kts index 67d58029..44867747 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,10 +17,6 @@ dependencyResolutionManagement { maven { url = uri("impulse-core/build/generated-sources-m2") } - // Crucible Maven repository. - maven { - url = uri("https://gitlab.com/api/v4/projects/82033924/packages/maven") - } } } From 7e4346c01a5dccd216c182a4c2c0d42f7198b8fb Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 25 Jun 2026 12:45:32 +0200 Subject: [PATCH 529/534] refactor(backends): nest backend modules Signed-off-by: Blovien --- .cargo/config.toml | 2 +- .github/workflows/backend-artifacts.yml | 68 +++++++++---------- README.md | 17 ++--- build.gradle.kts | 14 ++-- .../api}/build.gradle.kts | 4 ++ .../hytalemodding/impulse/api/BackendId.java | 0 .../impulse/api/ImpulseBackendRegistry.java | 0 .../impulse/api/PhysicsAxis.java | 0 .../impulse/api/PhysicsBackendEventKind.java | 0 .../api/PhysicsBodyActivationPhase.java | 0 .../impulse/api/PhysicsBodySnapshot.java | 0 .../impulse/api/PhysicsBodyType.java | 0 .../impulse/api/PhysicsCollisionFilters.java | 0 .../impulse/api/PhysicsContactPhase.java | 0 .../impulse/api/PhysicsRuntimeStats.java | 0 .../impulse/api/PhysicsStepPhaseStats.java | 0 .../hytalemodding/impulse/api/ShapeType.java | 0 .../hytalemodding/impulse/api/SpaceId.java | 0 .../capability/PhysicsActivationTuning.java | 0 .../api/capability/PhysicsCapabilityId.java | 0 .../api/capability/PhysicsSolverTuning.java | 0 .../api/runtime/BackendBodyIdSource.java | 0 .../api/runtime/BackendBodySnapshotSink.java | 0 .../api/runtime/BackendContactSink.java | 0 .../BackendExtensionSettingsSource.java | 0 .../impulse/api/runtime/BackendJointType.java | 0 .../impulse/api/runtime/BackendQuatSink.java | 0 .../api/runtime/BackendRayHitSink.java | 0 .../api/runtime/BackendRuntimeCodes.java | 0 .../api/runtime/BackendRuntimeStatsSink.java | 0 .../runtime/BackendStepPhaseStatsSink.java | 0 .../impulse/api/runtime/BackendVec3Sink.java | 0 .../api/runtime/PhysicsBackendRuntime.java | 0 .../PhysicsBackendRuntimeProvider.java | 0 .../impulse/api/BackendIdTest.java | 0 .../ImpulseBackendRegistryRegistryTest.java | 0 .../impulse/api/PhysicsAxisTest.java | 0 .../impulse/api/PhysicsBodySnapshotTest.java | 0 .../capability/PhysicsCapabilityIdTest.java | 0 .../PhysicsCapabilitySettingsTest.java | 0 .../FakePhysicsBackendRuntimeProvider.java | 0 .../jolt}/README.md | 2 +- .../jolt}/build.gradle.kts | 8 ++- .../jolt}/src/main/cpp/CMakeLists.txt | 0 .../main/cpp/abi/impulse_jolt_body_api.cpp | 0 .../main/cpp/abi/impulse_jolt_joint_api.cpp | 0 .../src/main/cpp/abi/impulse_jolt_native.h | 0 .../main/cpp/abi/impulse_jolt_query_api.cpp | 0 .../main/cpp/abi/impulse_jolt_space_api.cpp | 0 .../main/cpp/internal/impulse_jolt_joints.cpp | 0 .../main/cpp/internal/impulse_jolt_joints.h | 0 .../main/cpp/internal/impulse_jolt_query.cpp | 0 .../main/cpp/internal/impulse_jolt_query.h | 0 .../cpp/internal/impulse_jolt_registry.cpp | 0 .../main/cpp/internal/impulse_jolt_registry.h | 0 .../main/cpp/internal/impulse_jolt_shapes.cpp | 0 .../main/cpp/internal/impulse_jolt_shapes.h | 0 .../cpp/internal/impulse_jolt_snapshot.cpp | 0 .../main/cpp/internal/impulse_jolt_snapshot.h | 0 .../main/cpp/internal/impulse_jolt_space.cpp | 0 .../main/cpp/internal/impulse_jolt_space.h | 0 .../impulse/jolt/JoltBackend.java | 0 .../impulse/jolt/JoltBackendRuntime.java | 0 .../jolt/JoltBackendRuntimeProvider.java | 0 .../impulse/jolt/JoltBodySnapshot.java | 0 .../impulse/jolt/JoltNative.java | 0 .../impulse/jolt/JoltNativeLibrary.java | 0 .../impulse/jolt/PanamaJoltNativeLibrary.java | 0 ....api.runtime.PhysicsBackendRuntimeProvider | 0 .../jolt/JoltBackendRuntimeContractTest.java | 0 .../jolt/JoltBackendRuntimeProviderTest.java | 0 .../impulse/jolt/JoltBodyLifecycleTest.java | 0 .../impulse/jolt/JoltBodySnapshotTest.java | 0 .../impulse/jolt/JoltJointLifecycleTest.java | 0 .../jolt/JoltMaterialAndFilterTest.java | 0 .../jolt/JoltNativeAbiIntegrationTest.java | 0 .../JoltNativePhysicsIntegrationTest.java | 0 .../jolt/JoltNativeQueryIntegrationTest.java | 0 .../impulse/jolt/JoltQueryMappingTest.java | 0 .../impulse/jolt/JoltSpaceLifecycleTest.java | 0 .../impulse/jolt/JoltTestNativeLibrary.java | 0 .../native-loader/build.gradle.kts | 7 ++ .../nativelib/NativeLibraryLoader.java | 0 .../nativelib/NativeLibraryResource.java | 0 .../nativelib/NativeLibraryLoaderTest.java | 0 .../nativelib/NativeLibraryResourceTest.java | 0 .../resources/native/linux/x86_64/libfake.so | 0 .../rapier}/build.gradle.kts | 8 ++- .../impulse/rapier/RapierBackendRuntime.java | 0 .../rapier/RapierBackendRuntimeProvider.java | 0 .../impulse/rapier/RapierNative.java | 0 ....api.runtime.PhysicsBackendRuntimeProvider | 0 .../rapier}/src/main/rust/.gitignore | 0 .../rapier}/src/main/rust/Cargo.lock | 0 .../rapier}/src/main/rust/Cargo.toml | 0 .../rapier3d-0.32.0-simd-body-masks.patch | 0 .../rust/scripts/prepare-rapier-patched.ps1 | 0 .../rust/scripts/prepare-rapier-patched.sh | 0 .../rapier}/src/main/rust/src/body_exports.rs | 0 .../src/main/rust/src/joint_exports.rs | 0 .../rapier}/src/main/rust/src/lib.rs | 0 .../src/main/rust/src/query_exports.rs | 0 .../src/main/rust/src/space_exports.rs | 0 .../src/main/rust/src/voxel_exports.rs | 0 .../RapierBackendRuntimeProviderTest.java | 0 .../rapier/RapierBodyDynamicsTest.java | 0 .../rapier/RapierBoundedContactsTest.java | 0 .../rapier/RapierNativeBodyRemovalTest.java | 0 .../rapier/RapierVoxelTerrainTest.java | 0 impulse-core/build.gradle.kts | 8 ++- impulse-examples/build.gradle.kts | 4 +- impulse-native-loader/build.gradle.kts | 3 - licenses/RAPIER_RUST_BACKEND_LICENSES | 4 +- settings.gradle.kts | 8 +-- 114 files changed, 88 insertions(+), 69 deletions(-) rename {impulse-backend-api => impulse-backends/api}/build.gradle.kts (88%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/BackendId.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java (100%) rename {impulse-backend-api => impulse-backends/api}/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java (100%) rename {impulse-jolt => impulse-backends/jolt}/README.md (98%) rename {impulse-jolt => impulse-backends/jolt}/build.gradle.kts (98%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/CMakeLists.txt (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/abi/impulse_jolt_body_api.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/abi/impulse_jolt_joint_api.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/abi/impulse_jolt_native.h (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/abi/impulse_jolt_query_api.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/abi/impulse_jolt_space_api.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_joints.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_joints.h (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_query.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_query.h (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_registry.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_registry.h (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_shapes.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_shapes.h (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_snapshot.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_snapshot.h (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_space.cpp (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/cpp/internal/impulse_jolt_space.h (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java (100%) rename {impulse-jolt => impulse-backends/jolt}/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java (100%) create mode 100644 impulse-backends/native-loader/build.gradle.kts rename {impulse-native-loader => impulse-backends/native-loader}/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoader.java (100%) rename {impulse-native-loader => impulse-backends/native-loader}/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResource.java (100%) rename {impulse-native-loader => impulse-backends/native-loader}/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoaderTest.java (100%) rename {impulse-native-loader => impulse-backends/native-loader}/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResourceTest.java (100%) rename {impulse-native-loader => impulse-backends/native-loader}/src/test/resources/native/linux/x86_64/libfake.so (100%) rename {impulse-rapier => impulse-backends/rapier}/build.gradle.kts (98%) rename {impulse-rapier => impulse-backends/rapier}/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/.gitignore (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/Cargo.lock (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/Cargo.toml (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/patches/rapier3d-0.32.0-simd-body-masks.patch (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/scripts/prepare-rapier-patched.ps1 (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/scripts/prepare-rapier-patched.sh (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/src/body_exports.rs (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/src/joint_exports.rs (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/src/lib.rs (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/src/query_exports.rs (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/src/space_exports.rs (100%) rename {impulse-rapier => impulse-backends/rapier}/src/main/rust/src/voxel_exports.rs (100%) rename {impulse-rapier => impulse-backends/rapier}/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java (100%) rename {impulse-rapier => impulse-backends/rapier}/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java (100%) rename {impulse-rapier => impulse-backends/rapier}/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java (100%) rename {impulse-rapier => impulse-backends/rapier}/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java (100%) rename {impulse-rapier => impulse-backends/rapier}/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java (100%) delete mode 100644 impulse-native-loader/build.gradle.kts diff --git a/.cargo/config.toml b/.cargo/config.toml index d4c169c4..bf53f2be 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [patch.crates-io] -rapier3d = { path = "impulse-rapier/src/main/rust/target/impulse-patched/rapier3d-0.32.0-impulse" } +rapier3d = { path = "impulse-backends/rapier/src/main/rust/target/impulse-patched/rapier3d-0.32.0-impulse" } diff --git a/.github/workflows/backend-artifacts.yml b/.github/workflows/backend-artifacts.yml index 161884ff..104cce75 100644 --- a/.github/workflows/backend-artifacts.yml +++ b/.github/workflows/backend-artifacts.yml @@ -19,22 +19,22 @@ jobs: runner: ubuntu-22.04 resource-path: linux/x86_64 library: libimpulse_jolt.so - gradle-command: ./gradlew :impulse-jolt:stageJoltNativeResource + gradle-command: ./gradlew :impulse-backends:jolt:stageJoltNativeResource - platform: linux-arm64 runner: ubuntu-24.04-arm resource-path: linux/arm64 library: libimpulse_jolt.so - gradle-command: ./gradlew :impulse-jolt:stageJoltNativeResource + gradle-command: ./gradlew :impulse-backends:jolt:stageJoltNativeResource - platform: osx-arm64 runner: macos-15 resource-path: osx/arm64 library: libimpulse_jolt.dylib - gradle-command: ./gradlew :impulse-jolt:stageJoltNativeResource + gradle-command: ./gradlew :impulse-backends:jolt:stageJoltNativeResource - platform: windows-x86_64 runner: windows-2025 resource-path: windows/x86_64 library: impulse_jolt.dll - gradle-command: ./gradlew.bat :impulse-jolt:stageJoltNativeResource + gradle-command: ./gradlew.bat :impulse-backends:jolt:stageJoltNativeResource steps: - name: Check out repository @@ -58,7 +58,7 @@ jobs: run: | artifact_dir="jolt-native-artifact/native/${{ matrix.resource-path }}" mkdir -p "$artifact_dir" - cp "impulse-jolt/build/generated/jolt-native/native/${{ matrix.resource-path }}/${{ matrix.library }}" "$artifact_dir/" + cp "impulse-backends/jolt/build/generated/jolt-native/native/${{ matrix.resource-path }}/${{ matrix.library }}" "$artifact_dir/" - name: Upload Jolt native resource uses: actions/upload-artifact@v4 @@ -80,25 +80,25 @@ jobs: rust-target: x86_64-unknown-linux-gnu resource-path: linux/x86_64 library: libimpulse_rapier.so - gradle-command: ./gradlew :impulse-rapier:stageRapierNativeResource + gradle-command: ./gradlew :impulse-backends:rapier:stageRapierNativeResource - platform: linux-arm64 runner: ubuntu-24.04-arm rust-target: aarch64-unknown-linux-gnu resource-path: linux/arm64 library: libimpulse_rapier.so - gradle-command: ./gradlew :impulse-rapier:stageRapierNativeResource + gradle-command: ./gradlew :impulse-backends:rapier:stageRapierNativeResource - platform: osx-arm64 runner: macos-15 rust-target: aarch64-apple-darwin resource-path: osx/arm64 library: libimpulse_rapier.dylib - gradle-command: ./gradlew :impulse-rapier:stageRapierNativeResource + gradle-command: ./gradlew :impulse-backends:rapier:stageRapierNativeResource - platform: windows-x86_64 runner: windows-2025 rust-target: x86_64-pc-windows-msvc resource-path: windows/x86_64 library: impulse_rapier.dll - gradle-command: ./gradlew.bat :impulse-rapier:stageRapierNativeResource + gradle-command: ./gradlew.bat :impulse-backends:rapier:stageRapierNativeResource steps: - name: Check out repository @@ -129,7 +129,7 @@ jobs: run: | artifact_dir="rapier-native-artifact/native/${{ matrix.resource-path }}" mkdir -p "$artifact_dir" - cp "impulse-rapier/build/generated/rapier-native/native/${{ matrix.resource-path }}/${{ matrix.library }}" "$artifact_dir/" + cp "impulse-backends/rapier/build/generated/rapier-native/native/${{ matrix.resource-path }}/${{ matrix.library }}" "$artifact_dir/" - name: Upload Rapier native resource uses: actions/upload-artifact@v4 @@ -162,47 +162,47 @@ jobs: uses: actions/download-artifact@v4 with: pattern: rapier-native-* - path: impulse-rapier/build/ci/rapier-native-downloads + path: impulse-backends/rapier/build/ci/rapier-native-downloads - name: Download Jolt native resources uses: actions/download-artifact@v4 with: pattern: jolt-native-* - path: impulse-jolt/build/ci/jolt-native-downloads + path: impulse-backends/jolt/build/ci/jolt-native-downloads - name: Normalize Rapier native resource tree shell: bash run: | - mkdir -p impulse-rapier/build/ci/rapier-native/native - for dir in impulse-rapier/build/ci/rapier-native-downloads/rapier-native-*; do + mkdir -p impulse-backends/rapier/build/ci/rapier-native/native + for dir in impulse-backends/rapier/build/ci/rapier-native-downloads/rapier-native-*; do if [ -d "$dir/native" ]; then - cp -R "$dir/native/." impulse-rapier/build/ci/rapier-native/native/ + cp -R "$dir/native/." impulse-backends/rapier/build/ci/rapier-native/native/ else - cp -R "$dir/." impulse-rapier/build/ci/rapier-native/native/ + cp -R "$dir/." impulse-backends/rapier/build/ci/rapier-native/native/ fi done - find impulse-rapier/build/ci/rapier-native/native -type f | sort + find impulse-backends/rapier/build/ci/rapier-native/native -type f | sort - name: Normalize Jolt native resource tree shell: bash run: | - mkdir -p impulse-jolt/build/ci/jolt-native/native - for dir in impulse-jolt/build/ci/jolt-native-downloads/jolt-native-*; do + mkdir -p impulse-backends/jolt/build/ci/jolt-native/native + for dir in impulse-backends/jolt/build/ci/jolt-native-downloads/jolt-native-*; do if [ -d "$dir/native" ]; then - cp -R "$dir/native/." impulse-jolt/build/ci/jolt-native/native/ + cp -R "$dir/native/." impulse-backends/jolt/build/ci/jolt-native/native/ else - cp -R "$dir/." impulse-jolt/build/ci/jolt-native/native/ + cp -R "$dir/." impulse-backends/jolt/build/ci/jolt-native/native/ fi done - find impulse-jolt/build/ci/jolt-native/native -type f | sort + find impulse-backends/jolt/build/ci/jolt-native/native -type f | sort - name: Build backend provider jars run: > ./gradlew packageBackendPlatformJars -PbuildJoltNative=false - -Pimpulse.joltNativeResourceRoot=${{ github.workspace }}/impulse-jolt/build/ci/jolt-native + -Pimpulse.joltNativeResourceRoot=${{ github.workspace }}/impulse-backends/jolt/build/ci/jolt-native -PbuildRapierNative=false - -Pimpulse.rapierNativeResourceRoot=${{ github.workspace }}/impulse-rapier/build/ci/rapier-native + -Pimpulse.rapierNativeResourceRoot=${{ github.workspace }}/impulse-backends/rapier/build/ci/rapier-native - name: Write artifact notice shell: bash @@ -228,14 +228,14 @@ jobs: LICENSE licenses/JOLT_PHYSICS_LICENSE licenses/RAPIER_RUST_BACKEND_LICENSES - impulse-jolt/build/libs/impulse-jolt-*-linux-x86_64.jar - impulse-jolt/build/libs/impulse-jolt-*-linux-arm64.jar - impulse-jolt/build/libs/impulse-jolt-*-osx-arm64.jar - impulse-jolt/build/libs/impulse-jolt-*-windows-x86_64.jar - impulse-jolt/build/libs/impulse-jolt-*-universal.jar - impulse-rapier/build/libs/impulse-rapier-*-linux-x86_64.jar - impulse-rapier/build/libs/impulse-rapier-*-linux-arm64.jar - impulse-rapier/build/libs/impulse-rapier-*-osx-arm64.jar - impulse-rapier/build/libs/impulse-rapier-*-windows-x86_64.jar - impulse-rapier/build/libs/impulse-rapier-*-universal.jar + impulse-backends/jolt/build/libs/impulse-backend-jolt-*-linux-x86_64.jar + impulse-backends/jolt/build/libs/impulse-backend-jolt-*-linux-arm64.jar + impulse-backends/jolt/build/libs/impulse-backend-jolt-*-osx-arm64.jar + impulse-backends/jolt/build/libs/impulse-backend-jolt-*-windows-x86_64.jar + impulse-backends/jolt/build/libs/impulse-backend-jolt-*-universal.jar + impulse-backends/rapier/build/libs/impulse-backend-rapier-*-linux-x86_64.jar + impulse-backends/rapier/build/libs/impulse-backend-rapier-*-linux-arm64.jar + impulse-backends/rapier/build/libs/impulse-backend-rapier-*-osx-arm64.jar + impulse-backends/rapier/build/libs/impulse-backend-rapier-*-windows-x86_64.jar + impulse-backends/rapier/build/libs/impulse-backend-rapier-*-universal.jar if-no-files-found: error diff --git a/README.md b/README.md index 06c4e3f1..117fc0ab 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,13 @@ Impulse is a physics framework for Hytale that connects Hytale ECS worlds to plu Impulse codebase is divided as follows: - **impulse-core** - Hytale ECS integration and backend communication. -- **impulse-api** - backend-agnostic API layer and contracts. -- **impulse-native-loader** - native library loader for backend provider jars. +- **impulse-backends/api** - backend-agnostic API layer and contracts. +- **impulse-backends/native-loader** - native library loader for backend provider jars. - **impulse-examples** - example plugins to understand the framework usage. Official physics backend implementations: -- **impulse-rapier** - Rapier backend with a small Rust/JNI native shim. +- **impulse-backends/rapier** - Rapier backend with a small Rust/JNI native shim. +- **impulse-backends/jolt** - Jolt backend with a C++/Panama native shim. ### Architecture @@ -65,7 +66,7 @@ flowchart TB Dispatch["serialized backend calls"] end - subgraph API["impulse-api"] + subgraph API["impulse-backends/api"] direction TB Runtime["PhysicsBackendRuntime"] @@ -76,7 +77,7 @@ flowchart TB Java["Java engines"] Native["native engines"] - Bridge["impulse-native-bridge\nFFM (WIP) or JNI"] + Bridge["native loader\nFFM or JNI"] Active["active backend instances\nper physics space"] Native --> Bridge @@ -174,10 +175,10 @@ When multiple backend jars are installed, create spaces with an explicit backend /impulse space create --backend=impulse:rapier ``` -The Rapier backend needs a Rust toolchain to build its native library. If `cargo` is available, `:impulse-rapier:processResources` builds and packages the current build platform native library automatically. You can also force native compilation with: +The Rapier backend needs a Rust toolchain to build its native library. If `cargo` is available, `:impulse-backends:rapier:processResources` builds and packages the current build platform native library automatically. You can also force native compilation with: ```bash -./gradlew :impulse-rapier:build -PbuildRapierNative=true +./gradlew :impulse-backends:rapier:build -PbuildRapierNative=true ``` It also supports SIMD optimizations that can be enabled using: @@ -199,7 +200,7 @@ Hytale runtime when the bug depends on plugin loading, live worlds, or command b ```bash ./gradlew :impulse-core:test -./gradlew :impulse-rapier:test +./gradlew :impulse-backends:rapier:test ./gradlew runAllMods ``` diff --git a/build.gradle.kts b/build.gradle.kts index d700c58e..42e42ce5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,7 +67,7 @@ subprojects { } } -val backendProjectPaths = setOf(":impulse-jolt", ":impulse-rapier") +val backendProjectPaths = setOf(":impulse-backends:jolt", ":impulse-backends:rapier") val stagedBackendJarDirectory = layout.projectDirectory.dir("run/mods/impulse-backends") val stagedEarlyPluginJarDirectory = layout.projectDirectory.dir("run/earlyplugins") val physicsStoreEarlyPluginEnabled = providers.gradleProperty("impulse.physicsStoreEarlyPlugin") @@ -137,8 +137,8 @@ tasks.register("packageBackendPlatformJars") { group = "build" description = "Packages all per-platform and universal backend provider jars" dependsOn( - ":impulse-jolt:packageJoltBackendPlatformJars", - ":impulse-rapier:packageRapierBackendPlatformJars" + ":impulse-backends:jolt:packageJoltBackendPlatformJars", + ":impulse-backends:rapier:packageRapierBackendPlatformJars" ) } @@ -146,10 +146,10 @@ tasks.register("headlessTest") { group = "verification" description = "Runs automated headless/serverless tests without booting the Hytale server" dependsOn( - ":impulse-backend-api:test", - ":impulse-native-loader:test", - ":impulse-jolt:test", - ":impulse-rapier:test", + ":impulse-backends:api:test", + ":impulse-backends:native-loader:test", + ":impulse-backends:jolt:test", + ":impulse-backends:rapier:test", ":impulse-core:test", ":impulse-examples:test", ":impulse-early-plugin:test" diff --git a/impulse-backend-api/build.gradle.kts b/impulse-backends/api/build.gradle.kts similarity index 88% rename from impulse-backend-api/build.gradle.kts rename to impulse-backends/api/build.gradle.kts index 14b63948..7a8b5243 100644 --- a/impulse-backend-api/build.gradle.kts +++ b/impulse-backends/api/build.gradle.kts @@ -3,6 +3,10 @@ plugins { id("java-test-fixtures") } +base { + archivesName.set("impulse-backend-api") +} + dependencies { api(libs.jsr305) api(libs.joml) diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/BackendId.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistry.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsAxis.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBackendEventKind.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyActivationPhase.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshot.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsBodyType.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsCollisionFilters.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsContactPhase.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsRuntimeStats.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/PhysicsStepPhaseStats.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/ShapeType.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/SpaceId.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsActivationTuning.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityId.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/capability/PhysicsSolverTuning.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodyIdSource.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendBodySnapshotSink.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendContactSink.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendExtensionSettingsSource.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendJointType.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendQuatSink.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRayHitSink.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeCodes.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendRuntimeStatsSink.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendStepPhaseStatsSink.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/BackendVec3Sink.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntime.java diff --git a/impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java similarity index 100% rename from impulse-backend-api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java rename to impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java b/impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java similarity index 100% rename from impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java rename to impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/BackendIdTest.java diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java b/impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java similarity index 100% rename from impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java rename to impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/ImpulseBackendRegistryRegistryTest.java diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java b/impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java similarity index 100% rename from impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java rename to impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/PhysicsAxisTest.java diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java b/impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java similarity index 100% rename from impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java rename to impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/PhysicsBodySnapshotTest.java diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java b/impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java similarity index 100% rename from impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java rename to impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilityIdTest.java diff --git a/impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java b/impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java similarity index 100% rename from impulse-backend-api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java rename to impulse-backends/api/src/test/java/dev/hytalemodding/impulse/api/capability/PhysicsCapabilitySettingsTest.java diff --git a/impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java b/impulse-backends/api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java similarity index 100% rename from impulse-backend-api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java rename to impulse-backends/api/src/testFixtures/java/dev/hytalemodding/impulse/api/testsupport/FakePhysicsBackendRuntimeProvider.java diff --git a/impulse-jolt/README.md b/impulse-backends/jolt/README.md similarity index 98% rename from impulse-jolt/README.md rename to impulse-backends/jolt/README.md index b4d0458f..e24952a0 100644 --- a/impulse-jolt/README.md +++ b/impulse-backends/jolt/README.md @@ -1,6 +1,6 @@ # Impulse Jolt Backend -`impulse-jolt` is the Jolt backend provider module for Impulse. +`impulse-backends/jolt` is the Jolt backend provider module for Impulse. The backend id is `impulse:jolt`. The module is discovered through the same `PhysicsBackendRuntimeProvider` service mechanism used by the other backend provider jars. diff --git a/impulse-jolt/build.gradle.kts b/impulse-backends/jolt/build.gradle.kts similarity index 98% rename from impulse-jolt/build.gradle.kts rename to impulse-backends/jolt/build.gradle.kts index 37821c0b..f2fb9749 100644 --- a/impulse-jolt/build.gradle.kts +++ b/impulse-backends/jolt/build.gradle.kts @@ -8,6 +8,10 @@ plugins { id("java-library") } +base { + archivesName.set("impulse-backend-jolt") +} + data class JoltBackendPlatform( val taskSuffix: String, val archiveClassifier: String, @@ -288,7 +292,7 @@ tasks.register("packageJoltBackendPlatformJars") { } dependencies { - api(project(":impulse-backend-api")) + api(project(":impulse-backends:api")) - implementation(project(":impulse-native-loader")) + implementation(project(":impulse-backends:native-loader")) } diff --git a/impulse-jolt/src/main/cpp/CMakeLists.txt b/impulse-backends/jolt/src/main/cpp/CMakeLists.txt similarity index 100% rename from impulse-jolt/src/main/cpp/CMakeLists.txt rename to impulse-backends/jolt/src/main/cpp/CMakeLists.txt diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp b/impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp rename to impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_body_api.cpp diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp b/impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp rename to impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_joint_api.cpp diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_native.h b/impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_native.h similarity index 100% rename from impulse-jolt/src/main/cpp/abi/impulse_jolt_native.h rename to impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_native.h diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp b/impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp rename to impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_query_api.cpp diff --git a/impulse-jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp b/impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp rename to impulse-backends/jolt/src/main/cpp/abi/impulse_jolt_space_api.cpp diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.cpp b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_joints.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.cpp rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_joints.cpp diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.h b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_joints.h similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_joints.h rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_joints.h diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.cpp b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_query.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_query.cpp rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_query.cpp diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_query.h b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_query.h similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_query.h rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_query.h diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.cpp b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_registry.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.cpp rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_registry.cpp diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.h b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_registry.h similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_registry.h rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_registry.h diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_shapes.cpp diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.h b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_shapes.h similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_shapes.h rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_shapes.h diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_snapshot.cpp diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.h b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_snapshot.h similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_snapshot.h rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_snapshot.h diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.cpp b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_space.cpp similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_space.cpp rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_space.cpp diff --git a/impulse-jolt/src/main/cpp/internal/impulse_jolt_space.h b/impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_space.h similarity index 100% rename from impulse-jolt/src/main/cpp/internal/impulse_jolt_space.h rename to impulse-backends/jolt/src/main/cpp/internal/impulse_jolt_space.h diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java b/impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java similarity index 100% rename from impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java rename to impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackend.java diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java b/impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java similarity index 100% rename from impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java rename to impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntime.java diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java b/impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java similarity index 100% rename from impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java rename to impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProvider.java diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java b/impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java similarity index 100% rename from impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java rename to impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshot.java diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java b/impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java similarity index 100% rename from impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java rename to impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNative.java diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java b/impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java similarity index 100% rename from impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java rename to impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/JoltNativeLibrary.java diff --git a/impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java b/impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java similarity index 100% rename from impulse-jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java rename to impulse-backends/jolt/src/main/java/dev/hytalemodding/impulse/jolt/PanamaJoltNativeLibrary.java diff --git a/impulse-jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider b/impulse-backends/jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider similarity index 100% rename from impulse-jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider rename to impulse-backends/jolt/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeContractTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBackendRuntimeProviderTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodyLifecycleTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltBodySnapshotTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltJointLifecycleTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltMaterialAndFilterTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeAbiIntegrationTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativePhysicsIntegrationTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltNativeQueryIntegrationTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltQueryMappingTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltSpaceLifecycleTest.java diff --git a/impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java b/impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java similarity index 100% rename from impulse-jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java rename to impulse-backends/jolt/src/test/java/dev/hytalemodding/impulse/jolt/JoltTestNativeLibrary.java diff --git a/impulse-backends/native-loader/build.gradle.kts b/impulse-backends/native-loader/build.gradle.kts new file mode 100644 index 00000000..d302fbbc --- /dev/null +++ b/impulse-backends/native-loader/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("java-library") +} + +base { + archivesName.set("impulse-native-loader") +} diff --git a/impulse-native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoader.java b/impulse-backends/native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoader.java similarity index 100% rename from impulse-native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoader.java rename to impulse-backends/native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoader.java diff --git a/impulse-native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResource.java b/impulse-backends/native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResource.java similarity index 100% rename from impulse-native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResource.java rename to impulse-backends/native-loader/src/main/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResource.java diff --git a/impulse-native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoaderTest.java b/impulse-backends/native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoaderTest.java similarity index 100% rename from impulse-native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoaderTest.java rename to impulse-backends/native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryLoaderTest.java diff --git a/impulse-native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResourceTest.java b/impulse-backends/native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResourceTest.java similarity index 100% rename from impulse-native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResourceTest.java rename to impulse-backends/native-loader/src/test/java/dev/hytalemodding/impulse/internal/nativelib/NativeLibraryResourceTest.java diff --git a/impulse-native-loader/src/test/resources/native/linux/x86_64/libfake.so b/impulse-backends/native-loader/src/test/resources/native/linux/x86_64/libfake.so similarity index 100% rename from impulse-native-loader/src/test/resources/native/linux/x86_64/libfake.so rename to impulse-backends/native-loader/src/test/resources/native/linux/x86_64/libfake.so diff --git a/impulse-rapier/build.gradle.kts b/impulse-backends/rapier/build.gradle.kts similarity index 98% rename from impulse-rapier/build.gradle.kts rename to impulse-backends/rapier/build.gradle.kts index b8af2a16..a76d33b1 100644 --- a/impulse-rapier/build.gradle.kts +++ b/impulse-backends/rapier/build.gradle.kts @@ -7,6 +7,10 @@ plugins { id("java-library") } +base { + archivesName.set("impulse-backend-rapier") +} + data class RapierBackendPlatform( val taskSuffix: String, val archiveClassifier: String, @@ -246,9 +250,9 @@ tasks.processResources { } dependencies { - api(project(":impulse-backend-api")) + api(project(":impulse-backends:api")) - implementation(project(":impulse-native-loader")) + implementation(project(":impulse-backends:native-loader")) } diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java b/impulse-backends/rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java similarity index 100% rename from impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java rename to impulse-backends/rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntime.java diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java b/impulse-backends/rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java similarity index 100% rename from impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java rename to impulse-backends/rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProvider.java diff --git a/impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java b/impulse-backends/rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java similarity index 100% rename from impulse-rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java rename to impulse-backends/rapier/src/main/java/dev/hytalemodding/impulse/rapier/RapierNative.java diff --git a/impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider b/impulse-backends/rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider similarity index 100% rename from impulse-rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider rename to impulse-backends/rapier/src/main/resources/META-INF/services/dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider diff --git a/impulse-rapier/src/main/rust/.gitignore b/impulse-backends/rapier/src/main/rust/.gitignore similarity index 100% rename from impulse-rapier/src/main/rust/.gitignore rename to impulse-backends/rapier/src/main/rust/.gitignore diff --git a/impulse-rapier/src/main/rust/Cargo.lock b/impulse-backends/rapier/src/main/rust/Cargo.lock similarity index 100% rename from impulse-rapier/src/main/rust/Cargo.lock rename to impulse-backends/rapier/src/main/rust/Cargo.lock diff --git a/impulse-rapier/src/main/rust/Cargo.toml b/impulse-backends/rapier/src/main/rust/Cargo.toml similarity index 100% rename from impulse-rapier/src/main/rust/Cargo.toml rename to impulse-backends/rapier/src/main/rust/Cargo.toml diff --git a/impulse-rapier/src/main/rust/patches/rapier3d-0.32.0-simd-body-masks.patch b/impulse-backends/rapier/src/main/rust/patches/rapier3d-0.32.0-simd-body-masks.patch similarity index 100% rename from impulse-rapier/src/main/rust/patches/rapier3d-0.32.0-simd-body-masks.patch rename to impulse-backends/rapier/src/main/rust/patches/rapier3d-0.32.0-simd-body-masks.patch diff --git a/impulse-rapier/src/main/rust/scripts/prepare-rapier-patched.ps1 b/impulse-backends/rapier/src/main/rust/scripts/prepare-rapier-patched.ps1 similarity index 100% rename from impulse-rapier/src/main/rust/scripts/prepare-rapier-patched.ps1 rename to impulse-backends/rapier/src/main/rust/scripts/prepare-rapier-patched.ps1 diff --git a/impulse-rapier/src/main/rust/scripts/prepare-rapier-patched.sh b/impulse-backends/rapier/src/main/rust/scripts/prepare-rapier-patched.sh similarity index 100% rename from impulse-rapier/src/main/rust/scripts/prepare-rapier-patched.sh rename to impulse-backends/rapier/src/main/rust/scripts/prepare-rapier-patched.sh diff --git a/impulse-rapier/src/main/rust/src/body_exports.rs b/impulse-backends/rapier/src/main/rust/src/body_exports.rs similarity index 100% rename from impulse-rapier/src/main/rust/src/body_exports.rs rename to impulse-backends/rapier/src/main/rust/src/body_exports.rs diff --git a/impulse-rapier/src/main/rust/src/joint_exports.rs b/impulse-backends/rapier/src/main/rust/src/joint_exports.rs similarity index 100% rename from impulse-rapier/src/main/rust/src/joint_exports.rs rename to impulse-backends/rapier/src/main/rust/src/joint_exports.rs diff --git a/impulse-rapier/src/main/rust/src/lib.rs b/impulse-backends/rapier/src/main/rust/src/lib.rs similarity index 100% rename from impulse-rapier/src/main/rust/src/lib.rs rename to impulse-backends/rapier/src/main/rust/src/lib.rs diff --git a/impulse-rapier/src/main/rust/src/query_exports.rs b/impulse-backends/rapier/src/main/rust/src/query_exports.rs similarity index 100% rename from impulse-rapier/src/main/rust/src/query_exports.rs rename to impulse-backends/rapier/src/main/rust/src/query_exports.rs diff --git a/impulse-rapier/src/main/rust/src/space_exports.rs b/impulse-backends/rapier/src/main/rust/src/space_exports.rs similarity index 100% rename from impulse-rapier/src/main/rust/src/space_exports.rs rename to impulse-backends/rapier/src/main/rust/src/space_exports.rs diff --git a/impulse-rapier/src/main/rust/src/voxel_exports.rs b/impulse-backends/rapier/src/main/rust/src/voxel_exports.rs similarity index 100% rename from impulse-rapier/src/main/rust/src/voxel_exports.rs rename to impulse-backends/rapier/src/main/rust/src/voxel_exports.rs diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java b/impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java similarity index 100% rename from impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java rename to impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBackendRuntimeProviderTest.java diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java b/impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java similarity index 100% rename from impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java rename to impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBodyDynamicsTest.java diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java b/impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java similarity index 100% rename from impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java rename to impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierBoundedContactsTest.java diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java b/impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java similarity index 100% rename from impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java rename to impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierNativeBodyRemovalTest.java diff --git a/impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java b/impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java similarity index 100% rename from impulse-rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java rename to impulse-backends/rapier/src/test/java/dev/hytalemodding/impulse/rapier/RapierVoxelTerrainTest.java diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index c127bfa2..d25707d7 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -4,6 +4,8 @@ plugins { version = rootProject.version +evaluationDependsOn(":impulse-backends:api") + val coreModuleName = "dev.hytalemodding.impulse.core" // These parent dependencies are shared by the core plugin and inherited by bundled subplugins. val impulseManifestDependencies = listOf( @@ -19,7 +21,7 @@ val moduleInfoModulePath by configurations.creating { } dependencies { - implementation(project(":impulse-backend-api")) + implementation(project(":impulse-backends:api")) compileOnly(project(":impulse-early-plugin")) compileOnly(libs.lombok) @@ -28,7 +30,7 @@ dependencies { annotationProcessor(libs.lombok) - testImplementation(testFixtures(project(":impulse-backend-api"))) + testImplementation(testFixtures(project(":impulse-backends:api"))) testImplementation(libs.objenesis) testCompileOnly(project(":impulse-early-plugin")) testRuntimeOnly(project(":impulse-early-plugin")) @@ -37,7 +39,7 @@ dependencies { } -val impulseApiJar = project(":impulse-backend-api").tasks.named("jar") +val impulseApiJar = project(":impulse-backends:api").tasks.named("jar") tasks.named("compileJava") { doFirst { diff --git a/impulse-examples/build.gradle.kts b/impulse-examples/build.gradle.kts index 63c0b416..62190852 100644 --- a/impulse-examples/build.gradle.kts +++ b/impulse-examples/build.gradle.kts @@ -7,12 +7,12 @@ plugins { version = rootProject.version dependencies { - implementation(project(":impulse-backend-api")) + implementation(project(":impulse-backends:api")) compileOnly(project(":impulse-core")) compileOnly(project(":impulse-early-plugin")) testImplementation(project(":impulse-core")) testImplementation(project(":impulse-early-plugin")) - testImplementation(testFixtures(project(":impulse-backend-api"))) + testImplementation(testFixtures(project(":impulse-backends:api"))) testImplementation(libs.objenesis) testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") diff --git a/impulse-native-loader/build.gradle.kts b/impulse-native-loader/build.gradle.kts deleted file mode 100644 index b600092f..00000000 --- a/impulse-native-loader/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id("java-library") -} diff --git a/licenses/RAPIER_RUST_BACKEND_LICENSES b/licenses/RAPIER_RUST_BACKEND_LICENSES index 3e954033..b7a3aaad 100644 --- a/licenses/RAPIER_RUST_BACKEND_LICENSES +++ b/licenses/RAPIER_RUST_BACKEND_LICENSES @@ -1,4 +1,4 @@ -The experimental impulse-rapier backend links a first-party native shim against Rust crates. +The experimental impulse-backends/rapier backend links a first-party native shim against Rust crates. Direct Rust dependencies: @@ -10,4 +10,4 @@ Direct Rust dependencies: Repository: https://github.com/jni-rs/jni-rs License files: https://github.com/jni-rs/jni-rs/tree/master/licenses -Run `cargo metadata` in `impulse-rapier/src/main/rust` when producing a release artifact to audit transitive crate licenses. +Run `cargo metadata` in `impulse-backends/rapier/src/main/rust` when producing a release artifact to audit transitive crate licenses. diff --git a/settings.gradle.kts b/settings.gradle.kts index 44867747..c8eac1d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,10 +22,10 @@ dependencyResolutionManagement { rootProject.name = "impulse" -include("impulse-backend-api") -include("impulse-native-loader") -include("impulse-jolt") -include("impulse-rapier") +include(":impulse-backends:api") +include(":impulse-backends:native-loader") +include(":impulse-backends:jolt") +include(":impulse-backends:rapier") include("impulse-core") include("impulse-examples") include("impulse-early-plugin") From fea1ade090b202fe001b1df257e17a0aad461ae8 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 25 Jun 2026 12:47:55 +0200 Subject: [PATCH 530/534] refactor(core): relocate physics internals Signed-off-by: Blovien --- .../impulse/core/ImpulsePlugin.java | 4 +- .../PhysicsComponentTypeRegistry.java | 2 +- .../PhysicsStoreRegistration.java | 2 +- .../internal/commands/debug/DebugCommand.java | 3 +- .../commands/debug/DebugFlagCommand.java | 2 +- .../impulse/core/internal/math/UuidMath.java | 29 --- .../physicschunk/PhysicsChunkSubPlugin.java | 2 +- .../commands/PhysicsChunkCommandSet.java | 2 +- ...nkCollisionRestoreDependencyComponent.java | 2 +- .../ChunkCollisionSourceComponent.java | 23 +-- .../resources}/PhysicsBodyRuntimeState.java | 2 +- .../PhysicsBodySyncStateResource.java | 2 +- .../systems/sync/PhysicsSyncPolicy.java | 2 +- .../systems/sync/PhysicsSyncSystem.java | 4 +- .../resources/PhysicsDebugResource.java | 3 +- .../resources/PhysicsResourceTypes.java | 1 - .../resources/body/PhysicsBodySnapshots.java | 182 ------------------ .../systems/debug/PhysicsDebugSystem.java | 2 +- .../components/PhysicsComponentTypes.java | 2 +- .../CleanCommandLifecycleGuardTest.java | 2 +- .../space/SpaceCommandDeleteTest.java | 2 +- .../PhysicsChunkStoreTypesTest.java | 4 +- ...ChunkCollisionComponentSyncSystemTest.java | 2 +- ...ChunkCollisionMutationDrainSystemTest.java | 2 +- ...hunkCollisionVoxelStitchingSystemTest.java | 2 +- .../systems/sync/PhysicsSyncPolicyTest.java | 2 +- .../systems/sync/PhysicsSyncSystemTest.java | 2 +- .../physics/PhysicsStoreRowCleanupTest.java | 2 +- .../PhysicsStoreTopologyMutationsTest.java | 2 +- .../PhysicsStoreHolderPersistenceTest.java | 4 +- .../resources/PhysicsDebugResourceTest.java | 1 + .../PhysicsTypeRegistrationApiTest.java | 8 +- .../systems/BodyBindingSystemTest.java | 2 +- .../BodyCommandApplicationSystemTest.java | 2 +- .../systems/JointBindingSystemTest.java | 2 +- .../systems/StaleBodyRemovalSystemTest.java | 2 +- .../binding/SpaceBindingSystemTest.java | 2 +- .../debug/PhysicsStoreDebugQueriesTest.java | 2 +- .../plugin/physics/PhysicsBodiesTest.java | 2 +- .../physics/PhysicsBodyEntitiesTest.java | 2 +- .../PhysicsSpacesSettingsComponentTest.java | 2 +- .../plugin/physics/RaycastHitViewTest.java | 2 +- .../DirectionalPendulumsCommandTest.java | 2 +- 43 files changed, 49 insertions(+), 279 deletions(-) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{registration => }/PhysicsComponentTypeRegistry.java (99%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{registration => }/PhysicsStoreRegistration.java (99%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/UuidMath.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{resources/body => modules/physicsentity/resources}/PhysicsBodyRuntimeState.java (98%) rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/{physics => }/resources/PhysicsDebugResource.java (90%) delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java index bcdabbbc..f3b0f2c3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/ImpulsePlugin.java @@ -13,8 +13,8 @@ import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandTreeRegistry; import dev.hytalemodding.impulse.core.internal.modules.ImpulseSubPluginRegistration; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.PhysicsStoreEarlyPluginProbe; import java.nio.file.Path; import java.util.ArrayList; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsComponentTypeRegistry.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsComponentTypeRegistry.java index 0dc7b401..430910d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsComponentTypeRegistry.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsComponentTypeRegistry.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.registration; +package dev.hytalemodding.impulse.core.internal; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ComponentType; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsStoreRegistration.java similarity index 99% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsStoreRegistration.java index 9122df31..fe71637c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsStoreRegistration.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/PhysicsStoreRegistration.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.registration; +package dev.hytalemodding.impulse.core.internal; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.Resource; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java index 5e2ebfd4..3b42f62b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugCommand.java @@ -2,9 +2,8 @@ import com.hypixel.hytale.server.core.command.system.AbstractCommand; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; -import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import java.util.Collection; -import java.util.List; import javax.annotation.Nonnull; public class DebugCommand extends AbstractCommandCollection { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java index ab092da7..f2861507 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/debug/DebugFlagCommand.java @@ -9,7 +9,7 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/UuidMath.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/UuidMath.java deleted file mode 100644 index ded7288f..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/UuidMath.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.math; - -/** - * Bit helpers for UUID-compatible identifiers. - * - *

        Impulse random physics keys use {@code ThreadLocalRandom} longs because they are non-secret - * identifiers. This keeps UUID-shaped v4 values without paying for {@code UUID.randomUUID()}'s - * cryptographically strong generator on runtime allocation paths.

        - */ -public final class UuidMath { - - // UUID v4 stores the version in bits 12-15 of the most-significant long. - private static final long UUID_VERSION_MASK = 0xffffffffffff0fffL; - private static final long UUID_VERSION_4_BITS = 0x0000000000004000L; - // RFC 4122/IETF UUIDs store variant bits 10xx in the top bits of the least-significant long. - private static final long UUID_VARIANT_MASK = 0x3fffffffffffffffL; - private static final long UUID_IETF_VARIANT_BITS = 0x8000000000000000L; - - private UuidMath() { - } - - public static long version4MostSignificantBits(long randomBits) { - return (randomBits & UUID_VERSION_MASK) | UUID_VERSION_4_BITS; - } - - public static long ietfVariantLeastSignificantBits(long randomBits) { - return (randomBits & UUID_VARIANT_MASK) | UUID_IETF_VARIANT_BITS; - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java index 2f383c9f..15985c1a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkSubPlugin.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.commands.PhysicsChunkCommandSet; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; +import dev.hytalemodding.impulse.core.internal.PhysicsStoreRegistration; import java.util.logging.Level; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java index 54d5ef75..0827e89a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/commands/PhysicsChunkCommandSet.java @@ -2,7 +2,7 @@ import dev.hytalemodding.impulse.core.internal.commands.ImpulseCommandTreeRegistry; import dev.hytalemodding.impulse.core.internal.commands.debug.DebugFlagCommand; -import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; /** * Command set owned by the PhysicsChunk subplugin. diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java index af56e471..0c785db3 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionRestoreDependencyComponent.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.math.vector.Vector3fUtil; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollisionMode; import java.util.Objects; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java index f2462431..862ac489 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/components/ChunkCollisionSourceComponent.java @@ -7,7 +7,8 @@ import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; +import lombok.Getter; import java.util.Objects; import javax.annotation.Nonnull; @@ -54,13 +55,17 @@ public final class ChunkCollisionSourceComponent implements Component getComponentType() { return PhysicsComponentTypeRegistry.chunkCollisionSourceComponentType(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodyRuntimeState.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodyRuntimeState.java index 79df8038..23c6274f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodyRuntimeState.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodyRuntimeState.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; +package dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodySyncStateResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodySyncStateResource.java index da86b0f6..004e205f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodySyncStateResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/resources/PhysicsBodySyncStateResource.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState.BodySyncState; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodyRuntimeState.BodySyncState; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import java.util.Map; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java index 463f9ae3..c414640a 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicy.java @@ -1,6 +1,6 @@ package dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.resources.PhysicsVisualRuntime.BodyVisualInterestState; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.VisualOcclusionMode; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java index fce85fad..ea76c7c6 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystem.java @@ -22,7 +22,7 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsProjectionIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.profiling.PhysicsRuntimeProfilingResource; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual.PhysicsProjectionCleanupSystem; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.visual.VisualInterestCollector; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; @@ -47,7 +47,7 @@ * Synchronizes physics bodies with Hytale transforms each tick. * *

        Runs after the persistence restore group so that newly bootstrapped spaces, - * hydrated bodies, and hydrated joints are all settled before this system reads + * hydrated bodies and hydrated joints are all settled before this system reads * body transforms.

        * *

        Entities attach to authoritative PhysicsStore body UUIDs. Backend body destruction is explicit diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java similarity index 90% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java index 9570b584..94657d51 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsDebugResource.java @@ -1,9 +1,8 @@ -package dev.hytalemodding.impulse.core.internal.physics.resources; +package dev.hytalemodding.impulse.core.internal.resources; import com.hypixel.hytale.component.Resource; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import lombok.Getter; import lombok.Setter; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java index cb5b4977..6f9526d1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsResourceTypes.java @@ -3,7 +3,6 @@ import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java deleted file mode 100644 index 62798319..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/body/PhysicsBodySnapshots.java +++ /dev/null @@ -1,182 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.resources.body; - -import dev.hytalemodding.impulse.api.PhysicsBodySnapshot; -import dev.hytalemodding.impulse.api.runtime.BackendBodySnapshotSink; -import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Internal conversion helpers for primitive backend-runtime body snapshots. - */ -public final class PhysicsBodySnapshots { - - private PhysicsBodySnapshots() { - } - - @Nullable - public static PhysicsBodySnapshot read(@Nonnull PhysicsBackendRuntime runtime, - int spaceId, - long backendBodyId) { - SnapshotCapture capture = new SnapshotCapture(backendBodyId); - boolean present = runtime.bodySnapshot(spaceId, - backendBodyId, - capture); - return present ? capture.snapshot : null; - } - - @Nonnull - public static PhysicsBodySnapshot fromRuntimeFields(int shapeTypeCode, - int bodyTypeCode, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - float linearVelocityX, - float linearVelocityY, - float linearVelocityZ, - float angularVelocityX, - float angularVelocityY, - float angularVelocityZ, - boolean sleeping, - boolean sensor, - float mass, - float friction, - float restitution, - float linearDamping, - float angularDamping, - int collisionGroup, - int collisionMask, - boolean continuousCollisionEnabled, - float centerOfMassOffsetY, - boolean hasBoxHalfExtents, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - int axisCode) { - return PhysicsBodySnapshot.of(positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - linearVelocityX, - linearVelocityY, - linearVelocityZ, - angularVelocityX, - angularVelocityY, - angularVelocityZ, - BackendRuntimeCodes.bodyType(bodyTypeCode), - sleeping, - sensor, - mass, - friction, - restitution, - linearDamping, - angularDamping, - collisionGroup, - collisionMask, - continuousCollisionEnabled, - centerOfMassOffsetY, - BackendRuntimeCodes.shapeType(shapeTypeCode), - hasBoxHalfExtents, - halfExtentX, - halfExtentY, - halfExtentZ, - radius, - halfHeight, - BackendRuntimeCodes.axis(axisCode)); - } - - private static final class SnapshotCapture implements BackendBodySnapshotSink { - - private final long expectedBodyId; - @Nullable - private PhysicsBodySnapshot snapshot; - - private SnapshotCapture(long expectedBodyId) { - this.expectedBodyId = expectedBodyId; - } - - @Override - public void accept(long bodyId, - int shapeTypeCode, - int bodyTypeCode, - float positionX, - float positionY, - float positionZ, - float rotationX, - float rotationY, - float rotationZ, - float rotationW, - float linearVelocityX, - float linearVelocityY, - float linearVelocityZ, - float angularVelocityX, - float angularVelocityY, - float angularVelocityZ, - boolean sleeping, - boolean sensor, - float mass, - float friction, - float restitution, - float linearDamping, - float angularDamping, - int collisionGroup, - int collisionMask, - boolean continuousCollisionEnabled, - float centerOfMassOffsetY, - boolean hasBoxHalfExtents, - float halfExtentX, - float halfExtentY, - float halfExtentZ, - float radius, - float halfHeight, - int axisCode) { - if (bodyId != expectedBodyId) { - throw new IllegalStateException("Backend emitted snapshot for unexpected body id " - + bodyId + " while reading " + expectedBodyId); - } - snapshot = fromRuntimeFields(shapeTypeCode, - bodyTypeCode, - positionX, - positionY, - positionZ, - rotationX, - rotationY, - rotationZ, - rotationW, - linearVelocityX, - linearVelocityY, - linearVelocityZ, - angularVelocityX, - angularVelocityY, - angularVelocityZ, - sleeping, - sensor, - mass, - friction, - restitution, - linearDamping, - angularDamping, - collisionGroup, - collisionMask, - continuousCollisionEnabled, - centerOfMassOffsetY, - hasBoxHalfExtents, - halfExtentX, - halfExtentY, - halfExtentZ, - radius, - halfHeight, - axisCode); - } - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java index c303e33b..b17bbfb5 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsDebugSystem.java @@ -24,7 +24,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; -import dev.hytalemodding.impulse.core.internal.physics.resources.PhysicsDebugResource; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsDebugOverlayResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java index d7c1e013..fbda5c38 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/components/PhysicsComponentTypes.java @@ -2,7 +2,7 @@ import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.CollisionLodSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.components.ChunkCollisionSettingsComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.VisualMaterializationSettingsComponent; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java index 20fb227d..7759639e 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommandLifecycleGuardTest.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommandDeleteTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommandDeleteTest.java index 5f04d1af..caeacc51 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommandDeleteTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/commands/space/SpaceCommandDeleteTest.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java index eb5281ca..519ecb0f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/PhysicsChunkStoreTypesTest.java @@ -7,8 +7,8 @@ import com.hypixel.hytale.component.EmptyResourceStorage; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkComponentSyncResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystemTest.java index 4a963efb..a700e058 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionComponentSyncSystemTest.java @@ -25,7 +25,7 @@ import dev.hytalemodding.impulse.core.internal.systems.IdentityIndexSystem; import dev.hytalemodding.impulse.core.internal.systems.PersistenceHydrationSystem; import dev.hytalemodding.impulse.core.internal.systems.SpaceSettingsApplicationSystem; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystemTest.java index 6459204a..07107d6f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionMutationDrainSystemTest.java @@ -30,7 +30,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkCollisionDefaults; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionMutationQueueResource; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystemTest.java index a55b7f29..5ac04c44 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicschunk/systems/ChunkCollisionVoxelStitchingSystemTest.java @@ -25,7 +25,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.ChunkCollisionPayload.Neighbor; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkCollisionPayloadResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java index 21e419a1..109cc61c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncPolicyTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings.PhysicsVisualSyncSettings; import java.util.Arrays; import java.util.List; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java index 8344be42..1af11d7e 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/systems/sync/PhysicsSyncSystemTest.java @@ -7,7 +7,7 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.math.PhysicsVisualPoseMath; -import dev.hytalemodding.impulse.core.internal.resources.body.PhysicsBodyRuntimeState; +import dev.hytalemodding.impulse.core.internal.modules.physicsentity.resources.PhysicsBodyRuntimeState; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.AttachmentLifecycle; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent.TransformAuthority; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java index 9fb69c1d..e8d9d720 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanupTest.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java index f1722b4f..564ed67f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreTopologyMutationsTest.java @@ -26,7 +26,7 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java index f13cb132..31041af6 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/persistence/PhysicsStoreHolderPersistenceTest.java @@ -31,8 +31,8 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionRestoreDependencyComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsStoreRegistration; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsStoreRegistration; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResourceTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResourceTest.java index b4450cd9..ee50f85a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResourceTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/physics/resources/PhysicsDebugResourceTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import dev.hytalemodding.impulse.core.internal.resources.PhysicsDebugResource; import org.junit.jupiter.api.Test; class PhysicsDebugResourceTest { diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java index 095e1958..48433286 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/registration/PhysicsTypeRegistrationApiTest.java @@ -4,11 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.component.IComponentRegistry; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.plugin.components.PhysicsComponentTypes; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityTypes; import java.lang.reflect.Method; @@ -23,17 +21,13 @@ void internalRegistriesOwnRegistrationAndLifecycleWithoutPublicMutators() throws NoSuchMethodException { assertNotNull(PhysicsComponentTypeRegistry.class.getDeclaredMethod("registerComponentTypes", ComponentRegistryProxy.class)); - assertNotNull(ControlTypeRegistry.class.getDeclaredMethod("registerComponentTypes", - IComponentRegistry.class)); assertNotNull(PhysicsResourceTypes.class.getDeclaredMethod("registerResourceTypes", ComponentRegistryProxy.class)); assertFalse(hasPublicSetter(PhysicsComponentTypes.class)); assertFalse(hasPublicSetter(PhysicsResourceTypes.class)); - assertFalse(hasPublicSetter(ImpulseControllableComponent.class)); assertFalse(hasPublicRegistrationMethod(PhysicsComponentTypes.class)); assertFalse(hasPublicRegistrationMethod(PhysicsEntityTypes.class)); - assertFalse(hasPublicRegistrationMethod(ImpulseControllableComponent.class)); assertFalse(hasPublicLifecycleMutator(PhysicsChunkCollision.class)); } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java index 4b2a1f91..ba24577c 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyBindingSystemTest.java @@ -23,7 +23,7 @@ import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.components.ChunkCollisionSourceComponent.PartKind; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.systems.ChunkCollisionMutationDrainSystem; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.resources.PhysicsChunkSettingsIndexResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java index 5cd92c53..160288e0 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/BodyCommandApplicationSystemTest.java @@ -14,7 +14,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; import dev.hytalemodding.impulse.core.internal.systems.binding.BodyBindingSystem; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java index 3c24a4e7..ac1c5351 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/JointBindingSystemTest.java @@ -21,7 +21,7 @@ import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; import dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkStoreTypes; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java index 7b5160ea..724bd25f 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/StaleBodyRemovalSystemTest.java @@ -22,7 +22,7 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java index 5202f13c..0fe582dd 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/binding/SpaceBindingSystemTest.java @@ -17,7 +17,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java index 0131a321..0d26477a 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/systems/debug/PhysicsStoreDebugQueriesTest.java @@ -14,7 +14,7 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.api.runtime.BackendContactSink; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java index 55e3acfa..65f57388 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodiesTest.java @@ -15,7 +15,7 @@ import com.hypixel.hytale.server.core.util.thread.TickingThread; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java index 82b16ae4..23639122 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsBodyEntitiesTest.java @@ -16,7 +16,7 @@ import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java index 0c83e755..40e3ec4b 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsSpacesSettingsComponentTest.java @@ -17,7 +17,7 @@ import com.hypixel.hytale.server.core.util.thread.TickingThread; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.ExtensionSettingsComponent; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitViewTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitViewTest.java index 90ceb6e1..7802d7cf 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitViewTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/plugin/physics/RaycastHitViewTest.java @@ -13,7 +13,7 @@ import dev.hytalemodding.impulse.api.PhysicsAxis; import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.ShapeType; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; import dev.hytalemodding.impulse.core.plugin.components.DynamicsComponent; import dev.hytalemodding.impulse.core.plugin.components.ShapeComponent; diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java index 9233a522..3887a787 100644 --- a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/commands/DirectionalPendulumsCommandTest.java @@ -19,7 +19,7 @@ import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider.FakePhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; import dev.hytalemodding.impulse.core.internal.resources.PhysicsResourceTypes; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRestoreStatusResource; From 211bf3a1e7bc4fafd14185699f84927dee8483b3 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 25 Jun 2026 12:48:20 +0200 Subject: [PATCH 531/534] refactor(control): extract builtin plugin Signed-off-by: Blovien --- build.gradle.kts | 4 +- .../PhysicsBackendRuntimeProvider.java | 4 - impulse-builtins/control/.gitignore | 1 + impulse-builtins/control/build.gradle.kts | 49 +++++ .../builtin/control/ImpulseControlPlugin.java | 88 +++++++++ .../control/ImpulseControllableComponent.java | 4 +- .../control/PhysicsControlSessions.java | 12 +- .../control/internal}/ControlLifecycle.java | 151 +++++++++++++-- .../internal}/ControlTypeRegistry.java | 6 +- .../internal}/PhysicsControlRuntimeState.java | 2 +- .../PhysicsControlRuntimeStates.java | 2 +- .../PhysicsControlSessionComponent.java | 4 +- .../PhysicsControlRuntimeHolderSystem.java | 8 +- .../systems/PhysicsControlSessionCleanup.java | 6 +- .../PhysicsControlSessionCleanupSystem.java | 6 +- .../PhysicsControllableLifecycleSystem.java | 6 +- .../PhysicsKinematicControlSystem.java | 6 +- .../PhysicsStoreControlSessionMutations.java | 4 +- .../control/ImpulseControlManifestTest.java | 43 +++++ .../internal}/ControlLifecycleTest.java | 10 +- ...ControllableComponentRegistrationTest.java | 4 +- .../PhysicsControlSessionComponentTest.java | 6 +- .../PhysicsControlSystemRegistrationTest.java | 12 +- .../PhysicsKinematicControlSystemTest.java | 8 +- ...ysicsStoreControlSessionMutationsTest.java | 6 +- .../testsupport/TestInstanceFactory.java | 70 +++++++ impulse-core/build.gradle.kts | 10 +- .../core/internal/commands/CleanCommand.java | 125 ++---------- .../internal/math/PhysicsVisualPoseMath.java | 4 +- .../modules/control/ControlModule.java | 38 ---- ...ubPlugin.java => PhysicsEntityModule.java} | 4 +- .../PhysicsEntityProjectionCleanup.java | 53 +++++- .../VisualMaterializationSettingsCommand.java | 2 + .../physics/PhysicsStoreRowCleanup.java | 2 - .../physics/PhysicsTopologyMutations.java | 2 - .../systems/step/StepSubmissionSystem.java | 4 + .../components/BodyAttachmentComponent.java | 14 +- .../GeneratedVisualProxyComponent.java | 5 + .../impulse/core/plugin/package-info.java | 2 +- .../plugin/physics/PhysicsCleanupHooks.java | 179 ++++++++++++++++++ impulse-core/src/module-info/module-info.java | 1 - ...endRegistryPluginBackendSelectionTest.java | 1 - ...kendRegistrySubPluginRegistrationTest.java | 35 +++- .../PhysicsEntityProjectionCleanupTest.java | 5 +- impulse-examples/build.gradle.kts | 3 + .../examples/commands/DropCommand.java | 4 +- .../examples/commands/GrabCommand.java | 4 +- .../examples/commands/ReleaseCommand.java | 2 +- .../examples/utils/ExamplePhysicsUtils.java | 4 +- settings.gradle.kts | 1 + 50 files changed, 753 insertions(+), 273 deletions(-) create mode 100644 impulse-builtins/control/.gitignore create mode 100644 impulse-builtins/control/build.gradle.kts create mode 100644 impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin}/control/ImpulseControllableComponent.java (87%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin}/control/PhysicsControlSessions.java (94%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/ControlLifecycle.java (52%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/ControlTypeRegistry.java (89%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/PhysicsControlRuntimeState.java (97%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/PhysicsControlRuntimeStates.java (98%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/components/PhysicsControlSessionComponent.java (95%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsControlRuntimeHolderSystem.java (87%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsControlSessionCleanup.java (80%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsControlSessionCleanupSystem.java (93%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsControllableLifecycleSystem.java (91%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsKinematicControlSystem.java (98%) rename {impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsStoreControlSessionMutations.java (96%) create mode 100644 impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java rename {impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal}/ControlLifecycleTest.java (95%) rename {impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal}/ImpulseBackendRegistryControllableComponentRegistrationTest.java (90%) rename {impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal}/components/PhysicsControlSessionComponentTest.java (85%) rename {impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsControlSystemRegistrationTest.java (91%) rename {impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsKinematicControlSystemTest.java (93%) rename {impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control => impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal}/systems/PhysicsStoreControlSessionMutationsTest.java (98%) create mode 100644 impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/testsupport/TestInstanceFactory.java delete mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlModule.java rename impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/{PhysicsEntitySubPlugin.java => PhysicsEntityModule.java} (91%) create mode 100644 impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsCleanupHooks.java diff --git a/build.gradle.kts b/build.gradle.kts index 42e42ce5..81869110 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,10 +18,11 @@ val coreOnlyWorkspace = providers.gradleProperty("impulse.coreOnlyWorkspace") .map(String::toBoolean) .orElse(false) val coreModProjects = listOf(":impulse-core") +val builtinModProjects = listOf(":impulse-builtins:control") val workspaceModProjects = if (coreOnlyWorkspace.get()) { coreModProjects } else { - listOf(":impulse-examples") + coreModProjects + listOf(":impulse-examples") + builtinModProjects + coreModProjects } hytaleWorkspace { @@ -150,6 +151,7 @@ tasks.register("headlessTest") { ":impulse-backends:native-loader:test", ":impulse-backends:jolt:test", ":impulse-backends:rapier:test", + ":impulse-builtins:control:test", ":impulse-core:test", ":impulse-examples:test", ":impulse-early-plugin:test" diff --git a/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java index 1a033d31..8d5fc3ce 100644 --- a/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java +++ b/impulse-backends/api/src/main/java/dev/hytalemodding/impulse/api/runtime/PhysicsBackendRuntimeProvider.java @@ -1,7 +1,6 @@ package dev.hytalemodding.impulse.api.runtime; import dev.hytalemodding.impulse.api.BackendId; -import java.util.logging.Level; import javax.annotation.Nonnull; /** @@ -15,9 +14,6 @@ public interface PhysicsBackendRuntimeProvider { default void init() { } - default void setInternalLoggingLevel(@Nonnull Level level) { - } - @Nonnull PhysicsBackendRuntime createRuntime(); } diff --git a/impulse-builtins/control/.gitignore b/impulse-builtins/control/.gitignore new file mode 100644 index 00000000..8c25fdb9 --- /dev/null +++ b/impulse-builtins/control/.gitignore @@ -0,0 +1 @@ +/src/main/resources/manifest.json diff --git a/impulse-builtins/control/build.gradle.kts b/impulse-builtins/control/build.gradle.kts new file mode 100644 index 00000000..ac94c251 --- /dev/null +++ b/impulse-builtins/control/build.gradle.kts @@ -0,0 +1,49 @@ +import org.gradle.api.tasks.testing.Test + +plugins { + id("com.azuredoom.hytale-tools") +} + +version = rootProject.version + +dependencies { + implementation(project(":impulse-backends:api")) + compileOnly(project(":impulse-core")) + compileOnly(project(":impulse-early-plugin")) + testImplementation(project(":impulse-core")) + testImplementation(project(":impulse-early-plugin")) + testImplementation(testFixtures(project(":impulse-backends:api"))) + testImplementation(libs.objenesis) + testRuntimeOnly(project(":impulse-early-plugin")) + testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") + testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") + compileOnly(libs.lombok) + annotationProcessor(libs.lombok) +} + +tasks.compileJava { + dependsOn(tasks.named("downloadAssetsZip")) +} + +tasks.withType().configureEach { + jvmArgs("-Djava.util.logging.manager=com.hypixel.hytale.logger.backend.HytaleLogManager") +} + +val downloadAssetsZip = tasks.named("downloadAssetsZip") + +project(":impulse-core").tasks.named("compileJava") { + mustRunAfter(downloadAssetsZip) +} + +hytaleTools { + modId = property("mod_name") as String + "Control" + mainClass = "dev.hytalemodding.impulse.builtin.control.ImpulseControlPlugin" + modCredits = property("mod_credits") as String + modUrl = property("mod_website") as String + modDescription = "Official kinematic-control builtin for Impulse" + manifestServerVersion = property("hytale_version") as String + manifestDependencies = listOf( + "HytaleModding:Impulse=*", + "HytaleModding:ImpulsePhysicsEntity=*" + ).joinToString(",") +} diff --git a/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java new file mode 100644 index 00000000..6d5f7536 --- /dev/null +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java @@ -0,0 +1,88 @@ +package dev.hytalemodding.impulse.builtin.control; + +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.builtin.control.internal.ControlLifecycle; +import dev.hytalemodding.impulse.builtin.control.internal.ControlTypeRegistry; +import dev.hytalemodding.impulse.builtin.control.internal.PhysicsControlRuntimeStates; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsControllableLifecycleSystem; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsControlRuntimeHolderSystem; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsControlSessionCleanupSystem; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsKinematicControlSystem; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsCleanupHooks; +import java.util.UUID; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +/** + * Builtin plugin that enables Impulse kinematic control sessions. + */ +public final class ImpulseControlPlugin extends JavaPlugin { + + @Nonnull + private static final PhysicsCleanupHooks.EntityStoreCleanup ENTITY_STORE_CLEANUP = + ControlLifecycle::cleanupStoreForExternalCleanup; + @Nonnull + private static final PhysicsCleanupHooks.SelectedEntityStoreCleanup SELECTED_ENTITY_STORE_CLEANUP = + ControlLifecycle::cleanupSelectedStoreForExternalCleanup; + @Nonnull + private static final Consumer> BODY_RUNTIME_CLEANUP = + PhysicsControlRuntimeStates::clear; + @Nonnull + private static final PhysicsCleanupHooks.BodyRowCleanup BODY_ROW_CLEANUP = + ImpulseControlPlugin::clearControlledBody; + + public ImpulseControlPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); + ControlTypeRegistry.registerComponentTypes(entityRegistry); + entityRegistry.registerSystem(new PhysicsControlRuntimeHolderSystem()); + entityRegistry.registerSystem(new PhysicsControllableLifecycleSystem()); + entityRegistry.registerSystem(new PhysicsControlSessionCleanupSystem()); + entityRegistry.registerSystem(new PhysicsKinematicControlSystem()); + registerCleanupHooks(); + ControlLifecycle.enable(); + } + + @Override + protected void shutdown() { + ControlLifecycle.disable(); + unregisterCleanupHooks(); + ControlTypeRegistry.clearComponentTypes(); + } + + private static void registerCleanupHooks() { + PhysicsCleanupHooks.registerDetachableEntityMarker( + ImpulseControllableComponent.getComponentType()); + PhysicsCleanupHooks.registerEntityStoreCleanup(ENTITY_STORE_CLEANUP); + PhysicsCleanupHooks.registerSelectedEntityStoreCleanup(SELECTED_ENTITY_STORE_CLEANUP); + PhysicsCleanupHooks.registerBodyRuntimeCleanup(BODY_RUNTIME_CLEANUP); + PhysicsCleanupHooks.registerBodyRowCleanup(BODY_ROW_CLEANUP); + } + + private static void unregisterCleanupHooks() { + if (ImpulseControllableComponent.isComponentTypeRegistered()) { + PhysicsCleanupHooks.unregisterDetachableEntityMarker( + ImpulseControllableComponent.getComponentType()); + } + PhysicsCleanupHooks.unregisterEntityStoreCleanup(ENTITY_STORE_CLEANUP); + PhysicsCleanupHooks.unregisterSelectedEntityStoreCleanup(SELECTED_ENTITY_STORE_CLEANUP); + PhysicsCleanupHooks.unregisterBodyRuntimeCleanup(BODY_RUNTIME_CLEANUP); + PhysicsCleanupHooks.unregisterBodyRowCleanup(BODY_ROW_CLEANUP); + } + + private static void clearControlledBody(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef) { + PhysicsControlRuntimeStates.clearControlled(bodyRef); + } +} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponent.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControllableComponent.java similarity index 87% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponent.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControllableComponent.java index aa19fcfb..84b1cf14 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/ImpulseControllableComponent.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControllableComponent.java @@ -1,10 +1,10 @@ -package dev.hytalemodding.impulse.core.plugin.modules.control; +package dev.hytalemodding.impulse.builtin.control; import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; +import dev.hytalemodding.impulse.builtin.control.internal.ControlTypeRegistry; import javax.annotation.Nonnull; public class ImpulseControllableComponent implements Component { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/PhysicsControlSessions.java similarity index 94% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/PhysicsControlSessions.java index a8237413..0c8d4930 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/control/PhysicsControlSessions.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/PhysicsControlSessions.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.plugin.modules.control; +package dev.hytalemodding.impulse.builtin.control; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; @@ -6,11 +6,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsStoreControlSessionMutations; +import dev.hytalemodding.impulse.builtin.control.internal.ControlLifecycle; +import dev.hytalemodding.impulse.builtin.control.internal.PhysicsControlRuntimeStates; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsKinematicControlSystem; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsStoreControlSessionMutations; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import java.util.UUID; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/ControlLifecycle.java similarity index 52% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/ControlLifecycle.java index bf1f6bda..b2406b88 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycle.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/ControlLifecycle.java @@ -1,20 +1,26 @@ -package dev.hytalemodding.impulse.core.internal.modules.control; +package dev.hytalemodding.impulse.builtin.control.internal; import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.core.internal.modules.SubPluginLifecycleGate; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsControlSessionCleanup; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; import java.util.ArrayList; import java.util.Collections; import java.util.Set; import java.util.WeakHashMap; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; @@ -22,6 +28,7 @@ import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.joml.Vector3d; /** * Server-level lifecycle controlled by the Impulse control subplugin. @@ -91,34 +98,63 @@ private static void cleanupStores() { } } - private static void cleanupStore(@Nonnull Store store, + public static int cleanupStoreForExternalCleanup(@Nonnull Store store) { + ComponentType controllableType = + ImpulseControllableComponent.isComponentTypeRegistered() + ? ImpulseControllableComponent.getComponentType() + : null; + ComponentType sessionType = + PhysicsControlSessionComponent.isComponentTypeRegistered() + ? PhysicsControlSessionComponent.getComponentType() + : null; + if (controllableType == null && sessionType == null) { + return 0; + } + return cleanupStore(store, controllableType, sessionType); + } + + public static int cleanupSelectedStoreForExternalCleanup(@Nonnull Store store, + @Nonnull Set selectedBodyUuids, + @Nonnull Vector3d center, + double radiusSquared) { + if (!PhysicsControlSessionComponent.isComponentTypeRegistered()) { + return 0; + } + return cleanupSelectedStoreOnWorldThread(store, + PhysicsControlSessionComponent.getComponentType(), + selectedBodyUuids, + center, + radiusSquared); + } + + private static int cleanupStore(@Nonnull Store store, @Nullable ComponentType controllableType, @Nullable ComponentType sessionType) { World world = store.getExternalData().getWorld(); if (world.isInThread()) { - cleanupStoreOnWorldThread(store, controllableType, sessionType); - return; + return cleanupStoreOnWorldThread(store, controllableType, sessionType); } if (!world.isStarted()) { - return; + return 0; } // PluginManager.unload holds the asset write lock while world ticks drain queued // tasks under the read lock, so waiting here would stall until the timeout. boolean waitForCleanup = !isAssetWriteLockHeldByCurrentThread(); - CompletableFuture cleanup = waitForCleanup ? new CompletableFuture<>() : null; + CompletableFuture cleanup = waitForCleanup ? new CompletableFuture<>() : null; try { world.execute(() -> cleanupStoreSafely(store, controllableType, sessionType, cleanup)); } catch (RuntimeException exception) { if (isWorldTaskRejection(exception)) { - return; + return 0; } throw exception; } if (cleanup != null) { - cleanup.orTimeout(CLEANUP_TIMEOUT_SECONDS, TimeUnit.SECONDS).join(); + return cleanup.orTimeout(CLEANUP_TIMEOUT_SECONDS, TimeUnit.SECONDS).join(); } + return 0; } private static boolean isAssetWriteLockHeldByCurrentThread() { @@ -130,11 +166,11 @@ private static boolean isAssetWriteLockHeldByCurrentThread() { private static void cleanupStoreSafely(@Nonnull Store store, @Nullable ComponentType controllableType, @Nullable ComponentType sessionType, - @Nullable CompletableFuture completion) { + @Nullable CompletableFuture completion) { try { - cleanupStoreOnWorldThread(store, controllableType, sessionType); + int removedSessions = cleanupStoreOnWorldThread(store, controllableType, sessionType); if (completion != null) { - completion.complete(null); + completion.complete(removedSessions); } } catch (RuntimeException exception) { if (completion != null) { @@ -157,9 +193,10 @@ private static boolean isWorldTaskRejection(@Nonnull RuntimeException exception) return false; } - private static void cleanupStoreOnWorldThread(@Nonnull Store store, + private static int cleanupStoreOnWorldThread(@Nonnull Store store, @Nullable ComponentType controllableType, @Nullable ComponentType sessionType) { + int removedSessions = 0; if (sessionType != null) { ArrayList sessions = new ArrayList<>(); store.forEachEntityParallel(sessionType, @@ -179,6 +216,7 @@ private static void cleanupStoreOnWorldThread(@Nonnull Store store, PhysicsControlSessionCleanup.cleanup(store, target.session()); store.removeComponent(target.ref(), sessionType); } + removedSessions = sessions.size(); } if (controllableType != null) { store.forEachEntityParallel(controllableType, @@ -186,6 +224,89 @@ private static void cleanupStoreOnWorldThread(@Nonnull Store store, commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), controllableType)); } + return removedSessions; + } + + private static int cleanupSelectedStoreOnWorldThread(@Nonnull Store store, + @Nonnull ComponentType sessionType, + @Nonnull Set selectedBodyUuids, + @Nonnull Vector3d center, + double radiusSquared) { + ArrayList sessions = new ArrayList<>(); + store.forEachEntityParallel(sessionType, + (index, archetypeChunk, commandBuffer) -> { + PhysicsControlSessionComponent session = + archetypeChunk.getComponent(index, sessionType); + if (session == null || !controlSessionSelected(commandBuffer, + archetypeChunk, + index, + session, + selectedBodyUuids, + center, + radiusSquared)) { + return; + } + SessionCleanupTarget target = new SessionCleanupTarget( + archetypeChunk.getReferenceTo(index), + session); + synchronized (sessions) { + sessions.add(target); + } + }); + for (SessionCleanupTarget target : sessions) { + PhysicsControlSessionCleanup.cleanup(store, target.session()); + store.removeComponent(target.ref(), sessionType); + } + return sessions.size(); + } + + private static boolean controlSessionSelected( + @Nonnull CommandBuffer commandBuffer, + @Nonnull ArchetypeChunk archetypeChunk, + int index, + @Nonnull PhysicsControlSessionComponent session, + @Nonnull Set selectedBodyUuids, + @Nonnull Vector3d center, + double radiusSquared) { + if (containsBody(selectedBodyUuids, session.getBodyRef()) + || containsBody(selectedBodyUuids, session.getAnchorBodyRef()) + || entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { + return true; + } + + Ref targetRef = session.getTargetRef(); + if (targetRef == null || !targetRef.isValid()) { + return false; + } + + TransformComponent targetTransform = + commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); + return targetTransform != null && targetTransform.getPosition().distanceSquared(center) + <= radiusSquared; + } + + private static boolean containsBody(@Nonnull Set bodyUuids, + @Nullable Ref bodyRef) { + UUID bodyUuid = rowUuid(bodyRef); + return bodyUuid != null && bodyUuids.contains(bodyUuid); + } + + @Nullable + private static UUID rowUuid(@Nullable Ref bodyRef) { + if (bodyRef == null || !bodyRef.isValid()) { + return null; + } + UuidComponent uuid = bodyRef.getStore().getComponent(bodyRef, UuidComponent.getComponentType()); + return uuid != null ? uuid.getUuid() : null; + } + + private static boolean entityWithinRadius(@Nonnull ArchetypeChunk archetypeChunk, + int index, + @Nonnull Vector3d center, + double radiusSquared) { + TransformComponent transform = + archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + return transform != null && transform.getPosition().distanceSquared(center) <= radiusSquared; } private record SessionCleanupTarget( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlTypeRegistry.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/ControlTypeRegistry.java similarity index 89% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlTypeRegistry.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/ControlTypeRegistry.java index 3e7b6345..cfc725ba 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlTypeRegistry.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/ControlTypeRegistry.java @@ -1,10 +1,10 @@ -package dev.hytalemodding.impulse.core.internal.modules.control; +package dev.hytalemodding.impulse.builtin.control.internal; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.IComponentRegistry; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/PhysicsControlRuntimeState.java similarity index 97% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/PhysicsControlRuntimeState.java index 76b86241..a38f2815 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeState.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/PhysicsControlRuntimeState.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control; +package dev.hytalemodding.impulse.builtin.control.internal; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/PhysicsControlRuntimeStates.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/PhysicsControlRuntimeStates.java index 9aa81adc..1b31faad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/PhysicsControlRuntimeStates.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/PhysicsControlRuntimeStates.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control; +package dev.hytalemodding.impulse.builtin.control.internal; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/components/PhysicsControlSessionComponent.java similarity index 95% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/components/PhysicsControlSessionComponent.java index a4912639..7bd912bf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponent.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/components/PhysicsControlSessionComponent.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.components; +package dev.hytalemodding.impulse.builtin.control.internal.components; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; +import dev.hytalemodding.impulse.builtin.control.internal.ControlTypeRegistry; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlRuntimeHolderSystem.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlRuntimeHolderSystem.java similarity index 87% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlRuntimeHolderSystem.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlRuntimeHolderSystem.java index 1a35facb..a76c1885 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlRuntimeHolderSystem.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlRuntimeHolderSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ComponentType; @@ -8,9 +8,9 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.HolderSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.internal.ControlLifecycle; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; import java.util.Objects; import javax.annotation.Nonnull; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSessionCleanup.java similarity index 80% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSessionCleanup.java index 790709c5..ba08e9a9 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanup.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSessionCleanup.java @@ -1,11 +1,11 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.PhysicsControlRuntimeStates; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; import javax.annotation.Nonnull; public final class PhysicsControlSessionCleanup { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSessionCleanupSystem.java similarity index 93% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSessionCleanupSystem.java index cce1ef24..f18dff69 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSessionCleanupSystem.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSessionCleanupSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; @@ -8,8 +8,8 @@ import com.hypixel.hytale.component.system.RefChangeSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.ControlLifecycle; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControllableLifecycleSystem.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControllableLifecycleSystem.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControllableLifecycleSystem.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControllableLifecycleSystem.java index 77879cc9..3483d5ab 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControllableLifecycleSystem.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControllableLifecycleSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; @@ -7,8 +7,8 @@ import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefChangeSystem; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.internal.ControlLifecycle; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsKinematicControlSystem.java similarity index 98% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsKinematicControlSystem.java index c41eebde..4e85543e 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystem.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsKinematicControlSystem.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -18,8 +18,8 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.ControlLifecycle; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.systems.sync.PhysicsSyncSystem; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsStoreControlSessionMutations.java similarity index 96% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java rename to impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsStoreControlSessionMutations.java index 10c4c7f2..e2c01984 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutations.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsStoreControlSessionMutations.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; @@ -6,7 +6,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResource; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; diff --git a/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java new file mode 100644 index 00000000..c6ee70f4 --- /dev/null +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java @@ -0,0 +1,43 @@ +package dev.hytalemodding.impulse.builtin.control; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class ImpulseControlManifestTest { + + @Test + void generatedManifestDeclaresStandaloneControlPlugin() throws IOException { + PluginManifest manifest = decodeGeneratedManifest(); + + assertEquals("HytaleModding", manifest.getGroup()); + assertEquals("ImpulseControl", manifest.getName()); + assertEquals("dev.hytalemodding.impulse.builtin.control.ImpulseControlPlugin", + manifest.getMain()); + assertTrue(manifest.getDependencies() + .containsKey(new PluginIdentifier("HytaleModding", "Impulse"))); + assertTrue(manifest.getDependencies() + .containsKey(new PluginIdentifier("HytaleModding", "ImpulsePhysicsEntity"))); + } + + private static PluginManifest decodeGeneratedManifest() throws IOException { + InputStream stream = ImpulseControlManifestTest.class + .getClassLoader() + .getResourceAsStream("manifest.json"); + assertNotNull(stream); + try (InputStreamReader input = new InputStreamReader(stream, StandardCharsets.UTF_8); + RawJsonReader reader = new RawJsonReader(input, RawJsonReader.READ_BUFFER.get())) { + return PluginManifest.CODEC.decodeJson(reader, new ExtraInfo()); + } + } +} diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/ControlLifecycleTest.java similarity index 95% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java rename to impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/ControlLifecycleTest.java index c620443b..42c91890 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlLifecycleTest.java +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/ControlLifecycleTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control; +package dev.hytalemodding.impulse.builtin.control.internal; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -16,10 +16,10 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import com.hypixel.hytale.server.core.util.thread.TickingThread; import dev.hytalemodding.impulse.api.PhysicsBodyType; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.PhysicsControlSessions; import java.lang.reflect.Method; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseBackendRegistryControllableComponentRegistrationTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/ImpulseBackendRegistryControllableComponentRegistrationTest.java similarity index 90% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseBackendRegistryControllableComponentRegistrationTest.java rename to impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/ImpulseBackendRegistryControllableComponentRegistrationTest.java index 45fea058..5aed4381 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/ImpulseBackendRegistryControllableComponentRegistrationTest.java +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/ImpulseBackendRegistryControllableComponentRegistrationTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control; +package dev.hytalemodding.impulse.builtin.control.internal; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponentTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/components/PhysicsControlSessionComponentTest.java similarity index 85% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponentTest.java rename to impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/components/PhysicsControlSessionComponentTest.java index 10663e86..080c112d 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/components/PhysicsControlSessionComponentTest.java +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/components/PhysicsControlSessionComponentTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.components; +package dev.hytalemodding.impulse.builtin.control.internal.components; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; @@ -8,7 +8,7 @@ import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; +import dev.hytalemodding.impulse.builtin.control.internal.ControlTypeRegistry; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -20,7 +20,7 @@ void clearRegistration() { } @Test - void componentTypeCanBeClearedWhenControlModuleUnloads() { + void componentTypeCanBeClearedWhenControlBuiltinUnloads() { ComponentRegistry registry = new ComponentRegistry<>(); ControlTypeRegistry.registerComponentTypes(registry); ComponentType type = diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSystemRegistrationTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSystemRegistrationTest.java similarity index 91% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSystemRegistrationTest.java rename to impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSystemRegistrationTest.java index fdff2204..0289a9c1 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsControlSystemRegistrationTest.java +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsControlSystemRegistrationTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -10,11 +10,11 @@ import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlLifecycle; -import dev.hytalemodding.impulse.core.internal.modules.control.ControlTypeRegistry; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.internal.ControlLifecycle; +import dev.hytalemodding.impulse.builtin.control.internal.ControlTypeRegistry; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.builtin.control.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; import java.lang.reflect.Field; import javax.annotation.Nonnull; import org.junit.jupiter.api.AfterEach; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsKinematicControlSystemTest.java similarity index 93% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java rename to impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsKinematicControlSystemTest.java index 828b64a6..5523cbb4 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsKinematicControlSystemTest.java +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsKinematicControlSystemTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -10,9 +10,9 @@ import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem.ControlAnchorUpdate; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem.ControlMutationState; -import dev.hytalemodding.impulse.core.internal.testsupport.TestInstanceFactory; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsKinematicControlSystem.ControlAnchorUpdate; +import dev.hytalemodding.impulse.builtin.control.internal.systems.PhysicsKinematicControlSystem.ControlMutationState; +import dev.hytalemodding.impulse.builtin.control.internal.testsupport.TestInstanceFactory; import javax.annotation.Nonnull; import org.joml.Vector3f; import org.junit.jupiter.api.Test; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsStoreControlSessionMutationsTest.java similarity index 98% rename from impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java rename to impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsStoreControlSessionMutationsTest.java index 6a25def6..5e3ffeba 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/control/systems/PhysicsStoreControlSessionMutationsTest.java +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/systems/PhysicsStoreControlSessionMutationsTest.java @@ -1,4 +1,4 @@ -package dev.hytalemodding.impulse.core.internal.modules.control.systems; +package dev.hytalemodding.impulse.builtin.control.internal.systems; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -25,8 +25,8 @@ import dev.hytalemodding.impulse.api.runtime.BackendRuntimeCodes; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.registration.PhysicsComponentTypeRegistry; +import dev.hytalemodding.impulse.builtin.control.internal.components.PhysicsControlSessionComponent; +import dev.hytalemodding.impulse.core.internal.PhysicsComponentTypeRegistry; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; diff --git a/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/testsupport/TestInstanceFactory.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/testsupport/TestInstanceFactory.java new file mode 100644 index 00000000..81c642d7 --- /dev/null +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/internal/testsupport/TestInstanceFactory.java @@ -0,0 +1,70 @@ +package dev.hytalemodding.impulse.builtin.control.internal.testsupport; + +import com.hypixel.hytale.server.core.universe.world.World; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.objenesis.ObjenesisStd; + +/** + * Allocates Hytale server classes whose public constructors bind to global server state. + */ +public final class TestInstanceFactory { + + private static final ObjenesisStd OBJENESIS = new ObjenesisStd(); + + private TestInstanceFactory() { + } + + @Nonnull + public static World world(@Nonnull String worldName) { + World world = allocate(World.class); + setField(world, World.class, "name", worldName); + return world; + } + + @Nonnull + private static T allocate(@Nonnull Class type) { + T constructed = constructNoArg(type); + if (constructed != null) { + return constructed; + } + return constructWithoutInvokingConstructor(type); + } + + @Nullable + private static T constructNoArg(@Nonnull Class type) { + try { + Constructor constructor = type.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (NoSuchMethodException exception) { + return null; + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Failed to allocate test instance of " + type.getName(), exception); + } + } + + @Nonnull + private static T constructWithoutInvokingConstructor(@Nonnull Class type) { + try { + return OBJENESIS.newInstance(type); + } catch (RuntimeException exception) { + throw new AssertionError("Failed to allocate test instance of " + type.getName(), exception); + } + } + + private static void setField(@Nonnull Object target, + @Nonnull Class owner, + @Nonnull String name, + @Nonnull Object value) { + try { + Field field = owner.getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } catch (ReflectiveOperationException exception) { + throw new AssertionError("Failed to set test field " + owner.getName() + "." + name, exception); + } + } +} diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index d25707d7..f2fcb493 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -97,16 +97,9 @@ hytaleTools { manifestServerVersion = property("hytale_version") as String manifestDependencies = impulseManifestDependencies - subPlugin ( - "ImpulseControl", - "dev.hytalemodding.impulse.core.internal.modules.control.ControlModule", - false, /* disabledByDefault */ - false /* includeAssetPack */ - ) - subPlugin ( "ImpulsePhysicsEntity", - "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntitySubPlugin", + "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityModule", false, /* disabledByDefault */ false /* includeAssetPack */ ) @@ -142,7 +135,6 @@ tasks.named("updatePluginManifest") { subPlugins.firstOrNull { it["Name"] == "ImpulsePhysicsEntity" } ?.mergeLoadBefore(mapOf( - "HytaleModding:ImpulseControl" to "*", "HytaleModding:ImpulsePhysicsChunk" to "*" )) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java index 147f7ad3..194ca12b 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/commands/CleanCommand.java @@ -1,8 +1,5 @@ package dev.hytalemodding.impulse.core.internal.commands; -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; @@ -14,16 +11,13 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.internal.modules.control.components.PhysicsControlSessionComponent; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanup; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityProjectionCleanup; import dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityProjectionCleanup.Result; import dev.hytalemodding.impulse.core.internal.physics.PhysicsTopologyMutations; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; -import dev.hytalemodding.impulse.core.plugin.components.UuidComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; +import dev.hytalemodding.impulse.core.plugin.physics.PhysicsCleanupHooks; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.snapshots.PhysicsBodySnapshot; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -31,7 +25,6 @@ import java.util.UUID; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; -import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joml.Vector3d; @@ -77,19 +70,9 @@ private static void cleanAll(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { Result projectionCleanup = - PhysicsEntityProjectionCleanup.cleanAll(store, controllableTypeOrNull()); - - AtomicInteger removedSessions = new AtomicInteger(); - ComponentType controlSessionType = - controlSessionTypeOrNull(); - if (controlSessionType != null) { - store.forEachEntityParallel(controlSessionType, - (index, archetypeChunk, commandBuffer) -> { - removedSessions.incrementAndGet(); - commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), - controlSessionType); - }); - } + PhysicsEntityProjectionCleanup.cleanAll(store, + PhysicsCleanupHooks.detachableEntityMarkers()); + int removedPluginEntities = PhysicsCleanupHooks.cleanupEntityStore(store); CompletionStage reset = PhysicsThreading.callWhenBackendIdleOnWorldThread(world, @@ -98,7 +81,7 @@ private static void cleanAll(@Nonnull CommandContext context, reset.whenComplete((result, failure) -> sendCleanAllResult(world, context, projectionCleanup, - removedSessions.get(), + removedPluginEntities, result, failure)); } @@ -209,33 +192,11 @@ private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store selectedBodies.bodyUuids(), center, radiusSquared, - controllableTypeOrNull()); - - AtomicInteger removedSessions = new AtomicInteger(); - ComponentType controlSessionType = - controlSessionTypeOrNull(); - if (controlSessionType != null) { - store.forEachEntityParallel(controlSessionType, - (index, archetypeChunk, commandBuffer) -> { - PhysicsControlSessionComponent session = - archetypeChunk.getComponent(index, controlSessionType); - assert session != null; - if (!controlSessionSelected(commandBuffer, - archetypeChunk, - index, - session, - selectedBodies.bodyUuids(), - center, - radiusSquared)) { - return; - } - - removedSessions.incrementAndGet(); - PhysicsControlSessionCleanup.cleanup(store, session); - commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), - controlSessionType); - }); - } + PhysicsCleanupHooks.detachableEntityMarkers()); + int removedPluginEntities = PhysicsCleanupHooks.cleanupSelectedEntityStore(store, + selectedBodies.bodyUuids(), + center, + radiusSquared); int removedBodies = 0; for (UUID bodyUuid : selectedBodies.bodyUuids()) { @@ -244,7 +205,7 @@ private static RadiusCleanResult cleanSelectedBodies(@Nonnull Store } return new RadiusCleanResult(projectionCleanup, - removedSessions.get(), + removedPluginEntities, removedBodies); } @@ -326,52 +287,6 @@ private static SelectedBodies selectBodiesNear(@Nonnull Store stor return new SelectedBodies(bodyUuids); } - private static boolean controlSessionSelected( - @Nonnull CommandBuffer commandBuffer, - @Nonnull ArchetypeChunk archetypeChunk, - int index, - @Nonnull PhysicsControlSessionComponent session, - @Nonnull Set selectedBodyUuids, - @Nonnull Vector3d center, - double radiusSquared) { - if (containsBody(selectedBodyUuids, session.getBodyRef()) - || containsBody(selectedBodyUuids, session.getAnchorBodyRef()) - || entityWithinRadius(archetypeChunk, index, center, radiusSquared)) { - return true; - } - - Ref targetRef = session.getTargetRef(); - if (targetRef == null || !targetRef.isValid()) { - return false; - } - - TransformComponent targetTransform = - commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); - return targetTransform != null && positionWithinRadius(targetTransform.getPosition(), - center, - radiusSquared); - } - - @Nullable - private static ComponentType controlSessionTypeOrNull() { - return PhysicsControlSessionComponent.isComponentTypeRegistered() - ? PhysicsControlSessionComponent.getComponentType() - : null; - } - - @Nullable - private static ComponentType controllableTypeOrNull() { - return ImpulseControllableComponent.isComponentTypeRegistered() - ? ImpulseControllableComponent.getComponentType() - : null; - } - - private static boolean containsBody(@Nonnull Set bodyUuids, - @Nullable Ref bodyRef) { - UUID bodyUuid = rowUuid(bodyRef); - return bodyUuid != null && bodyUuids.contains(bodyUuid); - } - private record SelectedBodies(@Nonnull Set bodyUuids) { } @@ -380,24 +295,6 @@ private record RadiusCleanResult(@Nonnull Result projectionCleanup, int removedBodies) { } - @Nullable - private static UUID rowUuid(@Nullable Ref bodyRef) { - if (bodyRef == null || !bodyRef.isValid()) { - return null; - } - UuidComponent uuid = bodyRef.getStore().getComponent(bodyRef, UuidComponent.getComponentType()); - return uuid != null ? uuid.getUuid() : null; - } - - private static boolean entityWithinRadius(@Nonnull ArchetypeChunk archetypeChunk, - int index, - @Nonnull Vector3d center, - double radiusSquared) { - TransformComponent transform = - archetypeChunk.getComponent(index, TransformComponent.getComponentType()); - return transform != null && positionWithinRadius(transform.getPosition(), center, radiusSquared); - } - private static boolean positionWithinRadius(@Nonnull Vector3d position, @Nonnull Vector3d center, double radiusSquared) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/PhysicsVisualPoseMath.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/PhysicsVisualPoseMath.java index 539b54da..e0f61add 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/PhysicsVisualPoseMath.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/math/PhysicsVisualPoseMath.java @@ -7,7 +7,9 @@ import org.joml.Vector3f; /** - * Transform helpers for physics bodies with Hytale visual attachments. + * Various helpers to fix annoyances between origin convention of EntityStore entities and PhysicsStore ones + * + * TODO: this and related PhysicsDebugResource methods should be owned by the PhysicsEntity module */ public final class PhysicsVisualPoseMath { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlModule.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlModule.java deleted file mode 100644 index 64cb4a12..00000000 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/control/ControlModule.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.hytalemodding.impulse.core.internal.modules.control; - -import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.server.core.plugin.JavaPlugin; -import com.hypixel.hytale.server.core.plugin.JavaPluginInit; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControllableLifecycleSystem; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlRuntimeHolderSystem; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsControlSessionCleanupSystem; -import dev.hytalemodding.impulse.core.internal.modules.control.systems.PhysicsKinematicControlSystem; -import javax.annotation.Nonnull; - -/** - * Subplugin that enables Impulse kinematic control sessions. - */ -public final class ControlModule extends JavaPlugin { - - public ControlModule(@Nonnull JavaPluginInit init) { - super(init); - } - - @Override - protected void setup() { - ComponentRegistryProxy entityRegistry = getEntityStoreRegistry(); - ControlTypeRegistry.registerComponentTypes(entityRegistry); - entityRegistry.registerSystem(new PhysicsControlRuntimeHolderSystem()); - entityRegistry.registerSystem(new PhysicsControllableLifecycleSystem()); - entityRegistry.registerSystem(new PhysicsControlSessionCleanupSystem()); - entityRegistry.registerSystem(new PhysicsKinematicControlSystem()); - ControlLifecycle.enable(); - } - - @Override - protected void shutdown() { - ControlLifecycle.disable(); - ControlTypeRegistry.clearComponentTypes(); - } -} diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java similarity index 91% rename from impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java rename to impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java index 49c19152..b2d1a9d4 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntitySubPlugin.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityModule.java @@ -10,9 +10,9 @@ /** * Bundled subplugin that integrates authoritative PhysicsStore bodies with EntityStore entities. */ -public final class PhysicsEntitySubPlugin extends JavaPlugin { +public final class PhysicsEntityModule extends JavaPlugin { - public PhysicsEntitySubPlugin(@Nonnull JavaPluginInit init) { + public PhysicsEntityModule(@Nonnull JavaPluginInit init) { super(init); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java index 14fa3841..3cf3fe53 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanup.java @@ -12,6 +12,8 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; +import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -36,9 +38,18 @@ private PhysicsEntityProjectionCleanup() { @Nonnull public static Result cleanAll(@Nonnull Store store, @Nullable ComponentType> detachableMarkerType) { + return cleanAll(store, markerList(detachableMarkerType)); + } + + @Nonnull + public static Result cleanAll(@Nonnull Store store, + @Nonnull Collection>> + detachableMarkerTypes) { if (!PhysicsEntityAttachments.isAvailable()) { return Result.skippedResult(); } + Collection>> checkedMarkerTypes = + Objects.requireNonNull(detachableMarkerTypes, "detachableMarkerTypes"); AtomicIntegerArray counters = new AtomicIntegerArray(COUNTERS); ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); @@ -56,7 +67,7 @@ public static Result cleanAll(@Nonnull Store store, commandBuffer, archetypeChunk.getReferenceTo(index), attachmentType, - detachableMarkerType, + checkedMarkerTypes, attachment); }); @@ -77,12 +88,28 @@ public static Result cleanSelected(@Nonnull Store store, @Nonnull Vector3d center, double radiusSquared, @Nullable ComponentType> detachableMarkerType) { + return cleanSelected(store, + selectedBodyUuids, + center, + radiusSquared, + markerList(detachableMarkerType)); + } + + @Nonnull + public static Result cleanSelected(@Nonnull Store store, + @Nonnull Set selectedBodyUuids, + @Nonnull Vector3d center, + double radiusSquared, + @Nonnull Collection>> + detachableMarkerTypes) { if (!PhysicsEntityAttachments.isAvailable()) { return Result.skippedResult(); } Set checkedBodyUuids = Objects.requireNonNull(selectedBodyUuids, "selectedBodyUuids"); Vector3d checkedCenter = Objects.requireNonNull(center, "center"); + Collection>> checkedMarkerTypes = + Objects.requireNonNull(detachableMarkerTypes, "detachableMarkerTypes"); AtomicIntegerArray counters = new AtomicIntegerArray(COUNTERS); ComponentType attachmentType = BodyAttachmentComponent.getComponentType(); @@ -100,7 +127,7 @@ public static Result cleanSelected(@Nonnull Store store, commandBuffer, archetypeChunk.getReferenceTo(index), attachmentType, - detachableMarkerType, + checkedMarkerTypes, attachment); }); @@ -121,7 +148,8 @@ private static void cleanAttachedEntity( @Nonnull CommandBuffer commandBuffer, @Nonnull Ref entityRef, @Nonnull ComponentType attachmentType, - @Nullable ComponentType> detachableMarkerType, + @Nonnull Collection>> + detachableMarkerTypes, @Nonnull BodyAttachmentComponent attachment) { if (attachment.shouldRemoveEntityWhenBodyMissing()) { counters.incrementAndGet(REMOVED_ATTACHMENT_ENTITIES); @@ -129,18 +157,27 @@ private static void cleanAttachedEntity( return; } counters.incrementAndGet(DETACHED_EXTERNAL_ATTACHMENTS); - removeMarker(commandBuffer, entityRef, detachableMarkerType); + removeMarkers(commandBuffer, entityRef, detachableMarkerTypes); commandBuffer.removeComponent(entityRef, attachmentType); } - private static void removeMarker(@Nonnull CommandBuffer commandBuffer, + private static void removeMarkers(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref entityRef, - @Nullable ComponentType> markerType) { - if (markerType != null && commandBuffer.getComponent(entityRef, markerType) != null) { - commandBuffer.removeComponent(entityRef, markerType); + @Nonnull Collection>> + markerTypes) { + for (ComponentType> markerType : markerTypes) { + if (commandBuffer.getComponent(entityRef, markerType) != null) { + commandBuffer.removeComponent(entityRef, markerType); + } } } + @Nonnull + private static List>> markerList( + @Nullable ComponentType> markerType) { + return markerType != null ? List.of(markerType) : List.of(); + } + private static void removeOrphanProxy(@Nonnull AtomicIntegerArray counters, @Nonnull CommandBuffer commandBuffer, @Nonnull Ref entityRef) { diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java index 9d8c7ead..7e205dad 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/commands/VisualMaterializationSettingsCommand.java @@ -20,6 +20,8 @@ import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +// NOTE: there's already a system in hytale that handles Entities Materialization/Visibility, we +// should probably integrate with that public class VisualMaterializationSettingsCommand extends AbstractAsyncPlayerCommand { private final OptionalArg enabledArg = this.withOptionalArg( diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java index 657d5f6a..8a4c545c 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsStoreRowCleanup.java @@ -6,7 +6,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; import dev.hytalemodding.impulse.api.BackendId; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; -import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.internal.resources.BackendBodyHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendJointHandle; import dev.hytalemodding.impulse.core.internal.resources.BackendSpaceHandle; @@ -161,7 +160,6 @@ public static void clearBodyCopiedState(@Nonnull Store store, for (BodyEntityRemoval removal : removals) { Objects.requireNonNull(removal, "removal"); bodyUuids.add(removal.bodyUuid()); - PhysicsControlRuntimeStates.clearControlled(removal.bodyRef()); } store.getResource(PhysicsSnapshotResource.getResourceType()).removeBodies(bodyUuids); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java index 589a4cfe..468b0ecf 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/physics/PhysicsTopologyMutations.java @@ -10,7 +10,6 @@ import dev.hytalemodding.impulse.core.internal.resources.PhysicsSnapshotResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsSpaceCompatibilityIndexResource; import dev.hytalemodding.impulse.core.internal.resources.PhysicsRuntimeResetResult; -import dev.hytalemodding.impulse.core.internal.modules.control.PhysicsControlRuntimeStates; import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreRowCleanup.BodyEntityRemoval; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsThreading; import dev.hytalemodding.impulse.core.plugin.components.BodyComponent; @@ -48,7 +47,6 @@ public static PhysicsRuntimeResetResult clearBodiesKeepingSpaces( @Nonnull Store store) { PhysicsThreading.requireBackendIdle(store, "clear PhysicsStore body entities"); PhysicsRuntimeResource runtime = store.getResource(PhysicsRuntimeResource.getResourceType()); - PhysicsControlRuntimeStates.clear(store); TopologyCounts removed = countBackendTopology(runtime); List removals = collectRows(store, null, null, null, null); runtime.destroyBackendBindings(); diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/StepSubmissionSystem.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/StepSubmissionSystem.java index 18c04fba..3e3e4bef 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/StepSubmissionSystem.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/systems/step/StepSubmissionSystem.java @@ -137,6 +137,7 @@ private static CompletedStep runOwnerStep(@Nonnull PhysicsRuntimeResource runtim for (RuntimeStepBinding binding : bindings) { counters.spaceCount++; for (int step = 0; step < steps; step++) { + //noinspection resource binding.backendRuntime().step(binding.spaceHandle().value(), stepDt); counters.substeps++; } @@ -179,6 +180,7 @@ private static PhysicsSnapshotResource.CompactSnapshot collectOwnerLaneSnapshot( PhysicsSnapshotResource.CompactSnapshotBuilder snapshot = PhysicsSnapshotResource.compactBuilder(runtimeBodyHandleCount(runtime, bindings)); for (RuntimeStepBinding binding : bindings) { + //noinspection resource binding.backendRuntime().snapshotBodies(binding.spaceHandle().value(), bodyIds -> runtime.forEachBodyHandle(binding.backendId(), binding.spaceHandle(), @@ -298,6 +300,7 @@ private static void collectOwnerLaneSnapshot(@Nonnull PhysicsRuntimeResource run sleeping); } + @SuppressWarnings("resource") @Nonnull private static StepBackendEvents collectOwnerLaneBackendEvents( @Nonnull PhysicsRuntimeResource runtime, @@ -464,6 +467,7 @@ private static PhysicsStepPhaseStats collectStepPhaseStats( StepPhaseStatsCapture capture = new StepPhaseStatsCapture(); for (RuntimeStepBinding binding : bindings) { capture.reset(); + //noinspection resource binding.backendRuntime().stepPhaseStats(binding.spaceHandle().value(), capture); stats.add(capture.value()); } diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java index 626a5b88..a993da0f 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/BodyAttachmentComponent.java @@ -23,6 +23,8 @@ /** * EntityStore projection relationship to an authoritative PhysicsStore body. + * + * TODO: probably refactor the name */ public class BodyAttachmentComponent implements Component { @@ -86,6 +88,8 @@ public class BodyAttachmentComponent implements Component { @Getter private final Quaternionf localRotationOffset = new Quaternionf(); + // TODO: static/final? + @Getter private float visualOriginOffsetY = USE_BODY_VISUAL_ORIGIN_OFFSET_Y; public BodyAttachmentComponent() { @@ -207,10 +211,6 @@ public AttachmentLifecycle getLifecycle() { return lifecycle; } - public float getVisualOriginOffsetY() { - return visualOriginOffsetY; - } - public void setVisualOriginOffsetY(float visualOriginOffsetY) { this.visualOriginOffsetY = normalizeVisualOriginOffsetY(visualOriginOffsetY); } @@ -257,12 +257,18 @@ private static float normalizeVisualOriginOffsetY(float value) { return normalizeVisualOriginOffsetY(Float.valueOf(value)); } + // TODO: rework and think deeply if we can just infer ownership based public enum TransformAuthority { BODY, CONTROLLER, ENTITY_KINEMATIC } + /** + * @deprecated AttachmentLifecycle is basically legacy stuff, the lifecycle is trivial given the + * relationship with another Store or not. + */ + @Deprecated(forRemoval = true) public enum AttachmentLifecycle { EXTERNAL_ENTITY, IMPULSE_OWNED_VISUAL, diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java index e61313df..3e600ba1 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/modules/physicsentity/components/GeneratedVisualProxyComponent.java @@ -9,7 +9,12 @@ /** * Durable ownership marker for Impulse-generated visual proxy entities. + * + * @deprecated This follows the old physics entity ownership model, the TransformComponent of the + * entities spawned with BodyAttachmentComponent is by definition owned by the bound PhysicsStore + * entity anyway */ +@Deprecated (forRemoval = true) public final class GeneratedVisualProxyComponent implements Component { @Nonnull diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java index 0bdc7178..add06457 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/package-info.java @@ -3,6 +3,6 @@ * *

        Types under this package tree are the preferred import surface for * third-party Hytale plugins. Backend-neutral physics contracts remain in the - * {@code impulse-backend-api} module.

        + * {@code impulse-backend-api} artifact.

        */ package dev.hytalemodding.impulse.core.plugin; diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsCleanupHooks.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsCleanupHooks.java new file mode 100644 index 00000000..7b7135a7 --- /dev/null +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/plugin/physics/PhysicsCleanupHooks.java @@ -0,0 +1,179 @@ +package dev.hytalemodding.impulse.core.plugin.physics; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; +import dev.hytalemodding.impulse.core.internal.physics.PhysicsStoreCleanupHooks; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.joml.Vector3d; + +/** + * Cleanup extension points for optional Impulse plugins that own runtime state. + */ +public final class PhysicsCleanupHooks { + + @Nonnull + private static final Set ENTITY_STORE_CLEANUPS = + new CopyOnWriteArraySet<>(); + @Nonnull + private static final Set SELECTED_ENTITY_STORE_CLEANUPS = + new CopyOnWriteArraySet<>(); + @Nonnull + private static final Set>> + DETACHABLE_ENTITY_MARKERS = new CopyOnWriteArraySet<>(); + @Nonnull + private static final ConcurrentMap + BODY_ROW_CLEANUP_ADAPTERS = new ConcurrentHashMap<>(); + + private PhysicsCleanupHooks() { + } + + public static void registerEntityStoreCleanup(@Nonnull EntityStoreCleanup cleanup) { + ENTITY_STORE_CLEANUPS.add(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void unregisterEntityStoreCleanup(@Nonnull EntityStoreCleanup cleanup) { + ENTITY_STORE_CLEANUPS.remove(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static int cleanupEntityStore(@Nonnull Store store) { + Objects.requireNonNull(store, "store"); + RuntimeException failure = null; + int removed = 0; + for (EntityStoreCleanup cleanup : ENTITY_STORE_CLEANUPS) { + try { + removed += cleanup.cleanup(store); + } catch (RuntimeException exception) { + failure = append(failure, exception); + } + } + throwIfFailed(failure); + return removed; + } + + public static void registerSelectedEntityStoreCleanup( + @Nonnull SelectedEntityStoreCleanup cleanup) { + SELECTED_ENTITY_STORE_CLEANUPS.add(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static void unregisterSelectedEntityStoreCleanup( + @Nonnull SelectedEntityStoreCleanup cleanup) { + SELECTED_ENTITY_STORE_CLEANUPS.remove(Objects.requireNonNull(cleanup, "cleanup")); + } + + public static int cleanupSelectedEntityStore(@Nonnull Store store, + @Nonnull Set selectedBodyUuids, + @Nonnull Vector3d center, + double radiusSquared) { + Objects.requireNonNull(store, "store"); + Objects.requireNonNull(selectedBodyUuids, "selectedBodyUuids"); + Objects.requireNonNull(center, "center"); + RuntimeException failure = null; + int removed = 0; + for (SelectedEntityStoreCleanup cleanup : SELECTED_ENTITY_STORE_CLEANUPS) { + try { + removed += cleanup.cleanup(store, selectedBodyUuids, center, radiusSquared); + } catch (RuntimeException exception) { + failure = append(failure, exception); + } + } + throwIfFailed(failure); + return removed; + } + + public static void registerDetachableEntityMarker( + @Nonnull ComponentType> markerType) { + DETACHABLE_ENTITY_MARKERS.add(Objects.requireNonNull(markerType, "markerType")); + } + + public static void unregisterDetachableEntityMarker( + @Nonnull ComponentType> markerType) { + DETACHABLE_ENTITY_MARKERS.remove(Objects.requireNonNull(markerType, "markerType")); + } + + @Nonnull + public static List>> + detachableEntityMarkers() { + return List.copyOf(DETACHABLE_ENTITY_MARKERS); + } + + public static void registerBodyRuntimeCleanup( + @Nonnull Consumer> cleanup) { + PhysicsStoreCleanupHooks.registerBodyRuntimeCleanup(cleanup); + } + + public static void unregisterBodyRuntimeCleanup( + @Nonnull Consumer> cleanup) { + PhysicsStoreCleanupHooks.unregisterBodyRuntimeCleanup(cleanup); + } + + public static void registerBodyRowCleanup(@Nonnull BodyRowCleanup cleanup) { + BodyRowCleanup checkedCleanup = Objects.requireNonNull(cleanup, "cleanup"); + PhysicsStoreCleanupHooks.BodyRowCleanup adapter = + (store, bodyUuid, bodyRef) -> checkedCleanup.cleanup(store, bodyUuid, bodyRef); + PhysicsStoreCleanupHooks.BodyRowCleanup previous = + BODY_ROW_CLEANUP_ADAPTERS.putIfAbsent(checkedCleanup, adapter); + if (previous == null) { + PhysicsStoreCleanupHooks.registerBodyRowCleanup(adapter); + } + } + + public static void unregisterBodyRowCleanup(@Nonnull BodyRowCleanup cleanup) { + PhysicsStoreCleanupHooks.BodyRowCleanup adapter = + BODY_ROW_CLEANUP_ADAPTERS.remove(Objects.requireNonNull(cleanup, "cleanup")); + if (adapter != null) { + PhysicsStoreCleanupHooks.unregisterBodyRowCleanup(adapter); + } + } + + @Nullable + private static RuntimeException append(@Nullable RuntimeException failure, + @Nonnull RuntimeException exception) { + if (failure == null) { + return exception; + } + failure.addSuppressed(exception); + return failure; + } + + private static void throwIfFailed(@Nullable RuntimeException failure) { + if (failure != null) { + throw failure; + } + } + + @FunctionalInterface + public interface EntityStoreCleanup { + + int cleanup(@Nonnull Store store); + } + + @FunctionalInterface + public interface SelectedEntityStoreCleanup { + + int cleanup(@Nonnull Store store, + @Nonnull Set selectedBodyUuids, + @Nonnull Vector3d center, + double radiusSquared); + } + + @FunctionalInterface + public interface BodyRowCleanup { + + void cleanup(@Nonnull Store store, + @Nonnull UUID bodyUuid, + @Nonnull Ref bodyRef); + } +} diff --git a/impulse-core/src/module-info/module-info.java b/impulse-core/src/module-info/module-info.java index 2a694769..f66ffa73 100644 --- a/impulse-core/src/module-info/module-info.java +++ b/impulse-core/src/module-info/module-info.java @@ -6,7 +6,6 @@ exports dev.hytalemodding.impulse.core.plugin.codec; exports dev.hytalemodding.impulse.core.plugin.components; exports dev.hytalemodding.impulse.core.plugin.events; - exports dev.hytalemodding.impulse.core.plugin.modules.control; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components; exports dev.hytalemodding.impulse.core.plugin.modules.physicsentity.settings; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulseBackendRegistryPluginBackendSelectionTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulseBackendRegistryPluginBackendSelectionTest.java index ad6a4d11..c1dbc410 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulseBackendRegistryPluginBackendSelectionTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/ImpulseBackendRegistryPluginBackendSelectionTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dev.hytalemodding.impulse.api.BackendId; -import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntime; import dev.hytalemodding.impulse.api.runtime.PhysicsBackendRuntimeProvider; import dev.hytalemodding.impulse.api.testsupport.FakePhysicsBackendRuntimeProvider; import java.io.IOException; diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java index ee0fee39..a53f45ac 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java @@ -1,6 +1,7 @@ package dev.hytalemodding.impulse.core.internal.modules; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -31,11 +32,12 @@ void generatedManifestSubPluginsSupportHytalePendingLoadInheritance() throws IOE for (PluginManifest subPlugin : prepared) { assertTrue(subPlugin.getDependencies().containsKey(parentId)); } - assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulseControl"); + assertMissingSubPlugin(parent, "ImpulseControl"); + assertSubPluginDoesNotLoadBefore(parent, "ImpulsePhysicsEntity", "ImpulseControl"); assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulsePhysicsChunk"); assertSubPluginMain(parent, "ImpulsePhysicsEntity", - "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntitySubPlugin"); + "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityModule"); assertSubPluginMain(parent, "ImpulsePhysicsChunk", "dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkSubPlugin"); @@ -48,8 +50,8 @@ void preparesEverySubPluginManifestForDynamicLoad() { "dev.hytalemodding.impulse.core.ImpulsePlugin", List.of( manifest(null, - "ImpulseControl", - "dev.hytalemodding.impulse.core.internal.modules.control.ControlModule", + "FixtureSubPlugin", + "example.FixtureSubPlugin", List.of(), false)), false); @@ -58,7 +60,7 @@ void preparesEverySubPluginManifestForDynamicLoad() { ImpulseSubPluginRegistration.prepareSubPluginManifests(parent); assertEquals(1, prepared.size()); - assertPreparedSubPlugin(prepared.getFirst(), "ImpulseControl", false); + assertPreparedSubPlugin(prepared.getFirst(), "FixtureSubPlugin", false); } private static void assertPreparedSubPlugin(PluginManifest manifest, @@ -86,6 +88,28 @@ private static void assertSubPluginLoadsBefore(@Nonnull PluginManifest parent, throw new AssertionError("Missing subplugin " + subPluginName); } + private static void assertSubPluginDoesNotLoadBefore(@Nonnull PluginManifest parent, + @Nonnull String subPluginName, + @Nonnull String dependencyName) { + PluginIdentifier dependencyId = new PluginIdentifier("HytaleModding", dependencyName); + for (PluginManifest subPlugin : parent.getSubPlugins()) { + if (subPluginName.equals(subPlugin.getName())) { + assertFalse(subPlugin.getLoadBefore().containsKey(dependencyId), + subPluginName + " should not order against " + dependencyName); + return; + } + } + throw new AssertionError("Missing subplugin " + subPluginName); + } + + private static void assertMissingSubPlugin(@Nonnull PluginManifest parent, + @Nonnull String subPluginName) { + for (PluginManifest subPlugin : parent.getSubPlugins()) { + assertFalse(subPluginName.equals(subPlugin.getName()), + "Unexpected bundled subplugin " + subPluginName); + } + } + private static void assertSubPluginMain(@Nonnull PluginManifest parent, @Nonnull String subPluginName, @Nonnull String expectedMain) { @@ -128,4 +152,5 @@ private static PluginManifest decodeGeneratedManifest() throws IOException { return PluginManifest.CODEC.decodeJson(reader, new ExtraInfo()); } } + } diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java index 4ae6307a..109bf5a2 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/physicsentity/PhysicsEntityProjectionCleanupTest.java @@ -19,6 +19,7 @@ import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.GeneratedVisualProxyComponent; import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; @@ -53,7 +54,7 @@ void cleanAllOwnsAttachmentAndProxyCleanupResult() { Ref orphanProxy = addOrphanProxy(store); PhysicsEntityProjectionCleanup.Result result = - PhysicsEntityProjectionCleanup.cleanAll(store, null); + PhysicsEntityProjectionCleanup.cleanAll(store, List.of()); assertFalse(result.skipped()); assertEquals(1, result.removedAttachmentEntities()); @@ -87,7 +88,7 @@ void cleanSelectedFiltersAttachmentsByBody() { Set.of(selectedBodyUuid), new org.joml.Vector3d(), 1.0, - null); + List.of()); assertEquals(1, result.detachedExternalAttachments()); assertEquals(0, result.removedOrphanVisualEntities()); diff --git a/impulse-examples/build.gradle.kts b/impulse-examples/build.gradle.kts index 62190852..02b09c57 100644 --- a/impulse-examples/build.gradle.kts +++ b/impulse-examples/build.gradle.kts @@ -9,8 +9,10 @@ version = rootProject.version dependencies { implementation(project(":impulse-backends:api")) compileOnly(project(":impulse-core")) + compileOnly(project(":impulse-builtins:control")) compileOnly(project(":impulse-early-plugin")) testImplementation(project(":impulse-core")) + testImplementation(project(":impulse-builtins:control")) testImplementation(project(":impulse-early-plugin")) testImplementation(testFixtures(project(":impulse-backends:api"))) testImplementation(libs.objenesis) @@ -43,6 +45,7 @@ hytaleTools { manifestServerVersion = property("hytale_version") as String manifestDependencies = listOf( "HytaleModding:Impulse=*", + "HytaleModding:ImpulseControl=*", "HytaleModding:ImpulsePhysicsEntity=*", "HytaleModding:ImpulsePhysicsChunk=*" ).joinToString(",") diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java index f17c7a9b..926e46ed 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/DropCommand.java @@ -17,8 +17,8 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.universe.world.storage.PhysicsStore; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java index 8384f82e..d6815cef 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/GrabCommand.java @@ -19,9 +19,9 @@ import dev.hytalemodding.impulse.api.PhysicsBodyType; import dev.hytalemodding.impulse.api.PhysicsCollisionFilters; import dev.hytalemodding.impulse.api.SpaceId; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.builtin.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.modules.physicschunk.PhysicsChunkCollision; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodyEntities; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsJointEntities; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ReleaseCommand.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ReleaseCommand.java index 7a985287..d6f17d94 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ReleaseCommand.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/commands/ReleaseCommand.java @@ -8,7 +8,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.builtin.control.PhysicsControlSessions; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; diff --git a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java index e683e876..fe0894fb 100644 --- a/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java +++ b/impulse-examples/src/main/java/dev/hytalemodding/impulse/examples/utils/ExamplePhysicsUtils.java @@ -14,8 +14,8 @@ import dev.hytalemodding.impulse.api.SpaceId; import dev.hytalemodding.impulse.core.plugin.components.BodyCommandComponent; import dev.hytalemodding.impulse.core.plugin.components.JointComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.ImpulseControllableComponent; -import dev.hytalemodding.impulse.core.plugin.modules.control.PhysicsControlSessions; +import dev.hytalemodding.impulse.builtin.control.ImpulseControllableComponent; +import dev.hytalemodding.impulse.builtin.control.PhysicsControlSessions; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.PhysicsEntityAttachments; import dev.hytalemodding.impulse.core.plugin.modules.physicsentity.components.BodyAttachmentComponent; import dev.hytalemodding.impulse.core.plugin.physics.PhysicsBodies; diff --git a/settings.gradle.kts b/settings.gradle.kts index c8eac1d2..be4b1eb3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,7 @@ include(":impulse-backends:api") include(":impulse-backends:native-loader") include(":impulse-backends:jolt") include(":impulse-backends:rapier") +include(":impulse-builtins:control") include("impulse-core") include("impulse-examples") include("impulse-early-plugin") From afad4a738c098f7ab74d4e7ccd4b45c824d7b09d Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 25 Jun 2026 12:50:17 +0200 Subject: [PATCH 532/534] refactor(core): align projection ref comparison Signed-off-by: Blovien --- .../resources/PhysicsProjectionIndexResource.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java index d08cdbef..a323e8da 100644 --- a/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java +++ b/impulse-core/src/main/java/dev/hytalemodding/impulse/core/internal/resources/PhysicsProjectionIndexResource.java @@ -426,11 +426,9 @@ private Ref liveGeneratedVisualProxy(@Nonnull Ref bod private static boolean sameRef(@Nullable Ref first, @Nullable Ref second) { return first == second - || (first != null - && second != null - && first.getStore() != null - && first.getStore() == second.getStore() - && first.getIndex() == second.getIndex()); + || first != null && second != null + && first.getStore() == second.getStore() + && first.getIndex() == second.getIndex(); } private record BodyAttachmentRefs(@Nonnull Ref bodyRef, From a1621b2db5c378be967dd6c3c8e87e12050ddae6 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 25 Jun 2026 13:09:38 +0200 Subject: [PATCH 533/534] build(builtins): shadow control into impulse plugin Signed-off-by: Blovien --- build.gradle.kts | 8 +++- impulse-builtins/control/.gitignore | 1 - impulse-builtins/control/build.gradle.kts | 47 ++++++++++--------- .../control/ImpulseControlManifestTest.java | 28 ++++------- impulse-core/build.gradle.kts | 14 ++++++ ...kendRegistrySubPluginRegistrationTest.java | 6 ++- .../examples/ImpulseExamplesManifestTest.java | 42 +++++++++++++++++ 7 files changed, 100 insertions(+), 46 deletions(-) delete mode 100644 impulse-builtins/control/.gitignore create mode 100644 impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/ImpulseExamplesManifestTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 81869110..90b82f5a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,11 +18,11 @@ val coreOnlyWorkspace = providers.gradleProperty("impulse.coreOnlyWorkspace") .map(String::toBoolean) .orElse(false) val coreModProjects = listOf(":impulse-core") -val builtinModProjects = listOf(":impulse-builtins:control") +val shadowedBuiltinProjects = listOf(":impulse-builtins:control") val workspaceModProjects = if (coreOnlyWorkspace.get()) { coreModProjects } else { - listOf(":impulse-examples") + builtinModProjects + coreModProjects + listOf(":impulse-examples") + coreModProjects } hytaleWorkspace { @@ -177,6 +177,10 @@ gradle.projectsEvaluated { val sourceSets = project(path).extensions.getByType() sourceSets.named("main").get().runtimeClasspath }.toMutableList() + shadowedBuiltinProjects.forEach { path -> + val sourceSets = project(path).extensions.getByType() + toolRuntimeClasspaths.add(sourceSets.named("main").get().runtimeClasspath) + } if (physicsStoreEarlyPluginEnabled.get()) { val sourceSets = project(":impulse-early-plugin") .extensions.getByType() diff --git a/impulse-builtins/control/.gitignore b/impulse-builtins/control/.gitignore deleted file mode 100644 index 8c25fdb9..00000000 --- a/impulse-builtins/control/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/src/main/resources/manifest.json diff --git a/impulse-builtins/control/build.gradle.kts b/impulse-builtins/control/build.gradle.kts index ac94c251..93c8cd19 100644 --- a/impulse-builtins/control/build.gradle.kts +++ b/impulse-builtins/control/build.gradle.kts @@ -1,49 +1,54 @@ +import org.gradle.api.tasks.Delete +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.testing.Test plugins { - id("com.azuredoom.hytale-tools") + id("java-library") } version = rootProject.version +evaluationDependsOn(":impulse-core") + +val coreMain = project(":impulse-core") + .extensions + .getByType() + .named("main") + .get() + dependencies { - implementation(project(":impulse-backends:api")) - compileOnly(project(":impulse-core")) + api(project(":impulse-backends:api")) + compileOnly(coreMain.output) + compileOnly(files(coreMain.compileClasspath)) compileOnly(project(":impulse-early-plugin")) testImplementation(project(":impulse-core")) testImplementation(project(":impulse-early-plugin")) testImplementation(testFixtures(project(":impulse-backends:api"))) testImplementation(libs.objenesis) testRuntimeOnly(project(":impulse-early-plugin")) - testCompileOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") - testRuntimeOnly("com.hypixel.hytale:Server:${property("hytale_version") as String}") + testCompileOnly(files(coreMain.compileClasspath)) + testRuntimeOnly(files(coreMain.compileClasspath)) compileOnly(libs.lombok) annotationProcessor(libs.lombok) } -tasks.compileJava { - dependsOn(tasks.named("downloadAssetsZip")) +tasks.named("compileJava") { + dependsOn(":impulse-core:compileJava") } tasks.withType().configureEach { jvmArgs("-Djava.util.logging.manager=com.hypixel.hytale.logger.backend.HytaleLogManager") } -val downloadAssetsZip = tasks.named("downloadAssetsZip") +val removeStandaloneManifest by tasks.registering(Delete::class) { + delete(layout.buildDirectory.file("resources/main/manifest.json")) +} -project(":impulse-core").tasks.named("compileJava") { - mustRunAfter(downloadAssetsZip) +tasks.named("processResources") { + dependsOn(removeStandaloneManifest) } -hytaleTools { - modId = property("mod_name") as String + "Control" - mainClass = "dev.hytalemodding.impulse.builtin.control.ImpulseControlPlugin" - modCredits = property("mod_credits") as String - modUrl = property("mod_website") as String - modDescription = "Official kinematic-control builtin for Impulse" - manifestServerVersion = property("hytale_version") as String - manifestDependencies = listOf( - "HytaleModding:Impulse=*", - "HytaleModding:ImpulsePhysicsEntity=*" - ).joinToString(",") +tasks.named("jar") { + exclude("manifest.json") } diff --git a/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java index c6ee70f4..eb4b26cb 100644 --- a/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java +++ b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java @@ -1,12 +1,9 @@ package dev.hytalemodding.impulse.builtin.control; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import com.hypixel.hytale.codec.ExtraInfo; import com.hypixel.hytale.codec.util.RawJsonReader; -import com.hypixel.hytale.common.plugin.PluginIdentifier; import com.hypixel.hytale.common.plugin.PluginManifest; import java.io.IOException; import java.io.InputStream; @@ -17,27 +14,18 @@ class ImpulseControlManifestTest { @Test - void generatedManifestDeclaresStandaloneControlPlugin() throws IOException { - PluginManifest manifest = decodeGeneratedManifest(); - - assertEquals("HytaleModding", manifest.getGroup()); - assertEquals("ImpulseControl", manifest.getName()); - assertEquals("dev.hytalemodding.impulse.builtin.control.ImpulseControlPlugin", - manifest.getMain()); - assertTrue(manifest.getDependencies() - .containsKey(new PluginIdentifier("HytaleModding", "Impulse"))); - assertTrue(manifest.getDependencies() - .containsKey(new PluginIdentifier("HytaleModding", "ImpulsePhysicsEntity"))); - } - - private static PluginManifest decodeGeneratedManifest() throws IOException { + void controlBuiltinDoesNotDeclareStandalonePluginManifest() throws IOException { InputStream stream = ImpulseControlManifestTest.class .getClassLoader() .getResourceAsStream("manifest.json"); - assertNotNull(stream); + if (stream == null) { + return; + } + try (InputStreamReader input = new InputStreamReader(stream, StandardCharsets.UTF_8); RawJsonReader reader = new RawJsonReader(input, RawJsonReader.READ_BUFFER.get())) { - return PluginManifest.CODEC.decodeJson(reader, new ExtraInfo()); + PluginManifest manifest = PluginManifest.CODEC.decodeJson(reader, new ExtraInfo()); + assertNotEquals("ImpulseControl", manifest.getName()); } } } diff --git a/impulse-core/build.gradle.kts b/impulse-core/build.gradle.kts index f2fcb493..4b9109c7 100644 --- a/impulse-core/build.gradle.kts +++ b/impulse-core/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { } val impulseApiJar = project(":impulse-backends:api").tasks.named("jar") +val controlBuiltinProject = project(":impulse-builtins:control") tasks.named("compileJava") { doFirst { @@ -79,9 +80,14 @@ tasks.named("classes") { tasks.named("jar") { dependsOn(compileCoreModuleInfo) + dependsOn(":impulse-builtins:control:classes") from(compileCoreModuleInfo.flatMap { it.destinationDirectory }) { include("module-info.class") } + from(controlBuiltinProject.layout.buildDirectory.dir("classes/java/main")) + from(controlBuiltinProject.layout.buildDirectory.dir("resources/main")) { + exclude("manifest.json") + } } tasks.withType().configureEach { @@ -104,6 +110,13 @@ hytaleTools { false /* includeAssetPack */ ) + subPlugin ( + "ImpulseControl", + "dev.hytalemodding.impulse.builtin.control.ImpulseControlPlugin", + false, /* disabledByDefault */ + false /* includeAssetPack */ + ) + subPlugin ( "ImpulsePhysicsChunk", "dev.hytalemodding.impulse.core.internal.modules.physicschunk.PhysicsChunkSubPlugin", @@ -135,6 +148,7 @@ tasks.named("updatePluginManifest") { subPlugins.firstOrNull { it["Name"] == "ImpulsePhysicsEntity" } ?.mergeLoadBefore(mapOf( + "HytaleModding:ImpulseControl" to "*", "HytaleModding:ImpulsePhysicsChunk" to "*" )) diff --git a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java index a53f45ac..10744a98 100644 --- a/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java +++ b/impulse-core/src/test/java/dev/hytalemodding/impulse/core/internal/modules/ImpulseBackendRegistrySubPluginRegistrationTest.java @@ -32,9 +32,11 @@ void generatedManifestSubPluginsSupportHytalePendingLoadInheritance() throws IOE for (PluginManifest subPlugin : prepared) { assertTrue(subPlugin.getDependencies().containsKey(parentId)); } - assertMissingSubPlugin(parent, "ImpulseControl"); - assertSubPluginDoesNotLoadBefore(parent, "ImpulsePhysicsEntity", "ImpulseControl"); + assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulseControl"); assertSubPluginLoadsBefore(parent, "ImpulsePhysicsEntity", "ImpulsePhysicsChunk"); + assertSubPluginMain(parent, + "ImpulseControl", + "dev.hytalemodding.impulse.builtin.control.ImpulseControlPlugin"); assertSubPluginMain(parent, "ImpulsePhysicsEntity", "dev.hytalemodding.impulse.core.internal.modules.physicsentity.PhysicsEntityModule"); diff --git a/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/ImpulseExamplesManifestTest.java b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/ImpulseExamplesManifestTest.java new file mode 100644 index 00000000..b50e5012 --- /dev/null +++ b/impulse-examples/src/test/java/dev/hytalemodding/impulse/examples/ImpulseExamplesManifestTest.java @@ -0,0 +1,42 @@ +package dev.hytalemodding.impulse.examples; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class ImpulseExamplesManifestTest { + + @Test + void generatedManifestDependsOnBundledControlSubPlugin() throws IOException { + PluginManifest manifest = decodeGeneratedManifest(); + + assertTrue(manifest.getDependencies() + .containsKey(new PluginIdentifier("HytaleModding", "Impulse"))); + assertTrue(manifest.getDependencies() + .containsKey(new PluginIdentifier("HytaleModding", "ImpulsePhysicsEntity"))); + assertTrue(manifest.getDependencies() + .containsKey(new PluginIdentifier("HytaleModding", "ImpulsePhysicsChunk"))); + assertTrue(manifest.getDependencies() + .containsKey(new PluginIdentifier("HytaleModding", "ImpulseControl"))); + } + + private static PluginManifest decodeGeneratedManifest() throws IOException { + InputStream stream = ImpulseExamplesManifestTest.class + .getClassLoader() + .getResourceAsStream("manifest.json"); + assertNotNull(stream); + try (InputStreamReader input = new InputStreamReader(stream, StandardCharsets.UTF_8); + RawJsonReader reader = new RawJsonReader(input, RawJsonReader.READ_BUFFER.get())) { + return PluginManifest.CODEC.decodeJson(reader, new ExtraInfo()); + } + } +} From 13c1a0c6f1e299af001d6a8bcb68820a55b73e02 Mon Sep 17 00:00:00 2001 From: Blovien Date: Thu, 25 Jun 2026 13:53:24 +0200 Subject: [PATCH 534/534] chore(control): marked for deprecation Signed-off-by: Blovien --- .../builtin/control/ImpulseControlPlugin.java | 1 + .../control/ImpulseControlManifestTest.java | 31 ------------------- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java diff --git a/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java index 6d5f7536..07fd4247 100644 --- a/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java +++ b/impulse-builtins/control/src/main/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlPlugin.java @@ -22,6 +22,7 @@ /** * Builtin plugin that enables Impulse kinematic control sessions. */ +@Deprecated public final class ImpulseControlPlugin extends JavaPlugin { @Nonnull diff --git a/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java b/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java deleted file mode 100644 index eb4b26cb..00000000 --- a/impulse-builtins/control/src/test/java/dev/hytalemodding/impulse/builtin/control/ImpulseControlManifestTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.hytalemodding.impulse.builtin.control; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import com.hypixel.hytale.codec.ExtraInfo; -import com.hypixel.hytale.codec.util.RawJsonReader; -import com.hypixel.hytale.common.plugin.PluginManifest; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; - -class ImpulseControlManifestTest { - - @Test - void controlBuiltinDoesNotDeclareStandalonePluginManifest() throws IOException { - InputStream stream = ImpulseControlManifestTest.class - .getClassLoader() - .getResourceAsStream("manifest.json"); - if (stream == null) { - return; - } - - try (InputStreamReader input = new InputStreamReader(stream, StandardCharsets.UTF_8); - RawJsonReader reader = new RawJsonReader(input, RawJsonReader.READ_BUFFER.get())) { - PluginManifest manifest = PluginManifest.CODEC.decodeJson(reader, new ExtraInfo()); - assertNotEquals("ImpulseControl", manifest.getName()); - } - } -}